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
32 changes: 32 additions & 0 deletions .trinity/seals/t27b_T27bAstWalk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"built_by": "t27c-bootstrap@0.5.2+a561d6d13",
"closure": [
{
"spec": "specs/tri/t27b/type_text.t27",
"spec_hash": "sha256:2ddefc1bb1e94520ee8cced7ec3eeea68ae4b220311f3452005e691df899baca"
}
],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:fb23ea22cd3d6ed7a5cdbf69ca0a1be713d9a7caa38520f53968779fbf26a83f",
"gen_hash_rust": "sha256:1479454c82b470b419a2f665b5b1c01d4c1aac04e89785cb2a37a3fd19976882",
"gen_hash_verilog": "sha256:d86c12c3f3a2a0741a62f4e22d82dfdaace40419e5f0d76f285c10444acda608",
"gen_hash_zig": "sha256:d80a90285db348098d064050bb87eb6ce997edb7e9760442c4300e49584dfd9e",
"module": "T27bAstWalk",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-09T16:27:29Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:6653bee4e36f576c6b04d29b537ad52481c959e98b922257f48f3c0d4cf809e3",
"spec_path": "specs/tri/t27b/ast_walk.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 18,
"total": 18
},
"toolchain": {
"test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d",
"zig": "0.16.0"
}
}
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,11 @@ measure it are written in **t27**, not in Rust or Python by hand.
(`specs/tri/t27b/any_param_plan.t27`), +23, with `CmpOp`'s `symbol`,
`negate`, `swap` and `holds_f64` moved from `ir.rs` to
`specs/tri/t27b/cmp_op.t27`, -30, net -7, 0 and 0 on master 3ac8bef87's
14694 plus 1222 and 8284),
14694 plus 1222 and 8284; 14622 plus 1222, 15844 in all, and tests 8284,
after #8249 moved the body walks of `lower.rs` (`name_count`, `mutated`,
`zig_mutates` and six more, over the body's bytes) to
`specs/tri/t27b/ast_walk.t27`, -65, 0 and 0 on master 95ae4bcd6's 14687
plus 1222 and 8284),
`scripts/tri_loop/t27b.py` 1829
(Python, `tri t27b`; 894 at c532fcae5, 1481 after #6317's `next`, 1568 after #6334's master look-back, 1829 after #6445's `reduce` wiring), `scripts/tri_loop/t27b_reduce.py` 632 (Python, `tri t27b reduce`, #6445; its decisions are `specs/tri/t27b/reduce.t27`), and `contrib/railway/t27b-lab/lab.py` 608 (Python, the
Railway lab; 956 on master 0fbb0a033, 986 after #7672's lane requests). Update these numbers in the PR that moves them.
Expand Down
127 changes: 31 additions & 96 deletions cli/t27b/src/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ mod tt; // t27c gen-rust of specs/tri/t27b/type_text.t27: a type's text, read wi
mod st; // t27c gen-rust of specs/tri/t27b/source_text.t27: literal text, header lines, prose labels
mod refvars;
mod tuple;
#[path = "../../../gen/rust/tri/t27b/ast_walk.rs"] #[allow(dead_code, unused_parens)]
mod aw; // t27c gen-rust of specs/tri/t27b/ast_walk.t27: walks of a list of nodes, read from `flat`'s bytes

/// A construct outside the supported subset (or a type error inside it).
#[derive(Clone, Debug)]
Expand Down Expand Up @@ -7413,54 +7415,42 @@ fn decls_of<'n>(ns: &'n [Node], name: &str, out: &mut Vec<&'n Node>) {
}
}

/// `ns` as specs/tri/t27b/ast_walk.t27 reads it: a record per node in preorder, then the fields' text.
fn flat(ns: &[Node]) -> (Vec<u8>, Vec<u8>) {
fn put(n: &Node, a: &mut Vec<u8>, t: &mut Vec<u8>) {
let at = a.len();
a.extend_from_slice(&[n.kind.clone() as u8, 0, 0, 0, 0, 0, 0, 0]);
for f in [&n.name, &n.value, &n.extra_type, &n.extra_field, &n.extra_size, &n.extra_kind, &n.extra_op, &n.extra_return_type] {
a.extend_from_slice(&(t.len() as u32).to_le_bytes());
t.extend_from_slice(f.as_bytes());
a.extend_from_slice(&(t.len() as u32).to_le_bytes());
}
n.children.iter().for_each(|c| put(c, a, t));
let end = (a.len() / aw::REC) as u32;
a[at + 4..at + 8].copy_from_slice(&end.to_le_bytes());
}
let (mut a, mut t) = (Vec::new(), Vec::new());
ns.iter().for_each(|n| put(n, &mut a, &mut t));
(a, t)
}
const _: () = assert!(NodeKind::StmtExpr as u8 == aw::K_STMT_EXPR && NodeKind::ExprRange as u8 == aw::K_EXPR_RANGE);

