Skip to main content
Code generation is the final phase of compilation where MIR (Mid-level Intermediate Representation) is translated into executable machine code. The Rust compiler supports multiple codegen backends, with LLVM being the primary and most mature option.

Codegen Architecture

The compiler’s codegen infrastructure is split into backend-agnostic and backend-specific components:
The rustc_codegen_ssa crate provides backend-agnostic code generation infrastructure, defining traits and common functionality that all codegen backends must implement.

Backend-Agnostic Layer (rustc_codegen_ssa)

This crate defines the interface that all backends must implement:
pub trait CodegenBackend {
    /// Compile a single codegen unit
    fn compile_codegen_unit(
        &self,
        tcx: TyCtxt<'_>,
        cgu_name: Symbol,
    ) -> (ModuleCodegen<Self::Module>, u64);
    
    /// Create a target machine factory
    fn target_machine_factory(
        &self,
        sess: &Session,
        optlvl: OptLevel,
        target_features: &[String],
    ) -> TargetMachineFactoryFn<Self>;
    
    /// Join compiled modules
    fn join_codegen(
        &self,
        ongoing_codegen: Box<dyn Any>,
        sess: &Session,
        outputs: &OutputFilenames,
    ) -> Result<(CompiledModules, ProfQueriesMsg), ErrorGuaranteed>;
    
    // ... more methods
}

Available Backends

1

LLVM (rustc_codegen_llvm)

The default and most mature backend. Produces highly optimized code for many platforms.
2

Cranelift (rustc_codegen_cranelift)

A fast code generator focused on compilation speed rather than runtime performance. Useful for debug builds.
3

GCC (rustc_codegen_gcc)

An experimental backend using GCC as the code generator, enabling Rust on platforms GCC supports.

LLVM Backend

The rustc_codegen_llvm crate implements code generation using LLVM:
#[derive(Clone)]
pub struct LlvmCodegenBackend(());

impl ExtraBackendMethods for LlvmCodegenBackend {
    fn compile_codegen_unit(
        &self,
        tcx: TyCtxt<'_>,
        cgu_name: Symbol,
    ) -> (ModuleCodegen<ModuleLlvm>, u64) {
        base::compile_codegen_unit(tcx, cgu_name)
    }
    
    fn target_machine_factory(
        &self,
        sess: &Session,
        optlvl: OptLevel,
        target_features: &[String],
    ) -> TargetMachineFactoryFn<Self> {
        back::write::target_machine_factory(sess, optlvl, target_features)
    }
    
    // ... more implementations
}

LLVM IR Generation

MIR is translated to LLVM IR through several steps:
Generic functions are instantiated with concrete types. The rustc_monomorphize crate handles this, creating separate versions of generic functions for each concrete type combination used.
Each MIR basic block is translated to LLVM basic blocks, and MIR statements/terminators become LLVM instructions.
Rust types are mapped to LLVM types. This includes handling representation optimizations like niche filling for enums.
The abi module handles calling conventions and argument passing according to platform ABIs.

Codegen Units

Code is divided into compilation units (CGUs) for parallel compilation:
pub fn compile_codegen_unit(
    tcx: TyCtxt<'_>,
    cgu_name: Symbol,
) -> (ModuleCodegen<ModuleLlvm>, u64) {
    // Compile a single codegen unit:
    // 1. Create LLVM module
    // 2. Generate code for all items in the CGU
    // 3. Run LLVM optimization passes
    // 4. Return compiled module
}
Codegen units are compiled in parallel to speed up compilation. The number of CGUs can be controlled with the -C codegen-units=N flag.

Translation Process

Here’s how MIR translates to LLVM IR:

Basic Blocks

// MIR basic block:
bb0: {
    _3 = Add(_1, _2);
    _0 = _3;
    return;
}

// Becomes LLVM IR:
// bb0:
//   %3 = add i32 %1, %2
//   store i32 %3, i32* %0
//   ret i32 %3

Function Calls

// MIR call:
Call {
    func: const foo,
    args: [_1, _2],
    destination: _0,
    target: Some(bb1),
}

// Becomes LLVM IR:
// %result = call i32 @foo(i32 %1, i32 %2)
// store i32 %result, i32* %0
// br label %bb1

Control Flow

// MIR switch:
switchInt(_1) -> [0: bb1, 1: bb2, otherwise: bb3]

// Becomes LLVM IR:
// switch i32 %1, label %bb3 [
//   i32 0, label %bb1
//   i32 1, label %bb2
// ]

Type Translation

