Make Xtensa linker scripts compatible with rust-lld#5582
Conversation
There was a problem hiding this comment.
Pull request overview
Makes the Xtensa linker scripts work with both GNU ld and rust-lld by removing semantics that differ between the two: bare . = <const> assignments inside output sections (interpreted as section-relative by GNU ld but absolute by LLD) and combined .literal .text patterns whose ordering depended on GNU ld's pattern-position sorting.
Changes:
- Rewrite
.vectorsto useSUBALIGN(0x40)and relative. = . + 0x40padding so vector slots fall at architecturally fixed offsets under both linkers. - Split combined
*(.literal .text ...)patterns into separate literal-first / text-after clauses so Xtensa L32R (literals must precede code) is satisfied regardless of pattern-position vs input-order sorting. - Add per-archive selectors for GCC-built blobs (wifi/phy/bt) in
text.xso each archive's per-function literal/text pairs stay grouped together and within the 256 KB L32R reach.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| xtensa-lx-rt/exception-esp32.x.template | Replace absolute . = 0xNN slot assignments with SUBALIGN(0x40) + . = . + 0x40 gap before DoubleExceptionVector. |
| xtensa-lx-rt/xtensa.in.x | Split combined .literal .text patterns in .text and .rwtext into literal-first/text-after clauses. |
| esp-hal/ld/sections/text.x | Add per-archive selectors for GCC blobs (wifi/phy/bt); split catch-all into separate literal/text clauses. |
| esp-hal/ld/sections/rwtext.x | Split .rwtext literal/text patterns; also split the esp_rom_spiflash.* selector. |
| esp-hal/ld/sections/rtc_slow.x | Split .rtc_slow literal/text patterns. |
| esp-hal/ld/sections/rtc_fast.x | Split .rtc_fast literal/text patterns. |
| SECTIONS { | ||
|
|
||
| .vectors : | ||
| .vectors : SUBALIGN(0x40) |
There was a problem hiding this comment.
Literally would have never found this by myself, makes what we want to do here a lot easier!
|
CI looks like we don't get away with only this much |
Working on it :D |
|
Whilst CI passes, I think we should do some more thorough testing (mostly with GCC) to ensure this doesn't break tonnes of stuff. |
cc8ddb4 to
4f6d6f3
Compare
|
/hil full |
|
Triggered full HIL run for #5582. Run: https://github.com/esp-rs/esp-hal/actions/runs/26630574679 Status update: HIL (full) run is still in progress or status unknown. |
|
New commits in main have made this PR unmergeable. Please resolve the conflicts. |
GNU ld and LLD interpret bare `. = <const>` assignments inside an output section differently — GNU ld treats them as section-relative offsets, LLD treats them as absolute addresses. The `.vectors` section in the exception template relied on the section-relative behaviour, and the combined `*(.literal .text .literal.* .text.*)` patterns relied on GNU ld's pattern-order section sorting, which LLD does not perform. Rework `.vectors` to use SUBALIGN(0x40) plus relative `. = . + N` padding so the vector slot positions are derived from input section sizes rather than absolute assignments, and split the combined literal/text patterns into separate clauses so literals are always emitted before the code that references them (required because Xtensa L32R can only address literals at negative PC offsets). The resulting binaries flash to the same load addresses and produce the same number of esptool segments under both linkers.
With the previous split `*(.literal .literal.*) *(.text .text.*)`, every literal pool across every archive was emitted before any code, so once the wifi/BT blobs were linked the per-function `.literal.<f>` pools ended up >256 KB from their own `.text.<f>` — past the L32R PC-relative range — and the link failed with `R_XTENSA_SLOT0_OP out of range`. Pull each Espressif archive in as its own block using the combined `(.literal .text .literal.* .text.*)` pattern. Both GNU ld and LLD preserve input-file order within a single pattern group, so GCC's per-function literal/text pairs stay adjacent and every L32R inside an archive resolves regardless of total image size. The catch-all keeps the split form because rustc's Xtensa backend emits its `.literal` after the function `.text.*` sections in each object. LLD- and GCC-linked images of a minimal embassy_net + esp-radio app are now byte-structure identical (same 6 esptool segments, same load addresses, same sizes).
The split catch-all bunched all literals at the start of .text, pushing late code past the 256 KB L32R window in large (radio) images. Use a combined `*(...)` group so each function's literal pool stays adjacent to its code, and pull the boot code's bare `.literal` pool and its two consumers (`__pre_init` / `__post_init`) to the front so their L32R loads stay in reach regardless of image size.
72af832 to
cda5f74
Compare
Pure claude work here, but verified by building various examples. The rest of this PR body is claude, but interesting if you want to understand the changes.
GNU ld and LLD interpret bare
. = <const>assignments inside an outputsection differently — GNU ld treats them as section-relative offsets,
LLD treats them as absolute addresses. The
.vectorssection in theexception template relied on the section-relative behaviour, and the
combined
*(.literal .text .literal.* .text.*)patterns relied on GNUld's pattern-order section sorting, which LLD does not perform.
Rework
.vectorsto use SUBALIGN(0x40) plus relative. = . + Npadding so the vector slot positions are derived from input section
sizes rather than absolute assignments, and split the combined
literal/text patterns into separate clauses so literals are always
emitted before the code that references them (required because Xtensa
L32R can only address literals at negative PC offsets).
Changelog
esp-hal
xtensa-lx-rt