How Sway's Inline IR Optimization Pass Improves Performance: A Deep Dive
Sway's inline IR optimization pass improves performance by replacing function call instructions with the callee's body, eliminating call overhead and enabling downstream optimizations like constant folding and dead code elimination.
The FuelLabs/sway compiler uses an intermediate representation (IR) to perform aggressive optimizations before generating bytecode for the FuelVM. Sway's inline IR optimization pass is a module-level transformation that directly impacts execution speed and binary size by intelligently flattening function hierarchies.
What the Inline IR Optimization Pass Does
The inline pass operates on Sway's IR in sway-ir/src/optimize/inline.rs, transforming Call instructions into direct instruction sequences.
Pass Registration and Call Graph Analysis
The pass is registered as a module pass via create_fn_inline_pass (lines 24-33). Before inlining, it collects call counts for every function in the module to identify frequently invoked routines (lines 80-98).
The Inline Heuristic Decision Logic
The inline_heuristic function determines whether a specific call site should be inlined based on several criteria (lines 100-140):
- Never-inline: Functions annotated with
#[inline(never)]are always skipped - Always-inline: Functions marked
#[inline(always)]are unconditionally inlined - Single-call sites: Functions called only once are always inlined (lines 121-124)
- Size threshold: Small functions with ≤12 instructions are inlined by default (lines 126-130)
Function Call Inlining Implementation
The pass walks the call graph in callee-first order, ensuring callees are inlined before their callers (lines 135-139). For each function, inline_some_function_calls identifies candidate call sites and forwards them to inline_function_call (lines 161-170).
The inline_function_call routine (starting at line 560) performs the actual transformation:
- Splits the caller basic block at the call site
- Removes the
Callinstruction - Maps callee locals and arguments to caller values
- Copies callee instructions into the caller
- Updates block and value references
- Deletes the original call instruction
Performance Benefits of Sway's Inline IR Optimization
Eliminating function calls through Sway's inline IR optimization pass creates cascading performance improvements:
- Removes call overhead: Eliminates stack frame setup, argument passing, and return handling costs by replacing
Call+Retpairs with direct instruction execution - Enables dead code elimination: Once inlined, unused arguments and locals become visible to the DCE pass, allowing removal of unreachable code
- Improves constant propagation: Constant arguments at call sites can be folded immediately into the inlined body, reducing runtime computation
- Facilitates scalar replacement: Exposing small function internals allows the SROA pass to decompose structs and arrays into scalar registers
- Reduces instruction cache pressure: Hot small functions become contiguous instruction sequences, improving locality and reducing cache misses
- Preserves developer control: Attributes like
#[inline(always)]and#[inline(never)]allow explicit performance tuning without source modification
Practical Examples and Usage
Registering the Pass in a Pipeline
To include the inline pass in a custom optimization pipeline, use the PassManager API as demonstrated in test/src/ir_generation/mod.rs:
use sway_ir::{
create_fn_inline_pass, PassManager, PassGroup,
};
let mut pass_mgr = PassManager::default();
let mut group = PassGroup::default();
// Register the inline pass
let inline = pass_mgr.register(create_fn_inline_pass());
group.append_pass(inline);
// Run the pass on an IR module `ir`
let _ = pass_mgr.run(&mut ir, &group);
This pattern mirrors the test harness implementation (lines 16-23).
Using Inline Attributes in Sway Code
Control inlining behavior directly in Sway source using attributes parsed in sway-parse/src/item/item_fn.rs:
#[inline(always)]
fn add_one(x: u64) -> u64 {
x + 1
}
#[inline(never)]
fn debug_trace() {
// This will never be inlined, preserving call stack
}
The metadata_to_inline function (lines 43-73) extracts these attributes and converts them to IR metadata that the heuristic checks.
Before and After IR Transformation
Consider a simple function call before inlining:
// Before inline pass
fn main() -> u64 {
entry:
%0 = call @add_one(%arg0)
ret u64 %0
}
After the inline pass processes this module, the call disappears:
// After inline pass
fn main() -> u64 {
entry:
%0 = add %arg0, 1u64
ret u64 %0
}
The Call instruction and associated overhead are replaced by a single add instruction, demonstrating how the pass eliminates abstraction penalties.
Key Source Files and Implementation Details
Understanding the inline pass requires familiarity with these specific locations in the FuelLabs/sway repository:
sway-ir/src/optimize/inline.rs: Contains the complete implementation includingcreate_fn_inline_pass,inline_heuristic,inline_function_call, andmetadata_to_inline(source)sway-ir/src/pass_manager.rs: Defines thePassManagerandPassGroupAPIs used to orchestrate the inline pass within optimization pipelines (source)test/src/ir_generation/mod.rs: Demonstrates how the inline pass is enabled in tests via theoptimisation_inlineflag andPassManagerregistration (source)sway-parse/src/item/item_fn.rs: Handles parsing of#[inline(...)]attributes from Sway source code into metadata consumable by the IR pass (source)
Summary
Sway's inline IR optimization pass delivers measurable performance improvements by:
- Eliminating call overhead through direct instruction substitution in
sway-ir/src/optimize/inline.rs - Enabling cross-function optimizations like constant propagation and dead code elimination
- Applying intelligent heuristics that balance code size against speed using call counts and instruction thresholds
- Respecting developer intent via
#[inline(always)]and#[inline(never)]attributes - Processing callees before callers to maximize inlining opportunities through callee-first call graph traversal
Frequently Asked Questions
What is the default size threshold for inlining in Sway?
By default, Sway's inline heuristic inlines functions containing 12 or fewer instructions. This threshold is checked in inline_heuristic at lines 126-130 of sway-ir/src/optimize/inline.rs. Functions larger than this limit are only inlined if marked with #[inline(always)] or called exactly once.
How does Sway handle recursive function inlining?
The inline pass prevents infinite recursion by tracking the call graph and avoiding inlining cycles. When inline_function_call processes a call site, it checks whether the callee has already been inlined into the current call stack. This protection ensures that recursive functions maintain their call structure rather than causing infinite expansion during the optimization phase.
Can I disable inlining for specific functions?
Yes, Sway provides the #[inline(never)] attribute to explicitly prevent inlining. When the parser encounters this attribute in sway-parse/src/item/item_fn.rs, it stores the metadata that metadata_to_inline (lines 43-73) checks during the heuristic phase. Functions marked with this attribute will always retain their call instructions regardless of size or call frequency.
Where does the inline pass fit in Sway's compilation pipeline?
The inline pass runs as a module-level optimization after initial IR generation but before backend code generation. In the test harness (test/src/ir_generation/mod.rs lines 16-23), it is registered via create_fn_inline_pass() and executed through the PassManager alongside other optimizations. This placement ensures that inlined code can be further optimized by subsequent passes like constant propagation and dead code elimination before final bytecode emission.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →