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 {...@@ -1146,6 +1146,13 @@ const LinuxThreadImpl = struct {
1146 parent_tid: i32 = undefined,1146 parent_tid: i32 = undefined,
1147 mapped: []align(std.heap.page_size_min) u8,1147 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
1149 /// Calls `munmap(mapped.ptr, mapped.len)` then `exit(1)` without touching the stack (which lives in `mapped.ptr`).1156 /// Calls `munmap(mapped.ptr, mapped.len)` then `exit(1)` without touching the stack (which lives in `mapped.ptr`).
1150 /// Ported over from musl libc's pthread detached implementation:1157 /// Ported over from musl libc's pthread detached implementation:
1151 /// https://github.com/ifduyue/musl/search?q=__unmapself1158 /// https://github.com/ifduyue/musl/search?q=__unmapself
...@@ -1365,51 +1372,39 @@ const LinuxThreadImpl = struct {...@@ -1365,51 +1372,39 @@ const LinuxThreadImpl = struct {
1365 [len] "{r5}" (self.mapped.len),1372 [len] "{r5}" (self.mapped.len),
1366 ),1373 ),
1367 .sparc => asm volatile (1374 .sparc => asm volatile (
1368 \\ # See sparc64 comments below.1375 \\ // See sparc64 comments below.
1369 \\ 1:1376 \\ t 0x3 // ST_FLUSH_WINDOWS
1370 \\ cmp %%fp, 01377 \\ mov %%g3, %%sp
1371 \\ beq 2f1378 \\ mov %%g1, %%o0
1372 \\ nop1379 \\ mov %%g2, %%o1
1373 \\ ba 1b1380 \\ mov 73, %%g1 // SYS_munmap
1374 \\ restore1381 \\ t 0x10
1375 \\ 2:1382 \\ mov 1, %%g1 // SYS_exit
1376 \\ mov %%g1, %%o0 // ptr1383 \\ mov 0, %%o0
1377 \\ mov %%g2, %%o1 // len1384 \\ t 0x10
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
1384 :1385 :
1385 : [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)),1386 : [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)),
1386 [len] "{g2}" (self.mapped.len),1387 [len] "{g2}" (self.mapped.len),
1388 [stack] "{g3}" (&sparc_exit_stack),
1387 : .{ .memory = true }),1389 : .{ .memory = true }),
1388 .sparc64 => asm volatile (1390 .sparc64 => asm volatile (
1389 \\ # SPARCs really don't like it when active stack frames1391 \\ // Ensure that the kernel only has to flush the current register window.
1390 \\ # is unmapped (it will result in a segfault), so we1392 \\ flushw
1391 \\ # force-deactivate it by running `restore` until1393 \\ // Set up a fake stack for the syscall to restore l/i registers from. Local
1392 \\ # all frames are cleared.1394 \\ // and incoming registers must be treated as effectively garbage past this
1393 \\ 1:1395 \\ // instruction!
1394 \\ cmp %%fp, 01396 \\ sub %%g3, 2047, %%sp
1395 \\ beq 2f1397 \\ mov %%g1, %%o0
1396 \\ nop1398 \\ mov %%g2, %%o1
1397 \\ ba 1b1399 \\ mov 73, %%g1 // SYS_munmap
1398 \\ restore1400 \\ t 0x6d
1399 \\ 2:1401 \\ mov 1, %%g1 // SYS_exit
1400 \\ mov %%g1, %%o0 // ptr1402 \\ mov 0, %%o0
1401 \\ mov %%g2, %%o1 // len1403 \\ t 0x6d
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
1410 :1404 :
1411 : [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)),1405 : [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)),
1412 [len] "{g2}" (self.mapped.len),1406 [len] "{g2}" (self.mapped.len),
1407 [stack] "{g3}" (&sparc_exit_stack),
1413 : .{ .memory = true }),1408 : .{ .memory = true }),
1414 .loongarch32, .loongarch64 => asm volatile (1409 .loongarch32, .loongarch64 => asm volatile (
1415 \\ ori $a7, $zero, 215 # SYS_munmap1410 \\ ori $a7, $zero, 215 # SYS_munmap