From 75c6347b314c670f2c78c5a2dcf8b7f444756617 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 24 Aug 2026 08:49:53 +0000 Subject: [PATCH 1/5] wip: F --- src/v1/05_emit_rust.dag | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index d842048873b..13a5fc851ea 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -8968,16 +8968,30 @@ fn emit_rust_fold_method_call(method_call_node: Node, fold_accumulator_type: Nod } Absent => "compile_error!(\"missing fold init argument\")" } - let fold_fn = match args |> skip(1) |> first { - Present { value: a } => - emit_typed_fold_lambda(lambda_expr: arg_value(n: a), acc_type_str: lambda_acc_type_str, elem_type_str: fold_elem_type_str, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: fold_emit_info) - Absent => "compile_error!(\"missing fold function argument\")" - } let sharing = language_spec(target: Rust).sharing let elem_unused = match args |> skip(1) |> first { Present { value: a } => fold_lambda_element_unused(lambda_expr: arg_value(n: a), source_indices: scope.type_env.source_indices) Absent => false } + // ONE CONDITION, TWO CONSEQUENCES, AND ONLY ONE OF THEM WAS APPLIED. Dropping `.cloned()` for an + // unused element is a real saving -- the clone is paid per element for a value the body never names. + // But it also changes the ITEM TYPE the iterator yields, from `T` to `&T`, and the lambda's element + // parameter kept its by-value annotation: rustc E0631, `expected fn(Rc<_>, &Rc<_>)`, `found + // fn(Rc<_>, Rc<_>)`, with the emitter's own help line reading "consider adjusting the signature so + // it borrows its argument". The optimization and the signature were derived from the same fact in + // two places, and the second place did not know. + // + // The element is `_` by construction here -- fold_lambda_element_unused is exactly the test that the + // parameter is named `_` -- so the annotation carries no information for a reader and only has to be + // ACCEPTED by rustc. Emitting `_` lets inference supply whichever of `T` / `&T` the iterator yields, + // which keeps the two consequences in agreement without the emitter having to restate the item type. + // This is the same device already used one binding up for elem_render_has_spurious_generic. + let fold_elem_type_str_final = if elem_unused { "_" } else { fold_elem_type_str } + let fold_fn = match args |> skip(1) |> first { + Present { value: a } => + emit_typed_fold_lambda(lambda_expr: arg_value(n: a), acc_type_str: lambda_acc_type_str, elem_type_str: fold_elem_type_str_final, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: fold_emit_info) + Absent => "compile_error!(\"missing fold function argument\")" + } let iter_template = if elem_unused { replace(sharing.iter_owned, ".cloned()", "") } else { sharing.iter_owned } concat(apply_type_template1(template: iter_template, arg0: recv_str), ".fold(", init_str, ", ", fold_fn, ")") } From 4f48f345a2e91c3e5495997584174a4b977d1616 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 24 Aug 2026 09:16:17 +0000 Subject: [PATCH 2/5] Hoist the note to module-item grain: DESIGN 4c admits no in-body annotation --- src/v1/05_emit_rust.dag | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 13a5fc851ea..2e076eb6c60 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -8820,6 +8820,22 @@ fn emit_typed_fold_lambda(lambda_expr: Node, acc_type_str: String, elem_type_str } } +// FOLD'S UNUSED-ELEMENT OPTIMIZATION CHANGES THE ITEM TYPE, AND THE CLOSURE SIGNATURE WAS NOT TOLD. +// Dropping `.cloned()` when the fold lambda's element parameter is `_` is a real saving -- the clone is +// paid per element for a value the body never names. But it also changes the ITEM TYPE the iterator +// yields, from `T` to `&T`, while the lambda's element parameter kept its by-value annotation: rustc +// E0631, `expected fn(Rc<_>, &Rc<_>)`, `found fn(Rc<_>, Rc<_>)`, with rustc's own help line reading +// "consider adjusting the signature so it borrows its argument". One fact, derived in two places, and +// the second place did not know about the first. +// +// fold_elem_type_str_final emits `_` for that parameter's type. The element is `_` BY CONSTRUCTION -- +// fold_lambda_element_unused is exactly the test that the parameter is named `_` -- so the annotation +// carries no information for a reader and only has to be ACCEPTED by rustc. Emitting `_` lets inference +// supply whichever of `T` / `&T` the iterator actually yields, which keeps the two consequences in +// agreement without the emitter restating the item type at the second site. Emitting `&{elem_type}` +// instead would restate it, and would then have to be kept in step with any future change to the strip +// -- reintroducing the shape of this defect while fixing this instance of it. `_` is also the device +// already used for elem_render_has_spurious_generic a few bindings earlier. fn emit_rust_fold_method_call(method_call_node: Node, fold_accumulator_type: Node?, result_type: InferredNode?, receiver: Node, args: List, registry: Map, scope: InferScope, depth: Int, shared_types: Set, emit_info: EmitGraphInfo) -> String { let recv_str = emit_typed_iter_owned_receiver(receiver: receiver, downstream: args, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: emit_info) let elem_type_str = collection_element_type(receiver_type: receiver.inferred, shared_types: shared_types, source_indices: scope.type_env.source_indices, emit_info: emit_info, type_env: scope.type_env) @@ -8973,19 +8989,6 @@ fn emit_rust_fold_method_call(method_call_node: Node, fold_accumulator_type: Nod Present { value: a } => fold_lambda_element_unused(lambda_expr: arg_value(n: a), source_indices: scope.type_env.source_indices) Absent => false } - // ONE CONDITION, TWO CONSEQUENCES, AND ONLY ONE OF THEM WAS APPLIED. Dropping `.cloned()` for an - // unused element is a real saving -- the clone is paid per element for a value the body never names. - // But it also changes the ITEM TYPE the iterator yields, from `T` to `&T`, and the lambda's element - // parameter kept its by-value annotation: rustc E0631, `expected fn(Rc<_>, &Rc<_>)`, `found - // fn(Rc<_>, Rc<_>)`, with the emitter's own help line reading "consider adjusting the signature so - // it borrows its argument". The optimization and the signature were derived from the same fact in - // two places, and the second place did not know. - // - // The element is `_` by construction here -- fold_lambda_element_unused is exactly the test that the - // parameter is named `_` -- so the annotation carries no information for a reader and only has to be - // ACCEPTED by rustc. Emitting `_` lets inference supply whichever of `T` / `&T` the iterator yields, - // which keeps the two consequences in agreement without the emitter having to restate the item type. - // This is the same device already used one binding up for elem_render_has_spurious_generic. let fold_elem_type_str_final = if elem_unused { "_" } else { fold_elem_type_str } let fold_fn = match args |> skip(1) |> first { Present { value: a } => From 9720e9e1352b2f925efbb92ab1181e45f97cb237 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 24 Aug 2026 10:05:36 +0000 Subject: [PATCH 3/5] Force the element position, not the fallback: the resolved type was winning The first attempt changed fold_elem_type_str, the FALLBACK type string, and measured no effect on the board with a live arm (candidate present, change installed, seed clean). Reading the emitted line with the repair installed shows why: |state: Rc, _: Rc| ... lambda_param_type_strs prefers the parameter node's own RESOLVED type and consults the fallback only when there is none. Inference had resolved the unused element to Rc, so the fallback I edited was never read at this site. The override belongs where the parameter string is chosen. emit_typed_fold_lambda now takes elem_borrowed and forces `_` at the element position. Same reasoning as before for `_` over `&{elem_type}`: the element is `_` by construction, so the annotation only has to be accepted by rustc, and restating the item type at the second site would have to be kept in step with the strip forever. Co-Authored-By: Claude Opus 5 (1M context) --- src/v1/05_emit_rust.dag | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/v1/05_emit_rust.dag b/src/v1/05_emit_rust.dag index 2e076eb6c60..730da103628 100644 --- a/src/v1/05_emit_rust.dag +++ b/src/v1/05_emit_rust.dag @@ -8792,7 +8792,7 @@ fn emit_typed_collection_lambda(lambda_expr: Node, elem_type_str: String, regist } } -fn emit_typed_fold_lambda(lambda_expr: Node, acc_type_str: String, elem_type_str: String, registry: Map, scope: InferScope, depth: Int, shared_types: Set, emit_info: EmitGraphInfo) -> String { +fn emit_typed_fold_lambda(lambda_expr: Node, acc_type_str: String, elem_type_str: String, elem_borrowed: Bool, registry: Map, scope: InferScope, depth: Int, shared_types: Set, emit_info: EmitGraphInfo) -> String { match lambda_expr.expr_data { ExprLambda => let ps = lambda_param_names_at(texpr: lambda_expr, source_indices: scope.type_env.source_indices) @@ -8803,7 +8803,10 @@ fn emit_typed_fold_lambda(lambda_expr: Node, acc_type_str: String, elem_type_str let fallback_types = ps |> enumerate |> map(pair => if pair.first == 0 { safe_acc_type } else { elem_type_str } ) - let param_strs = lambda_param_type_strs(params: ps, param_nodes: pn, fallback_types: fallback_types, shared_types: shared_types, source_indices: scope.type_env.source_indices, emit_info: emit_info, fold_acc_uses_fallback: true, type_env: scope.type_env) + let param_strs_raw = lambda_param_type_strs(params: ps, param_nodes: pn, fallback_types: fallback_types, shared_types: shared_types, source_indices: scope.type_env.source_indices, emit_info: emit_info, fold_acc_uses_fallback: true, type_env: scope.type_env) + let param_strs = if elem_borrowed { + param_strs_raw |> enumerate |> map(pair => if pair.first == 1 { "_" } else { pair.second }) + } else { param_strs_raw } let params_str = param_strs |> join(separator: ", ") let lambda_scope = lambda_scope_from_children(scope: scope, params: ps, param_nodes: pn) let body_str = emit_typed_expr(texpr: bd, registry: registry, scope: lambda_scope, depth: depth, shared_types: shared_types, emit_info: emit_info, fuel: 1024) @@ -8828,7 +8831,11 @@ fn emit_typed_fold_lambda(lambda_expr: Node, acc_type_str: String, elem_type_str // "consider adjusting the signature so it borrows its argument". One fact, derived in two places, and // the second place did not know about the first. // -// fold_elem_type_str_final emits `_` for that parameter's type. The element is `_` BY CONSTRUCTION -- +// emit_typed_fold_lambda's elem_borrowed forces `_` at the element position. THE FIRST ATTEMPT AT THIS +// CHANGED THE FALLBACK TYPE STRING AND MEASURED NO EFFECT: lambda_param_type_strs prefers the parameter +// node's own RESOLVED type and only falls back when there is none, so the site still emitted +// `_: Rc`. The override has to sit where the parameter string is chosen, not where the +// fallback is supplied. The element is `_` BY CONSTRUCTION -- // fold_lambda_element_unused is exactly the test that the parameter is named `_` -- so the annotation // carries no information for a reader and only has to be ACCEPTED by rustc. Emitting `_` lets inference // supply whichever of `T` / `&T` the iterator actually yields, which keeps the two consequences in @@ -8989,10 +8996,9 @@ fn emit_rust_fold_method_call(method_call_node: Node, fold_accumulator_type: Nod Present { value: a } => fold_lambda_element_unused(lambda_expr: arg_value(n: a), source_indices: scope.type_env.source_indices) Absent => false } - let fold_elem_type_str_final = if elem_unused { "_" } else { fold_elem_type_str } let fold_fn = match args |> skip(1) |> first { Present { value: a } => - emit_typed_fold_lambda(lambda_expr: arg_value(n: a), acc_type_str: lambda_acc_type_str, elem_type_str: fold_elem_type_str_final, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: fold_emit_info) + emit_typed_fold_lambda(lambda_expr: arg_value(n: a), acc_type_str: lambda_acc_type_str, elem_type_str: fold_elem_type_str, elem_borrowed: elem_unused, registry: registry, scope: scope, depth: depth, shared_types: shared_types, emit_info: fold_emit_info) Absent => "compile_error!(\"missing fold function argument\")" } let iter_template = if elem_unused { replace(sharing.iter_owned, ".cloned()", "") } else { sharing.iter_owned } From a79e973a05f0eab97001e5f35cf2dec772d45cff Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 24 Aug 2026 11:33:16 +0000 Subject: [PATCH 4/5] Regenerate the stage0 mirror for the fold element-position change The compiler-authored bytes for the .dag change in this branch: emit_typed_fold_lambda gains elem_borrowed and forces `_` at the element position. Produced by `claim_executor --required-regen`, not hand-written -- extracted as the candidate's own diff so the mirror stays authored by the compiler. This closes the regen phase, which was correctly refusing a .dag authority edit whose mirror was not committed. Committing it here is the change completing rather than a gate being satisfied: the board measurement is E0631 2 -> 1, primary 100 -> 99, eleven codes byte-identical, generation-2 seed clean, arm verified live via the installed mirror. Co-Authored-By: Claude Opus 5 (1M context) --- src/v1/stage0/src/v1_compiler_emit_rust.rs | 46 +++++++++++++++++----- 1 file changed, 37 insertions(+), 9 deletions(-) diff --git a/src/v1/stage0/src/v1_compiler_emit_rust.rs b/src/v1/stage0/src/v1_compiler_emit_rust.rs index b71ce441aed..cadcca22b36 100644 --- a/src/v1/stage0/src/v1_compiler_emit_rust.rs +++ b/src/v1/stage0/src/v1_compiler_emit_rust.rs @@ -22060,6 +22060,7 @@ pub fn emit_typed_fold_lambda( lambda_expr: Rc, acc_type_str: String, elem_type_str: String, + elem_borrowed: bool, registry: Rc>>, scope: Rc, depth: i64, @@ -22111,7 +22112,7 @@ pub fn emit_typed_fold_lambda( } __result }); - let param_strs = lambda_param_type_strs( + let param_strs_raw = lambda_param_type_strs( ps.clone(), pn.clone(), fallback_types.clone(), @@ -22121,6 +22122,32 @@ pub fn emit_typed_fold_lambda( true, scope.type_env.clone(), ); + let param_strs = if elem_borrowed.clone() { + Rc::new({ + let mut __result = Vec::new(); + for pair in Rc::new( + param_strs_raw + .clone() + .iter() + .cloned() + .enumerate() + .map(|(i, v)| (i as i64, v)) + .collect::>(), + ) + .iter() + .cloned() + { + __result.push(if (pair.0.clone() == 1) { + "_".to_string() + } else { + pair.1.clone() + }); + } + __result + }) + } else { + param_strs_raw.clone() + }; let params_str = param_strs.clone().join(&", ".to_string()); let lambda_scope = lambda_scope_from_children(scope.clone(), ps.clone(), pn.clone()); let body_str = emit_typed_expr( @@ -22594,11 +22621,20 @@ pub fn emit_rust_fold_method_call( }, None => "compile_error!(\"missing fold init argument\")".to_string(), }; + let sharing = language_spec(RenderTarget::Rust).sharing.clone(); + let elem_unused = match args.clone().iter().cloned().skip(1 as usize).next() { + Some(a) => fold_lambda_element_unused( + arg_value(a.clone()), + scope.type_env.clone().source_indices.clone(), + ), + None => false, + }; let fold_fn = match args.clone().iter().cloned().skip(1 as usize).next() { Some(a) => emit_typed_fold_lambda( arg_value(a.clone()), lambda_acc_type_str.clone(), fold_elem_type_str.clone(), + elem_unused.clone(), registry.clone(), scope.clone(), depth.clone(), @@ -22607,14 +22643,6 @@ pub fn emit_rust_fold_method_call( ), None => "compile_error!(\"missing fold function argument\")".to_string(), }; - let sharing = language_spec(RenderTarget::Rust).sharing.clone(); - let elem_unused = match args.clone().iter().cloned().skip(1 as usize).next() { - Some(a) => fold_lambda_element_unused( - arg_value(a.clone()), - scope.type_env.clone().source_indices.clone(), - ), - None => false, - }; let iter_template = if elem_unused.clone() { v1_rt::replace( sharing.iter_owned.clone(), From 4e05115f70d39388c2b46f2a3c7e8ea0d0850f21 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Mon, 24 Aug 2026 12:53:55 +0000 Subject: [PATCH 5/5] Second generation: the fixed point needs two rounds, not one The first mirror commit was the new .dag emitted by the OLD compiler. Committing it means the compiler built from that mirror now carries the new fold behaviour, so when IT regenerates it emits every other module containing an unused-element fold differently -- std_types.rs list_length was the next one out. That is the self-host fixed point, not a one-shot regeneration, and my earlier "one file, 37 insertions" was generation one's delta rather than the fixed point. Measured to convergence remotely rather than assumed: iteration 1 RC=1 first_generation_equal=false drift std_types.rs; iteration 2 RC=0 first_generation_equal=true. Two rounds, stable, no oscillation. The generation-2 delta is one line -- `|acc: i64, _: _|` becomes `|acc: i64, _|` -- which is the same element-position override reaching a module whose fold the first generation had not yet re-emitted. Co-Authored-By: Claude Opus 5 (1M context) --- src/v1/stage0/src/std_types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/v1/stage0/src/std_types.rs b/src/v1/stage0/src/std_types.rs index 1d767804ca4..44e4fedbfe9 100644 --- a/src/v1/stage0/src/std_types.rs +++ b/src/v1/stage0/src/std_types.rs @@ -224,7 +224,7 @@ pub type Set = Rc>; pub type Map = Rc>; pub fn list_length(items: Rc>) -> i64 { - items.iter().fold(0, |acc: i64, _: _| (acc + 1)) + items.iter().fold(0, |acc: i64, _| (acc + 1)) } pub type CommitSha = String;