sendilkumarn / svelte

Svelte is a code size profiler

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

svelte

Build Status

svelte is a code size profiler.

It analyzes a binary's call graph to answer questions like:

  • Why was this function included in the binary in the first place?

  • What is the retained size of this function? I.e. how much space would be saved if I removed it and all the functions that become dead code after its removal.

Use svelte to make your binaries slim!


Install

Ensure that you have the Rust toolchain installed, then run:

$ cargo install --git https://github.com/fitzgen/svelte.git

Usage

$ svelte --help

Concepts

Call Graph

Consider the following functions:

pub fn shred() {
    gnar_gnar();
    bluebird();
}

fn gnar_gnar() {
    weather_report();
    pow();
}

fn bluebird() {
    weather_report();
}

fn weather_report() {
    shred();
}

fn pow() {
    fluffy();
    soft();
}

fn fluffy() {}

fn soft() {}

pub fn baker() {
    hood();
}

fn hood() {}

If we treat every function as a vertex in a graph, and if we add an edge from A to B if function A calls function B, then we get the following call graph:

Call Graph

Paths

If there is a path where A → B → ... → C through the call graph, then we say that C is reachable through from A. Dead code is code that is not reachable in the call graph from any publicly exported functions (for libraries) or the main function (for executables).

Imagine that shred from the last example was our executable's main function. In this scenario, there is no path through the call graph from shred to baker or hood, so they are dead code. We would expect that the linker would remove them, and they wouldn't show up in the final binary.

But what if some function that you thought was dead code is appearing inside your binary? Maybe it is deep down in some library you depend on, but inside a submodule of that library that you aren't using, and you wouldn't expect it to be included in the final binary.

In this scenario, svelte can show you all the paths in the call graph that lead to the unexpected function. This lets you understand why the unwelcome function is present, and decide what you can do about it. Maybe if you refactored your code to avoid calling Y, then there wouldn't be any paths to the unwelcome function anymore, it would be dead code, and the linker would remove it.

You can use the svelte paths subcommand to view the paths to a function in a given binary's call graph:

$ svelte paths wee_alloc.wasm 'wee_alloc::alloc_first_fit::h9a72de3af77ef93f'
 Shallow Bytes │ Shallow % │ Retaining Paths
───────────────┼───────────┼───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
           225 ┊     7.99% ┊ wee_alloc::alloc_first_fit::h9a72de3af77ef93f
               ┊           ┊   ⬑ func[3]
               ┊           ┊       ⬑ wee_alloc::alloc_with_refill::hb32c1bbce9ebda8e
               ┊           ┊           ⬑ func[2]
               ┊           ┊               ⬑ <wee_alloc::size_classes::SizeClassAllocPolicy<'a> as wee_alloc::AllocPolicy>::new_cell_for_free_list::h3987e3054b8224e6
               ┊           ┊                   ⬑ func[5]
               ┊           ┊                       ⬑ elem[0]
               ┊           ┊               ⬑ hello
               ┊           ┊                   ⬑ func[8]
               ┊           ┊                       ⬑ export "hello"

Dominators and Retained Size

Imagine the pow function itself might is not very large. But it calls functions soft and fluffy, both of which are huge. And they are both only called by pow, so if pow were removed, then soft and fluffy would both become dead code and get removed as well. Therefore, pow's "real" size is huge, even though it doesn't look like it at a glance. The dominator relationship gives us a way to reason about the retained size of a function.

In a graph that is rooted at vertex R, vertex A is said to dominate vertex B if every path in the graph from R to B includes A. It follows that if A were removed from the graph, then B would become unreachable.

In our call graphs, the roots are the main function (for executables) or publicly exported functions (for libraries).

V is the immediate dominator of a vertex U if V != U, and there does not exist another distinct vertex W that is dominated by V but also dominates U. If we take all the vertices from a graph, remove the edges, and then add edges for each immediate dominator relationship, then we get a tree. Here is the dominator tree for our call graph from earlier, where shred is the root:

Dominator Tree

Using the dominator relationship, we can find the retained size of some function by taking its shallow size and adding the retained sizes of each function that it immediately dominates.

You can use the svelte dominators subcommand to view the dominator tree for a given binary's call graph:

$ svelte dominators wee_alloc.wasm
 Retained Bytes │ Retained % │ Dominator Tree
────────────────┼────────────┼────────────────────────────────────────────────────────────────────────
            774 ┊     27.48% ┊ "function names" subsection
            564 ┊     20.02% ┊ export "hello"
            556 ┊     19.74% ┊   ⤷ func[8]
            551 ┊     19.56% ┊       ⤷ hello
            387 ┊     13.74% ┊           ⤷ func[2]
            378 ┊     13.42% ┊               ⤷ wee_alloc::alloc_with_refill::hb32c1bbce9ebda8e
            226 ┊      8.02% ┊                   ⤷ func[3]
            225 ┊      7.99% ┊                       ⤷ wee_alloc::alloc_first_fit::h9a72de3af77ef93f
              8 ┊      0.28% ┊               ⤷ type[4]
              4 ┊      0.14% ┊       ⤷ type[5]
             59 ┊      2.09% ┊ export "goodbye"
             49 ┊      1.74% ┊   ⤷ func[9]
             44 ┊      1.56% ┊       ⤷ goodbye
              4 ┊      0.14% ┊       ⤷ type[3]
             11 ┊      0.39% ┊ export "memory"
              2 ┊      0.07% ┊   ⤷ memory[0]

Supported Binary Formats

  • WebAssembly's .wasm format

Although svelte doesn't currently support ELF, Mach-O, or PE/COFF, it is designed with extensibility in mind. The input is translated into a format-agnostic internal representation (IR), and adding support for new formats only requires parsing them into this IR. The vast majority of svelte will not need modification.

We would love to gain support for new binary formats, and if you're interested in doing that implementation work, check out CONTRIBUTING.md.

About

Svelte is a code size profiler


Languages

Language:Rust 100.0%