Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions .trinity/seals/t27o_T27oLedger.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"built_by": "t27c-bootstrap@0.5.2+9b556b7c3",
"built_by": "t27c-bootstrap@0.5.2+ea35d9b72",
"closure": [
{
"spec": "specs/tri/lab/receipt.t27",
Expand All @@ -15,23 +15,23 @@
}
],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:428200eaf55079318d360ecec24a359a21320f1f64f94c50001bb93159d8f882",
"gen_hash_rust": "sha256:02f9f45cec23ed1149a09edd668dfc48aef3b4d83d0ad7a1f0709a564fd6d3c5",
"gen_hash_verilog": "sha256:7d461b24959b4ee95bcb3617f2a41e5eaac048bf7640f8a467f7e60b6db1c3b8",
"gen_hash_zig": "sha256:174c0e2cf57c8647ba48908f90aaaf5f87456ddbae3346f0db0550bbd8c57026",
"gen_hash_c": "sha256:9d516eccbc6e4404953f1f5c85c3fcce8c3e0abe08bcca15cfa0e5abb70a5185",
"gen_hash_rust": "sha256:1b30b3432b64262d5c3e42e5fbec57937afbc0c0b31fbc2ccf27a11513431955",
"gen_hash_verilog": "sha256:744dab1dd12a361c179b15417e0feebebaef4cd8b3c7044c0452458c5ba43cba",
"gen_hash_zig": "sha256:274bbd97674fbb7f4d3e100a400e64e10861c1651dd961a31ad9335690a124c9",
"module": "T27oLedger",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-18T21:16:15Z",
"sealed_at": "2026-10-19T05:41:02Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:98b04611fe2950faac8f137f1dbc7be83d595049988dbb13a520d840e4b09dd6",
"spec_hash": "sha256:1ca252b0187eb7b996e2e1f0c5331fe53c0db65b3f4aa78f85a3eb5d0704b023",
"spec_path": "specs/tools/t27o/ledger.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 29,
"total": 29,
"passed": 38,
"total": 38,
"vacuous": 0
},
"toolchain": {
Expand Down
16 changes: 8 additions & 8 deletions .trinity/seals/t27o_T27oZig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,23 @@
}
],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:1902658b3fdd4234f5995a344bff8e880b6469635ea1fb0af023899d9cc6ead8",
"gen_hash_rust": "sha256:b804e5f4927cb4226bd6623c919e290f26a54d42d2c09ae9b949daeb1f00ee46",
"gen_hash_verilog": "sha256:1824c0407ae4c0cf97e7afb4b103ad6adcc20052636dbad081dd9330c1439e8e",
"gen_hash_zig": "sha256:c6e3f12811dd2eeaab6f29949789c444d9b65bdfa683d7a9d2ddb46d0359b906",
"gen_hash_c": "sha256:7725f48195f1d63c346c554a9f476564993956aa96528440617597f9f7af0df2",
"gen_hash_rust": "sha256:5b79bfee2a5d9770de677f1717c9fb5fbf022ca8353f0ced3993efc58011cb60",
"gen_hash_verilog": "sha256:172face9cae7881f4f41c5fd65f10e3c57ddeee52e62861bfe810c8152590e4f",
"gen_hash_zig": "sha256:8a89267d881f63316a6ff08b4ed3d755002640d19562da007fc2023179ed9f9f",
"module": "T27oZig",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-19T03:31:24Z",
"sealed_at": "2026-10-19T06:34:03Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:a21f02d266648e370fcaf0e2d9cc50f30b4b23277caa3dc4a625369a1b4c59e0",
"spec_hash": "sha256:8322d4dd716d084c17c366d336e9b70f4720b392dd8614b3983579fdf2c28b38",
"spec_path": "specs/tools/t27o/zig_convert.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 45,
"total": 45,
"passed": 61,
"total": 61,
"vacuous": 0
},
"toolchain": {
Expand Down
28 changes: 28 additions & 0 deletions .trinity/seals/t27o_T27oZigStd.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"built_by": "t27c-bootstrap@0.5.2+ea35d9b72",
"closure": [],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:75c0ec2458e26d4de273854fe7346972fcff9e85a19b52bd940c94f82cbaee1a",
"gen_hash_rust": "sha256:b0c91ce7507930661c0606e5764ca3c1c49fb9f18f1a3672835f156a52818ba7",
"gen_hash_verilog": "sha256:d49e1fdaf0787d3c2f03c44f1bcc3020d9c3c62fba11c7dcfb5bfa38ec1e40fa",
"gen_hash_zig": "sha256:05fe1623474e2296103ada5d5ade121c1271609c5581723d4f7fd4bdfb31c702",
"module": "T27oZigStd",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-19T06:07:00Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:282b6910324ebe55c2d5ba68c39ed2db1663c4fbf2864deba3265c908ebbef3a",
"spec_path": "specs/tools/t27o/zig_std.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 7,
"total": 7,
"vacuous": 0
},
"toolchain": {
"test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10",
"zig": "0.16.0"
}
}
56 changes: 28 additions & 28 deletions cli/tri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,10 @@ mod gen_cache; // the gen-hash cache's eviction limit, for `tri hot caches` (#89
mod bit_cache; // the bitstream cache's eviction limit, for `tri hot caches` (#8952)
#[path = "../../../gen/rust/tri/hot/hot.rs"] #[allow(dead_code, unused_parens)]
mod hot; // t27c gen-rust of specs/tri/hot/hot.t27 (#8870): every rule of `tri hot`
#[path = "../../../gen/rust/tools/t27o/ledger.rs"] #[allow(dead_code, unused_parens, clippy::all)]
mod t27o_ledger; // t27c gen-rust of specs/tools/t27o/ledger.t27: which runner's exit code is a verdict for `tri t27o --check` (#10525)
#[path = "../../../gen/rust/tools/t27o/ledger.rs"] #[allow(dead_code, unused_parens, clippy::all)] mod t27o_ledger; // t27c gen-rust of specs/tools/t27o/ledger.t27: which runner's exit code is a verdict for `tri t27o --check` (#10525), which front end reads a file, what a .zig file is judged by (#10823)
#[path = "../../../gen/rust/tri/lanes/lanes.rs"] #[allow(dead_code, unused_parens)]
mod lanes; // t27c gen-rust of specs/tri/lanes/lanes.t27: every rule of `tri lanes`
#[path = "../../../gen/rust/tools/t27o/rust_convert.rs"] #[allow(dead_code, unused_parens, unused_mut, unused_variables, non_snake_case, unused_assignments, clippy::all)]
mod t27o; // t27c gen-rust of specs/tools/t27o/rust_convert.t27: every rule of `tri t27o` (#8967)
#[path = "../../../gen/rust/tools/t27o/rust_convert.rs"] #[allow(dead_code, unused_parens, unused_mut, unused_variables, non_snake_case, unused_assignments, clippy::all)] mod t27o; // t27c gen-rust of specs/tools/t27o/rust_convert.t27: every rule of `tri t27o` (#8967)
#[path = "../../../gen/rust/tools/t27o/spice_convert.rs"] #[allow(dead_code, unused_parens, unused_mut, unused_variables, non_snake_case, unused_assignments, clippy::all)] mod spice; // t27c gen-rust of specs/tools/t27o/spice_convert.t27: the SPICE front end of `tri t27o` (#10509)
#[path = "../../../gen/rust/tools/t27o/zig_convert.rs"] #[allow(dead_code, unused_parens, unused_mut, unused_variables, non_snake_case, unused_assignments, clippy::all)] mod zig; // t27c gen-rust of specs/tools/t27o/zig_convert.t27: the Zig front end of `tri t27o` (#10765)
mod renum;
Expand Down Expand Up @@ -432,7 +430,7 @@ enum Commands {
/// Print one JSON object instead of the table.
#[arg(long)] json: bool,
},
/// t27o (Ouroboros): a Rust file as a t27 spec; --oracle prints the rustc harness, --check runs it and t27c test-report on the spec and its oracle tests (rules: specs/tools/t27o/rust_convert.t27). A .spice/.cir/.sp netlist prints its inventory or the refusal, and with --emit-cell the constants of the M1 linear cell folded from it (rules: specs/tools/t27o/spice_convert.t27). A .zig file prints the spec and its count line (rules: specs/tools/t27o/zig_convert.t27); its own tests are the oracle, so run `t27b test` on the spec (--oracle and --check are for Rust).
/// t27o (Ouroboros): a Rust file as a t27 spec; --oracle prints the rustc harness, --check runs it and t27c test-report on the spec and its oracle tests (rules: specs/tools/t27o/rust_convert.t27). A .spice/.cir/.sp netlist prints its inventory or the refusal, and with --emit-cell the constants of the M1 linear cell folded from it (rules: specs/tools/t27o/spice_convert.t27). A .zig file prints the spec and its count line (rules: specs/tools/t27o/zig_convert.t27); its own tests are the oracle, so --check writes the spec and judges it by them, and --oracle says it does not apply.
T27o {
file: String,
/// The module name; default the file's stem.
Expand All @@ -445,7 +443,7 @@ enum Commands {
/// A netlist: print the M1 cell constants (CELL_N, CELL_D, CELL_NV) folded from it, or the refusal, instead of the inventory.
#[arg(long)] emit_cell: bool,
},
/// t27o over a tree (#10543): every tracked .rs file converted in-process; a file's row is reused while its key holds (its SHA-256 and the seals of rust_convert.t27 and rust_std.t27, read from the t27 checkout this runs in); `--repo <dir>` sweeps another checkout, with a cache of its own (#10638); counts, coverage and the ranked first-refusal kinds (rules: specs/tools/t27o/ledger.t27).
/// t27o over a tree (#10543): every tracked .rs and .zig file converted in-process; a file's row is reused while its key holds (its SHA-256 and the seals of rust_convert.t27 and rust_std.t27, and of zig_convert.t27 for a .zig file, read from the t27 checkout this runs in); `--repo <dir>` sweeps another checkout, with a cache of its own (#10638); counts, coverage and the ranked first-refusal kinds (rules: specs/tools/t27o/ledger.t27).
T27oSweep { paths: Vec<String>, #[arg(long, default_value = t27o_ledger::SWEEP_HERE)] repo: String, #[arg(long, default_value_t = t27o_ledger::SWEEP_MAX_BYTES)] max_bytes: usize, #[arg(long)] cache: Option<String>, #[arg(long, default_value_t = t27o_ledger::SWEEP_TOP)] top: usize },
}

Expand Down Expand Up @@ -1355,6 +1353,12 @@ fn main() -> Result<()> {
}

/// `tri t27o` (#8967): every rule is gen-rust of rust_convert.t27; this reads the file, runs rustc and t27c, and writes.
fn zig_source(file: &Path, src: &[u8]) -> Vec<u8> { // a .zig source with the consts it imports from files of its own directory tree written in: zig_convert.t27 `imports` names the files, `inline_imports` writes the values, this only reads (a symlink out of the tree is not followed; #10823)
let (dir, mut want, mut ext, mut flat, mut st) = (file.parent().and_then(|d| fs::canonicalize(d).ok()).unwrap_or_default(), vec![0u8; src.len() + 64], Vec::new(), vec![0u8; 4 * src.len() + 64], vec![0usize; zig::ZI_SLOTS]);
let w = zig::imports(src, &mut want, &mut st).min(want.len()); for rel in String::from_utf8_lossy(&want[..w]).lines() { if let Some(t) = fs::canonicalize(dir.join(rel)).ok().filter(|f| f.starts_with(&dir)).and_then(|f| fs::read(f).ok()) { ext.extend([&[zig::Z_B_REC][..], rel.as_bytes(), b"\n", &t].concat()); } }
let n = zig::inline_imports(src, &ext, &mut flat, &mut st).min(flat.len()); flat.truncate(n); flat
}

fn cmd_t27o(file: &str, name: Option<&str>, oracle: bool, check: bool, t27c: Option<&str>, emit_cell: bool) -> Result<u8> {
let src = fs::read(file).with_context(|| format!("reading {file}"))?;
if spice::is_spice_name(file.as_bytes()) { // a netlist: every rule is specs/tools/t27o/spice_convert.t27 (#10509)
Expand All @@ -1364,46 +1368,42 @@ fn cmd_t27o(file: &str, name: Option<&str>, oracle: bool, check: bool, t27c: Opt
return Ok(spice::exit_code(&st));
}
let stem = name.map_or_else(|| Path::new(file).file_stem().map_or("Converted".into(), |s| s.to_string_lossy().into_owned()), str::to_string);
let zig = t27o_ledger::front_end(file.as_bytes()) == t27o_ledger::FE_ZIG; if zig && oracle { println!("{}", t27o_ledger::NOTE_ZIG_ORACLE); return Ok(0); } // a .zig file: its own tests are the oracle (ledger.t27, #10823)
let src = if zig { zig_source(Path::new(file), &src) } else { src };
let (mut out, mut st, mut spec) = (vec![0u8; 1 << 24], vec![0usize; 1 << 18], vec![0u8; 1 << 24]);
let n = if zig::is_zig_name(file.as_bytes()) { zig::to_t27(&src, stem.as_bytes(), &mut out, &mut st) } else if oracle || check { t27o::oracle(&src, &mut out, &mut st) } else { t27o::convert(&src, stem.as_bytes(), &mut out, &mut st) };
if !check {
std::io::Write::write_all(&mut std::io::stdout(), &out[..n.min(out.len())])?;
return Ok(0);
let mut n = if zig { zig::to_t27(&src, stem.as_bytes(), &mut out, &mut st) } else if oracle || check { t27o::oracle(&src, &mut out, &mut st) } else { t27o::convert(&src, stem.as_bytes(), &mut out, &mut st) };
if !check { std::io::Write::write_all(&mut std::io::stdout(), &out[..n.min(out.len())])?; return Ok(0); }
let (h, exe, path) = (Path::new(file).with_file_name("zz_t27o_oracle.rs"), std::env::temp_dir().join("t27o_oracle"), PathBuf::from(format!("specs/t27o_check/{stem}.t27"))); // the harness sits next to the source, so its #[path] mods resolve; the spec under specs/, so its `use`s do
if !zig { // a Rust file: the harness rustc builds is run and its samples follow the spec
fs::write(&h, &out[..n.min(out.len())])?; let built = Command::new("rustc").args(["--edition", "2021", "-o"]).arg(&exe).arg(&h).status(); fs::remove_file(&h)?; anyhow::ensure!(built?.success(), "rustc refused the oracle harness of {file}");
let m = t27o::convert(&src, stem.as_bytes(), &mut spec, &mut st); out = [&spec[..m.min(spec.len())], &Command::new(&exe).output()?.stdout[..]].concat(); n = out.len();
}
// the harness sits next to the source, so its #[path] mods resolve; the spec under specs/, so its `use`s do
let (h, exe, path) = (Path::new(file).with_file_name("zz_t27o_oracle.rs"), std::env::temp_dir().join("t27o_oracle"), PathBuf::from(format!("specs/t27o_check/{stem}.t27")));
fs::write(&h, &out[..n.min(out.len())])?;
let built = Command::new("rustc").args(["--edition", "2021", "-o"]).arg(&exe).arg(&h).status();
fs::remove_file(&h)?;
anyhow::ensure!(built?.success(), "rustc refused the oracle harness of {file}");
let m = t27o::convert(&src, stem.as_bytes(), &mut spec, &mut st);
fs::create_dir_all("specs/t27o_check")?;
fs::write(&path, [&spec[..m.min(spec.len())], &Command::new(&exe).output()?.stdout[..]].concat())?;
println!("{}", path.display());
let b = ["target/release/t27b", "target/debug/t27b"].into_iter().find(|p| Path::new(p).exists()).unwrap_or("t27b"); // t27b judges first, t27c only when its exit is no verdict (specs/tools/t27o/ledger.t27, #10525)
let c = Command::new(b).arg("test").arg(&path).status().ok().and_then(|s| s.code()).unwrap_or(-1);
fs::create_dir_all("specs/t27o_check")?; fs::write(&path, &out[..n.min(out.len())])?; println!("{}", path.display());
if !t27o_ledger::has_evidence(zig, st[zig::Z_NTEST]) { eprintln!("{}", t27o_ledger::NOTE_ZIG_UNJUDGED); return Ok(t27o_ledger::EXIT_UNJUDGED as u8); }
let b = ["target/release/t27b", "target/debug/t27b"].into_iter().find(|p| Path::new(p).exists()).unwrap_or("t27b"); let c = Command::new(b).arg("test").arg(&path).status().ok().and_then(|s| s.code()).unwrap_or(-1); // t27b judges first, t27c only when its exit is no verdict (specs/tools/t27o/ledger.t27, #10525)
if !t27o_ledger::needs_reference(t27o_ledger::t27b_outcome(c)) { return Ok(c as u8); } eprintln!("t27o: {b} test exited {c} ({}), so t27c test-report judges instead", t27o_ledger::ORC_NAMES[t27o_ledger::t27b_outcome(c) as usize]);
Ok(Command::new(mutate::resolve_t27c(t27c)).arg("test-report").arg(&path).status()?.code().unwrap_or(1) as u8)
}

/// `tri t27o-sweep` (#10543): every rule is ledger.t27 (which files, whether a row is reusable, coverage, root vs cascade, the ranking); this lists, hashes, converts in-process and keeps the cache.
fn cmd_t27o_sweep(paths: &[String], repo: &str, max: usize, cache: Option<&str>, top: usize) -> Result<u8> {
let t0 = std::time::Instant::now(); let seal = |f: &str| -> Result<String> { let p = std::env::current_dir()?.join(f); Ok(serde_json::from_slice::<serde_json::Value>(&fs::read(&p).with_context(|| format!("{}: cannot read the seal; {}", p.display(), t27o_ledger::SEAL_HINT))?)?["spec_hash"].as_str().with_context(|| format!("{}: no spec_hash", p.display()))?.to_string()) };
let (conv, std) = (seal(t27o_ledger::SEAL_CONVERTER)?, seal(t27o_ledger::SEAL_STD)?); let repo: &'static str = repo.to_string().leak(); // the keys come from this checkout; the listing, the files and the cache belong to `root`
let (conv, std, zs) = (seal(t27o_ledger::SEAL_CONVERTER)?, seal(t27o_ledger::SEAL_STD)?, seal(t27o_ledger::SEAL_ZIG)?); let zk = format!("{conv}+{zs}"); let repo: &'static str = repo.to_string().leak(); // the keys come from this checkout; the listing, the files and the cache belong to `root`
let root: &'static str = if t27o_ledger::repo_is_here(repo) { t27o_ledger::SWEEP_HERE } else { fs::canonicalize(repo).with_context(|| format!("{repo}: cannot open the repository"))?.to_string_lossy().into_owned().leak() }; let cache = cache.map_or_else(|| { let mut b = vec![0u8; 4096]; let n = t27o_ledger::cache_path(root, &mut b); String::from_utf8_lossy(&b[..n]).into_owned() }, str::to_string);
let old: &'static str = fs::read_to_string(&cache).unwrap_or_default().leak(); let rows: std::collections::HashMap<&'static str, Vec<&'static str>> = old.lines().map(|l| l.split('\t').collect::<Vec<_>>()).filter(|f| f.len() == 10).map(|f| (f[0], f)).collect();
let ls = Command::new("git").args(["-C", root, "ls-files", "--"]).args(paths).output()?; anyhow::ensure!(ls.status.success(), "{root}: git ls-files failed: {}", String::from_utf8_lossy(&ls.stderr).trim()); let listing: &'static str = String::from_utf8(ls.stdout)?.leak();
let (mut tab, mut kinds, mut tot, mut n) = (String::new(), std::collections::BTreeMap::<&'static str, usize>::new(), [0usize; 5], [0usize; 3]);
for p in listing.lines().filter(|p| t27o_ledger::sweep_path(p)) {
let Ok(src) = fs::read(Path::new(root).join(p)) else { continue };
if t27o_ledger::over_cap(src.len(), max) { n[2] += 1; println!("{p} {} bytes skipped", src.len()); continue; }
let (sha, r) = (format!("{:x}", Sha256::digest(&src)), rows.get(p));
let reuse = t27o_ledger::reuse_row(t27o_ledger::key_check(r.is_some(), r.is_some_and(|r| r[1] == sha), r.is_some_and(|r| r[2] == conv), r.is_some_and(|r| r[3] == std)));
let z = t27o_ledger::front_end(p.as_bytes()) == t27o_ledger::FE_ZIG; let ck = if z { &zk } else { &conv }; // a .zig row is keyed by both converters' seals, and by the source with its imported consts written in (#10823)
let src = if z { zig_source(&Path::new(root).join(p), &src) } else { src }; let (sha, r) = (format!("{:x}", Sha256::digest(&src)), rows.get(p));
let reuse = t27o_ledger::reuse_row(t27o_ledger::key_check(r.is_some(), r.is_some_and(|r| r[1] == sha), r.is_some_and(|r| r[2] == *ck), r.is_some_and(|r| r[3] == std)));
let f: Vec<&'static str> = if reuse { r.unwrap().clone() } else {
let (mut out, mut st, mut k) = (vec![0u8; 1 << 24], vec![0usize; 1 << 16], std::collections::BTreeMap::<&'static str, usize>::new());
let m = t27o::convert(&src, Path::new(p).file_stem().map_or("Converted".into(), |s| s.to_string_lossy().into_owned()).as_bytes(), &mut out, &mut st);
let (mut out, mut st, mut k) = (vec![0u8; 1 << 24], vec![0usize; 1 << 18], std::collections::BTreeMap::<&'static str, usize>::new()); let stem = Path::new(p).file_stem().map_or("Converted".into(), |s| s.to_string_lossy().into_owned());
let m = if z { zig::to_t27(&src, stem.as_bytes(), &mut out, &mut st) } else { t27o::convert(&src, stem.as_bytes(), &mut out, &mut st) };
let o: &'static str = String::from_utf8_lossy(&out[..m.min(out.len())]).into_owned().leak(); for l in o.lines().filter(|l| t27o_ledger::is_refusal(l)) { *k.entry(&l[t27o_ledger::kind_from(l)..t27o_ledger::kind_to(l)]).or_default() += 1; }
format!("{p}\t{sha}\t{conv}\t{std}\t{}\t{}\t{}\t{}\t{}\t{}", st[t27o::NFN], st[t27o::NOK], st[t27o::NTEST], st[t27o::NTS], st[t27o::NSTUB], k.iter().map(|(a, b)| format!("{a}={b}")).collect::<Vec<_>>().join(";")).leak().split('\t').collect()
format!("{p}\t{sha}\t{ck}\t{std}\t{}\t{}\t{}\t{}\t{}\t{}", st[t27o::NFN], st[t27o::NOK], st[t27o::NTEST], st[t27o::NTS], st[t27o::NSTUB], k.iter().map(|(a, b)| format!("{a}={b}")).collect::<Vec<_>>().join(";")).leak().split('\t').collect()
};
n[reuse as usize] += 1; tab += &format!("{}\n", f.join("\t"));
(0..5).for_each(|i| tot[i] += f[4 + i].parse::<usize>().unwrap_or(0));
Expand Down
Loading
Loading