[BOLT][AArch64] Experimental call relaxation uses an incomplete output layout
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
## Summary
While working on BOLT layout correctness, I created [#218232](https://github.com/llvm/llvm-project/pull/218232), which replaces LongJmp's coarse layout estimates with a section-aware layout model. The experimental call relaxation from [#173952](https://github.com/llvm/llvm-project/pull/173952) was merged while that work was in progress.
#218232 currently applies the new model only to the non-compact-code-model LongJmp path. The same layout model should also be applicable to `relaxLocalBranches()` and `relaxCalls()`.
I tested `--relax-exp` against the 13 LongJmp layout tests from #218232. Ignoring FileCheck results and considering only the actual `llvm-bolt` exit status, 6 of 30 BOLT invocations failed. Variants of the same test inputs expose four additional failures, giving 10 failing configurations in total.
All failures ultimately involve an out-of-range PC-relative fixup. Depending on where the fixup is resolved, this is reported as either:
```text
:0: error: fixup value out of range
BOLT-ERROR: Emission failed.
```
or:
```text
BOLT-ERROR: JITLink failed: ... is out of range of Branch26PCRel fixup ...
```
MC and JITLink are only detecting the invalid final displacement. The underlying problem is that relaxation used a layout estimate that did not
match the emitted layout, so the necessary thunk was not created or was placed incorrectly.
## Missing or incomplete layout inputs
The experimental layout calculation does not fully model:
- Bytes emitted by `--pad-funcs`, `--pad-funcs-before`, `--break-funcs` and
`--mark-funcs`.
- Section placement and alignment caused by `--hot-text` and `--hugify`.
- Section alignment due to the individual alignment of its functions and their basic blocks.
- Complete placement for `--hot-functions-at-end` and `--use-old-text`.
- Call-site clustering and thunk placement do not distinguish main and split fragments.
- Constant islands and injected, fixed, ignored and skipped functions.
- Actual output-section ordering, alignment gaps and the hot/cold frontier.
## Failures from the #218232 tests
### Function padding
All four invocations from [`function-padding.s`](https://github.com/adam-bzowski-arm/llvm-project/blob/d50e7053b2b485b10e7304d2aea1dc86c1b4074f/bolt/test/AArch64/LongJmp-Layout/function-padding.s#L11-L22) fail with `--relax-exp`:
```bash
llvm-bolt --relax-exp %t.exe -o %t.after.bolt \
--data %t.fdata --lite=0 \
--pad-funcs=foo:134217728
llvm-bolt --relax-exp %t.exe -o %t.before.bolt \
--data %t.fdata --lite=0 \
--pad-funcs-before=target:134217728
llvm-bolt --relax-exp %t.exe -o %t.split-before.bolt \
--data %t.fdata --lite=0 \
--split-functions --split-all-cold \
--pad-funcs-before=target:134217728
llvm-bolt --relax-exp %t.exe -o %t.split-after.bolt \
--data %t.fdata --lite=0 \
--split-functions --split-all-cold \
--pad-funcs=foo:134217728
```
The unsplit cases fail in JITLink with an out-of-range `Branch26PCRel`. The split cases fail during MC emission with `fixup value out of range`.
### Ignored function at the hot/cold frontier
The invocation from [`skip-function-frontier.s`](https://github.com/adam-bzowski-arm/llvm-project/blob/d50e7053b2b485b10e7304d2aea1dc86c1b4074f/bolt/test/AArch64/LongJmp-Layout/skip-function-frontier.s#L31-L35) fails:
```bash
llvm-bolt --relax-exp %t.exe -o %t.bolt \
--data %t.fdata --lite=0 \
--reorder-functions=exec-count \
--split-functions --split-all-cold \
--skip-funcs=skipped --force-patch \
--pad-funcs-before=separator:134217728
```
BOLT reports that it created one long thunk, but emission subsequently fails with `fixup value out of range`.
### Skipped function
The relaxed invocation from [`skip-function.s`](https://github.com/adam-bzowski-arm/llvm-project/blob/d50e7053b2b485b10e7304d2aea1dc86c1b4074f/bolt/test/AArch64/LongJmp-Layout/skip-function.s#L18-L25) fails:
```bash
llvm-bolt --relax-exp %t.exe -o %t.relaxed.bolt --lite=0 \
--pad-funcs-before=end:134217716
```
It fails with `fixup value out of range`. The corresponding variant containing `--skip-funcs=target` succeeds.
## Additional variants
### Function boundary markers
`--break-funcs` and `--mark-funcs` add bytes that are absent from the experimental estimate. They are exercised by [`basic.s`](https://github.com/adam-bzowski-arm/llvm-project/blob/d50e7053b2b485b10e7304d2aea1dc86c1b4074f/bolt/test/AArch64/LongJmp-Layout/basic.s#L24-L29).
Near the branch limit, these variants fail:
```bash
llvm-bolt --relax-exp %t.exe -o %t.mark.bolt --lite=0 \
--pad-funcs=_start:134217716 --mark-funcs
llvm-bolt --relax-exp %t.exe -o %t.break.bolt --lite=0 \
--pad-funcs=_start:134217712 --break-funcs=middle
```
Both report:
```text
:0: error: fixup value out of range
BOLT-ERROR: Emission failed.
```
The corresponding commands without `--mark-funcs` or `--break-funcs` succeed.
### Hugify alignment
This is a separate standalone reproducer rather than a modification of `LongJmp-Layout/hugify.s`. The additional huge-page placement and alignment introduced by `--hugify` is not taken into account by `relaxCalls()`. The baseline succeeds, whereas the `--hugify` invocation fails with an out-of-range `Branch26PCRel`:
```asm
.text
.globl prefix
.type prefix, %function
prefix:
ret
.space 0x100000
.size prefix, .-prefix
.globl _start
.type _start, %function
_start:
bl target
ret
.size _start, .-_start
.globl target
.type target, %function
target:
ret
.size target, .-target
.reloc 0, R_AARCH64_NONE
```
Profile:
```text
no_lbr
1 prefix 0 200
1 _start 0 100
```
Command:
```bash
llvm-bolt --relax-exp %t.exe -o %t.bolt \
--data %t.fdata --lite=0 \
--reorder-functions=exec-count \
--pad-funcs=_start:132120576 --hugify
```
The same command without `--hugify` succeeds.
### Split fragments across multiple clusters
`relaxCalls()` assigns the entire `BinaryFunction` to one cluster, aggregates calls from its main and cold fragments, redirects them through the same per-cluster thunk, and places that thunk in the cluster's main code section. With split functions, a call originating in `.text.cold` can therefore be redirected to a thunk in `.text` that is itself outside the call's 128 MiB range.
The following input creates three approximately 112 MiB functions. Input order keeps the call from `a` to `c` in range, while profile order creates three hot clusters in the order `a`, `b`, `c`. The cold fall-through fragment of `a` calls `c`.
```asm
.text
.globl c
.type c, %function
c:
ret
.space 0x7000000
.size c, .-c
.globl a
.type a, %function
a:
.entry_a:
cbnz x0, .hot_a
bl c
ret
.hot_a:
ret
.space 0x7000000
.size a, .-a
.globl b
.type b, %function
b:
ret
.space 0x7000000
.size b, .-b
.globl _start
.type _start, %function
_start:
ret
.size _start, .-_start
.reloc 0, R_AARCH64_NONE
```
Profile:
```text
no_lbr
1 c 0 100
1 a 0 300
1 a c 300
1 b 0 200
```
Command:
```bash
llvm-bolt --relax-exp input.exe -o output.bolt --lite=0 \
--data input.fdata --reorder-functions=exec-count \
--split-functions --split-all-cold
```
Experimental relaxation reports three clusters and creates one long thunk, but JITLink subsequently reports an out-of-range `Branch26PCRel`.
## Analysis
`relaxCalls()` estimates each function using approximately:
```text
BF.estimateSize()
+ worst-case function alignment
+ split-fragment alignment
+ constant-island size and alignment
```
This produces a single scalar size for each function. It does not describe where the function's fragments are emitted or account for every byte and
alignment gap inserted by the emitter. Branch reachability depends on actual addresses after:
- assigning fragments to output sections;
- ordering those sections;
- applying section and fragment alignment;
- inserting explicit function padding and markers;
- placing split fragments, constant islands and injected functions; and
- allocating sections forward or backward around old text.
Adding further special cases to `estimateFunctionSize()` is unlikely to make it a reliable representation of the emitted layout.
Contributor guide
Research direction
Compare relaxCalls() and relaxLocalBranches() with the section-aware layout model from #218232, then run the cited LongJmp-Layout cases: function-padding.s, skip-function-frontier.s, skip-function.s, and basic.s. Done means the experimental relaxation models the emitted layout across these configurations without out-of-range fixups or emission failures.
Written by the indexing model from the issue text.
Assessment
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100