mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
Apple's own headers and tbd files prefer to think of Mac Catalyst as a distinct OS target. Earlier, when DriverKit support was added to LLVM, it was represented a distinct OS. So why Apple decided to only represent Mac Catalyst as an ABI in the target triple is beyond me. But this isn't the first time they've ignored established target triple norms (see: armv7k and aarch64_32) and it probably won't be the last. While doing this, I also audited all Darwin OS prongs throughout the codebase and made sure they cover all the tags.
218 lines
8.6 KiB
Zig
218 lines
8.6 KiB
Zig
const std = @import("std.zig");
|
|
const builtin = @import("builtin");
|
|
const assert = std.debug.assert;
|
|
const testing = std.testing;
|
|
const math = std.math;
|
|
const windows = std.os.windows;
|
|
const posix = std.posix;
|
|
|
|
pub const epoch = @import("time/epoch.zig");
|
|
|
|
// Divisions of a nanosecond.
|
|
pub const ns_per_us = 1000;
|
|
pub const ns_per_ms = 1000 * ns_per_us;
|
|
pub const ns_per_s = 1000 * ns_per_ms;
|
|
pub const ns_per_min = 60 * ns_per_s;
|
|
pub const ns_per_hour = 60 * ns_per_min;
|
|
pub const ns_per_day = 24 * ns_per_hour;
|
|
pub const ns_per_week = 7 * ns_per_day;
|
|
|
|
// Divisions of a microsecond.
|
|
pub const us_per_ms = 1000;
|
|
pub const us_per_s = 1000 * us_per_ms;
|
|
pub const us_per_min = 60 * us_per_s;
|
|
pub const us_per_hour = 60 * us_per_min;
|
|
pub const us_per_day = 24 * us_per_hour;
|
|
pub const us_per_week = 7 * us_per_day;
|
|
|
|
// Divisions of a millisecond.
|
|
pub const ms_per_s = 1000;
|
|
pub const ms_per_min = 60 * ms_per_s;
|
|
pub const ms_per_hour = 60 * ms_per_min;
|
|
pub const ms_per_day = 24 * ms_per_hour;
|
|
pub const ms_per_week = 7 * ms_per_day;
|
|
|
|
// Divisions of a second.
|
|
pub const s_per_min = 60;
|
|
pub const s_per_hour = s_per_min * 60;
|
|
pub const s_per_day = s_per_hour * 24;
|
|
pub const s_per_week = s_per_day * 7;
|
|
|
|
/// An Instant represents a timestamp with respect to the currently
|
|
/// executing program that ticks during suspend and can be used to
|
|
/// record elapsed time unlike `nanoTimestamp`.
|
|
///
|
|
/// It tries to sample the system's fastest and most precise timer available.
|
|
/// It also tries to be monotonic, but this is not a guarantee due to OS/hardware bugs.
|
|
/// If you need monotonic readings for elapsed time, consider `Timer` instead.
|
|
pub const Instant = struct {
|
|
timestamp: if (is_posix) posix.timespec else u64,
|
|
|
|
// true if we should use clock_gettime()
|
|
const is_posix = switch (builtin.os.tag) {
|
|
.windows, .uefi, .wasi => false,
|
|
else => true,
|
|
};
|
|
|
|
/// Queries the system for the current moment of time as an Instant.
|
|
/// This is not guaranteed to be monotonic or steadily increasing, but for
|
|
/// most implementations it is.
|
|
/// Returns `error.Unsupported` when a suitable clock is not detected.
|
|
pub fn now() error{Unsupported}!Instant {
|
|
const clock_id = switch (builtin.os.tag) {
|
|
.windows => {
|
|
// QPC on windows doesn't fail on >= XP/2000 and includes time suspended.
|
|
return .{ .timestamp = windows.QueryPerformanceCounter() };
|
|
},
|
|
.wasi => {
|
|
var ns: std.os.wasi.timestamp_t = undefined;
|
|
const rc = std.os.wasi.clock_time_get(.MONOTONIC, 1, &ns);
|
|
if (rc != .SUCCESS) return error.Unsupported;
|
|
return .{ .timestamp = ns };
|
|
},
|
|
.uefi => {
|
|
const value, _ = std.os.uefi.system_table.runtime_services.getTime() catch return error.Unsupported;
|
|
return .{ .timestamp = value.toEpoch() };
|
|
},
|
|
// On darwin, use UPTIME_RAW instead of MONOTONIC as it ticks while
|
|
// suspended.
|
|
.driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => posix.CLOCK.UPTIME_RAW,
|
|
// On freebsd derivatives, use MONOTONIC_FAST as currently there's
|
|
// no precision tradeoff.
|
|
.freebsd, .dragonfly => posix.CLOCK.MONOTONIC_FAST,
|
|
// On linux, use BOOTTIME instead of MONOTONIC as it ticks while
|
|
// suspended.
|
|
.linux => posix.CLOCK.BOOTTIME,
|
|
// On other posix systems, MONOTONIC is generally the fastest and
|
|
// ticks while suspended.
|
|
else => posix.CLOCK.MONOTONIC,
|
|
};
|
|
|
|
const ts = posix.clock_gettime(clock_id) catch return error.Unsupported;
|
|
return .{ .timestamp = ts };
|
|
}
|
|
|
|
/// Quickly compares two instances between each other.
|
|
pub fn order(self: Instant, other: Instant) std.math.Order {
|
|
// windows and wasi timestamps are in u64 which is easily comparible
|
|
if (!is_posix) {
|
|
return std.math.order(self.timestamp, other.timestamp);
|
|
}
|
|
|
|
var ord = std.math.order(self.timestamp.sec, other.timestamp.sec);
|
|
if (ord == .eq) {
|
|
ord = std.math.order(self.timestamp.nsec, other.timestamp.nsec);
|
|
}
|
|
return ord;
|
|
}
|
|
|
|
/// Returns elapsed time in nanoseconds since the `earlier` Instant.
|
|
/// This assumes that the `earlier` Instant represents a moment in time before or equal to `self`.
|
|
/// This also assumes that the time that has passed between both Instants fits inside a u64 (~585 yrs).
|
|
pub fn since(self: Instant, earlier: Instant) u64 {
|
|
switch (builtin.os.tag) {
|
|
.windows => {
|
|
// We don't need to cache QPF as it's internally just a memory read to KUSER_SHARED_DATA
|
|
// (a read-only page of info updated and mapped by the kernel to all processes):
|
|
// https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data
|
|
// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm
|
|
const qpc = self.timestamp - earlier.timestamp;
|
|
const qpf = windows.QueryPerformanceFrequency();
|
|
|
|
// 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it.
|
|
// https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701
|
|
const common_qpf = 10_000_000;
|
|
if (qpf == common_qpf) {
|
|
return qpc * (ns_per_s / common_qpf);
|
|
}
|
|
|
|
// Convert to ns using fixed point.
|
|
const scale = @as(u64, std.time.ns_per_s << 32) / @as(u32, @intCast(qpf));
|
|
const result = (@as(u96, qpc) * scale) >> 32;
|
|
return @as(u64, @truncate(result));
|
|
},
|
|
.uefi, .wasi => {
|
|
// UEFI and WASI timestamps are directly in nanoseconds
|
|
return self.timestamp - earlier.timestamp;
|
|
},
|
|
else => {
|
|
// Convert timespec diff to ns
|
|
const seconds = @as(u64, @intCast(self.timestamp.sec - earlier.timestamp.sec));
|
|
const elapsed = (seconds * ns_per_s) + @as(u32, @intCast(self.timestamp.nsec));
|
|
return elapsed - @as(u32, @intCast(earlier.timestamp.nsec));
|
|
},
|
|
}
|
|
}
|
|
};
|
|
|
|
/// A monotonic, high performance timer.
|
|
///
|
|
/// Timer.start() is used to initialize the timer
|
|
/// and gives the caller an opportunity to check for the existence of a supported clock.
|
|
/// Once a supported clock is discovered,
|
|
/// it is assumed that it will be available for the duration of the Timer's use.
|
|
///
|
|
/// Monotonicity is ensured by saturating on the most previous sample.
|
|
/// This means that while timings reported are monotonic,
|
|
/// they're not guaranteed to tick at a steady rate as this is up to the underlying system.
|
|
pub const Timer = struct {
|
|
started: Instant,
|
|
previous: Instant,
|
|
|
|
pub const Error = error{TimerUnsupported};
|
|
|
|
/// Initialize the timer by querying for a supported clock.
|
|
/// Returns `error.TimerUnsupported` when such a clock is unavailable.
|
|
/// This should only fail in hostile environments such as linux seccomp misuse.
|
|
pub fn start() Error!Timer {
|
|
const current = Instant.now() catch return error.TimerUnsupported;
|
|
return Timer{ .started = current, .previous = current };
|
|
}
|
|
|
|
/// Reads the timer value since start or the last reset in nanoseconds.
|
|
pub fn read(self: *Timer) u64 {
|
|
const current = self.sample();
|
|
return current.since(self.started);
|
|
}
|
|
|
|
/// Resets the timer value to 0/now.
|
|
pub fn reset(self: *Timer) void {
|
|
const current = self.sample();
|
|
self.started = current;
|
|
}
|
|
|
|
/// Returns the current value of the timer in nanoseconds, then resets it.
|
|
pub fn lap(self: *Timer) u64 {
|
|
const current = self.sample();
|
|
defer self.started = current;
|
|
return current.since(self.started);
|
|
}
|
|
|
|
/// Returns an Instant sampled at the callsite that is
|
|
/// guaranteed to be monotonic with respect to the timer's starting point.
|
|
fn sample(self: *Timer) Instant {
|
|
const current = Instant.now() catch unreachable;
|
|
if (current.order(self.previous) == .gt) {
|
|
self.previous = current;
|
|
}
|
|
return self.previous;
|
|
}
|
|
};
|
|
|
|
test Timer {
|
|
const io = std.testing.io;
|
|
|
|
var timer = try Timer.start();
|
|
|
|
try std.Io.Clock.Duration.sleep(.{ .clock = .awake, .raw = .fromMilliseconds(10) }, io);
|
|
const time_0 = timer.read();
|
|
try testing.expect(time_0 > 0);
|
|
|
|
const time_1 = timer.lap();
|
|
try testing.expect(time_1 >= time_0);
|
|
}
|
|
|
|
test {
|
|
_ = epoch;
|
|
}
|