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 {...@@ -107,7 +107,7 @@ pub const Environment = struct {
107 if (parsed > max_timestamp) return error.InvalidEpoch;107 if (parsed > max_timestamp) return error.InvalidEpoch;
108 return .{ .provided = parsed };108 return .{ .provided = parsed };
109 } else {109 } else {
110 const timestamp = try Io.Clock.real.now(io);110 const timestamp = Io.Clock.real.now(io);
111 const seconds = std.math.cast(u64, timestamp.toSeconds()) orelse return error.InvalidEpoch;111 const seconds = std.math.cast(u64, timestamp.toSeconds()) orelse return error.InvalidEpoch;
112 return .{ .system = std.math.clamp(seconds, 0, max_timestamp) };112 return .{ .system = std.math.clamp(seconds, 0, max_timestamp) };
113 }113 }
lib/compiler/aro/aro/Preprocessor.zig+1-1
...@@ -301,7 +301,7 @@ pub fn init(comp: *Compilation, source_epoch: SourceEpoch) Preprocessor {...@@ -301,7 +301,7 @@ pub fn init(comp: *Compilation, source_epoch: SourceEpoch) Preprocessor {
301/// Initialize Preprocessor with builtin macros.301/// Initialize Preprocessor with builtin macros.
302pub fn initDefault(comp: *Compilation) !Preprocessor {302pub fn initDefault(comp: *Compilation) !Preprocessor {
303 const source_epoch: SourceEpoch = comp.environment.sourceEpoch(comp.io) catch |er| switch (er) {303 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: {
305 const diagnostic: Diagnostic = .invalid_source_epoch;305 const diagnostic: Diagnostic = .invalid_source_epoch;
306 try comp.diagnostics.add(.{ .text = diagnostic.fmt, .kind = diagnostic.kind, .opt = diagnostic.opt, .location = null });306 try comp.diagnostics.add(.{ .text = diagnostic.fmt, .kind = diagnostic.kind, .opt = diagnostic.opt, .location = null });
307 break :blk .default;307 break :blk .default;
lib/compiler/build_runner.zig+1-1
...@@ -548,7 +548,7 @@ pub fn main(init: process.Init.Minimal) !void {...@@ -548,7 +548,7 @@ pub fn main(init: process.Init.Minimal) !void {
548 break :w try .init(graph.cache.cwd);548 break :w try .init(graph.cache.cwd);
549 };549 };
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
553 run.web_server = if (webui_listen) |listen_address| ws: {553 run.web_server = if (webui_listen) |listen_address| ws: {
554 if (builtin.single_threaded) unreachable; // `fatal` above554 if (builtin.single_threaded) unreachable; // `fatal` above
lib/std/Build/Step.zig+11-8
...@@ -266,16 +266,19 @@ pub fn init(options: StepOptions) Step {...@@ -266,16 +266,19 @@ pub fn init(options: StepOptions) Step {
266/// here.266/// here.
267pub fn make(s: *Step, options: MakeOptions) error{ MakeFailed, MakeSkipped }!void {267pub fn make(s: *Step, options: MakeOptions) error{ MakeFailed, MakeSkipped }!void {
268 const arena = s.owner.allocator;268 const arena = s.owner.allocator;
269 const graph = s.owner.graph;
270 const io = graph.io;
269271
270 var timer: ?std.time.Timer = t: {272 var start_ts: ?Io.Timestamp = t: {
271 if (!s.owner.graph.time_report) break :t null;273 if (!graph.time_report) break :t null;
272 if (s.id == .compile) break :t null;274 if (s.id == .compile) break :t null;
273 if (s.id == .run and s.cast(Run).?.stdio == .zig_test) break :t null;275 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);
275 };277 };
276 const make_result = s.makeFn(s, options);278 const make_result = s.makeFn(s, options);
277 if (timer) |*t| {279 if (start_ts) |*ts| {
278 options.web_server.?.updateTimeReportGeneric(s, t.read());280 const duration = ts.untilNow(io, .awake);
281 options.web_server.?.updateTimeReportGeneric(s, duration);
279 }282 }
280283
281 make_result catch |err| switch (err) {284 make_result catch |err| switch (err) {
...@@ -534,7 +537,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build....@@ -534,7 +537,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.
534 const arena = b.allocator;537 const arena = b.allocator;
535 const io = b.graph.io;538 const io = b.graph.io;
536539
537 var timer = try std.time.Timer.start();540 const start_ts = Io.Clock.awake.now(io);
538541
539 try sendMessage(io, zp.child.stdin.?, .update);542 try sendMessage(io, zp.child.stdin.?, .update);
540 if (!watch) try sendMessage(io, zp.child.stdin.?, .exit);543 if (!watch) try sendMessage(io, zp.child.stdin.?, .exit);
...@@ -637,7 +640,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build....@@ -637,7 +640,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.
637 .compile = s.cast(Step.Compile).?,640 .compile = s.cast(Step.Compile).?,
638 .use_llvm = tr.flags.use_llvm,641 .use_llvm = tr.flags.use_llvm,
639 .stats = tr.stats,642 .stats = tr.stats,
640 .ns_total = timer.read(),643 .ns_total = @intCast(start_ts.untilNow(io, .awake).toNanoseconds()),
641 .llvm_pass_timings_len = tr.llvm_pass_timings_len,644 .llvm_pass_timings_len = tr.llvm_pass_timings_len,
642 .files_len = tr.files_len,645 .files_len = tr.files_len,
643 .decls_len = tr.decls_len,646 .decls_len = tr.decls_len,
...@@ -648,7 +651,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build....@@ -648,7 +651,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.
648 }651 }
649 }652 }
650653
651 s.result_duration_ns = timer.read();654 s.result_duration_ns = @intCast(start_ts.untilNow(io, .awake).toNanoseconds());
652655
653 const stderr_contents = zp.multi_reader.reader(1).buffered();656 const stderr_contents = zp.multi_reader.reader(1).buffered();
654 if (stderr_contents.len > 0) {657 if (stderr_contents.len > 0) {
lib/std/Build/Step/Run.zig+13-13
...@@ -1587,12 +1587,12 @@ fn spawnChildAndCollect(...@@ -1587,12 +1587,12 @@ fn spawnChildAndCollect(
1587 };1587 };
15881588
1589 if (run.stdio == .zig_test) {1589 if (run.stdio == .zig_test) {
1590 const started: Io.Clock.Timestamp = try .now(io, .awake);1590 const started: Io.Clock.Timestamp = .now(io, .awake);
1591 const result = evalZigTest(run, spawn_options, options, fuzz_context) catch |err| switch (err) {1591 const result = evalZigTest(run, spawn_options, options, fuzz_context) catch |err| switch (err) {
1592 error.Canceled => |e| return e,1592 error.Canceled => |e| return e,
1593 else => |e| e,1593 else => |e| e,
1594 };1594 };
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);
1596 try result;1596 try result;
1597 return null;1597 return null;
1598 } else {1598 } else {
...@@ -1607,12 +1607,12 @@ fn spawnChildAndCollect(...@@ -1607,12 +1607,12 @@ fn spawnChildAndCollect(
1607 defer if (inherit) io.unlockStderr();1607 defer if (inherit) io.unlockStderr();
1608 try setColorEnvironmentVariables(run, environ_map, terminal_mode);1608 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);
1611 const result = evalGeneric(run, spawn_options) catch |err| switch (err) {1611 const result = evalGeneric(run, spawn_options) catch |err| switch (err) {
1612 error.Canceled => |e| return e,1612 error.Canceled => |e| return e,
1613 else => |e| e,1613 else => |e| e,
1614 };1614 };
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);
1616 return try result;1616 return try result;
1617 }1617 }
1618}1618}
...@@ -1869,7 +1869,7 @@ fn waitZigTest(...@@ -1869,7 +1869,7 @@ fn waitZigTest(
18691869
1870 var active_test_index: ?u32 = null;1870 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
1874 var coverage_id: ?u64 = null;1874 var coverage_id: ?u64 = null;
18751875
...@@ -1908,11 +1908,11 @@ fn waitZigTest(...@@ -1908,11 +1908,11 @@ fn waitZigTest(
1908 multi_reader.fill(64, timeout) catch |err| switch (err) {1908 multi_reader.fill(64, timeout) catch |err| switch (err) {
1909 error.Timeout => return .{ .timeout = .{1909 error.Timeout => return .{ .timeout = .{
1910 .active_test_index = active_test_index,1910 .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),
1912 } },1912 } },
1913 error.EndOfStream => return .{ .no_poll = .{1913 error.EndOfStream => return .{ .no_poll = .{
1914 .active_test_index = active_test_index,1914 .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),
1916 } },1916 } },
1917 else => |e| return e,1917 else => |e| return e,
1918 };1918 };
...@@ -1926,11 +1926,11 @@ fn waitZigTest(...@@ -1926,11 +1926,11 @@ fn waitZigTest(
1926 multi_reader.fill(64, timeout) catch |err| switch (err) {1926 multi_reader.fill(64, timeout) catch |err| switch (err) {
1927 error.Timeout => return .{ .timeout = .{1927 error.Timeout => return .{ .timeout = .{
1928 .active_test_index = active_test_index,1928 .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),
1930 } },1930 } },
1931 error.EndOfStream => return .{ .no_poll = .{1931 error.EndOfStream => return .{ .no_poll = .{
1932 .active_test_index = active_test_index,1932 .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),
1934 } },1934 } },
1935 else => |e| return e,1935 else => |e| return e,
1936 };1936 };
...@@ -1976,13 +1976,13 @@ fn waitZigTest(...@@ -1976,13 +1976,13 @@ fn waitZigTest(
1976 @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64));1976 @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64));
19771977
1978 active_test_index = null;1978 active_test_index = null;
1979 last_update = try .now(io, .awake);1979 last_update = .now(io, .awake);
19801980
1981 requestNextTest(io, child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err };1981 requestNextTest(io, child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err };
1982 },1982 },
1983 .test_started => {1983 .test_started => {
1984 active_test_index = opt_metadata.*.?.next_index - 1;1984 active_test_index = opt_metadata.*.?.next_index - 1;
1985 last_update = try .now(io, .awake);1985 last_update = .now(io, .awake);
1986 },1986 },
1987 .test_results => {1987 .test_results => {
1988 assert(fuzz_context == null);1988 assert(fuzz_context == null);
...@@ -2026,7 +2026,7 @@ fn waitZigTest(...@@ -2026,7 +2026,7 @@ fn waitZigTest(
20262026
2027 active_test_index = null;2027 active_test_index = null;
20282028
2029 const now: Io.Clock.Timestamp = try .now(io, .awake);2029 const now: Io.Clock.Timestamp = .now(io, .awake);
2030 md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds);2030 md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds);
2031 last_update = now;2031 last_update = now;
20322032
...@@ -2239,7 +2239,7 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul...@@ -2239,7 +2239,7 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul
2239 return error.StderrStreamTooLong;2239 return error.StderrStreamTooLong;
2240 }2240 }
2241 } else |err| switch (err) {2241 } else |err| switch (err) {
2242 error.UnsupportedClock, error.Timeout => unreachable,2242 error.Timeout => unreachable,
2243 error.EndOfStream => {},2243 error.EndOfStream => {},
2244 else => |e| return e,2244 else => |e| return e,
2245 }2245 }
lib/std/Build/WebServer.zig+3-3
...@@ -243,7 +243,7 @@ pub fn finishBuild(ws: *WebServer, opts: struct {...@@ -243,7 +243,7 @@ pub fn finishBuild(ws: *WebServer, opts: struct {
243243
244pub fn now(s: *const WebServer) i64 {244pub fn now(s: *const WebServer) i64 {
245 const io = s.graph.io;245 const io = s.graph.io;
246 const ts = base_clock.now(io) catch s.base_timestamp;246 const ts = base_clock.now(io);
247 return @intCast(s.base_timestamp.durationTo(ts).toNanoseconds());247 return @intCast(s.base_timestamp.durationTo(ts).toNanoseconds());
248}248}
249249
...@@ -761,7 +761,7 @@ pub fn updateTimeReportCompile(ws: *WebServer, opts: struct {...@@ -761,7 +761,7 @@ pub fn updateTimeReportCompile(ws: *WebServer, opts: struct {
761 ws.notifyUpdate();761 ws.notifyUpdate();
762}762}
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 {
765 const gpa = ws.gpa;765 const gpa = ws.gpa;
766 const io = ws.graph.io;766 const io = ws.graph.io;
767767
...@@ -780,7 +780,7 @@ pub fn updateTimeReportGeneric(ws: *WebServer, step: *Build.Step, ns_total: u64)...@@ -780,7 +780,7 @@ pub fn updateTimeReportGeneric(ws: *WebServer, step: *Build.Step, ns_total: u64)
780 const out: *align(1) abi.time_report.GenericResult = @ptrCast(buf);780 const out: *align(1) abi.time_report.GenericResult = @ptrCast(buf);
781 out.* = .{781 out.* = .{
782 .step_idx = step_idx,782 .step_idx = step_idx,
783 .ns_total = ns_total,783 .ns_total = @intCast(duration.toNanoseconds()),
784 };784 };
785 {785 {
786 ws.time_report_mutex.lock(io) catch return;786 ws.time_report_mutex.lock(io) catch return;
lib/std/Io.zig+88-34
...@@ -231,8 +231,9 @@ pub const VTable = struct {...@@ -231,8 +231,9 @@ pub const VTable = struct {
231231
232 progressParentFile: *const fn (?*anyopaque) std.Progress.ParentFileError!File,232 progressParentFile: *const fn (?*anyopaque) std.Progress.ParentFileError!File,
233233
234 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,234 now: *const fn (?*anyopaque, Clock) Timestamp,
235 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,235 clockResolution: *const fn (?*anyopaque, Clock) Duration,
236 sleep: *const fn (?*anyopaque, Timeout) Cancelable!void,
236237
237 random: *const fn (?*anyopaque, buffer: []u8) void,238 random: *const fn (?*anyopaque, buffer: []u8) void,
238 randomSecure: *const fn (?*anyopaque, buffer: []u8) RandomSecureError!void,239 randomSecure: *const fn (?*anyopaque, buffer: []u8) RandomSecureError!void,
...@@ -701,30 +702,48 @@ pub const Clock = enum {...@@ -701,30 +702,48 @@ pub const Clock = enum {
701 /// thread.702 /// thread.
702 cpu_thread,703 cpu_thread,
703704
704 pub const Error = error{UnsupportedClock} || UnexpectedError;705 /// This function is not cancelable because it does not block.
705706 ///
706 /// This function is not cancelable because first of all it does not block,707 /// Resolution is determined by `resolution` which may be 0 if the
707 /// but more importantly, the cancelation logic itself may want to check708 /// clock is unsupported.
708 /// the time.709 ///
709 pub fn now(clock: Clock, io: Io) Error!Io.Timestamp {710 /// See also:
711 /// * `Clock.Timestamp.now`
712 pub fn now(clock: Clock, io: Io) Io.Timestamp {
710 return io.vtable.now(io.userdata, clock);713 return io.vtable.now(io.userdata, clock);
711 }714 }
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
713 pub const Timestamp = struct {722 pub const Timestamp = struct {
714 raw: Io.Timestamp,723 raw: Io.Timestamp,
715 clock: Clock,724 clock: Clock,
716725
717 /// This function is not cancelable because first of all it does not block,726 /// This function is not cancelable because it does not block.
718 /// but more importantly, the cancelation logic itself may want to check727 ///
719 /// the time.728 /// Resolution is determined by `resolution` which may be 0 if
720 pub fn now(io: Io, clock: Clock) Error!Clock.Timestamp {729 /// the clock is unsupported.
730 ///
731 /// See also:
732 /// * `Clock.now`
733 pub fn now(io: Io, clock: Clock) Clock.Timestamp {
721 return .{734 return .{
722 .raw = try io.vtable.now(io.userdata, clock),735 .raw = io.vtable.now(io.userdata, clock),
723 .clock = clock,736 .clock = clock,
724 };737 };
725 }738 }
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 {
728 return io.vtable.sleep(io.userdata, .{ .deadline = t });747 return io.vtable.sleep(io.userdata, .{ .deadline = t });
729 }748 }
730749
...@@ -752,30 +771,38 @@ pub const Clock = enum {...@@ -752,30 +771,38 @@ pub const Clock = enum {
752 };771 };
753 }772 }
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 {
756 return .{777 return .{
757 .clock = duration.clock,778 .clock = duration.clock,
758 .raw = (try duration.clock.now(io)).addDuration(duration.raw),779 .raw = duration.clock.now(io).addDuration(duration.raw),
759 };780 };
760 }781 }
761782
762 pub fn untilNow(timestamp: Clock.Timestamp, io: Io) Error!Clock.Duration {783 /// Resolution is determined by `resolution` which may be 0 if
763 const now_ts = try Clock.Timestamp.now(io, timestamp.clock);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);
764 return timestamp.durationTo(now_ts);787 return timestamp.durationTo(now_ts);
765 }788 }
766789
767 pub fn durationFromNow(timestamp: Clock.Timestamp, io: Io) Error!Clock.Duration {790 /// Resolution is determined by `resolution` which may be 0 if
768 const now_ts = try timestamp.clock.now(io);791 /// the clock is unsupported.
792 pub fn durationFromNow(timestamp: Clock.Timestamp, io: Io) Clock.Duration {
793 const now_ts = timestamp.clock.now(io);
769 return .{794 return .{
770 .clock = timestamp.clock,795 .clock = timestamp.clock,
771 .raw = now_ts.durationTo(timestamp.raw),796 .raw = now_ts.durationTo(timestamp.raw),
772 };797 };
773 }798 }
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 {
776 if (t.clock == clock) return t;803 if (t.clock == clock) return t;
777 const now_old = try t.clock.now(io);804 const now_old = t.clock.now(io);
778 const now_new = try clock.now(io);805 const now_new = clock.now(io);
779 const duration = now_old.durationTo(t);806 const duration = now_old.durationTo(t);
780 return .{807 return .{
781 .clock = clock,808 .clock = clock,
...@@ -793,7 +820,13 @@ pub const Clock = enum {...@@ -793,7 +820,13 @@ pub const Clock = enum {
793 raw: Io.Duration,820 raw: Io.Duration,
794 clock: Clock,821 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 {
797 return io.vtable.sleep(io.userdata, .{ .duration = duration });830 return io.vtable.sleep(io.userdata, .{ .duration = duration });
798 }831 }
799 };832 };
...@@ -802,6 +835,10 @@ pub const Clock = enum {...@@ -802,6 +835,10 @@ pub const Clock = enum {
802pub const Timestamp = struct {835pub const Timestamp = struct {
803 nanoseconds: i96,836 nanoseconds: i96,
804837
838 pub fn now(io: Io, clock: Clock) Io.Timestamp {
839 return io.vtable.now(io.userdata, clock);
840 }
841
805 pub const zero: Timestamp = .{ .nanoseconds = 0 };842 pub const zero: Timestamp = .{ .nanoseconds = 0 };
806843
807 pub fn durationTo(from: Timestamp, to: Timestamp) Duration {844 pub fn durationTo(from: Timestamp, to: Timestamp) Duration {
...@@ -844,6 +881,13 @@ pub const Timestamp = struct {...@@ -844,6 +881,13 @@ pub const Timestamp = struct {
844 .fill = n.fill,881 .fill = n.fill,
845 });882 });
846 }883 }
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 }
847};891};
848892
849pub const Duration = struct {893pub const Duration = struct {
...@@ -883,12 +927,12 @@ pub const Timeout = union(enum) {...@@ -883,12 +927,12 @@ pub const Timeout = union(enum) {
883 duration: Clock.Duration,927 duration: Clock.Duration,
884 deadline: Clock.Timestamp,928 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 {
889 return switch (t) {933 return switch (t) {
890 .none => null,934 .none => null,
891 .duration => |d| try .fromNow(io, d),935 .duration => |d| .fromNow(io, d),
892 .deadline => |d| d,936 .deadline => |d| d,
893 };937 };
894 }938 }
...@@ -896,20 +940,26 @@ pub const Timeout = union(enum) {...@@ -896,20 +940,26 @@ pub const Timeout = union(enum) {
896 pub fn toDeadline(t: Timeout, io: Io) Timeout {940 pub fn toDeadline(t: Timeout, io: Io) Timeout {
897 return switch (t) {941 return switch (t) {
898 .none => .none,942 .none => .none,
899 .duration => |d| .{ .deadline = Clock.Timestamp.fromNow(io, d) catch @panic("TODO") },943 .duration => |d| .{ .deadline = .fromNow(io, d) },
900 .deadline => |d| .{ .deadline = d },944 .deadline => |d| .{ .deadline = d },
901 };945 };
902 }946 }
903947
904 pub fn toDurationFromNow(t: Timeout, io: Io) Clock.Error!?Clock.Duration {948 pub fn toDurationFromNow(t: Timeout, io: Io) ?Clock.Duration {
905 return switch (t) {949 return switch (t) {
906 .none => null,950 .none => null,
907 .duration => |d| d,951 .duration => |d| d,
908 .deadline => |d| try d.durationFromNow(io),952 .deadline => |d| d.durationFromNow(io),
909 };953 };
910 }954 }
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 {
913 return io.vtable.sleep(io.userdata, timeout);963 return io.vtable.sleep(io.userdata, timeout);
914 }964 }
915};965};
...@@ -2027,9 +2077,13 @@ pub fn concurrent(...@@ -2027,9 +2077,13 @@ pub fn concurrent(
2027 return future;2077 return future;
2028}2078}
20292079
2030pub const SleepError = error{UnsupportedClock} || UnexpectedError || Cancelable;2080/// Waits until a specified amount of time has passed on `clock`.
20312081///
2032pub fn sleep(io: Io, duration: Duration, clock: Clock) SleepError!void {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 {
2033 return io.vtable.sleep(io.userdata, .{ .duration = .{2087 return io.vtable.sleep(io.userdata, .{ .duration = .{
2034 .raw = duration,2088 .raw = duration,
2035 .clock = clock,2089 .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 {...@@ -179,7 +179,7 @@ fn rebase(r: *Io.Reader, capacity: usize) Io.Reader.RebaseError!void {
179179
180fn fillUntimed(context: *Context, capacity: usize) Io.Reader.Error!void {180fn fillUntimed(context: *Context, capacity: usize) Io.Reader.Error!void {
181 fill(context.mr, capacity, .none) catch |err| switch (err) {181 fill(context.mr, capacity, .none) catch |err| switch (err) {
182 error.Timeout, error.UnsupportedClock => unreachable,182 error.Timeout => unreachable,
183 error.Canceled, error.ConcurrencyUnavailable => |e| {183 error.Canceled, error.ConcurrencyUnavailable => |e| {
184 context.err = e;184 context.err = e;
185 return error.ReadFailed;185 return error.ReadFailed;
lib/std/Io/Threaded.zig+99-61
...@@ -1712,6 +1712,7 @@ pub fn io(t: *Threaded) Io {...@@ -1712,6 +1712,7 @@ pub fn io(t: *Threaded) Io {
1712 .progressParentFile = progressParentFile,1712 .progressParentFile = progressParentFile,
17131713
1714 .now = now,1714 .now = now,
1715 .clockResolution = clockResolution,
1715 .sleep = sleep,1716 .sleep = sleep,
17161717
1717 .random = random,1718 .random = random,
...@@ -1875,6 +1876,7 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -1875,6 +1876,7 @@ pub fn ioBasic(t: *Threaded) Io {
1875 .progressParentFile = progressParentFile,1876 .progressParentFile = progressParentFile,
18761877
1877 .now = now,1878 .now = now,
1879 .clockResolution = clockResolution,
1878 .sleep = sleep,1880 .sleep = sleep,
18791881
1880 .random = random,1882 .random = random,
...@@ -2487,7 +2489,7 @@ fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io....@@ -2487,7 +2489,7 @@ fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.
2487 const t: *Threaded = @ptrCast(@alignCast(userdata));2489 const t: *Threaded = @ptrCast(@alignCast(userdata));
2488 const t_io = ioBasic(t);2490 const t_io = ioBasic(t);
2489 const timeout_ns: ?u64 = ns: {2491 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;
2491 break :ns std.math.lossyCast(u64, d.raw.toNanoseconds());2493 break :ns std.math.lossyCast(u64, d.raw.toNanoseconds());
2492 };2494 };
2493 return Thread.futexWait(ptr, expected, timeout_ns);2495 return Thread.futexWait(ptr, expected, timeout_ns);
...@@ -2655,24 +2657,12 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void {...@@ -2655,24 +2657,12 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void {
2655fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.AwaitConcurrentError!void {2657fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.AwaitConcurrentError!void {
2656 const t: *Threaded = @ptrCast(@alignCast(userdata));2658 const t: *Threaded = @ptrCast(@alignCast(userdata));
2657 if (is_windows) {2659 if (is_windows) {
2658 const deadline: ?Io.Clock.Timestamp = timeout.toTimestamp(ioBasic(t)) catch |err| switch (err) {2660 const deadline: ?Io.Clock.Timestamp = timeout.toTimestamp(ioBasic(t));
2659 error.Unexpected => deadline: {
2660 recoverableOsBugDetected();
2661 break :deadline .{ .raw = .{ .nanoseconds = 0 }, .clock = .awake };
2662 },
2663 error.UnsupportedClock => |e| return e,
2664 };
2665 try batchAwaitWindows(b, true);2661 try batchAwaitWindows(b, true);
2666 while (b.pending.head != .none and b.completions.head == .none) {2662 while (b.pending.head != .none and b.completions.head == .none) {
2667 var delay_interval: windows.LARGE_INTEGER = interval: {2663 var delay_interval: windows.LARGE_INTEGER = interval: {
2668 const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER);2664 const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER);
2669 break :interval t.deadlineToWindowsInterval(d) catch |err| switch (err) {2665 break :interval t.deadlineToWindowsInterval(d);
2670 error.UnsupportedClock => |e| return e,
2671 error.Unexpected => {
2672 recoverableOsBugDetected();
2673 break :interval -1;
2674 },
2675 };
2676 };2666 };
2677 const alertable_syscall = try AlertableSyscall.start();2667 const alertable_syscall = try AlertableSyscall.start();
2678 const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval);2668 const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval);
...@@ -2754,7 +2744,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout...@@ -2754,7 +2744,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout
2754 else => {},2744 else => {},
2755 }2745 }
2756 const t_io = ioBasic(t);2746 const t_io = ioBasic(t);
2757 const deadline = timeout.toTimestamp(t_io) catch return error.UnsupportedClock;2747 const deadline = timeout.toTimestamp(t_io);
2758 while (true) {2748 while (true) {
2759 const timeout_ms: i32 = t: {2749 const timeout_ms: i32 = t: {
2760 if (b.completions.head != .none) {2750 if (b.completions.head != .none) {
...@@ -2765,7 +2755,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout...@@ -2765,7 +2755,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout
2765 break :t 0;2755 break :t 0;
2766 }2756 }
2767 const d = deadline orelse break :t -1;2757 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);
2769 if (duration.raw.nanoseconds <= 0) return error.Timeout;2759 if (duration.raw.nanoseconds <= 0) return error.Timeout;
2770 const max_poll_ms = std.math.maxInt(i32);2760 const max_poll_ms = std.math.maxInt(i32);
2771 break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));2761 break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));
...@@ -10821,22 +10811,21 @@ fn fileWriteFilePositional(...@@ -10821,22 +10811,21 @@ fn fileWriteFilePositional(
10821 return error.Unimplemented;10811 return error.Unimplemented;
10822}10812}
1082310813
10824fn nowPosix(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {10814fn nowPosix(clock: Io.Clock) Io.Timestamp {
10825 const clock_id: posix.clockid_t = clockToPosix(clock);10815 const clock_id: posix.clockid_t = clockToPosix(clock);
10826 var tp: posix.timespec = undefined;10816 var timespec: posix.timespec = undefined;
10827 switch (posix.errno(posix.system.clock_gettime(clock_id, &tp))) {10817 switch (posix.errno(posix.system.clock_gettime(clock_id, &timespec))) {
10828 .SUCCESS => return timestampFromPosix(&tp),10818 .SUCCESS => return timestampFromPosix(&timespec),
10829 .INVAL => return error.UnsupportedClock,10819 else => return .zero,
10830 else => |err| return posix.unexpectedErrno(err),
10831 }10820 }
10832}10821}
1083310822
10834fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {10823fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Timestamp {
10835 const t: *Threaded = @ptrCast(@alignCast(userdata));10824 const t: *Threaded = @ptrCast(@alignCast(userdata));
10836 _ = t;10825 _ = t;
10837 return nowInner(clock);10826 return nowInner(clock);
10838}10827}
10839fn nowInner(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {10828fn nowInner(clock: Io.Clock) Io.Timestamp {
10840 return switch (native_os) {10829 return switch (native_os) {
10841 .windows => nowWindows(clock),10830 .windows => nowWindows(clock),
10842 .wasi => nowWasi(clock),10831 .wasi => nowWasi(clock),
...@@ -10844,7 +10833,55 @@ fn nowInner(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {...@@ -10844,7 +10833,55 @@ fn nowInner(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
10844 };10833 };
10845}10834}
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 {
10848 switch (clock) {10885 switch (clock) {
10849 .real => {10886 .real => {
10850 // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds10887 // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds
...@@ -10882,8 +10919,7 @@ fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {...@@ -10882,8 +10919,7 @@ fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
10882 &times,10919 &times,
10883 @sizeOf(windows.KERNEL_USER_TIMES),10920 @sizeOf(windows.KERNEL_USER_TIMES),
10884 null,10921 null,
10885 ) != .SUCCESS)10922 ) != .SUCCESS) return .zero;
10886 return error.Unexpected;
1088710923
10888 const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime);10924 const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime);
10889 return .{ .nanoseconds = sum * 100 };10925 return .{ .nanoseconds = sum * 100 };
...@@ -10899,8 +10935,7 @@ fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {...@@ -10899,8 +10935,7 @@ fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
10899 &times,10935 &times,
10900 @sizeOf(windows.KERNEL_USER_TIMES),10936 @sizeOf(windows.KERNEL_USER_TIMES),
10901 null,10937 null,
10902 ) != .SUCCESS)10938 ) != .SUCCESS) return .zero;
10903 return error.Unexpected;
1090410939
10905 const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime);10940 const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime);
10906 return .{ .nanoseconds = sum * 100 };10941 return .{ .nanoseconds = sum * 100 };
...@@ -10908,23 +10943,23 @@ fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {...@@ -10908,23 +10943,23 @@ fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
10908 }10943 }
10909}10944}
1091010945
10911fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {10946fn nowWasi(clock: Io.Clock) Io.Timestamp {
10912 var ns: std.os.wasi.timestamp_t = undefined;10947 var ns: std.os.wasi.timestamp_t = undefined;
10913 const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns);10948 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;
10915 return .fromNanoseconds(ns);10950 return .fromNanoseconds(ns);
10916}10951}
1091710952
10918fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {10953fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.Cancelable!void {
10919 const t: *Threaded = @ptrCast(@alignCast(userdata));10954 const t: *Threaded = @ptrCast(@alignCast(userdata));
10920 if (timeout == .none) return;10955 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)));
10922 if (native_os == .wasi) return sleepWasi(t, timeout);10957 if (native_os == .wasi) return sleepWasi(t, timeout);
10923 if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout);10958 if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout);
10924 return sleepNanosleep(t, timeout);10959 return sleepNanosleep(t, timeout);
10925}10960}
1092610961
10927fn sleepPosix(timeout: Io.Timeout) Io.SleepError!void {10962fn sleepPosix(timeout: Io.Timeout) Io.Cancelable!void {
10928 const clock_id: posix.clockid_t = clockToPosix(switch (timeout) {10963 const clock_id: posix.clockid_t = clockToPosix(switch (timeout) {
10929 .none => .awake,10964 .none => .awake,
10930 .duration => |d| d.clock,10965 .duration => |d| d.clock,
...@@ -10944,25 +10979,27 @@ fn sleepPosix(timeout: Io.Timeout) Io.SleepError!void {...@@ -10944,25 +10979,27 @@ fn sleepPosix(timeout: Io.Timeout) Io.SleepError!void {
10944 } }, &timespec, &timespec);10979 } }, &timespec, &timespec);
10945 // POSIX-standard libc clock_nanosleep() returns *positive* errno values directly10980 // POSIX-standard libc clock_nanosleep() returns *positive* errno values directly
10946 switch (if (builtin.link_libc) @as(posix.E, @enumFromInt(rc)) else posix.errno(rc)) {10981 switch (if (builtin.link_libc) @as(posix.E, @enumFromInt(rc)) else posix.errno(rc)) {
10947 .SUCCESS => {
10948 syscall.finish();
10949 return;
10950 },
10951 .INTR => {10982 .INTR => {
10952 try syscall.checkCancel();10983 try syscall.checkCancel();
10953 continue;10984 continue;
10954 },10985 },
10955 .INVAL => return syscall.fail(error.UnsupportedClock),10986 // Handles SUCCESS as well as clock not available and unexpected
10956 else => |err| return syscall.unexpectedErrno(err),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 },
10957 }10994 }
10958 }10995 }
10959}10996}
1096010997
10961fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void {10998fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void {
10962 const t_io = ioBasic(t);10999 const t_io = ioBasic(t);
10963 const w = std.os.wasi;11000 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| .{
10966 .id = clockToWasi(d.clock),11003 .id = clockToWasi(d.clock),
10967 .timeout = std.math.lossyCast(u64, d.raw.nanoseconds),11004 .timeout = std.math.lossyCast(u64, d.raw.nanoseconds),
10968 .precision = 0,11005 .precision = 0,
...@@ -10987,13 +11024,13 @@ fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void {...@@ -10987,13 +11024,13 @@ fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void {
10987 syscall.finish();11024 syscall.finish();
10988}11025}
1098911026
10990fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void {11027fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void {
10991 const t_io = ioBasic(t);11028 const t_io = ioBasic(t);
10992 const sec_type = @typeInfo(posix.timespec).@"struct".fields[0].type;11029 const sec_type = @typeInfo(posix.timespec).@"struct".fields[0].type;
10993 const nsec_type = @typeInfo(posix.timespec).@"struct".fields[1].type;11030 const nsec_type = @typeInfo(posix.timespec).@"struct".fields[1].type;
1099411031
10995 var timespec: posix.timespec = t: {11032 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 .{
10997 .sec = std.math.maxInt(sec_type),11034 .sec = std.math.maxInt(sec_type),
10998 .nsec = std.math.maxInt(nsec_type),11035 .nsec = std.math.maxInt(nsec_type),
10999 };11036 };
...@@ -12630,7 +12667,7 @@ fn netReceivePosix(...@@ -12630,7 +12667,7 @@ fn netReceivePosix(
12630 var message_i: usize = 0;12667 var message_i: usize = 0;
12631 var data_i: usize = 0;12668 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
12635 recv: while (true) {12672 recv: while (true) {
12636 if (message_buffer.len - message_i == 0) return .{ null, message_i };12673 if (message_buffer.len - message_i == 0) return .{ null, message_i };
...@@ -12678,7 +12715,7 @@ fn netReceivePosix(...@@ -12678,7 +12715,7 @@ fn netReceivePosix(
1267812715
12679 const max_poll_ms = std.math.maxInt(u31);12716 const max_poll_ms = std.math.maxInt(u31);
12680 const timeout_ms: u31 = if (deadline) |d| t: {12717 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);
12682 if (duration.raw.nanoseconds <= 0) return .{ error.Timeout, message_i };12719 if (duration.raw.nanoseconds <= 0) return .{ error.Timeout, message_i };
12683 break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));12720 break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));
12684 } else max_poll_ms;12721 } else max_poll_ms;
...@@ -13875,7 +13912,11 @@ fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat {...@@ -13875,7 +13912,11 @@ fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat {
13875}13912}
1387613913
13877fn timestampFromPosix(timespec: *const posix.timespec) Io.Timestamp {13914fn 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);
13879}13920}
1388013921
13881fn timestampToPosix(nanoseconds: i96) posix.timespec {13922fn timestampToPosix(nanoseconds: i96) posix.timespec {
...@@ -14013,13 +14054,13 @@ fn lookupDns(...@@ -14013,13 +14054,13 @@ fn lookupDns(
14013 // boot clock is chosen because time the computer is suspended should count14054 // boot clock is chosen because time the computer is suspended should count
14014 // against time spent waiting for external messages to arrive.14055 // against time spent waiting for external messages to arrive.
14015 const clock: Io.Clock = .boot;14056 const clock: Io.Clock = .boot;
14016 var now_ts = try clock.now(t_io);14057 var now_ts = clock.now(t_io);
14017 const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds));14058 const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds));
14018 const attempt_duration: Io.Duration = .{14059 const attempt_duration: Io.Duration = .{
14019 .nanoseconds = (std.time.ns_per_s / rc.attempts) * @as(i96, rc.timeout_seconds),14060 .nanoseconds = (std.time.ns_per_s / rc.attempts) * @as(i96, rc.timeout_seconds),
14020 };14061 };
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)) {
14023 const max_messages = queries_buffer.len * HostName.ResolvConf.max_nameservers;14064 const max_messages = queries_buffer.len * HostName.ResolvConf.max_nameservers;
14024 {14065 {
14025 var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined;14066 var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined;
...@@ -17021,7 +17062,7 @@ const parking_futex = struct {...@@ -17021,7 +17062,7 @@ const parking_futex = struct {
17021 const deadline: ?Io.Clock.Timestamp = switch (timeout) {17062 const deadline: ?Io.Clock.Timestamp = switch (timeout) {
17022 .none => null,17063 .none => null,
17023 .duration => |d| .{17064 .duration => |d| .{
17024 .raw = (nowInner(d.clock) catch unreachable).addDuration(d.raw),17065 .raw = nowInner(d.clock).addDuration(d.raw),
17025 .clock = d.clock,17066 .clock = d.clock,
17026 },17067 },
17027 .deadline => |d| d,17068 .deadline => |d| d,
...@@ -17143,7 +17184,7 @@ const parking_sleep = struct {...@@ -17143,7 +17184,7 @@ const parking_sleep = struct {
17143 comptime {17184 comptime {
17144 assert(use_parking_sleep);17185 assert(use_parking_sleep);
17145 }17186 }
17146 fn sleep(deadline: ?Io.Clock.Timestamp) Io.SleepError!void {17187 fn sleep(deadline: ?Io.Clock.Timestamp) Io.Cancelable!void {
17147 const opt_thread = Thread.current;17188 const opt_thread = Thread.current;
17148 cancelable: {17189 cancelable: {
17149 const thread = opt_thread orelse break :cancelable;17190 const thread = opt_thread orelse break :cancelable;
...@@ -17216,12 +17257,9 @@ const parking_sleep = struct {...@@ -17216,12 +17257,9 @@ const parking_sleep = struct {
17216 }17257 }
17217 /// Sleep for approximately `ms` awake milliseconds in an attempt to work around Windows kernel bugs.17258 /// Sleep for approximately `ms` awake milliseconds in an attempt to work around Windows kernel bugs.
17218 fn windowsRetrySleep(ms: u32) (Io.Cancelable || Io.UnexpectedError)!void {17259 fn windowsRetrySleep(ms: u32) (Io.Cancelable || Io.UnexpectedError)!void {
17219 const now_timestamp = nowWindows(.awake) catch unreachable; // '.awake' is supported on Windows17260 const now_timestamp = nowWindows(.awake); // '.awake' is supported on Windows
17220 const deadline = now_timestamp.addDuration(.fromMilliseconds(ms));17261 const deadline = now_timestamp.addDuration(.fromMilliseconds(ms));
17221 parking_sleep.sleep(.{ .raw = deadline, .clock = .awake }) catch |err| switch (err) {17262 try parking_sleep.sleep(.{ .raw = deadline, .clock = .awake });
17222 error.UnsupportedClock => unreachable,
17223 else => |e| return e,
17224 };
17225 }17263 }
17226};17264};
1722717265
...@@ -17234,7 +17272,7 @@ fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{T...@@ -17234,7 +17272,7 @@ fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{T
17234 .windows => {17272 .windows => {
17235 var timeout_buf: windows.LARGE_INTEGER = undefined;17273 var timeout_buf: windows.LARGE_INTEGER = undefined;
17236 const raw_timeout: ?*windows.LARGE_INTEGER = if (opt_deadline) |deadline| timeout: {17274 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);
17238 const nanoseconds = now_timestamp.durationTo(deadline.raw).nanoseconds;17276 const nanoseconds = now_timestamp.durationTo(deadline.raw).nanoseconds;
17239 timeout_buf = @intCast(@divTrunc(-nanoseconds, 100));17277 timeout_buf = @intCast(@divTrunc(-nanoseconds, 100));
17240 break :timeout &timeout_buf;17278 break :timeout &timeout_buf;
...@@ -17284,17 +17322,17 @@ fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{T...@@ -17284,17 +17322,17 @@ fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{T
17284 }17322 }
17285}17323}
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 {
17288 // ntdll only supports two combinations:17326 // ntdll only supports two combinations:
17289 // * real-time (`.real`) sleeps with absolute deadlines17327 // * real-time (`.real`) sleeps with absolute deadlines
17290 // * monotonic (`.awake`/`.boot`) sleeps with relative durations17328 // * monotonic (`.awake`/`.boot`) sleeps with relative durations
17291 switch (deadline.clock) {17329 switch (deadline.clock) {
17292 .cpu_process, .cpu_thread => unreachable, // cannot sleep for CPU time17330 .cpu_process, .cpu_thread => return 0,
17293 .real => {17331 .real => {
17294 return @intCast(@max(@divTrunc(deadline.raw.nanoseconds, 100), 0));17332 return @intCast(@max(@divTrunc(deadline.raw.nanoseconds, 100), 0));
17295 },17333 },
17296 .awake, .boot => {17334 .awake, .boot => {
17297 const duration = try deadline.durationFromNow(ioBasic(t));17335 const duration = deadline.durationFromNow(ioBasic(t));
17298 return @intCast(@min(@divTrunc(-duration.raw.nanoseconds, 100), -1));17336 return @intCast(@min(@divTrunc(-duration.raw.nanoseconds, 100), -1));
17299 },17337 },
17300 }17338 }
lib/std/Io/net.zig+1-1
...@@ -1137,7 +1137,7 @@ pub const Socket = struct {...@@ -1137,7 +1137,7 @@ pub const Socket = struct {
1137 const maybe_err, const count = io.vtable.netReceive(io.userdata, s.handle, (&message)[0..1], buffer, .{}, .none);1137 const maybe_err, const count = io.vtable.netReceive(io.userdata, s.handle, (&message)[0..1], buffer, .{}, .none);
1138 if (maybe_err) |err| switch (err) {1138 if (maybe_err) |err| switch (err) {
1139 // No timeout is passed to `netReceieve`, so it must not return timeout related errors.1139 // No timeout is passed to `netReceieve`, so it must not return timeout related errors.
1140 error.Timeout, error.UnsupportedClock => unreachable,1140 error.Timeout => unreachable,
1141 else => |e| return e,1141 else => |e| return e,
1142 };1142 };
1143 assert(1 == count);1143 assert(1 == count);
lib/std/Io/net/HostName.zig+1-1
...@@ -145,7 +145,7 @@ pub const LookupError = error{...@@ -145,7 +145,7 @@ pub const LookupError = error{
145 NoAddressReturned,145 NoAddressReturned,
146 /// Failed to open or read "/etc/hosts" or "/etc/resolv.conf".146 /// Failed to open or read "/etc/hosts" or "/etc/resolv.conf".
147 DetectingNetworkConfigurationFailed,147 DetectingNetworkConfigurationFailed,
148} || Io.Clock.Error || IpAddress.BindError || Io.Cancelable;148} || IpAddress.BindError || Io.Cancelable;
149149
150pub const LookupResult = union(enum) {150pub const LookupResult = union(enum) {
151 address: IpAddress,151 address: IpAddress,
lib/std/Io/test.zig-6
...@@ -216,14 +216,12 @@ test "Group.cancel" {...@@ -216,14 +216,12 @@ test "Group.cancel" {
216 defer result.* = 1;216 defer result.* = 1;
217 io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {217 io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {
218 error.Canceled => |e| return e,218 error.Canceled => |e| return e,
219 else => {},
220 };219 };
221 }220 }
222221
223 fn sleepRecancel(io: Io, result: *usize) void {222 fn sleepRecancel(io: Io, result: *usize) void {
224 io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {223 io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {
225 error.Canceled => io.recancel(),224 error.Canceled => io.recancel(),
226 else => {},
227 };225 };
228 result.* = 1;226 result.* = 1;
229 }227 }
...@@ -523,8 +521,6 @@ test "cancel sleep" {...@@ -523,8 +521,6 @@ test "cancel sleep" {
523 fn blockUntilCanceled(io: Io) void {521 fn blockUntilCanceled(io: Io) void {
524 while (true) io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {522 while (true) io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {
525 error.Canceled => return,523 error.Canceled => return,
526 error.UnsupportedClock => @panic("unsupported clock"),
527 error.Unexpected => @panic("unexpected"),
528 };524 };
529 }525 }
530 };526 };
...@@ -552,8 +548,6 @@ test "tasks spawned in group after Group.cancel are canceled" {...@@ -552,8 +548,6 @@ test "tasks spawned in group after Group.cancel are canceled" {
552 fn blockUntilCanceled(io: Io) Io.Cancelable!void {548 fn blockUntilCanceled(io: Io) Io.Cancelable!void {
553 while (true) io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {549 while (true) io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) {
554 error.Canceled => |e| return e,550 error.Canceled => |e| return e,
555 error.UnsupportedClock => @panic("unsupported clock"),
556 error.Unexpected => @panic("unexpected"),
557 };551 };
558 }552 }
559 };553 };
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...@@ -212,7 +212,7 @@ pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp, i
212 }212 }
213}213}
214214
215pub const AddCertsFromFilePathError = Io.File.OpenError || AddCertsFromFileError || Io.Clock.Error;215pub const AddCertsFromFilePathError = Io.File.OpenError || AddCertsFromFileError;
216216
217pub fn addCertsFromFilePathAbsolute(217pub fn addCertsFromFilePathAbsolute(
218 cb: *Bundle,218 cb: *Bundle,
...@@ -338,7 +338,7 @@ test "scan for OS-provided certificates" {...@@ -338,7 +338,7 @@ test "scan for OS-provided certificates" {
338 var bundle: Bundle = .{};338 var bundle: Bundle = .{};
339 defer bundle.deinit(gpa);339 defer bundle.deinit(gpa);
340340
341 const now = try Io.Clock.real.now(io);341 const now = Io.Clock.real.now(io);
342342
343 try bundle.rescan(gpa, io, now);343 try bundle.rescan(gpa, io, now);
344}344}
lib/std/http/Client.zig+1-1
...@@ -1700,7 +1700,7 @@ pub fn request(...@@ -1700,7 +1700,7 @@ pub fn request(
1700 defer client.ca_bundle_mutex.unlock(io);1700 defer client.ca_bundle_mutex.unlock(io);
17011701
1702 if (client.now == null) {1702 if (client.now == null) {
1703 const now = try Io.Clock.real.now(io);1703 const now = Io.Clock.real.now(io);
1704 client.now = now;1704 client.now = now;
1705 client.ca_bundle.rescan(client.allocator, io, now) catch1705 client.ca_bundle.rescan(client.allocator, io, now) catch
1706 return error.CertificateBundleLoadFailure;1706 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 {...@@ -1914,21 +1914,21 @@ fn init_vdso_clock_gettime(clk: clockid_t, ts: *timespec) callconv(.c) usize {
1914 @atomicStore(?VdsoClockGettime, &vdso_clock_gettime, ptr, .monotonic);1914 @atomicStore(?VdsoClockGettime, &vdso_clock_gettime, ptr, .monotonic);
1915 // Call into the VDSO if available1915 // Call into the VDSO if available
1916 if (ptr) |f| return f(clk, ts);1916 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)));
1918}1918}
19191919
1920pub fn clock_getres(clk_id: i32, tp: *timespec) usize {1920pub fn clock_getres(clk_id: clockid_t, tp: *timespec) usize {
1921 return syscall2(1921 return syscall2(
1922 if (@hasField(SYS, "clock_getres") and native_arch != .hexagon) .clock_getres else .clock_getres_time64,1922 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)),
1924 @intFromPtr(tp),1924 @intFromPtr(tp),
1925 );1925 );
1926}1926}
19271927
1928pub fn clock_settime(clk_id: i32, tp: *const timespec) usize {1928pub fn clock_settime(clk_id: clockid_t, tp: *const timespec) usize {
1929 return syscall2(1929 return syscall2(
1930 if (@hasField(SYS, "clock_settime") and native_arch != .hexagon) .clock_settime else .clock_settime64,1930 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)),
1932 @intFromPtr(tp),1932 @intFromPtr(tp),
1933 );1933 );
1934}1934}
lib/std/os/linux/IoUring/test.zig+2-2
...@@ -620,12 +620,12 @@ test "timeout (after a relative time)" {...@@ -620,12 +620,12 @@ test "timeout (after a relative time)" {
620 const margin = 5;620 const margin = 5;
621 const ts: linux.kernel_timespec = .{ .sec = 0, .nsec = ms * 1000000 };621 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);
624 const sqe = try ring.timeout(0x55555555, &ts, 0, 0);624 const sqe = try ring.timeout(0x55555555, &ts, 0, 0);
625 try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe.opcode);625 try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe.opcode);
626 try testing.expectEqual(@as(u32, 1), try ring.submit());626 try testing.expectEqual(@as(u32, 1), try ring.submit());
627 const cqe = try ring.copy_cqe();627 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
630 try testing.expectEqual(linux.io_uring_cqe{630 try testing.expectEqual(linux.io_uring_cqe{
631 .user_data = 0x55555555,631 .user_data = 0x55555555,
lib/std/posix.zig-52
...@@ -864,58 +864,6 @@ pub fn dl_iterate_phdr(...@@ -864,58 +864,6 @@ pub fn dl_iterate_phdr(
864 }864 }
865}865}
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
919pub const SchedGetAffinityError = error{PermissionDenied} || UnexpectedError;867pub const SchedGetAffinityError = error{PermissionDenied} || UnexpectedError;
920868
921pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t {869pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t {
lib/std/time.zig-182
...@@ -1,11 +1,3 @@...@@ -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
9pub const epoch = @import("time/epoch.zig");1pub const epoch = @import("time/epoch.zig");
102
11// Divisions of a nanosecond.3// Divisions of a nanosecond.
...@@ -38,180 +30,6 @@ pub const s_per_hour = s_per_min * 60;...@@ -38,180 +30,6 @@ pub const s_per_hour = s_per_min * 60;
38pub const s_per_day = s_per_hour * 24;30pub const s_per_day = s_per_hour * 24;
39pub const s_per_week = s_per_day * 7;31pub 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
215test {33test {
216 _ = epoch;34 _ = epoch;
217}35}
src/Compilation.zig+15-20
...@@ -331,48 +331,42 @@ const QueuedJobs = struct {...@@ -331,48 +331,42 @@ const QueuedJobs = struct {
331pub const Timer = union(enum) {331pub const Timer = union(enum) {
332 unused,332 unused,
333 active: struct {333 active: struct {
334 start: std.time.Instant,334 start: Io.Timestamp,
335 saved_ns: u64,335 saved_ns: u64,
336 },336 },
337 paused: u64,337 paused: u64,
338 stopped,338 stopped,
339339
340 pub fn pause(t: *Timer) void {340 pub fn pause(t: *Timer, io: Io) void {
341 switch (t.*) {341 switch (t.*) {
342 .unused => return,342 .unused => return,
343 .active => |a| {343 .active => |a| {
344 const current = std.time.Instant.now() catch unreachable;344 const current: Io.Timestamp = .now(io, .awake);
345 const new_ns = switch (current.order(a.start)) {345 const new_ns: u64 = @intCast(current.nanoseconds -| a.start.nanoseconds);
346 .lt, .eq => 0,
347 .gt => current.since(a.start),
348 };
349 t.* = .{ .paused = a.saved_ns + new_ns };346 t.* = .{ .paused = a.saved_ns + new_ns };
350 },347 },
351 .paused => unreachable,348 .paused => unreachable,
352 .stopped => unreachable,349 .stopped => unreachable,
353 }350 }
354 }351 }
355 pub fn @"resume"(t: *Timer) void {352 pub fn @"resume"(t: *Timer, io: Io) void {
356 switch (t.*) {353 switch (t.*) {
357 .unused => return,354 .unused => return,
358 .active => unreachable,355 .active => unreachable,
359 .paused => |saved_ns| t.* = .{ .active = .{356 .paused => |saved_ns| t.* = .{ .active = .{
360 .start = std.time.Instant.now() catch unreachable,357 .start = .now(io, .awake),
361 .saved_ns = saved_ns,358 .saved_ns = saved_ns,
362 } },359 } },
363 .stopped => unreachable,360 .stopped => unreachable,
364 }361 }
365 }362 }
366 pub fn finish(t: *Timer) ?u64 {363 pub fn finish(t: *Timer, io: Io) ?u64 {
367 defer t.* = .stopped;364 defer t.* = .stopped;
368 switch (t.*) {365 switch (t.*) {
369 .unused => return null,366 .unused => return null,
370 .active => |a| {367 .active => |a| {
371 const current = std.time.Instant.now() catch unreachable;368 const current: Io.Timestamp = .now(io, .awake);
372 const new_ns = switch (current.order(a.start)) {369 const new_ns: u64 = @intCast(current.nanoseconds -| a.start.nanoseconds);
373 .lt, .eq => 0,
374 .gt => current.since(a.start),
375 };
376 return a.saved_ns + new_ns;370 return a.saved_ns + new_ns;
377 },371 },
378 .paused => |ns| return ns,372 .paused => |ns| return ns,
...@@ -387,7 +381,8 @@ pub const Timer = union(enum) {...@@ -387,7 +381,8 @@ pub const Timer = union(enum) {
387/// is set.381/// is set.
388pub fn startTimer(comp: *Compilation) Timer {382pub fn startTimer(comp: *Compilation) Timer {
389 if (comp.time_report == null) return .unused;383 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);
391 return .{ .active = .{386 return .{ .active = .{
392 .start = now,387 .start = now,
393 .saved_ns = 0,388 .saved_ns = 0,
...@@ -3408,7 +3403,7 @@ fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id) (Io.Cancel...@@ -3408,7 +3403,7 @@ fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id) (Io.Cancel
3408 defer sub_prog_node.end();3403 defer sub_prog_node.end();
34093404
3410 var timer = comp.startTimer();3405 var timer = comp.startTimer();
3411 defer if (timer.finish()) |ns| {3406 defer if (timer.finish(io)) |ns| {
3412 comp.mutex.lockUncancelable(io);3407 comp.mutex.lockUncancelable(io);
3413 defer comp.mutex.unlock(io);3408 defer comp.mutex.unlock(io);
3414 comp.time_report.?.stats.real_ns_llvm_emit = ns;3409 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...@@ -3453,7 +3448,7 @@ fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id) (Io.Cancel
3453 }3448 }
3454 if (comp.bin_file) |lf| {3449 if (comp.bin_file) |lf| {
3455 var timer = comp.startTimer();3450 var timer = comp.startTimer();
3456 defer if (timer.finish()) |ns| {3451 defer if (timer.finish(io)) |ns| {
3457 comp.mutex.lockUncancelable(io);3452 comp.mutex.lockUncancelable(io);
3458 defer comp.mutex.unlock(io);3453 defer comp.mutex.unlock(io);
3459 comp.time_report.?.stats.real_ns_link_flush = ns;3454 comp.time_report.?.stats.real_ns_link_flush = ns;
...@@ -4686,7 +4681,7 @@ fn performAllTheWork(...@@ -4686,7 +4681,7 @@ fn performAllTheWork(
4686 var decl_work_timer: ?Timer = null;4681 var decl_work_timer: ?Timer = null;
4687 defer commit_timer: {4682 defer commit_timer: {
4688 const t = &(decl_work_timer orelse break :commit_timer);4683 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;
4690 comp.mutex.lockUncancelable(io);4685 comp.mutex.lockUncancelable(io);
4691 defer comp.mutex.unlock(io);4686 defer comp.mutex.unlock(io);
4692 comp.time_report.?.stats.real_ns_decls = ns;4687 comp.time_report.?.stats.real_ns_decls = ns;
...@@ -4719,7 +4714,7 @@ fn performAllTheWork(...@@ -4719,7 +4714,7 @@ fn performAllTheWork(
4719 defer zir_prog_node.end();4714 defer zir_prog_node.end();
47204715
4721 var timer = comp.startTimer();4716 var timer = comp.startTimer();
4722 defer if (timer.finish()) |ns| {4717 defer if (timer.finish(io)) |ns| {
4723 comp.mutex.lockUncancelable(io);4718 comp.mutex.lockUncancelable(io);
4724 defer comp.mutex.unlock(io);4719 defer comp.mutex.unlock(io);
4725 comp.time_report.?.stats.real_ns_files = ns;4720 comp.time_report.?.stats.real_ns_files = ns;
src/Zcu.zig+6-4
...@@ -4754,6 +4754,7 @@ const TrackedUnitSema = struct {...@@ -4754,6 +4754,7 @@ const TrackedUnitSema = struct {
4754 analysis_timer_decl: ?InternPool.TrackedInst.Index,4754 analysis_timer_decl: ?InternPool.TrackedInst.Index,
4755 pub fn end(tus: TrackedUnitSema, zcu: *Zcu) void {4755 pub fn end(tus: TrackedUnitSema, zcu: *Zcu) void {
4756 const comp = zcu.comp;4756 const comp = zcu.comp;
4757 const io = comp.io;
4757 if (tus.old_name) |old_name| {4758 if (tus.old_name) |old_name| {
4758 zcu.sema_prog_node.completeOne(); // we're just renaming, but it's effectively completion4759 zcu.sema_prog_node.completeOne(); // we're just renaming, but it's effectively completion
4759 zcu.cur_sema_prog_node.setName(&old_name);4760 zcu.cur_sema_prog_node.setName(&old_name);
...@@ -4762,9 +4763,8 @@ const TrackedUnitSema = struct {...@@ -4762,9 +4763,8 @@ const TrackedUnitSema = struct {
4762 zcu.cur_sema_prog_node = .none;4763 zcu.cur_sema_prog_node = .none;
4763 }4764 }
4764 report_time: {4765 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;
4766 const zir_decl = tus.analysis_timer_decl orelse break :report_time;4767 const zir_decl = tus.analysis_timer_decl orelse break :report_time;
4767 const io = comp.io;
4768 comp.mutex.lockUncancelable(io);4768 comp.mutex.lockUncancelable(io);
4769 defer comp.mutex.unlock(io);4769 defer comp.mutex.unlock(io);
4770 comp.time_report.?.stats.cpu_ns_sema += sema_ns;4770 comp.time_report.?.stats.cpu_ns_sema += sema_ns;
...@@ -4779,11 +4779,13 @@ const TrackedUnitSema = struct {...@@ -4779,11 +4779,13 @@ const TrackedUnitSema = struct {
4779 gop.value_ptr.count += 1;4779 gop.value_ptr.count += 1;
4780 }4780 }
4781 zcu.cur_analysis_timer = tus.old_analysis_timer;4781 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);
4783 }4783 }
4784};4784};
4785pub fn trackUnitSema(zcu: *Zcu, name: []const u8, zir_inst: ?InternPool.TrackedInst.Index) TrackedUnitSema {4785pub 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);
4787 const old_analysis_timer = zcu.cur_analysis_timer;4789 const old_analysis_timer = zcu.cur_analysis_timer;
4788 zcu.cur_analysis_timer = zcu.comp.startTimer();4790 zcu.cur_analysis_timer = zcu.comp.startTimer();
4789 const old_name: ?[std.Progress.Node.max_name_len]u8 = old_name: {4791 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(...@@ -263,7 +263,7 @@ pub fn updateFile(
263 var timer = comp.startTimer();263 var timer = comp.startTimer();
264 // Any potential AST errors are converted to ZIR errors when we run AstGen/ZonGen.264 // Any potential AST errors are converted to ZIR errors when we run AstGen/ZonGen.
265 file.tree = try Ast.parse(gpa, source, file.getMode());265 file.tree = try Ast.parse(gpa, source, file.getMode());
266 if (timer.finish()) |ns_parse| {266 if (timer.finish(io)) |ns_parse| {
267 comp.mutex.lockUncancelable(io);267 comp.mutex.lockUncancelable(io);
268 defer comp.mutex.unlock(io);268 defer comp.mutex.unlock(io);
269 comp.time_report.?.stats.cpu_ns_parse += ns_parse;269 comp.time_report.?.stats.cpu_ns_parse += ns_parse;
...@@ -295,7 +295,7 @@ pub fn updateFile(...@@ -295,7 +295,7 @@ pub fn updateFile(
295 else => |e| return e,295 else => |e| return e,
296 };296 };
297297
298 if (timer.finish()) |ns_astgen| {298 if (timer.finish(io)) |ns_astgen| {
299 comp.mutex.lockUncancelable(io);299 comp.mutex.lockUncancelable(io);
300 defer comp.mutex.unlock(io);300 defer comp.mutex.unlock(io);
301 comp.time_report.?.stats.cpu_ns_astgen += ns_astgen;301 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...@@ -4485,7 +4485,7 @@ pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) Ru
44854485
4486 const codegen_result = runCodegenInner(pt, func_index, air);4486 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: {
4489 const ip = &zcu.intern_pool;4489 const ip = &zcu.intern_pool;
4490 const nav = ip.indexToKey(func_index).func.owner_nav;4490 const nav = ip.indexToKey(func_index).func.owner_nav;
4491 const zir_decl = ip.getNav(nav).srcInst(ip);4491 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 {...@@ -1388,7 +1388,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void {
1388 };1388 };
13891389
1390 var timer = comp.startTimer();1390 var timer = comp.startTimer();
1391 defer if (timer.finish()) |ns| {1391 defer if (timer.finish(io)) |ns| {
1392 comp.mutex.lockUncancelable(io);1392 comp.mutex.lockUncancelable(io);
1393 defer comp.mutex.unlock(io);1393 defer comp.mutex.unlock(io);
1394 comp.time_report.?.stats.cpu_ns_link += ns;1394 comp.time_report.?.stats.cpu_ns_link += ns;
...@@ -1535,12 +1535,12 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void {...@@ -1535,12 +1535,12 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void {
1535 break :nav nav_index;1535 break :nav nav_index;
1536 },1536 },
1537 .link_func => |codegen_task| nav: {1537 .link_func => |codegen_task| nav: {
1538 timer.pause();1538 timer.pause(io);
1539 const func, var mir = codegen_task.wait(&zcu.codegen_task_pool, io) catch |err| switch (err) {1539 const func, var mir = codegen_task.wait(&zcu.codegen_task_pool, io) catch |err| switch (err) {
1540 error.Canceled, error.AlreadyReported => return,1540 error.Canceled, error.AlreadyReported => return,
1541 };1541 };
1542 defer mir.deinit(zcu);1542 defer mir.deinit(zcu);
1543 timer.@"resume"();1543 timer.@"resume"(io);
15441544
1545 const nav = zcu.funcInfo(func).owner_nav;1545 const nav = zcu.funcInfo(func).owner_nav;
1546 const fqn_slice = ip.getNav(nav).fqn.toSlice(ip);1546 const fqn_slice = ip.getNav(nav).fqn.toSlice(ip);
...@@ -1592,7 +1592,7 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void {...@@ -1592,7 +1592,7 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void {
1592 },1592 },
1593 };1593 };
15941594
1595 if (timer.finish()) |ns_link| report_time: {1595 if (timer.finish(io)) |ns_link| report_time: {
1596 comp.mutex.lockUncancelable(io);1596 comp.mutex.lockUncancelable(io);
1597 defer comp.mutex.unlock(io);1597 defer comp.mutex.unlock(io);
1598 const tr = &zcu.comp.time_report.?;1598 const tr = &zcu.comp.time_report.?;