From 922ab8b8bc3b6dc14da9393b65ca2601f9a82728 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 1 Feb 2026 18:09:39 -0800 Subject: [PATCH 1/3] std: finish moving time to Io interface Importantly, adds ability to get Clock resolution, which may be zero. This allows error.Unexpected and error.ClockUnsupported to be removed from timeout and clock reading error sets. --- lib/compiler/aro/aro/Compilation.zig | 2 +- lib/compiler/aro/aro/Preprocessor.zig | 2 +- lib/compiler/build_runner.zig | 2 +- lib/std/Build/Step.zig | 19 +-- lib/std/Build/Step/Run.zig | 26 ++-- lib/std/Build/WebServer.zig | 6 +- lib/std/Io.zig | 122 ++++++++++++----- lib/std/Io/File/MultiReader.zig | 2 +- lib/std/Io/Threaded.zig | 160 +++++++++++++--------- lib/std/Io/net.zig | 2 +- lib/std/Io/net/HostName.zig | 2 +- lib/std/Io/test.zig | 6 - lib/std/crypto/Certificate/Bundle.zig | 4 +- lib/std/http/Client.zig | 2 +- lib/std/os/linux.zig | 10 +- lib/std/os/linux/IoUring/test.zig | 4 +- lib/std/posix.zig | 52 -------- lib/std/time.zig | 182 -------------------------- src/Compilation.zig | 35 +++-- src/Zcu.zig | 10 +- src/Zcu/PerThread.zig | 6 +- src/link.zig | 8 +- 22 files changed, 258 insertions(+), 406 deletions(-) diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index c5f400f1d9f1cbf6e18daafa5eaa9dbb3f2d92ef..44e0248b8b519799b647078cbcf8c369e04b705a 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -107,7 +107,7 @@ pub const Environment = struct { if (parsed > max_timestamp) return error.InvalidEpoch; return .{ .provided = parsed }; } else { - const timestamp = try Io.Clock.real.now(io); + const timestamp = Io.Clock.real.now(io); const seconds = std.math.cast(u64, timestamp.toSeconds()) orelse return error.InvalidEpoch; return .{ .system = std.math.clamp(seconds, 0, max_timestamp) }; } diff --git a/lib/compiler/aro/aro/Preprocessor.zig b/lib/compiler/aro/aro/Preprocessor.zig index 6e36703df14a100feca2b200e1998962d2d7c99f..854c3afbf38b25e2b622da064e99e1033c819385 100644 --- a/lib/compiler/aro/aro/Preprocessor.zig +++ b/lib/compiler/aro/aro/Preprocessor.zig @@ -301,7 +301,7 @@ pub fn init(comp: *Compilation, source_epoch: SourceEpoch) Preprocessor { /// Initialize Preprocessor with builtin macros. pub fn initDefault(comp: *Compilation) !Preprocessor { const source_epoch: SourceEpoch = comp.environment.sourceEpoch(comp.io) catch |er| switch (er) { - error.InvalidEpoch, error.UnsupportedClock, error.Unexpected => blk: { + error.InvalidEpoch => blk: { const diagnostic: Diagnostic = .invalid_source_epoch; try comp.diagnostics.add(.{ .text = diagnostic.fmt, .kind = diagnostic.kind, .opt = diagnostic.opt, .location = null }); break :blk .default; diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 04ceb6212c84ce9c91633fa3ad10c316702a1855..2dd18d4f0dcaa426988c8747fa03c49859830d72 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -548,7 +548,7 @@ pub fn main(init: process.Init.Minimal) !void { break :w try .init(graph.cache.cwd); }; - const now = Io.Clock.Timestamp.now(io, .awake) catch |err| fatal("failed to collect timestamp: {t}", .{err}); + const now = Io.Clock.Timestamp.now(io, .awake); run.web_server = if (webui_listen) |listen_address| ws: { if (builtin.single_threaded) unreachable; // `fatal` above diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 2f5e4316d47cb32e1dee00c79bee977333b3bc79..b9581196c0ec59e125a8d0da6f607264f2afddb0 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -266,16 +266,19 @@ pub fn init(options: StepOptions) Step { /// here. pub fn make(s: *Step, options: MakeOptions) error{ MakeFailed, MakeSkipped }!void { const arena = s.owner.allocator; + const graph = s.owner.graph; + const io = graph.io; - var timer: ?std.time.Timer = t: { - if (!s.owner.graph.time_report) break :t null; + var start_ts: ?Io.Timestamp = t: { + if (!graph.time_report) break :t null; if (s.id == .compile) break :t null; if (s.id == .run and s.cast(Run).?.stdio == .zig_test) break :t null; - break :t std.time.Timer.start() catch @panic("--time-report not supported on this host"); + break :t Io.Clock.awake.now(io); }; const make_result = s.makeFn(s, options); - if (timer) |*t| { - options.web_server.?.updateTimeReportGeneric(s, t.read()); + if (start_ts) |*ts| { + const duration = ts.untilNow(io, .awake); + options.web_server.?.updateTimeReportGeneric(s, duration); } make_result catch |err| switch (err) { @@ -534,7 +537,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. const arena = b.allocator; const io = b.graph.io; - var timer = try std.time.Timer.start(); + const start_ts = Io.Clock.awake.now(io); try sendMessage(io, zp.child.stdin.?, .update); if (!watch) try sendMessage(io, zp.child.stdin.?, .exit); @@ -637,7 +640,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. .compile = s.cast(Step.Compile).?, .use_llvm = tr.flags.use_llvm, .stats = tr.stats, - .ns_total = timer.read(), + .ns_total = @intCast(start_ts.untilNow(io, .awake).toNanoseconds()), .llvm_pass_timings_len = tr.llvm_pass_timings_len, .files_len = tr.files_len, .decls_len = tr.decls_len, @@ -648,7 +651,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. } } - s.result_duration_ns = timer.read(); + s.result_duration_ns = @intCast(start_ts.untilNow(io, .awake).toNanoseconds()); const stderr_contents = zp.multi_reader.reader(1).buffered(); if (stderr_contents.len > 0) { diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 3f5df9f2ae351db03282648d3ce38c488f8b3288..e80f57f0bc33db759b32ef03b37c5a355e680f84 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1587,12 +1587,12 @@ fn spawnChildAndCollect( }; if (run.stdio == .zig_test) { - const started: Io.Clock.Timestamp = try .now(io, .awake); + const started: Io.Clock.Timestamp = .now(io, .awake); const result = evalZigTest(run, spawn_options, options, fuzz_context) catch |err| switch (err) { error.Canceled => |e| return e, else => |e| e, }; - run.step.result_duration_ns = @intCast((try started.untilNow(io)).raw.nanoseconds); + run.step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); try result; return null; } else { @@ -1607,12 +1607,12 @@ fn spawnChildAndCollect( defer if (inherit) io.unlockStderr(); try setColorEnvironmentVariables(run, environ_map, terminal_mode); - const started: Io.Clock.Timestamp = try .now(io, .awake); + const started: Io.Clock.Timestamp = .now(io, .awake); const result = evalGeneric(run, spawn_options) catch |err| switch (err) { error.Canceled => |e| return e, else => |e| e, }; - run.step.result_duration_ns = @intCast((try started.untilNow(io)).raw.nanoseconds); + run.step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); return try result; } } @@ -1869,7 +1869,7 @@ fn waitZigTest( var active_test_index: ?u32 = null; - var last_update: Io.Clock.Timestamp = try .now(io, .awake); + var last_update: Io.Clock.Timestamp = .now(io, .awake); var coverage_id: ?u64 = null; @@ -1908,11 +1908,11 @@ fn waitZigTest( multi_reader.fill(64, timeout) catch |err| switch (err) { error.Timeout => return .{ .timeout = .{ .active_test_index = active_test_index, - .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds), + .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds), } }, error.EndOfStream => return .{ .no_poll = .{ .active_test_index = active_test_index, - .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds), + .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds), } }, else => |e| return e, }; @@ -1926,11 +1926,11 @@ fn waitZigTest( multi_reader.fill(64, timeout) catch |err| switch (err) { error.Timeout => return .{ .timeout = .{ .active_test_index = active_test_index, - .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds), + .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds), } }, error.EndOfStream => return .{ .no_poll = .{ .active_test_index = active_test_index, - .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds), + .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds), } }, else => |e| return e, }; @@ -1976,13 +1976,13 @@ fn waitZigTest( @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64)); active_test_index = null; - last_update = try .now(io, .awake); + last_update = .now(io, .awake); requestNextTest(io, child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err }; }, .test_started => { active_test_index = opt_metadata.*.?.next_index - 1; - last_update = try .now(io, .awake); + last_update = .now(io, .awake); }, .test_results => { assert(fuzz_context == null); @@ -2026,7 +2026,7 @@ fn waitZigTest( active_test_index = null; - const now: Io.Clock.Timestamp = try .now(io, .awake); + const now: Io.Clock.Timestamp = .now(io, .awake); md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds); last_update = now; @@ -2239,7 +2239,7 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul return error.StderrStreamTooLong; } } else |err| switch (err) { - error.UnsupportedClock, error.Timeout => unreachable, + error.Timeout => unreachable, error.EndOfStream => {}, else => |e| return e, } diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 7205291400ab81d6a693f6361ebc6c2975ce9a40..08bc584522683423c04ee33fe6b03f96f68b64e8 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -243,7 +243,7 @@ pub fn finishBuild(ws: *WebServer, opts: struct { pub fn now(s: *const WebServer) i64 { const io = s.graph.io; - const ts = base_clock.now(io) catch s.base_timestamp; + const ts = base_clock.now(io); return @intCast(s.base_timestamp.durationTo(ts).toNanoseconds()); } @@ -761,7 +761,7 @@ pub fn updateTimeReportCompile(ws: *WebServer, opts: struct { ws.notifyUpdate(); } -pub fn updateTimeReportGeneric(ws: *WebServer, step: *Build.Step, ns_total: u64) void { +pub fn updateTimeReportGeneric(ws: *WebServer, step: *Build.Step, duration: Io.Duration) void { const gpa = ws.gpa; const io = ws.graph.io; @@ -780,7 +780,7 @@ pub fn updateTimeReportGeneric(ws: *WebServer, step: *Build.Step, ns_total: u64) const out: *align(1) abi.time_report.GenericResult = @ptrCast(buf); out.* = .{ .step_idx = step_idx, - .ns_total = ns_total, + .ns_total = @intCast(duration.toNanoseconds()), }; { ws.time_report_mutex.lock(io) catch return; diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 22ca78fd2b1a8552e0e6fb599c299638add1046e..09da9b533acdcfa58bc443992015c646660ee141 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -231,8 +231,9 @@ pub const VTable = struct { progressParentFile: *const fn (?*anyopaque) std.Progress.ParentFileError!File, - now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, - sleep: *const fn (?*anyopaque, Timeout) SleepError!void, + now: *const fn (?*anyopaque, Clock) Timestamp, + clockResolution: *const fn (?*anyopaque, Clock) Duration, + sleep: *const fn (?*anyopaque, Timeout) Cancelable!void, random: *const fn (?*anyopaque, buffer: []u8) void, randomSecure: *const fn (?*anyopaque, buffer: []u8) RandomSecureError!void, @@ -701,30 +702,48 @@ pub const Clock = enum { /// thread. cpu_thread, - pub const Error = error{UnsupportedClock} || UnexpectedError; - - /// This function is not cancelable because first of all it does not block, - /// but more importantly, the cancelation logic itself may want to check - /// the time. - pub fn now(clock: Clock, io: Io) Error!Io.Timestamp { + /// This function is not cancelable because it does not block. + /// + /// Resolution is determined by `resolution` which may be 0 if the + /// clock is unsupported. + /// + /// See also: + /// * `Clock.Timestamp.now` + pub fn now(clock: Clock, io: Io) Io.Timestamp { return io.vtable.now(io.userdata, clock); } + /// Reveals the granularity of `clock`. May be zero, indicating + /// unsupported clock. + pub fn resolution(clock: Clock, io: Io) Io.Duration { + return io.vtable.clockResolution(io.userdata, clock); + } + pub const Timestamp = struct { raw: Io.Timestamp, clock: Clock, - /// This function is not cancelable because first of all it does not block, - /// but more importantly, the cancelation logic itself may want to check - /// the time. - pub fn now(io: Io, clock: Clock) Error!Clock.Timestamp { + /// This function is not cancelable because it does not block. + /// + /// Resolution is determined by `resolution` which may be 0 if + /// the clock is unsupported. + /// + /// See also: + /// * `Clock.now` + pub fn now(io: Io, clock: Clock) Clock.Timestamp { return .{ - .raw = try io.vtable.now(io.userdata, clock), + .raw = io.vtable.now(io.userdata, clock), .clock = clock, }; } - pub fn wait(t: Clock.Timestamp, io: Io) SleepError!void { + /// Sleeps until the timestamp arrives. + /// + /// See also: + /// * `Io.sleep` + /// * `Clock.Duration.sleep` + /// * `Timeout.sleep` + pub fn wait(t: Clock.Timestamp, io: Io) Cancelable!void { return io.vtable.sleep(io.userdata, .{ .deadline = t }); } @@ -752,30 +771,38 @@ pub const Clock = enum { }; } - pub fn fromNow(io: Io, duration: Clock.Duration) Error!Clock.Timestamp { + /// Resolution is determined by `resolution` which may be 0 if + /// the clock is unsupported. + pub fn fromNow(io: Io, duration: Clock.Duration) Clock.Timestamp { return .{ .clock = duration.clock, - .raw = (try duration.clock.now(io)).addDuration(duration.raw), + .raw = duration.clock.now(io).addDuration(duration.raw), }; } - pub fn untilNow(timestamp: Clock.Timestamp, io: Io) Error!Clock.Duration { - const now_ts = try Clock.Timestamp.now(io, timestamp.clock); + /// Resolution is determined by `resolution` which may be 0 if + /// the clock is unsupported. + pub fn untilNow(timestamp: Clock.Timestamp, io: Io) Clock.Duration { + const now_ts = Clock.Timestamp.now(io, timestamp.clock); return timestamp.durationTo(now_ts); } - pub fn durationFromNow(timestamp: Clock.Timestamp, io: Io) Error!Clock.Duration { - const now_ts = try timestamp.clock.now(io); + /// Resolution is determined by `resolution` which may be 0 if + /// the clock is unsupported. + pub fn durationFromNow(timestamp: Clock.Timestamp, io: Io) Clock.Duration { + const now_ts = timestamp.clock.now(io); return .{ .clock = timestamp.clock, .raw = now_ts.durationTo(timestamp.raw), }; } - pub fn toClock(t: Clock.Timestamp, io: Io, clock: Clock) Error!Clock.Timestamp { + /// Resolution is determined by `resolution` which may be 0 if + /// the clock is unsupported. + pub fn toClock(t: Clock.Timestamp, io: Io, clock: Clock) Clock.Timestamp { if (t.clock == clock) return t; - const now_old = try t.clock.now(io); - const now_new = try clock.now(io); + const now_old = t.clock.now(io); + const now_new = clock.now(io); const duration = now_old.durationTo(t); return .{ .clock = clock, @@ -793,7 +820,13 @@ pub const Clock = enum { raw: Io.Duration, clock: Clock, - pub fn sleep(duration: Clock.Duration, io: Io) SleepError!void { + /// Waits until a specified amount of time has passed on `clock`. + /// + /// See also: + /// * `Io.sleep` + /// * `Clock.Timestamp.wait` + /// * `Timeout.sleep` + pub fn sleep(duration: Clock.Duration, io: Io) Cancelable!void { return io.vtable.sleep(io.userdata, .{ .duration = duration }); } }; @@ -802,6 +835,10 @@ pub const Clock = enum { pub const Timestamp = struct { nanoseconds: i96, + pub fn now(io: Io, clock: Clock) Io.Timestamp { + return io.vtable.now(io.userdata, clock); + } + pub const zero: Timestamp = .{ .nanoseconds = 0 }; pub fn durationTo(from: Timestamp, to: Timestamp) Duration { @@ -844,6 +881,13 @@ pub const Timestamp = struct { .fill = n.fill, }); } + + /// Resolution is determined by `Clock.resolution` which may be 0 if + /// the clock is unsupported. + pub fn untilNow(t: Timestamp, io: Io, clock: Clock) Duration { + const now_ts = clock.now(io); + return t.durationTo(now_ts); + } }; pub const Duration = struct { @@ -883,12 +927,12 @@ pub const Timeout = union(enum) { duration: Clock.Duration, deadline: Clock.Timestamp, - pub const Error = error{ Timeout, UnsupportedClock }; + pub const Error = error{Timeout}; - pub fn toTimestamp(t: Timeout, io: Io) Clock.Error!?Clock.Timestamp { + pub fn toTimestamp(t: Timeout, io: Io) ?Clock.Timestamp { return switch (t) { .none => null, - .duration => |d| try .fromNow(io, d), + .duration => |d| .fromNow(io, d), .deadline => |d| d, }; } @@ -896,20 +940,26 @@ pub const Timeout = union(enum) { pub fn toDeadline(t: Timeout, io: Io) Timeout { return switch (t) { .none => .none, - .duration => |d| .{ .deadline = Clock.Timestamp.fromNow(io, d) catch @panic("TODO") }, + .duration => |d| .{ .deadline = .fromNow(io, d) }, .deadline => |d| .{ .deadline = d }, }; } - pub fn toDurationFromNow(t: Timeout, io: Io) Clock.Error!?Clock.Duration { + pub fn toDurationFromNow(t: Timeout, io: Io) ?Clock.Duration { return switch (t) { .none => null, .duration => |d| d, - .deadline => |d| try d.durationFromNow(io), + .deadline => |d| d.durationFromNow(io), }; } - pub fn sleep(timeout: Timeout, io: Io) SleepError!void { + /// Waits until the timeout has passed. + /// + /// See also: + /// * `Io.sleep` + /// * `Clock.Duration.sleep` + /// * `Clock.Timestamp.wait` + pub fn sleep(timeout: Timeout, io: Io) Cancelable!void { return io.vtable.sleep(io.userdata, timeout); } }; @@ -2027,9 +2077,13 @@ pub fn concurrent( return future; } -pub const SleepError = error{UnsupportedClock} || UnexpectedError || Cancelable; - -pub fn sleep(io: Io, duration: Duration, clock: Clock) SleepError!void { +/// Waits until a specified amount of time has passed on `clock`. +/// +/// See also: +/// * `Clock.Duration.sleep` +/// * `Clock.Timestamp.wait` +/// * `Timeout.sleep` +pub fn sleep(io: Io, duration: Duration, clock: Clock) Cancelable!void { return io.vtable.sleep(io.userdata, .{ .duration = .{ .raw = duration, .clock = clock, diff --git a/lib/std/Io/File/MultiReader.zig b/lib/std/Io/File/MultiReader.zig index 217215a3636e40b4fe4ad805be2df88fa2830947..85841d4a976d90a396685be689e8ea04cc8e4e24 100644 --- a/lib/std/Io/File/MultiReader.zig +++ b/lib/std/Io/File/MultiReader.zig @@ -179,7 +179,7 @@ fn rebase(r: *Io.Reader, capacity: usize) Io.Reader.RebaseError!void { fn fillUntimed(context: *Context, capacity: usize) Io.Reader.Error!void { fill(context.mr, capacity, .none) catch |err| switch (err) { - error.Timeout, error.UnsupportedClock => unreachable, + error.Timeout => unreachable, error.Canceled, error.ConcurrencyUnavailable => |e| { context.err = e; return error.ReadFailed; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 053a87b27c8809bfdb0aca8efd2629e4afd14879..2cd350f58c9efcddba6f9bb9704fbaa21de0644e 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1712,6 +1712,7 @@ pub fn io(t: *Threaded) Io { .progressParentFile = progressParentFile, .now = now, + .clockResolution = clockResolution, .sleep = sleep, .random = random, @@ -1875,6 +1876,7 @@ pub fn ioBasic(t: *Threaded) Io { .progressParentFile = progressParentFile, .now = now, + .clockResolution = clockResolution, .sleep = sleep, .random = random, @@ -2487,7 +2489,7 @@ fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io. const t: *Threaded = @ptrCast(@alignCast(userdata)); const t_io = ioBasic(t); const timeout_ns: ?u64 = ns: { - const d = (timeout.toDurationFromNow(t_io) catch break :ns 10) orelse break :ns null; + const d = timeout.toDurationFromNow(t_io) orelse break :ns null; break :ns std.math.lossyCast(u64, d.raw.toNanoseconds()); }; return Thread.futexWait(ptr, expected, timeout_ns); @@ -2655,24 +2657,12 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void { fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.AwaitConcurrentError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); if (is_windows) { - const deadline: ?Io.Clock.Timestamp = timeout.toTimestamp(ioBasic(t)) catch |err| switch (err) { - error.Unexpected => deadline: { - recoverableOsBugDetected(); - break :deadline .{ .raw = .{ .nanoseconds = 0 }, .clock = .awake }; - }, - error.UnsupportedClock => |e| return e, - }; + const deadline: ?Io.Clock.Timestamp = timeout.toTimestamp(ioBasic(t)); try batchAwaitWindows(b, true); while (b.pending.head != .none and b.completions.head == .none) { var delay_interval: windows.LARGE_INTEGER = interval: { const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER); - break :interval t.deadlineToWindowsInterval(d) catch |err| switch (err) { - error.UnsupportedClock => |e| return e, - error.Unexpected => { - recoverableOsBugDetected(); - break :interval -1; - }, - }; + break :interval t.deadlineToWindowsInterval(d); }; const alertable_syscall = try AlertableSyscall.start(); const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval); @@ -2754,7 +2744,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout else => {}, } const t_io = ioBasic(t); - const deadline = timeout.toTimestamp(t_io) catch return error.UnsupportedClock; + const deadline = timeout.toTimestamp(t_io); while (true) { const timeout_ms: i32 = t: { if (b.completions.head != .none) { @@ -2765,7 +2755,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout break :t 0; } const d = deadline orelse break :t -1; - const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; + const duration = d.durationFromNow(t_io); if (duration.raw.nanoseconds <= 0) return error.Timeout; const max_poll_ms = std.math.maxInt(i32); break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); @@ -10821,22 +10811,21 @@ fn fileWriteFilePositional( return error.Unimplemented; } -fn nowPosix(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { +fn nowPosix(clock: Io.Clock) Io.Timestamp { const clock_id: posix.clockid_t = clockToPosix(clock); - var tp: posix.timespec = undefined; - switch (posix.errno(posix.system.clock_gettime(clock_id, &tp))) { - .SUCCESS => return timestampFromPosix(&tp), - .INVAL => return error.UnsupportedClock, - else => |err| return posix.unexpectedErrno(err), + var timespec: posix.timespec = undefined; + switch (posix.errno(posix.system.clock_gettime(clock_id, ×pec))) { + .SUCCESS => return timestampFromPosix(×pec), + else => return .zero, } } -fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { +fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Timestamp { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; return nowInner(clock); } -fn nowInner(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { +fn nowInner(clock: Io.Clock) Io.Timestamp { return switch (native_os) { .windows => nowWindows(clock), .wasi => nowWasi(clock), @@ -10844,7 +10833,55 @@ fn nowInner(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { }; } -fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { +fn clockResolution(userdata: ?*anyopaque, clock: Io.Clock) Io.Duration { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; + return switch (native_os) { + .windows => switch (clock) { + .awake, .boot, .real => { + // We don't need to cache QPF as it's internally just a memory read to KUSER_SHARED_DATA + // (a read-only page of info updated and mapped by the kernel to all processes): + // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data + // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm + var qpf: windows.LARGE_INTEGER = undefined; + if (windows.ntdll.RtlQueryPerformanceFrequency(&qpf) != 0) { + recoverableOsBugDetected(); + return .zero; + } + // 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it. + // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701 + const common_qpf = 10_000_000; + if (qpf == common_qpf) return .fromNanoseconds(std.time.ns_per_s / common_qpf); + + // Convert to ns using fixed point. + const scale = @as(u64, std.time.ns_per_s << 32) / @as(u32, @intCast(qpf)); + const result = scale >> 32; + return .fromNanoseconds(result); + }, + .cpu_process, .cpu_thread => return .zero, + }, + .wasi => { + if (builtin.link_libc) return clockResolutionPosix(clock); + var ns: std.os.wasi.timestamp_t = undefined; + return switch (std.os.wasi.clock_res_get(clockToWasi(clock), &ns)) { + .SUCCESS => .fromNanoseconds(ns), + else => .zero, + }; + }, + else => return clockResolutionPosix(clock), + }; +} + +fn clockResolutionPosix(clock: Io.Clock) Io.Duration { + const clock_id: posix.clockid_t = clockToPosix(clock); + var timespec: posix.timespec = undefined; + return switch (posix.errno(posix.system.clock_getres(clock_id, ×pec))) { + .SUCCESS => .fromNanoseconds(nanosecondsFromPosix(×pec)), + else => .zero, + }; +} + +fn nowWindows(clock: Io.Clock) Io.Timestamp { switch (clock) { .real => { // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds @@ -10882,8 +10919,7 @@ fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { ×, @sizeOf(windows.KERNEL_USER_TIMES), null, - ) != .SUCCESS) - return error.Unexpected; + ) != .SUCCESS) return .zero; const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime); return .{ .nanoseconds = sum * 100 }; @@ -10899,8 +10935,7 @@ fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { ×, @sizeOf(windows.KERNEL_USER_TIMES), null, - ) != .SUCCESS) - return error.Unexpected; + ) != .SUCCESS) return .zero; const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime); return .{ .nanoseconds = sum * 100 }; @@ -10908,23 +10943,23 @@ fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { } } -fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { +fn nowWasi(clock: Io.Clock) Io.Timestamp { var ns: std.os.wasi.timestamp_t = undefined; const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns); - if (err != .SUCCESS) return error.Unexpected; + if (err != .SUCCESS) return .zero; return .fromNanoseconds(ns); } -fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { +fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.Cancelable!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); if (timeout == .none) return; - if (use_parking_sleep) return parking_sleep.sleep(try timeout.toTimestamp(ioBasic(t))); + if (use_parking_sleep) return parking_sleep.sleep(timeout.toTimestamp(ioBasic(t))); if (native_os == .wasi) return sleepWasi(t, timeout); if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout); return sleepNanosleep(t, timeout); } -fn sleepPosix(timeout: Io.Timeout) Io.SleepError!void { +fn sleepPosix(timeout: Io.Timeout) Io.Cancelable!void { const clock_id: posix.clockid_t = clockToPosix(switch (timeout) { .none => .awake, .duration => |d| d.clock, @@ -10944,25 +10979,27 @@ fn sleepPosix(timeout: Io.Timeout) Io.SleepError!void { } }, ×pec, ×pec); // POSIX-standard libc clock_nanosleep() returns *positive* errno values directly switch (if (builtin.link_libc) @as(posix.E, @enumFromInt(rc)) else posix.errno(rc)) { - .SUCCESS => { - syscall.finish(); - return; - }, .INTR => { try syscall.checkCancel(); continue; }, - .INVAL => return syscall.fail(error.UnsupportedClock), - else => |err| return syscall.unexpectedErrno(err), + // Handles SUCCESS as well as clock not available and unexpected + // errors. The user had a chance to check clock resolution before + // getting here, which would have reported 0, making this a legal + // amount of time to sleep. + else => { + syscall.finish(); + return; + }, } } } -fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void { +fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void { const t_io = ioBasic(t); const w = std.os.wasi; - const clock: w.subscription_clock_t = if (try timeout.toDurationFromNow(t_io)) |d| .{ + const clock: w.subscription_clock_t = if (timeout.toDurationFromNow(t_io)) |d| .{ .id = clockToWasi(d.clock), .timeout = std.math.lossyCast(u64, d.raw.nanoseconds), .precision = 0, @@ -10987,13 +11024,13 @@ fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void { syscall.finish(); } -fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void { +fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void { const t_io = ioBasic(t); const sec_type = @typeInfo(posix.timespec).@"struct".fields[0].type; const nsec_type = @typeInfo(posix.timespec).@"struct".fields[1].type; var timespec: posix.timespec = t: { - const d = (try timeout.toDurationFromNow(t_io)) orelse break :t .{ + const d = timeout.toDurationFromNow(t_io) orelse break :t .{ .sec = std.math.maxInt(sec_type), .nsec = std.math.maxInt(nsec_type), }; @@ -12630,7 +12667,7 @@ fn netReceivePosix( var message_i: usize = 0; var data_i: usize = 0; - const deadline = timeout.toTimestamp(t_io) catch |err| return .{ err, message_i }; + const deadline = timeout.toTimestamp(t_io); recv: while (true) { if (message_buffer.len - message_i == 0) return .{ null, message_i }; @@ -12678,7 +12715,7 @@ fn netReceivePosix( const max_poll_ms = std.math.maxInt(u31); const timeout_ms: u31 = if (deadline) |d| t: { - const duration = d.durationFromNow(t_io) catch |err| return .{ err, message_i }; + const duration = d.durationFromNow(t_io); if (duration.raw.nanoseconds <= 0) return .{ error.Timeout, message_i }; break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); } else max_poll_ms; @@ -13875,7 +13912,11 @@ fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat { } fn timestampFromPosix(timespec: *const posix.timespec) Io.Timestamp { - return .{ .nanoseconds = @intCast(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec) }; + return .{ .nanoseconds = nanosecondsFromPosix(timespec) }; +} + +fn nanosecondsFromPosix(timespec: *const posix.timespec) i96 { + return @intCast(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec); } fn timestampToPosix(nanoseconds: i96) posix.timespec { @@ -14013,13 +14054,13 @@ fn lookupDns( // boot clock is chosen because time the computer is suspended should count // against time spent waiting for external messages to arrive. const clock: Io.Clock = .boot; - var now_ts = try clock.now(t_io); + var now_ts = clock.now(t_io); const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds)); const attempt_duration: Io.Duration = .{ .nanoseconds = (std.time.ns_per_s / rc.attempts) * @as(i96, rc.timeout_seconds), }; - send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = try clock.now(t_io)) { + send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = clock.now(t_io)) { const max_messages = queries_buffer.len * HostName.ResolvConf.max_nameservers; { var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined; @@ -17021,7 +17062,7 @@ const parking_futex = struct { const deadline: ?Io.Clock.Timestamp = switch (timeout) { .none => null, .duration => |d| .{ - .raw = (nowInner(d.clock) catch unreachable).addDuration(d.raw), + .raw = nowInner(d.clock).addDuration(d.raw), .clock = d.clock, }, .deadline => |d| d, @@ -17143,7 +17184,7 @@ const parking_sleep = struct { comptime { assert(use_parking_sleep); } - fn sleep(deadline: ?Io.Clock.Timestamp) Io.SleepError!void { + fn sleep(deadline: ?Io.Clock.Timestamp) Io.Cancelable!void { const opt_thread = Thread.current; cancelable: { const thread = opt_thread orelse break :cancelable; @@ -17216,12 +17257,9 @@ const parking_sleep = struct { } /// Sleep for approximately `ms` awake milliseconds in an attempt to work around Windows kernel bugs. fn windowsRetrySleep(ms: u32) (Io.Cancelable || Io.UnexpectedError)!void { - const now_timestamp = nowWindows(.awake) catch unreachable; // '.awake' is supported on Windows + const now_timestamp = nowWindows(.awake); // '.awake' is supported on Windows const deadline = now_timestamp.addDuration(.fromMilliseconds(ms)); - parking_sleep.sleep(.{ .raw = deadline, .clock = .awake }) catch |err| switch (err) { - error.UnsupportedClock => unreachable, - else => |e| return e, - }; + try parking_sleep.sleep(.{ .raw = deadline, .clock = .awake }); } }; @@ -17234,7 +17272,7 @@ fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{T .windows => { var timeout_buf: windows.LARGE_INTEGER = undefined; const raw_timeout: ?*windows.LARGE_INTEGER = if (opt_deadline) |deadline| timeout: { - const now_timestamp = nowWindows(deadline.clock) catch unreachable; + const now_timestamp = nowWindows(deadline.clock); const nanoseconds = now_timestamp.durationTo(deadline.raw).nanoseconds; timeout_buf = @intCast(@divTrunc(-nanoseconds, 100)); break :timeout &timeout_buf; @@ -17284,17 +17322,17 @@ fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{T } } -fn deadlineToWindowsInterval(t: *Io.Threaded, deadline: Io.Clock.Timestamp) Io.Clock.Error!windows.LARGE_INTEGER { +fn deadlineToWindowsInterval(t: *Io.Threaded, deadline: Io.Clock.Timestamp) windows.LARGE_INTEGER { // ntdll only supports two combinations: // * real-time (`.real`) sleeps with absolute deadlines // * monotonic (`.awake`/`.boot`) sleeps with relative durations switch (deadline.clock) { - .cpu_process, .cpu_thread => unreachable, // cannot sleep for CPU time + .cpu_process, .cpu_thread => return 0, .real => { return @intCast(@max(@divTrunc(deadline.raw.nanoseconds, 100), 0)); }, .awake, .boot => { - const duration = try deadline.durationFromNow(ioBasic(t)); + const duration = deadline.durationFromNow(ioBasic(t)); return @intCast(@min(@divTrunc(-duration.raw.nanoseconds, 100), -1)); }, } diff --git a/lib/std/Io/net.zig b/lib/std/Io/net.zig index 21bd13caf387e758d91333809879e87469250677..72daee6f94625f9ce88fb2e71b7802a0b3bfbbd4 100644 --- a/lib/std/Io/net.zig +++ b/lib/std/Io/net.zig @@ -1137,7 +1137,7 @@ pub const Socket = struct { const maybe_err, const count = io.vtable.netReceive(io.userdata, s.handle, (&message)[0..1], buffer, .{}, .none); if (maybe_err) |err| switch (err) { // No timeout is passed to `netReceieve`, so it must not return timeout related errors. - error.Timeout, error.UnsupportedClock => unreachable, + error.Timeout => unreachable, else => |e| return e, }; assert(1 == count); diff --git a/lib/std/Io/net/HostName.zig b/lib/std/Io/net/HostName.zig index 66b5b648a665e05c26bf93f469d3a1a114d2faca..db669c958b910f9c568fa1bd1251a916e85de14b 100644 --- a/lib/std/Io/net/HostName.zig +++ b/lib/std/Io/net/HostName.zig @@ -145,7 +145,7 @@ pub const LookupError = error{ NoAddressReturned, /// Failed to open or read "/etc/hosts" or "/etc/resolv.conf". DetectingNetworkConfigurationFailed, -} || Io.Clock.Error || IpAddress.BindError || Io.Cancelable; +} || IpAddress.BindError || Io.Cancelable; pub const LookupResult = union(enum) { address: IpAddress, diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index a9e2eb28d4125826143239ccc3d6ff5ceef70ffc..70af7a25ee2c909492ff53fb56ecfdfb337e5ac7 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -216,14 +216,12 @@ test "Group.cancel" { defer result.* = 1; io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) { error.Canceled => |e| return e, - else => {}, }; } fn sleepRecancel(io: Io, result: *usize) void { io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) { error.Canceled => io.recancel(), - else => {}, }; result.* = 1; } @@ -523,8 +521,6 @@ test "cancel sleep" { fn blockUntilCanceled(io: Io) void { while (true) io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) { error.Canceled => return, - error.UnsupportedClock => @panic("unsupported clock"), - error.Unexpected => @panic("unexpected"), }; } }; @@ -552,8 +548,6 @@ test "tasks spawned in group after Group.cancel are canceled" { fn blockUntilCanceled(io: Io) Io.Cancelable!void { while (true) io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) { error.Canceled => |e| return e, - error.UnsupportedClock => @panic("unsupported clock"), - error.Unexpected => @panic("unexpected"), }; } }; diff --git a/lib/std/crypto/Certificate/Bundle.zig b/lib/std/crypto/Certificate/Bundle.zig index 385ef23c9c7ee205a24263472b08ee107ed95234..5a07ff8a390b5a3402d61660059bcee3265ef031 100644 --- a/lib/std/crypto/Certificate/Bundle.zig +++ b/lib/std/crypto/Certificate/Bundle.zig @@ -212,7 +212,7 @@ pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp, i } } -pub const AddCertsFromFilePathError = Io.File.OpenError || AddCertsFromFileError || Io.Clock.Error; +pub const AddCertsFromFilePathError = Io.File.OpenError || AddCertsFromFileError; pub fn addCertsFromFilePathAbsolute( cb: *Bundle, @@ -338,7 +338,7 @@ test "scan for OS-provided certificates" { var bundle: Bundle = .{}; defer bundle.deinit(gpa); - const now = try Io.Clock.real.now(io); + const now = Io.Clock.real.now(io); try bundle.rescan(gpa, io, now); } diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index a46e12ecf4846dd56c31de2444ebd3c1700f85bc..bc0e3ec0ff10c80d090ac1361f7e999684bed60e 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -1700,7 +1700,7 @@ pub fn request( defer client.ca_bundle_mutex.unlock(io); if (client.now == null) { - const now = try Io.Clock.real.now(io); + const now = Io.Clock.real.now(io); client.now = now; client.ca_bundle.rescan(client.allocator, io, now) catch return error.CertificateBundleLoadFailure; diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 501ba44557b4a817d7de516795bd4ee4d3361c24..4539aaff4e3aaf9df611d7a7da04b5557438394e 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1914,21 +1914,21 @@ fn init_vdso_clock_gettime(clk: clockid_t, ts: *timespec) callconv(.c) usize { @atomicStore(?VdsoClockGettime, &vdso_clock_gettime, ptr, .monotonic); // Call into the VDSO if available if (ptr) |f| return f(clk, ts); - return @as(usize, @bitCast(-@as(isize, @intFromEnum(E.NOSYS)))); + return @bitCast(-@as(isize, @intFromEnum(E.NOSYS))); } -pub fn clock_getres(clk_id: i32, tp: *timespec) usize { +pub fn clock_getres(clk_id: clockid_t, tp: *timespec) usize { return syscall2( if (@hasField(SYS, "clock_getres") and native_arch != .hexagon) .clock_getres else .clock_getres_time64, - @as(usize, @bitCast(@as(isize, clk_id))), + @as(usize, @intFromEnum(clk_id)), @intFromPtr(tp), ); } -pub fn clock_settime(clk_id: i32, tp: *const timespec) usize { +pub fn clock_settime(clk_id: clockid_t, tp: *const timespec) usize { return syscall2( if (@hasField(SYS, "clock_settime") and native_arch != .hexagon) .clock_settime else .clock_settime64, - @as(usize, @bitCast(@as(isize, clk_id))), + @as(usize, @intFromEnum(clk_id)), @intFromPtr(tp), ); } diff --git a/lib/std/os/linux/IoUring/test.zig b/lib/std/os/linux/IoUring/test.zig index 644b9b7c77c2666a0493a84eab6cc6b8065b4e2d..240a60c2e7a59b555bd2954e2c6b07e1239db4e9 100644 --- a/lib/std/os/linux/IoUring/test.zig +++ b/lib/std/os/linux/IoUring/test.zig @@ -620,12 +620,12 @@ test "timeout (after a relative time)" { const margin = 5; const ts: linux.kernel_timespec = .{ .sec = 0, .nsec = ms * 1000000 }; - const started = try std.Io.Clock.awake.now(io); + const started = std.Io.Clock.awake.now(io); const sqe = try ring.timeout(0x55555555, &ts, 0, 0); try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe.opcode); try testing.expectEqual(@as(u32, 1), try ring.submit()); const cqe = try ring.copy_cqe(); - const stopped = try std.Io.Clock.awake.now(io); + const stopped = std.Io.Clock.awake.now(io); try testing.expectEqual(linux.io_uring_cqe{ .user_data = 0x55555555, diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 7c03e7953b5be7a4eba881adb33fd6a71310fa01..1742325026ed269ef38c78ddb61d80ec750e8390 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -864,58 +864,6 @@ pub fn dl_iterate_phdr( } } -pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError; - -pub fn clock_gettime(clock_id: clockid_t) ClockGetTimeError!timespec { - var tp: timespec = undefined; - - if (native_os == .windows) { - @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.time API"); - } else if (native_os == .wasi and !builtin.link_libc) { - var ts: timestamp_t = undefined; - switch (system.clock_time_get(clock_id, 1, &ts)) { - .SUCCESS => { - tp = .{ - .sec = @intCast(ts / std.time.ns_per_s), - .nsec = @intCast(ts % std.time.ns_per_s), - }; - }, - .INVAL => return error.UnsupportedClock, - else => |err| return unexpectedErrno(err), - } - return tp; - } - - switch (errno(system.clock_gettime(clock_id, &tp))) { - .SUCCESS => return tp, - .FAULT => unreachable, - .INVAL => return error.UnsupportedClock, - else => |err| return unexpectedErrno(err), - } -} - -pub fn clock_getres(clock_id: clockid_t, res: *timespec) ClockGetTimeError!void { - if (native_os == .wasi and !builtin.link_libc) { - var ts: timestamp_t = undefined; - switch (system.clock_res_get(@bitCast(clock_id), &ts)) { - .SUCCESS => res.* = .{ - .sec = @intCast(ts / std.time.ns_per_s), - .nsec = @intCast(ts % std.time.ns_per_s), - }, - .INVAL => return error.UnsupportedClock, - else => |err| return unexpectedErrno(err), - } - return; - } - - switch (errno(system.clock_getres(clock_id, res))) { - .SUCCESS => return, - .FAULT => unreachable, - .INVAL => return error.UnsupportedClock, - else => |err| return unexpectedErrno(err), - } -} - pub const SchedGetAffinityError = error{PermissionDenied} || UnexpectedError; pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t { diff --git a/lib/std/time.zig b/lib/std/time.zig index c47a3121a7bbff32875e274abf7d23510d32b482..562d2042c7a7afc26cf4441600ca49abe183371b 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -1,11 +1,3 @@ -const std = @import("std.zig"); -const builtin = @import("builtin"); -const assert = std.debug.assert; -const testing = std.testing; -const math = std.math; -const windows = std.os.windows; -const posix = std.posix; - pub const epoch = @import("time/epoch.zig"); // Divisions of a nanosecond. @@ -38,180 +30,6 @@ pub const s_per_hour = s_per_min * 60; pub const s_per_day = s_per_hour * 24; pub const s_per_week = s_per_day * 7; -/// An Instant represents a timestamp with respect to the currently -/// executing program that ticks during suspend and can be used to -/// record elapsed time unlike `nanoTimestamp`. -/// -/// It tries to sample the system's fastest and most precise timer available. -/// It also tries to be monotonic, but this is not a guarantee due to OS/hardware bugs. -/// If you need monotonic readings for elapsed time, consider `Timer` instead. -pub const Instant = struct { - timestamp: if (is_posix) posix.timespec else u64, - - // true if we should use clock_gettime() - const is_posix = switch (builtin.os.tag) { - .windows, .uefi, .wasi => false, - else => true, - }; - - /// Queries the system for the current moment of time as an Instant. - /// This is not guaranteed to be monotonic or steadily increasing, but for - /// most implementations it is. - /// Returns `error.Unsupported` when a suitable clock is not detected. - pub fn now() error{Unsupported}!Instant { - const clock_id = switch (builtin.os.tag) { - .windows => { - // QPC on windows doesn't fail on >= XP/2000 and includes time suspended. - return .{ .timestamp = windows.QueryPerformanceCounter() }; - }, - .wasi => { - var ns: std.os.wasi.timestamp_t = undefined; - const rc = std.os.wasi.clock_time_get(.MONOTONIC, 1, &ns); - if (rc != .SUCCESS) return error.Unsupported; - return .{ .timestamp = ns }; - }, - .uefi => { - const value, _ = std.os.uefi.system_table.runtime_services.getTime() catch return error.Unsupported; - return .{ .timestamp = value.toEpoch() }; - }, - // On darwin, use UPTIME_RAW instead of MONOTONIC as it ticks while - // suspended. - .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => posix.CLOCK.UPTIME_RAW, - // On freebsd derivatives, use MONOTONIC_FAST as currently there's - // no precision tradeoff. - .freebsd, .dragonfly => posix.CLOCK.MONOTONIC_FAST, - // On linux, use BOOTTIME instead of MONOTONIC as it ticks while - // suspended. - .linux => posix.CLOCK.BOOTTIME, - // On other posix systems, MONOTONIC is generally the fastest and - // ticks while suspended. - else => posix.CLOCK.MONOTONIC, - }; - - const ts = posix.clock_gettime(clock_id) catch return error.Unsupported; - return .{ .timestamp = ts }; - } - - /// Quickly compares two instances between each other. - pub fn order(self: Instant, other: Instant) std.math.Order { - // windows and wasi timestamps are in u64 which is easily comparible - if (!is_posix) { - return std.math.order(self.timestamp, other.timestamp); - } - - var ord = std.math.order(self.timestamp.sec, other.timestamp.sec); - if (ord == .eq) { - ord = std.math.order(self.timestamp.nsec, other.timestamp.nsec); - } - return ord; - } - - /// Returns elapsed time in nanoseconds since the `earlier` Instant. - /// This assumes that the `earlier` Instant represents a moment in time before or equal to `self`. - /// This also assumes that the time that has passed between both Instants fits inside a u64 (~585 yrs). - pub fn since(self: Instant, earlier: Instant) u64 { - switch (builtin.os.tag) { - .windows => { - // We don't need to cache QPF as it's internally just a memory read to KUSER_SHARED_DATA - // (a read-only page of info updated and mapped by the kernel to all processes): - // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data - // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm - const qpc = self.timestamp - earlier.timestamp; - const qpf = windows.QueryPerformanceFrequency(); - - // 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it. - // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701 - const common_qpf = 10_000_000; - if (qpf == common_qpf) { - return qpc * (ns_per_s / common_qpf); - } - - // Convert to ns using fixed point. - const scale = @as(u64, std.time.ns_per_s << 32) / @as(u32, @intCast(qpf)); - const result = (@as(u96, qpc) * scale) >> 32; - return @as(u64, @truncate(result)); - }, - .uefi, .wasi => { - // UEFI and WASI timestamps are directly in nanoseconds - return self.timestamp - earlier.timestamp; - }, - else => { - // Convert timespec diff to ns - const seconds = @as(u64, @intCast(self.timestamp.sec - earlier.timestamp.sec)); - const elapsed = (seconds * ns_per_s) + @as(u32, @intCast(self.timestamp.nsec)); - return elapsed - @as(u32, @intCast(earlier.timestamp.nsec)); - }, - } - } -}; - -/// A monotonic, high performance timer. -/// -/// Timer.start() is used to initialize the timer -/// and gives the caller an opportunity to check for the existence of a supported clock. -/// Once a supported clock is discovered, -/// it is assumed that it will be available for the duration of the Timer's use. -/// -/// Monotonicity is ensured by saturating on the most previous sample. -/// This means that while timings reported are monotonic, -/// they're not guaranteed to tick at a steady rate as this is up to the underlying system. -pub const Timer = struct { - started: Instant, - previous: Instant, - - pub const Error = error{TimerUnsupported}; - - /// Initialize the timer by querying for a supported clock. - /// Returns `error.TimerUnsupported` when such a clock is unavailable. - /// This should only fail in hostile environments such as linux seccomp misuse. - pub fn start() Error!Timer { - const current = Instant.now() catch return error.TimerUnsupported; - return Timer{ .started = current, .previous = current }; - } - - /// Reads the timer value since start or the last reset in nanoseconds. - pub fn read(self: *Timer) u64 { - const current = self.sample(); - return current.since(self.started); - } - - /// Resets the timer value to 0/now. - pub fn reset(self: *Timer) void { - const current = self.sample(); - self.started = current; - } - - /// Returns the current value of the timer in nanoseconds, then resets it. - pub fn lap(self: *Timer) u64 { - const current = self.sample(); - defer self.started = current; - return current.since(self.started); - } - - /// Returns an Instant sampled at the callsite that is - /// guaranteed to be monotonic with respect to the timer's starting point. - fn sample(self: *Timer) Instant { - const current = Instant.now() catch unreachable; - if (current.order(self.previous) == .gt) { - self.previous = current; - } - return self.previous; - } -}; - -test Timer { - const io = std.testing.io; - - var timer = try Timer.start(); - - try std.Io.Clock.Duration.sleep(.{ .clock = .awake, .raw = .fromMilliseconds(10) }, io); - const time_0 = timer.read(); - try testing.expect(time_0 > 0); - - const time_1 = timer.lap(); - try testing.expect(time_1 >= time_0); -} - test { _ = epoch; } diff --git a/src/Compilation.zig b/src/Compilation.zig index 6b6021ab3c5e51351dd99a3e1e5b73a1ee18b33c..4f671b71b944886940d74f1dd6639b76b28e1aaf 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -331,48 +331,42 @@ const QueuedJobs = struct { pub const Timer = union(enum) { unused, active: struct { - start: std.time.Instant, + start: Io.Timestamp, saved_ns: u64, }, paused: u64, stopped, - pub fn pause(t: *Timer) void { + pub fn pause(t: *Timer, io: Io) void { switch (t.*) { .unused => return, .active => |a| { - const current = std.time.Instant.now() catch unreachable; - const new_ns = switch (current.order(a.start)) { - .lt, .eq => 0, - .gt => current.since(a.start), - }; + const current: Io.Timestamp = .now(io, .awake); + const new_ns: u64 = @intCast(current.nanoseconds -| a.start.nanoseconds); t.* = .{ .paused = a.saved_ns + new_ns }; }, .paused => unreachable, .stopped => unreachable, } } - pub fn @"resume"(t: *Timer) void { + pub fn @"resume"(t: *Timer, io: Io) void { switch (t.*) { .unused => return, .active => unreachable, .paused => |saved_ns| t.* = .{ .active = .{ - .start = std.time.Instant.now() catch unreachable, + .start = .now(io, .awake), .saved_ns = saved_ns, } }, .stopped => unreachable, } } - pub fn finish(t: *Timer) ?u64 { + pub fn finish(t: *Timer, io: Io) ?u64 { defer t.* = .stopped; switch (t.*) { .unused => return null, .active => |a| { - const current = std.time.Instant.now() catch unreachable; - const new_ns = switch (current.order(a.start)) { - .lt, .eq => 0, - .gt => current.since(a.start), - }; + const current: Io.Timestamp = .now(io, .awake); + const new_ns: u64 = @intCast(current.nanoseconds -| a.start.nanoseconds); return a.saved_ns + new_ns; }, .paused => |ns| return ns, @@ -387,7 +381,8 @@ pub const Timer = union(enum) { /// is set. pub fn startTimer(comp: *Compilation) Timer { if (comp.time_report == null) return .unused; - const now = std.time.Instant.now() catch @panic("std.time.Timer unsupported; cannot emit time report"); + const io = comp.io; + const now: Io.Timestamp = .now(io, .awake); return .{ .active = .{ .start = now, .saved_ns = 0, @@ -3408,7 +3403,7 @@ fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id) (Io.Cancel defer sub_prog_node.end(); var timer = comp.startTimer(); - defer if (timer.finish()) |ns| { + defer if (timer.finish(io)) |ns| { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); comp.time_report.?.stats.real_ns_llvm_emit = ns; @@ -3453,7 +3448,7 @@ fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id) (Io.Cancel } if (comp.bin_file) |lf| { var timer = comp.startTimer(); - defer if (timer.finish()) |ns| { + defer if (timer.finish(io)) |ns| { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); comp.time_report.?.stats.real_ns_link_flush = ns; @@ -4686,7 +4681,7 @@ fn performAllTheWork( var decl_work_timer: ?Timer = null; defer commit_timer: { const t = &(decl_work_timer orelse break :commit_timer); - const ns = t.finish() orelse break :commit_timer; + const ns = t.finish(io) orelse break :commit_timer; comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); comp.time_report.?.stats.real_ns_decls = ns; @@ -4719,7 +4714,7 @@ fn performAllTheWork( defer zir_prog_node.end(); var timer = comp.startTimer(); - defer if (timer.finish()) |ns| { + defer if (timer.finish(io)) |ns| { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); comp.time_report.?.stats.real_ns_files = ns; diff --git a/src/Zcu.zig b/src/Zcu.zig index ceccc2c192cd8a8fef9248179b82535476d34cf3..6a2872eae8fa3c0d3ce1beed7a29c1b080c4d1b2 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -4754,6 +4754,7 @@ const TrackedUnitSema = struct { analysis_timer_decl: ?InternPool.TrackedInst.Index, pub fn end(tus: TrackedUnitSema, zcu: *Zcu) void { const comp = zcu.comp; + const io = comp.io; if (tus.old_name) |old_name| { zcu.sema_prog_node.completeOne(); // we're just renaming, but it's effectively completion zcu.cur_sema_prog_node.setName(&old_name); @@ -4762,9 +4763,8 @@ const TrackedUnitSema = struct { zcu.cur_sema_prog_node = .none; } report_time: { - const sema_ns = zcu.cur_analysis_timer.?.finish() orelse break :report_time; + const sema_ns = zcu.cur_analysis_timer.?.finish(io) orelse break :report_time; const zir_decl = tus.analysis_timer_decl orelse break :report_time; - const io = comp.io; comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); comp.time_report.?.stats.cpu_ns_sema += sema_ns; @@ -4779,11 +4779,13 @@ const TrackedUnitSema = struct { gop.value_ptr.count += 1; } zcu.cur_analysis_timer = tus.old_analysis_timer; - if (zcu.cur_analysis_timer) |*t| t.@"resume"(); + if (zcu.cur_analysis_timer) |*t| t.@"resume"(io); } }; pub fn trackUnitSema(zcu: *Zcu, name: []const u8, zir_inst: ?InternPool.TrackedInst.Index) TrackedUnitSema { - if (zcu.cur_analysis_timer) |*t| t.pause(); + const comp = zcu.comp; + const io = comp.io; + if (zcu.cur_analysis_timer) |*t| t.pause(io); const old_analysis_timer = zcu.cur_analysis_timer; zcu.cur_analysis_timer = zcu.comp.startTimer(); const old_name: ?[std.Progress.Node.max_name_len]u8 = old_name: { diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 950a3e5a19f5c08524df7087aa201f54e4f05400..56b09d9da1110eaf2491bff1ff03e7b5e6b50c53 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -263,7 +263,7 @@ pub fn updateFile( var timer = comp.startTimer(); // Any potential AST errors are converted to ZIR errors when we run AstGen/ZonGen. file.tree = try Ast.parse(gpa, source, file.getMode()); - if (timer.finish()) |ns_parse| { + if (timer.finish(io)) |ns_parse| { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); comp.time_report.?.stats.cpu_ns_parse += ns_parse; @@ -295,7 +295,7 @@ pub fn updateFile( else => |e| return e, }; - if (timer.finish()) |ns_astgen| { + if (timer.finish(io)) |ns_astgen| { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); comp.time_report.?.stats.cpu_ns_astgen += ns_astgen; @@ -4485,7 +4485,7 @@ pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) Ru const codegen_result = runCodegenInner(pt, func_index, air); - if (timer.finish()) |ns_codegen| report_time: { + if (timer.finish(io)) |ns_codegen| report_time: { const ip = &zcu.intern_pool; const nav = ip.indexToKey(func_index).func.owner_nav; const zir_decl = ip.getNav(nav).srcInst(ip); diff --git a/src/link.zig b/src/link.zig index 3af768a363733e5b2dc25e8f46412c601b2e05a0..c8e46540bac608301ee75b5c315b5c9b2ac05b72 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1388,7 +1388,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { }; var timer = comp.startTimer(); - defer if (timer.finish()) |ns| { + defer if (timer.finish(io)) |ns| { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); comp.time_report.?.stats.cpu_ns_link += ns; @@ -1535,12 +1535,12 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void { break :nav nav_index; }, .link_func => |codegen_task| nav: { - timer.pause(); + timer.pause(io); const func, var mir = codegen_task.wait(&zcu.codegen_task_pool, io) catch |err| switch (err) { error.Canceled, error.AlreadyReported => return, }; defer mir.deinit(zcu); - timer.@"resume"(); + timer.@"resume"(io); const nav = zcu.funcInfo(func).owner_nav; const fqn_slice = ip.getNav(nav).fqn.toSlice(ip); @@ -1592,7 +1592,7 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void { }, }; - if (timer.finish()) |ns_link| report_time: { + if (timer.finish(io)) |ns_link| report_time: { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); const tr = &zcu.comp.time_report.?; -- 2.54.0 From 11476d83c9218ad2b22ab59b2fee3a4a4bf36367 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 1 Feb 2026 22:32:48 -0800 Subject: [PATCH 2/3] stage1: add wasi_snapshot_preview1_clock_res_get to wasi.c --- stage1/wasi.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/stage1/wasi.c b/stage1/wasi.c index e4772735d9ada66cc977d7c681a4930faebc203e..3be4bd00248a7da45f643c070fa3576970024798 100644 --- a/stage1/wasi.c +++ b/stage1/wasi.c @@ -924,6 +924,15 @@ uint32_t wasi_snapshot_preview1_clock_time_get(uint32_t id, uint64_t precision, return wasi_errno_success; } +uint32_t wasi_snapshot_preview1_clock_res_get(uint32_t id, uint32_t res_timestamp) { + uint8_t *const m = *wasm_memory; + uint64_t *res_timestamp_ptr = (uint64_t *)&m[res_timestamp]; +#if LOG_TRACE + fprintf(stderr, "wasi_snapshot_preview1_clock_res_get(%u, %llu)\n", id, (unsigned long long)res_timestamp); +#endif + return wasi_errno_notcapable; +} + uint32_t wasi_snapshot_preview1_path_remove_directory(uint32_t fd, uint32_t path, uint32_t path_len) { uint8_t *const m = *wasm_memory; const char *path_ptr = (const char *)&m[path]; -- 2.54.0 From fe5da36aa3b1dbeb02276c6628640c68a5922191 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 3 Feb 2026 01:02:48 -0800 Subject: [PATCH 3/3] std.Io make Clock.resolution fallible --- lib/std/Io.zig | 9 +++++++-- lib/std/Io/Threaded.zig | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 09da9b533acdcfa58bc443992015c646660ee141..92327e0f5d16f0e83cd3cfabe0c86f463a68f03c 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -232,7 +232,7 @@ pub const VTable = struct { progressParentFile: *const fn (?*anyopaque) std.Progress.ParentFileError!File, now: *const fn (?*anyopaque, Clock) Timestamp, - clockResolution: *const fn (?*anyopaque, Clock) Duration, + clockResolution: *const fn (?*anyopaque, Clock) Clock.ResolutionError!Duration, sleep: *const fn (?*anyopaque, Timeout) Cancelable!void, random: *const fn (?*anyopaque, buffer: []u8) void, @@ -713,9 +713,14 @@ pub const Clock = enum { return io.vtable.now(io.userdata, clock); } + pub const ResolutionError = error{ + ClockUnavailable, + Unexpected, + }; + /// Reveals the granularity of `clock`. May be zero, indicating /// unsupported clock. - pub fn resolution(clock: Clock, io: Io) Io.Duration { + pub fn resolution(clock: Clock, io: Io) ResolutionError!Io.Duration { return io.vtable.clockResolution(io.userdata, clock); } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 2cd350f58c9efcddba6f9bb9704fbaa21de0644e..6255d96978405cc0d1b1fc4908644facea0ef4b0 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -10833,7 +10833,7 @@ fn nowInner(clock: Io.Clock) Io.Timestamp { }; } -fn clockResolution(userdata: ?*anyopaque, clock: Io.Clock) Io.Duration { +fn clockResolution(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.ResolutionError!Io.Duration { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; return switch (native_os) { @@ -10872,7 +10872,7 @@ fn clockResolution(userdata: ?*anyopaque, clock: Io.Clock) Io.Duration { }; } -fn clockResolutionPosix(clock: Io.Clock) Io.Duration { +fn clockResolutionPosix(clock: Io.Clock) Io.Clock.ResolutionError!Io.Duration { const clock_id: posix.clockid_t = clockToPosix(clock); var timespec: posix.timespec = undefined; return switch (posix.errno(posix.system.clock_getres(clock_id, ×pec))) { -- 2.54.0