/// How many nodes under `ns` carry `name` (or a dotted path starting with
/// it) as their name, of any kind: an over-count of its uses.
fn name_count(ns: &[Node], name: &str) -> usize {
ns.iter()
.map(|n| {
let hit = n.name == name || n.name.strip_prefix(name).is_some_and(|r| r.starts_with('.'));
hit as usize + name_count(&n.children, name)
})
.sum()
let (a, t) = flat(ns); aw::name_count(&a, &t, name.as_bytes())
}

/// `name_count`, plus each node whose type or size text names `name` as a
/// word (array literal elements and array sizes are kept as text): an
/// over-count of its mentions.
fn name_mentions(ns: &[Node], name: &str) -> usize {
fn word_in(text: &str, name: &str) -> bool {
let b = text.as_bytes();
let id = |c: u8| c.is_ascii_alphanumeric() || c == b'_';
text.match_indices(name).any(|(i, _)| {
let e = i + name.len();
(i == 0 || !id(b[i - 1])) && (e >= b.len() || !id(b[e]))
})
}
ns.iter()
.map(|n| {
let hit = n.name == name || n.name.strip_prefix(name).is_some_and(|r| r.starts_with('.'));
let text = [&n.extra_size, &n.extra_type, &n.extra_field, &n.extra_return_type]
.iter()
.any(|t| word_in(t, name));
hit as usize + text as usize + name_mentions(&n.children, name)
})
.sum()
let (a, t) = flat(ns); aw::name_mentions(&a, &t, name.as_bytes())
}

/// How many `_ = name;` statements lie under `ns`, at any depth.
fn discard_count(ns: &[Node], name: &str) -> usize {
ns.iter()
.map(|n| {
let hit = n.kind == NodeKind::StmtAssign
&& matches!(n.extra_op.as_str(), "" | "=")
&& n.children.len() == 2
&& n.children[0].kind == NodeKind::ExprIdentifier
&& n.children[0].name == "_"
&& n.children[1].kind == NodeKind::ExprIdentifier
&& n.children[1].name == name;
hit as usize + discard_count(&n.children, name)
})
.sum()
let (a, t) = flat(ns); aw::discard_count(&a, &t, name.as_bytes())
}

