authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-06-20 10:08:22+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2025-06-20 10:08:22+02:00
log14ad8378a19dbbf84e0be491292019356b9e8752
treeffcbc32673018b97c13c9f930013a772c3b79e07
parentcf1a7bbd44b9542552c7b5dc6532aafb5142bf7a
parent89d15a8d47fdfe41ae650e399d258de3184e6b4d
signaturebadge-check Signed by PGP key B5690EEEBB952194

Merge pull request #23464 from rootbeer/futex-casts

Linux futex (v1 and v2) API fixes, tests and Ziggification

4 files changed, 405 insertions(+), 101 deletions(-)

lib/std/Thread.zig+3-3
...@@ -1539,10 +1539,10 @@ const LinuxThreadImpl = struct {...@@ -1539,10 +1539,10 @@ const LinuxThreadImpl = struct {
1539 continue;1539 continue;
1540 }1540 }
15411541
1542 switch (linux.E.init(linux.futex_wait(1542 switch (linux.E.init(linux.futex_4arg(
1543 &self.thread.child_tid.raw,1543 &self.thread.child_tid.raw,
1544 linux.FUTEX.WAIT,1544 .{ .cmd = .WAIT, .private = false },
1545 tid,1545 @bitCast(tid),
1546 null,1546 null,
1547 ))) {1547 ))) {
1548 .SUCCESS => continue,1548 .SUCCESS => continue,
lib/std/Thread/Futex.zig+8-8
...@@ -262,10 +262,10 @@ const LinuxImpl = struct {...@@ -262,10 +262,10 @@ const LinuxImpl = struct {
262 ts.nsec = @as(@TypeOf(ts.nsec), @intCast(timeout_ns % std.time.ns_per_s));262 ts.nsec = @as(@TypeOf(ts.nsec), @intCast(timeout_ns % std.time.ns_per_s));
263 }263 }
264264
265 const rc = linux.futex_wait(265 const rc = linux.futex_4arg(
266 @as(*const i32, @ptrCast(&ptr.raw)),266 &ptr.raw,
267 linux.FUTEX.PRIVATE_FLAG | linux.FUTEX.WAIT,267 .{ .cmd = .WAIT, .private = true },
268 @as(i32, @bitCast(expect)),268 expect,
269 if (timeout != null) &ts else null,269 if (timeout != null) &ts else null,
270 );270 );
271271
...@@ -284,10 +284,10 @@ const LinuxImpl = struct {...@@ -284,10 +284,10 @@ const LinuxImpl = struct {
284 }284 }
285285
286 fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void {286 fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void {
287 const rc = linux.futex_wake(287 const rc = linux.futex_3arg(
288 @as(*const i32, @ptrCast(&ptr.raw)),288 &ptr.raw,
289 linux.FUTEX.PRIVATE_FLAG | linux.FUTEX.WAKE,289 .{ .cmd = .WAKE, .private = true },
290 std.math.cast(i32, max_waiters) orelse std.math.maxInt(i32),290 @min(max_waiters, std.math.maxInt(i32)),
291 );291 );
292292
293 switch (linux.E.init(rc)) {293 switch (linux.E.init(rc)) {
lib/std/os/linux.zig+174-90
...@@ -673,23 +673,43 @@ pub fn fallocate(fd: i32, mode: i32, offset: i64, length: i64) usize {...@@ -673,23 +673,43 @@ pub fn fallocate(fd: i32, mode: i32, offset: i64, length: i64) usize {
673 }673 }
674}674}
675675
676pub fn futex_wait(uaddr: *const i32, futex_op: u32, val: i32, timeout: ?*const timespec) usize {676// The 4th parameter to the v1 futex syscall can either be an optional
677 return syscall4(.futex, @intFromPtr(uaddr), futex_op, @as(u32, @bitCast(val)), @intFromPtr(timeout));677// pointer to a timespec, or a uint32, depending on which "op" is being
678// performed.
679pub const futex_param4 = extern union {
680 timeout: ?*const timespec,
681 /// On all platforms only the bottom 32-bits of `val2` are relevant.
682 /// This is 64-bit to match the pointer in the union.
683 val2: usize,
684};
685
686/// The futex v1 syscall, see also the newer the futex2_{wait,wakeup,requeue,waitv} syscalls.
687///
688/// The futex_op parameter is a sub-command and flags. The sub-command
689/// defines which of the subsequent paramters are relevant.
690pub fn futex(uaddr: *const anyopaque, futex_op: FUTEX_OP, val: u32, val2timeout: futex_param4, uaddr2: ?*const anyopaque, val3: u32) usize {
691 return syscall6(.futex, @intFromPtr(uaddr), @as(u32, @bitCast(futex_op)), val, @intFromPtr(val2timeout.timeout), @intFromPtr(uaddr2), val3);
678}692}
679693
680pub fn futex_wake(uaddr: *const i32, futex_op: u32, val: i32) usize {694/// Three-argument variation of the v1 futex call. Only suitable for a
681 return syscall3(.futex, @intFromPtr(uaddr), futex_op, @as(u32, @bitCast(val)));695/// futex_op that ignores the remaining arguments (e.g., FUTUX_OP.WAKE).
696pub fn futex_3arg(uaddr: *const anyopaque, futex_op: FUTEX_OP, val: u32) usize {
697 return syscall3(.futex, @intFromPtr(uaddr), @as(u32, @bitCast(futex_op)), val);
682}698}
683699
684/// Given an array of `futex_waitv`, wait on each uaddr.700/// Four-argument variation on the v1 futex call. Only suitable for
701/// futex_op that ignores the remaining arguments (e.g., FUTEX_OP.WAIT).
702pub fn futex_4arg(uaddr: *const anyopaque, futex_op: FUTEX_OP, val: u32, timeout: ?*const timespec) usize {
703 return syscall4(.futex, @intFromPtr(uaddr), @as(u32, @bitCast(futex_op)), val, @intFromPtr(timeout));
704}
705
706/// Given an array of `futex2_waitone`, wait on each uaddr.
685/// The thread wakes if a futex_wake() is performed at any uaddr.707/// The thread wakes if a futex_wake() is performed at any uaddr.
686/// The syscall returns immediately if any waiter has *uaddr != val.708/// The syscall returns immediately if any futex has *uaddr != val.
687/// timeout is an optional timeout value for the operation.709/// timeout is an optional, absolute timeout value for the operation.
688/// Each waiter has individual flags.710/// The `flags` argument is for future use and currently should be `.{}`.
689/// The `flags` argument for the syscall should be used solely for specifying711/// Flags for private futexes, sizes, etc. should be set on the
690/// the timeout as realtime, if needed.712/// individual flags of each `futex2_waitone`.
691/// Flags for private futexes, sizes, etc. should be used on the
692/// individual flags of each waiter.
693///713///
694/// Returns the array index of one of the woken futexes.714/// Returns the array index of one of the woken futexes.
695/// No further information is provided: any number of other futexes may also715/// No further information is provided: any number of other futexes may also
...@@ -697,42 +717,43 @@ pub fn futex_wake(uaddr: *const i32, futex_op: u32, val: i32) usize {...@@ -697,42 +717,43 @@ pub fn futex_wake(uaddr: *const i32, futex_op: u32, val: i32) usize {
697/// the returned index may refer to any one of them.717/// the returned index may refer to any one of them.
698/// (It is not necessaryily the futex with the smallest index, nor the one718/// (It is not necessaryily the futex with the smallest index, nor the one
699/// most recently woken, nor...)719/// most recently woken, nor...)
720///
721/// Requires at least kernel v5.16.
700pub fn futex2_waitv(722pub fn futex2_waitv(
701 /// List of futexes to wait on.723 futexes: [*]const futex2_waitone,
702 waiters: [*]futex_waitv,724 /// Length of `futexes`. Max of FUTEX2_WAITONE_MAX.
703 /// Length of `waiters`.
704 nr_futexes: u32,725 nr_futexes: u32,
705 /// Flag for timeout (monotonic/realtime).726 flags: FUTEX2_FLAGS_WAITV,
706 flags: u32,727 /// Optional absolute timeout. Always 64-bit, even on 32-bit platforms.
707 /// Optional absolute timeout.728 timeout: ?*const kernel_timespec,
708 timeout: ?*const timespec,
709 /// Clock to be used for the timeout, realtime or monotonic.729 /// Clock to be used for the timeout, realtime or monotonic.
710 clockid: clockid_t,730 clockid: clockid_t,
711) usize {731) usize {
712 return syscall5(732 return syscall5(
713 .futex_waitv,733 .futex_waitv,
714 @intFromPtr(waiters),734 @intFromPtr(futexes),
715 nr_futexes,735 nr_futexes,
716 flags,736 @as(u32, @bitCast(flags)),
717 @intFromPtr(timeout),737 @intFromPtr(timeout),
718 @bitCast(@as(isize, @intFromEnum(clockid))),738 @intFromEnum(clockid),
719 );739 );
720}740}
721741
722/// Wait on a futex.742/// Wait on a single futex.
723/// Identical to the traditional `FUTEX.FUTEX_WAIT_BITSET` op, except it is part of the743/// Identical to the futex v1 `FUTEX.FUTEX_WAIT_BITSET` op, except it is part of the
724/// futex2 familiy of calls.744/// futex2 family of calls.
745///
746/// Requires at least kernel v6.7.
725pub fn futex2_wait(747pub fn futex2_wait(
726 /// Address of the futex to wait on.748 /// Address of the futex to wait on.
727 uaddr: *const anyopaque,749 uaddr: *const anyopaque,
728 /// Value of `uaddr`.750 /// Value of `uaddr`.
729 val: usize,751 val: usize,
730 /// Bitmask.752 /// Bitmask to match against incoming wakeup masks. Must not be zero.
731 mask: usize,753 mask: usize,
732 /// `FUTEX2` flags.754 flags: FUTEX2_FLAGS,
733 flags: u32,755 /// Optional absolute timeout. Always 64-bit, even on 32-bit platforms.
734 /// Optional absolute timeout.756 timeout: ?*const kernel_timespec,
735 timeout: ?*const timespec,
736 /// Clock to be used for the timeout, realtime or monotonic.757 /// Clock to be used for the timeout, realtime or monotonic.
737 clockid: clockid_t,758 clockid: clockid_t,
738) usize {759) usize {
...@@ -741,52 +762,55 @@ pub fn futex2_wait(...@@ -741,52 +762,55 @@ pub fn futex2_wait(
741 @intFromPtr(uaddr),762 @intFromPtr(uaddr),
742 val,763 val,
743 mask,764 mask,
744 flags,765 @as(u32, @bitCast(flags)),
745 @intFromPtr(timeout),766 @intFromPtr(timeout),
746 @bitCast(@as(isize, @intFromEnum(clockid))),767 @intFromEnum(clockid),
747 );768 );
748}769}
749770
750/// Wake a number of futexes.771/// Wake (subset of) waiters on given futex.
751/// Identical to the traditional `FUTEX.FUTEX_WAIT_BITSET` op, except it is part of the772/// Identical to the traditional `FUTEX.FUTEX_WAKE_BITSET` op, except it is part of the
752/// futex2 family of calls.773/// futex2 family of calls.
774///
775/// Requires at least kernel v6.7.
753pub fn futex2_wake(776pub fn futex2_wake(
754 /// Address of the futex(es) to wake.777 /// Futex to wake
755 uaddr: *const anyopaque,778 uaddr: *const anyopaque,
756 /// Bitmask779 /// Bitmask to match against waiters.
757 mask: usize,780 mask: usize,
758 /// Number of the futexes to wake.781 /// Maximum number of waiters on the futex to wake.
759 nr: i32,782 nr_wake: i32,
760 /// `FUTEX2` flags.783 flags: FUTEX2_FLAGS,
761 flags: u32,
762) usize {784) usize {
763 return syscall4(785 return syscall4(
764 .futex_wake,786 .futex_wake,
765 @intFromPtr(uaddr),787 @intFromPtr(uaddr),
766 mask,788 mask,
767 @bitCast(@as(isize, nr)),789 @as(u32, @bitCast(nr_wake)),
768 flags,790 @as(u32, @bitCast(flags)),
769 );791 );
770}792}
771793
772/// Requeue a waiter from one futex to another.794/// Wake and/or requeue waiter(s) from one futex to another.
773/// Identical to `FUTEX.CMP_REQUEUE`, except it is part of the futex2 family of calls.795/// Identical to `FUTEX.CMP_REQUEUE`, except it is part of the futex2 family of calls.
796///
797/// Requires at least kernel v6.7.
774pub fn futex2_requeue(798pub fn futex2_requeue(
775 /// Array describing the source and destination futex.799 /// The source and destination futexes. Must be a 2-element array.
776 waiters: [*]futex_waitv,800 waiters: [*]const futex2_waitone,
777 /// Unused.801 /// Currently unused.
778 flags: u32,802 flags: FUTEX2_FLAGS_REQUEUE,
779 /// Number of futexes to wake.803 /// Maximum number of waiters to wake on the source futex.
780 nr_wake: i32,804 nr_wake: i32,
781 /// Number of futexes to requeue.805 /// Maximum number of waiters to transfer to the destination futex.
782 nr_requeue: i32,806 nr_requeue: i32,
783) usize {807) usize {
784 return syscall4(808 return syscall4(
785 .futex_requeue,809 .futex_requeue,
786 @intFromPtr(waiters),810 @intFromPtr(waiters),
787 flags,811 @as(u32, @bitCast(flags)),
788 @bitCast(@as(isize, nr_wake)),812 @as(u32, @bitCast(nr_wake)),
789 @bitCast(@as(isize, nr_requeue)),813 @as(u32, @bitCast(nr_requeue)),
790 );814 );
791}815}
792816
...@@ -3385,37 +3409,97 @@ pub const FALLOC = struct {...@@ -3385,37 +3409,97 @@ pub const FALLOC = struct {
3385 pub const FL_UNSHARE_RANGE = 0x40;3409 pub const FL_UNSHARE_RANGE = 0x40;
3386};3410};
33873411
3388pub const FUTEX = struct {3412// Futex v1 API commands. See futex man page for each command's
3389 pub const WAIT = 0;3413// interpretation of the futex arguments.
3390 pub const WAKE = 1;3414pub const FUTEX_COMMAND = enum(u7) {
3391 pub const FD = 2;3415 WAIT = 0,
3392 pub const REQUEUE = 3;3416 WAKE = 1,
3393 pub const CMP_REQUEUE = 4;3417 FD = 2,
3394 pub const WAKE_OP = 5;3418 REQUEUE = 3,
3395 pub const LOCK_PI = 6;3419 CMP_REQUEUE = 4,
3396 pub const UNLOCK_PI = 7;3420 WAKE_OP = 5,
3397 pub const TRYLOCK_PI = 8;3421 LOCK_PI = 6,
3398 pub const WAIT_BITSET = 9;3422 UNLOCK_PI = 7,
3399 pub const WAKE_BITSET = 10;3423 TRYLOCK_PI = 8,
3400 pub const WAIT_REQUEUE_PI = 11;3424 WAIT_BITSET = 9,
3401 pub const CMP_REQUEUE_PI = 12;3425 WAKE_BITSET = 10,
34023426 WAIT_REQUEUE_PI = 11,
3403 pub const PRIVATE_FLAG = 128;3427 CMP_REQUEUE_PI = 12,
34043428};
3405 pub const CLOCK_REALTIME = 256;3429
34063430/// Futex v1 API command and flags for the `futex_op` parameter
3407 /// Max numbers of elements in a `futex_waitv` array.3431pub const FUTEX_OP = packed struct(u32) {
3408 pub const WAITV_MAX = 128;3432 cmd: FUTEX_COMMAND,
3409};3433 private: bool,
34103434 realtime: bool = false, // realtime clock vs. monotonic clock
3411pub const FUTEX2 = struct {3435 _reserved: u23 = 0,
3412 pub const SIZE_U8 = 0x00;3436};
3413 pub const SIZE_U16 = 0x01;3437
3414 pub const SIZE_U32 = 0x02;3438/// Futex v1 FUTEX_WAKE_OP `val3` operation:
3415 pub const SIZE_U64 = 0x03;3439pub const FUTEX_WAKE_OP = packed struct(u32) {
3416 pub const NUMA = 0x04;3440 cmd: FUTEX_WAKE_OP_CMD,
34173441 /// From C API `FUTEX_OP_ARG_SHIFT`: Use (1 << oparg) as operand
3418 pub const PRIVATE = FUTEX.PRIVATE_FLAG;3442 arg_shift: bool = false,
3443 cmp: FUTEX_WAKE_OP_CMP,
3444 oparg: u12,
3445 cmdarg: u12,
3446};
3447
3448/// Futex v1 cmd for FUTEX_WAKE_OP `val3` command.
3449pub const FUTEX_WAKE_OP_CMD = enum(u3) {
3450 /// uaddr2 = oparg
3451 SET = 0,
3452 /// uaddr2 += oparg
3453 ADD = 1,
3454 /// uaddr2 |= oparg
3455 OR = 2,
3456 /// uaddr2 &= ~oparg
3457 ANDN = 3,
3458 /// uaddr2 ^= oparg
3459 XOR = 4,
3460};
3461
3462/// Futex v1 comparison op for FUTEX_WAKE_OP `val3` cmp
3463pub const FUTEX_WAKE_OP_CMP = enum(u4) {
3464 EQ = 0,
3465 NE = 1,
3466 LT = 2,
3467 LE = 3,
3468 GT = 4,
3469 GE = 5,
3470};
3471
3472/// Max numbers of elements in a `futex2_waitone` array.
3473pub const FUTEX2_WAITONE_MAX = 128;
3474
3475/// For futex v2 API, the size of the futex at the uaddr. v1 futex are
3476/// always implicitly U32. As of kernel v6.14, only U32 is implemented
3477/// for v2 futexes.
3478pub const FUTEX2_SIZE = enum(u2) {
3479 U8 = 0,
3480 U16 = 1,
3481 U32 = 2,
3482 U64 = 3,
3483};
3484
3485/// As of kernel 6.14 there are no defined flags to futex2_waitv.
3486pub const FUTEX2_FLAGS_WAITV = packed struct(u32) {
3487 _reserved: u32 = 0,
3488};
3489
3490/// As of kernel 6.14 there are no defined flags to futex2_requeue.
3491pub const FUTEX2_FLAGS_REQUEUE = packed struct(u32) {
3492 _reserved: u32 = 0,
3493};
3494
3495/// Flags for futex v2 APIs (futex2_wait, futex2_wake, futex2_requeue, but
3496/// not the futex2_waitv syscall, but also used in the futex2_waitone struct).
3497pub const FUTEX2_FLAGS = packed struct(u32) {
3498 size: FUTEX2_SIZE,
3499 numa: bool = false,
3500 _reserved: u4 = 0,
3501 private: bool,
3502 _undefined: u24 = 0,
3419};3503};
34203504
3421pub const PROT = struct {3505pub const PROT = struct {
...@@ -9281,17 +9365,17 @@ pub const PTRACE = struct {...@@ -9281,17 +9365,17 @@ pub const PTRACE = struct {
9281 pub const GET_SYSCALL_INFO = 0x420e;9365 pub const GET_SYSCALL_INFO = 0x420e;
9282};9366};
92839367
9284/// A waiter for vectorized wait.9368/// For futex2_waitv and futex2_requeue. Arrays of `futex2_waitone` allow
9285pub const futex_waitv = extern struct {9369/// waiting on multiple futexes in one call.
9286 // Expected value at uaddr9370pub const futex2_waitone = extern struct {
9371 /// Expected value at uaddr, should match size of futex.
9287 val: u64,9372 val: u64,
9288 /// User address to wait on.9373 /// User address to wait on. Top-bits must be 0 on 32-bit.
9289 uaddr: u64,9374 uaddr: u64,
9290 /// Flags for this waiter.9375 /// Flags for this waiter.
9291 flags: u32,9376 flags: FUTEX2_FLAGS,
9292 /// Reserved member to preserve alignment.9377 /// Reserved member to preserve alignment.
9293 /// Should be 0.9378 __reserved: u32 = 0,
9294 __reserved: u32,
9295};9379};
92969380
9297pub const cache_stat_range = extern struct {9381pub const cache_stat_range = extern struct {
lib/std/os/linux/test.zig+220
...@@ -207,6 +207,226 @@ test "sysinfo" {...@@ -207,6 +207,226 @@ test "sysinfo" {
207 try expect(info.mem_unit <= std.heap.page_size_max);207 try expect(info.mem_unit <= std.heap.page_size_max);
208}208}
209209
210comptime {
211 std.debug.assert(128 == @as(u32, @bitCast(linux.FUTEX_OP{ .cmd = @enumFromInt(0), .private = true, .realtime = false })));
212 std.debug.assert(256 == @as(u32, @bitCast(linux.FUTEX_OP{ .cmd = @enumFromInt(0), .private = false, .realtime = true })));
213
214 // Check futex_param4 union is packed correctly
215 const param_union = linux.futex_param4{
216 .val2 = 0xaabbcc,
217 };
218 std.debug.assert(@intFromPtr(param_union.timeout) == 0xaabbcc);
219}
220
221test "futex v1" {
222 var lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1);
223 var rc: usize = 0;
224
225 // No-op wait, lock value is not expected value
226 rc = linux.futex(&lock.raw, .{ .cmd = .WAIT, .private = true }, 2, .{ .timeout = null }, null, 0);
227 try expectEqual(.AGAIN, linux.E.init(rc));
228
229 rc = linux.futex_4arg(&lock.raw, .{ .cmd = .WAIT, .private = true }, 2, null);
230 try expectEqual(.AGAIN, linux.E.init(rc));
231
232 // Short-fuse wait, timeout kicks in
233 rc = linux.futex(&lock.raw, .{ .cmd = .WAIT, .private = true }, 1, .{ .timeout = &.{ .sec = 0, .nsec = 2 } }, null, 0);
234 try expectEqual(.TIMEDOUT, linux.E.init(rc));
235
236 rc = linux.futex_4arg(&lock.raw, .{ .cmd = .WAIT, .private = true }, 1, &.{ .sec = 0, .nsec = 2 });
237 try expectEqual(.TIMEDOUT, linux.E.init(rc));
238
239 // Wakeup (no waiters)
240 rc = linux.futex(&lock.raw, .{ .cmd = .WAKE, .private = true }, 2, .{ .timeout = null }, null, 0);
241 try expectEqual(0, rc);
242
243 rc = linux.futex_3arg(&lock.raw, .{ .cmd = .WAKE, .private = true }, 2);
244 try expectEqual(0, rc);
245
246 // CMP_REQUEUE - val3 mismatch
247 rc = linux.futex(&lock.raw, .{ .cmd = .CMP_REQUEUE, .private = true }, 2, .{ .val2 = 0 }, null, 99);
248 try expectEqual(.AGAIN, linux.E.init(rc));
249
250 // CMP_REQUEUE - requeue (but no waiters, so ... not much)
251 {
252 const val3 = 1;
253 const wake_nr = 3;
254 const requeue_max = std.math.maxInt(u31);
255 var target_lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1);
256 rc = linux.futex(&lock.raw, .{ .cmd = .CMP_REQUEUE, .private = true }, wake_nr, .{ .val2 = requeue_max }, &target_lock.raw, val3);
257 try expectEqual(0, rc);
258 }
259
260 // WAKE_OP - just to see if we can construct the arguments ...
261 {
262 var lock2: std.atomic.Value(u32) = std.atomic.Value(u32).init(1);
263 const wake1_nr = 2;
264 const wake2_nr = 3;
265 const wake_op = linux.FUTEX_WAKE_OP{
266 .cmd = .ANDN,
267 .arg_shift = true,
268 .cmp = .LT,
269 .oparg = 4,
270 .cmdarg = 5,
271 };
272
273 rc = linux.futex(&lock.raw, .{ .cmd = .WAKE_OP, .private = true }, wake1_nr, .{ .val2 = wake2_nr }, &lock2.raw, @bitCast(wake_op));
274 try expectEqual(0, rc);
275 }
276
277 // WAIT_BITSET
278 {
279 // val1 return early
280 rc = linux.futex(&lock.raw, .{ .cmd = .WAIT_BITSET, .private = true }, 2, .{ .timeout = null }, null, 0xfff);
281 try expectEqual(.AGAIN, linux.E.init(rc));
282
283 // timeout wait
284 const timeout: linux.timespec = .{ .sec = 0, .nsec = 2 };
285 rc = linux.futex(&lock.raw, .{ .cmd = .WAIT_BITSET, .private = true }, 1, .{ .timeout = &timeout }, null, 0xfff);
286 try expectEqual(.TIMEDOUT, linux.E.init(rc));
287 }
288
289 // WAKE_BITSET
290 {
291 rc = linux.futex(&lock.raw, .{ .cmd = .WAKE_BITSET, .private = true }, 2, .{ .timeout = null }, null, 0xfff000);
292 try expectEqual(0, rc);
293
294 // bitmask must have at least 1 bit set:
295 rc = linux.futex(&lock.raw, .{ .cmd = .WAKE_BITSET, .private = true }, 2, .{ .timeout = null }, null, 0);
296 try expectEqual(.INVAL, linux.E.init(rc));
297 }
298}
299
300comptime {
301 std.debug.assert(2 == @as(u32, @bitCast(linux.FUTEX2_FLAGS{ .size = .U32, .private = false })));
302 std.debug.assert(128 == @as(u32, @bitCast(linux.FUTEX2_FLAGS{ .size = @enumFromInt(0), .private = true })));
303}
304
305test "futex2_waitv" {
306 const locks = [_]std.atomic.Value(u32){
307 std.atomic.Value(u32).init(1),
308 std.atomic.Value(u32).init(1),
309 std.atomic.Value(u32).init(1),
310 };
311
312 const futexes = [_]linux.futex2_waitone{
313 .{
314 .val = 1,
315 .uaddr = @intFromPtr(&locks[0].raw),
316 .flags = .{ .size = .U32, .private = true },
317 },
318 .{
319 .val = 1,
320 .uaddr = @intFromPtr(&locks[1].raw),
321 .flags = .{ .size = .U32, .private = true },
322 },
323 .{
324 .val = 1,
325 .uaddr = @intFromPtr(&locks[2].raw),
326 .flags = .{ .size = .U32, .private = true },
327 },
328 };
329
330 const timeout = linux.kernel_timespec{ .sec = 0, .nsec = 2 }; // absolute timeout, so this is 1970...
331 const rc = linux.futex2_waitv(&futexes, futexes.len, .{}, &timeout, .MONOTONIC);
332 switch (linux.E.init(rc)) {
333 .NOSYS => return error.SkipZigTest, // futex2_waitv added in kernel v5.16
334 else => |err| try expectEqual(.TIMEDOUT, err),
335 }
336}
337
338// Futex v2 API is only supported on recent kernels (v6.7), so skip tests if the syscalls
339// return ENOSYS.
340fn futex2_skip_if_unsupported() !void {
341 const lock: u32 = 0;
342 const rc = linux.futex2_wake(&lock, 0, 1, .{ .size = .U32, .private = true });
343 if (linux.E.init(rc) == .NOSYS) {
344 return error.SkipZigTest;
345 }
346}
347
348test "futex2_wait" {
349 var lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1);
350 var rc: usize = 0;
351 const mask = 0x1;
352
353 try futex2_skip_if_unsupported();
354
355 // The API for 8,16,64 bit futexes is defined, but as of kernel v6.14
356 // (at least) they're not implemented.
357 if (false) {
358 rc = linux.futex2_wait(&lock.raw, 1, mask, .{ .size = .U8, .private = true }, null, .MONOTONIC);
359 try expectEqual(.INVAL, linux.E.init(rc));
360
361 rc = linux.futex2_wait(&lock.raw, 1, mask, .{ .size = .U16, .private = true }, null, .MONOTONIC);
362 try expectEqual(.INVAL, linux.E.init(rc));
363
364 rc = linux.futex2_wait(&lock.raw, 1, mask, .{ .size = .U64, .private = true }, null, .MONOTONIC);
365 try expectEqual(.INVAL, linux.E.init(rc));
366 }
367
368 const flags = linux.FUTEX2_FLAGS{ .size = .U32, .private = true };
369 // no-wait, lock state mismatch
370 rc = linux.futex2_wait(&lock.raw, 2, mask, flags, null, .MONOTONIC);
371 try expectEqual(.AGAIN, linux.E.init(rc));
372
373 // hit timeout on wait
374 rc = linux.futex2_wait(&lock.raw, 1, mask, flags, &.{ .sec = 0, .nsec = 2 }, .MONOTONIC);
375 try expectEqual(.TIMEDOUT, linux.E.init(rc));
376
377 // timeout is absolute
378 {
379 var curr: linux.timespec = undefined;
380 rc = linux.clock_gettime(.MONOTONIC, &curr); // gettime() uses platform timespec
381 try expectEqual(0, rc);
382
383 // ... but futex2_wait always uses 64-bit timespec
384 var timeout: linux.kernel_timespec = .{
385 .sec = curr.sec,
386 .nsec = curr.nsec + 2,
387 };
388 rc = linux.futex2_wait(&lock.raw, 1, mask, flags, &timeout, .MONOTONIC);
389 try expectEqual(.TIMEDOUT, linux.E.init(rc));
390 }
391
392 rc = linux.futex2_wait(&lock.raw, 1, mask, flags, &.{ .sec = 0, .nsec = 2 }, .REALTIME);
393 try expectEqual(.TIMEDOUT, linux.E.init(rc));
394}
395
396test "futex2_wake" {
397 var lock: std.atomic.Value(u32) = std.atomic.Value(u32).init(1);
398
399 try futex2_skip_if_unsupported();
400
401 const rc = linux.futex2_wake(&lock.raw, 0xFF, 1, .{ .size = .U32, .private = true });
402 try expectEqual(0, rc);
403}
404
405test "futex2_requeue" {
406 try futex2_skip_if_unsupported();
407
408 const locks = [_]std.atomic.Value(u32){
409 std.atomic.Value(u32).init(1),
410 std.atomic.Value(u32).init(1),
411 };
412
413 const futexes = [_]linux.futex2_waitone{
414 .{
415 .val = 1,
416 .uaddr = @intFromPtr(&locks[0].raw),
417 .flags = .{ .size = .U32, .private = true },
418 },
419 .{
420 .val = 1,
421 .uaddr = @intFromPtr(&locks[1].raw),
422 .flags = .{ .size = .U32, .private = true },
423 },
424 };
425
426 const rc = linux.futex2_requeue(&futexes, .{}, 2, 2);
427 try expectEqual(0, rc);
428}
429
210test {430test {
211 _ = linux.IoUring;431 _ = linux.IoUring;
212}432}