[Xtensa] Missing .literal section emission causes l32r misaligned literal target link errors
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
This issue was isolated and reduced by Claude, but I had trouble creating a solid reproducible demonstration.
I hope this is clear, and the issue is identifiable.
## Summary
The upstream LLVM Xtensa backend embeds literal pool data inline within `.text` sections instead of emitting separate `.literal` sections. When per-function sections are used (e.g. `-ffunction-sections`), functions with code sizes that are not multiples of 4 bytes cause subsequent inline literal data to land at non-4-byte-aligned offsets after the linker concatenates sections. The Xtensa `l32r` instruction requires its literal target to be 4-byte aligned, so GNU ld rejects the link with "dangerous relocation: l32r: misaligned literal target".
Espressif's LLVM fork (esp-clang 20.1.1) correctly emits separate `.literal.funcname` sections with proper 4-byte alignment. The upstream LLVM 21 backend is missing this literal section splitting.
## Reproducer
The following LLVM IR, when compiled with the upstream Xtensa backend and linked with GNU ld, triggers the error:
```llvm
; xtensa_literal_bug.ll
target datalayout = "e-m:e-p:32:32-i64:64-i128:128-n32"
target triple = "xtensa-none-elf"
define i32 @func_a(i32 %x) #0 {
%1 = alloca i32, align 4
store i32 %x, ptr %1, align 4
%2 = load i32, ptr %1, align 4
%3 = icmp sgt i32 %2, 0
br i1 %3, label %then, label %else
then:
%4 = load i32, ptr %1, align 4
%5 = add i32 %4, 1
ret i32 %5
else:
%6 = load i32, ptr %1, align 4
%7 = sub i32 %6, 1
ret i32 %7
}
define i32 @func_b() #0 {
%fp = alloca ptr, align 4
store ptr @func_a, ptr %fp, align 4
%1 = load ptr, ptr %fp, align 4
%2 = call i32 %1(i32 42)
ret i32 %2
}
attributes #0 = { "frame-pointer"="all" "target-cpu"="generic" "target-features"="+density,+threadptr,+windowed,+mul16,+mul32,+mul32high,+div32,+sext,+nsa,+clamps,+minmax,+bool,+fp,+loop" }
```
```bash
# Compile IR to object (using llc from LLVM 21):
llc -mtriple=xtensa-none-elf -function-sections -o xtensa_literal_bug.o xtensa_literal_bug.ll
# Inspect: no .literal sections, only .text sections
readelf -S xtensa_literal_bug.o | grep -E 'text\.|literal'
# .text.func_a PROGBITS size=0x26 (38 bytes -- NOT 4-byte aligned)
# .text.func_b PROGBITS size=0x22 (34 bytes)
# No .literal.func_a or .literal.func_b sections
# Link:
xtensa-esp32s3-elf-gcc -nostdlib -e func_b xtensa_literal_bug.o -o test
# Error: dangerous relocation: l32r: misaligned literal target: (.text.func_a+0x22)
```
### Verified output
Object compiled from the above IR via LDC (which uses stock LLVM 21.1.8):
```
$ readelf -S repro.o | grep -E 'text\.|literal'
[ 3] .text.func_a PROGBITS 00000000 000034 000026 00 AX 0 0 4
[ 5] .text.func_b PROGBITS 00000000 00005c 000022 00 AX 0 0 4
# Note: NO .literal sections at all
$ xtensa-esp32s3-elf-gcc -nostdlib -e func_b repro.o -o test
(.text.func_b+0x6): dangerous relocation: l32r: misaligned literal target: (.text.func_a+0x22)
```
### Comparison with Espressif's fork
The same C equivalent compiled with esp-clang (Espressif LLVM 20.1.1):
```
$ readelf -S espclang.o | grep -E 'text\.|literal'
[ 3] .text.func_a PROGBITS 00000000 000034 00000b 00 AX 0 0 4
[ 4] .literal.func_b PROGBITS 00000000 000040 000004 00 AX 0 0 4
[ 6] .text.func_b PROGBITS 00000000 000044 000020 00 AX 0 0 4
# .literal.func_b is emitted separately with 4-byte alignment
$ xtensa-esp32s3-elf-gcc -nostdlib -e func_b espclang.o -o test
# Links successfully
```
## Root Cause
On Xtensa, the `l32r` instruction loads a 32-bit literal from a 4-byte-aligned address within a 256KB range before the instruction. GCC and Espressif's LLVM fork handle this by emitting literal pool entries in separate `.literal` or `.literal.funcname` sections. The Xtensa linker script convention places `.literal.funcname` immediately before `.text.funcname`, ensuring proper alignment.
The upstream LLVM Xtensa backend does not emit separate `.literal` sections. Literal pool data is embedded inline at the beginning of each `.text` function section. When the linker concatenates `.text.func_a` (38 bytes, 0x26) followed by the literal data for `func_b` (embedded at the end of `.text.func_a`), the literal data lands at offset 0x22 (34 bytes from func_a's start, within func_a's 38-byte section). Since 0x22 % 4 = 2, the `l32r` target is not 4-byte aligned.
## Expected Behavior
The LLVM Xtensa backend should emit `.literal` (or `.literal.funcname` with `-function-sections`) sections for literal pool entries, matching Espressif's fork and GCC behavior. The Espressif fork's implementation can be found in their `llvm-project` repository.
## Environment
- LLVM: 21.1.8 (stock upstream, via LDC 1.42.0) -- affected
- Espressif LLVM 20.1.1 (esp-clang/llc) -- NOT affected (emits .literal sections)
- GCC xtensa-esp-elf 15.2.0 -- NOT affected (emits .literal sections)
- Linker: GNU ld (binutils) 2.45 from xtensa-esp-elf toolchain (esp-15.2.0)
The IR was generated by LDC 1.42.0 and verified to produce the link failure. The same IR compiled with Espressif's `llc` (which has the .literal section emission patch) links successfully. To reproduce from the IR directly, use upstream `llc` from LLVM 21+ with `-mtriple=xtensa-none-elf -function-sections`.
## Workaround
No known workaround for the upstream backend. `--align-all-functions=2` does not fix this as it only affects function entry alignment, not inline literal pool placement.
Contributor guide
Research direction
Start by running the provided llc command on xtensa_literal_bug.ll, then inspect the generated sections with readelf and reproduce the GNU ld failure. Compare the upstream Xtensa backend's literal-pool emission with the Espressif fork; done means separate, aligned .literal sections are emitted under -function-sections and the reproducer links successfully.
Written by the indexing model from the issue text.
Assessment
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100