rtl: add racy_read

This commit is contained in:
Loïc Grégoire
2026-10-24 22:20:21 -04:00
parent 0ffaa8df5b
commit 8958854a28
7 changed files with 16 additions and 21 deletions
+5
View File
@@ -30,3 +30,8 @@ pub fn read_struct(
return result;
}
/// Racily read a pointer on purpose.
pub fn racy_read(comptime T: type, ptr: *T) T {
return @atomicLoad(T, ptr, .unordered);
}
+4 -4
View File
@@ -24,8 +24,8 @@ pub fn SeqLockType(comptime T: type) type {
continue;
}
// Relaxed load memcpy is needed here since data races are UB.
rtl.barrier.atomic_load_memcpy(&ret, &self.raw, .monotonic);
// Unordered load memcpy is needed here since data races are UB.
rtl.barrier.atomic_load_memcpy(&ret, &self.raw, .unordered);
// Ensure the sequence read happens *after* the data is fully
// loaded.
@@ -49,8 +49,8 @@ pub fn SeqLockType(comptime T: type) type {
// is incremented.
rtl.barrier.fence(.release);
// Relaxed store memcpy is needed here since data races are UB.
rtl.barrier.atomic_store_memcpy(&self.raw, &data, .monotonic);
// Unordered store memcpy is needed here since data races are UB.
rtl.barrier.atomic_store_memcpy(&self.raw, &data, .unordered);
self.sequence.store(seq + 2, .release);
}
+2 -3
View File
@@ -144,13 +144,12 @@ const Pool = struct {
/// Return whether or not a new worker thread should be added to this pool.
fn should_grow(self: *Pool) bool {
// NOTE: lockless racy check whether or not the queue is empty
// Lockless racy check whether or not the queue is empty
// and its active count.
// This is fine as this is best effort anyway and is a heuristic.
const is_empty = @atomicLoad(
const is_empty = rtl.mem.racy_read(
*rtl.List.Entry,
&self.queue.items.head.next,
.monotonic,
) == &self.queue.items.head;
const active = self.queue.active.load(.monotonic);
+1 -5
View File
@@ -293,11 +293,7 @@ pub fn RingBufferType(data_size_bits: usize, avg_msg_bits: usize) type {
// Loading the ID here is purposely racey because
// we check if the descriptor is valid anyway.
const id: Id = @truncate(
@atomicLoad(
usize,
@as(*usize, @ptrCast(&blk.id)),
.monotonic,
),
rtl.mem.racy_read(usize, @ptrCast(&blk.id)),
);
const res = self.read_desc(id);
desc = res.desc;
+1 -2
View File
@@ -175,10 +175,9 @@ pub const Mutex = struct {
var gas: usize = 0;
while (@atomicLoad(
while (rtl.mem.racy_read(
?*ke.Thread,
cpu_curtd,
.unordered,
) == owner and gas < optimistic_spins) : (gas += 1) {
if (m.state.load(.monotonic) == State.uncontended) {
// Got released, retry acquiring it.
+1 -5
View File
@@ -1779,11 +1779,7 @@ pub fn idle(_: ?*anyopaque) noreturn {
while (true) {
// Racy but only a hint anyway
const next = @atomicLoad(
?*ke.Thread,
&cpu.next_thread,
.monotonic,
);
const next = rtl.mem.racy_read(?*ke.Thread, &cpu.next_thread);
if (cpu.load.load(.monotonic) != 0 or next != null) {
std.atomic.spinLoopHint();
+2 -2
View File
@@ -736,7 +736,7 @@ pub const Zone = struct {
// Local depot is empty, rebalance with the zone depot.
// Purposefully race against cpu_depot_size because locking is
// tricky here, we may get a stale value but it is harmless.
const n = @atomicLoad(usize, &self.cpu_depot_size, .monotonic);
const n = rtl.mem.racy_read(usize, &self.cpu_depot_size);
self.alloc_depot_rebalance(n, cpu);
@@ -924,7 +924,7 @@ pub const Zone = struct {
// Local depot is empty, rebalance with the zone depot.
// Race explained in alloc.
const n = @atomicLoad(usize, &self.cpu_depot_size, .monotonic);
const n = rtl.mem.racy_read(usize, &self.cpu_depot_size);
self.free_depot_rebalance(n, cpu);