Rust types are mapped to LLVM types:

Integers

i32i32, u64i64, etc.

Pointers

References and raw pointers → LLVM pointers

Structs

Structs → LLVM struct types with appropriate layout

Enums

Enums → Tagged unions or optimized representations

Enum Optimizations

The compiler applies sophisticated optimizations to enums:
// Option<&T> has the same size as &T!
// Null pointer represents None, non-null represents Some
enum Option<T> {
    None,
    Some(T),
}

// In LLVM: Just a pointer, using null as discriminant
// %Option = type { i8* }

Optimization Levels

LLVM optimization is controlled by the -C opt-level flag:
1

-C opt-level=0

No optimizations. Fast compilation, slow runtime. Used for debug builds.
2

-C opt-level=1

Basic optimizations. Reasonable compromise between compile time and runtime.
3

-C opt-level=2

Default for release builds. Extensive optimizations without excessive compile time.
4

-C opt-level=3

Maximum optimization. May increase compile time significantly.
5

-C opt-level=s

Optimize for size instead of speed.
6

-C opt-level=z

Aggressively optimize for size.
LTO performs whole-program optimization:
# Enable thin LTO (faster, less memory)
rustc -C lto=thin main.rs

# Enable fat LTO (slower, more optimization)
rustc -C lto=fat main.rs
Thin LTO provides most of the benefits of LTO with significantly reduced compilation time and memory usage compared to fat LTO.

LTO Modes

Each codegen unit is optimized independently. Fast compilation but less optimization.
LLVM performs lightweight cross-module optimization. Good balance of compile time and runtime performance.
Full program optimization across all codegen units. Slowest compilation but maximum optimization.

Debugging Information

The compiler can emit debugging information for debuggers:
// The debuginfo module handles:
// - DWARF debug info generation
// - Source location mapping
// - Variable name preservation
// - Type information for debuggers
# Control debug info level
rustc -C debuginfo=0  # No debug info
rustc -C debuginfo=1  # Line tables only
rustc -C debuginfo=2  # Full debug info (default for dev)

Target Selection

The compiler supports many target platforms:
# List all targets
rustc --print target-list

# Compile for a specific target
rustc --target x86_64-unknown-linux-gnu main.rs
rustc --target wasm32-unknown-unknown lib.rs
rustc --target aarch64-apple-darwin main.rs
Target specifications are defined in rustc_target and include:
  • Architecture (x86_64, aarch64, wasm32, etc.)
  • Operating system (linux, windows, macos, none)
  • ABI and calling conventions
  • Supported features and optimizations

Inline Assembly

The compiler supports inline assembly via the asm! macro:
use std::arch::asm;

let result: u64;
unsafe {
    asm!(
        "mov {}, 5",
        out(reg) result,
    );
}
The asm module in rustc_codegen_llvm handles translating inline assembly to LLVM inline asm.

Intrinsics

Compiler intrinsics provide access to low-level operations:
// Intrinsics are special functions handled by the compiler:
// - size_of::<T>()
// - transmute::<T, U>()
// - atomic operations
// - SIMD operations
// - Platform-specific operations
The intrinsic module maps Rust intrinsics to LLVM intrinsics or custom code.

Codegen Workflow

The complete codegen workflow:
1

Collect Items

Determine which items (functions, statics, etc.) need to be codegen’d
2

Partition CGUs

Divide items into codegen units for parallel compilation
3

Monomorphization

Instantiate generic functions with concrete types
4

Generate LLVM IR

Translate MIR to LLVM IR for each codegen unit
5

Optimize

Run LLVM optimization passes
6

Emit Object Files

Generate machine code and write object files
7

Link

Link object files into final executable or library

Performance Considerations

Codegen Units

More CGUs = faster compilation but less optimization. Fewer CGUs = better optimization but slower compilation.

LTO

Enable for release builds when maximum performance is needed, despite longer compile times.

Target CPU

Use -C target-cpu=native to optimize for your specific CPU.

Profile-Guided Optimization

Use PGO to optimize based on actual runtime behavior.
# Codegen options
rustc -C help  # List all codegen options

# Common flags:
-C opt-level=2           # Optimization level
-C lto=thin             # Link-time optimization
-C codegen-units=16     # Number of codegen units
-C target-cpu=native    # Optimize for native CPU
-C debuginfo=2          # Debug info level
-C panic=abort          # Panic strategy

Next Steps

MIR

Learn about the MIR that feeds code generation

Incremental Compilation

Understand how the compiler optimizes rebuilds

Build docs developers (and LLMs) love