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. |
|
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. |
72af832 to
cda5f74
Compare
cda5f74 to
4134725
Compare
4134725 to
96f8702
Compare
bjoernQ
left a comment
There was a problem hiding this comment.
the two examples which failed before now work
not sure if we should test a few more non-trivial examples?
|
/hil full |
|
Triggered full HIL run for #5582. Run: https://github.com/esp-rs/esp-hal/actions/runs/26630574679 Status update: ❌ HIL (full) run failed (conclusion: failure). |
|
/hil full |
|
Triggered full HIL run for #5582. Run: https://github.com/esp-rs/esp-hal/actions/runs/28029882302 Status update: HIL (full) run is still in progress or status unknown. |
4972b97 to
0d1a0c1
Compare
|
/hil full |
|
Triggered full HIL run for #5582. Run: https://github.com/esp-rs/esp-hal/actions/runs/28036050239 Status update: HIL (full) run is still in progress or status unknown. |
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.
The comment claimed the trailing pad to 0x400 was expressed with . = . + N, but no such pad exists — only the gap before DoubleExceptionVector is padded. Correct the comment to match.
The SUBALIGN(0x40) rewrite of .vectors dropped the trailing pad, leaving SIZEOF(.vectors) at 0x3c6 instead of 0x400. On ESP32-S3, esp32s3.x's .rwdata_dummy reserves a DRAM hole of SIZEOF(.vectors) to keep IRAM code off its physical DRAM mirror; under-reserving ~0x3a bytes let .data overlap the SRAM mirror of .rwtext/vectors and corrupt memory at runtime. Re-add the pad as `. = ALIGN(0x400)` (not the original bare `. = 0x400`, which is absolute in LLD but section-relative in GNU ld). vectors_seg origin is 0x400-aligned on every Xtensa chip, so ALIGN(0x400) yields a 0x400-byte .vectors identically under GNU ld and rust-lld.
- exception-esp32.x.template: ASSERT(SIZEOF(.vectors) == 0x400) restores the link-time overflow check the fixed `. = 0x40/0x80/...` offsets provided before the SUBALIGN switch; an oversized vector now fails the link instead of silently growing .vectors past its 0x400 slot. - rwtext.x: remove trailing spaces on the blank line before _rwtext_start.
89267a8 to
b8242d8
Compare
|
/hil full --chips esp32,esp32s2,esp32s3 |
|
Triggered full HIL run for #5582. Run: https://github.com/esp-rs/esp-hal/actions/runs/28169029481 Status update: HIL (full) run is still in progress or status unknown. |
The .text catch-all in `text.x` already uses a single combined `*(.literal .text .literal.* .text.*)` group so each object's literal pool stays adjacent to its code, keeping every L32R load within the 256 KB window regardless of image size. The other Xtensa sections still used the older split form (`*(.literal ...)` then `*(.text ...)`), which bunches all literals at the section start. Recombine them for consistency with `text.x`: - xtensa-lx-rt/xtensa.in.x: .text and .rwtext - esp-hal rwtext.x: .rwtext and the esp_rom_spiflash patch selectors - esp-hal rtc_fast.x / rtc_slow.x: .rtc_*.text The rwtext/rtc segments are small RAM regions that never approach the L32R window in practice, so this is correctness-by-construction rather than a fix for an observed failure. The combined form links identically under both GNU ld and LLD (both preserve input-file order within a single group); the full xtensa-lld CI matrix (hello_world, embassy_dhcp, bas_peripheral on esp32/s2/s3) links cleanly with rust-lld.
|
/hil esp32 esp32s2 esp32s3 |
|
Triggered HIL run for #5582 (chips: esp32 esp32s2 esp32s3). Run: https://github.com/esp-rs/esp-hal/actions/runs/28173502693 Status update: HIL (per-chip) run is still in progress or status unknown. |
|
S2 & S3 are green across the board, lets finally land this. |
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