all: type-safe time units

This commit is contained in:
Loïc Grégoire
2026-08-02 00:02:44 -04:00
parent 24372a2b4e
commit e36416304b
23 changed files with 137 additions and 61 deletions
+1 -1
View File
@@ -197,7 +197,7 @@ fn work_loop(p: ?*anyopaque) void {
const dynamic = ctx.get_tag() == 1;
while (true) {
const timeout: ?r.Nanoseconds = if (dynamic) std.time.ns_per_s * 5 else null;
const timeout: ?r.Nanoseconds = if (dynamic) .from(r.Seconds.init(5)) else null;
const item = ctx.get_ptr().queue.remove(timeout) catch {
_ = ctx.get_ptr().total_threads.fetchSub(1, .monotonic);
+2 -2
View File
@@ -113,7 +113,7 @@ fn dispatch_queue(cpu: u32) void {
if (sched_cpu.start_timer) {
ke.timer.set(
&sched_cpu.resched_timer,
std.time.ns_per_ms * config.sched_timeslice,
.from(r.Milliseconds.init(config.sched_timeslice)),
.{ .dpc = &sched_cpu.resched_dpc },
);
}
@@ -123,7 +123,7 @@ fn dispatch_queue(cpu: u32) void {
ke.timer.cancel(&sched_cpu.resched_timer);
ke.timer.set(
&sched_cpu.resched_timer,
std.time.ns_per_ms * config.sched_timeslice,
.from(r.Milliseconds.init(config.sched_timeslice)),
.{ .dpc = &sched_cpu.resched_dpc },
);
}
+7 -1
View File
@@ -20,7 +20,13 @@ pub fn init(boot_info: *r.BootInfo) linksection(r.init) void {
ki.cpu.init_cpu(0);
ki.turnstile.init_turnstiles();
thread0.init(@intFromPtr(&stack), r.kib(16), ke.Thread.Priority.idle_low, ki.sched.idle, null);
thread0.init(
@intFromPtr(&stack),
r.kib(16),
ke.Thread.Priority.idle_low,
ki.sched.idle,
null,
);
thread0.priority = 0;
thread0.base_priority = 0;
thread0.pinned = true;
+7 -1
View File
@@ -89,6 +89,12 @@ pub fn log(
const ipl = out_lock.acquire_at(.High);
defer out_lock.release(ipl);
writer.interface.print("[{:>5}.{:06}] ", .{ res.info.timestamp / std.time.ns_per_s, (res.info.timestamp % std.time.ns_per_s) / std.time.ns_per_us }) catch return;
writer.interface.print(
"[{:>5}.{:06}] ",
.{
res.info.timestamp.value / std.time.ns_per_s,
(res.info.timestamp.value % std.time.ns_per_s) / std.time.ns_per_us,
},
) catch return;
_ = writer.interface.writeAll(res.buf[0..res.info.length]) catch return;
}
+1 -1
View File
@@ -92,7 +92,7 @@ const Desc = struct {
pub const Info = struct {
sequence: u64,
timestamp: u64,
timestamp: r.Nanoseconds,
length: u16,
};
+10 -10
View File
@@ -215,7 +215,7 @@ pub const Average = struct {
// Amount of the period that has been accounted for
period_contrib: usize,
/// Timestamp (us) up to which history has been accounted.
last_update: u64,
last_update: r.Microseconds,
};
pub const percpu = ke.CpuLocal(PerCpu, undefined);
@@ -242,8 +242,8 @@ fn pelt_do_decay(val: u64, n: u64) u64 {
}
fn pelt_update(avg: *Average, runnable: bool) void {
const now = ke.time.read_time() / std.time.ns_per_us;
const new_delta = now - avg.last_update;
const now = ke.time.read_time().to(r.Microseconds);
const new_delta = now.value - avg.last_update.value;
if (new_delta == 0) return;
@@ -318,8 +318,8 @@ fn pelt_update_td(td: *ke.Thread, cpu: ?*PerCpu, runnable: bool) void {
fn attach_load_avg(cpu: *PerCpu, td: *ke.Thread) void {
if (!tracks_load_avg(td)) return;
if (td.avg.last_update == 0) {
td.avg.last_update = ke.time.read_time() / std.time.ns_per_us;
if (td.avg.last_update.value == 0) {
td.avg.last_update = ke.time.read_time().to(r.Microseconds);
} else {
pelt_update_td(td, null, false);
}
@@ -470,7 +470,7 @@ pub fn block() void {
/// Block the currently running thread with its lock held.
pub fn block_locked(curtd: *ke.Thread) void {
curtd.state.store(.Blocked, .monotonic);
curtd.sleep_start = ke.time.read_time();
curtd.sleep_start = ke.time.read_time().value;
curtd.runq = null;
detach_load_avg(percpu.local(), curtd);
@@ -537,7 +537,7 @@ pub fn unblock_locked(td: *ke.Thread) void {
std.debug.assert(td.lock.is_locked());
std.debug.assert(td.state.load(.monotonic) == .Blocked);
const delta = (ke.time.read_time() - td.sleep_start) / std.time.ns_per_ms;
const delta = (ke.time.read_time().value - td.sleep_start) / std.time.ns_per_ms;
td.sleep_time += delta;
@@ -685,7 +685,7 @@ pub fn late_init() linksection(r.init) void {
balance_timer.init();
ke.timer.set(
&balance_timer,
balance_interval * std.time.ns_per_ms,
.from(r.Milliseconds.init(balance_interval)),
.{ .dpc = &balance_dpc },
);
}
@@ -1136,7 +1136,7 @@ fn pick_cpu(td: *ke.Thread) u32 {
var ran_recently = true;
if (td.sleep_start != 0) {
const delta = ke.time.read_time() - td.sleep_start;
const delta = ke.time.read_time().value - td.sleep_start;
// If we haven't been on this CPU in the last second, don't bother.
if (delta > std.time.ns_per_s) {
@@ -1455,5 +1455,5 @@ fn balance(_: *ke.Dpc, _: ?*anyopaque) void {
const offset = lcg.next() % balance_interval;
const ms = (balance_interval) + offset;
ke.timer.set(&balance_timer, ms * std.time.ns_per_ms, .{ .dpc = &balance_dpc });
ke.timer.set(&balance_timer, .from(r.Milliseconds.init(ms)), .{ .dpc = &balance_dpc });
}
+1 -1
View File
@@ -159,7 +159,7 @@ pub const Thread = struct {
.stack = stack,
.switching = .init(false),
.avg = .{
.last_update = 0,
.last_update = .init(0),
// New threads are considered heavy until they prove themselves,
// this might allow for better balancing during bursts.
.load = ki.sched.pelt_load_avg_max,
+5 -5
View File
@@ -89,12 +89,12 @@ pub fn register_source(tc: *TimeCounter) void {
/// Return the time elapsed since boot in nanoseconds.
pub fn read_time() r.Nanoseconds {
const tc = best_tc orelse return 0;
const tc = best_tc orelse return .init(0);
const s = state.load();
const curr_count = tc.read_count() & tc.mask;
const elapsed = (curr_count - s.initial_count) & tc.mask;
return s.offset + ticks_to_ns(tc, elapsed);
return .init(s.offset + ticks_to_ns(tc, elapsed));
}
/// Return the best TimeCounter.
@@ -109,14 +109,14 @@ pub fn sleep(ns: r.Nanoseconds) void {
while (true) {
var cur = read_time();
if (cur < start) continue;
if (cur.value < start.value) continue;
if (cur - start >= ns) {
if (cur.value - start.value >= ns.value) {
cur = read_time();
// Check again as we may have overflowed and read a bogus value..
// This is kinda hacky but oh well.
if (cur - start >= ns) {
if (cur.value - start.value >= ns.value) {
break;
}
}
+7 -7
View File
@@ -42,7 +42,7 @@ pub const Timer = struct {
self.* = .{
.hdr = undefined,
.state = .init(.Stopped),
.deadline = 0,
.deadline = .init(0),
.dpc = null,
.node = .{},
.cpu = null,
@@ -79,7 +79,7 @@ pub fn set(timer: *Timer, time: r.Nanoseconds, opts: Options) void {
defer cpu.lock.release_no_ipl();
// Initialize the timer.
timer.deadline = ke.time.read_time() + time;
timer.deadline = .init(ke.time.read_time().value + time.value);
timer.cpu = cpu;
timer.dpc = opts.dpc;
@@ -130,7 +130,7 @@ pub fn cmp_timer(a: *rtl.pairing_heap.Node, b_: *rtl.pairing_heap.Node) std.math
const timer_a: *Timer = @fieldParentPtr("node", a);
const timer_b: *Timer = @fieldParentPtr("node", b_);
return std.math.order(timer_a.deadline, timer_b.deadline);
return std.math.order(timer_a.deadline.value, timer_b.deadline.value);
}
/// Called by the platform on a clock interrupt.
@@ -161,18 +161,18 @@ 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 > curtime and timer.deadline - curtime > 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 <= now or next.deadline - now <= 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
}
std.debug.assert(next.deadline > now);
pl.arm_timer(next.deadline - now);
std.debug.assert(next.deadline.value > now.value);
pl.arm_timer(.init(next.deadline.value - now.value));
return;
}
+1 -1
View File
@@ -166,7 +166,7 @@ pub fn wait_any(objects: []*DispatchHeader, reason: []const u8, opts: Options) !
}
// Wait was already satisfied, back out.
if (satisfier != null or (has_timeout and opts.timeout.? == 0)) {
if (satisfier != null or (has_timeout and opts.timeout.?.value == 0)) {
if (satisfier != null) {
std.debug.assert(curtd.wait_status.load(.acquire) == .Satisfied);
}
+3 -3
View File
@@ -118,7 +118,7 @@ const mi = mm.private;
/// Interval at which we do housekeeping (working set update, reaping, etc.)
const update_interval_s = 15;
const update_interval = std.time.ns_per_s * update_interval_s;
const update_interval: r.Nanoseconds = .from(r.Seconds.init(update_interval_s));
var update_work_item: ex.DelayedWorkItem = undefined;
/// Fixed point conversion for WMA computation.
@@ -134,7 +134,7 @@ const zones_num = 32;
const magazine_size = ke.Tunable(u8, 8, "mm.zone.mag_size");
/// How much extra memory CPUs are allowed to keep around.
const max_local_memory = ke.Tunable(u32, r.kib(128), "mm.zone.max_local_mem");
const max_local_memory = ke.Tunable(u32, @intCast(r.kib(128)), "mm.zone.max_local_mem");
/// Number of contentions allowed per second before the depot grows.
const depot_grow_level = ke.Tunable(u32, 5 * wma_unit, "mm.zone.depot_grow_level");
@@ -146,7 +146,7 @@ const depot_shrink_level = ke.Tunable(u32, wma_unit / 2, "mm.zone.depot_shrink_l
const excess_magazines = ke.Tunable(u32, 8, "mm.zone.excess_mags");
/// Excess memory in a zone before magazines are trimmed.
const excess_memory = ke.Tunable(u32, r.kib(16), "mm.zone.excess_mem");
const excess_memory = ke.Tunable(u32, @intCast(r.kib(16)), "mm.zone.excess_mem");
/// Every power-of-two-size from 8 to 2048 (inclusively).
const generic_zones_num = 9;
+3 -3
View File
@@ -161,7 +161,7 @@ fn lapic_calibrate(ms: u64) u64 {
write(.LvtTimer, (1 << 16));
write(.TimerInitialCount, std.math.maxInt(u32));
ke.time.sleep(std.time.ns_per_ms * ms);
ke.time.sleep(.from(r.Milliseconds.init(ms)));
const ticks = std.math.maxInt(u32) - read(.TimerCurrentCount);
@@ -200,7 +200,7 @@ pub fn arm_timer(ns: r.Nanoseconds) void {
if (amd64.cpu_features.tsc_deadline) {
const now = amd64.rdtsc();
const delta: u64 = @intCast((@as(u128, ns) * tsc.tsc_timer.frequency) / std.time.ns_per_s);
const delta: u64 = @intCast((@as(u128, ns.value) * tsc.tsc_timer.frequency) / std.time.ns_per_s);
const deadline = now +% delta;
@@ -212,7 +212,7 @@ pub fn arm_timer(ns: r.Nanoseconds) void {
write(.LvtTimer, 1 << 16);
write(.TimerInitialCount, 0);
const us = ns / std.time.ns_per_us;
const us = ns.to(r.Microseconds).value;
const ticks: u32 = @truncate(@max(us * timer_ticks_per_us.local().*, 1));
// Setup IRQ.
+2 -2
View File
@@ -65,8 +65,8 @@ pub fn init() linksection(r.init) void {
.Unknown => false,
};
const init_delay: usize = if (skip_delay) 0 else std.time.ns_per_ms * 10;
const sipi_delay: usize = if (skip_delay) std.time.ns_per_us * 10 else std.time.ns_per_us * 300;
const init_delay: r.Nanoseconds = .init(if (skip_delay) 0 else std.time.ns_per_ms * 10);
const sipi_delay: r.Nanoseconds = .init(if (skip_delay) std.time.ns_per_us * 10 else std.time.ns_per_us * 300);
const page: [*]u8 = @ptrFromInt(mm.p2v(0x8000));
@memcpy(page[0..trampoline_size], @as([*]u8, @ptrFromInt(trampoline_start)));
+2 -2
View File
@@ -46,7 +46,7 @@ pub fn init() linksection(r.init) void {
// Calculate how many ticks it costs to call tc_sleep.
for (0..calib_cost_runs) |_| {
const start = amd64.rdtsc();
ke.time.sleep(0);
ke.time.sleep(.init(0));
const end = amd64.rdtsc();
calib_cost += (end - start);
}
@@ -61,7 +61,7 @@ pub fn init() linksection(r.init) void {
for (0..runs) |i| {
const start = amd64.rdtsc();
ke.time.sleep(calib_time);
ke.time.sleep(.init(calib_time));
const end = amd64.rdtsc();
freqs[i] = (end - start - calib_cost) * (std.time.ns_per_s / calib_time);
}
+1 -1
View File
@@ -33,7 +33,7 @@ pub inline fn debug_read() u8 {
}
/// Arm a one-shot timer to fire in `ns` nanoseconds.
pub inline fn arm_timer(ns: u64) void {
pub inline fn arm_timer(ns: r.Nanoseconds) void {
return impl.arm_timer(ns);
}
+12 -9
View File
@@ -21,13 +21,24 @@ pub const percpu = ".data.percpu";
pub const VAddr = usize;
pub const PAddr = usize;
pub const Nanoseconds = u64;
pub const Nanoseconds = rtl.units.Nanoseconds;
pub const Microseconds = rtl.units.Microseconds;
pub const Milliseconds = rtl.units.Milliseconds;
pub const Seconds = rtl.units.Seconds;
pub var kernel_heap_base: usize = 0;
pub var kernel_pfndb_base: usize = 0;
pub var boot_info: *BootInfo = undefined;
pub inline fn mmio_read(comptime T: type, addr: usize) T {
return @as(*volatile T, @ptrFromInt(addr)).*;
}
pub inline fn mmio_write(comptime T: type, addr: usize, value: T) void {
@as(*volatile T, @ptrFromInt(addr)).* = value;
}
/// Return N kibibytes in bytes.
pub fn kib(comptime N: u32) usize {
return N * 1024;
@@ -48,14 +59,6 @@ pub fn tib(comptime N: u32) usize {
return gib(N) * 1024;
}
pub inline fn mmio_read(comptime T: type, addr: usize) T {
return @as(*volatile T, @ptrFromInt(addr)).*;
}
pub inline fn mmio_write(comptime T: type, addr: usize, value: T) void {
@as(*volatile T, @ptrFromInt(addr)).* = value;
}
pub const std_options = std.Options{
.logFn = ke.log.log,
};
+1
View File
@@ -1,5 +1,6 @@
const std = @import("std");
pub const units = @import("units.zig");
pub const List = @import("list.zig").List;
pub const SeqLock = @import("seqlock.zig").SeqLock;
pub const barrier = @import("barrier.zig");
+60
View File
@@ -0,0 +1,60 @@
/// Type-safe measurement units.
const std = @import("std");
const Dimension = enum { Time };
fn scale_value(comptime from_scale: u64, comptime to_scale: u64, value: u64) u64 {
if (comptime from_scale >= to_scale) {
return value * comptime (from_scale / to_scale);
} else {
return value / comptime (to_scale / from_scale);
}
}
pub fn Unit(
comptime dim: Dimension,
comptime scale: u64,
comptime unit_suffix: []const u8,
) type {
return struct {
const Self = @This();
pub const dimension = dim;
pub const base_per_unit = scale;
value: u64,
fn check(comptime T: type) void {
comptime {
if (!(@typeInfo(T) == .@"struct" and @hasDecl(T, "dimension")))
@compileError(@typeName(T) ++ " is not a unit");
if (T.dimension != dim)
@compileError("cannot convert between " ++ @tagName(T.dimension) ++
" and " ++ @tagName(dim));
}
}
pub fn init(value: u64) Self {
return .{ .value = value };
}
pub fn to(self: Self, comptime T: type) T {
check(T);
return T.init(scale_value(scale, T.base_per_unit, self.value));
}
pub fn from(other: anytype) Self {
const From = @TypeOf(other);
check(From);
return Self.init(scale_value(From.base_per_unit, scale, other.value));
}
pub inline fn format(self: Self, writer: *std.Io.Writer) !void {
try writer.print("{} {s}", .{ self.value, unit_suffix });
}
};
}
pub const Nanoseconds = Unit(.Time, 1, "ns");
pub const Microseconds = Unit(.Time, std.time.ns_per_us, "us");
pub const Milliseconds = Unit(.Time, std.time.ns_per_ms, "ms");
pub const Seconds = Unit(.Time, std.time.ns_per_s, "s");
+2 -2
View File
@@ -32,7 +32,7 @@ fn plot_pixel(x: i64, y: i64, color: u32) void {
}
fn rand_tsc_based() u32 {
const tsc = ke.time.read_time();
const tsc = ke.time.read_time().value;
const lo: u32 = @truncate(tsc);
const hi: u32 = @truncate(tsc >> 32);
return lo ^ hi;
@@ -100,7 +100,7 @@ fn get_random_color() u32 {
fn perform_delay(ms: usize) void {
var timer: ke.Timer = undefined;
timer.init();
ke.timer.set(&timer, std.time.ns_per_ms * ms, .{});
ke.timer.set(&timer, .init(std.time.ns_per_ms * ms), .{});
_ = ke.wait.wait_one(&timer.hdr, "sleep", .{}) catch unreachable;
}
+2 -2
View File
@@ -34,13 +34,13 @@ fn rand(state: *u64) u64 {
fn delay_ms(ms: usize) void {
var timer: ke.Timer = undefined;
timer.init();
ke.timer.set(&timer, std.time.ns_per_ms * ms, .{});
ke.timer.set(&timer, .init(std.time.ns_per_ms * ms), .{});
_ = ke.wait.wait_one(&timer.hdr, "sleep", .{}) catch unreachable;
}
fn worker(param: ?*anyopaque) void {
const id = @intFromPtr(param.?) - 1;
var seed = (ke.time.read_time() ^ (id *% 0x9e3779b97f4a7c15)) | 1;
var seed = (ke.time.read_time().value ^ (id *% 0x9e3779b97f4a7c15)) | 1;
var iter: u64 = 0;
while (true) : (iter += 1) {
+2 -2
View File
@@ -35,13 +35,13 @@ fn rand(state: *u64) u64 {
}
fn seed_from(param: ?*anyopaque) u64 {
return (ke.time.read_time() ^ (@intFromPtr(param.?) *% 0x9e3779b97f4a7c15)) | 1;
return (ke.time.read_time().value ^ (@intFromPtr(param.?) *% 0x9e3779b97f4a7c15)) | 1;
}
fn delay_ms(ms: usize) void {
var timer: ke.Timer = undefined;
timer.init();
ke.timer.set(&timer, std.time.ns_per_ms * ms, .{});
ke.timer.set(&timer, .init(std.time.ns_per_ms * ms), .{});
_ = ke.wait.wait_one(&timer.hdr, "sleep", .{}) catch unreachable;
}
+3 -3
View File
@@ -57,13 +57,13 @@ fn rand(state: *u64) u64 {
}
fn seed_from(param: ?*anyopaque) u64 {
return (ke.time.read_time() ^ (@intFromPtr(param.?) *% 0x9e3779b97f4a7c15)) | 1;
return (ke.time.read_time().value ^ (@intFromPtr(param.?) *% 0x9e3779b97f4a7c15)) | 1;
}
fn delay_ms(ms: usize) void {
var timer: ke.Timer = undefined;
timer.init();
ke.timer.set(&timer, std.time.ns_per_ms * ms, .{});
ke.timer.set(&timer, .init(std.time.ns_per_ms * ms), .{});
_ = ke.wait.wait_one(&timer.hdr, "sleep", .{}) catch unreachable;
}
@@ -79,7 +79,7 @@ fn park_point() void {
fn spin_us(us: u64) void {
const began = ke.time.read_time();
while (ke.time.read_time() - began < us * std.time.ns_per_us) {
while (ke.time.read_time().value - began.value < us * std.time.ns_per_us) {
std.atomic.spinLoopHint();
}
}
+2 -2
View File
@@ -45,7 +45,7 @@ fn rand(state: *u64) u64 {
fn delay_ms(ms: usize) void {
var timer: ke.Timer = undefined;
timer.init();
ke.timer.set(&timer, std.time.ns_per_ms * ms, .{});
ke.timer.set(&timer, .init(std.time.ns_per_ms * ms), .{});
_ = ke.wait.wait_one(&timer.hdr, "sleep", .{}) catch unreachable;
}
@@ -145,7 +145,7 @@ fn chain_test() void {
fn stress(param: ?*anyopaque) void {
const id = @intFromPtr(param.?) - 1;
var seed = (ke.time.read_time() ^ (id *% 0x9e3779b97f4a7c15)) | 1;
var seed = (ke.time.read_time().value ^ (id *% 0x9e3779b97f4a7c15)) | 1;
var iter: u64 = 0;
while (true) : (iter += 1) {