/// An array literal whose elements are all plain literals (a negated one
Expand Down Expand Up @@ -7689,23 +7679,7 @@ fn misprinted_ifs(n: &Node, out: &mut HashSet<usize>) {
/// The node kinds t27c's `gen_expr` prints anything for; every other kind (a
/// statement) renders as the empty string.
fn zig_renders(k: &NodeKind) -> bool {
matches!(
k,
NodeKind::ExprLiteral
| NodeKind::ExprIdentifier
| NodeKind::ExprEnumValue
| NodeKind::ExprCall
| NodeKind::ExprBinary
| NodeKind::ExprUnary
| NodeKind::ExprFieldAccess
| NodeKind::ExprIndex
| NodeKind::ExprSwitch
| NodeKind::ExprIf
| NodeKind::ExprArrayLiteral
| NodeKind::ExprStructLit
| NodeKind::ExprCast
| NodeKind::ExprTuple
)
aw::renders(k.clone() as u8)
}

/// Collect the `*` expressions t27c's strength reduction rewrites as `<<`
Expand Down Expand Up @@ -7803,17 +7777,7 @@ fn mark_tail_returns(ns: &[Node], out: &mut HashSet<usize>) {
/// (`value of type ... ignored`). Calls, `return`, `try`, `if`, `switch` and
/// `undefined` have their own rules and are not in this set.
fn is_value_stmt(c: &Node) -> bool {
match c.kind {
NodeKind::ExprBinary
| NodeKind::ExprLiteral
| NodeKind::ExprCast
| NodeKind::ExprFieldAccess
| NodeKind::ExprIndex
| NodeKind::ExprStructLit
| NodeKind::ExprArrayLiteral => true,
NodeKind::ExprIdentifier => c.name != "undefined",
_ => false,
}
aw::value_stmt(c.kind.clone() as u8, c.name.as_bytes())
}

/// Parameter `p: t` of fn `f` is `anytype` and `f`'s body never names it (specs/tri/t27b/any_param_plan.t27).
Expand All @@ -7822,7 +7786,7 @@ fn unread_any(f: &Node, p: &str, t: &str) -> bool {
}

fn mentions(ns: &[Node], name: &str) -> bool {
ns.iter().any(|n| (n.kind == NodeKind::ExprIdentifier && n.name == name) || mentions(&n.children, name))
let (a, t) = flat(ns); aw::mentions(&a, &t, name.as_bytes())
}

/// The module `var`s a top-level local of `body` may shadow: the reference's
Expand All @@ -7831,7 +7795,7 @@ fn mentions(ns: &[Node], name: &str) -> bool {
/// nothing before it (its own initializer included) mentions the name.
fn shadow_names(body: &[Node], mod_vars: &HashMap<String, Place>) -> HashSet<String> {
fn decls(ns: &[Node], name: &str) -> usize {
ns.iter().map(|n| usize::from(n.kind == NodeKind::StmtLocal && n.name == name) + decls(&n.children, name)).sum()
let (a, t) = flat(ns); aw::decl_count(&a, &t, name.as_bytes())
}
let mut ok = HashSet::new();
for (i, s) in body.iter().enumerate() {
Expand All @@ -7850,20 +7814,7 @@ fn shadow_names(body: &[Node], mod_vars: &HashMap<String, Place>) -> HashSet<Str
/// The reference's `collect_mutable_names`: is `name` the target (or the base
/// of an indexed or field target) of an assignment in `ns`?
fn mutated(ns: &[Node], name: &str) -> bool {
ns.iter().any(|n| {
if n.kind == NodeKind::StmtAssign {
if let Some(t) = n.children.first() {
let base = match t.kind {
NodeKind::ExprIndex | NodeKind::ExprFieldAccess => t.children.first(),
_ => Some(t),
};
if base.is_some_and(|b| b.kind == NodeKind::ExprIdentifier && b.name == name) {
return true;
}
}
}
mutated(&n.children, name)
})
let (a, t) = flat(ns); aw::mutated(&a, &t, name.as_bytes())
}

fn count_assigns(ns: &[Node], counts: &mut HashMap<String, u32>) {
Expand Down Expand Up @@ -7970,23 +7921,7 @@ fn ref_mutable_names(ns: &[Node], out: &mut HashSet<String>) {
/// target reaches `name` through fields and indexes but no `.*`, `&` of such
/// a path, or a method call on it. Errs toward yes: a yes refuses nothing.
fn zig_mutates(ns: &[Node], name: &str) -> bool {
fn rooted(t: &Node, name: &str) -> bool {
match t.kind {
NodeKind::ExprIdentifier => t.name == name,
NodeKind::ExprFieldAccess if t.name == "*" => false,
NodeKind::ExprFieldAccess | NodeKind::ExprIndex => t.children.first().is_some_and(|b| rooted(b, name)),
_ => false,
}
}
ns.iter().any(|n| {
let hit = match n.kind {
NodeKind::StmtAssign => n.children.first().is_some_and(|t| rooted(t, name)),
NodeKind::ExprUnary if n.extra_op == "&" => n.children.first().is_some_and(|t| rooted(t, name)),
NodeKind::ExprCall => n.name.split('.').next() == Some(name) && n.name.contains('.'),
_ => false,
};
hit || zig_mutates(&n.children, name)
})
let (a, t) = flat(ns); aw::zig_mutates(&a, &t, name.as_bytes())
}

/// Syntax the reference prints that Zig cannot parse: a childless typed
Expand Down
4 changes: 3 additions & 1 deletion docs/reports/t27b_expectations.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"reference-bug": "the reference passes it for the wrong reason; t27b is right not to",
"n/a": "outside what t27b is for (say why in the PR that sets it)"
},
"max_not_pass": 19,
"max_not_pass": 20,
"entries": [
{"path": "specs/a/b_c.t27", "t27b": "pass_vacuous"},
{"path": "specs/a_b/c.t27", "t27b": "pass_vacuous"},
Expand Down Expand Up @@ -1060,6 +1060,7 @@
{"path": "specs/tri/sort/selection_sort.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/a64.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/any_param_plan.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/ast_walk.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/bit_cast_plan.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/blockers.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/builtin_plan.t27", "t27b": "pass"},
Expand Down Expand Up @@ -1157,6 +1158,7 @@
{"path": "specs/tri/t27b/reduce.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/scaffold_plan.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/slice_lit_plan.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/source_text.t27", "t27b": "blocked", "blocker": "type i128", "reason": "unimplemented"},
{"path": "specs/tri/t27b/steward.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/tuple_local_plan.t27", "t27b": "pass"},
{"path": "specs/tri/t27b/type_text.t27", "t27b": "blocked", "blocker": "ExprCall(@intCast)", "reason": "unimplemented"},
Expand Down
Loading
Loading