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+ea35d9b72",
"built_by": "t27c-bootstrap@0.5.2+b258b9007",
"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:9d516eccbc6e4404953f1f5c85c3fcce8c3e0abe08bcca15cfa0e5abb70a5185",
"gen_hash_rust": "sha256:1b30b3432b64262d5c3e42e5fbec57937afbc0c0b31fbc2ccf27a11513431955",
"gen_hash_verilog": "sha256:744dab1dd12a361c179b15417e0feebebaef4cd8b3c7044c0452458c5ba43cba",
"gen_hash_zig": "sha256:274bbd97674fbb7f4d3e100a400e64e10861c1651dd961a31ad9335690a124c9",
"gen_hash_c": "sha256:852ac5df69c82e3105afd569588b91ea3f8f523ff0f7735998d966444bd0c0bb",
"gen_hash_rust": "sha256:dbdec23bddbd792eb02e1dccf030ae97d857632a2d0db30f72da1ae26b889fed",
"gen_hash_verilog": "sha256:0d09f99c7b82ebac2ebede0f14057a37fea2b0adf4da781375bad836a3912e05",
"gen_hash_zig": "sha256:d5ed66af09cfbff648cbd88b9ebb2f1330edf91e9b5915a41acfe39c029f539d",
"module": "T27oLedger",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-19T05:41:02Z",
"sealed_at": "2026-10-19T18:13:45Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:1ca252b0187eb7b996e2e1f0c5331fe53c0db65b3f4aa78f85a3eb5d0704b023",
"spec_hash": "sha256:5eca91dc4bdcb3feb648c3cd11cf86982409390f63f174391affe7e45415614e",
"spec_path": "specs/tools/t27o/ledger.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 38,
"total": 38,
"passed": 51,
"total": 51,
"vacuous": 0
},
"toolchain": {
Expand Down
18 changes: 9 additions & 9 deletions cli/tri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,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 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).
/// 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, the files that convert whole, and the ranked first-refusal kinds, a cascade counted under its root (#11122; 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 },
/// Land a lane branch (default: the current one): merge master, then tests with no vacuous pass, seals, types ratchet, dupe subset, push, pull request, auto-merge; the first failing gate stops it with a named code (CONFLICT, TESTS_NOT_CLEAN, SEAL_NOT_CLEAN, TYPES_NOT_CLEAN, DUPE_NOT_SUBSET, PUSH_NOT_CONFIRMED) and a second run prints ALREADY_LANDED; --plan prints the steps and runs none, --check stops before the commit; T27C names the t27c program (default t27c on PATH) (rules: specs/tri/land/land.t27, #11068).
Land { branch: Option<String>, #[arg(long, default_value_t = 0)] issue: u64, #[arg(long)] plan: bool, #[arg(long)] check: bool },
Expand Down Expand Up @@ -1417,14 +1417,14 @@ fn cmd_t27o(file: &str, name: Option<&str>, oracle: bool, check: bool, t27c: Opt
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.
/// `tri t27o-sweep` (#10543): every rule is ledger.t27 (which files, whether a row is reusable, coverage, whole files, a cascade's root, 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, 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 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() == 11).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]);
let (mut tab, mut kinds, mut tot, mut n) = (String::new(), std::collections::BTreeMap::<&'static str, usize>::new(), [0usize; 6], [0usize; 4]);
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; }
Expand All @@ -1434,18 +1434,18 @@ fn cmd_t27o_sweep(paths: &[String], repo: &str, max: usize, cache: Option<&str>,
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 << 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{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()
let o: &'static str = String::from_utf8_lossy(&out[..m.min(out.len())]).into_owned().leak(); let (mut at, mut via) = (t27o_ledger::next_refusal(o, 0), 0usize); while at < o.len() { let r = t27o_ledger::root_of(o, at); *k.entry(&o[t27o_ledger::kind_from(o, r)..t27o_ledger::kind_to(o, r)]).or_default() += 1; via += t27o_ledger::attributed(o, at, r) as usize; at = t27o_ledger::next_refusal(o, at + 1); }
format!("{p}\t{sha}\t{ck}\t{std}\t{}\t{}\t{}\t{}\t{}\t{via}\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));
f[9].split(';').filter_map(|e| e.rsplit_once('=')).for_each(|(k, c)| *kinds.entry(k).or_default() += c.parse::<usize>().unwrap_or(0));
(0..6).for_each(|i| tot[i] += f[4 + i].parse::<usize>().unwrap_or(0)); n[3] += t27o_ledger::converts_whole(f[4].parse().unwrap_or(0), f[5].parse().unwrap_or(0)) as usize;
f[10].split(';').filter_map(|e| e.rsplit_once('=')).for_each(|(k, c)| *kinds.entry(k).or_default() += c.parse::<usize>().unwrap_or(0));
println!("{} {} {} {}/{} {} {}", f[0], f[4], f[5], f[6], f[7], f[8], if reuse { "reused" } else { "fresh" });
}
let cov = t27o_ledger::coverage_permille(tot[0], tot[1]); println!("total {} files {} fn seen {} converted tests {}/{} stubs {} coverage {}", n[0] + n[1], tot[0], tot[1], tot[2], tot[3], tot[4], if t27o_ledger::coverage_known(cov) { format!("{}.{}%", cov / 10, cov % 10) } else { "unknown".into() });
let (casc, mut root): (Vec<_>, Vec<_>) = kinds.into_iter().partition(|(k, _)| t27o_ledger::is_cascade(*k));
root.sort_by(|a, b| t27o_ledger::rank_cmp(a.1, a.0, b.1, b.0).cmp(&0)); root.iter().take(top).for_each(|(k, c)| println!("{c:>7} {k}"));
println!("{:>7} cascade ({})\ncold {} / reused {} / skipped {} wall {:.1} s", casc.iter().map(|k| k.1).sum::<usize>(), t27o_ledger::KIND_CASCADE, n[0], n[1], n[2], t0.elapsed().as_secs_f64());
let ext = casc.iter().map(|k| k.1).sum::<usize>(); println!("{:>7} files convert whole (every fn converted)\n{:>7} refused only through a cascade: {} counted under their root above, {ext} {}\ncold {} / reused {} / skipped {} wall {:.1} s", n[3], tot[5] + ext, tot[5], t27o_ledger::KIND_EXTERNAL, n[0], n[1], n[2], t0.elapsed().as_secs_f64());
if let Some(d) = Path::new(&cache).parent() { fs::create_dir_all(d)?; } fs::write(&cache, tab)?; Ok(0)
}

Expand Down
169 changes: 150 additions & 19 deletions gen/rust/tools/t27o/ledger.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// Generated from .t27 spec
// DO NOT EDIT — generated by t27c
// NOT LOWERED BY THIS BACKEND: 38 test(s), 0 invariant(s), 0 function(s) with no body.
// NOT LOWERED BY THIS BACKEND: 51 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.

Expand Down Expand Up @@ -271,6 +271,14 @@ pub const COVERAGE_UNKNOWN: usize = 1001;

pub const KIND_CASCADE: &'static str = "uses refused";

pub const KIND_EXTERNAL: &'static str = "uses refused (external)";

pub const ROOT_HOPS: usize = 64;

pub const REFUSED: &'static str = "// t27o: refused ";

pub const REF_LINE: &'static str = " (line ";

pub fn reuse_row(state: u32) -> bool {
return (state == K_FRESH);
}
Expand Down Expand Up @@ -353,41 +361,43 @@ pub fn coverage_known(permille: usize) -> bool {
return (permille < COVERAGE_UNKNOWN);
}

pub fn is_refusal(line: &'static str) -> bool {
return starts_at(line, 0, "// t27o: refused ");
pub fn is_refusal(text: &'static str, at: usize) -> bool {
return starts_at(text, at, REFUSED);
}

pub fn reason_from(line: &'static str) -> usize {
pub fn reason_from(text: &'static str, at: usize) -> usize {
let e: usize = line_stop(text, at);
let mut seen_line: bool = false;
let mut k: usize = 0;
while (k < line.len()) {
if starts_at(line, k, "(line ") {
let mut k: usize = at;
while (k < e) {
if starts_at(text, k, "(line ") {
seen_line = true;
}
if (seen_line && starts_at(line, k, "): ")) {
if (seen_line && starts_at(text, k, "): ")) {
return (k + 3);
}
k = (k + 1);
}
return line.len();
return e;
}

pub fn kind_from(line: &'static str) -> usize {
if (reason_from(line) == line.len()) {
return 0;
pub fn kind_from(text: &'static str, at: usize) -> usize {
if (reason_from(text, at) == line_stop(text, at)) {
return at;
}
return reason_from(line);
return reason_from(text, at);
}

pub fn kind_to(line: &'static str) -> usize {
if (reason_from(line) == line.len()) {
return line.len();
pub fn kind_to(text: &'static str, at: usize) -> usize {
let e: usize = line_stop(text, at);
if (reason_from(text, at) == e) {
return e;
}
let mut k: usize = reason_from(line);
while (((k < line.len()) && (line.as_bytes()[(k) as usize] != 96)) && (line.as_bytes()[(k) as usize] != 59)) {
let mut k: usize = reason_from(text, at);
while (((k < e) && (text.as_bytes()[(k) as usize] != 96)) && (text.as_bytes()[(k) as usize] != 59)) {
k = (k + 1);
}
if ((k > reason_from(line)) && (line.as_bytes()[((k - 1)) as usize] == 32)) {
if ((k > reason_from(text, at)) && (text.as_bytes()[((k - 1)) as usize] == 32)) {
return (k - 1);
}
return k;
Expand Down Expand Up @@ -427,6 +437,119 @@ pub fn rank_cmp(count_a: usize, kind_a: &'static str, count_b: usize, kind_b: &'
return text_order(kind_a, kind_b);
}

pub fn next_refusal(text: &'static str, from: usize) -> usize {
let mut k: usize = from;
if (((k > 0) && (k <= text.len())) && (text.as_bytes()[((k - 1)) as usize] != 10)) {
k = (line_stop(text, k) + 1);
}
while (k < text.len()) {
if is_refusal(text, k) {
return k;
}
k = (line_stop(text, k) + 1);
}
return text.len();
}

pub fn item_from(text: &'static str, at: usize) -> usize {
let e: usize = line_stop(text, at);
let mut k: usize = (at + REFUSED.len());
while ((k < e) && (text.as_bytes()[(k) as usize] != 32)) {
k = (k + 1);
}
if ((k >= e) || starts_at(text, k, REF_LINE)) {
return k;
}
return (k + 1);
}

pub fn item_to(text: &'static str, at: usize) -> usize {
let e: usize = line_stop(text, at);
let mut k: usize = item_from(text, at);
while ((k < e) && (starts_at(text, k, REF_LINE) == false)) {
k = (k + 1);
}
if (k >= e) {
return item_from(text, at);
}
return k;
}

pub fn target_from(text: &'static str, at: usize) -> usize {
let e: usize = line_stop(text, at);
let mut k: usize = reason_from(text, at);
while ((k < e) && (text.as_bytes()[(k) as usize] != 96)) {
k = (k + 1);
}
if (k < e) {
return (k + 1);
}
return e;
}

pub fn target_to(text: &'static str, at: usize) -> usize {
let e: usize = line_stop(text, at);
let mut k: usize = target_from(text, at);
while ((k < e) && (text.as_bytes()[(k) as usize] != 96)) {
k = (k + 1);
}
return k;
}

pub fn quoted_refusal(text: &'static str, at: usize) -> usize {
let from: usize = target_from(text, at);
let n: usize = (target_to(text, at) - from);
if (n == 0) {
return text.len();
}
let mut k: usize = next_refusal(text, 0);
while (k < text.len()) {
if ((item_to(text, k) - item_from(text, k)) == n) {
let mut i: usize = 0;
while ((i < n) && (text.as_bytes()[((item_from(text, k) + i)) as usize] == text.as_bytes()[((from + i)) as usize])) {
i = (i + 1);
}
if (i == n) {
return k;
}
}
k = next_refusal(text, (k + 1));
}
return text.len();
}

pub fn is_cascade_at(text: &'static str, at: usize) -> bool {
if ((kind_to(text, at) - kind_from(text, at)) < KIND_CASCADE.len()) {
return false;
}
return starts_at(text, kind_from(text, at), KIND_CASCADE);
}

pub fn root_of(text: &'static str, at: usize) -> usize {
let mut cur: usize = at;
let mut hops: usize = 0;
while (hops < ROOT_HOPS) {
if (is_cascade_at(text, cur) == false) {
return cur;
}
let next: usize = quoted_refusal(text, cur);
if (next >= text.len()) {
return cur;
}
cur = next;
hops = (hops + 1);
}
return at;
}

pub fn attributed(text: &'static str, at: usize, root: usize) -> bool {
return (is_cascade_at(text, at) && (is_cascade_at(text, root) == false));
}

pub fn converts_whole(seen: usize, converted: usize) -> bool {
return ((seen > 0) && (converted == seen));
}

pub fn out_is(out: impl AsRef<[u8]>, n: usize, want: &'static str) -> bool {
let out: &[u8] = out.as_ref();
if ((n != want.len()) || (n > out.len())) {
Expand Down Expand Up @@ -680,6 +803,14 @@ pub fn line(v: u8, k: u8) -> &'static str {
return "sealed bootstrap/src/compiler.rs";
}

pub fn line_stop(raw: &'static str, at: usize) -> usize {
let mut k: usize = at;
while ((k < raw.len()) && (raw.as_bytes()[(k) as usize] != 10)) {
k = (k + 1);
}
return k;
}

pub fn starts_at(raw: &'static str, at: usize, lit: &'static str) -> bool {
if ((at + lit.len()) > raw.len()) {
return false;
Expand Down
Loading
Loading