nx_time.nx source
↩ module page · 98 lines · 3842 B
1// nx_time.nx -- monotonic wall-clock time + duration utilities.
2//
3// nx_clock_now_ns() -- monotonic nanoseconds via the
4// clock_gettime(CLOCK_MONOTONIC) syscall (113
5// on RV64 Linux). This is what nx_trace
6// wires to once spans get real timestamps.
7//
8// Per the decade-horizon doctrine, we do NOT trust gettimeofday()
9// or wall-clock realtime here -- those skew, leap, and re-set, and
10// any logic that mixes them with monotonic produces phantom bugs
11// at boundaries.
12//
13// For LOGICAL ordering use nx_trace's tick counter; for PROFILING
14// use nx_clock_now_ns(). Don't cross-mix in the same measurement.
15//
16// Design references:
17// Linux clock_gettime(2) -- monotonic clock contract
18// Go runtime nanotime() -- platform-portable monotonic clock
19// Java System.nanoTime -- JVM monotonic clock guarantee
20// Rust std::time::Instant -- monotonic-only Duration source
21
22// nx_safety_envelope:
23// intended_use: AUTO_APPLIED -- primitive-specific tuning queued
24// sil_target: SIL1
25// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail]
26// verdict: NOT_YET_EVALUATED
27
28import "syscalls.nx"
29const NX_MAGIC_1000000000: i64 = 1000000000
30const NX_MAGIC_1000000: i64 = 1000000
31const NX_MAGIC_2500: i64 = 2500
32const NX_MAGIC_2500000: i64 = 2500000
33const NX_MAGIC_2500000000: i64 = 2500000000
34
35struct NxTimespec {
36 tv_sec: i64,
37 tv_nsec: i64,
38}
39
40const NX_TIMESPEC_BYTES: i64 = 16
41const NX_CLOCK_MONOTONIC: i64 = 1
42const NX_SYS_CLOCK_GETTIME: i64 = 113
43
44// nx_clock_now_ns -- monotonic wall-clock nanoseconds.
45// Returns 0 on syscall failure (caller should treat 0 as "unknown").
46func nx_clock_now_ns() -> i64 {
47 let buf: *u8 = sys_mmap(NX_TIMESPEC_BYTES)
48 let ts: *NxTimespec = buf as *NxTimespec
49 ts.tv_sec = 0
50 ts.tv_nsec = 0
51 let rc: i64 = __syscall(NX_SYS_CLOCK_GETTIME, NX_CLOCK_MONOTONIC,
52 buf as i64, 0, 0, 0, 0)
53 if rc < 0 { return 0 }
54 return ts.tv_sec * NX_MAGIC_1000000000 + ts.tv_nsec
55}
56
57// --- Duration helpers ----------------------------------------------
58
59// nx_dur_ns_diff -- safe end-start delta in ns. Returns 0 if
60// inputs look invalid (zero or end < start) so callers can't
61// silently consume nonsense durations.
62func nx_dur_ns_diff(start: i64, end: i64) -> i64 {
63 if start == 0 { return 0 }
64 if end == 0 { return 0 }
65 if end < start { return 0 }
66 return end - start
67}
68
69// nx_dur_to_us -- nanoseconds to microseconds, integer floor.
70func nx_dur_to_us(ns: i64) -> i64 { return ns / 1000 }
71
72// nx_dur_to_ms -- nanoseconds to milliseconds, integer floor.
73func nx_dur_to_ms(ns: i64) -> i64 { return ns / NX_MAGIC_1000000 }
74
75// nx_dur_to_s -- nanoseconds to seconds, integer floor.
76func nx_dur_to_s(ns: i64) -> i64 { return ns / NX_MAGIC_1000000000 }
77
78// --- self-test ------------------------------------------------------
79
80func main() -> i64 {
81 // Wall-clock: at least non-negative, monotonic across calls.
82 let w1: i64 = nx_clock_now_ns()
83 let w2: i64 = nx_clock_now_ns()
84 if w1 < 0 { return __syscall(93, 30, 0, 0, 0, 0, 0) }
85 if w2 < w1 { return __syscall(93, 31, 0, 0, 0, 0, 0) }
86
87 // Duration helpers.
88 if nx_dur_ns_diff(0, 100) != 0 { return __syscall(93, 40, 0, 0, 0, 0, 0) }
89 if nx_dur_ns_diff(100, 0) != 0 { return __syscall(93, 41, 0, 0, 0, 0, 0) }
90 if nx_dur_ns_diff(200, 100) != 0 { return __syscall(93, 42, 0, 0, 0, 0, 0) }
91 if nx_dur_ns_diff(100, 250) != 150 { return __syscall(93, 43, 0, 0, 0, 0, 0) }
92
93 if nx_dur_to_us(NX_MAGIC_2500) != 2 { return __syscall(93, 50, 0, 0, 0, 0, 0) }
94 if nx_dur_to_ms(NX_MAGIC_2500000) != 2 { return __syscall(93, 51, 0, 0, 0, 0, 0) }
95 if nx_dur_to_s(NX_MAGIC_2500000000) != 2 { return __syscall(93, 52, 0, 0, 0, 0, 0) }
96
97 return 0
98}