From 56879b830041da02885216b2aa50cee3cc4c7b1b Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:06:38 +0700 Subject: [PATCH 01/10] spec(t27b): watch_build -- when t27b watch rebuilds an object (Refs #10859) Co-Authored-By: Claude Opus 5.5 --- specs/tri/t27b/watch_build.t27 | 96 ++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 specs/tri/t27b/watch_build.t27 diff --git a/specs/tri/t27b/watch_build.t27 b/specs/tri/t27b/watch_build.t27 new file mode 100644 index 0000000000..a094b1d17a --- /dev/null +++ b/specs/tri/t27b/watch_build.t27 @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/tri/t27b/watch_build.t27 -- when `t27b watch` rebuilds a native object. Issue gHashTag/t27#10859, +; epic #10863 (T27F developer tools in t27b and t27c, sped up by t27c seal). +; -- +; Why. A T27 OS window swaps itself when its binary changes (specs/t27os/hot_reload.t27); what was left was +; running `t27b build` by hand after every save. `t27b watch -o ` builds once, then +; rebuilds the object after every settled save to the spec or to any spec in its `use` closure. +; -- +; Nothing is decided twice. The poll interval and the idle look are specs/verified/watch.t27 (a look stats +; the files the last build read and builds nothing when no stat moved, #8821); the debounce is +; specs/t27f/hot.t27 (an editor save is two or three file events: a burst is ready after HOT_DEBOUNCE_MS +; of quiet, or HOT_MAX_WAIT_MS after its first event). This spec adds only what is specific to an object: +; - when a build starts: on the first look, or when a stat moved and the burst is ready; +; - how long the loop sleeps: the poll interval, or what is left of the debounce while a burst is open; +; - the object is written to `` + WATCH_TMP_SUFFIX and renamed over `` only when the build +; succeeded, so a hot-reloading app never maps a half-written object and a type error keeps the old one; +; - `--then` runs exactly once per successful build, never after a failed one, and its exit code never +; stops the watch. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module T27bWatchBuild; + +use verified::watch::WATCH_POLL_MS; +use verified::watch::watch_walk; +use t27f::hot::HOT_DEBOUNCE_MS; +use t27f::hot::HOT_MAX_WAIT_MS; +use t27f::hot::hot_wait_ms; +use t27f::hot::hot_ready; + +/// The build writes here, next to the object, and is renamed over it on success. +pub const WATCH_TMP_SUFFIX : str = ".watch-tmp"; + +/// Build on this look. `pending` is true while a stat moved since the last build; quiet_ms is the time +/// since the last look that saw a stat move, waited_ms the time since the first one did. +pub fn watch_build_now(first_look: bool, pending: bool, quiet_ms: u32, waited_ms: u32) -> bool { + return watch_walk(first_look, pending and hot_ready(quiet_ms, waited_ms)); +} + +/// Milliseconds to sleep before the next look: what is left of the debounce while a burst is open, +/// otherwise the poll interval. +pub fn watch_sleep_ms(pending: bool, quiet_ms: u32, waited_ms: u32) -> u32 { + if (pending) { + const left : u32 = hot_wait_ms(quiet_ms, waited_ms); + if (left > 0) { return left; } + return 1; + } + return WATCH_POLL_MS; +} + +/// Rename the temporary object over the output: only after a successful build. +pub fn watch_publish(build_ok: bool) -> bool { + return build_ok; +} + +/// Run the `--then` command: once per successful build, when one was given. +pub fn watch_run_then(build_ok: bool, has_then: bool) -> bool { + return build_ok and has_then; +} + +test the_first_look_always_builds { + assert watch_build_now(true, false, 0, 0); + assert watch_build_now(true, true, 0, 0); +} + +test an_idle_look_builds_nothing { + assert watch_build_now(false, false, 0, 0) == false; + assert watch_build_now(false, false, 100000, 100000) == false; + assert watch_sleep_ms(false, 0, 0) == WATCH_POLL_MS; +} + +test a_save_builds_after_the_debounce { + // just seen: wait out the quiet window + assert watch_build_now(false, true, 0, 0) == false; + assert watch_sleep_ms(true, 0, 0) == HOT_DEBOUNCE_MS; + // quiet long enough + assert watch_build_now(false, true, HOT_DEBOUNCE_MS, HOT_DEBOUNCE_MS); + // a burst that never goes quiet still builds at the maximum wait + assert watch_build_now(false, true, 0, HOT_MAX_WAIT_MS); +} + +test the_sleep_never_spins_on_zero { + assert watch_sleep_ms(true, HOT_DEBOUNCE_MS, HOT_MAX_WAIT_MS) == 1; +} + +test a_failed_build_keeps_the_object_and_skips_then { + assert watch_publish(false) == false; + assert watch_run_then(false, true) == false; + assert watch_publish(true); + assert watch_run_then(true, true); + assert watch_run_then(true, false) == false; +} + +test a_save_is_rebuilt_within_one_poll_and_the_debounce { + // the 700 ms criterion of #10859 is WATCH_POLL_MS + HOT_MAX_WAIT_MS + the build + assert WATCH_POLL_MS + HOT_MAX_WAIT_MS <= 560; +} From 37ebe9193efcacce4e24b50aba44f3edee169d24 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:08:09 +0700 Subject: [PATCH 02/10] spec(t27b): watch_build -- the 700 ms test runs a call, not a folded constant (Refs #10859) Co-Authored-By: Claude Opus 5.5 --- specs/tri/t27b/watch_build.t27 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/tri/t27b/watch_build.t27 b/specs/tri/t27b/watch_build.t27 index a094b1d17a..5117777e66 100644 --- a/specs/tri/t27b/watch_build.t27 +++ b/specs/tri/t27b/watch_build.t27 @@ -92,5 +92,5 @@ test a_failed_build_keeps_the_object_and_skips_then { test a_save_is_rebuilt_within_one_poll_and_the_debounce { // the 700 ms criterion of #10859 is WATCH_POLL_MS + HOT_MAX_WAIT_MS + the build - assert WATCH_POLL_MS + HOT_MAX_WAIT_MS <= 560; + assert watch_sleep_ms(false, 0, 0) + HOT_MAX_WAIT_MS <= 560; } From db2d87481e9e4d70fabcaa6ae9544a352a1dcbfd Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:10:45 +0700 Subject: [PATCH 03/10] feat(t27b): watch -- rebuild the object when the spec or its use closure is saved (Refs #10859) Co-Authored-By: Claude Opus 5.5 --- cli/t27b/README.md | 11 +++++++++++ cli/t27b/src/main.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/cli/t27b/README.md b/cli/t27b/README.md index 61c998a950..2a2b8d8cfc 100644 --- a/cli/t27b/README.md +++ b/cli/t27b/README.md @@ -309,6 +309,7 @@ Each cell is the outcome of that test (`inc_max`, `shr_big`, `rem_neg`): ``` t27b test [--overflow trap|wrap] [--time] [--quiet] [--check] [--blockers] t27b build -o [--elf | --macho] [--overflow trap|wrap] [--time] +t27b watch -o [--elf | --macho] [--overflow trap|wrap] [--then ""] t27b asm [--overflow trap|wrap] t27b verilog t27b ts @@ -318,6 +319,16 @@ t27b corpus [--timeout-ms N] [--jobs N] [--overflow trap|wrap] [--list] [--reference-timeout-ms N]]] ``` +`t27b watch` builds the object once, then again after every settled save to the spec or to any +spec in its `use` closure (#10859). An idle look only stats the files the last build read; a +save is built after `HOT_DEBOUNCE_MS` of quiet or `HOT_MAX_WAIT_MS` after its first event, so a +save is rebuilt within `WATCH_POLL_MS` + `HOT_MAX_WAIT_MS` + the build. The object is written to +`.watch-tmp` and renamed over `` only when the build succeeded: a type error prints the +error and keeps the old object, and a hot-reloading app (`specs/t27os/hot_reload.t27`) never maps a +half-written one. `--then ""` runs through `sh -c` once per successful build, never after a +failed one; its exit code is printed and never stops the watch. The decisions are +`specs/tri/t27b/watch_build.t27`, reusing `specs/verified/watch.t27` and `specs/t27f/hot.t27`. + `t27b verilog` writes a Verilog module for the spec to stdout, and after it, under `` `ifdef T27B_TESTBENCH ``, a test bench that runs the spec's own tests against that module (`iverilog -DT27B_TESTBENCH`). A construct it does not lower is diff --git a/cli/t27b/src/main.rs b/cli/t27b/src/main.rs index 148213e750..d045dad0a0 100644 --- a/cli/t27b/src/main.rs +++ b/cli/t27b/src/main.rs @@ -3,6 +3,7 @@ //! ```text //! t27b test [--overflow trap|wrap] [--time] [--quiet] [--check] [--blockers] //! t27b build -o [--elf | --macho] [--overflow trap|wrap] [--time] +//! t27b watch -o [--elf | --macho] [--overflow trap|wrap] [--then ""] //! t27b asm [--overflow trap|wrap] //! t27b corpus [--timeout-ms N] [--jobs N] [--overflow trap|wrap] [--list] //! [--json ] [--runner " [args]"] @@ -28,6 +29,7 @@ use t27b::timing::Phases; #[path = "../../../gen/rust/tri/t27b/check_budget.rs"] #[allow(dead_code, unused_parens)] mod check_budget; // --check fuel policy, t27c gen-rust of specs/tri/t27b/check_budget.t27 (#6664) +#[path = "../../../gen/rust/tri/t27b/watch_build.rs"] #[allow(dead_code, unused_parens)] mod watch_build; // `t27b watch`: when to build, the temp path, when --then runs (#10859) use t27b::blockers::{self, Reference, Verdicts}; use t27b::{a64, front, lower, macho}; @@ -66,6 +68,7 @@ macro_rules! errln { const USAGE: &str = "usage: t27b test [--overflow trap|wrap] [--time] [--quiet] [--check] [--blockers] t27b build -o [--elf | --macho] [--overflow trap|wrap] [--time] + t27b watch -o [--elf | --macho] [--overflow trap|wrap] [--then \"\"] t27b asm [--overflow trap|wrap] t27b verilog (the module, and its test bench under `ifdef T27B_TESTBENCH) t27b ts (consts, scalar fns and tests as TypeScript; specs/tri/t27b/ts_emit.t27) @@ -92,7 +95,9 @@ const USAGE: &str = "usage: `mismatch` (= `jit_interp_mismatch`) is t27b's JIT against t27b's own interpreter, not against the reference."; +#[derive(Clone)] struct Opts { + then: Option, cmd: String, input: Option, out: Option, @@ -125,6 +130,7 @@ fn parse_args() -> Result { let cmd = args.next().ok_or("missing command")?; let mut o = Opts { cmd, + then: None, input: None, out: None, mode: OverflowMode::Trap, @@ -152,6 +158,7 @@ fn parse_args() -> Result { _ => return Err("--overflow takes trap or wrap".into()), } } + "--then" => o.then = Some(args.next().ok_or("--then needs a command")?), "--time" => o.time = true, "--quiet" | "-q" => o.quiet = true, "--check" => o.check = true, @@ -226,6 +233,7 @@ fn main() -> ExitCode { Err(code) => ExitCode::from(code), } } + "watch" => cmd_watch(&o), "corpus" => { let Some(dir) = o.input.clone() else { return usage("missing directory"); @@ -501,6 +509,38 @@ fn cmd_build(prog: &Program, ph: &mut Phases, o: &Opts) -> ExitCode { ExitCode::SUCCESS } +/// `t27b watch` (#10859): I/O only; when to build, the temp path and when --then runs are watch_build.t27. +fn cmd_watch(o: &Opts) -> ExitCode { + let (Some(input), Some(out)) = (o.input.clone(), o.out.clone()) else { return usage("watch needs -o ") }; + let (tmp, ms) = (format!("{}{}", out, watch_build::WATCH_TMP_SUFFIX), |t: Instant| t.elapsed().as_millis().min(u32::MAX as u128) as u32); + let stamp = |p: &PathBuf| std::fs::metadata(p).and_then(|m| Ok((m.modified()?, m.len()))).ok(); + let (mut files, mut seen, mut first, mut last, mut opened) = (Vec::::new(), Vec::new(), true, Instant::now(), None::); + loop { + let now: Vec<_> = files.iter().map(stamp).collect(); + if now != seen { (seen, last) = (now, Instant::now()); opened.get_or_insert(last); } + let (quiet, waited) = (ms(last), opened.map_or(0, ms)); + if watch_build::watch_build_now(first, opened.is_some(), quiet, waited) { + (first, opened, files) = (false, None, use_closure(Path::new(&input))); + seen = files.iter().map(stamp).collect(); // stats before the build: a save during it is seen next look + let _ = std::fs::remove_file(&tmp); + let ok = compile_front(&input, o).map(|(prog, mut ph)| cmd_build(&prog, &mut ph, &Opts { out: Some(tmp.clone()), ..o.clone() })).is_ok() && Path::new(&tmp).exists(); + if watch_build::watch_publish(ok) && std::fs::rename(&tmp, &out).is_ok() { errln!("t27b watch: built {} ({} files)", out, files.len()); } + if watch_build::watch_run_then(ok, o.then.is_some()) { errln!("t27b watch: then exited {:?}", Command::new("sh").arg("-c").arg(o.then.as_deref().unwrap_or("")).status().ok().and_then(|s| s.code())); } + } + std::thread::sleep(Duration::from_millis(watch_build::watch_sleep_ms(opened.is_some(), quiet, waited) as u64)); + } +} + +fn use_closure(input: &Path) -> Vec { // the spec and every spec its `use` lines reach (t27c's resolver) + let root = input.ancestors().find(|a| a.ends_with("specs")).map(Path::to_path_buf).unwrap_or_default(); + let (mut files, mut i) = (vec![input.to_path_buf()], 0); + while let Some(f) = files.get(i).cloned() { + for t in t27b::use_resolve::use_edges(&std::fs::read_to_string(f).unwrap_or_default(), &root) { if !files.contains(&t) { files.push(t) } } + i += 1; + } + files +} + fn cmd_asm(prog: &Program) -> ExitCode { let code = match codegen::compile(prog, TrapStyle::Brk, true) { Ok(c) => c, From 339479b3aa6df4bfbcfc89da9d809ee76d056143 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:11:16 +0700 Subject: [PATCH 04/10] gen(t27b): watch_build.rs from t27c gen-rust of specs/tri/t27b/watch_build.t27 (Refs #10859) Co-Authored-By: Claude Opus 5.5 --- gen/rust/tri/t27b/watch_build.rs | 63 ++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 gen/rust/tri/t27b/watch_build.rs diff --git a/gen/rust/tri/t27b/watch_build.rs b/gen/rust/tri/t27b/watch_build.rs new file mode 100644 index 0000000000..704316244b --- /dev/null +++ b/gen/rust/tri/t27b/watch_build.rs @@ -0,0 +1,63 @@ +// Generated from .t27 spec +// DO NOT EDIT — generated by t27c +// NOT LOWERED BY THIS BACKEND: 6 test(s), 0 invariant(s), 0 function(s) with no body. +// This backend emits declarations only. The spec's checks live in +// the Zig and Verilog outputs; do not read this file as verified. + +pub const WATCH_TMP_SUFFIX: &'static str = ".watch-tmp"; + +pub fn watch_build_now(first_look: bool, pending: bool, quiet_ms: u32, waited_ms: u32) -> bool { + return watch_walk(first_look, (pending && hot_ready(quiet_ms, waited_ms))); +} + +pub fn watch_sleep_ms(pending: bool, quiet_ms: u32, waited_ms: u32) -> u32 { + if pending { + let left: u32 = hot_wait_ms(quiet_ms, waited_ms); + if (left > 0) { + return left; + } + return 1; + } + return WATCH_POLL_MS; +} + +pub fn watch_publish(build_ok: bool) -> bool { + return build_ok; +} + +pub fn watch_run_then(build_ok: bool, has_then: bool) -> bool { + return (build_ok && has_then); +} + +pub const HOT_DEBOUNCE_MS: u32 = 20; + +pub const HOT_MAX_WAIT_MS: u32 = 60; + +pub fn hot_ready(quiet_ms: u32, waited_ms: u32) -> bool { + return (hot_wait_ms(quiet_ms, waited_ms) == 0); +} + +pub fn hot_wait_ms(quiet_ms: u32, waited_ms: u32) -> u32 { + let mut quiet_left: u32 = 0; + if (quiet_ms < HOT_DEBOUNCE_MS) { + quiet_left = (HOT_DEBOUNCE_MS - quiet_ms); + } + let mut wait_left: u32 = 0; + if (waited_ms < HOT_MAX_WAIT_MS) { + wait_left = (HOT_MAX_WAIT_MS - waited_ms); + } + if (quiet_left < wait_left) { + return quiet_left; + } + return wait_left; +} + +pub const WATCH_POLL_MS: u32 = 500; + +pub fn watch_walk(first_look: bool, a_stat_moved: bool) -> bool { + if first_look { + return true; + } + return a_stat_moved; +} + From 6954032f21127aa55488f44443ee438bd730ec20 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:13:10 +0700 Subject: [PATCH 05/10] fix(t27b): corpus worker Opts takes the shared fields from a clone (Refs #10859) Co-Authored-By: Claude Opus 5.5 --- cli/t27b/src/main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/cli/t27b/src/main.rs b/cli/t27b/src/main.rs index d045dad0a0..f8645f18f6 100644 --- a/cli/t27b/src/main.rs +++ b/cli/t27b/src/main.rs @@ -784,20 +784,17 @@ fn cmd_corpus(dir: &Path, o: &Opts) -> ExitCode { cmd: String::new(), input: None, out: None, - mode: o.mode, time: false, quiet: true, check: true, list: false, - timeout_ms: o.timeout_ms, jobs: 1, json: None, - runner: o.runner.clone(), - blockers: o.blockers, elf: false, reference: None, reference_cache: None, reference_timeout_ms: 0, + ..o.clone() // mode, timeout_ms, runner, blockers }; handles.push(std::thread::spawn(move || loop { let i = { From 3d0f8403750b469006f6e56c64357d1264bf016d Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:16:53 +0700 Subject: [PATCH 06/10] chore(t27b): keep watch glue within the 40-line cap (Refs #10859) Co-Authored-By: Claude Opus 5.5 --- cli/t27b/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/cli/t27b/src/main.rs b/cli/t27b/src/main.rs index f8645f18f6..78427686e9 100644 --- a/cli/t27b/src/main.rs +++ b/cli/t27b/src/main.rs @@ -530,7 +530,6 @@ fn cmd_watch(o: &Opts) -> ExitCode { std::thread::sleep(Duration::from_millis(watch_build::watch_sleep_ms(opened.is_some(), quiet, waited) as u64)); } } - fn use_closure(input: &Path) -> Vec { // the spec and every spec its `use` lines reach (t27c's resolver) let root = input.ancestors().find(|a| a.ends_with("specs")).map(Path::to_path_buf).unwrap_or_default(); let (mut files, mut i) = (vec![input.to_path_buf()], 0); From e849072495ab6de8b811310d5eb01f11bbe72b9c Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:32:59 +0700 Subject: [PATCH 07/10] refactor(t27b): usage text moves to specs/tri/t27b/usage.t27 so watch shrinks hand Rust (Refs #10859) Co-Authored-By: Claude Opus 5.5 --- cli/t27b/src/main.rs | 46 ++-------------------------------------- specs/tri/t27b/usage.t27 | 25 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 44 deletions(-) create mode 100644 specs/tri/t27b/usage.t27 diff --git a/cli/t27b/src/main.rs b/cli/t27b/src/main.rs index 78427686e9..1682a738ee 100644 --- a/cli/t27b/src/main.rs +++ b/cli/t27b/src/main.rs @@ -1,19 +1,5 @@ //! `t27b`: t27's own native backend (MVP), command line. -//! -//! ```text -//! t27b test [--overflow trap|wrap] [--time] [--quiet] [--check] [--blockers] -//! t27b build -o [--elf | --macho] [--overflow trap|wrap] [--time] -//! t27b watch -o [--elf | --macho] [--overflow trap|wrap] [--then ""] -//! t27b asm [--overflow trap|wrap] -//! t27b corpus [--timeout-ms N] [--jobs N] [--overflow trap|wrap] [--list] -//! [--json ] [--runner " [args]"] -//! [--blockers [--reference [--reference-cache ] -//! [--reference-timeout-ms N]]] -//! ``` -//! -//! Exit codes: 0 success; 1 a test failed; 2 unsupported construct; -//! 3 front-end (parse or typecheck) error; 4 JIT and interpreter disagree -//! (`--check`); 5 code generation limit; 64 usage; 74 I/O error. +//! Usage and exit codes: specs/tri/t27b/usage.t27 (`t27b help`). use std::collections::{BTreeSet, HashMap}; use std::path::{Path, PathBuf}; @@ -65,35 +51,7 @@ macro_rules! errln { }}; } -const USAGE: &str = "usage: - t27b test [--overflow trap|wrap] [--time] [--quiet] [--check] [--blockers] - t27b build -o [--elf | --macho] [--overflow trap|wrap] [--time] - t27b watch -o [--elf | --macho] [--overflow trap|wrap] [--then \"\"] - t27b asm [--overflow trap|wrap] - t27b verilog (the module, and its test bench under `ifdef T27B_TESTBENCH) - t27b ts (consts, scalar fns and tests as TypeScript; specs/tri/t27b/ts_emit.t27) - t27b corpus [--timeout-ms N] [--jobs N] [--overflow trap|wrap] [--list] - [--json ] [--runner \" [args]\"] - [--blockers [--reference [--reference-cache ] - [--reference-timeout-ms N]]] - - --elf build: an ELF64 object for aarch64 Linux (specs/tri/t27b/elf.t27), the default on - Linux; --macho: a Mach-O object for arm64 macOS, the default elsewhere - --json corpus: also write per-file results and totals to as JSON - (with --reference, the reference verdicts too) - --runner corpus: start each per-file `t27b test` as ` [args] test ...`, - e.g. --runner \"qemu-aarch64 -L /usr/aarch64-linux-gnu\" when the corpus - itself runs under qemu-user without binfmt_misc - --blockers test: report every unsupported construct, not just the first. - corpus: per file, every construct t27b lacks; then the greedy - order in which supporting them unlocks the most whole files. - --reference corpus: also run the reference path (` test-report`, - t27c's Zig backend) on every file, and leave the files it fails - out of the denominator. Where both sides ran a file's tests, - compare them test by test: `reference_disagree` counts files - where a test's verdict differs or only one side has it (#6441). - `mismatch` (= `jit_interp_mismatch`) is t27b's JIT against - t27b's own interpreter, not against the reference."; +#[path = "../../../gen/rust/tri/t27b/usage.rs"] #[allow(dead_code)] mod usage; use usage::T27B_USAGE as USAGE; // the usage text, t27c gen-rust of specs/tri/t27b/usage.t27 #[derive(Clone)] struct Opts { diff --git a/specs/tri/t27b/usage.t27 b/specs/tri/t27b/usage.t27 new file mode 100644 index 0000000000..8eb06530da --- /dev/null +++ b/specs/tri/t27b/usage.t27 @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/tri/t27b/usage.t27 -- the text `t27b help` prints, and that every usage error prints after its +; message. Issue gHashTag/t27#10859 (moved here from a 29-line Rust literal in cli/t27b/src/main.rs, so +; adding `t27b watch` shrinks the hand-written Rust instead of growing it; owner rule 2026-10-09). +; -- +; Exit codes, for the record (the values are consts in main.rs): 0 success; 1 a test failed; 2 unsupported +; construct; 3 front-end (parse or typecheck) error; 4 JIT and interpreter disagree (`--check`); 5 code +; generation limit; 64 usage; 74 I/O error. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module T27bUsage; + +/// The whole usage text, one line per `\n`. +pub const T27B_USAGE : str = "usage:\n t27b test [--overflow trap|wrap] [--time] [--quiet] [--check] [--blockers]\n t27b build -o [--elf | --macho] [--overflow trap|wrap] [--time]\n t27b watch -o [--elf | --macho] [--overflow trap|wrap] [--then '']\n t27b asm [--overflow trap|wrap]\n t27b verilog (the module, and its test bench under `ifdef T27B_TESTBENCH)\n t27b ts (consts, scalar fns and tests as TypeScript; specs/tri/t27b/ts_emit.t27)\n t27b corpus [--timeout-ms N] [--jobs N] [--overflow trap|wrap] [--list]\n [--json ] [--runner ' [args]']\n [--blockers [--reference [--reference-cache ]\n [--reference-timeout-ms N]]]\n\n --elf build: an ELF64 object for aarch64 Linux (specs/tri/t27b/elf.t27), the default on\n Linux; --macho: a Mach-O object for arm64 macOS, the default elsewhere\n --then watch: after every successful rebuild, run with sh -c (never after a failed one)\n --json corpus: also write per-file results and totals to as JSON\n (with --reference, the reference verdicts too)\n --runner corpus: start each per-file `t27b test` as ` [args] test ...`,\n e.g. --runner 'qemu-aarch64 -L /usr/aarch64-linux-gnu' when the corpus\n itself runs under qemu-user without binfmt_misc\n --blockers test: report every unsupported construct, not just the first.\n corpus: per file, every construct t27b lacks; then the greedy\n order in which supporting them unlocks the most whole files.\n --reference corpus: also run the reference path (` test-report`,\n t27c's Zig backend) on every file, and leave the files it fails\n out of the denominator. Where both sides ran a file's tests,\n compare them test by test: `reference_disagree` counts files\n where a test's verdict differs or only one side has it (#6441).\n `mismatch` (= `jit_interp_mismatch`) is t27b's JIT against\n t27b's own interpreter, not against the reference."; + +test the_usage_starts_with_its_heading { + // 'u' 's' 'a' 'g' 'e' ':' + assert T27B_USAGE[0] == 117; + assert T27B_USAGE[5] == 58; +} + +test the_usage_is_the_whole_text_not_a_stub { + assert T27B_USAGE.len > 2000; + assert T27B_USAGE.len < 4000; +} From d7f1f0bf4502f6f905c0bd5991a1402e1ff885a3 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:33:30 +0700 Subject: [PATCH 08/10] spec(t27b): usage tests read the text at run time (Refs #10859) Co-Authored-By: Claude Opus 5.5 --- specs/tri/t27b/usage.t27 | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/specs/tri/t27b/usage.t27 b/specs/tri/t27b/usage.t27 index 8eb06530da..ffeb314e17 100644 --- a/specs/tri/t27b/usage.t27 +++ b/specs/tri/t27b/usage.t27 @@ -13,13 +13,23 @@ module T27bUsage; /// The whole usage text, one line per `\n`. pub const T27B_USAGE : str = "usage:\n t27b test [--overflow trap|wrap] [--time] [--quiet] [--check] [--blockers]\n t27b build -o [--elf | --macho] [--overflow trap|wrap] [--time]\n t27b watch -o [--elf | --macho] [--overflow trap|wrap] [--then '']\n t27b asm [--overflow trap|wrap]\n t27b verilog (the module, and its test bench under `ifdef T27B_TESTBENCH)\n t27b ts (consts, scalar fns and tests as TypeScript; specs/tri/t27b/ts_emit.t27)\n t27b corpus [--timeout-ms N] [--jobs N] [--overflow trap|wrap] [--list]\n [--json ] [--runner ' [args]']\n [--blockers [--reference [--reference-cache ]\n [--reference-timeout-ms N]]]\n\n --elf build: an ELF64 object for aarch64 Linux (specs/tri/t27b/elf.t27), the default on\n Linux; --macho: a Mach-O object for arm64 macOS, the default elsewhere\n --then watch: after every successful rebuild, run with sh -c (never after a failed one)\n --json corpus: also write per-file results and totals to as JSON\n (with --reference, the reference verdicts too)\n --runner corpus: start each per-file `t27b test` as ` [args] test ...`,\n e.g. --runner 'qemu-aarch64 -L /usr/aarch64-linux-gnu' when the corpus\n itself runs under qemu-user without binfmt_misc\n --blockers test: report every unsupported construct, not just the first.\n corpus: per file, every construct t27b lacks; then the greedy\n order in which supporting them unlocks the most whole files.\n --reference corpus: also run the reference path (` test-report`,\n t27c's Zig backend) on every file, and leave the files it fails\n out of the denominator. Where both sides ran a file's tests,\n compare them test by test: `reference_disagree` counts files\n where a test's verdict differs or only one side has it (#6441).\n `mismatch` (= `jit_interp_mismatch`) is t27b's JIT against\n t27b's own interpreter, not against the reference."; +/// Byte i of the usage text (the tests read it at run time, not folded at compile time). +pub fn usage_byte(i: usize) -> u8 { + return T27B_USAGE[i]; +} + +/// Length of the usage text in bytes. +pub fn usage_len() -> usize { + return T27B_USAGE.len; +} + test the_usage_starts_with_its_heading { - // 'u' 's' 'a' 'g' 'e' ':' - assert T27B_USAGE[0] == 117; - assert T27B_USAGE[5] == 58; + // 'u' then ':' at index 5 + assert usage_byte(0) == 117; + assert usage_byte(5) == 58; } test the_usage_is_the_whole_text_not_a_stub { - assert T27B_USAGE.len > 2000; - assert T27B_USAGE.len < 4000; + assert usage_len() > 2000; + assert usage_len() < 4000; } From ef75fc2b9bd41c993eb73730f948650e51bc2f94 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:33:49 +0700 Subject: [PATCH 09/10] gen(t27b): usage.rs from specs/tri/t27b/usage.t27 (Refs #10859) Co-Authored-By: Claude Opus 5.5 --- gen/rust/tri/t27b/usage.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 gen/rust/tri/t27b/usage.rs diff --git a/gen/rust/tri/t27b/usage.rs b/gen/rust/tri/t27b/usage.rs new file mode 100644 index 0000000000..361e00e96f --- /dev/null +++ b/gen/rust/tri/t27b/usage.rs @@ -0,0 +1,16 @@ +// Generated from .t27 spec +// DO NOT EDIT — generated by t27c +// NOT LOWERED BY THIS BACKEND: 2 test(s), 0 invariant(s), 0 function(s) with no body. +// This backend emits declarations only. The spec's checks live in +// the Zig and Verilog outputs; do not read this file as verified. + +pub const T27B_USAGE: &'static str = "usage:\n t27b test [--overflow trap|wrap] [--time] [--quiet] [--check] [--blockers]\n t27b build -o [--elf | --macho] [--overflow trap|wrap] [--time]\n t27b watch -o [--elf | --macho] [--overflow trap|wrap] [--then '']\n t27b asm [--overflow trap|wrap]\n t27b verilog (the module, and its test bench under `ifdef T27B_TESTBENCH)\n t27b ts (consts, scalar fns and tests as TypeScript; specs/tri/t27b/ts_emit.t27)\n t27b corpus [--timeout-ms N] [--jobs N] [--overflow trap|wrap] [--list]\n [--json ] [--runner ' [args]']\n [--blockers [--reference [--reference-cache ]\n [--reference-timeout-ms N]]]\n\n --elf build: an ELF64 object for aarch64 Linux (specs/tri/t27b/elf.t27), the default on\n Linux; --macho: a Mach-O object for arm64 macOS, the default elsewhere\n --then watch: after every successful rebuild, run with sh -c (never after a failed one)\n --json corpus: also write per-file results and totals to as JSON\n (with --reference, the reference verdicts too)\n --runner corpus: start each per-file `t27b test` as ` [args] test ...`,\n e.g. --runner 'qemu-aarch64 -L /usr/aarch64-linux-gnu' when the corpus\n itself runs under qemu-user without binfmt_misc\n --blockers test: report every unsupported construct, not just the first.\n corpus: per file, every construct t27b lacks; then the greedy\n order in which supporting them unlocks the most whole files.\n --reference corpus: also run the reference path (` test-report`,\n t27c's Zig backend) on every file, and leave the files it fails\n out of the denominator. Where both sides ran a file's tests,\n compare them test by test: `reference_disagree` counts files\n where a test's verdict differs or only one side has it (#6441).\n `mismatch` (= `jit_interp_mismatch`) is t27b's JIT against\n t27b's own interpreter, not against the reference."; + +pub fn usage_byte(i: usize) -> u8 { + return T27B_USAGE.as_bytes()[(i) as usize]; +} + +pub fn usage_len() -> usize { + return T27B_USAGE.len(); +} + From 130d2dca1928e7212d301c9e40cc9fe7337983ff Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Mon, 19 Oct 2026 12:35:27 +0700 Subject: [PATCH 10/10] ledger(t27b): list usage.t27 and watch_build.t27 as pass (Refs #10859) Co-Authored-By: Claude Opus 5.5 --- docs/reports/t27b_expectations.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/reports/t27b_expectations.json b/docs/reports/t27b_expectations.json index 1223512d7b..7802e89b5c 100644 --- a/docs/reports/t27b_expectations.json +++ b/docs/reports/t27b_expectations.json @@ -1958,10 +1958,12 @@ {"path": "specs/tri/t27b/tuple_local_plan.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/type_text.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/undefined_arg_plan.t27", "t27b": "pass"}, + {"path": "specs/tri/t27b/usage.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/verdict_key.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/verilog_emit.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/verilog_plan.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/void_bind_plan.t27", "t27b": "pass"}, + {"path": "specs/tri/t27b/watch_build.t27", "t27b": "pass"}, {"path": "specs/tri/t27b/wide_plan.t27", "t27b": "pass"}, {"path": "specs/tri/test/report.t27", "t27b": "pass"}, {"path": "specs/tri/trees/avl_tree.t27", "t27b": "pass"},