From 5245c13a8a46520127600845f1c9ab5bca42ded5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 1 Aug 2026 11:32:32 +0200 Subject: [PATCH 1/6] std.heap.PageAllocator: disable hinting on sparc[64]-linux https://bugzilla.kernel.org/show_bug.cgi?id=221820 --- lib/std/heap/PageAllocator.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/heap/PageAllocator.zig b/lib/std/heap/PageAllocator.zig index db736036b3f416d34a934b7185ac4a953affa303..1adac776bb9ac7a1d145bc7a5e464eedefd84f29 100644 --- a/lib/std/heap/PageAllocator.zig +++ b/lib/std/heap/PageAllocator.zig @@ -24,6 +24,7 @@ pub const vtable: Allocator.VTable = .{ /// that don't provide a hint (for security reasons, but it serves our needs /// too). const enable_hints = switch (builtin.target.os.tag) { + .linux => !builtin.target.cpu.arch.isSPARC(), // https://bugzilla.kernel.org/show_bug.cgi?id=221820 .openbsd => false, else => true, }; -- 2.54.0 From 2085096f50fe235e9b6ddff8abc04daa4e5e7667 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 1 Aug 2026 11:29:32 +0200 Subject: [PATCH 2/6] 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. --- lib/std/Thread.zig | 69 +++++++++++++++++++++------------------------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 6eb76719a55d2787d04f124e58fa8829cbc7e8e9..05125adbe852a3586b4d77fa2d0f8f7dd4d4bb94 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -1146,6 +1146,13 @@ const LinuxThreadImpl = struct { parent_tid: i32 = undefined, mapped: []align(std.heap.page_size_min) u8, + // 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 this undefined global buffer. + var sparc_exit_stack: [192]u8 align(16) = undefined; + /// Calls `munmap(mapped.ptr, mapped.len)` then `exit(1)` without touching the stack (which lives in `mapped.ptr`). /// Ported over from musl libc's pthread detached implementation: /// https://github.com/ifduyue/musl/search?q=__unmapself @@ -1365,51 +1372,39 @@ const LinuxThreadImpl = struct { [len] "{r5}" (self.mapped.len), ), .sparc => asm volatile ( - \\ # See sparc64 comments below. - \\ 1: - \\ cmp %%fp, 0 - \\ beq 2f - \\ nop - \\ ba 1b - \\ restore - \\ 2: - \\ mov %%g1, %%o0 // ptr - \\ mov %%g2, %%o1 // len - \\ mov 73, %%g1 // SYS_munmap - \\ t 0x3 // ST_FLUSH_WINDOWS - \\ t 0x10 - \\ mov 1, %%g1 // SYS_exit - \\ mov 0, %%o0 - \\ t 0x10 + \\ // See sparc64 comments below. + \\ t 0x3 // ST_FLUSH_WINDOWS + \\ mov %%g3, %%sp + \\ mov %%g1, %%o0 + \\ mov %%g2, %%o1 + \\ mov 73, %%g1 // SYS_munmap + \\ t 0x10 + \\ mov 1, %%g1 // SYS_exit + \\ mov 0, %%o0 + \\ t 0x10 : : [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)), [len] "{g2}" (self.mapped.len), + [stack] "{g3}" (&sparc_exit_stack), : .{ .memory = true }), .sparc64 => asm volatile ( - \\ # SPARCs really don't like it when active stack frames - \\ # is unmapped (it will result in a segfault), so we - \\ # force-deactivate it by running `restore` until - \\ # all frames are cleared. - \\ 1: - \\ cmp %%fp, 0 - \\ beq 2f - \\ nop - \\ ba 1b - \\ restore - \\ 2: - \\ mov %%g1, %%o0 // ptr - \\ mov %%g2, %%o1 // len - \\ mov 73, %%g1 // SYS_munmap - \\ # Flush register window contents to prevent background - \\ # memory access before unmapping the stack. - \\ flushw - \\ t 0x6d - \\ mov 1, %%g1 // SYS_exit - \\ mov 0, %%o0 - \\ t 0x6d + \\ // Ensure that the kernel only has to flush the current register window. + \\ flushw + \\ // Set up a fake stack for the syscall to restore l/i registers from. Local + \\ // and incoming registers must be treated as effectively garbage past this + \\ // instruction! + \\ sub %%g3, 2047, %%sp + \\ mov %%g1, %%o0 + \\ mov %%g2, %%o1 + \\ mov 73, %%g1 // SYS_munmap + \\ t 0x6d + \\ mov 1, %%g1 // SYS_exit + \\ mov 0, %%o0 + \\ t 0x6d : : [ptr] "{g1}" (@intFromPtr(self.mapped.ptr)), [len] "{g2}" (self.mapped.len), + [stack] "{g3}" (&sparc_exit_stack), : .{ .memory = true }), .loongarch32, .loongarch64 => asm volatile ( \\ ori $a7, $zero, 215 # SYS_munmap -- 2.54.0 From eda296c24a80f15b4f28b985fa7ff782bb6b731e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 1 Aug 2026 14:15:08 +0200 Subject: [PATCH 3/6] std.os.linux: fix restore_rt() on sparc/sparc64 Not really sure what this function was trying to do before, but it seems kind of nonsensical. All it needs to do is perform an rt_sigreturn syscall, with the gotcha that it needs two nop instructions at the beginning to accommodate the way that ret instructions are done in the SPARC ABI (%i7 + 8 to skip the call instruction and delay slot). The callconv(.c) is certainly not necessary and probably caused bugs on its own. --- lib/std/os/linux/sparc.zig | 13 ++++++++----- lib/std/os/linux/sparc64.zig | 13 ++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/std/os/linux/sparc.zig b/lib/std/os/linux/sparc.zig index 4a2e7188a6b2a9cf582edddf204ce8fc61d4fb95..c8addd36c45bfc08aff71672c2aeef42bb948f06 100644 --- a/lib/std/os/linux/sparc.zig +++ b/lib/std/os/linux/sparc.zig @@ -260,13 +260,16 @@ pub fn clone() callconv(.naked) u32 { pub const restore = restore_rt; -// Need to use C ABI here instead of naked -// to prevent an infinite loop when calling rt_sigreturn. -pub fn restore_rt() callconv(.c) void { - return asm volatile ("t 0x10" +pub fn restore_rt() callconv(.naked) noreturn { + asm volatile ( + \\ nop + \\ nop + ); + asm volatile ( + \\ t 0x10 : : [number] "{g1}" (@backingInt(SYS.rt_sigreturn)), - : .{ .memory = true, .xcc = true, .o0 = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); + ); } pub const VDSO = struct { diff --git a/lib/std/os/linux/sparc64.zig b/lib/std/os/linux/sparc64.zig index f7e859cc72abfe37dfee1f5ab5927979c2625615..955ae477c3f81d1f190ad7cb440108747cff9b56 100644 --- a/lib/std/os/linux/sparc64.zig +++ b/lib/std/os/linux/sparc64.zig @@ -259,13 +259,16 @@ pub fn clone() callconv(.naked) u64 { pub const restore = restore_rt; -// Need to use C ABI here instead of naked -// to prevent an infinite loop when calling rt_sigreturn. -pub fn restore_rt() callconv(.c) void { - return asm volatile ("t 0x6d" +pub fn restore_rt() callconv(.naked) noreturn { + asm volatile ( + \\ nop + \\ nop + ); + asm volatile ( + \\ t 0x6d : : [number] "{g1}" (@backingInt(SYS.rt_sigreturn)), - : .{ .memory = true, .xcc = true, .o0 = true, .o1 = true, .o2 = true, .o3 = true, .o4 = true, .o5 = true, .o7 = true }); + ); } pub const VDSO = struct { -- 2.54.0 From c089dd22a97c55f36bcdcf962d646bd67cc33fb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 1 Aug 2026 14:33:08 +0200 Subject: [PATCH 4/6] Revert "std: disable some failing cancelation tests on sparc64-linux" This reverts commit 3b330f1d7c36416924f8986a6c541331130297d4. --- lib/std/Io/Threaded/test.zig | 2 -- lib/std/Io/net/test.zig | 2 -- lib/std/Io/test.zig | 2 -- 3 files changed, 6 deletions(-) diff --git a/lib/std/Io/Threaded/test.zig b/lib/std/Io/Threaded/test.zig index bb49dbc1b5fbc2e502c0368756d838c39a2e0258..392323de9b15c8674b4af0ea7aea21a5f49d6faf 100644 --- a/lib/std/Io/Threaded/test.zig +++ b/lib/std/Io/Threaded/test.zig @@ -149,8 +149,6 @@ test "async with array return type" { } test "cancel blocked read from pipe" { - if (builtin.cpu.arch.isSPARC() and builtin.os.tag == .linux) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/35347 - const global = struct { fn readFromPipe(io: Io, pipe: Io.File) !void { var buf: [1]u8 = undefined; diff --git a/lib/std/Io/net/test.zig b/lib/std/Io/net/test.zig index 308dc1a32670e207e72511d0128b634972050e6a..12fa0a846c0b6576e0f0b593bc51290e6f086b14 100644 --- a/lib/std/Io/net/test.zig +++ b/lib/std/Io/net/test.zig @@ -356,8 +356,6 @@ test "decompress compressed DNS name" { } test "cancel accept" { - if (builtin.cpu.arch.isSPARC() and builtin.os.tag == .linux) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/35347 - const io = testing.io; const localhost: net.IpAddress = .{ .ip4 = .loopback(0) }; diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index 3d9523da7675c01e8a780e139e886231f1e04617..0874553c9f42656e14fafffbf0c26aee2d271f41 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -326,8 +326,6 @@ test "Group materializes error.Cancel" { } test "Group task receives cancelation unknowingly" { - if (builtin.cpu.arch.isSPARC() and builtin.os.tag == .linux) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/35347 - const S = struct { io: Io, err: ?Io.Cancelable!void, -- 2.54.0 From b6810db40a18026e0362ef04570468a6a3a89635 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 1 Aug 2026 14:31:50 +0200 Subject: [PATCH 5/6] Revert "std.Io.RwLock: disable `lock canceling` test on SPARC" This reverts commit fe6c3e58ef11c8377ce4adfc89301bf4edaf42ab. --- lib/std/Io/RwLock.zig | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/std/Io/RwLock.zig b/lib/std/Io/RwLock.zig index 7a445033db6b50da7ab31ee8a6cb7b7f6c3ed438..de9bf86f366314f64ed45877c675dd2cbd9eee4a 100644 --- a/lib/std/Io/RwLock.zig +++ b/lib/std/Io/RwLock.zig @@ -284,8 +284,6 @@ test "concurrent access" { } test "lock canceling" { - if (builtin.cpu.arch.isSPARC() and builtin.os.tag == .linux) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/35347 - const io = testing.io; var rl: Io.RwLock = .init; -- 2.54.0 From bcf65eb51085c0fa1a9380172cdea55811d9fe97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 1 Aug 2026 14:31:08 +0200 Subject: [PATCH 6/6] Revert "std.Io: disable `Group.cancel` on sparc64-linux" This reverts commit ef3040786e502ea82e87ffefb9570b4e88dff79b. --- lib/std/Io/test.zig | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index 0874553c9f42656e14fafffbf0c26aee2d271f41..54d79fceec8e6adebbc4e35867bcea93aa318bf2 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -232,8 +232,6 @@ fn count(a: usize, b: usize, result: *usize) void { } test "Group.cancel" { - if (builtin.cpu.arch.isSPARC() and builtin.os.tag == .linux) return error.SkipZigTest; // https://codeberg.org/ziglang/zig/issues/35347 - const global = struct { fn sleep(io: Io, result: *usize) Io.Cancelable!void { defer result.* = 1; -- 2.54.0