Proposal: #[likely]
/ #[unlikely]
Attributes for Branch and Layout Optimization
Summary
Rust currently provides a few coarse mechanisms to help the compiler reason about branch frequency — #[cold]
on functions, core::intrinsics::{likely, unlikely}
(nightly), and profile-guided optimization (PGO). These are useful, but they don’t scale elegantly to common patterns such as multi-arm match
statements or nested if
chains, where the programmer knows which paths are overwhelmingly dominant (“happy path”) and which are rare (“sad path”).
I’d like to explore adding lightweight, first-class attributes such as:
if #[likely] cond { ... } else #[unlikely] { ... }
match result {
Ok(v) #[likely(0)] => process(v),
Retry #[likely(1)] => retry(),
Timeout #[likely(0.05)] => backoff(),
Err(e) #[unlikely] => handle(e),
}
The goal is to let programmers express expected execution likelihood in a way the compiler can translate into existing LLVM/Cranelift metadata, improving branch prediction, code layout, cache locality, and pipeline efficiency, without affecting semantics.
Motivation
Programmers often have domain knowledge that static analysis and profiling can’t capture easily — for example:
- Error or failure paths that occur <1% of the time.
- Status enums where one or two arms dominate under normal operation.
- Control loops where the “continue” case is typical and the “break” case is exceptional.
Today we can use intrinsics or code re-ordering tricks, but these are less expressive and inconsistent across if/match constructs.
Explicit hints would help:
- Shape code layout (hot fall-through first, cold blocks out-of-line).
- Drive branch-weight metadata (llvm.expect, !prof branch_weights).
- Influence backend layout (ARM branch hint bits, x86 fall-through, etc.).
- Improve I-cache and pipeline utilization by keeping hot paths contiguous.
Semantics (sketch)
- Purely advisory; no effect on program logic.
- Accepted forms:
- #[likely] → hot path.
- #[unlikely] → cold path.
- #[likely(p)] → numeric hint (p as ordinal or probability).
- Unannotated arms/branches are neutral.
- Compiler lowers hints to MIR metadata → backend branch weights → target-specific layout and hints.
- PGO or JIT data override static hints when available.
Implementation considerations
- On LLVM backends, this maps cleanly to llvm.expect or !prof branch_weights.
- On Cranelift, could attach similar metadata to control-flow edges.
- On x86, this affects block ordering and alignment; on ARM, also sets architectural “taken/not-taken” bits.
- Can interact naturally with existing #[cold] and inlining heuristics.
- #[likely]/#[unlikely] on match arms could influence both ordering and outlining decisions.
Example
#[cold]
fn handle_error(e: Error) { log::error!("{:?}", e); }
fn process(x: i32) -> Result<i32, Error> {
if #[likely(x >= 0)] {
Ok(x + 1) // hot fall-through
} else #[unlikely] {
handle_error(Error::BadArg);
Err(Error::BadArg)
}
}
Even on x86 (no explicit hint bits), this would encourage contiguous layout of the hot path and push the error handler into a cold section. On ARM and others, actual branch-hint encodings would be emitted.
Open questions
- Should numeric weights (#[likely(0.9)]) be supported, or only binary hints?
- How would this interact with #[cold] and inlining thresholds?
- Should unannotated arms remain neutral, or implicitly less likely?
- What’s the best MIR representation (block metadata vs. edge attributes)?
- Is there a feasible path to make this work uniformly across LLVM, Cranelift, and GCC backends?
Why discuss now
LLVM and Cranelift already support branch-weight metadata. Rust developers frequently reach for core::intrinsics::likely/unlikely, but a first-class attribute syntax could make this idiomatic, stable, and portable — especially valuable for embedded and high-performance workloads where layout matters as much as prediction.
Would the language or compiler teams be open to exploring this direction, perhaps starting as an experimental -Z likely-attributes feature?
Links / background
- #[cold] attribute docs
- core::intrinsics::likely, unlikely
- LLVM llvm.expect intrinsic
- LLVM PGO and BOLT documentation
Closing
This proposal isn’t about micromanaging the predictor; it’s about giving the compiler better priors so it can produce straighter, more cache-friendly instruction streams. Feedback from the compiler and language teams on feasibility, naming, and potential pitfalls would be greatly appreciated.
Note: this markdown was generate by ChatGPT based on our discussions.