authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-01 18:09:39-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-02 23:02:31-08:00
log922ab8b8bc3b6dc14da9393b65ca2601f9a82728
tree5df93fefb4885953dba33c11ffc18b6df1795776
parente7e700334d1432efec7d19887c6656d956e260e7

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.

22 files changed, 258 insertions(+), 406 deletions(-)

lib/compiler/aro/aro/Compilation.zig+1-1
......@@ -107,7 +107,7 @@ pub const Environment = struct {
107107 if (parsed > max_timestamp) return error.InvalidEpoch;
108108 return .{ .provided = parsed };
109109 } else {
110 const timestamp = try Io.Clock.real.now(io);
110 const timestamp = Io.Clock.real.now(io);
111111 const seconds = std.math.cast(u64, timestamp.toSeconds()) orelse return error.InvalidEpoch;
112112 return .{ .system = std.math.clamp(seconds, 0, max_timestamp) };
113113 }
lib/compiler/aro/aro/Preprocessor.zig+1-1
......@@ -301,7 +301,7 @@ pub fn init(comp: *Compilation, source_epoch: SourceEpoch) Preprocessor {
301301/// Initialize Preprocessor with builtin macros.
302302pub fn initDefault(comp: *Compilation) !Preprocessor {
303303 const source_epoch: SourceEpoch = comp.environment.sourceEpoch(comp.io) catch |er| switch (er) {
304 error.InvalidEpoch, error.UnsupportedClock, error.Unexpected => blk: {
304 error.InvalidEpoch => blk: {
305305 const diagnostic: Diagnostic = .invalid_source_epoch;
306306 try comp.diagnostics.add(.{ .text = diagnostic.fmt, .kind = diagnostic.kind, .opt = diagnostic.opt, .location = null });
307307 break :blk .default;
lib/compiler/build_runner.zig+1-1
......@@ -548,7 +548,7 @@ pub fn main(init: process.Init.Minimal) !void {
548548 break :w try .init(graph.cache.cwd);
549549 };
550550
551 const now = Io.Clock.Timestamp.now(io, .awake) catch |err| fatal("failed to collect timestamp: {t}", .{err});
551 const now = Io.Clock.Timestamp.now(io, .awake);
552552
553553 run.web_server = if (webui_listen) |listen_address| ws: {
554554 if (builtin.single_threaded) unreachable; // `fatal` above
lib/std/Build/Step.zig+11-8
......@@ -266,16 +266,19 @@ pub fn init(options: StepOptions) Step {
266266/// here.
267267pub fn make(s: *Step, options: MakeOptions) error{ MakeFailed, MakeSkipped }!void {
268268 const arena = s.owner.allocator;
269 const graph = s.owner.graph;
270 const io = graph.io;
269271
270 var timer: ?std.time.Timer = t: {
271 if (!s.owner.graph.time_report) break :t null;
272 var start_ts: ?Io.Timestamp = t: {
273 if (!graph.time_report) break :t null;
272274 if (s.id == .compile) break :t null;
273275 if (s.id == .run and s.cast(Run).?.stdio == .zig_test) break :t null;
274 break :t std.time.Timer.start() catch @panic("--time-report not supported on this host");
276 break :t Io.Clock.awake.now(io);
275277 };
276278 const make_result = s.makeFn(s, options);
277 if (timer) |*t| {
278 options.web_server.?.updateTimeReportGeneric(s, t.read());
279 if (start_ts) |*ts| {
280 const duration = ts.untilNow(io, .awake);
281 options.web_server.?.updateTimeReportGeneric(s, duration);
279282 }
280283
281284 make_result catch |err| switch (err) {
......@@ -534,7 +537,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.
534537 const arena = b.allocator;
535538 const io = b.graph.io;
536539
537 var timer = try std.time.Timer.start();
540 const start_ts = Io.Clock.awake.now(io);
538541
539542 try sendMessage(io, zp.child.stdin.?, .update);
540543 if (!watch) try sendMessage(io, zp.child.stdin.?, .exit);
......@@ -637,7 +640,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.
637640 .compile = s.cast(Step.Compile).?,
638641 .use_llvm = tr.flags.use_llvm,
639642 .stats = tr.stats,
640 .ns_total = timer.read(),
643 .ns_total = @intCast(start_ts.untilNow(io, .awake).toNanoseconds()),
641644 .llvm_pass_timings_len = tr.llvm_pass_timings_len,
642645 .files_len = tr.files_len,
643646 .decls_len = tr.decls_len,
......@@ -648,7 +651,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.
648651 }
649652 }
650653
651 s.result_duration_ns = timer.read();
654 s.result_duration_ns = @intCast(start_ts.untilNow(io, .awake).toNanoseconds());
652655
653656 const stderr_contents = zp.multi_reader.reader(1).buffered();
654657 if (stderr_contents.len > 0) {
lib/std/Build/Step/Run.zig+13-13
......@@ -1587,12 +1587,12 @@ fn spawnChildAndCollect(
15871587 };
15881588
15891589 if (run.stdio == .zig_test) {
1590 const started: Io.Clock.Timestamp = try .now(io, .awake);
1590 const started: Io.Clock.Timestamp = .now(io, .awake);
15911591 const result = evalZigTest(run, spawn_options, options, fuzz_context) catch |err| switch (err) {
15921592 error.Canceled => |e| return e,
15931593 else => |e| e,
15941594 };
1595 run.step.result_duration_ns = @intCast((try started.untilNow(io)).raw.nanoseconds);
1595 run.step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);
15961596 try result;
15971597 return null;
15981598 } else {
......@@ -1607,12 +1607,12 @@ fn spawnChildAndCollect(
16071607 defer if (inherit) io.unlockStderr();
16081608 try setColorEnvironmentVariables(run, environ_map, terminal_mode);
16091609
1610 const started: Io.Clock.Timestamp = try .now(io, .awake);
1610 const started: Io.Clock.Timestamp = .now(io, .awake);
16111611 const result = evalGeneric(run, spawn_options) catch |err| switch (err) {
16121612 error.Canceled => |e| return e,
16131613 else => |e| e,
16141614 };
1615 run.step.result_duration_ns = @intCast((try started.untilNow(io)).raw.nanoseconds);
1615 run.step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds);
16161616 return try result;
16171617 }
16181618}
......@@ -1869,7 +1869,7 @@ fn waitZigTest(
18691869
18701870 var active_test_index: ?u32 = null;
18711871
1872 var last_update: Io.Clock.Timestamp = try .now(io, .awake);
1872 var last_update: Io.Clock.Timestamp = .now(io, .awake);
18731873
18741874 var coverage_id: ?u64 = null;
18751875
......@@ -1908,11 +1908,11 @@ fn waitZigTest(
19081908 multi_reader.fill(64, timeout) catch |err| switch (err) {
19091909 error.Timeout => return .{ .timeout = .{
19101910 .active_test_index = active_test_index,
1911 .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds),
1911 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
19121912 } },
19131913 error.EndOfStream => return .{ .no_poll = .{
19141914 .active_test_index = active_test_index,
1915 .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds),
1915 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
19161916 } },
19171917 else => |e| return e,
19181918 };
......@@ -1926,11 +1926,11 @@ fn waitZigTest(
19261926 multi_reader.fill(64, timeout) catch |err| switch (err) {
19271927 error.Timeout => return .{ .timeout = .{
19281928 .active_test_index = active_test_index,
1929 .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds),
1929 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
19301930 } },
19311931 error.EndOfStream => return .{ .no_poll = .{
19321932 .active_test_index = active_test_index,
1933 .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds),
1933 .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds),
19341934 } },
19351935 else => |e| return e,
19361936 };
......@@ -1976,13 +1976,13 @@ fn waitZigTest(
19761976 @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64));
19771977
19781978 active_test_index = null;
1979 last_update = try .now(io, .awake);
1979 last_update = .now(io, .awake);
19801980
19811981 requestNextTest(io, child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err };
19821982 },
19831983 .test_started => {
19841984 active_test_index = opt_metadata.*.?.next_index - 1;
1985 last_update = try .now(io, .awake);
1985 last_update = .now(io, .awake);
19861986 },
19871987 .test_results => {
19881988 assert(fuzz_context == null);
......@@ -2026,7 +2026,7 @@ fn waitZigTest(
20262026
20272027 active_test_index = null;
20282028
2029 const now: Io.Clock.Timestamp = try .now(io, .awake);
2029 const now: Io.Clock.Timestamp = .now(io, .awake);
20302030 md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds);
20312031 last_update = now;
20322032
......@@ -2239,7 +2239,7 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul
22392239 return error.StderrStreamTooLong;
22402240 }
22412241 } else |err| switch (err) {
2242 error.UnsupportedClock, error.Timeout => unreachable,
2242 error.Timeout => unreachable,
22432243 error.EndOfStream => {},
22442244 else => |e| return e,
22452245 }
lib/std/Build/WebServer.zig+3-3
......@@ -243,7 +243,7 @@ pub fn finishBuild(ws: *WebServer, opts: struct {
243243
244244pub fn now(s: *const WebServer) i64 {
245245 const io = s.graph.io;
246 const ts = base_clock.now(io) catch s.base_timestamp;
246 const ts = base_clock.now(io);
247247 return @intCast(s.base_timestamp.durationTo(ts).toNanoseconds());
248248}
249249
......@@ -761,7 +761,7 @@ pub fn updateTimeReportCompile(ws: *WebServer, opts: struct {
761761 ws.notifyUpdate();
762762}
763763
764pub fn updateTimeReportGeneric(ws: *WebServer, step: *Build.Step, ns_total: u64) void {
764pub fn updateTimeReportGeneric(ws: *WebServer, step: *Build.Step, duration: Io.Duration) void {
765765 const gpa = ws.gpa;
766766 const io = ws.graph.io;
767767
......@@ -780,7 +780,7 @@ pub fn updateTimeReportGeneric(ws: *WebServer, step: *Build.Step, ns_total: u64)
780780 const out: *align(1) abi.time_report.GenericResult = @ptrCast(buf);
781781 out.* = .{
782782 .step_idx = step_idx,
783 .ns_total = ns_total,
783 .ns_total = @intCast(duration.toNanoseconds()),
784784 };
785785 {
786786 ws.time_report_mutex.lock(io) catch return;
lib/std/Io.zig+88-34
......@@ -231,8 +231,9 @@ pub const VTable = struct {
231231
232232 progressParentFile: *const fn (?*anyopaque) std.Progress.ParentFileError!File,
233233
234 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
235 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
234 now: *const fn (?*anyopaque, Clock) Timestamp,
235 clockResolution: *const fn (?*anyopaque, Clock) Duration,
236 sleep: *const fn (?*anyopaque, Timeout) Cancelable!void,
236237
237238 random: *const fn (?*anyopaque, buffer: []u8) void,
238239 randomSecure: *const fn (?*anyopaque, buffer: []u8) RandomSecureError!void,
......@@ -701,30 +702,48 @@ pub const Clock = enum {
701702 /// thread.
702703 cpu_thread,
703704
704 pub const Error = error{UnsupportedClock} || UnexpectedError;
705
706 /// This function is not cancelable because first of all it does not block,
707 /// but more importantly, the cancelation logic itself may want to check
708 /// the time.
709 pub fn now(clock: Clock, io: Io) Error!Io.Timestamp {
705 /// This function is not cancelable because it does not block.
706 ///
707 /// Resolution is determined by `resolution` which may be 0 if the
708 /// clock is unsupported.
709 ///
710 /// See also:
711 /// * `Clock.Timestamp.now`
712 pub fn now(clock: Clock, io: Io) Io.Timestamp {
710713 return io.vtable.now(io.userdata, clock);
711714 }
712715
716 /// Reveals the granularity of `clock`. May be zero, indicating
717 /// unsupported clock.
718 pub fn resolution(clock: Clock, io: Io) Io.Duration {
719 return io.vtable.clockResolution(io.userdata, clock);
720 }
721
713722 pub const Timestamp = struct {
714723 raw: Io.Timestamp,
715724 clock: Clock,
716725
717 /// This function is not cancelable because first of all it does not block,
718 /// but more importantly, the cancelation logic itself may want to check
719 /// the time.
720 pub fn now(io: Io, clock: Clock) Error!Clock.Timestamp {
726 /// This function is not cancelable because it does not block.
727 ///
728 /// Resolution is determined by `resolution` which may be 0 if
729 /// the clock is unsupported.
730 ///
731 /// See also:
732 /// * `Clock.now`
733 pub fn now(io: Io, clock: Clock) Clock.Timestamp {
721734 return .{
722 .raw = try io.vtable.now(io.userdata, clock),
735 .raw = io.vtable.now(io.userdata, clock),
723736 .clock = clock,
724737 };
725738 }
726739
727 pub fn wait(t: Clock.Timestamp, io: Io) SleepError!void {
740 /// Sleeps until the timestamp arrives.
741 ///
742 /// See also:
743 /// * `Io.sleep`
744 /// * `Clock.Duration.sleep`
745 /// * `Timeout.sleep`
746 pub fn wait(t: Clock.Timestamp, io: Io) Cancelable!void {
728747 return io.vtable.sleep(io.userdata, .{ .deadline = t });
729748 }
730749
......@@ -752,30 +771,38 @@ pub const Clock = enum {
752771 };
753772 }
754773
755 pub fn fromNow(io: Io, duration: Clock.Duration) Error!Clock.Timestamp {
774 /// Resolution is determined by `resolution` which may be 0 if
775 /// the clock is unsupported.
776 pub fn fromNow(io: Io, duration: Clock.Duration) Clock.Timestamp {
756777 return .{
757778 .clock = duration.clock,
758 .raw = (try duration.clock.now(io)).addDuration(duration.raw),
779 .raw = duration.clock.now(io).addDuration(duration.raw),
759780 };
760781 }
761782
762 pub fn untilNow(timestamp: Clock.Timestamp, io: Io) Error!Clock.Duration {
763 const now_ts = try Clock.Timestamp.now(io, timestamp.clock);
783 /// Resolution is determined by `resolution` which may be 0 if
784 /// the clock is unsupported.
785 pub fn untilNow(timestamp: Clock.Timestamp, io: Io) Clock.Duration {
786 const now_ts = Clock.Timestamp.now(io, timestamp.clock);
764787 return timestamp.durationTo(now_ts);
765788 }
766789
767 pub fn durationFromNow(timestamp: Clock.Timestamp, io: Io) Error!Clock.Duration {
768 const now_ts = try timestamp.clock.now(io);
790 /// Resolution is determined by `resolution` which may be 0 if
791 /// the clock is unsupported.
792 pub fn durationFromNow(timestamp: Clock.Timestamp, io: Io) Clock.Duration {
793 const now_ts = timestamp.clock.now(io);
769794 return .{
770795 .clock = timestamp.clock,
771796 .raw = now_ts.durationTo(timestamp.raw),
772797 };
773798 }
774799
775 pub fn toClock(t: Clock.Timestamp, io: Io, clock: Clock) Error!Clock.Timestamp {
800 /// Resolution is determined by `resolution` which may be 0 if
801 /// the clock is unsupported.
802 pub fn toClock(t: Clock.Timestamp, io: Io, clock: Clock) Clock.Timestamp {
776803 if (t.clock == clock) return t;
777 const now_old = try t.clock.now(io);
778 const now_new = try clock.now(io);
804 const now_old = t.clock.now(io);
805 const now_new = clock.now(io);
779806 const duration = now_old.durationTo(t);
780807 return .{
781808 .clock = clock,
......@@ -793,7 +820,13 @@ pub const Clock = enum {
793820 raw: Io.Duration,
794821 clock: Clock,
795822
796 pub fn sleep(duration: Clock.Duration, io: Io) SleepError!void {
823 /// Waits until a specified amount of time has passed on `clock`.
824 ///
825 /// See also:
826 /// * `Io.sleep`
827 /// * `Clock.Timestamp.wait`
828 /// * `Timeout.sleep`
829 pub fn sleep(duration: Clock.Duration, io: Io) Cancelable!void {
797830 return io.vtable.sleep(io.userdata, .{ .duration = duration });
798831 }
799832 };
......@@ -802,6 +835,10 @@ pub const Clock = enum {
802835pub const Timestamp = struct {
803836 nanoseconds: i96,
804837
838 pub fn now(io: Io, clock: Clock) Io.Timestamp {
839 return io.vtable.now(io.userdata, clock);
840 }
841
805842 pub const zero: Timestamp = .{ .nanoseconds = 0 };
806843
807844 pub fn durationTo(from: Timestamp, to: Timestamp) Duration {
......@@ -844,6 +881,13 @@ pub const Timestamp = struct {
844881 .fill = n.fill,
845882 });
846883 }
884
885 /// Resolution is determined by `Clock.resolution` which may be 0 if
886 /// the clock is unsupported.
887 pub fn untilNow(t: Timestamp, io: Io, clock: Clock) Duration {
888 const now_ts = clock.now(io);
889 return t.durationTo(now_ts);
890 }
847891};
848892
849893pub const Duration = struct {
......@@ -883,12 +927,12 @@ pub const Timeout = union(enum) {
883927 duration: Clock.Duration,
884928 deadline: Clock.Timestamp,
885929
886 pub const Error = error{ Timeout, UnsupportedClock };
930 pub const Error = error{Timeout};
887931
888 pub fn toTimestamp(t: Timeout, io: Io) Clock.Error!?Clock.Timestamp {
932 pub fn toTimestamp(t: Timeout, io: Io) ?Clock.Timestamp {
889933 return switch (t) {
890934 .none => null,
891 .duration => |d| try .fromNow(io, d),
935 .duration => |d| .fromNow(io, d),
892936 .deadline => |d| d,
893937 };
894938 }
......@@ -896,20 +940,26 @@ pub const Timeout = union(enum) {
896940 pub fn toDeadline(t: Timeout, io: Io) Timeout {
897941 return switch (t) {
898942 .none => .none,
899 .duration => |d| .{ .deadline = Clock.Timestamp.fromNow(io, d) catch @panic("TODO") },
943 .duration => |d| .{ .deadline = .fromNow(io, d) },
900944 .deadline => |d| .{ .deadline = d },
901945 };
902946 }
903947
904 pub fn toDurationFromNow(t: Timeout, io: Io) Clock.Error!?Clock.Duration {
948 pub fn toDurationFromNow(t: Timeout, io: Io) ?Clock.Duration {
905949 return switch (t) {
906950 .none => null,
907951 .duration => |d| d,
908 .deadline => |d| try d.durationFromNow(io),
952 .deadline => |d| d.durationFromNow(io),
909953 };
910954 }
911955
912 pub fn sleep(timeout: Timeout, io: Io) SleepError!void {
956 /// Waits until the timeout has passed.
957 ///
958 /// See also:
959 /// * `Io.sleep`
960 /// * `Clock.Duration.sleep`
961 /// * `Clock.Timestamp.wait`
962 pub fn sleep(timeout: Timeout, io: Io) Cancelable!void {
913963 return io.vtable.sleep(io.userdata, timeout);
914964 }
915965};
......@@ -2027,9 +2077,13 @@ pub fn concurrent(
20272077 return future;
20282078}
20292079
2030pub const SleepError = error{UnsupportedClock} || UnexpectedError || Cancelable;
2031
2032pub fn sleep(io: Io, duration: Duration, clock: Clock) SleepError!void {
2080/// Waits until a specified amount of time has passed on `clock`.
2081///
2082/// See also:
2083/// * `Clock.Duration.sleep`
2084/// * `Clock.Timestamp.wait`
2085/// * `Timeout.sleep`
2086pub fn sleep(io: Io, duration: Duration, clock: Clock) Cancelable!void {
20332087 return io.vtable.sleep(io.userdata, .{ .duration = .{
20342088 .raw = duration,
20352089 .clock = clock,
lib/std/Io/File/MultiReader.zig+1-1
......@@ -179,7 +179,7 @@ fn rebase(r: *Io.Reader, capacity: usize) Io.Reader.RebaseError!void {
179179
180180fn fillUntimed(context: *Context, capacity: usize) Io.Reader.Error!void {
181181 fill(context.mr, capacity, .none) catch |err| switch (err) {
182 error.Timeout, error.UnsupportedClock => unreachable,
182 error.Timeout => unreachable,
183183 error.Canceled, error.ConcurrencyUnavailable => |e| {
184184 context.err = e;
185185 return error.ReadFailed;
lib/std/Io/Threaded.zig+99-61
......@@ -1712,6 +1712,7 @@ pub fn io(t: *Threaded) Io {
17121712 .progressParentFile = progressParentFile,
17131713
17141714 .now = now,
1715 .clockResolution = clockResolution,
17151716 .sleep = sleep,
17161717
17171718 .random = random,
......@@ -1875,6 +1876,7 @@ pub fn ioBasic(t: *Threaded) Io {
18751876 .progressParentFile = progressParentFile,
18761877
18771878 .now = now,
1879 .clockResolution = clockResolution,
18781880 .sleep = sleep,
18791881
18801882 .random = random,
......@@ -2487,7 +2489,7 @@ fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.
24872489 const t: *Threaded = @ptrCast(@alignCast(userdata));
24882490 const t_io = ioBasic(t);
24892491 const timeout_ns: ?u64 = ns: {
2490 const d = (timeout.toDurationFromNow(t_io) catch break :ns 10) orelse break :ns null;
2492 const d = timeout.toDurationFromNow(t_io) orelse break :ns null;
24912493 break :ns std.math.lossyCast(u64, d.raw.toNanoseconds());
24922494 };
24932495 return Thread.futexWait(ptr, expected, timeout_ns);
......@@ -2655,24 +2657,12 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void {
26552657fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.AwaitConcurrentError!void {
26562658 const t: *Threaded = @ptrCast(@alignCast(userdata));
26572659 if (is_windows) {
2658 const deadline: ?Io.Clock.Timestamp = timeout.toTimestamp(ioBasic(t)) catch |err| switch (err) {
2659 error.Unexpected => deadline: {
2660 recoverableOsBugDetected();
2661 break :deadline .{ .raw = .{ .nanoseconds = 0 }, .clock = .awake };
2662 },
2663 error.UnsupportedClock => |e| return e,
2664 };
2660 const deadline: ?Io.Clock.Timestamp = timeout.toTimestamp(ioBasic(t));
26652661 try batchAwaitWindows(b, true);
26662662 while (b.pending.head != .none and b.completions.head == .none) {
26672663 var delay_interval: windows.LARGE_INTEGER = interval: {
26682664 const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER);
2669 break :interval t.deadlineToWindowsInterval(d) catch |err| switch (err) {
2670 error.UnsupportedClock => |e| return e,
2671 error.Unexpected => {
2672 recoverableOsBugDetected();
2673 break :interval -1;
2674 },
2675 };
2665 break :interval t.deadlineToWindowsInterval(d);
26762666 };
26772667 const alertable_syscall = try AlertableSyscall.start();
26782668 const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval);
......@@ -2754,7 +2744,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout
27542744 else => {},
27552745 }
27562746 const t_io = ioBasic(t);
2757 const deadline = timeout.toTimestamp(t_io) catch return error.UnsupportedClock;
2747 const deadline = timeout.toTimestamp(t_io);
27582748 while (true) {
27592749 const timeout_ms: i32 = t: {
27602750 if (b.completions.head != .none) {
......@@ -2765,7 +2755,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout
27652755 break :t 0;
27662756 }
27672757 const d = deadline orelse break :t -1;
2768 const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock;
2758 const duration = d.durationFromNow(t_io);
27692759 if (duration.raw.nanoseconds <= 0) return error.Timeout;
27702760 const max_poll_ms = std.math.maxInt(i32);
27712761 break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));
......@@ -10821,22 +10811,21 @@ fn fileWriteFilePositional(
1082110811 return error.Unimplemented;
1082210812}
1082310813
10824fn nowPosix(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
10814fn nowPosix(clock: Io.Clock) Io.Timestamp {
1082510815 const clock_id: posix.clockid_t = clockToPosix(clock);
10826 var tp: posix.timespec = undefined;
10827 switch (posix.errno(posix.system.clock_gettime(clock_id, &tp))) {
10828 .SUCCESS => return timestampFromPosix(&tp),
10829 .INVAL => return error.UnsupportedClock,
10830 else => |err| return posix.unexpectedErrno(err),
10816 var timespec: posix.timespec = undefined;
10817 switch (posix.errno(posix.system.clock_gettime(clock_id, &timespec))) {
10818 .SUCCESS => return timestampFromPosix(&timespec),
10819 else => return .zero,
1083110820 }
1083210821}
1083310822
10834fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
10823fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Timestamp {
1083510824 const t: *Threaded = @ptrCast(@alignCast(userdata));
1083610825 _ = t;
1083710826 return nowInner(clock);
1083810827}
10839fn nowInner(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
10828fn nowInner(clock: Io.Clock) Io.Timestamp {
1084010829 return switch (native_os) {
1084110830 .windows => nowWindows(clock),
1084210831 .wasi => nowWasi(clock),
......@@ -10844,7 +10833,55 @@ fn nowInner(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
1084410833 };
1084510834}
1084610835
10847fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
10836fn clockResolution(userdata: ?*anyopaque, clock: Io.Clock) Io.Duration {
10837 const t: *Threaded = @ptrCast(@alignCast(userdata));
10838 _ = t;
10839 return switch (native_os) {
10840 .windows => switch (clock) {
10841 .awake, .boot, .real => {
10842 // We don't need to cache QPF as it's internally just a memory read to KUSER_SHARED_DATA
10843 // (a read-only page of info updated and mapped by the kernel to all processes):
10844 // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data
10845 // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm
10846 var qpf: windows.LARGE_INTEGER = undefined;
10847 if (windows.ntdll.RtlQueryPerformanceFrequency(&qpf) != 0) {
10848 recoverableOsBugDetected();
10849 return .zero;
10850 }
10851 // 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it.
10852 // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701
10853 const common_qpf = 10_000_000;
10854 if (qpf == common_qpf) return .fromNanoseconds(std.time.ns_per_s / common_qpf);
10855
10856 // Convert to ns using fixed point.
10857 const scale = @as(u64, std.time.ns_per_s << 32) / @as(u32, @intCast(qpf));
10858 const result = scale >> 32;
10859 return .fromNanoseconds(result);
10860 },
10861 .cpu_process, .cpu_thread => return .zero,
10862 },
10863 .wasi => {
10864 if (builtin.link_libc) return clockResolutionPosix(clock);
10865 var ns: std.os.wasi.timestamp_t = undefined;
10866 return switch (std.os.wasi.clock_res_get(clockToWasi(clock), &ns)) {
10867 .SUCCESS => .fromNanoseconds(ns),
10868 else => .zero,
10869 };
10870 },
10871 else => return clockResolutionPosix(clock),
10872 };
10873}
10874
10875fn clockResolutionPosix(clock: Io.Clock) Io.Duration {
10876 const clock_id: posix.clockid_t = clockToPosix(clock);
10877 var timespec: posix.timespec = undefined;
10878 return switch (posix.errno(posix.system.clock_getres(clock_id, &timespec))) {
10879 .SUCCESS => .fromNanoseconds(nanosecondsFromPosix(&timespec)),
10880 else => .zero,
10881 };
10882}
10883
10884fn nowWindows(clock: Io.Clock) Io.Timestamp {
1084810885 switch (clock) {
1084910886 .real => {
1085010887 // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds
......@@ -10882,8 +10919,7 @@ fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
1088210919 &times,
1088310920 @sizeOf(windows.KERNEL_USER_TIMES),
1088410921 null,
10885 ) != .SUCCESS)
10886 return error.Unexpected;
10922 ) != .SUCCESS) return .zero;
1088710923
1088810924 const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime);
1088910925 return .{ .nanoseconds = sum * 100 };
......@@ -10899,8 +10935,7 @@ fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
1089910935 &times,
1090010936 @sizeOf(windows.KERNEL_USER_TIMES),
1090110937 null,
10902 ) != .SUCCESS)
10903 return error.Unexpected;
10938 ) != .SUCCESS) return .zero;
1090410939
1090510940 const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime);
1090610941 return .{ .nanoseconds = sum * 100 };
......@@ -10908,23 +10943,23 @@ fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
1090810943 }
1090910944}
1091010945
10911fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
10946fn nowWasi(clock: Io.Clock) Io.Timestamp {
1091210947 var ns: std.os.wasi.timestamp_t = undefined;
1091310948 const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns);
10914 if (err != .SUCCESS) return error.Unexpected;
10949 if (err != .SUCCESS) return .zero;
1091510950 return .fromNanoseconds(ns);
1091610951}
1091710952
10918fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
10953fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.Cancelable!void {
1091910954 const t: *Threaded = @ptrCast(@alignCast(userdata));
1092010955 if (timeout == .none) return;
10921 if (use_parking_sleep) return parking_sleep.sleep(try timeout.toTimestamp(ioBasic(t)));
10956 if (use_parking_sleep) return parking_sleep.sleep(timeout.toTimestamp(ioBasic(t)));
1092210957 if (native_os == .wasi) return sleepWasi(t, timeout);
1092310958 if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout);
1092410959 return sleepNanosleep(t, timeout);
1092510960}
1092610961
10927fn sleepPosix(timeout: Io.Timeout) Io.SleepError!void {
10962fn sleepPosix(timeout: Io.Timeout) Io.Cancelable!void {
1092810963 const clock_id: posix.clockid_t = clockToPosix(switch (timeout) {
1092910964 .none => .awake,
1093010965 .duration => |d| d.clock,
......@@ -10944,25 +10979,27 @@ fn sleepPosix(timeout: Io.Timeout) Io.SleepError!void {
1094410979 } }, &timespec, &timespec);
1094510980 // POSIX-standard libc clock_nanosleep() returns *positive* errno values directly
1094610981 switch (if (builtin.link_libc) @as(posix.E, @enumFromInt(rc)) else posix.errno(rc)) {
10947 .SUCCESS => {
10948 syscall.finish();
10949 return;
10950 },
1095110982 .INTR => {
1095210983 try syscall.checkCancel();
1095310984 continue;
1095410985 },
10955 .INVAL => return syscall.fail(error.UnsupportedClock),
10956 else => |err| return syscall.unexpectedErrno(err),
10986 // Handles SUCCESS as well as clock not available and unexpected
10987 // errors. The user had a chance to check clock resolution before
10988 // getting here, which would have reported 0, making this a legal
10989 // amount of time to sleep.
10990 else => {
10991 syscall.finish();
10992 return;
10993 },
1095710994 }
1095810995 }
1095910996}
1096010997
10961fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void {
10998fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void {
1096210999 const t_io = ioBasic(t);
1096311000 const w = std.os.wasi;
1096411001
10965 const clock: w.subscription_clock_t = if (try timeout.toDurationFromNow(t_io)) |d| .{
11002 const clock: w.subscription_clock_t = if (timeout.toDurationFromNow(t_io)) |d| .{
1096611003 .id = clockToWasi(d.clock),
1096711004 .timeout = std.math.lossyCast(u64, d.raw.nanoseconds),
1096811005 .precision = 0,
......@@ -10987,13 +11024,13 @@ fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void {
1098711024 syscall.finish();
1098811025}
1098911026
10990fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void {
11027fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void {
1099111028 const t_io = ioBasic(t);
1099211029 const sec_type = @typeInfo(posix.timespec).@"struct".fields[0].type;
1099311030 const nsec_type = @typeInfo(posix.timespec).@"struct".fields[1].type;
1099411031
1099511032 var timespec: posix.timespec = t: {
10996 const d = (try timeout.toDurationFromNow(t_io)) orelse break :t .{
11033 const d = timeout.toDurationFromNow(t_io) orelse break :t .{
1099711034 .sec = std.math.maxInt(sec_type),
1099811035 .nsec = std.math.maxInt(nsec_type),
1099911036 };
......@@ -12630,7 +12667,7 @@ fn netReceivePosix(
1263012667 var message_i: usize = 0;
1263112668 var data_i: usize = 0;
1263212669
12633 const deadline = timeout.toTimestamp(t_io) catch |err| return .{ err, message_i };
12670 const deadline = timeout.toTimestamp(t_io);
1263412671
1263512672 recv: while (true) {
1263612673 if (message_buffer.len - message_i == 0) return .{ null, message_i };
......@@ -12678,7 +12715,7 @@ fn netReceivePosix(
1267812715
1267912716 const max_poll_ms = std.math.maxInt(u31);
1268012717 const timeout_ms: u31 = if (deadline) |d| t: {
12681 const duration = d.durationFromNow(t_io) catch |err| return .{ err, message_i };
12718 const duration = d.durationFromNow(t_io);
1268212719 if (duration.raw.nanoseconds <= 0) return .{ error.Timeout, message_i };
1268312720 break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));
1268412721 } else max_poll_ms;
......@@ -13875,7 +13912,11 @@ fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat {
1387513912}
1387613913
1387713914fn timestampFromPosix(timespec: *const posix.timespec) Io.Timestamp {
13878 return .{ .nanoseconds = @intCast(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec) };
13915 return .{ .nanoseconds = nanosecondsFromPosix(timespec) };
13916}
13917
13918fn nanosecondsFromPosix(timespec: *const posix.timespec) i96 {
13919 return @intCast(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec);
1387913920}
1388013921
1388113922fn timestampToPosix(nanoseconds: i96) posix.timespec {
......@@ -14013,13 +14054,13 @@ fn lookupDns(
1401314054 // boot clock is chosen because time the computer is suspended should count
1401414055 // against time spent waiting for external messages to arrive.
1401514056 const clock: Io.Clock = .boot;
14016 var now_ts = try clock.now(t_io);
14057 var now_ts = clock.now(t_io);
1401714058 const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds));
1401814059 const attempt_duration: Io.Duration = .{
1401914060 .nanoseconds = (std.time.ns_per_s / rc.attempts) * @as(i96, rc.timeout_seconds),
1402014061 };
1402114062
14022 send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = try clock.now(t_io)) {
14063 send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = clock.now(t_io)) {
1402314064 const max_messages = queries_buffer.len * HostName.ResolvConf.max_nameservers;
1402414065 {
1402514066 var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined;
......@@ -17021,7 +17062,7 @@ const parking_futex = struct {
1702117062 const deadline: ?Io.Clock.Timestamp = switch (timeout) {
1702217063 .none => null,
1702317064 .duration => |d| .{
17024 .raw = (nowInner(d.clock) catch unreachable).addDuration(d.raw),
17065 .raw = nowInner(d.clock).addDuration(d.raw),
1702517066 .clock = d.clock,
1702617067 },
1702717068 .deadline => |d| d,
......@@ -17143,7 +17184,7 @@ const parking_sleep = struct {
1714317184 comptime {
1714417185 assert(use_parking_sleep);
1714517186 }
17146 fn sleep(deadline: ?Io.Clock.Timestamp) Io.SleepError!void {
17187 fn sleep(deadline: ?Io.Clock.Timestamp) Io.Cancelable!void {
1714717188 const opt_thread = Thread.current;
1714817189 cancelable: {
1714917190 const thread = opt_thread orelse break :cancelable;
......@@ -17216,12 +17257,9 @@ const parking_sleep = struct {
1721617257 }
1721717258 /// Sleep for approximately `ms` awake milliseconds in an attempt to work around Windows kernel bugs.
1721817259 fn windowsRetrySleep(ms: u32) (Io.Cancelable || Io.UnexpectedError)!void {
17219 const now_timestamp = nowWindows(.awake) catch unreachable; // '.awake' is supported on Windows
17260 const now_timestamp = nowWindows(.awake); // '.awake' is supported on Windows
1722017261 const deadline = now_timestamp.addDuration(.fromMilliseconds(ms));
17221 parking_sleep.sleep(.{ .raw = deadline, .clock = .awake }) catch |err| switch (err) {
17222 error.UnsupportedClock => unreachable,
17223 else => |e| return e,
17224 };
17262 try parking_sleep.sleep(.{ .raw = deadline, .clock = .awake });
1722517263 }
1722617264};
1722717265
......@@ -17234,7 +17272,7 @@ fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{T
1723417272 .windows => {
1723517273 var timeout_buf: windows.LARGE_INTEGER = undefined;
1723617274 const raw_timeout: ?*windows.LARGE_INTEGER = if (opt_deadline) |deadline| timeout: {
17237 const now_timestamp = nowWindows(deadline.clock) catch unreachable;
17275 const now_timestamp = nowWindows(deadline.clock);
1723817276 const nanoseconds = now_timestamp.durationTo(deadline.raw).nanoseconds;
1723917277 timeout_buf = @intCast(@divTrunc(-nanoseconds, 100));
1724017278 break :timeout &timeout_buf;
......@@ -17284,17 +17322,17 @@ fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{T
1728417322 }
1728517323}
1728617324
17287fn deadlineToWindowsInterval(t: *Io.Threaded, deadline: Io.Clock.Timestamp) Io.Clock.Error!windows.LARGE_INTEGER {
17325fn deadlineToWindowsInterval(t: *Io.Threaded, deadline: Io.Clock.Timestamp) windows.LARGE_INTEGER {
1728817326 // ntdll only supports two combinations:
1728917327 // * real-time (`.real`) sleeps with absolute deadlines
1729017328 // * monotonic (`.awake`/`.boot`) sleeps with relative durations
1729117329 switch (deadline.clock) {
17292 .cpu_process, .cpu_thread => unreachable, // cannot sleep for CPU time
17330 .cpu_process, .cpu_thread => return 0,
1729317331 .real => {
1729417332 return @intCast(@max(@divTrunc(deadline.raw.nanoseconds, 100), 0));
1729517333 },
1729617334 .awake, .boot => {
17297 const duration = try deadline.durationFromNow(ioBasic(t));
17335 const duration = deadline.durationFromNow(ioBasic(t));
1729817336 return @intCast(@min(@divTrunc(-duration.raw.nanoseconds, 100), -1));
1729917337 },
1730017338 }
lib/std/Io/net.zig+1-1
......@@ -1137,7 +1137,7 @@ pub const Socket = struct {
11371137 const maybe_err, const count = io.vtable.netReceive(io.userdata, s.handle, (&message)[0..1], buffer, .{}, .none);
11381138 if (maybe_err) |err| switch (err) {
11391139 // No timeout is passed to `netReceieve`, so it must not return timeout related errors.
1140 error.Timeout, error.UnsupportedClock => unreachable,
1140 error.Timeout => unreachable,
11411141 else => |e| return e,
11421142 };
11431143 assert(1 == count);
lib/std/Io/net/HostName.zig+1-1
......@@ -145,7 +145,7 @@ pub const LookupError = error{
145145 NoAddressReturned,
146146 /// Failed to open or read "/etc/hosts" or "/etc/resolv.conf".
147147 DetectingNetworkConfigurationFailed,
148} || Io.Clock.Error || IpAddress.BindError || Io.Cancelable;
148} || IpAddress.BindError || Io.Cancelable;
149149
150150pub const LookupResult = union(enum) {
151151 address: IpAddress,
lib/std/Io/test.zig-6
......@@ -216,14 +216,12 @@ test "Group.cancel" {
216216 defer result.* = 1;
217217 io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {
218218 error.Canceled => |e| return e,
219 else => {},
220219 };
221220 }
222221
223222 fn sleepRecancel(io: Io, result: *usize) void {
224223 io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {
225224 error.Canceled => io.recancel(),
226 else => {},
227225 };
228226 result.* = 1;
229227 }
......@@ -523,8 +521,6 @@ test "cancel sleep" {
523521 fn blockUntilCanceled(io: Io) void {
524522 while (true) io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {
525523 error.Canceled => return,
526 error.UnsupportedClock => @panic("unsupported clock"),
527 error.Unexpected => @panic("unexpected"),
528524 };
529525 }
530526 };
......@@ -552,8 +548,6 @@ test "tasks spawned in group after Group.cancel are canceled" {
552548 fn blockUntilCanceled(io: Io) Io.Cancelable!void {
553549 while (true) io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {
554550 error.Canceled => |e| return e,
555 error.UnsupportedClock => @panic("unsupported clock"),
556 error.Unexpected => @panic("unexpected"),
557551 };
558552 }
559553 };
lib/std/crypto/Certificate/Bundle.zig+2-2
......@@ -212,7 +212,7 @@ pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp, i
212212 }
213213}
214214
215pub const AddCertsFromFilePathError = Io.File.OpenError || AddCertsFromFileError || Io.Clock.Error;
215pub const AddCertsFromFilePathError = Io.File.OpenError || AddCertsFromFileError;
216216
217217pub fn addCertsFromFilePathAbsolute(
218218 cb: *Bundle,
......@@ -338,7 +338,7 @@ test "scan for OS-provided certificates" {
338338 var bundle: Bundle = .{};
339339 defer bundle.deinit(gpa);
340340
341 const now = try Io.Clock.real.now(io);
341 const now = Io.Clock.real.now(io);
342342
343343 try bundle.rescan(gpa, io, now);
344344}
lib/std/http/Client.zig+1-1
......@@ -1700,7 +1700,7 @@ pub fn request(
17001700 defer client.ca_bundle_mutex.unlock(io);
17011701
17021702 if (client.now == null) {
1703 const now = try Io.Clock.real.now(io);
1703 const now = Io.Clock.real.now(io);
17041704 client.now = now;
17051705 client.ca_bundle.rescan(client.allocator, io, now) catch
17061706 return error.CertificateBundleLoadFailure;
lib/std/os/linux.zig+5-5
......@@ -1914,21 +1914,21 @@ fn init_vdso_clock_gettime(clk: clockid_t, ts: *timespec) callconv(.c) usize {
19141914 @atomicStore(?VdsoClockGettime, &vdso_clock_gettime, ptr, .monotonic);
19151915 // Call into the VDSO if available
19161916 if (ptr) |f| return f(clk, ts);
1917 return @as(usize, @bitCast(-@as(isize, @intFromEnum(E.NOSYS))));
1917 return @bitCast(-@as(isize, @intFromEnum(E.NOSYS)));
19181918}
19191919
1920pub fn clock_getres(clk_id: i32, tp: *timespec) usize {
1920pub fn clock_getres(clk_id: clockid_t, tp: *timespec) usize {
19211921 return syscall2(
19221922 if (@hasField(SYS, "clock_getres") and native_arch != .hexagon) .clock_getres else .clock_getres_time64,
1923 @as(usize, @bitCast(@as(isize, clk_id))),
1923 @as(usize, @intFromEnum(clk_id)),
19241924 @intFromPtr(tp),
19251925 );
19261926}
19271927
1928pub fn clock_settime(clk_id: i32, tp: *const timespec) usize {
1928pub fn clock_settime(clk_id: clockid_t, tp: *const timespec) usize {
19291929 return syscall2(
19301930 if (@hasField(SYS, "clock_settime") and native_arch != .hexagon) .clock_settime else .clock_settime64,
1931 @as(usize, @bitCast(@as(isize, clk_id))),
1931 @as(usize, @intFromEnum(clk_id)),
19321932 @intFromPtr(tp),
19331933 );
19341934}
lib/std/os/linux/IoUring/test.zig+2-2
......@@ -620,12 +620,12 @@ test "timeout (after a relative time)" {
620620 const margin = 5;
621621 const ts: linux.kernel_timespec = .{ .sec = 0, .nsec = ms * 1000000 };
622622
623 const started = try std.Io.Clock.awake.now(io);
623 const started = std.Io.Clock.awake.now(io);
624624 const sqe = try ring.timeout(0x55555555, &ts, 0, 0);
625625 try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe.opcode);
626626 try testing.expectEqual(@as(u32, 1), try ring.submit());
627627 const cqe = try ring.copy_cqe();
628 const stopped = try std.Io.Clock.awake.now(io);
628 const stopped = std.Io.Clock.awake.now(io);
629629
630630 try testing.expectEqual(linux.io_uring_cqe{
631631 .user_data = 0x55555555,
lib/std/posix.zig-52
......@@ -864,58 +864,6 @@ pub fn dl_iterate_phdr(
864864 }
865865}
866866
867pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError;
868
869pub fn clock_gettime(clock_id: clockid_t) ClockGetTimeError!timespec {
870 var tp: timespec = undefined;
871
872 if (native_os == .windows) {
873 @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.time API");
874 } else if (native_os == .wasi and !builtin.link_libc) {
875 var ts: timestamp_t = undefined;
876 switch (system.clock_time_get(clock_id, 1, &ts)) {
877 .SUCCESS => {
878 tp = .{
879 .sec = @intCast(ts / std.time.ns_per_s),
880 .nsec = @intCast(ts % std.time.ns_per_s),
881 };
882 },
883 .INVAL => return error.UnsupportedClock,
884 else => |err| return unexpectedErrno(err),
885 }
886 return tp;
887 }
888
889 switch (errno(system.clock_gettime(clock_id, &tp))) {
890 .SUCCESS => return tp,
891 .FAULT => unreachable,
892 .INVAL => return error.UnsupportedClock,
893 else => |err| return unexpectedErrno(err),
894 }
895}
896
897pub fn clock_getres(clock_id: clockid_t, res: *timespec) ClockGetTimeError!void {
898 if (native_os == .wasi and !builtin.link_libc) {
899 var ts: timestamp_t = undefined;
900 switch (system.clock_res_get(@bitCast(clock_id), &ts)) {
901 .SUCCESS => res.* = .{
902 .sec = @intCast(ts / std.time.ns_per_s),
903 .nsec = @intCast(ts % std.time.ns_per_s),
904 },
905 .INVAL => return error.UnsupportedClock,
906 else => |err| return unexpectedErrno(err),
907 }
908 return;
909 }
910
911 switch (errno(system.clock_getres(clock_id, res))) {
912 .SUCCESS => return,
913 .FAULT => unreachable,
914 .INVAL => return error.UnsupportedClock,
915 else => |err| return unexpectedErrno(err),
916 }
917}
918
919867pub const SchedGetAffinityError = error{PermissionDenied} || UnexpectedError;
920868
921869pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t {
lib/std/time.zig-182
......@@ -1,11 +1,3 @@
1const std = @import("std.zig");
2const builtin = @import("builtin");
3const assert = std.debug.assert;
4const testing = std.testing;
5const math = std.math;
6const windows = std.os.windows;
7const posix = std.posix;
8
91pub const epoch = @import("time/epoch.zig");
102
113// Divisions of a nanosecond.
......@@ -38,180 +30,6 @@ pub const s_per_hour = s_per_min * 60;
3830pub const s_per_day = s_per_hour * 24;
3931pub const s_per_week = s_per_day * 7;
4032
41/// An Instant represents a timestamp with respect to the currently
42/// executing program that ticks during suspend and can be used to
43/// record elapsed time unlike `nanoTimestamp`.
44///
45/// It tries to sample the system's fastest and most precise timer available.
46/// It also tries to be monotonic, but this is not a guarantee due to OS/hardware bugs.
47/// If you need monotonic readings for elapsed time, consider `Timer` instead.
48pub const Instant = struct {
49 timestamp: if (is_posix) posix.timespec else u64,
50
51 // true if we should use clock_gettime()
52 const is_posix = switch (builtin.os.tag) {
53 .windows, .uefi, .wasi => false,
54 else => true,
55 };
56
57 /// Queries the system for the current moment of time as an Instant.
58 /// This is not guaranteed to be monotonic or steadily increasing, but for
59 /// most implementations it is.
60 /// Returns `error.Unsupported` when a suitable clock is not detected.
61 pub fn now() error{Unsupported}!Instant {
62 const clock_id = switch (builtin.os.tag) {
63 .windows => {
64 // QPC on windows doesn't fail on >= XP/2000 and includes time suspended.
65 return .{ .timestamp = windows.QueryPerformanceCounter() };
66 },
67 .wasi => {
68 var ns: std.os.wasi.timestamp_t = undefined;
69 const rc = std.os.wasi.clock_time_get(.MONOTONIC, 1, &ns);
70 if (rc != .SUCCESS) return error.Unsupported;
71 return .{ .timestamp = ns };
72 },
73 .uefi => {
74 const value, _ = std.os.uefi.system_table.runtime_services.getTime() catch return error.Unsupported;
75 return .{ .timestamp = value.toEpoch() };
76 },
77 // On darwin, use UPTIME_RAW instead of MONOTONIC as it ticks while
78 // suspended.
79 .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => posix.CLOCK.UPTIME_RAW,
80 // On freebsd derivatives, use MONOTONIC_FAST as currently there's
81 // no precision tradeoff.
82 .freebsd, .dragonfly => posix.CLOCK.MONOTONIC_FAST,
83 // On linux, use BOOTTIME instead of MONOTONIC as it ticks while
84 // suspended.
85 .linux => posix.CLOCK.BOOTTIME,
86 // On other posix systems, MONOTONIC is generally the fastest and
87 // ticks while suspended.
88 else => posix.CLOCK.MONOTONIC,
89 };
90
91 const ts = posix.clock_gettime(clock_id) catch return error.Unsupported;
92 return .{ .timestamp = ts };
93 }
94
95 /// Quickly compares two instances between each other.
96 pub fn order(self: Instant, other: Instant) std.math.Order {
97 // windows and wasi timestamps are in u64 which is easily comparible
98 if (!is_posix) {
99 return std.math.order(self.timestamp, other.timestamp);
100 }
101
102 var ord = std.math.order(self.timestamp.sec, other.timestamp.sec);
103 if (ord == .eq) {
104 ord = std.math.order(self.timestamp.nsec, other.timestamp.nsec);
105 }
106 return ord;
107 }
108
109 /// Returns elapsed time in nanoseconds since the `earlier` Instant.
110 /// This assumes that the `earlier` Instant represents a moment in time before or equal to `self`.
111 /// This also assumes that the time that has passed between both Instants fits inside a u64 (~585 yrs).
112 pub fn since(self: Instant, earlier: Instant) u64 {
113 switch (builtin.os.tag) {
114 .windows => {
115 // We don't need to cache QPF as it's internally just a memory read to KUSER_SHARED_DATA
116 // (a read-only page of info updated and mapped by the kernel to all processes):
117 // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data
118 // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm
119 const qpc = self.timestamp - earlier.timestamp;
120 const qpf = windows.QueryPerformanceFrequency();
121
122 // 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it.
123 // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701
124 const common_qpf = 10_000_000;
125 if (qpf == common_qpf) {
126 return qpc * (ns_per_s / common_qpf);
127 }
128
129 // Convert to ns using fixed point.
130 const scale = @as(u64, std.time.ns_per_s << 32) / @as(u32, @intCast(qpf));
131 const result = (@as(u96, qpc) * scale) >> 32;
132 return @as(u64, @truncate(result));
133 },
134 .uefi, .wasi => {
135 // UEFI and WASI timestamps are directly in nanoseconds
136 return self.timestamp - earlier.timestamp;
137 },
138 else => {
139 // Convert timespec diff to ns
140 const seconds = @as(u64, @intCast(self.timestamp.sec - earlier.timestamp.sec));
141 const elapsed = (seconds * ns_per_s) + @as(u32, @intCast(self.timestamp.nsec));
142 return elapsed - @as(u32, @intCast(earlier.timestamp.nsec));
143 },
144 }
145 }
146};
147
148/// A monotonic, high performance timer.
149///
150/// Timer.start() is used to initialize the timer
151/// and gives the caller an opportunity to check for the existence of a supported clock.
152/// Once a supported clock is discovered,
153/// it is assumed that it will be available for the duration of the Timer's use.
154///
155/// Monotonicity is ensured by saturating on the most previous sample.
156/// This means that while timings reported are monotonic,
157/// they're not guaranteed to tick at a steady rate as this is up to the underlying system.
158pub const Timer = struct {
159 started: Instant,
160 previous: Instant,
161
162 pub const Error = error{TimerUnsupported};
163
164 /// Initialize the timer by querying for a supported clock.
165 /// Returns `error.TimerUnsupported` when such a clock is unavailable.
166 /// This should only fail in hostile environments such as linux seccomp misuse.
167 pub fn start() Error!Timer {
168 const current = Instant.now() catch return error.TimerUnsupported;
169 return Timer{ .started = current, .previous = current };
170 }
171
172 /// Reads the timer value since start or the last reset in nanoseconds.
173 pub fn read(self: *Timer) u64 {
174 const current = self.sample();
175 return current.since(self.started);
176 }
177
178 /// Resets the timer value to 0/now.
179 pub fn reset(self: *Timer) void {
180 const current = self.sample();
181 self.started = current;
182 }
183
184 /// Returns the current value of the timer in nanoseconds, then resets it.
185 pub fn lap(self: *Timer) u64 {
186 const current = self.sample();
187 defer self.started = current;
188 return current.since(self.started);
189 }
190
191 /// Returns an Instant sampled at the callsite that is
192 /// guaranteed to be monotonic with respect to the timer's starting point.
193 fn sample(self: *Timer) Instant {
194 const current = Instant.now() catch unreachable;
195 if (current.order(self.previous) == .gt) {
196 self.previous = current;
197 }
198 return self.previous;
199 }
200};
201
202test Timer {
203 const io = std.testing.io;
204
205 var timer = try Timer.start();
206
207 try std.Io.Clock.Duration.sleep(.{ .clock = .awake, .raw = .fromMilliseconds(10) }, io);
208 const time_0 = timer.read();
209 try testing.expect(time_0 > 0);
210
211 const time_1 = timer.lap();
212 try testing.expect(time_1 >= time_0);
213}
214
21533test {
21634 _ = epoch;
21735}
src/Compilation.zig+15-20
......@@ -331,48 +331,42 @@ const QueuedJobs = struct {
331331pub const Timer = union(enum) {
332332 unused,
333333 active: struct {
334 start: std.time.Instant,
334 start: Io.Timestamp,
335335 saved_ns: u64,
336336 },
337337 paused: u64,
338338 stopped,
339339
340 pub fn pause(t: *Timer) void {
340 pub fn pause(t: *Timer, io: Io) void {
341341 switch (t.*) {
342342 .unused => return,
343343 .active => |a| {
344 const current = std.time.Instant.now() catch unreachable;
345 const new_ns = switch (current.order(a.start)) {
346 .lt, .eq => 0,
347 .gt => current.since(a.start),
348 };
344 const current: Io.Timestamp = .now(io, .awake);
345 const new_ns: u64 = @intCast(current.nanoseconds -| a.start.nanoseconds);
349346 t.* = .{ .paused = a.saved_ns + new_ns };
350347 },
351348 .paused => unreachable,
352349 .stopped => unreachable,
353350 }
354351 }
355 pub fn @"resume"(t: *Timer) void {
352 pub fn @"resume"(t: *Timer, io: Io) void {
356353 switch (t.*) {
357354 .unused => return,
358355 .active => unreachable,
359356 .paused => |saved_ns| t.* = .{ .active = .{
360 .start = std.time.Instant.now() catch unreachable,
357 .start = .now(io, .awake),
361358 .saved_ns = saved_ns,
362359 } },
363360 .stopped => unreachable,
364361 }
365362 }
366 pub fn finish(t: *Timer) ?u64 {
363 pub fn finish(t: *Timer, io: Io) ?u64 {
367364 defer t.* = .stopped;
368365 switch (t.*) {
369366 .unused => return null,
370367 .active => |a| {
371 const current = std.time.Instant.now() catch unreachable;
372 const new_ns = switch (current.order(a.start)) {
373 .lt, .eq => 0,
374 .gt => current.since(a.start),
375 };
368 const current: Io.Timestamp = .now(io, .awake);
369 const new_ns: u64 = @intCast(current.nanoseconds -| a.start.nanoseconds);
376370 return a.saved_ns + new_ns;
377371 },
378372 .paused => |ns| return ns,
......@@ -387,7 +381,8 @@ pub const Timer = union(enum) {
387381/// is set.
388382pub fn startTimer(comp: *Compilation) Timer {
389383 if (comp.time_report == null) return .unused;
390 const now = std.time.Instant.now() catch @panic("std.time.Timer unsupported; cannot emit time report");
384 const io = comp.io;
385 const now: Io.Timestamp = .now(io, .awake);
391386 return .{ .active = .{
392387 .start = now,
393388 .saved_ns = 0,
......@@ -3408,7 +3403,7 @@ fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id) (Io.Cancel
34083403 defer sub_prog_node.end();
34093404
34103405 var timer = comp.startTimer();
3411 defer if (timer.finish()) |ns| {
3406 defer if (timer.finish(io)) |ns| {
34123407 comp.mutex.lockUncancelable(io);
34133408 defer comp.mutex.unlock(io);
34143409 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
34533448 }
34543449 if (comp.bin_file) |lf| {
34553450 var timer = comp.startTimer();
3456 defer if (timer.finish()) |ns| {
3451 defer if (timer.finish(io)) |ns| {
34573452 comp.mutex.lockUncancelable(io);
34583453 defer comp.mutex.unlock(io);
34593454 comp.time_report.?.stats.real_ns_link_flush = ns;
......@@ -4686,7 +4681,7 @@ fn performAllTheWork(
46864681 var decl_work_timer: ?Timer = null;
46874682 defer commit_timer: {
46884683 const t = &(decl_work_timer orelse break :commit_timer);
4689 const ns = t.finish() orelse break :commit_timer;
4684 const ns = t.finish(io) orelse break :commit_timer;
46904685 comp.mutex.lockUncancelable(io);
46914686 defer comp.mutex.unlock(io);
46924687 comp.time_report.?.stats.real_ns_decls = ns;
......@@ -4719,7 +4714,7 @@ fn performAllTheWork(
47194714 defer zir_prog_node.end();
47204715
47214716 var timer = comp.startTimer();
4722 defer if (timer.finish()) |ns| {
4717 defer if (timer.finish(io)) |ns| {
47234718 comp.mutex.lockUncancelable(io);
47244719 defer comp.mutex.unlock(io);
47254720 comp.time_report.?.stats.real_ns_files = ns;
src/Zcu.zig+6-4
......@@ -4754,6 +4754,7 @@ const TrackedUnitSema = struct {
47544754 analysis_timer_decl: ?InternPool.TrackedInst.Index,
47554755 pub fn end(tus: TrackedUnitSema, zcu: *Zcu) void {
47564756 const comp = zcu.comp;
4757 const io = comp.io;
47574758 if (tus.old_name) |old_name| {
47584759 zcu.sema_prog_node.completeOne(); // we're just renaming, but it's effectively completion
47594760 zcu.cur_sema_prog_node.setName(&old_name);
......@@ -4762,9 +4763,8 @@ const TrackedUnitSema = struct {
47624763 zcu.cur_sema_prog_node = .none;
47634764 }
47644765 report_time: {
4765 const sema_ns = zcu.cur_analysis_timer.?.finish() orelse break :report_time;
4766 const sema_ns = zcu.cur_analysis_timer.?.finish(io) orelse break :report_time;
47664767 const zir_decl = tus.analysis_timer_decl orelse break :report_time;
4767 const io = comp.io;
47684768 comp.mutex.lockUncancelable(io);
47694769 defer comp.mutex.unlock(io);
47704770 comp.time_report.?.stats.cpu_ns_sema += sema_ns;
......@@ -4779,11 +4779,13 @@ const TrackedUnitSema = struct {
47794779 gop.value_ptr.count += 1;
47804780 }
47814781 zcu.cur_analysis_timer = tus.old_analysis_timer;
4782 if (zcu.cur_analysis_timer) |*t| t.@"resume"();
4782 if (zcu.cur_analysis_timer) |*t| t.@"resume"(io);
47834783 }
47844784};
47854785pub fn trackUnitSema(zcu: *Zcu, name: []const u8, zir_inst: ?InternPool.TrackedInst.Index) TrackedUnitSema {
4786 if (zcu.cur_analysis_timer) |*t| t.pause();
4786 const comp = zcu.comp;
4787 const io = comp.io;
4788 if (zcu.cur_analysis_timer) |*t| t.pause(io);
47874789 const old_analysis_timer = zcu.cur_analysis_timer;
47884790 zcu.cur_analysis_timer = zcu.comp.startTimer();
47894791 const old_name: ?[std.Progress.Node.max_name_len]u8 = old_name: {
src/Zcu/PerThread.zig+3-3
......@@ -263,7 +263,7 @@ pub fn updateFile(
263263 var timer = comp.startTimer();
264264 // Any potential AST errors are converted to ZIR errors when we run AstGen/ZonGen.
265265 file.tree = try Ast.parse(gpa, source, file.getMode());
266 if (timer.finish()) |ns_parse| {
266 if (timer.finish(io)) |ns_parse| {
267267 comp.mutex.lockUncancelable(io);
268268 defer comp.mutex.unlock(io);
269269 comp.time_report.?.stats.cpu_ns_parse += ns_parse;
......@@ -295,7 +295,7 @@ pub fn updateFile(
295295 else => |e| return e,
296296 };
297297
298 if (timer.finish()) |ns_astgen| {
298 if (timer.finish(io)) |ns_astgen| {
299299 comp.mutex.lockUncancelable(io);
300300 defer comp.mutex.unlock(io);
301301 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
44854485
44864486 const codegen_result = runCodegenInner(pt, func_index, air);
44874487
4488 if (timer.finish()) |ns_codegen| report_time: {
4488 if (timer.finish(io)) |ns_codegen| report_time: {
44894489 const ip = &zcu.intern_pool;
44904490 const nav = ip.indexToKey(func_index).func.owner_nav;
44914491 const zir_decl = ip.getNav(nav).srcInst(ip);
src/link.zig+4-4
......@@ -1388,7 +1388,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
13881388 };
13891389
13901390 var timer = comp.startTimer();
1391 defer if (timer.finish()) |ns| {
1391 defer if (timer.finish(io)) |ns| {
13921392 comp.mutex.lockUncancelable(io);
13931393 defer comp.mutex.unlock(io);
13941394 comp.time_report.?.stats.cpu_ns_link += ns;
......@@ -1535,12 +1535,12 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void {
15351535 break :nav nav_index;
15361536 },
15371537 .link_func => |codegen_task| nav: {
1538 timer.pause();
1538 timer.pause(io);
15391539 const func, var mir = codegen_task.wait(&zcu.codegen_task_pool, io) catch |err| switch (err) {
15401540 error.Canceled, error.AlreadyReported => return,
15411541 };
15421542 defer mir.deinit(zcu);
1543 timer.@"resume"();
1543 timer.@"resume"(io);
15441544
15451545 const nav = zcu.funcInfo(func).owner_nav;
15461546 const fqn_slice = ip.getNav(nav).fqn.toSlice(ip);
......@@ -1592,7 +1592,7 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void {
15921592 },
15931593 };
15941594
1595 if (timer.finish()) |ns_link| report_time: {
1595 if (timer.finish(io)) |ns_link| report_time: {
15961596 comp.mutex.lockUncancelable(io);
15971597 defer comp.mutex.unlock(io);
15981598 const tr = &zcu.comp.time_report.?;