[llvm] [BOLT] Model emitted code sections in LongJmp layout (PR #218232)
Adam Bzowski via llvm-commits
llvm-commits at lists.llvm.org
Sun Aug 23 04:50:37 PDT 2026
adam-bzowski-arm wrote:
This work was motivated by processing a Chromium binary with BOLT. We observed ten layout misestimations that caused out-of-range AArch64 branch or call failures, either during MC emission or later in JITLink. In every case, LongJmp had considered a Branch26 relocation to be within its ±128 MiB range, while the final emitted layout placed it just outside that range.
This PR supersedes #215416, which proposed a smaller partial fix by excluding non-emitted functions and accounting for function padding. Further investigation showed that these corrections were necessary but insufficient: reliable Branch26 decisions require modeling the complete emitted section layout. The earlier PR will therefore be closed in favor of this comprehensive solution.
LongJmp must decide whether to insert a stub before code is emitted. The previous implementation approximated the layout by adding estimateSize(), estimateHotSize(), and estimateColdSize() for functions in an assumed hot/cold order. This is insufficient because the final address of an instruction depends on much more than the sum of its function’s instruction sizes:
- functions and fragments may be emitted into different code sections;
- sections have their own ordering and alignment;
- section alignment depends on all fragments assigned to that section;
- functions and basic blocks have bounded alignment directives;
- constant islands and their dependencies contribute alignment and bytes;
- --pad-funcs-before, --pad-funcs, --break-funcs, and --mark-funcs add bytes;
- --hot-functions-at-end, --use-old-text, --hot-text, and --hugify affect section placement;
- relocation and non-relocation modes use different mapping rules;
- fixed and non-fixed injected functions are placed differently.
The new implementation models the emitted layout section by section. It first assigns every emitted main or cold fragment to its output section and determines the alignment required by the complete section. It then orders and places sections using the same rules as RewriteInstance. Within each fragment, it follows BinaryEmitter::emitFunction() and emitFunctionBody(), including function alignment, basic-block alignment, option-controlled padding and markers, instruction bytes, and constant islands.
The implementation deliberately avoids the coarse whole-function size estimates. It still uses computeCodeSize() for the actual instruction bytes and estimateConstantIslandSize() for island contents, but surrounds those values with the same placement and alignment rules used during emission. Labels, CFI directives, debug directives and similar metadata are omitted because they do not advance the code-section address.
There is some intentional similarity with BinaryEmitter and RewriteInstance. LongJmp runs before emission, so it cannot obtain final addresses from the streamer. Introducing a shared dry-run emitter would be a substantially larger refactoring. Instead, the new code closely mirrors the relevant emission and mapping routines, with comments pointing back to the corresponding source. Shared behavior such as code-section ordering and option-controlled padding is exposed through common helpers to reduce the risk of the implementations drifting apart.
RewriteInstance::mapCodeSectionsInPlace() also required a functional adjustment. In non-relocation mode, non-fixed injected sections are now allocated explicitly and predictably immediately after moved cold fragments. Fixed-address injected patches are mapped separately at their pre-assigned addresses. Previously, injected functions were not part of getBinaryFunctions(), and non-fixed injected sections could be placed later through generic allocatable-section handling, which made their position impossible for LongJmp to reproduce using code layout alone.
Other small supporting changes include:
- sharing the code-section ordering comparator through BinaryContext;
- exposing the existing padding calculations and wrappers for --break-funcs and --mark-funcs;
- correcting dependency-island alignment estimation to match BinaryEmitter;
- using DenseMap::at() where layout entries must already exist, preventing accidental insertion of zero addresses;
- updating existing debug messages and tests to use “layout” rather than “tentative layout.”;
- sharing BinaryEmitter’s function-emission predicate with LongJmp, preventing the layout and relaxation logic from diverging when functions emit no code.
LongJmp continues to support only main and cold fragments; this patch does not attempt to introduce warm-fragment support or change that existing restriction.
The change adds targeted AArch64 LIT coverage for ordinary and split functions, section ordering and alignment, constant islands, empty functions, padding and markers, skipped functions, fixed and non-fixed injected functions, --hot-functions-at-end, --use-old-text, --hugify, and non-relocation mode. The attached coverage report shows that these tests exercise the layout implementation comprehensively, including its important alternative paths and boundary cases. The resulting model was also instrumented and compared against actual Chromium emission at section, function-fragment, and basic-block granularity.
Beyond the targeted tests, the model was validated against ten complete Chromium runs in relocation mode, covering the split-function, split-all-cold, split-EH, and hot-functions-at-end configurations. Instrumentation compared every predicted address with the address assigned during emission. Across these runs, all 20 section comparisons, 10,137,557 function-placement comparisons, and 93,776,265 basic-block comparisons matched exactly - without a single missing or mismatched address. This provides large-scale validation that the model faithfully reproduces the real BOLT emitter and section mapper.
Assisted-by: Codex
https://github.com/llvm/llvm-project/pull/218232
More information about the llvm-commits
mailing list