Skip to content

Commit 15936a5

Browse files
committed
yeast: Take fields by ownership in apply_rules_inner
Previously, apply_rules_inner snapshotted a node's fields by cloning the BTreeMap into a Vec<(FieldId, Vec<Id>)>, then built a fresh BTreeMap of new_fields for the rewritten Ids. For a node with N fields, this allocated 2N+1 things per visit (the snapshot Vec, N cloned children Vecs, the new BTreeMap entries) — even when nothing in the subtree was rewritten. Use std::mem::take to swap the parent's fields out by ownership: the recursion can mutate the AST (including pushing new nodes from rule firings) without any conflict, since we hold the owned BTreeMap locally. Iterate values_mut() and only allocate a fresh children Vec on the first divergence (lazy alloc): unchanged children stay in the existing slot. When done, swap the fields back. For a subtree with no rewrites, this is now zero allocations per node (modulo the recursion itself). For nodes with rewrites, it's one Vec allocation per field that contains a rewritten child, instead of two plus the BTreeMap rebuild.
1 parent 7bd27b8 commit 15936a5

1 file changed

Lines changed: 28 additions & 23 deletions

File tree

shared/yeast/src/lib.rs

Lines changed: 28 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -591,35 +591,40 @@ fn apply_rules_inner(
591591
}
592592
}
593593

594-
// Collect fields before recursing (avoids borrowing ast immutably during mutation)
595-
let field_entries: Vec<(FieldId, Vec<Id>)> = ast.nodes[id]
596-
.fields
597-
.iter()
598-
.map(|(&fid, children)| (fid, children.clone()))
599-
.collect();
600-
601-
// recursively descend into all the fields
594+
// Take the parent's fields by ownership: the recursion will rewrite
595+
// each child Id, and we'll write the (possibly mutated) field map back
596+
// when we're done. Avoids cloning the whole BTreeMap and its child
597+
// Vecs on entry. Each child Vec is only re-allocated if a rewrite
598+
// actually changes its contents.
599+
//
602600
// Child traversal does not increment rewrite depth and starts fresh
603601
// (no rule is skipped on child subtrees).
604-
let mut changed = false;
605-
let mut new_fields = BTreeMap::new();
606-
for (field_id, children) in field_entries {
607-
let mut new_children = Vec::new();
608-
for child_id in children {
602+
let mut fields = std::mem::take(&mut ast.nodes[id].fields);
603+
for children in fields.values_mut() {
604+
let mut new_children: Option<Vec<Id>> = None;
605+
for (i, &child_id) in children.iter().enumerate() {
609606
let result = apply_rules_inner(index, ast, child_id, fresh, rewrite_depth, None)?;
610-
if result.len() != 1 || result[0] != child_id {
611-
changed = true;
607+
let unchanged = result.len() == 1 && result[0] == child_id;
608+
match (&mut new_children, unchanged) {
609+
(None, true) => {} // unchanged so far, no allocation needed
610+
(None, false) => {
611+
// First divergence — copy already-processed Ids and
612+
// start collecting the rewritten sequence.
613+
let mut new = Vec::with_capacity(children.len());
614+
new.extend_from_slice(&children[..i]);
615+
new.extend(result);
616+
new_children = Some(new);
617+
}
618+
(Some(new), _) => {
619+
new.extend(result);
620+
}
612621
}
613-
new_children.extend(result);
614622
}
615-
new_fields.insert(field_id, new_children);
616-
}
617-
618-
if !changed {
619-
return Ok(vec![id]);
623+
if let Some(new) = new_children {
624+
*children = new;
625+
}
620626
}
621-
622-
ast.nodes[id].fields = new_fields;
627+
ast.nodes[id].fields = fields;
623628
Ok(vec![id])
624629
}
625630

0 commit comments

Comments
 (0)