Commit 752d38612f

Andrew Kelley <andrew@ziglang.org>
2025-10-17 10:14:16
std.Io.Threaded: fix -fsingle-threaded build
1 parent 81e7e9f
Changed files (1)
lib
lib/std/Io/Threaded.zig
@@ -265,6 +265,10 @@ const have_flock_open_flags = @hasField(posix.O, "EXLOCK");
 const have_networking = builtin.os.tag != .wasi;
 const have_flock = @TypeOf(posix.system.flock) != void;
 const have_sendmmsg = builtin.os.tag == .linux;
+const have_futex = switch (builtin.cpu.arch) {
+    .wasm32, .wasm64 => builtin.cpu.has(.wasm, .atomics),
+    else => true,
+};
 
 const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
 const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
@@ -731,6 +735,7 @@ fn checkCancel(t: *Threaded) error{Canceled}!void {
 }
 
 fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) Io.Cancelable!void {
+    if (builtin.single_threaded) unreachable; // Interface should have prevented this.
     const t: *Threaded = @ptrCast(@alignCast(userdata));
     if (prev_state == .contended) {
         try futexWait(t, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended));
@@ -741,6 +746,7 @@ fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex
 }
 
 fn mutexLockUncancelable(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void {
+    if (builtin.single_threaded) unreachable; // Interface should have prevented this.
     _ = userdata;
     if (prev_state == .contended) {
         futexWaitUncancelable(@ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended));
@@ -751,6 +757,7 @@ fn mutexLockUncancelable(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mute
 }
 
 fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void {
+    if (builtin.single_threaded) unreachable; // Interface should have prevented this.
     _ = userdata;
     _ = prev_state;
     if (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .unlocked, .release) == .contended) {
@@ -759,6 +766,7 @@ fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mut
 }
 
 fn conditionWaitUncancelable(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) void {
+    if (builtin.single_threaded) unreachable; // Deadlock.
     const t: *Threaded = @ptrCast(@alignCast(userdata));
     const t_io = t.io();
     comptime assert(@TypeOf(cond.state) == u64);
@@ -789,6 +797,7 @@ fn conditionWaitUncancelable(userdata: ?*anyopaque, cond: *Io.Condition, mutex:
 }
 
 fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void {
+    if (builtin.single_threaded) unreachable; // Deadlock.
     const t: *Threaded = @ptrCast(@alignCast(userdata));
     comptime assert(@TypeOf(cond.state) == u64);
     const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state);
@@ -833,6 +842,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I
 }
 
 fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void {
+    if (builtin.single_threaded) unreachable; // Nothing to wake up.
     const t: *Threaded = @ptrCast(@alignCast(userdata));
     _ = t;
     comptime assert(@TypeOf(cond.state) == u64);
@@ -4007,6 +4017,32 @@ fn futexWait(t: *Threaded, ptr: *const std.atomic.Value(u32), expect: u32) Io.Ca
         return;
     }
 
+    if (builtin.cpu.arch.isWasm()) {
+        comptime assert(builtin.cpu.has(.wasm, .atomics));
+        try t.checkCancel();
+        const timeout: i64 = -1;
+        const signed_expect: i32 = @bitCast(expect);
+        const result = asm volatile (
+            \\local.get %[ptr]
+            \\local.get %[expected]
+            \\local.get %[timeout]
+            \\memory.atomic.wait32 0
+            \\local.set %[ret]
+            : [ret] "=r" (-> u32),
+            : [ptr] "r" (&ptr.raw),
+              [expected] "r" (signed_expect),
+              [timeout] "r" (timeout),
+        );
+        const is_debug = builtin.mode == .Debug;
+        switch (result) {
+            0 => {}, // ok
+            1 => {}, // expected != loaded
+            2 => assert(!is_debug), // timeout
+            else => assert(!is_debug),
+        }
+        return;
+    }
+
     @compileError("TODO");
 }
 
@@ -4054,6 +4090,31 @@ pub fn futexWaitUncancelable(ptr: *const std.atomic.Value(u32), expect: u32) voi
         return;
     }
 
+    if (builtin.cpu.arch.isWasm()) {
+        comptime assert(builtin.cpu.has(.wasm, .atomics));
+        const timeout: i64 = -1;
+        const signed_expect: i32 = @bitCast(expect);
+        const result = asm volatile (
+            \\local.get %[ptr]
+            \\local.get %[expected]
+            \\local.get %[timeout]
+            \\memory.atomic.wait32 0
+            \\local.set %[ret]
+            : [ret] "=r" (-> u32),
+            : [ptr] "r" (&ptr.raw),
+              [expected] "r" (signed_expect),
+              [timeout] "r" (timeout),
+        );
+        const is_debug = builtin.mode == .Debug;
+        switch (result) {
+            0 => {}, // ok
+            1 => {}, // expected != loaded
+            2 => assert(!is_debug), // timeout
+            else => assert(!is_debug),
+        }
+        return;
+    }
+
     @compileError("TODO");
 }
 
@@ -4119,6 +4180,22 @@ pub fn futexWake(ptr: *const std.atomic.Value(u32), max_waiters: u32) void {
         }
     }
 
+    if (builtin.cpu.arch.isWasm()) {
+        comptime assert(builtin.cpu.has(.wasm, .atomics));
+        assert(max_waiters != 0);
+        const woken_count = asm volatile (
+            \\local.get %[ptr]
+            \\local.get %[waiters]
+            \\memory.atomic.notify 0
+            \\local.set %[ret]
+            : [ret] "=r" (-> u32),
+            : [ptr] "r" (&ptr.raw),
+              [waiters] "r" (max_waiters),
+        );
+        _ = woken_count; // can be 0 when linker flag 'shared-memory' is not enabled
+        return;
+    }
+
     @compileError("TODO");
 }