runtime: jump indirectly to longjmp on Xtensa

tinygo_longjmp ended with a j instruction, which reaches only 128 KB.
picolibc puts longjmp in the plain .text group of the linker script,
while all TinyGo code goes into the .text.* group after it. In a large
program the two are more than 128 KB apart and the link stops with an
out of range R_XTENSA_SLOT0_OP relocation. Small programs link
correctly, thus the smoke tests do not find this.

Load the address of longjmp from a literal and jump to it with jx. The
literal carries an R_XTENSA_32 relocation, which has no range limit.

Place the literal by hand in the same section as the code. Automatic
literals go into a separate section, which this tree already records as
a cause of bad l32r offsets with LLVM 22 and lld. See the comments in
src/device/esp/esp32.S and src/device/esp/esp32s3.S.

Keep the jump instead of a call. tinygo_longjmp has no entry
instruction and runs in the register window of its caller. A call would
rotate the window a second time and give longjmp bad arguments.

a8 is free in both ABIs. In the windowed ABI it becomes a0 of longjmp,
which longjmp overwrites at once from the jmp_buf. In the call0 ABI it
is a scratch register and longjmp reads only a2 and a3.

The recover test passes on ESP32 in QEMU and on ESP32-S3 hardware.
This commit is contained in:
deadprogram
2026-09-12 17:34:02 +02:00
committed by Ron Evans
parent 394884617e
commit ea3327a7cd
+8 -1
View File
@@ -21,6 +21,10 @@ __xtensa_libgcc_window_spill:
#endif
.section .text.tinygo_longjmp,"ax",@progbits
// Literal data for l32r (must be at a lower address than the l32r).
.balign 4
.Llongjmp_addr:
.word longjmp
.global tinygo_longjmp
.type tinygo_longjmp, %function
tinygo_longjmp:
@@ -30,5 +34,8 @@ tinygo_longjmp:
#else
movi a3, 1
#endif
j longjmp
// j only reaches 128 KB (Xtensa ISA Reference Manual, J instruction) and
// longjmp can be further away. a8 is free to use in both ABIs.
l32r a8, .Llongjmp_addr
jx a8
.size tinygo_longjmp, .-tinygo_longjmp