commit e5e4602b181360d20bbba56e9f15734c929d2a2f (tree)
parent 7aae7dd3f4d4b85837369fa591e566ae812f87dc
Author: Andrew Kelley <andrew@ziglang.org>
Date: Tue, 3 Feb 2026 20:16:18 +0100
Merge pull request 'std: finish moving time to Io interface' (#31086) from time into master
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31086
Diffstat:
23 files changed, 272 insertions(+), 406 deletions(-)
diff --git 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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) Clock.ResolutionError!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,53 @@ 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);
}
+ pub const ResolutionError = error{
+ ClockUnavailable,
+ Unexpected,
+ };
+
+ /// Reveals the granularity of `clock`. May be zero, indicating
+ /// unsupported clock.
+ pub fn resolution(clock: Clock, io: Io) ResolutionError!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 +776,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 +825,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 +840,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 +886,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 +932,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 +945,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 +2082,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
@@ -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
@@ -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.Clock.ResolutionError!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.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))) {
+ .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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.?;
diff --git 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];