diff --git a/Makefile b/Makefile index fe29973e..da6f6a9d 100644 --- a/Makefile +++ b/Makefile @@ -113,7 +113,7 @@ $(BIN)/wwdump_ww: selfhost/cmd/wwdump/main.ww \ selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \ selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \ selfhost/cmd/wcc/cgendecl.ww \ - lib/os/os.ww lib/strconv/strconv.ww lib/strings/strings.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \ + lib/os/os.ww lib/time/time.ww lib/strconv/strconv.ww lib/strings/strings.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) cd $(BIN) && ./ww build \ @@ -134,7 +134,7 @@ $(BIN)/w6c_ww: selfhost/cmd/w6c/main.ww \ selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \ selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \ selfhost/cmd/wcc/cgendecl.ww \ - lib/os/os.ww lib/strconv/strconv.ww lib/strings/strings.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \ + lib/os/os.ww lib/time/time.ww lib/strconv/strconv.ww lib/strings/strings.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) cd $(BIN) && ./ww build \ @@ -152,7 +152,7 @@ $(BIN)/w6a_ww: selfhost/cmd/w6a/main.ww selfhost/cmd/w6a/types.ww \ selfhost/cmd/w6a/lex.ww selfhost/cmd/w6a/parse.ww \ selfhost/cmd/w6a/asm.ww selfhost/cmd/w6a/obj.ww \ selfhost/cmd/wcc/mem.ww \ - lib/os/os.ww \ + lib/os/os.ww lib/time/time.ww \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) cd $(BIN) && ./ww build \ @@ -168,7 +168,7 @@ $(BIN)/w6l_ww: selfhost/cmd/w6l/main.ww selfhost/cmd/w6l/sym.ww \ selfhost/cmd/w6l/obj.ww selfhost/cmd/w6l/dyn.ww \ selfhost/cmd/w6l/pass.ww selfhost/cmd/w6l/dynout.ww \ selfhost/cmd/w6l/out.ww selfhost/cmd/wcc/mem.ww \ - lib/os/os.ww \ + lib/os/os.ww lib/time/time.ww \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) cd $(BIN) && ./ww build \ @@ -181,7 +181,7 @@ $(BIN)/w6l_ww: selfhost/cmd/w6l/main.ww selfhost/cmd/w6l/sym.ww \ # The driver pulls in lib/os (default search path) and selfhost/cmd/wcc # (for the bump arena). It then orchestrates w6c/w6a/w6l like the C driver. $(BIN)/ww_ww: selfhost/cmd/ww/main.ww selfhost/cmd/wcc/mem.ww lib/os/os.ww \ - lib/strconv/strconv.ww \ + lib/time/time.ww lib/strconv/strconv.ww \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) cd $(BIN) && ./ww build \ diff --git a/lib/os/os.ww b/lib/os/os.ww index 747ec8bd..c81d1e48 100644 --- a/lib/os/os.ww +++ b/lib/os/os.ww @@ -2,6 +2,8 @@ // either in libwwrt.a (rt_syscall trampoline) or libc bindings, // depending on how the program was linked. +use time; + @symbol("rt_syscall") fn syscall0(num: nr) i64; @symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64; @symbol("rt_syscall") fn syscall2(num: nr, a: i64, b: i64) i64; @@ -504,17 +506,10 @@ export type stat_mask = enum u32 { CTIME = 64u32, }; -// timespec — {sec, nsec} pair, matching Hare's time::instant -// (ref/hare/time/types.ha). Local to lib/os; graduates to -// lib/time.instant when lib/time and lib/fs ship. Same byte layout -// (i64+i64 = 16B) so a future migration is field-rename only. -export type timespec = struct { - sec: i64, - nsec: i64, -}; - // filestat — Hare's fs::filestat (ref/hare/fs/types.ha:141). 80 -// bytes. See module-header note re: graduation to lib/fs. +// bytes. Times are time.instant (ref/hare/time/instant.ha:9) — the +// canonical Hare shape. See module-header note re: graduation to +// lib/fs. export type filestat = struct { mask: stat_mask, // 0 (4) mode: mode, // 4 (4) @@ -522,9 +517,9 @@ export type filestat = struct { gid: u32, // 12 (4) sz: u64, // 16 (8) inode: u64, // 24 (8) - atime: timespec, // 32 (16) - mtime: timespec, // 48 (16) - ctime: timespec, // 64 (16) — ends at 80 + atime: time.instant, // 32 (16) + mtime: time.instant, // 48 (16) + ctime: time.instant, // 64 (16) — ends at 80 }; // kstat — x86_64 kernel `struct stat` layout. Mirrors diff --git a/lib/os/stattest.ww b/lib/os/stattest.ww index 086811b7..7fbea3e2 100644 --- a/lib/os/stattest.ww +++ b/lib/os/stattest.ww @@ -74,7 +74,7 @@ fn istype(m: os.mode, t: os.mode) bool = { // atime/mtime/ctime — kernel-set at create time, all // post-epoch (>0). Three distinct kstat offsets (72/88/104) // so a fillfilestat field-copy miscompile or a filestat - // timespec-offset bug surfaces here, not silently. + // time.instant offset bug surfaces here, not silently. if (fi.atime.sec <= 0i64) { fail(); }; if (fi.mtime.sec <= 0i64) { fail(); }; if (fi.ctime.sec <= 0i64) { fail(); }; diff --git a/selfhost/cmd/w6a/main.combined.ww b/selfhost/cmd/w6a/main.combined.ww index 168edeb0..92301c6d 100644 --- a/selfhost/cmd/w6a/main.combined.ww +++ b/selfhost/cmd/w6a/main.combined.ww @@ -1,8 +1,107 @@ +// MODULE: time +// 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 syscall2(num: i64, a: i64, b: i64) i64; +@symbol("rt_abort") fn abort(msg: str) void; + +def SYS_CLOCK_GETTIME: i64 = 228; + +// ref/hare/time/duration.ha:6. 290y representable range. +export type duration = i64; + +// 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; +}; + // MODULE: os // os — process and filesystem facade. The body of each call lands // either in libwwrt.a (rt_syscall trampoline) or libc bindings, // depending on how the program was linked. +use time; + @symbol("rt_syscall") fn syscall0(num: nr) i64; @symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64; @symbol("rt_syscall") fn syscall2(num: nr, a: i64, b: i64) i64; @@ -505,17 +604,10 @@ export type stat_mask = enum u32 { CTIME = 64u32, }; -// timespec — {sec, nsec} pair, matching Hare's time::instant -// (ref/hare/time/types.ha). Local to lib/os; graduates to -// lib/time.instant when lib/time and lib/fs ship. Same byte layout -// (i64+i64 = 16B) so a future migration is field-rename only. -export type timespec = struct { - sec: i64, - nsec: i64, -}; - // filestat — Hare's fs::filestat (ref/hare/fs/types.ha:141). 80 -// bytes. See module-header note re: graduation to lib/fs. +// bytes. Times are time.instant (ref/hare/time/instant.ha:9) — the +// canonical Hare shape. See module-header note re: graduation to +// lib/fs. export type filestat = struct { mask: stat_mask, // 0 (4) mode: mode, // 4 (4) @@ -523,9 +615,9 @@ export type filestat = struct { gid: u32, // 12 (4) sz: u64, // 16 (8) inode: u64, // 24 (8) - atime: timespec, // 32 (16) - mtime: timespec, // 48 (16) - ctime: timespec, // 64 (16) — ends at 80 + atime: time.instant, // 32 (16) + mtime: time.instant, // 48 (16) + ctime: time.instant, // 64 (16) — ends at 80 }; // kstat — x86_64 kernel `struct stat` layout. Mirrors diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index daafa212..44cb5346 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -1,8 +1,107 @@ +// MODULE: time +// 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 syscall2(num: i64, a: i64, b: i64) i64; +@symbol("rt_abort") fn abort(msg: str) void; + +def SYS_CLOCK_GETTIME: i64 = 228; + +// ref/hare/time/duration.ha:6. 290y representable range. +export type duration = i64; + +// 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; +}; + // MODULE: os // os — process and filesystem facade. The body of each call lands // either in libwwrt.a (rt_syscall trampoline) or libc bindings, // depending on how the program was linked. +use time; + @symbol("rt_syscall") fn syscall0(num: nr) i64; @symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64; @symbol("rt_syscall") fn syscall2(num: nr, a: i64, b: i64) i64; @@ -505,17 +604,10 @@ export type stat_mask = enum u32 { CTIME = 64u32, }; -// timespec — {sec, nsec} pair, matching Hare's time::instant -// (ref/hare/time/types.ha). Local to lib/os; graduates to -// lib/time.instant when lib/time and lib/fs ship. Same byte layout -// (i64+i64 = 16B) so a future migration is field-rename only. -export type timespec = struct { - sec: i64, - nsec: i64, -}; - // filestat — Hare's fs::filestat (ref/hare/fs/types.ha:141). 80 -// bytes. See module-header note re: graduation to lib/fs. +// bytes. Times are time.instant (ref/hare/time/instant.ha:9) — the +// canonical Hare shape. See module-header note re: graduation to +// lib/fs. export type filestat = struct { mask: stat_mask, // 0 (4) mode: mode, // 4 (4) @@ -523,9 +615,9 @@ export type filestat = struct { gid: u32, // 12 (4) sz: u64, // 16 (8) inode: u64, // 24 (8) - atime: timespec, // 32 (16) - mtime: timespec, // 48 (16) - ctime: timespec, // 64 (16) — ends at 80 + atime: time.instant, // 32 (16) + mtime: time.instant, // 48 (16) + ctime: time.instant, // 64 (16) — ends at 80 }; // kstat — x86_64 kernel `struct stat` layout. Mirrors diff --git a/selfhost/cmd/w6l/main.combined.ww b/selfhost/cmd/w6l/main.combined.ww index 40947784..7c74baf4 100644 --- a/selfhost/cmd/w6l/main.combined.ww +++ b/selfhost/cmd/w6l/main.combined.ww @@ -1,8 +1,107 @@ +// MODULE: time +// 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 syscall2(num: i64, a: i64, b: i64) i64; +@symbol("rt_abort") fn abort(msg: str) void; + +def SYS_CLOCK_GETTIME: i64 = 228; + +// ref/hare/time/duration.ha:6. 290y representable range. +export type duration = i64; + +// 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; +}; + // MODULE: os // os — process and filesystem facade. The body of each call lands // either in libwwrt.a (rt_syscall trampoline) or libc bindings, // depending on how the program was linked. +use time; + @symbol("rt_syscall") fn syscall0(num: nr) i64; @symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64; @symbol("rt_syscall") fn syscall2(num: nr, a: i64, b: i64) i64; @@ -505,17 +604,10 @@ export type stat_mask = enum u32 { CTIME = 64u32, }; -// timespec — {sec, nsec} pair, matching Hare's time::instant -// (ref/hare/time/types.ha). Local to lib/os; graduates to -// lib/time.instant when lib/time and lib/fs ship. Same byte layout -// (i64+i64 = 16B) so a future migration is field-rename only. -export type timespec = struct { - sec: i64, - nsec: i64, -}; - // filestat — Hare's fs::filestat (ref/hare/fs/types.ha:141). 80 -// bytes. See module-header note re: graduation to lib/fs. +// bytes. Times are time.instant (ref/hare/time/instant.ha:9) — the +// canonical Hare shape. See module-header note re: graduation to +// lib/fs. export type filestat = struct { mask: stat_mask, // 0 (4) mode: mode, // 4 (4) @@ -523,9 +615,9 @@ export type filestat = struct { gid: u32, // 12 (4) sz: u64, // 16 (8) inode: u64, // 24 (8) - atime: timespec, // 32 (16) - mtime: timespec, // 48 (16) - ctime: timespec, // 64 (16) — ends at 80 + atime: time.instant, // 32 (16) + mtime: time.instant, // 48 (16) + ctime: time.instant, // 64 (16) — ends at 80 }; // kstat — x86_64 kernel `struct stat` layout. Mirrors diff --git a/selfhost/cmd/ww/main.combined.ww b/selfhost/cmd/ww/main.combined.ww index ad02468e..ea9bb197 100644 --- a/selfhost/cmd/ww/main.combined.ww +++ b/selfhost/cmd/ww/main.combined.ww @@ -1,8 +1,107 @@ +// MODULE: time +// 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 syscall2(num: i64, a: i64, b: i64) i64; +@symbol("rt_abort") fn abort(msg: str) void; + +def SYS_CLOCK_GETTIME: i64 = 228; + +// ref/hare/time/duration.ha:6. 290y representable range. +export type duration = i64; + +// 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; +}; + // MODULE: os // os — process and filesystem facade. The body of each call lands // either in libwwrt.a (rt_syscall trampoline) or libc bindings, // depending on how the program was linked. +use time; + @symbol("rt_syscall") fn syscall0(num: nr) i64; @symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64; @symbol("rt_syscall") fn syscall2(num: nr, a: i64, b: i64) i64; @@ -505,17 +604,10 @@ export type stat_mask = enum u32 { CTIME = 64u32, }; -// timespec — {sec, nsec} pair, matching Hare's time::instant -// (ref/hare/time/types.ha). Local to lib/os; graduates to -// lib/time.instant when lib/time and lib/fs ship. Same byte layout -// (i64+i64 = 16B) so a future migration is field-rename only. -export type timespec = struct { - sec: i64, - nsec: i64, -}; - // filestat — Hare's fs::filestat (ref/hare/fs/types.ha:141). 80 -// bytes. See module-header note re: graduation to lib/fs. +// bytes. Times are time.instant (ref/hare/time/instant.ha:9) — the +// canonical Hare shape. See module-header note re: graduation to +// lib/fs. export type filestat = struct { mask: stat_mask, // 0 (4) mode: mode, // 4 (4) @@ -523,9 +615,9 @@ export type filestat = struct { gid: u32, // 12 (4) sz: u64, // 16 (8) inode: u64, // 24 (8) - atime: timespec, // 32 (16) - mtime: timespec, // 48 (16) - ctime: timespec, // 64 (16) — ends at 80 + atime: time.instant, // 32 (16) + mtime: time.instant, // 48 (16) + ctime: time.instant, // 64 (16) — ends at 80 }; // kstat — x86_64 kernel `struct stat` layout. Mirrors diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 24ee16e0..be567dee 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -1,8 +1,107 @@ +// MODULE: time +// 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 syscall2(num: i64, a: i64, b: i64) i64; +@symbol("rt_abort") fn abort(msg: str) void; + +def SYS_CLOCK_GETTIME: i64 = 228; + +// ref/hare/time/duration.ha:6. 290y representable range. +export type duration = i64; + +// 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; +}; + // MODULE: os // os — process and filesystem facade. The body of each call lands // either in libwwrt.a (rt_syscall trampoline) or libc bindings, // depending on how the program was linked. +use time; + @symbol("rt_syscall") fn syscall0(num: nr) i64; @symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64; @symbol("rt_syscall") fn syscall2(num: nr, a: i64, b: i64) i64; @@ -505,17 +604,10 @@ export type stat_mask = enum u32 { CTIME = 64u32, }; -// timespec — {sec, nsec} pair, matching Hare's time::instant -// (ref/hare/time/types.ha). Local to lib/os; graduates to -// lib/time.instant when lib/time and lib/fs ship. Same byte layout -// (i64+i64 = 16B) so a future migration is field-rename only. -export type timespec = struct { - sec: i64, - nsec: i64, -}; - // filestat — Hare's fs::filestat (ref/hare/fs/types.ha:141). 80 -// bytes. See module-header note re: graduation to lib/fs. +// bytes. Times are time.instant (ref/hare/time/instant.ha:9) — the +// canonical Hare shape. See module-header note re: graduation to +// lib/fs. export type filestat = struct { mask: stat_mask, // 0 (4) mode: mode, // 4 (4) @@ -523,9 +615,9 @@ export type filestat = struct { gid: u32, // 12 (4) sz: u64, // 16 (8) inode: u64, // 24 (8) - atime: timespec, // 32 (16) - mtime: timespec, // 48 (16) - ctime: timespec, // 64 (16) — ends at 80 + atime: time.instant, // 32 (16) + mtime: time.instant, // 48 (16) + ctime: time.instant, // 64 (16) — ends at 80 }; // kstat — x86_64 kernel `struct stat` layout. Mirrors diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww index 424a51b0..b2042555 100644 --- a/selfhost/test/smoke.combined.ww +++ b/selfhost/test/smoke.combined.ww @@ -1,8 +1,107 @@ +// MODULE: time +// 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 syscall2(num: i64, a: i64, b: i64) i64; +@symbol("rt_abort") fn abort(msg: str) void; + +def SYS_CLOCK_GETTIME: i64 = 228; + +// ref/hare/time/duration.ha:6. 290y representable range. +export type duration = i64; + +// 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; +}; + // MODULE: os // os — process and filesystem facade. The body of each call lands // either in libwwrt.a (rt_syscall trampoline) or libc bindings, // depending on how the program was linked. +use time; + @symbol("rt_syscall") fn syscall0(num: nr) i64; @symbol("rt_syscall") fn syscall1(num: nr, a: i64) i64; @symbol("rt_syscall") fn syscall2(num: nr, a: i64, b: i64) i64; @@ -505,17 +604,10 @@ export type stat_mask = enum u32 { CTIME = 64u32, }; -// timespec — {sec, nsec} pair, matching Hare's time::instant -// (ref/hare/time/types.ha). Local to lib/os; graduates to -// lib/time.instant when lib/time and lib/fs ship. Same byte layout -// (i64+i64 = 16B) so a future migration is field-rename only. -export type timespec = struct { - sec: i64, - nsec: i64, -}; - // filestat — Hare's fs::filestat (ref/hare/fs/types.ha:141). 80 -// bytes. See module-header note re: graduation to lib/fs. +// bytes. Times are time.instant (ref/hare/time/instant.ha:9) — the +// canonical Hare shape. See module-header note re: graduation to +// lib/fs. export type filestat = struct { mask: stat_mask, // 0 (4) mode: mode, // 4 (4) @@ -523,9 +615,9 @@ export type filestat = struct { gid: u32, // 12 (4) sz: u64, // 16 (8) inode: u64, // 24 (8) - atime: timespec, // 32 (16) - mtime: timespec, // 48 (16) - ctime: timespec, // 64 (16) — ends at 80 + atime: time.instant, // 32 (16) + mtime: time.instant, // 48 (16) + ctime: time.instant, // 64 (16) — ends at 80 }; // kstat — x86_64 kernel `struct stat` layout. Mirrors diff --git a/test/wcc/900_stdlib.c b/test/wcc/900_stdlib.c index 0fbfd6b4..f58479ec 100644 --- a/test/wcc/900_stdlib.c +++ b/test/wcc/900_stdlib.c @@ -16,7 +16,6 @@ static const char *modules[] = { "lib/strings/strings.ww", "lib/io/io.ww", "lib/errors/errors.ww", - "lib/os/os.ww", "lib/strconv/strconv.ww", "lib/sort/sort.ww", "lib/path/path.ww", @@ -33,13 +32,15 @@ static const char *modules[] = { "lib/math/random/random.ww", "lib/time/time.ww", "lib/c/libc/libc.ww", - /* lib/bufio/bufio.ww and lib/fmt/fmt.ww moved off this list: - * both graduated to io.stream-based sinks, which carry cross- - * module type refs (*io.stream, io.closed, io.eof) that only - * resolve once the driver concatenates `use`d modules. Coverage - * lives at lib/bufio/bufiotest.ww + lib/fmt/fmttest.ww (wired - * at 998_bufio_run.c and 970_fmt_run.c), plus the bufio.scanline - * and fmt.println e2e rows in test/wcc/700_e2e.c. */ + /* lib/bufio/bufio.ww, lib/fmt/fmt.ww, and lib/os/os.ww moved + * off this list: each has cross-module type refs that only + * resolve once the driver concatenates `use`d modules. bufio / + * fmt graduated to io.stream-based sinks (*io.stream, io.closed, + * io.eof); lib/os carries time.instant in filestat post-Commit B. + * Coverage lives at lib/bufio/bufiotest.ww + lib/fmt/fmttest.ww + + * lib/os/stattest.ww (wired at 998_bufio_run.c, 970_fmt_run.c, + * 976_stat_run.c), plus the bufio.scanline / fmt.println e2e rows + * in test/wcc/700_e2e.c. */ "lib/net/net.ww", NULL };