lib/time+test: add types, ops, now
Replaces the lib/time placeholder (a monotonic(*timespec) shim that
predated lib/os's syscall surface). Ships Hare's time module first
cut per ref/hare/time/{duration,instant,arithm,+linux/functions}.ha:
- duration (i64 ns); nanosecond / microsecond / millisecond / second
constants.
- instant (sec, nsec) — Hare's layout, NOT POSIX's nsec:u32. Matches
Linux struct timespec on 64-bit, so &instant lands directly in
clock_gettime.
- clock enum: realtime + monotonic only. The rest of Hare's set
(process_cpu / thread_cpu / boot / realtime_alarm / boot_alarm / tai)
graduates when a caller actually needs it (rule 9).
- now(c: clock) instant — aborts on EINVAL/EFAULT (mirrors Hare's
abort-on-impossible-errno). Deliberately NOT (instant | oserror) to
sidestep task #9's 1-word-payload tagged-return trap.
- add / diff / compare on instants per ref/hare/time/arithm.ha
verbatim.
Deferred to follow-up commits when a caller surfaces: sleep, format /
strftime, time.chrono / time.date calendar, timezone, time.error
sum-return shape, time.unix helpers, time.mult, conversion helpers.
Tests at lib/time/timetest.ww (15 rows, table-pattern, semantic per
Class B doctrine). Driver test/wcc/977_time_run.c slotted between
976_stat_run and 978_intdiv_signed in the 9xx _run band.
examples/cmatrix migrates to the new API in the same commit (sole
pre-existing caller — bisect-clean per rule 11).
Class B bug #16 (sign-extend before IDIVQ) was surfaced by the
negative-duration rows during this work; landed 63332fe..4fa4bcf
before this commit. Rows 8-9 (addneg_noborrow / addneg_borrow) are
the load-bearing Class B exercisers; would have stayed silently wrong
pre-#16.
lib/time's own .s output has a cstage/wwstage label-counter skew in
add (cstage emits add_ct_7/_ce_8/_end_6 vs wwstage _6/_7/_5). Filed
as task #15; non-bootstrap-blocking since lib/time is outside the
selfhost toolchain transitive chain.
This commit is contained in:
@@ -1,15 +1,95 @@
|
||||
// time — clocks. We expose monotonic-ns via a syscall trampoline.
|
||||
// Linux clock_gettime is syscall 228; clock id 1 is CLOCK_MONOTONIC.
|
||||
// Until we can pass struct-by-value the user supplies a buffer.
|
||||
// time — clocks, instants, durations. Mirrors Hare's lib/time
|
||||
// (ref/hare/time/duration.ha, instant.ha, arithm.ha,
|
||||
// +linux/functions.ha). Calendar / date / strftime / timezone /
|
||||
// sleep live in separate Hare modules and graduate when callers /
|
||||
// supporting stdlib arrive.
|
||||
//
|
||||
// `duration` is a NAMED alias of i64 (lib/math/random precedent
|
||||
// at lib/math/random/random.ww:8); ww treats NAMED as a newtype,
|
||||
// so cross-i64 arithmetic inside this module needs explicit casts.
|
||||
// Hare's structural alias semantics let those casts vanish, but
|
||||
// our type checker is strict.
|
||||
|
||||
@symbol("rt_syscall") fn syscall(num: i64, a: i64, b: i64, c: i64) i64;
|
||||
@symbol("rt_syscall") fn syscall2(num: i64, a: i64, b: i64) i64;
|
||||
@symbol("rt_abort") fn abort(msg: str) void;
|
||||
|
||||
def CLOCK_MONOTONIC: i64 = 1;
|
||||
def SYS_CLOCK_GETTIME: i64 = 228;
|
||||
|
||||
// timespec is {sec, nsec} — 16 bytes. Caller passes a pointer.
|
||||
type timespec = struct { sec: i64, nsec: i64 };
|
||||
// ref/hare/time/duration.ha:6. 290y representable range.
|
||||
export type duration = i64;
|
||||
|
||||
export fn monotonic(ts: *timespec) i32 = {
|
||||
return syscall(SYS_CLOCK_GETTIME, CLOCK_MONOTONIC, ts: i64, 0): i32;
|
||||
// ref/hare/time/duration.ha:9-18. Plan-9 naming (lowercase)
|
||||
// diverges from Hare's uppercase per project rule 4.
|
||||
export def nanosecond: duration = 1i64;
|
||||
export def microsecond: duration = 1000i64;
|
||||
export def millisecond: duration = 1000000i64;
|
||||
export def second: duration = 1000000000i64;
|
||||
|
||||
// ref/hare/time/instant.ha:9. (sec, nsec) pair — NOT POSIX struct
|
||||
// timespec (which uses u32 nsec). Layout matches Linux's struct
|
||||
// timespec on 64-bit (i64+i64) so we can pass &instant directly
|
||||
// to clock_gettime.
|
||||
export type instant = struct {
|
||||
sec: i64,
|
||||
nsec: i64,
|
||||
};
|
||||
|
||||
// ref/hare/time/+linux/functions.ha:84. First cut exposes only
|
||||
// realtime and monotonic; Hare's process_cpu / thread_cpu / boot /
|
||||
// realtime_alarm / boot_alarm / tai graduate when a caller needs
|
||||
// them (CLAUDE.md rule 9 — Hare-fidelity, no premature surface).
|
||||
export type clock = enum i32 {
|
||||
realtime = 0,
|
||||
monotonic = 1,
|
||||
};
|
||||
|
||||
// ref/hare/time/+linux/functions.ha:138. Hare's now() also aborts
|
||||
// on impossible errnos. (instant | oserror) is deliberately not
|
||||
// the return shape — EINVAL / EFAULT are programmer errors (bad
|
||||
// clock id, bad ptr), and a 1-word-payload sum return walks into
|
||||
// task #9's cgen-divergence trap.
|
||||
export fn now(c: clock) instant = {
|
||||
let i: instant;
|
||||
let rc = syscall2(SYS_CLOCK_GETTIME, (c as i32): i64, (&i): i64);
|
||||
if (rc != 0i64) { abort("time.now: clock_gettime failed"); };
|
||||
return i;
|
||||
};
|
||||
|
||||
// ref/hare/time/arithm.ha:9. Adds duration to instant. The
|
||||
// negative-duration branch normalises nsec into [0, second).
|
||||
export fn add(i: instant, x: duration) instant = {
|
||||
let r: instant;
|
||||
let xi: i64 = x: i64;
|
||||
let sec: i64 = second: i64;
|
||||
let nsec: i64 = nanosecond: i64;
|
||||
if (xi == 0i64) {
|
||||
r.sec = i.sec;
|
||||
r.nsec = i.nsec;
|
||||
return r;
|
||||
};
|
||||
if (xi > 0i64) {
|
||||
r.sec = i.sec + (i.nsec + xi) / sec;
|
||||
r.nsec = (i.nsec + xi) % sec;
|
||||
return r;
|
||||
};
|
||||
r.sec = i.sec + (i.nsec + xi - sec + nsec) / sec;
|
||||
r.nsec = (i.nsec + (xi % sec) + sec) % sec;
|
||||
return r;
|
||||
};
|
||||
|
||||
// ref/hare/time/arithm.ha:26. Returns duration from a to b.
|
||||
// Sign convention: b - a.
|
||||
export fn diff(a: instant, b: instant) duration = {
|
||||
let sec: i64 = second: i64;
|
||||
let v: i64 = ((b.sec - a.sec) * sec) + (b.nsec - a.nsec);
|
||||
return v: duration;
|
||||
};
|
||||
|
||||
// ref/hare/time/arithm.ha:32. -1 if a < b, 0 if equal, +1 if a > b.
|
||||
export fn compare(a: instant, b: instant) i8 = {
|
||||
if (a.sec < b.sec) { return -1i8; };
|
||||
if (a.sec > b.sec) { return 1i8; };
|
||||
if (a.nsec < b.nsec) { return -1i8; };
|
||||
if (a.nsec > b.nsec) { return 1i8; };
|
||||
return 0i8;
|
||||
};
|
||||
|
||||
180
lib/time/timetest.ww
Normal file
180
lib/time/timetest.ww
Normal file
@@ -0,0 +1,180 @@
|
||||
// timetest — exercises lib/time. Run with `out/bin/ww run
|
||||
// lib/time/timetest.ww`. Same `signalled`-then-fail()-with-+10
|
||||
// pattern as fmttest / logtest / stattest so a non-zero exit
|
||||
// pinpoints the offending scenario.
|
||||
|
||||
use os;
|
||||
use time;
|
||||
|
||||
let signalled: i32 = 0;
|
||||
|
||||
fn fail() void = { os.exit(signalled + 10); };
|
||||
|
||||
// ---- constants: load-bearing names + values ----------------------------
|
||||
//
|
||||
// Without these multipliers every caller would re-derive 1e9 in
|
||||
// place. The values themselves are nailed down by Hare's
|
||||
// ref/hare/time/duration.ha:9-18.
|
||||
|
||||
@test fn constants() void = {
|
||||
if ((time.nanosecond: i64) != 1i64) { fail(); };
|
||||
if ((time.microsecond: i64) != 1000i64) { fail(); };
|
||||
if ((time.millisecond: i64) != 1000000i64) { fail(); };
|
||||
if ((time.second: i64) != 1000000000i64) { fail(); };
|
||||
|
||||
// "Roundtrip" check: derived multiples land on the canonical
|
||||
// nanosecond count. Catches a future re-derivation drift.
|
||||
if (5i64 * (time.second: i64) != 5000000000i64) { fail(); };
|
||||
if (3i64 * (time.millisecond: i64) != 3000000i64) { fail(); };
|
||||
if (7i64 * (time.microsecond: i64) != 7000i64) { fail(); };
|
||||
};
|
||||
|
||||
// ---- now(monotonic) is monotonic across two calls ----------------------
|
||||
|
||||
@test fn nowmonotonic() void = {
|
||||
let a = time.now(time.clock.monotonic);
|
||||
let b = time.now(time.clock.monotonic);
|
||||
// Two back-to-back calls — b can equal a (same ns tick) but
|
||||
// must never precede it.
|
||||
if (time.compare(a, b) > 0i8) { fail(); };
|
||||
};
|
||||
|
||||
// ---- now(realtime) returns a post-2020 epoch ---------------------------
|
||||
//
|
||||
// Sanity check that the syscall plumbing actually lands real bytes
|
||||
// in the instant. 2020-01-01 UTC = 1577836800 unix seconds; any
|
||||
// past-2020 wall clock satisfies this. (CI / test machines running
|
||||
// pre-2020 would fail here; acceptable trade for catching a
|
||||
// zero-init / wrong-slot regression.)
|
||||
|
||||
@test fn nowrealtime() void = {
|
||||
let t = time.now(time.clock.realtime);
|
||||
if (t.sec < 1577836800i64) { fail(); };
|
||||
if (t.nsec < 0i64) { fail(); };
|
||||
if (t.nsec >= 1000000000i64) { fail(); };
|
||||
};
|
||||
|
||||
// ---- add: zero duration is identity ------------------------------------
|
||||
|
||||
@test fn addzero() void = {
|
||||
let a: time.instant;
|
||||
a.sec = 100i64; a.nsec = 500i64;
|
||||
let r = time.add(a, 0i64);
|
||||
if (r.sec != 100i64) { fail(); };
|
||||
if (r.nsec != 500i64) { fail(); };
|
||||
};
|
||||
|
||||
// ---- add: positive duration, no carry ----------------------------------
|
||||
|
||||
@test fn addpos_nocarry() void = {
|
||||
let a: time.instant;
|
||||
a.sec = 10i64; a.nsec = 100i64;
|
||||
let r = time.add(a, 200i64);
|
||||
if (r.sec != 10i64) { fail(); };
|
||||
if (r.nsec != 300i64) { fail(); };
|
||||
};
|
||||
|
||||
// ---- add: positive duration, carries into next second ------------------
|
||||
|
||||
@test fn addpos_carry() void = {
|
||||
let a: time.instant;
|
||||
a.sec = 10i64; a.nsec = 999999900i64;
|
||||
let r = time.add(a, 200i64);
|
||||
if (r.sec != 11i64) { fail(); };
|
||||
if (r.nsec != 100i64) { fail(); };
|
||||
};
|
||||
|
||||
// ---- add: full-second duration -----------------------------------------
|
||||
|
||||
@test fn addpos_fullsecond() void = {
|
||||
let a: time.instant;
|
||||
a.sec = 5i64; a.nsec = 0i64;
|
||||
let r = time.add(a, (time.second: i64) * 3i64);
|
||||
if (r.sec != 8i64) { fail(); };
|
||||
if (r.nsec != 0i64) { fail(); };
|
||||
};
|
||||
|
||||
// ---- add: negative duration, no borrow ---------------------------------
|
||||
|
||||
@test fn addneg_noborrow() void = {
|
||||
let a: time.instant;
|
||||
a.sec = 10i64; a.nsec = 500i64;
|
||||
let r = time.add(a, -100i64);
|
||||
if (r.sec != 10i64) { fail(); };
|
||||
if (r.nsec != 400i64) { fail(); };
|
||||
};
|
||||
|
||||
// ---- add: negative duration, borrows from previous second --------------
|
||||
|
||||
@test fn addneg_borrow() void = {
|
||||
let a: time.instant;
|
||||
a.sec = 10i64; a.nsec = 100i64;
|
||||
let r = time.add(a, -200i64);
|
||||
if (r.sec != 9i64) { fail(); };
|
||||
if (r.nsec != 999999900i64) { fail(); };
|
||||
};
|
||||
|
||||
// ---- diff: simple subtraction ------------------------------------------
|
||||
|
||||
@test fn diffsimple() void = {
|
||||
let a: time.instant; a.sec = 10i64; a.nsec = 100i64;
|
||||
let b: time.instant; b.sec = 12i64; b.nsec = 300i64;
|
||||
let d: i64 = time.diff(a, b): i64;
|
||||
// b - a = 2.0000002 sec = 2_000_000_200 ns
|
||||
if (d != 2000000200i64) { fail(); };
|
||||
};
|
||||
|
||||
// ---- diff: reversed sign -----------------------------------------------
|
||||
|
||||
@test fn diffneg() void = {
|
||||
let a: time.instant; a.sec = 12i64; a.nsec = 300i64;
|
||||
let b: time.instant; b.sec = 10i64; b.nsec = 100i64;
|
||||
let d: i64 = time.diff(a, b): i64;
|
||||
if (d != -2000000200i64) { fail(); };
|
||||
};
|
||||
|
||||
// ---- compare: -1 / 0 / +1 ladder ---------------------------------------
|
||||
|
||||
@test fn comparelt() void = {
|
||||
let a: time.instant; a.sec = 1i64; a.nsec = 0i64;
|
||||
let b: time.instant; b.sec = 2i64; b.nsec = 0i64;
|
||||
if (time.compare(a, b) != -1i8) { fail(); };
|
||||
};
|
||||
|
||||
@test fn comparegt() void = {
|
||||
let a: time.instant; a.sec = 2i64; a.nsec = 0i64;
|
||||
let b: time.instant; b.sec = 1i64; b.nsec = 0i64;
|
||||
if (time.compare(a, b) != 1i8) { fail(); };
|
||||
};
|
||||
|
||||
@test fn compareeq() void = {
|
||||
let a: time.instant; a.sec = 5i64; a.nsec = 42i64;
|
||||
let b: time.instant; b.sec = 5i64; b.nsec = 42i64;
|
||||
if (time.compare(a, b) != 0i8) { fail(); };
|
||||
};
|
||||
|
||||
@test fn comparensec_only() void = {
|
||||
let a: time.instant; a.sec = 5i64; a.nsec = 100i64;
|
||||
let b: time.instant; b.sec = 5i64; b.nsec = 200i64;
|
||||
if (time.compare(a, b) != -1i8) { fail(); };
|
||||
if (time.compare(b, a) != 1i8) { fail(); };
|
||||
};
|
||||
|
||||
export fn main() i32 = {
|
||||
signalled = 1; constants();
|
||||
signalled = 2; nowmonotonic();
|
||||
signalled = 3; nowrealtime();
|
||||
signalled = 4; addzero();
|
||||
signalled = 5; addpos_nocarry();
|
||||
signalled = 6; addpos_carry();
|
||||
signalled = 7; addpos_fullsecond();
|
||||
signalled = 8; addneg_noborrow();
|
||||
signalled = 9; addneg_borrow();
|
||||
signalled = 10; diffsimple();
|
||||
signalled = 11; diffneg();
|
||||
signalled = 12; comparelt();
|
||||
signalled = 13; comparegt();
|
||||
signalled = 14; compareeq();
|
||||
signalled = 15; comparensec_only();
|
||||
return 0;
|
||||
};
|
||||
Reference in New Issue
Block a user