authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-08-01 11:29:32+02:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2026-08-01 15:20:07+02:00
log2085096f50fe235e9b6ddff8abc04daa4e5e7667
treedc09832f0bcbd69d28adf59c72c224f49c614032
parent5245c13a8a46520127600845f1c9ab5bca42ded5
signaturebadge-check Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

std.Thread: fix freeAndExit() on sparc[64]-linux hardware

On SPARC, the kernel needs to be able to restore the current register window from the stack when returning from a syscall. That presents a bit of a problem in freeAndExit() since we're deallocating the stack! The good news is that, since we do not care about the contents of the incoming and local registers at that point, we can just tell the kernel that our stack is an undefined global buffer. This didn't manifest in QEMU but does reproduce deterministically on a real kernel/machine.

1 files changed, 32 insertions(+), 37 deletions(-)

lib/std/Thread.zig+32-37
......@@ -1146,6 +1146,13 @@ const LinuxThreadImpl = struct {
11461146 parent_tid: i32 = undefined,
11471147 mapped: []align(std.heap.page_size_min) u8,
11481148
1149 // On SPARC, the kernel needs to be able to restore the current register window from the
1150 // stack when returning from a syscall. That presents a bit of a problem in `freeAndExit`
1151 // since we're deallocating the stack! The good news is that, since we do not care about
1152 // the contents of the incoming and local registers at that point, we can just tell the
1153 // kernel that our stack is this undefined global buffer.
1154 var sparc_exit_stack: [192]u8 align(16) = undefined;
1155
11491156 /// Calls `munmap(mapped.ptr, mapped.len)` then `exit(1)` without touching the stack (which lives in `mapped.ptr`).
11501157 /// Ported over from musl libc's pthread detached implementation:
11511158 /// https://github.com/ifduyue/musl/search?q=__unmapself
......@@ -1365,51 +1372,39 @@ const LinuxThreadImpl = struct {
13651372 [len] "{r5}" (self.mapped.len),
13661373 ),
13671374 .sparc => asm volatile (
1368 \\ # See sparc64 comments below.
1369 \\ 1:
1370 \\ cmp %%fp, 0
1371 \\ beq 2f
1372 \\ nop
1373 \\ ba 1b
1374 \\ restore
1375 \\ 2:
1376 \\ mov %%g1, %%o0 // ptr
1377 \\ mov %%g2, %%o1 // len
1378 \\ mov 73, %%g1 // SYS_munmap
1379 \\ t 0x3 // ST_FLUSH_WINDOWS
1380 \\ t 0x10
1381 \\ mov 1, %%g1 // SYS_exit
1382 \\ mov 0, %%o0
1383 \\ t 0x10
1375 \\ // See sparc64 comments below.
1376 \\ t 0x3 // ST_FLUSH_WINDOWS
1377 \\ mov %%g3, %%sp
1378 \\ mov %%g1, %%o0
1379 \\ mov %%g2, %%o1
1380 \\ mov 73, %%g1 // SYS_munmap
1381 \\ t 0x10
1382 \\ mov 1, %%g1 // SYS_exit
1383 \\ mov 0, %%o0
1384 \\ t 0x10
13841385 :
13851386 : [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)),
13861387 [len] "{g2}" (self.mapped.len),
1388 [stack] "{g3}" (&sparc_exit_stack),
13871389 : .{ .memory = true }),
13881390 .sparc64 => asm volatile (
1389 \\ # SPARCs really don't like it when active stack frames
1390 \\ # is unmapped (it will result in a segfault), so we
1391 \\ # force-deactivate it by running `restore` until
1392 \\ # all frames are cleared.
1393 \\ 1:
1394 \\ cmp %%fp, 0
1395 \\ beq 2f
1396 \\ nop
1397 \\ ba 1b
1398 \\ restore
1399 \\ 2:
1400 \\ mov %%g1, %%o0 // ptr
1401 \\ mov %%g2, %%o1 // len
1402 \\ mov 73, %%g1 // SYS_munmap
1403 \\ # Flush register window contents to prevent background
1404 \\ # memory access before unmapping the stack.
1405 \\ flushw
1406 \\ t 0x6d
1407 \\ mov 1, %%g1 // SYS_exit
1408 \\ mov 0, %%o0
1409 \\ t 0x6d
1391 \\ // Ensure that the kernel only has to flush the current register window.
1392 \\ flushw
1393 \\ // Set up a fake stack for the syscall to restore l/i registers from. Local
1394 \\ // and incoming registers must be treated as effectively garbage past this
1395 \\ // instruction!
1396 \\ sub %%g3, 2047, %%sp
1397 \\ mov %%g1, %%o0
1398 \\ mov %%g2, %%o1
1399 \\ mov 73, %%g1 // SYS_munmap
1400 \\ t 0x6d
1401 \\ mov 1, %%g1 // SYS_exit
1402 \\ mov 0, %%o0
1403 \\ t 0x6d
14101404 :
14111405 : [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)),
14121406 [len] "{g2}" (self.mapped.len),
1407 [stack] "{g3}" (&sparc_exit_stack),
14131408 : .{ .memory = true }),
14141409 .loongarch32, .loongarch64 => asm volatile (
14151410 \\ ori $a7, $zero, 215 # SYS_munmap