@@ -143,6 +143,18 @@ pub fn transform_with_used(mut a flat.FlatAst, tc &types.TypeChecker, used_fns m
143143 mut augmented_used_fns := used_fns.clone ()
144144 mut t := new_transformer (mut a, tc, augmented_used_fns)
145145 t.prepare ()
146+ // Transform roughly grows the node/children arrays by ~75%. Reserve that capacity
147+ // up front so they don't double past it (the parsed AST already overshoots to the
148+ // next power of two, wasting ~40MB, and each doubling briefly holds both the old and
149+ // new arrays — the dominant peak-RSS contributor under -gc none).
150+ reserve_nodes := a.nodes.len * 7 / 4 - a.nodes.cap
151+ if reserve_nodes > 0 {
152+ unsafe { a.nodes.grow_cap (reserve_nodes) }
153+ }
154+ reserve_children := a.children.len * 7 / 4 - a.children.cap
155+ if reserve_children > 0 {
156+ unsafe { a.children.grow_cap (reserve_children) }
157+ }
146158 t.transform_all ()
147159 return augmented_used_fns
148160}
@@ -3121,6 +3133,7 @@ fn (mut t Transformer) transform_children_expr(id flat.NodeId, node flat.Node) f
31213133 return id
31223134 }
31233135 mut new_children := []flat.NodeId{cap: int (node.children_count)}
3136+ mut changed := false
31243137 for i in 0 .. node.children_count {
31253138 child_id := t.a.child (& node, i)
31263139 if int (child_id) < 0 {
@@ -3132,13 +3145,24 @@ fn (mut t Transformer) transform_children_expr(id flat.NodeId, node flat.Node) f
31323145 expanded := t.transform_stmt (child_id)
31333146 if expanded.len == 1 {
31343147 new_children << expanded[0 ]
3148+ if expanded[0 ] != child_id {
3149+ changed = true
3150+ }
31353151 } else {
31363152 new_children << t.make_block (expanded)
3153+ changed = true
31373154 }
31383155 } else {
3139- new_children << t.transform_expr (child_id)
3156+ nc := t.transform_expr (child_id)
3157+ new_children << nc
3158+ if nc != child_id {
3159+ changed = true
3160+ }
31403161 }
31413162 }
3163+ if ! changed {
3164+ return id
3165+ }
31423166 start := t.a.children.len
31433167 for nc in new_children {
31443168 t.a.children << nc
@@ -3207,6 +3231,12 @@ fn (mut t Transformer) transform_infix_expr(id flat.NodeId, node flat.Node) flat
32073231 if struct_result := t.transform_transformed_struct_eq (node, new_lhs, new_rhs) {
32083232 return struct_result
32093233 }
3234+ if new_lhs == lhs_id && new_rhs == rhs_id {
3235+ // Nothing was lowered (the common case for plain arithmetic): reuse the original
3236+ // node instead of allocating an identical copy. Under -gc none these copies are
3237+ // never freed, so avoiding them cuts both transform time and peak RAM.
3238+ return id
3239+ }
32103240 start := t.a.children.len
32113241 t.a.children << new_lhs
32123242 t.a.children << new_rhs
@@ -3314,6 +3344,7 @@ fn (mut t Transformer) transform_index_expr(id flat.NodeId, node flat.Node) flat
33143344 return lowered
33153345 }
33163346 mut new_children := []flat.NodeId{cap: int (node.children_count)}
3347+ mut changed := false
33173348 for i in 0 .. node.children_count {
33183349 child_id := t.a.child (& node, i)
33193350 mut new_child := t.transform_expr (child_id)
@@ -3327,8 +3358,19 @@ fn (mut t Transformer) transform_index_expr(id flat.NodeId, node flat.Node) flat
33273358 }
33283359 }
33293360 }
3361+ if new_child != child_id {
3362+ changed = true
3363+ }
33303364 new_children << new_child
33313365 }
3366+ // Children unchanged: update the type annotation in place (applying the same
3367+ // `typ = node.value when empty` fixup the rebuild would) instead of copying the node.
3368+ if ! changed {
3369+ if node.typ.len == 0 && node.value.len > 0 {
3370+ t.a.nodes[int (id)].typ = node.value
3371+ }
3372+ return id
3373+ }
33323374 start := t.a.children.len
33333375 for nc in new_children {
33343376 t.a.children << nc
@@ -3541,19 +3583,31 @@ fn (mut t Transformer) transform_selector_expr(id flat.NodeId, node flat.Node) f
35413583 return t.lower_sum_shared_field_selector (new_base, base_type0 , node.value, shared_typ)
35423584 }
35433585 new_base := t.transform_expr (base_id)
3586+ mut changed := new_base != base_id
35443587 mut new_children := []flat.NodeId{cap: int (node.children_count)}
35453588 new_children << new_base
35463589 for i in 1 .. node.children_count {
35473590 child_id := t.a.child (& node, i)
3548- new_children << t.transform_expr (child_id)
3591+ nc := t.transform_expr (child_id)
3592+ if nc != child_id {
3593+ changed = true
3594+ }
3595+ new_children << nc
3596+ }
3597+ sel_typ := if node.typ.len > 0 { node.typ } else { t.resolve_selector_type (node) }
3598+ base_type := t.node_type (base_id)
3599+ sel_op := if node.op == .arrow || base_type.starts_with ('&' ) { flat.Op.arrow } else { node.op }
3600+ if ! changed && sel_op == node.op {
3601+ // Children and op unchanged; only the type annotation may differ. Update it in
3602+ // place rather than allocating an identical copy (cuts -gc none peak RAM). (`op`
3603+ // is an immutable Node field, so a differing op still needs a fresh node below.)
3604+ t.a.nodes[int (id)].typ = sel_typ
3605+ return id
35493606 }
35503607 start := t.a.children.len
35513608 for nc in new_children {
35523609 t.a.children << nc
35533610 }
3554- sel_typ := if node.typ.len > 0 { node.typ } else { t.resolve_selector_type (node) }
3555- base_type := t.node_type (base_id)
3556- sel_op := if node.op == .arrow || base_type.starts_with ('&' ) { flat.Op.arrow } else { node.op }
35573611 return t.a.add_node (flat.Node{
35583612 kind: .selector
35593613 op: sel_op
@@ -4298,24 +4352,18 @@ fn (mut t Transformer) transform_ident_expr(id flat.NodeId, node flat.Node) flat
42984352 if smartcasted := t.smartcast_ident_value (node.value) {
42994353 return smartcasted
43004354 }
4355+ // Idents are the most common node; re-annotating them in place (rather than
4356+ // allocating a fresh node) avoids cascading rebuilds of every enclosing
4357+ // expression and the associated allocations (critical under -gc none).
43014358 if ! t.in_call_callee {
43024359 if fn_name := t.resolve_fn_value_ident (node.value) {
4303- return t.a.add_node (flat.Node{
4304- kind: .ident
4305- value: fn_name
4306- typ: node.typ
4307- pos: node.pos
4308- })
4360+ t.a.nodes[int (id)].value = fn_name
4361+ return id
43094362 }
43104363 }
43114364 typ := t.var_type (node.value)
4312- if typ.len > 0 {
4313- return t.a.add_node (flat.Node{
4314- kind: .ident
4315- value: node.value
4316- typ: typ
4317- pos: node.pos
4318- })
4365+ if typ.len > 0 && typ != node.typ {
4366+ t.a.nodes[int (id)].typ = typ
43194367 }
43204368 return id
43214369 }
@@ -5418,16 +5466,18 @@ fn (mut t Transformer) build_match_chain(match_expr_id flat.NodeId, orig_expr_id
54185466 branch := t.a.nodes[int (branches[idx])]
54195467 is_else := branch.value == 'else'
54205468
5421- body_start_idx := if is_else { 0 } else { t.count_conds (branch) }
5422- if ! is_else && t.match_branch_all_type_patterns (branch) && t.count_conds (branch) > 1 {
5469+ // count_conds scans the branch's condition children; compute it once and reuse
5470+ // (build_match_chain runs per branch, and the compiler has very large matches).
5471+ n_conds := if is_else { 0 } else { t.count_conds (branch) }
5472+ body_start_idx := n_conds
5473+ if ! is_else && n_conds > 1 && t.match_branch_all_type_patterns (branch) {
54235474 return t.build_match_type_branch_chain (match_expr_id, orig_expr_id, branch, branches, idx,
54245475 0 )
54255476 }
54265477 // Push a smartcast around the body transform when this branch matches a
54275478 // single sum-type variant, so selectors inside the body get narrowed.
54285479 mut sc_pushed := 0
54295480 if ! is_else {
5430- n_conds := t.count_conds (branch)
54315481 if n_conds == 1 {
54325482 cond_val_id := t.a.child (& branch, 0 )
54335483 if variant_name := t.match_type_pattern (cond_val_id) {
0 commit comments