Skip to content

Commit 14ad837

Browse files
authored
Merge pull request #23464 from rootbeer/futex-casts
Linux futex (v1 and v2) API fixes, tests and Ziggification
2 parents cf1a7bb + 89d15a8 commit 14ad837

File tree

4 files changed

+405
-101
lines changed

4 files changed

+405
-101
lines changed

lib/std/Thread.zig

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1539,10 +1539,10 @@ const LinuxThreadImpl = struct {
15391539
continue;
15401540
}
15411541

1542-
switch (linux.E.init(linux.futex_wait(
1542+
switch (linux.E.init(linux.futex_4arg(
15431543
&self.thread.child_tid.raw,
1544-
linux.FUTEX.WAIT,
1545-
tid,
1544+
.{ .cmd = .WAIT, .private = false },
1545+
@bitCast(tid),
15461546
null,
15471547
))) {
15481548
.SUCCESS => continue,

lib/std/Thread/Futex.zig

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -262,10 +262,10 @@ const LinuxImpl = struct {
262262
ts.nsec = @as(@TypeOf(ts.nsec), @intCast(timeout_ns % std.time.ns_per_s));
263263
}
264264

265-
const rc = linux.futex_wait(
266-
@as(*const i32, @ptrCast(&ptr.raw)),
267-
linux.FUTEX.PRIVATE_FLAG | linux.FUTEX.WAIT,
268-
@as(i32, @bitCast(expect)),
265+
const rc = linux.futex_4arg(
266+
&ptr.raw,
267+
.{ .cmd = .WAIT, .private = true },
268+
expect,
269269
if (timeout != null) &ts else null,
270270
);
271271

@@ -284,10 +284,10 @@ const LinuxImpl = struct {
284284
}
285285

286286
fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void {
287-
const rc = linux.futex_wake(
288-
@as(*const i32, @ptrCast(&ptr.raw)),
289-
linux.FUTEX.PRIVATE_FLAG | linux.FUTEX.WAKE,
290-
std.math.cast(i32, max_waiters) orelse std.math.maxInt(i32),
287+
const rc = linux.futex_3arg(
288+
&ptr.raw,
289+
.{ .cmd = .WAKE, .private = true },
290+
@min(max_waiters, std.math.maxInt(i32)),
291291
);
292292

293293
switch (linux.E.init(rc)) {

lib/std/os/linux.zig

Lines changed: 174 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -673,66 +673,87 @@ pub fn fallocate(fd: i32, mode: i32, offset: i64, length: i64) usize {
673673
}
674674
}
675675

676-
pub fn futex_wait(uaddr: *const i32, futex_op: u32, val: i32, timeout: ?*const timespec) usize {
677-
return syscall4(.futex, @intFromPtr(uaddr), futex_op, @as(u32, @bitCast(val)), @intFromPtr(timeout));
676+
// The 4th parameter to the v1 futex syscall can either be an optional
677+
// pointer to a timespec, or a uint32, depending on which "op" is being
678+
// performed.
679+
pub 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.
690+
pub 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);
678692
}
679693

680-
pub fn futex_wake(uaddr: *const i32, futex_op: u32, val: i32) usize {
681-
return syscall3(.futex, @intFromPtr(uaddr), futex_op, @as(u32, @bitCast(val)));
694+
/// Three-argument variation of the v1 futex call. Only suitable for a
695+
/// futex_op that ignores the remaining arguments (e.g., FUTUX_OP.WAKE).
696+
pub 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);
682698
}
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).
702+
pub 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.
685707
/// The thread wakes if a futex_wake() is performed at any uaddr.
686-
/// The syscall returns immediately if any waiter has *uaddr != val.
687-
/// timeout is an optional timeout value for the operation.
688-
/// Each waiter has individual flags.
689-
/// The `flags` argument for the syscall should be used solely for specifying
690-
/// the timeout as realtime, if needed.
691-
/// Flags for private futexes, sizes, etc. should be used on the
692-
/// individual flags of each waiter.
708+
/// The syscall returns immediately if any futex has *uaddr != val.
709+
/// timeout is an optional, absolute timeout value for the operation.
710+
/// The `flags` argument is for future use and currently should be `.{}`.
711+
/// Flags for private futexes, sizes, etc. should be set on the
712+
/// individual flags of each `futex2_waitone`.
693713
///
694714
/// Returns the array index of one of the woken futexes.
695715
/// No further information is provided: any number of other futexes may also
696716
/// have been woken by the same event, and if more than one futex was woken,
697717
/// the returned index may refer to any one of them.
698718
/// (It is not necessaryily the futex with the smallest index, nor the one
699719
/// most recently woken, nor...)
720+
///
721+
/// Requires at least kernel v5.16.
700722
pub fn futex2_waitv(
701-
/// List of futexes to wait on.
702-
waiters: [*]futex_waitv,
703-
/// Length of `waiters`.
723+
futexes: [*]const futex2_waitone,
724+
/// Length of `futexes`. Max of FUTEX2_WAITONE_MAX.
704725
nr_futexes: u32,
705-
/// Flag for timeout (monotonic/realtime).
706-
flags: u32,
707-
/// Optional absolute timeout.
708-
timeout: ?*const timespec,
726+
flags: FUTEX2_FLAGS_WAITV,
727+
/// Optional absolute timeout. Always 64-bit, even on 32-bit platforms.
728+
timeout: ?*const kernel_timespec,
709729
/// Clock to be used for the timeout, realtime or monotonic.
710730
clockid: clockid_t,
711731
) usize {
712732
return syscall5(
713733
.futex_waitv,
714-
@intFromPtr(waiters),
734+
@intFromPtr(futexes),
715735
nr_futexes,
716-
flags,
736+
@as(u32, @bitCast(flags)),
717737
@intFromPtr(timeout),
718-
@bitCast(@as(isize, @intFromEnum(clockid))),
738+
@intFromEnum(clockid),
719739
);
720740
}
721741

722-
/// Wait on a futex.
723-
/// Identical to the traditional `FUTEX.FUTEX_WAIT_BITSET` op, except it is part of the
724-
/// futex2 familiy of calls.
742+
/// Wait on a single futex.
743+
/// Identical to the futex v1 `FUTEX.FUTEX_WAIT_BITSET` op, except it is part of the
744+
/// futex2 family of calls.
745+
///
746+
/// Requires at least kernel v6.7.
725747
pub fn futex2_wait(
726748
/// Address of the futex to wait on.
727749
uaddr: *const anyopaque,
728750
/// Value of `uaddr`.
729751
val: usize,
730-
/// Bitmask.
752+
/// Bitmask to match against incoming wakeup masks. Must not be zero.
731753
mask: usize,
732-
/// `FUTEX2` flags.
733-
flags: u32,
734-
/// Optional absolute timeout.
735-
timeout: ?*const timespec,
754+
flags: FUTEX2_FLAGS,
755+
/// Optional absolute timeout. Always 64-bit, even on 32-bit platforms.
756+
timeout: ?*const kernel_timespec,
736757
/// Clock to be used for the timeout, realtime or monotonic.
737758
clockid: clockid_t,
738759
) usize {
@@ -741,52 +762,55 @@ pub fn futex2_wait(
741762
@intFromPtr(uaddr),
742763
val,
743764
mask,
744-
flags,
765+
@as(u32, @bitCast(flags)),
745766
@intFromPtr(timeout),
746-
@bitCast(@as(isize, @intFromEnum(clockid))),
767+
@intFromEnum(clockid),
747768
);
748769
}
749770

750-
/// Wake a number of futexes.
751-
/// Identical to the traditional `FUTEX.FUTEX_WAIT_BITSET` op, except it is part of the
771+
/// Wake (subset of) waiters on given futex.
772+
/// Identical to the traditional `FUTEX.FUTEX_WAKE_BITSET` op, except it is part of the
752773
/// futex2 family of calls.
774+
///
775+
/// Requires at least kernel v6.7.
753776
pub fn futex2_wake(
754-
/// Address of the futex(es) to wake.
777+
/// Futex to wake
755778
uaddr: *const anyopaque,
756-
/// Bitmask
779+
/// Bitmask to match against waiters.
757780
mask: usize,
758-
/// Number of the futexes to wake.
759-
nr: i32,
760-
/// `FUTEX2` flags.
761-
flags: u32,
781+
/// Maximum number of waiters on the futex to wake.
782+
nr_wake: i32,
783+
flags: FUTEX2_FLAGS,
762784
) usize {
763785
return syscall4(
764786
.futex_wake,
765787
@intFromPtr(uaddr),
766788
mask,
767-
@bitCast(@as(isize, nr)),
768-
flags,
789+
@as(u32, @bitCast(nr_wake)),
790+
@as(u32, @bitCast(flags)),
769791
);
770792
}
771793

772-
/// Requeue a waiter from one futex to another.
794+
/// Wake and/or requeue waiter(s) from one futex to another.
773795
/// Identical to `FUTEX.CMP_REQUEUE`, except it is part of the futex2 family of calls.
796+
///
797+
/// Requires at least kernel v6.7.
774798
pub fn futex2_requeue(
775-
/// Array describing the source and destination futex.
776-
waiters: [*]futex_waitv,
777-
/// Unused.
778-
flags: u32,
779-
/// Number of futexes to wake.
799+
/// The source and destination futexes. Must be a 2-element array.
800+
waiters: [*]const futex2_waitone,
801+
/// Currently unused.
802+
flags: FUTEX2_FLAGS_REQUEUE,
803+
/// Maximum number of waiters to wake on the source futex.
780804
nr_wake: i32,
781-
/// Number of futexes to requeue.
805+
/// Maximum number of waiters to transfer to the destination futex.
782806
nr_requeue: i32,
783807
) usize {
784808
return syscall4(
785809
.futex_requeue,
786810
@intFromPtr(waiters),
787-
flags,
788-
@bitCast(@as(isize, nr_wake)),
789-
@bitCast(@as(isize, nr_requeue)),
811+
@as(u32, @bitCast(flags)),
812+
@as(u32, @bitCast(nr_wake)),
813+
@as(u32, @bitCast(nr_requeue)),
790814
);
791815
}
792816

@@ -3385,37 +3409,97 @@ pub const FALLOC = struct {
33853409
pub const FL_UNSHARE_RANGE = 0x40;
33863410
};
33873411

3388-
pub const FUTEX = struct {
3389-
pub const WAIT = 0;
3390-
pub const WAKE = 1;
3391-
pub const FD = 2;
3392-
pub const REQUEUE = 3;
3393-
pub const CMP_REQUEUE = 4;
3394-
pub const WAKE_OP = 5;
3395-
pub const LOCK_PI = 6;
3396-
pub const UNLOCK_PI = 7;
3397-
pub const TRYLOCK_PI = 8;
3398-
pub const WAIT_BITSET = 9;
3399-
pub const WAKE_BITSET = 10;
3400-
pub const WAIT_REQUEUE_PI = 11;
3401-
pub const CMP_REQUEUE_PI = 12;
3402-
3403-
pub const PRIVATE_FLAG = 128;
3404-
3405-
pub const CLOCK_REALTIME = 256;
3406-
3407-
/// Max numbers of elements in a `futex_waitv` array.
3408-
pub const WAITV_MAX = 128;
3409-
};
3410-
3411-
pub const FUTEX2 = struct {
3412-
pub const SIZE_U8 = 0x00;
3413-
pub const SIZE_U16 = 0x01;
3414-
pub const SIZE_U32 = 0x02;
3415-
pub const SIZE_U64 = 0x03;
3416-
pub const NUMA = 0x04;
3417-
3418-
pub const PRIVATE = FUTEX.PRIVATE_FLAG;
3412+
// Futex v1 API commands. See futex man page for each command's
3413+
// interpretation of the futex arguments.
3414+
pub const FUTEX_COMMAND = enum(u7) {
3415+
WAIT = 0,
3416+
WAKE = 1,
3417+
FD = 2,
3418+
REQUEUE = 3,
3419+
CMP_REQUEUE = 4,
3420+
WAKE_OP = 5,
3421+
LOCK_PI = 6,
3422+
UNLOCK_PI = 7,
3423+
TRYLOCK_PI = 8,
3424+
WAIT_BITSET = 9,
3425+
WAKE_BITSET = 10,
3426+
WAIT_REQUEUE_PI = 11,
3427+
CMP_REQUEUE_PI = 12,
3428+
};
3429+
3430+
/// Futex v1 API command and flags for the `futex_op` parameter
3431+
pub const FUTEX_OP = packed struct(u32) {
3432+
cmd: FUTEX_COMMAND,
3433+
private: bool,
3434+
realtime: bool = false, // realtime clock vs. monotonic clock
3435+
_reserved: u23 = 0,
3436+
};
3437+
3438+
/// Futex v1 FUTEX_WAKE_OP `val3` operation:
3439+
pub const FUTEX_WAKE_OP = packed struct(u32) {
3440+
cmd: FUTEX_WAKE_OP_CMD,
3441+
/// From C API `FUTEX_OP_ARG_SHIFT`: Use (1 << oparg) as operand
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.
3449+
pub 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
3463+
pub 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.
3473+
pub 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.
3478+
pub 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.
3486+
pub 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.
3491+
pub 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).
3497+
pub 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,
34193503
};
34203504

34213505
pub const PROT = struct {
@@ -9281,17 +9365,17 @@ pub const PTRACE = struct {
92819365
pub const GET_SYSCALL_INFO = 0x420e;
92829366
};
92839367

9284-
/// A waiter for vectorized wait.
9285-
pub const futex_waitv = extern struct {
9286-
// Expected value at uaddr
9368+
/// For futex2_waitv and futex2_requeue. Arrays of `futex2_waitone` allow
9369+
/// waiting on multiple futexes in one call.
9370+
pub const futex2_waitone = extern struct {
9371+
/// Expected value at uaddr, should match size of futex.
92879372
val: u64,
9288-
/// User address to wait on.
9373+
/// User address to wait on. Top-bits must be 0 on 32-bit.
92899374
uaddr: u64,
92909375
/// Flags for this waiter.
9291-
flags: u32,
9376+
flags: FUTEX2_FLAGS,
92929377
/// Reserved member to preserve alignment.
9293-
/// Should be 0.
9294-
__reserved: u32,
9378+
__reserved: u32 = 0,
92959379
};
92969380

92979381
pub const cache_stat_range = extern struct {

0 commit comments

Comments
 (0)