Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

24.10 Inspecting Generated Assembly

For developers transitioning from C or C++ to Rust, understanding the machine code generated by the Rust compiler can be particularly insightful. It allows you to verify how Rust’s high-level abstractions translate to low-level instructions, often demonstrating their “zero-cost” nature. This can also be useful when diagnosing highly specific performance issues or simply satisfying curiosity about the compiler’s behavior.

There are primarily two powerful tools at your disposal for inspecting the generated assembly: the online Compiler Explorer and the local cargo-show-asm utility.

Using Compiler Explorer (Online Tool)

A widely-used online tool for inspecting generated assembly is Compiler Explorer, created by Matt Godbolt, and accessible at https://godbolt.org/. This interactive tool supports Rust (among many other languages) and allows you to write Rust code and instantly see the corresponding assembly output generated by various versions of the Rust compiler for different target architectures. You can also see the output with different optimization levels.

How It’s Useful for Rust Programmers:

  • Demystifying Abstractions: See how features like iterators, closures, Option<T>, Result<T, E>, and trait objects compile down. For example, you can observe how an iterator chain often compiles to the same efficient loop as a manually written C-style loop.
  • Comparing with C/C++: Directly compare the assembly output of similar Rust and C/C++ snippets side-by-side.
  • Learning about Optimizations: Observe the effects of different optimization flags (e.g., -O, -C target-cpu=native) on the generated code.
  • Quick Exploration: Ideal for quick tests or sharing code snippets and their assembly output.

Using cargo-show-asm (Local Tool)

For local inspection of your project’s compiled output, the cargo-show-asm subcommand is an invaluable tool. It allows you to view the assembly, LLVM IR (Intermediate Representation), MIR (Mid-level Intermediate Representation), and even WebAssembly (Wasm) generated by rustc directly from your terminal.

Installation:

To use cargo-show-asm, you first need to install it:

cargo install cargo-show-asm

How It’s Useful for Rust Programmers:

  • Local Analysis: Inspect the assembly for your specific project’s code, including dependencies.
  • Deep Dives: Provides access to various intermediate representations (LLVM IR, MIR), offering deeper insights into the compilation process.
  • Integration with Build Process: Can be integrated into local development workflows for performance profiling and optimization.
  • Detailed Output: Offers options to filter output, demangle symbols, and control the assembly syntax.

Basic Usage:

To analyze the assembly code for your Rust project, you can use

cargo show-asm

to get a list of all the functions, and then display the assembly with:

cargo show-asm <function_name>

For example, if you have a function named my_function in your crate, you can run:

cargo show-asm my_function

You can also specify modules or types. Use cargo show-asm --help for more options, including viewing LLVM IR (--llvm-ir) or MIR (--mir).

Note that cargo show-asm shows by default the assembly code that results from a build with --release.

Example Exploration: Summing a Slice

Consider comparing the assembly for these two functions, which sum the elements of a slice:

// Example to explore with Compiler Explorer or cargo-show-asm
#[no_mangle]
pub fn sum_with_iterator(slice: &[i32]) -> i32 {
    slice.iter().sum()
}

#[no_mangle]
pub fn sum_with_loop(slice: &[i32]) -> i32 {
    let mut sum = 0;
    for i in 0..slice.len() {
        sum += slice[i];
    }
    sum
}

By pasting this code into Compiler Explorer and selecting a Rust compiler (e.g., a recent stable version with optimizations enabled like -O), you can examine the generated assembly for both functions. Alternatively, you can save this code in a local Rust project (e.g., in src/lib.rs) and run cargo show-asm sum_with_iterator and cargo show-asm sum_with_loop (ensuring your Cargo.toml is configured for a library or a binary with the functions accessible).

You’ll often find that sum_with_iterator produces code that is just as efficient as, or even identical to, sum_with_loop, showcasing Rust’s ability to optimize high-level patterns effectively without manual intervention. Adding the #[no_mangle] attribute to Rust functions helps preserve their original names in the assembly output, making them easier to recognize.

While a deep dive into assembly language is beyond this chapter’s scope, both Compiler Explorer and cargo-show-asm offer accessible ways to “look under the hood.” For systems programmers, these tools can be invaluable resources for building a deeper understanding and confidence in Rust’s performance characteristics and code generation strategies.