authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-03 20:16:18+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-03 20:16:18+01:00
loge5e4602b181360d20bbba56e9f15734c929d2a2f
treef124eb04f53acf6b8029ddaef027a21c78f934d2
parent7aae7dd3f4d4b85837369fa591e566ae812f87dc
parentfe5da36aa3b1dbeb02276c6628640c68a5922191

Merge pull request 'std: finish moving time to Io interface' (#31086) from time into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31086

23 files changed, 272 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+93-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) Clock.ResolutionError!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,53 @@ 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 pub const ResolutionError = error{
717 ClockUnavailable,
718 Unexpected,
719 };
720
721 /// Reveals the granularity of `clock`. May be zero, indicating
722 /// unsupported clock.
723 pub fn resolution(clock: Clock, io: Io) ResolutionError!Io.Duration {
724 return io.vtable.clockResolution(io.userdata, clock);
725 }
726
713727 pub const Timestamp = struct {
714728 raw: Io.Timestamp,
715729 clock: Clock,
716730
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 {
731 /// This function is not cancelable because it does not block.
732 ///
733 /// Resolution is determined by `resolution` which may be 0 if
734 /// the clock is unsupported.
735 ///
736 /// See also:
737 /// * `Clock.now`
738 pub fn now(io: Io, clock: Clock) Clock.Timestamp {
721739 return .{
722 .raw = try io.vtable.now(io.userdata, clock),
740 .raw = io.vtable.now(io.userdata, clock),
723741 .clock = clock,
724742 };
725743 }
726744
727 pub fn wait(t: Clock.Timestamp, io: Io) SleepError!void {
745 /// Sleeps until the timestamp arrives.
746 ///
747 /// See also:
748 /// * `Io.sleep`
749 /// * `Clock.Duration.sleep`
750 /// * `Timeout.sleep`
751 pub fn wait(t: Clock.Timestamp, io: Io) Cancelable!void {
728752 return io.vtable.sleep(io.userdata, .{ .deadline = t });
729753 }
730754
......@@ -752,30 +776,38 @@ pub const Clock = enum {
752776 };
753777 }
754778
755 pub fn fromNow(io: Io, duration: Clock.Duration) Error!Clock.Timestamp {
779 /// Resolution is determined by `resolution` which may be 0 if
780 /// the clock is unsupported.
781 pub fn fromNow(io: Io, duration: Clock.Duration) Clock.Timestamp {
756782 return .{
757783 .clock = duration.clock,
758 .raw = (try duration.clock.now(io)).addDuration(duration.raw),
784 .raw = duration.clock.now(io).addDuration(duration.raw),
759785 };
760786 }
761787
762 pub fn untilNow(timestamp: Clock.Timestamp, io: Io) Error!Clock.Duration {
763 const now_ts = try Clock.Timestamp.now(io, timestamp.clock);
788 /// Resolution is determined by `resolution` which may be 0 if
789 /// the clock is unsupported.
790 pub fn untilNow(timestamp: Clock.Timestamp, io: Io) Clock.Duration {
791 const now_ts = Clock.Timestamp.now(io, timestamp.clock);
764792 return timestamp.durationTo(now_ts);
765793 }
766794
767 pub fn durationFromNow(timestamp: Clock.Timestamp, io: Io) Error!Clock.Duration {
768 const now_ts = try timestamp.clock.now(io);
795 /// Resolution is determined by `resolution` which may be 0 if
796 /// the clock is unsupported.
797 pub fn durationFromNow(timestamp: Clock.Timestamp, io: Io) Clock.Duration {
798 const now_ts = timestamp.clock.now(io);
769799 return .{
770800 .clock = timestamp.clock,
771801 .raw = now_ts.durationTo(timestamp.raw),
772802 };
773803 }
774804
775 pub fn toClock(t: Clock.Timestamp, io: Io, clock: Clock) Error!Clock.Timestamp {
805 /// Resolution is determined by `resolution` which may be 0 if
806 /// the clock is unsupported.
807 pub fn toClock(t: Clock.Timestamp, io: Io, clock: Clock) Clock.Timestamp {
776808 if (t.clock == clock) return t;
777 const now_old = try t.clock.now(io);
778 const now_new = try clock.now(io);
809 const now_old = t.clock.now(io);
810 const now_new = clock.now(io);
779811 const duration = now_old.durationTo(t);
780812 return .{
781813 .clock = clock,
......@@ -793,7 +825,13 @@ pub const Clock = enum {
793825 raw: Io.Duration,
794826 clock: Clock,
795827
796 pub fn sleep(duration: Clock.Duration, io: Io) SleepError!void {
828 /// Waits until a specified amount of time has passed on `clock`.
829 ///
830 /// See also:
831 /// * `Io.sleep`
832 /// * `Clock.Timestamp.wait`
833 /// * `Timeout.sleep`
834 pub fn sleep(duration: Clock.Duration, io: Io) Cancelable!void {
797835 return io.vtable.sleep(io.userdata, .{ .duration = duration });
798836 }
799837 };
......@@ -802,6 +840,10 @@ pub const Clock = enum {
802840pub const Timestamp = struct {
803841 nanoseconds: i96,
804842
843 pub fn now(io: Io, clock: Clock) Io.Timestamp {
844 return io.vtable.now(io.userdata, clock);
845 }
846
805847 pub const zero: Timestamp = .{ .nanoseconds = 0 };
806848
807849 pub fn durationTo(from: Timestamp, to: Timestamp) Duration {
......@@ -844,6 +886,13 @@ pub const Timestamp = struct {
844886 .fill = n.fill,
845887 });
846888 }
889
890 /// Resolution is determined by `Clock.resolution` which may be 0 if
891 /// the clock is unsupported.
892 pub fn untilNow(t: Timestamp, io: Io, clock: Clock) Duration {
893 const now_ts = clock.now(io);
894 return t.durationTo(now_ts);
895 }
847896};
848897
849898pub const Duration = struct {
......@@ -883,12 +932,12 @@ pub const Timeout = union(enum) {
883932 duration: Clock.Duration,
884933 deadline: Clock.Timestamp,
885934
886 pub const Error = error{ Timeout, UnsupportedClock };
935 pub const Error = error{Timeout};
887936
888 pub fn toTimestamp(t: Timeout, io: Io) Clock.Error!?Clock.Timestamp {
937 pub fn toTimestamp(t: Timeout, io: Io) ?Clock.Timestamp {
889938 return switch (t) {
890939 .none => null,
891 .duration => |d| try .fromNow(io, d),
940 .duration => |d| .fromNow(io, d),
892941 .deadline => |d| d,
893942 };
894943 }
......@@ -896,20 +945,26 @@ pub const Timeout = union(enum) {
896945 pub fn toDeadline(t: Timeout, io: Io) Timeout {
897946 return switch (t) {
898947 .none => .none,
899 .duration => |d| .{ .deadline = Clock.Timestamp.fromNow(io, d) catch @panic("TODO") },
948 .duration => |d| .{ .deadline = .fromNow(io, d) },
900949 .deadline => |d| .{ .deadline = d },
901950 };
902951 }
903952
904 pub fn toDurationFromNow(t: Timeout, io: Io) Clock.Error!?Clock.Duration {
953 pub fn toDurationFromNow(t: Timeout, io: Io) ?Clock.Duration {
905954 return switch (t) {
906955 .none => null,
907956 .duration => |d| d,
908 .deadline => |d| try d.durationFromNow(io),
957 .deadline => |d| d.durationFromNow(io),
909958 };
910959 }
911960
912 pub fn sleep(timeout: Timeout, io: Io) SleepError!void {
961 /// Waits until the timeout has passed.
962 ///
963 /// See also:
964 /// * `Io.sleep`
965 /// * `Clock.Duration.sleep`
966 /// * `Clock.Timestamp.wait`
967 pub fn sleep(timeout: Timeout, io: Io) Cancelable!void {
913968 return io.vtable.sleep(io.userdata, timeout);
914969 }
915970};
......@@ -2027,9 +2082,13 @@ pub fn concurrent(
20272082 return future;
20282083}
20292084
2030pub const SleepError = error{UnsupportedClock} || UnexpectedError || Cancelable;
2031
2032pub fn sleep(io: Io, duration: Duration, clock: Clock) SleepError!void {
2085/// Waits until a specified amount of time has passed on `clock`.
2086///
2087/// See also:
2088/// * `Clock.Duration.sleep`
2089/// * `Clock.Timestamp.wait`
2090/// * `Timeout.sleep`
2091pub fn sleep(io: Io, duration: Duration, clock: Clock) Cancelable!void {
20332092 return io.vtable.sleep(io.userdata, .{ .duration = .{
20342093 .raw = duration,
20352094 .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.Clock.ResolutionError!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.Clock.ResolutionError!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.?;
stage1/wasi.c+9
......@@ -924,6 +924,15 @@ uint32_t wasi_snapshot_preview1_clock_time_get(uint32_t id, uint64_t precision,
924924 return wasi_errno_success;
925925}
926926
927uint32_t wasi_snapshot_preview1_clock_res_get(uint32_t id, uint32_t res_timestamp) {
928 uint8_t *const m = *wasm_memory;
929 uint64_t *res_timestamp_ptr = (uint64_t *)&m[res_timestamp];
930#if LOG_TRACE
931 fprintf(stderr, "wasi_snapshot_preview1_clock_res_get(%u, %llu)\n", id, (unsigned long long)res_timestamp);
932#endif
933 return wasi_errno_notcapable;
934}
935
927936uint32_t wasi_snapshot_preview1_path_remove_directory(uint32_t fd, uint32_t path, uint32_t path_len) {
928937 uint8_t *const m = *wasm_memory;
929938 const char *path_ptr = (const char *)&m[path];