authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-30 20:24:00+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-30 20:24:00+01:00
log1eb8fe7c61d1de3b23afb00c0bb4729f3a0b037f
tree53021761cebbf7e7ddb53d77617b104fac54804f
parent2a02c7a0d59e25bac07a6ed2948a29438fb05527
parent4b26c49076a1c163e5fdd1bd3a657e0794e5a917

Merge pull request 'bsd-futex' (#30626) from mikdusan/zig:bsd-futex into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30626 Reviewed-by: Andrew Kelley <andrewrk@noreply.codeberg.org>

6 files changed, 97 insertions(+), 11 deletions(-)

lib/compiler/test_runner.zig+10-6
...@@ -66,13 +66,13 @@ pub fn main() void {...@@ -66,13 +66,13 @@ pub fn main() void {
66 }66 }
6767
68 if (listen) {68 if (listen) {
69 return mainServer() catch @panic("internal test runner failure");69 return mainServer(args) catch @panic("internal test runner failure");
70 } else {70 } else {
71 return mainTerminal();71 return mainTerminal(args);
72 }72 }
73}73}
7474
75fn mainServer() !void {75fn mainServer(args: []const [:0]const u8) !void {
76 @disableInstrumentation();76 @disableInstrumentation();
77 var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer);77 var stdin_reader = Io.File.stdin().readerStreaming(runner_threaded_io, &stdin_buffer);
78 var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer);78 var stdout_writer = Io.File.stdout().writerStreaming(runner_threaded_io, &stdout_buffer);
...@@ -131,7 +131,9 @@ fn mainServer() !void {...@@ -131,7 +131,9 @@ fn mainServer() !void {
131131
132 .run_test => {132 .run_test => {
133 testing.allocator_instance = .{};133 testing.allocator_instance = .{};
134 testing.io_instance = .init(testing.allocator, .{});134 testing.io_instance = .init(testing.allocator, .{
135 .argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{},
136 });
135 log_err_count = 0;137 log_err_count = 0;
136 const index = try server.receiveBody_u32();138 const index = try server.receiveBody_u32();
137 const test_fn = builtin.test_functions[index];139 const test_fn = builtin.test_functions[index];
...@@ -215,7 +217,7 @@ fn mainServer() !void {...@@ -215,7 +217,7 @@ fn mainServer() !void {
215 }217 }
216}218}
217219
218fn mainTerminal() void {220fn mainTerminal(args: []const [:0]const u8) void {
219 @disableInstrumentation();221 @disableInstrumentation();
220 if (builtin.fuzz) @panic("fuzz test requires server");222 if (builtin.fuzz) @panic("fuzz test requires server");
221223
...@@ -233,7 +235,9 @@ fn mainTerminal() void {...@@ -233,7 +235,9 @@ fn mainTerminal() void {
233 var leaks: usize = 0;235 var leaks: usize = 0;
234 for (test_fn_list, 0..) |test_fn, i| {236 for (test_fn_list, 0..) |test_fn, i| {
235 testing.allocator_instance = .{};237 testing.allocator_instance = .{};
236 testing.io_instance = .init(testing.allocator, .{});238 testing.io_instance = .init(testing.allocator, .{
239 .argv0 = if (@hasField(Io.Threaded.Argv0, "value")) .{ .value = args[0] } else .{},
240 });
237 defer {241 defer {
238 testing.io_instance.deinit();242 testing.io_instance.deinit();
239 if (testing.allocator_instance.deinit() == .leak) leaks += 1;243 if (testing.allocator_instance.deinit() == .leak) leaks += 1;
lib/std/Io/Dir.zig+1
...@@ -113,6 +113,7 @@ pub const Reader = struct {...@@ -113,6 +113,7 @@ pub const Reader = struct {
113 },113 },
114 .wasi => @sizeOf(std.os.wasi.dirent_t) +114 .wasi => @sizeOf(std.os.wasi.dirent_t) +
115 std.mem.alignForward(usize, max_name_bytes, @alignOf(std.os.wasi.dirent_t)),115 std.mem.alignForward(usize, max_name_bytes, @alignOf(std.os.wasi.dirent_t)),
116 .openbsd => std.c.S.BLKSIZE,
116 else => if (builtin.link_libc) @sizeOf(std.c.dirent) else std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)),117 else => if (builtin.link_libc) @sizeOf(std.c.dirent) else std.mem.alignForward(usize, max_name_bytes, @alignOf(usize)),
117 };118 };
118119
lib/std/Io/Threaded.zig+63
...@@ -390,6 +390,52 @@ const Thread = struct {...@@ -390,6 +390,52 @@ const Thread = struct {
390 else => unreachable,390 else => unreachable,
391 };391 };
392 },392 },
393 .openbsd => {
394 var tm: std.c.timespec = undefined;
395 var tm_ptr: ?*const std.c.timespec = null;
396 if (timeout_ns) |ns| {
397 tm_ptr = &tm;
398 tm = timestampToPosix(ns);
399 }
400 if (thread) |t| try t.beginSyscall();
401 const rc = std.c.futex(
402 ptr,
403 std.c.FUTEX.WAIT | std.c.FUTEX.PRIVATE_FLAG,
404 @as(c_int, @bitCast(expect)),
405 tm_ptr,
406 null, // uaddr2 is ignored
407 );
408 if (thread) |t| t.endSyscall();
409 if (is_debug) switch (posix.errno(rc)) {
410 .SUCCESS => {},
411 .NOSYS => unreachable, // constant op known good value
412 .AGAIN => {}, // contents of uaddr != val
413 .INVAL => unreachable, // invalid timeout
414 .TIMEDOUT => {}, // timeout
415 .INTR => {}, // a signal arrived
416 .CANCELED => {}, // a signal arrived and SA_RESTART was set
417 else => unreachable,
418 };
419 },
420 .dragonfly => {
421 var timeout_us: c_int = undefined;
422 if (timeout_ns) |ns| {
423 timeout_us = std.math.cast(c_int, ns / std.time.ns_per_us) orelse std.math.maxInt(c_int);
424 } else {
425 timeout_us = 0;
426 }
427 if (thread) |t| try t.beginSyscall();
428 const rc = std.c.umtx_sleep(@ptrCast(ptr), @bitCast(expect), timeout_us);
429 if (thread) |t| t.endSyscall();
430 if (is_debug) switch (std.posix.errno(rc)) {
431 .SUCCESS => {},
432 .BUSY => {}, // ptr != expect
433 .AGAIN => {}, // maybe timed out, or paged out, or hit 2s kernel refresh
434 .INTR => {}, // spurious wake
435 .INVAL => unreachable, // invalid timeout
436 else => unreachable,
437 };
438 },
393 else => if (std.Thread.use_pthreads) {439 else => if (std.Thread.use_pthreads) {
394 // TODO integrate the following function being called with robust cancelation.440 // TODO integrate the following function being called with robust cancelation.
395 return pthreads_futex.wait(ptr, expect, timeout_ns) catch |err| switch (err) {441 return pthreads_futex.wait(ptr, expect, timeout_ns) catch |err| switch (err) {
...@@ -473,6 +519,23 @@ const Thread = struct {...@@ -473,6 +519,23 @@ const Thread = struct {
473 else => unreachable, // deadlock due to operating system bug519 else => unreachable, // deadlock due to operating system bug
474 }520 }
475 },521 },
522 .openbsd => {
523 const rc = std.c.futex(
524 ptr,
525 std.c.FUTEX.WAKE | std.c.FUTEX.PRIVATE_FLAG,
526 @min(max_waiters, std.math.maxInt(c_int)),
527 null, // timeout is ignored
528 null, // uaddr2 is ignored
529 );
530 assert(rc >= 0);
531 },
532 .dragonfly => {
533 // will generally return 0 unless the address is bad
534 _ = std.c.umtx_wakeup(
535 @ptrCast(ptr),
536 @min(max_waiters, std.math.maxInt(c_int)),
537 );
538 },
476 else => if (std.Thread.use_pthreads) {539 else => if (std.Thread.use_pthreads) {
477 return pthreads_futex.wake(ptr, max_waiters);540 return pthreads_futex.wake(ptr, max_waiters);
478 } else {541 } else {
lib/std/c.zig+15
...@@ -167,6 +167,18 @@ pub const timespec = switch (native_os) {...@@ -167,6 +167,18 @@ pub const timespec = switch (native_os) {
167 .openbsd, .haiku => extern struct {167 .openbsd, .haiku => extern struct {
168 sec: time_t,168 sec: time_t,
169 nsec: isize,169 nsec: isize,
170
171 /// For use with `utimensat` and `futimens`.
172 pub const NOW: timespec = .{
173 .sec = 0, // ignored
174 .nsec = -2,
175 };
176
177 /// For use with `utimensat` and `futimens`.
178 pub const OMIT: timespec = .{
179 .sec = 0, // ignored
180 .nsec = -1,
181 };
170 },182 },
171 else => void,183 else => void,
172};184};
...@@ -2365,6 +2377,8 @@ pub const S = switch (native_os) {...@@ -2365,6 +2377,8 @@ pub const S = switch (native_os) {
2365 pub const IWOTH = 0o002;2377 pub const IWOTH = 0o002;
2366 pub const IXOTH = 0o001;2378 pub const IXOTH = 0o001;
23672379
2380 pub const BLKSIZE = 512;
2381
2368 pub fn ISFIFO(m: u32) bool {2382 pub fn ISFIFO(m: u32) bool {
2369 return m & IFMT == IFIFO;2383 return m & IFMT == IFIFO;
2370 }2384 }
...@@ -9676,6 +9690,7 @@ pub const NSIG = switch (native_os) {...@@ -9676,6 +9690,7 @@ pub const NSIG = switch (native_os) {
9676 .illumos => 75,9690 .illumos => 75,
9677 // https://github.com/SerenityOS/serenity/blob/046c23f567a17758d762a33bdf04bacbfd088f9f/Kernel/API/POSIX/signal_numbers.h#L429691 // https://github.com/SerenityOS/serenity/blob/046c23f567a17758d762a33bdf04bacbfd088f9f/Kernel/API/POSIX/signal_numbers.h#L42
9678 .openbsd, .serenity => 33,9692 .openbsd, .serenity => 33,
9693 .dragonfly => 64,
9679 else => {},9694 else => {},
9680};9695};
96819696
lib/std/fs/test.zig+3-1
...@@ -645,6 +645,7 @@ fn contains(entries: *const std.array_list.Managed(Dir.Entry), el: Dir.Entry) bo...@@ -645,6 +645,7 @@ fn contains(entries: *const std.array_list.Managed(Dir.Entry), el: Dir.Entry) bo
645645
646test "Dir.realPath smoke test" {646test "Dir.realPath smoke test" {
647 if (native_os == .wasi) return error.SkipZigTest;647 if (native_os == .wasi) return error.SkipZigTest;
648 if (native_os == .openbsd) return error.SkipZigTest;
648649
649 try testWithAllSupportedPathTypes(struct {650 try testWithAllSupportedPathTypes(struct {
650 fn impl(ctx: *TestContext) !void {651 fn impl(ctx: *TestContext) !void {
...@@ -820,7 +821,7 @@ test "file operations on directories" {...@@ -820,7 +821,7 @@ test "file operations on directories" {
820 try expectError(error.IsDir, ctx.dir.createFile(io, test_dir_name, .{}));821 try expectError(error.IsDir, ctx.dir.createFile(io, test_dir_name, .{}));
821 try expectError(error.IsDir, ctx.dir.deleteFile(io, test_dir_name));822 try expectError(error.IsDir, ctx.dir.deleteFile(io, test_dir_name));
822 switch (native_os) {823 switch (native_os) {
823 .dragonfly, .netbsd => {824 .netbsd => {
824 // no error when reading a directory. See https://github.com/ziglang/zig/issues/5732825 // no error when reading a directory. See https://github.com/ziglang/zig/issues/5732
825 const buf = try ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited);826 const buf = try ctx.dir.readFileAlloc(io, test_dir_name, testing.allocator, .unlimited);
826 testing.allocator.free(buf);827 testing.allocator.free(buf);
...@@ -1240,6 +1241,7 @@ test "createDirPath, put some files in it, deleteTreeMinStackSize" {...@@ -1240,6 +1241,7 @@ test "createDirPath, put some files in it, deleteTreeMinStackSize" {
12401241
1241test "createDirPath in a directory that no longer exists" {1242test "createDirPath in a directory that no longer exists" {
1242 if (native_os == .windows) return error.SkipZigTest; // Windows returns FileBusy if attempting to remove an open dir1243 if (native_os == .windows) return error.SkipZigTest; // Windows returns FileBusy if attempting to remove an open dir
1244 if (native_os == .dragonfly) return error.SkipZigTest; // DragonflyBSD does not produce error (hammer2 fs)
12431245
1244 const io = testing.io;1246 const io = testing.io;
12451247
lib/std/posix/test.zig+5-4
...@@ -364,9 +364,10 @@ test "getrlimit and setrlimit" {...@@ -364,9 +364,10 @@ test "getrlimit and setrlimit" {
364}364}
365365
366test "sigrtmin/max" {366test "sigrtmin/max" {
367 if (native_os == .wasi or native_os == .windows or native_os.isDarwin() or native_os == .openbsd) {367 if (native_os.isDarwin() or switch (native_os) {
368 return error.SkipZigTest;368 .wasi, .windows, .openbsd, .dragonfly => true,
369 }369 else => false,
370 }) return error.SkipZigTest;
370371
371 try expect(posix.sigrtmin() >= 32);372 try expect(posix.sigrtmin() >= 32);
372 try expect(posix.sigrtmin() >= posix.system.sigrtmin());373 try expect(posix.sigrtmin() >= posix.system.sigrtmin());
...@@ -397,7 +398,7 @@ fn reserved_signo(i: usize) bool {...@@ -397,7 +398,7 @@ fn reserved_signo(i: usize) bool {
397 if (!builtin.link_libc) return false;398 if (!builtin.link_libc) return false;
398 const max = if (native_os == .netbsd) 32 else 31;399 const max = if (native_os == .netbsd) 32 else 31;
399 if (i > max) return true;400 if (i > max) return true;
400 if (native_os == .openbsd) return false; // no RT signals401 if (native_os == .openbsd or native_os == .dragonfly) return false; // no RT signals
401 return i < posix.sigrtmin();402 return i < posix.sigrtmin();
402}403}
403404