LLVM Optimization
After rustc type-checks and borrow-checks your code in Mid-level Intermediate Representation (MIR), it lowers MIR into LLVM Intermediate Representation (LLVM IR) and hands it to the LLVM optimizer. Because Rust's ownership rules guarantee that &mut T references never alias any other active reference (noalias in LLVM IR) and &T references are never mutated (unless wrapped in UnsafeCell), LLVM can apply optimizations to safe Rust code that C and C++ compilers can only apply when manual restrict annotations are written.
Rust Source (`&[i32]`, `&mut [i32]`)
|
v `rustc` MIR Optimizations (Const prop, inlining, dead-branch removal)
|
v Emits LLVM IR with automatic `noalias` & `readonly` attributes!
[ LLVM Optimization Pipeline (`opt-level = 3`) ]
├── Function Inlining & SROA (Scalar Replacement of Aggregates)
├── GVN & LICM (Loop-Invariant Code Motion)
├── Loop Unrolling & SLP / Loop Auto-Vectorization (AVX2 / AVX-512 / NEON)
└── Tail-Call & Instruction Selection (Target Machine Assembly)
Key LLVM Optimization Passes Relevant to Rust
| LLVM Pass | What It Does to Rust Code | How to Help LLVM Succeed |
|---|---|---|
| SROA (Scalar Replacement of Aggregates) | Breaks temporary structs, tuples, Option<T>, and Result<T, E> apart into individual CPU registers, eliminating stack allocations completely. |
Return small structs/enums by value instead of boxing them on the heap. |
| Inlining | Replaces a function call site with the callee's body, exposing inner operations to constant folding and vectorization. | Keep hot cross-crate functions generic or mark small cross-crate helpers #[inline]. |
Alias Analysis (noalias) |
Uses Rust's guarantee that &mut T never overlaps with &U or another &mut T to hoist loads out of loops and vectorize without runtime overlap checks! |
Pass slices (&[T], &mut [T]) rather than raw pointers (*const T, *mut T), as raw pointers do not carry automatic noalias. |
| Loop Vectorization (SIMD) | Processes 4, 8, or 16 array elements per CPU instruction using 128-bit NEON or 256/512-bit AVX registers. | Use slice iterators (zip, chunks_exact) and avoid early data-dependent break exits inside numeric loops. |
| Constant Folding & Dead Code Elimination | Evaluates constant math at compile time and deletes unreachable branches. | Use const functions and exhaustive enums. |
How Rust's &mut Exclusivity Unlocks Better LLVM Codegen than C/C++
Consider adding a constant value read from *factor into every element of an output slice out:
- In C (
void scale_add(int *out, const int *factor, size_t n)),outandfactormight point to the same memory address. Therefore, the C compiler must re-load*factorfrom memory on every iteration (or emit a runtime pointer-overlap check). - In Rust (
fn scale_add(out: &mut [i32], factor: &i32)), the borrow checker proves at compile time thatoutandfactorcannot overlap!rustcmarks both pointersnoaliasin LLVM IR, allowing LLVM to load*factorinto a register once before the loop and vectorize the loop unconditionally:
pub fn add_scalar_in_place(out: &mut [u32], delta: &u32) {
// Because `out: &mut [u32]` and `delta: &u32` are guaranteed not to alias,
// LLVM hoists `*delta` into a SIMD vector register once before the loop!
for elem in out.iter_mut() {
*elem = elem.wrapping_add(*delta);
}
}
fn main() {
let mut values = [10u32, 20, 30, 40];
let delta = 5u32;
add_scalar_in_place(&mut values, &delta);
assert_eq!(values, [15, 25, 35, 45]);
println!("Vectorizable non-aliasing slice update: {values:?}");
}
Vectorizable non-aliasing slice update: [15, 25, 35, 45]
Inspecting Generated Assembly and LLVM IR (--emit)
To verify whether LLVM vectorized a loop or eliminated a bounds check, inspect the generated assembly or LLVM IR using rustc --emit or the cargo-show-asm tool (cargo install cargo-show-asm --locked):
# 1. Inspect the exact assembly generated for a specific function with `cargo-show-asm`:
cargo asm --lib my_crate::add_scalar_in_place
# 2. Emit `.ll` (LLVM IR) and `.s` (Assembly) files into target/release/deps/:
RUSTFLAGS="--emit=llvm-ir,asm -C llvm-args=-x86-asm-syntax=intel" cargo build --release