all: refactor coding style

This commit is contained in:
Loïc Grégoire
2026-08-05 17:43:46 -04:00
parent 2a09642f4d
commit bec872bc26
31 changed files with 887 additions and 330 deletions
+22 -4
View File
@@ -25,7 +25,12 @@ const ThreadFrame = extern struct {
extern fn asm_thread_entry() void;
extern fn do_context_switch(old: *ThreadContext, new: *ThreadContext, lock: *u8, switching: *bool) callconv(.c) void;
extern fn do_context_switch(
old: *ThreadContext,
new: *ThreadContext,
lock: *u8,
switching: *bool,
) callconv(.c) void;
extern fn do_context_load(td: *ThreadContext) callconv(.c) void;
pub const ThreadContext = extern struct {
@@ -38,7 +43,12 @@ pub const ThreadContext = extern struct {
entry_fn(@ptrFromInt(arg));
}
pub fn init(stack: r.VAddr, stack_size: usize, entry: *const fn (?*anyopaque) void, arg: ?*anyopaque) @This() {
pub fn init(
stack: r.VAddr,
stack_size: usize,
entry: *const fn (?*anyopaque) void,
arg: ?*anyopaque,
) @This() {
var ctx: @This() = undefined;
var sp: usize = stack + stack_size;
@@ -58,9 +68,17 @@ pub const ThreadContext = extern struct {
return ctx;
}
pub fn switch_to(self: *ThreadContext, new: *ThreadContext) callconv(.c) void {
pub fn switch_to(
self: *ThreadContext,
new: *ThreadContext,
) callconv(.c) void {
const thread: *ke.Thread = @alignCast(@fieldParentPtr("context", self));
do_context_switch(self, new, &thread.lock.inner.locked.raw, &thread.switching.raw);
do_context_switch(
self,
new,
&thread.lock.inner.locked.raw,
&thread.switching.raw,
);
}
pub fn load(self: *ThreadContext) callconv(.c) void {
+5 -1
View File
@@ -40,7 +40,11 @@ pub fn CpuLocal(comptime T: type, comptime init: T) type {
}
/// Wraps around CPU-local data with a designated symbol name.
pub fn ExportedCpuLocal(comptime T: type, comptime init: T, comptime name: []const u8) type {
pub fn ExportedCpuLocal(
comptime T: type,
comptime init: T,
comptime name: []const u8,
) type {
const S = struct {
var storage: T linksection(".data.percpu") = init;
+8 -6
View File
@@ -6,7 +6,7 @@ const ke = r.ke;
const ki = ke.private;
/// Deferred Procedure Call (DPC) structure.
/// Used for scheduling work to be done when IPL is lowered below `ke.Ipl.Dispatch`.
/// Used for scheduling work to be done when IPL is lowered below `.Dispatch`.
pub const Dpc = struct {
/// Entry into per-CPU DPC queue
link: rtl.List.Entry,
@@ -78,7 +78,8 @@ fn dispatch_queue(cpu: u32) void {
ipl_cpu.ipl = .Dispatch;
// Mark as handled, this must be done before enabling interrupts or we will race.
// Mark as handled, this must be done before enabling interrupts or we will
// race.
ki.ipl.clear_softint_pending(cpu, .Dispatch);
_ = ki.impl.enable_interrupts();
@@ -100,8 +101,9 @@ fn dispatch_queue(cpu: u32) void {
dpc = @fieldParentPtr("link", first_elem);
dpc.inserted.store(false, .release);
// An interrupt which would enqueue this DPC could occur between loading the argument and calling the routine,
// which is why we capture it here to ensure that we get the intended context.
// An interrupt which would enqueue this DPC could occur between
// loading the argument and calling the routine, which is why we capture
// it here to ensure that we get the intended context.
const arg = dpc.arg;
dpc_cpu.lock.release(ipl);
@@ -148,8 +150,8 @@ fn dispatch_queue(cpu: u32) void {
/// Dispatch the DPC queue on `cpu`.
pub fn dispatch(cpu: u32) void {
// DPC processing is done at Ipl.Dispatch
// We can't call lower/raise here because they might call us again recursively.
// DPC processing is done at IPL Dispatch.
// Don't call lower/raise here because they might call us again recursively.
var mycpu = cpu;
const old_ipl = ki.ipl.percpu.remote(mycpu).ipl;
const int_state = ki.impl.disable_interrupts();
+5 -1
View File
@@ -37,7 +37,11 @@ pub fn init(_: ?*anyopaque) linksection(r.init) void {
ki.sched.percpu.local().current_thread = &thread0;
ki.sched.percpu.local().idle_thread = &thread0;
std.log.info("Zag for {s} ({s}), cmdline is \"{?s}\"", .{ pl.name, arch.name, r.boot_info.cmdline });
std.log.info("Zag for {s} ({s}), cmdline is \"{?s}\"", .{
pl.name,
arch.name,
r.boot_info.cmdline,
});
ex.init();
ki.sched.idle(null);
+7 -3
View File
@@ -35,7 +35,11 @@ const percpu = ke.CpuLocal(PerCpu, undefined);
/// The function will be executed synchronously, i.e this will block until
/// the function finishes running. The callback is called with the sender cpu
/// and provided argument as arguments.
pub fn unicast(cpu: u32, func: *const fn (u32, ?*anyopaque) void, arg: ?*anyopaque) void {
pub fn unicast(
cpu: u32,
func: *const fn (u32, ?*anyopaque) void,
arg: ?*anyopaque,
) void {
const ipl = ke.ipl.raise(.Dispatch);
const curcpu = percpu.local();
const remote = percpu.remote(cpu);
@@ -93,8 +97,8 @@ pub fn ipi_handler() void {
while (true) {
// Mark the current CPU as processing IPIs, if the CAS fails here,
// then it's probably some kind of IPI that was sent to force us to check
// software interrupts (like a rescheduling IPI).
// then it's probably some kind of IPI that was sent to force us to
// check software interrupts (like a rescheduling IPI).
const val = curcpu.state.cmpxchgStrong(
.Pending,
.Running,
+15 -4
View File
@@ -21,7 +21,11 @@ const DebugWriter = struct {
};
}
fn drain(_: *std.Io.Writer, data: []const []const u8, _: usize) std.Io.Writer.Error!usize {
fn drain(
_: *std.Io.Writer,
data: []const []const u8,
_: usize,
) std.Io.Writer.Error!usize {
var total_written: usize = 0;
for (data) |slice| {
for (slice) |byte| {
@@ -37,7 +41,10 @@ const DebugWriter = struct {
// Messages are on average 2^5 = 32 bytes.
const avg_msg_size_bits = 5;
pub var ringbuffer = ki.log_ring.RingBuffer(config.log_buffer_shift, avg_msg_size_bits).init();
pub var ringbuffer = ki.log_ring.RingBuffer(
config.log_buffer_shift,
avg_msg_size_bits,
).init();
// Initialize manually because we need this ASAP.
pub var event: ke.Event = .{
@@ -46,7 +53,10 @@ pub var event: ke.Event = .{
.signaled = 0,
.type = .Notification,
.waitblocks = .{
.head = .{ .next = &event.hdr.waitblocks.head, .prev = &event.hdr.waitblocks.head },
.head = .{
.next = &event.hdr.waitblocks.head,
.prev = &event.hdr.waitblocks.head,
},
},
},
};
@@ -75,7 +85,8 @@ pub fn log(
res.info.length = @truncate(required_len);
// Format the log message into the reserved buffer.
res.buf = std.fmt.bufPrint(res.buf, scope_str ++ fmt ++ "\n", args) catch return;
res.buf = std.fmt.bufPrint(res.buf, scope_str ++ fmt ++ "\n", args) catch
return;
ringbuffer.publish(res);
+17 -3
View File
@@ -53,7 +53,13 @@ fn pcpu_init() linksection(r.init) void {
local.valid_states = @splat(.init(0));
local.senders = .init(false);
local.states = @splat(.{ .base = 0, .npages = 0, .state = .init(slot_free), .payload = undefined, .link = undefined });
local.states = @splat(.{
.base = 0,
.npages = 0,
.state = .init(slot_free),
.payload = undefined,
.link = undefined,
});
}
comptime {
@@ -66,7 +72,12 @@ fn allocate_slot(cpu: *PerCpu) ?usize {
for (0.., &cpu.states) |slot, *state| {
if (state.state.load(.monotonic) != slot_free) continue;
if (state.state.cmpxchgStrong(slot_free, slot_reserved, .acquire, .monotonic) == null) {
if (state.state.cmpxchgStrong(
slot_free,
slot_reserved,
.acquire,
.monotonic,
) == null) {
return slot;
}
}
@@ -167,7 +178,10 @@ pub fn submit(state: ShootdownState, target_mask: ke.CpuMask) !void {
while (iter.next()) |bit| {
if (bit == curcpu) continue;
_ = cpu.valid_states[bit].fetchOr(@as(u64, 1) << @truncate(slot), .release);
_ = cpu.valid_states[bit].fetchOr(
@as(u64, 1) << @truncate(slot),
.release,
);
ki.ipl.set_softint_pending(@truncate(bit), .Dispatch);
+32 -17
View File
@@ -77,7 +77,8 @@ pub const Tracker = struct {
pub var full_scans: std.atomic.Value(usize) = .init(0);
/// Scan all CPUs and return the minimum observed value.
/// If `should_wait` is true, this will spinloop (or block) until all CPUs have reached the given goal.
/// If `should_wait` is true, this will spinloop (or block)
/// until all CPUs have reached the given goal.
fn scan(dom: *Domain, goal: Sequence, clock: Clock, should_wait: bool) Sequence {
rtl.barrier.fence(.seq_cst);
@@ -162,7 +163,12 @@ fn scan(dom: *Domain, goal: Sequence, clock: Clock, should_wait: bool) Sequence
break;
}
ki.turnstile.block(ts, cpu, .{ .shared = &cpu.stalled }, .Exclusive);
ki.turnstile.block(
ts,
cpu,
.{ .shared = &cpu.stalled },
.Exclusive,
);
ke.ipl.lower(ipl);
seq = cpu.stall_seq.load(.monotonic);
@@ -256,8 +262,9 @@ pub fn advance(dom: *Domain) Sequence {
/// Pretend-advance the write sequence and return the value for use as a wait goal.
/// It is guaranteed that all previous memory writes made by
/// the calling thread are visible. The global clock isn't advanced unlike `advance`,
/// it is only when commit() is called.
/// the calling thread are visible.
/// The global clock isn't advanced unlike `advance`, it is only when commit()
/// is called.
pub fn deferred_advance(dom: *Domain) Sequence {
rtl.barrier.fence(.seq_cst);
@@ -267,7 +274,12 @@ pub fn deferred_advance(dom: *Domain) Sequence {
/// Actually advance the global clock to a value previously returned by
/// deferred_advance().
pub fn deferred_advance_commit(dom: *Domain, seq: Sequence) void {
_ = dom.clock.write_seq.cmpxchgStrong(seq - seq_incr, seq, .monotonic, .monotonic);
_ = dom.clock.write_seq.cmpxchgStrong(
seq - seq_incr,
seq,
.monotonic,
.monotonic,
);
}
/// Enter a read section.
@@ -276,18 +288,20 @@ fn enter_internal(dom: *Domain) Sequence {
// Store the currently observed write sequence into our CPU state.
// Subsequent loads must not be re-ordered w.r.t to the store here.
// On AMD64, we can simply use XADD as this is faster and provides the same guarantees,
// on other architectures we have to rely on a full (seq_cst) fence.
// The add operation works because cpu.current_seq is always only 0 (seq_invalid)
// when we write to it.
// On AMD64, we can simply use XADD as this is faster and provides the
// same guarantees, on other architectures we have to rely on a full fence.
// The add operation works because cpu.current_seq is always only 0
// (seq_invalid) when we write to it.
//
// There can be a large pause between the load from the domain write sequence
// and the store to the per-cpu state because of e.g an interrupt. This could lead
// to a state where another CPU advances the global write sequence and current_seq
// becomes lower than the global write sequence. This is not an issue with the ordering
// in scan(), which also relies on a full memory barrier, as it is guaranteed that the
// CPU was treated as inactive and cannot possibly hold a reference to anything the
// poll declared reclaimable. See scan().
// There can be a large pause between the load from the domain write
// sequence and the store to the per-cpu state because of e.g an interrupt.
// This could lead to a state where another CPU advances
// the global write sequence and current_seq ends up lower
// than the global write sequence.
// This is not an issue with the ordering in scan(), which also relies
// on a full memory barrier, as it is guaranteed that the CPU was treated as
// inactive and cannot possibly hold a reference to anything the poll
// declared reclaimable. See scan().
std.debug.assert(cpu.current_seq.load(.monotonic) == seq_invalid);
const wr_seq = dom.clock.write_seq.load(.monotonic);
@@ -434,7 +448,8 @@ pub fn exit_preempt(dom: *Domain, tracker: *Tracker) void {
}
if (wake) {
if (turnstile) |ts| ki.turnstile.wakeup(ts, .Exclusive, ts.waiters, null);
if (turnstile) |ts|
ki.turnstile.wakeup(ts, .Exclusive, ts.waiters, null);
}
ki.turnstile.exit(cpu);
+28 -5
View File
@@ -176,7 +176,12 @@ pub const QSpinLock = LockTemplate(struct {
pub fn acquire_no_ipl(self: *Self) void {
// Fast path: try to acquire the lock.
var v = self.data.val.cmpxchgStrong(0, locked_val, .acquire, .monotonic) orelse return;
var v = self.data.val.cmpxchgStrong(
0,
locked_val,
.acquire,
.monotonic,
) orelse return;
// Medium path: if only pending is set, the pending waiter is
// in the process of clearing pending and setting `locked` to take
@@ -229,7 +234,12 @@ pub const QSpinLock = LockTemplate(struct {
}
pub fn try_acquire_no_ipl(self: *Self) bool {
return self.data.val.cmpxchgStrong(0, locked_val, .acquire, .monotonic) == null;
return self.data.val.cmpxchgStrong(
0,
locked_val,
.acquire,
.monotonic,
) == null;
}
pub fn is_locked(self: *Self) bool {
@@ -247,7 +257,12 @@ pub const QSpinLock = LockTemplate(struct {
if (idx >= 4) {
// Exhausted all indices, spin.
while (true) {
_ = self.data.val.cmpxchgWeak(0, locked_val, .acquire, .monotonic) orelse break;
_ = self.data.val.cmpxchgWeak(
0,
locked_val,
.acquire,
.monotonic,
) orelse break;
std.atomic.spinLoopHint();
}
@@ -266,7 +281,10 @@ pub const QSpinLock = LockTemplate(struct {
// Store cpu + 1 so that we can distinguish from an empty tail field.
rtl.barrier.fence(.release);
const tail: u16 = @bitCast(Tail{ .cpu = @intCast(ke.cpu.current() + 1), .idx = @intCast(idx) });
const tail: u16 = @bitCast(Tail{
.cpu = @intCast(ke.cpu.current() + 1),
.idx = @intCast(idx),
});
const old: Tail = @bitCast(self.data.split.tail.swap(tail, .acq_rel));
if (old.cpu != 0) {
@@ -293,7 +311,12 @@ pub const QSpinLock = LockTemplate(struct {
if ((v & tail_mask) == @as(u32, tail) << 16) {
// We are the only waiter in the queue.
// Try to claim the lock and clear the tail atomically.
if (self.data.val.cmpxchgStrong(v, locked_val, .acquire, .monotonic) == null) {
if (self.data.val.cmpxchgStrong(
v,
locked_val,
.acquire,
.monotonic,
) == null) {
return;
}
}
+9 -2
View File
@@ -123,12 +123,19 @@ pub const Thread = struct {
smr_sections: rtl.List,
/// Initialize a thread.
/// - `stack`: Address of the **base** of the stack on which the initial context for the thread is built
/// - `stack`: Address of the base of the stack used by the thread.
/// - `stack_size`: Size of the stack
/// - `prio`: Base priority of the thread
/// - `entry`: Entry point of the thread
/// - `arg`: Extraneous argument to be passed to `entry`
pub fn init(thread: *Thread, stack: r.VAddr, stack_size: usize, prio: u8, entry: *const fn (?*anyopaque) void, arg: ?*anyopaque) void {
pub fn init(
thread: *Thread,
stack: r.VAddr,
stack_size: usize,
prio: u8,
entry: *const fn (?*anyopaque) void,
arg: ?*anyopaque,
) void {
thread.* = .{
.context = .init(stack, stack_size, entry, arg),
.lock = .init(),
+6 -6
View File
@@ -18,11 +18,9 @@ pub const TimeCounter = struct {
quality: i16,
/// Mask to apply to the counter value.
mask: u64,
/// Binary scaling exponent for fast ns conversion (0 if unused).
/// See `ticks_to_ns`.
/// Binary scaling exponent for fast ns conversion. See `ticks_to_ns`.
p: u64,
/// Scaled nanoseconds-per-tick multiplier for fast ns conversion (0 if unused).
/// See `ticks_to_ns`.
/// Scaled for fast ns conversion (0 if unused). See `ticks_to_ns`.
n: u64,
};
@@ -59,10 +57,12 @@ fn ticks_to_ns(tc: *TimeCounter, count: u64) u64 {
/// Register a time-keeping source.
/// The TimeCounter will be used if it is the highest quality one available.
/// This function cannot be safely called when the hardware timer has been started, as data races on `best_tc` are not avoided.
/// This function cannot be safely called when the hardware timer
/// has been started, as data races on `best_tc` are not avoided.
pub fn register_source(tc: *TimeCounter) void {
// Precompute scaling factors for the fast multiply-shift path.
// This is meant to avoid 128-bit division and only use multiplication for 64-bit counters.
// This is meant to avoid 128-bit division and only use multiplication for
// 64-bit counters.
if (tc.mask == std.math.maxInt(u64)) {
tc.p = std.math.log2_int_ceil(u64, tc.frequency);
tc.n = (@as(u64, std.time.ns_per_s) << @intCast(tc.p)) / tc.frequency;
+28 -7
View File
@@ -1,5 +1,6 @@
//! Timer object implementation.
//! Timer objects are useful when one wants to wait a fixed amount of time for an event to occur.
//! Timer objects are useful when one wants to wait a given amount of time for
//! an event to occur.
const std = @import("std");
const rtl = @import("rtl");
const r = @import("root");
@@ -70,7 +71,12 @@ pub fn set(timer: *Timer, time: r.Nanoseconds, opts: Options) void {
const cpu = percpu.local();
if (timer.state.cmpxchgStrong(.Stopped, .Pending, .acquire, .monotonic) != null) {
if (timer.state.cmpxchgStrong(
.Stopped,
.Pending,
.acquire,
.monotonic,
) != null) {
// Could not set the timer.
return;
}
@@ -107,7 +113,12 @@ pub fn cancel(timer: *Timer) void {
const ipl = timer.hdr.lock.acquire();
defer timer.hdr.lock.release(ipl);
if (timer.state.cmpxchgStrong(.Pending, .Stopped, .acquire, .monotonic) != null) {
if (timer.state.cmpxchgStrong(
.Pending,
.Stopped,
.acquire,
.monotonic,
) != null) {
// Something changed, it probably expired.
return;
}
@@ -137,7 +148,8 @@ pub fn cmp_timer(a: *rtl.pairing_heap.Node, b_: *rtl.pairing_heap.Node) std.math
pub fn clock() void {
ke.dpc.enqueue(&percpu.local().dpc, null);
// Check for overflows.
// This is fine to call a lot, as the function only gets expensive (i.e seqlock store) when an overflow actually happens.
// This is fine to call a lot, as the function only gets expensive
// (i.e seqlock store) when an overflow actually happens.
ki.time.update_overflow();
}
@@ -161,13 +173,17 @@ fn handle_expiry(_: *ke.Dpc, _: ?*anyopaque) void {
// If the timer expires more than 1ms in the future, consider it not yet due.
// Sub-millisecond differences are close enough to expire immediately.
if (timer.deadline.value > curtime.value and timer.deadline.value - curtime.value > std.time.ns_per_ms) {
if (timer.deadline.value > curtime.value and
timer.deadline.value - curtime.value > std.time.ns_per_ms)
{
const next = timer;
cpu.lock.release_no_ipl();
// Re-check in case the timer expired while we were here.
const now = ke.time.read_time();
if (next.deadline.value <= now.value or next.deadline.value - now.value <= std.time.ns_per_ms) {
if (next.deadline.value <= now.value or
next.deadline.value - now.value <= std.time.ns_per_ms)
{
continue; // expired in the meantime, loop again
}
@@ -176,7 +192,12 @@ fn handle_expiry(_: *ke.Dpc, _: ?*anyopaque) void {
return;
}
if (timer.state.cmpxchgStrong(.Pending, .Running, .acquire, .monotonic) != null) {
if (timer.state.cmpxchgStrong(
.Pending,
.Running,
.acquire,
.monotonic,
) != null) {
// Timer must've been canceled.
cpu.lock.release_no_ipl();
continue;
+59 -56
View File
@@ -16,7 +16,11 @@ const Entry = struct {
},
};
pub fn Tunable(comptime T: type, comptime default: T, comptime name: []const u8) type {
pub fn Tunable(
comptime T: type,
comptime default: T,
comptime name: []const u8,
) type {
return struct {
var storage: T = default;
@@ -42,66 +46,65 @@ pub fn Tunable(comptime T: type, comptime default: T, comptime name: []const u8)
};
}
fn store_int(comptime T: type, addr: *anyopaque, value: anytype) bool {
const ptr: *T = @ptrCast(@alignCast(addr));
ptr.* = std.math.cast(T, value) orelse return false;
return true;
}
fn apply_num(
comptime ValueT: type,
cmdline: []const u8,
entry: *const Entry,
info: std.builtin.Type.Int,
) void {
const value = rtl.cmdline.get_number(ValueT, cmdline, entry.name) catch |e| {
if (e == error.Format) {
std.log.warn(
"Invalid number value for '{s}', falling back.",
.{entry.name},
);
}
return;
};
const sign = @typeInfo(ValueT).int.signedness;
_ = switch (info.bits) {
8 => store_int(std.meta.Int(sign, 8), entry.address, value),
16 => store_int(std.meta.Int(sign, 16), entry.address, value),
32 => store_int(std.meta.Int(sign, 32), entry.address, value),
64 => store_int(std.meta.Int(sign, 64), entry.address, value),
else => unreachable,
};
}
fn apply_bool(cmdline: []const u8, entry: *const Entry) void {
const value = rtl.cmdline.get_string(cmdline, entry.name) orelse return;
const ptr: *bool = @ptrCast(@alignCast(entry.address));
if (std.mem.eql(u8, value, "true")) {
ptr.* = true;
} else if (std.mem.eql(u8, value, "false")) {
ptr.* = false;
} else {
std.log.warn(
"Invalid cmdline boolean value for tunable '{s}'",
.{entry.name},
);
}
}
pub fn init() void {
const cmdline = r.boot_info.cmdline orelse return;
const elems = set.elems();
for (elems) |entry| {
for (set.elems()) |entry| {
switch (entry.type) {
.num => |info| {
const a = entry.address;
switch (info.signedness) {
.unsigned => {
const value = rtl.cmdline.get_number(u64, cmdline, entry.name) catch |e| {
if (e == error.Format) {
std.log.warn("Invalid number value for '{s}', falling back.", .{entry.name});
}
continue;
};
switch (info.bits) {
8 => (@as(*u8, @ptrCast(@alignCast(a)))).* = std.math.cast(u8, value) orelse continue,
16 => (@as(*u16, @ptrCast(@alignCast(a)))).* = std.math.cast(u16, value) orelse continue,
32 => (@as(*u32, @ptrCast(@alignCast(a)))).* = std.math.cast(u32, value) orelse continue,
64 => (@as(*u64, @ptrCast(@alignCast(a)))).* = value,
else => unreachable,
}
},
.signed => {
const value = rtl.cmdline.get_number(i64, cmdline, entry.name) catch |e| {
if (e == error.Format) {
std.log.warn("Invalid number value for '{s}', falling back.", .{entry.name});
}
continue;
};
switch (info.bits) {
8 => (@as(*i8, @ptrCast(@alignCast(a)))).* = std.math.cast(i8, value) orelse continue,
16 => (@as(*i16, @ptrCast(@alignCast(a)))).* = std.math.cast(i16, value) orelse continue,
32 => (@as(*i32, @ptrCast(@alignCast(a)))).* = std.math.cast(i32, value) orelse continue,
64 => (@as(*i64, @ptrCast(@alignCast(a)))).* = value,
else => unreachable,
}
},
}
},
.bool => {
const value = rtl.cmdline.get_string(cmdline, entry.name) orelse continue;
const ptr: *bool = @ptrCast(@alignCast(entry.address));
if (std.mem.eql(u8, value, "true")) {
ptr.* = true;
} else if (std.mem.eql(u8, value, "false")) {
ptr.* = false;
} else {
std.log.warn("Invalid cmdline boolean value for tunable '{s}'", .{entry.name});
}
.num => |info| switch (info.signedness) {
.unsigned => apply_num(u64, cmdline, entry, info),
.signed => apply_num(i64, cmdline, entry, info),
},
.bool => apply_bool(cmdline, entry),
}
}
}
+16 -5
View File
@@ -314,7 +314,8 @@ fn propagate(curtd: *ke.Thread, root: *Chain) void {
// and they are forbidden to explicitly block.
var it = targets(ts);
while (it.next()) |t| {
if (t.thread == curtd) @panic("turnstile: cycle in blocking chain");
if (t.thread == curtd)
@panic("turnstile: cycle in blocking chain");
t.thread.lock.acquire_no_ipl();
_ = donate_to(t.boost, t.thread, donate);
t.thread.lock.release_no_ipl();
@@ -384,7 +385,12 @@ pub fn exit(obj: *const anyopaque) void {
/// This must be called with the appropriate turnstile chain lock held, and
/// returns with the chain lock released.
/// IPL is kept as before on return.
pub fn block(turnstile: ?*Turnstile, obj: *anyopaque, own: Ownership, queue: Queue) void {
pub fn block(
turnstile: ?*Turnstile,
obj: *anyopaque,
own: Ownership,
queue: Queue,
) void {
const chain = chain_for(obj);
const curtd = ki.sched.percpu.local().current_thread.?;
const ipl = ke.ipl.current();
@@ -441,7 +447,8 @@ pub fn block(turnstile: ?*Turnstile, obj: *anyopaque, own: Ownership, queue: Que
// Now block on the event.
_ = ke.ipl.lower(.Passive);
_ = ke.wait.wait_one(&waiter.event.hdr, "turnstile:waiter", .{}) catch unreachable;
_ = ke.wait.wait_one(&waiter.event.hdr, "turnstile:waiter", .{}) catch
unreachable;
_ = ke.ipl.raise(ipl);
}
@@ -464,7 +471,10 @@ pub fn wakeup(ts: *Turnstile, queue: Queue, count: usize, new_owner: ?*ke.Thread
} else {
for (0..count) |_| {
if (ts.queues[queue_idx].is_empty()) break;
const waiter: *Waiter = @fieldParentPtr("link", ts.queues[queue_idx].first());
const waiter: *Waiter = @fieldParentPtr(
"link",
ts.queues[queue_idx].first(),
);
dequeue(ts, waiter.thread);
}
if (ts.waiters > 0) ts.detach();
@@ -487,7 +497,8 @@ fn dequeue(ts: *Turnstile, td: *ke.Thread) void {
ts.next_free = free.next_free;
free.next_free = null;
} else {
// Last waiter, pull the turnstile off the chain and keep it for ourselves.
// Last waiter, pull the turnstile off the chain
// and keep it for ourselves.
ts.link.remove();
ts.reset();
}
+48 -19
View File
@@ -13,7 +13,8 @@ pub const DispatchHeader = struct {
pub const Type = enum {
/// When signaled, `signaled` is kept high and all waiters are woken up.
Notification,
/// When signaled, `signaled` is decreased until 0, and a single waiter is woken up.
/// When signaled, `signaled` is decreased until 0, and a single waiter
/// is woken up.
Synchronization,
/// Special type for queue objects.
Queue,
@@ -70,11 +71,11 @@ pub const DispatchHeader = struct {
pub const WaitBlock = struct {
pub const Status = enum(u8) {
/// WaitBlock is linked to an object as part of a thread that is waiting.
/// Block is linked to an object as part of a thread that is waiting.
Active,
/// The wait associated with this WaitBlock has been satisfied (or timed out).
/// The wait associated has been satisfied (or timed out).
Inactive,
/// A signal was delivered to the wait (this could be before it was committed).
/// A signal was delivered to the wait.
Signaled,
};
/// List linkage.
@@ -114,8 +115,9 @@ pub fn wait_one(object: *DispatchHeader, reason: []const u8, opts: Options) !usi
/// Returns the index of the object that was signaled, or an error
/// in the case of timeout.
/// If `timeout` is not provided, it will wait indefinitely.
/// If `waitblocks` is specified, then the wait will use those waitblocks for the operation.
/// Note that if `timeout` is provided, then one additional waitblock must be allocated.
/// If `waitblocks` is specified, then the wait will use those waitblocks
/// for the operation. Note that if `timeout` is provided, then one additional
/// waitblock must be allocated.
pub fn wait_any(objects: []*DispatchHeader, reason: []const u8, opts: Options) !usize {
const ipl = ke.ipl.raise(.Dispatch);
defer ke.ipl.lower(ipl);
@@ -123,7 +125,9 @@ pub fn wait_any(objects: []*DispatchHeader, reason: []const u8, opts: Options) !
const obj_count = objects.len;
const has_timeout = opts.timeout != null;
const blocks = opts.waitblocks orelse blk: {
std.debug.assert(obj_count <= curtd.waitblocks.len - @intFromBool(has_timeout));
std.debug.assert(
obj_count <= curtd.waitblocks.len - @intFromBool(has_timeout),
);
break :blk &curtd.waitblocks;
};
@@ -146,7 +150,12 @@ pub fn wait_any(objects: []*DispatchHeader, reason: []const u8, opts: Options) !
if (obj.can_satisfy()) {
// Object was already signaled. Try consuming it.
if (curtd.wait_status.cmpxchgStrong(.InProgress, .Satisfied, .acq_rel, .monotonic) == null) {
if (curtd.wait_status.cmpxchgStrong(
.InProgress,
.Satisfied,
.acq_rel,
.monotonic,
) == null) {
obj.consume(curtd);
satisfier = i;
}
@@ -161,7 +170,7 @@ pub fn wait_any(objects: []*DispatchHeader, reason: []const u8, opts: Options) !
wb.object = obj;
wb.thread = curtd;
wb.status = .Active;
// We are not satisfied yet, so add a waitblock to the object's waitblocks
// We are not satisfied yet, so add a waitblock to the object.
obj.waitblocks.insert_tail(&wb.link);
}
@@ -197,9 +206,15 @@ pub fn wait_any(objects: []*DispatchHeader, reason: []const u8, opts: Options) !
curtd.lock.acquire_no_ipl();
// Now try committing the wait.
// While we're trying to commit the wait, the object locks have been released, and the state could therefore change.
// While we're trying to commit the wait, the object locks
// have been released, and the state could therefore change.
// We need to re-check the state of the wait before actually blocking.
if (curtd.wait_status.cmpxchgStrong(.InProgress, .Committed, .acq_rel, .monotonic) == null) {
if (curtd.wait_status.cmpxchgStrong(
.InProgress,
.Committed,
.acq_rel,
.monotonic,
) == null) {
if (queue == null) {
if (curtd.queue) |q| {
ki.queue.signal_wait(q);
@@ -236,7 +251,8 @@ pub fn wait_any(objects: []*DispatchHeader, reason: []const u8, opts: Options) !
wb.link.remove();
}
if (wb.status == .Signaled) {
// This waitblock was signaled, it must be the one that satisfied the wait.
// This waitblock was signaled, it must be the one that satisfied
// the wait.
std.debug.assert(satisfier == null);
satisfier = i;
}
@@ -245,7 +261,10 @@ pub fn wait_any(objects: []*DispatchHeader, reason: []const u8, opts: Options) !
}
const final_sat = satisfier orelse return error.Timeout;
return if (has_timeout and final_sat == timer_i) error.Timeout else final_sat;
return if (has_timeout and final_sat == timer_i)
error.Timeout
else
final_sat;
}
/// Satisfy a wait on an object.
@@ -267,19 +286,29 @@ pub fn satisfy_wait(obj: *DispatchHeader) void {
wb.link.remove();
// Three cases may occur here:
// 1. The wait was still preparing (status == .InProgress) and we interrupted it.
// 2. The wait was already committed (status == .Committed) and we satisfied it.
// 3. The wait was already satisfied by another object (status == .Satisfied).
// 1. The wait was still preparing (.InProgress) and we interrupted it.
// 2. The wait was already committed (.Committed) and we satisfied it.
// 3. The wait was already satisfied by another object (.Satisfied).
//
// 1.
if (td.wait_status.cmpxchgStrong(.InProgress, .Satisfied, .acq_rel, .monotonic) == null) {
if (td.wait_status.cmpxchgStrong(
.InProgress,
.Satisfied,
.acq_rel,
.monotonic,
) == null) {
// We interrupted the wait while it was being prepared.
wb.status = .Signaled;
obj.consume(td);
}
// 2.
else if (td.wait_status.cmpxchgStrong(.Committed, .Satisfied, .acq_rel, .monotonic) == null) {
else if (td.wait_status.cmpxchgStrong(
.Committed,
.Satisfied,
.acq_rel,
.monotonic,
) == null) {
// We interrupted the wait while it was committed.
// Wake the thread.
wb.status = .Signaled;