From 0dc288c86620812b5f7872e8ab19efd7c9197e71 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 15 Jul 2026 03:50:03 +0000 Subject: [PATCH 1/6] WIP: v1 resolve is still superlinear --- src/v1/04_env.dag | 2 +- src/v1/04_infer.dag | 26 ++------------------------ 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/src/v1/04_env.dag b/src/v1/04_env.dag index 8731f415dd2..0e33f2cf502 100644 --- a/src/v1/04_env.dag +++ b/src/v1/04_env.dag @@ -76,7 +76,7 @@ fn scope_push(scope: Scope, binding: ScopeBinding) -> Scope { Scope { locals: map_insert(scope.locals, binding.name, binding) } } -data ancestry_cache_sharing_dissolution_trigger: String = "🟡 dissolve-on: single-parent ancestry_str_bindings borrows parent.type_env_cache.str_bindings (Rc-shared visible surface) instead of map_merge(parent.ancestry_str_bindings, parent.str_bindings) per module — kills O(M²) SipHash/map_merge churn on import chains; full SymbolIndex authority is the follow-on (type-env-single-authority-design.md)." +data ancestry_cache_sharing_dissolution_trigger: String = "DISSOLVED (v1-run-stability-throughline, single-parent ancestry share): build_type_env / build_type_env_unresolved formerly recomputed ancestry_str_bindings as a fresh per-module map_merge(parent.interface.env.ancestry_str_bindings, parent.interface.env.str_bindings) in the single-import case — provably byte-identical to parent.interface.cache.str_bindings (interface.cache.str_bindings == map_merge(env.ancestry_str_bindings, env.str_bindings) by construction at both build sites, line 5973/6081), which is exactly ancestry_cache.str_bindings when count==1 (union of one parent cache is that cache, Rc-shared unchanged). So all three branches resolved to ancestry_cache.str_bindings and the conditional collapsed to `let ancestry_str_bindings = ancestry_cache.str_bindings` — one Rc-borrow, no fresh HAMT per module. Kills the O(M²) SipHash/map_merge churn on single-import chains and lets chains Rc-share the parent's already-retained str_bindings spine (§2 redundancy, §6 bare-minimum-cost; construction not validation — the recompute is now unwritable). Full SymbolIndex authority is the follow-on (type-env-single-authority-design.md)." type TypeEnvCache { deps_map: Map> diff --git a/src/v1/04_infer.dag b/src/v1/04_infer.dag index 10f12594340..0353482054b 100644 --- a/src/v1/04_infer.dag +++ b/src/v1/04_infer.dag @@ -5926,18 +5926,7 @@ fn build_type_env(module: ResolvedModule, parent_index: Map Absent => empty_map() } let merged_inductive_fields = merge_inductive_fields(left: parent_inductive_fields, right: local_inductive_fields) - let ancestry_str_bindings = if module.resolved_imports |> count == 1 { - match module.resolved_imports |> first { - Present { value: imp } => - match map_get(parent_index, imp.module_path) { - Present { value: parent_mod } => map_merge(parent_mod.interface.env.ancestry_str_bindings, parent_mod.interface.env.str_bindings) - Absent => ancestry_cache.str_bindings - } - Absent => ancestry_cache.str_bindings - } - } else { - ancestry_cache.str_bindings - } + let ancestry_str_bindings = ancestry_cache.str_bindings let svn_local = fold(map_keys(local_str_bindings), init: empty_map(), f: (acc, n) => map_insert(acc, n, true)) let svn_kernel = fold(map_keys(kernel_type_set), init: svn_local, f: (acc, n) => map_insert(acc, n, true)) let source_visible_names = fold(module.resolved_imports, init: svn_kernel, f: (acc, imp) => @@ -6062,18 +6051,7 @@ fn build_type_env_unresolved(module: ResolvedModule, parent_index: Map empty_map() } let merged_inductive_fields = merge_inductive_fields(left: parent_inductive_fields, right: local_inductive_fields) - let ancestry_str_bindings = if module.resolved_imports |> count == 1 { - match module.resolved_imports |> first { - Present { value: imp } => - match map_get(parent_index, imp.module_path) { - Present { value: parent_mod } => map_merge(parent_mod.interface.env.ancestry_str_bindings, parent_mod.interface.env.str_bindings) - Absent => ancestry_cache.str_bindings - } - Absent => ancestry_cache.str_bindings - } - } else { - ancestry_cache.str_bindings - } + let ancestry_str_bindings = ancestry_cache.str_bindings let visible_str_bindings = map_merge(ancestry_str_bindings, local_str_bindings) let unresolved_env = TypeEnv { bindings: local_bindings, str_bindings: local_str_bindings, ancestry_str_bindings: ancestry_str_bindings, parents: scope_parents, recursive_types: cycle_set, recursive_type_set: cross_type_set, inductive_fields: merged_inductive_fields, source_indices: source_indices, intern_table: intern_table, source_visible_names: empty_map(), global_bare: empty_map(), symbol_index: empty_symbol_index() } let type_env_cache = TypeEnvCache { From fe253d4bbb42bd825e85223870803f675fd1313a Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 15 Jul 2026 04:00:31 +0000 Subject: [PATCH 2/6] WIP: v1 resolve is still superlinear --- src/v1/04_env.dag | 2 +- src/v1/04_infer.dag | 26 +++- src/v1/stage0/src/cli_run.rs | 288 ++++++++++++++++++----------------- 3 files changed, 170 insertions(+), 146 deletions(-) diff --git a/src/v1/04_env.dag b/src/v1/04_env.dag index 0e33f2cf502..8731f415dd2 100644 --- a/src/v1/04_env.dag +++ b/src/v1/04_env.dag @@ -76,7 +76,7 @@ fn scope_push(scope: Scope, binding: ScopeBinding) -> Scope { Scope { locals: map_insert(scope.locals, binding.name, binding) } } -data ancestry_cache_sharing_dissolution_trigger: String = "DISSOLVED (v1-run-stability-throughline, single-parent ancestry share): build_type_env / build_type_env_unresolved formerly recomputed ancestry_str_bindings as a fresh per-module map_merge(parent.interface.env.ancestry_str_bindings, parent.interface.env.str_bindings) in the single-import case — provably byte-identical to parent.interface.cache.str_bindings (interface.cache.str_bindings == map_merge(env.ancestry_str_bindings, env.str_bindings) by construction at both build sites, line 5973/6081), which is exactly ancestry_cache.str_bindings when count==1 (union of one parent cache is that cache, Rc-shared unchanged). So all three branches resolved to ancestry_cache.str_bindings and the conditional collapsed to `let ancestry_str_bindings = ancestry_cache.str_bindings` — one Rc-borrow, no fresh HAMT per module. Kills the O(M²) SipHash/map_merge churn on single-import chains and lets chains Rc-share the parent's already-retained str_bindings spine (§2 redundancy, §6 bare-minimum-cost; construction not validation — the recompute is now unwritable). Full SymbolIndex authority is the follow-on (type-env-single-authority-design.md)." +data ancestry_cache_sharing_dissolution_trigger: String = "🟡 dissolve-on: single-parent ancestry_str_bindings borrows parent.type_env_cache.str_bindings (Rc-shared visible surface) instead of map_merge(parent.ancestry_str_bindings, parent.str_bindings) per module — kills O(M²) SipHash/map_merge churn on import chains; full SymbolIndex authority is the follow-on (type-env-single-authority-design.md)." type TypeEnvCache { deps_map: Map> diff --git a/src/v1/04_infer.dag b/src/v1/04_infer.dag index 0353482054b..10f12594340 100644 --- a/src/v1/04_infer.dag +++ b/src/v1/04_infer.dag @@ -5926,7 +5926,18 @@ fn build_type_env(module: ResolvedModule, parent_index: Map Absent => empty_map() } let merged_inductive_fields = merge_inductive_fields(left: parent_inductive_fields, right: local_inductive_fields) - let ancestry_str_bindings = ancestry_cache.str_bindings + let ancestry_str_bindings = if module.resolved_imports |> count == 1 { + match module.resolved_imports |> first { + Present { value: imp } => + match map_get(parent_index, imp.module_path) { + Present { value: parent_mod } => map_merge(parent_mod.interface.env.ancestry_str_bindings, parent_mod.interface.env.str_bindings) + Absent => ancestry_cache.str_bindings + } + Absent => ancestry_cache.str_bindings + } + } else { + ancestry_cache.str_bindings + } let svn_local = fold(map_keys(local_str_bindings), init: empty_map(), f: (acc, n) => map_insert(acc, n, true)) let svn_kernel = fold(map_keys(kernel_type_set), init: svn_local, f: (acc, n) => map_insert(acc, n, true)) let source_visible_names = fold(module.resolved_imports, init: svn_kernel, f: (acc, imp) => @@ -6051,7 +6062,18 @@ fn build_type_env_unresolved(module: ResolvedModule, parent_index: Map empty_map() } let merged_inductive_fields = merge_inductive_fields(left: parent_inductive_fields, right: local_inductive_fields) - let ancestry_str_bindings = ancestry_cache.str_bindings + let ancestry_str_bindings = if module.resolved_imports |> count == 1 { + match module.resolved_imports |> first { + Present { value: imp } => + match map_get(parent_index, imp.module_path) { + Present { value: parent_mod } => map_merge(parent_mod.interface.env.ancestry_str_bindings, parent_mod.interface.env.str_bindings) + Absent => ancestry_cache.str_bindings + } + Absent => ancestry_cache.str_bindings + } + } else { + ancestry_cache.str_bindings + } let visible_str_bindings = map_merge(ancestry_str_bindings, local_str_bindings) let unresolved_env = TypeEnv { bindings: local_bindings, str_bindings: local_str_bindings, ancestry_str_bindings: ancestry_str_bindings, parents: scope_parents, recursive_types: cycle_set, recursive_type_set: cross_type_set, inductive_fields: merged_inductive_fields, source_indices: source_indices, intern_table: intern_table, source_visible_names: empty_map(), global_bare: empty_map(), symbol_index: empty_symbol_index() } let type_env_cache = TypeEnvCache { diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 3f5d3c7f12c..1a414380cac 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -7470,8 +7470,7 @@ fn test_claim_selection_has_node_corpus( ]), "RoundTripClaim" => all_nodes(&[variant_field(ctx, fields, "input").expect("input")]), "DiagnosticClaim" => { - variant_field(ctx, fields, "input") - .is_some_and(|v| value_is_node(v, ctx)) + variant_field(ctx, fields, "input").is_some_and(|v| value_is_node(v, ctx)) } _ => false, } @@ -8794,157 +8793,160 @@ pub fn run_discovery_corpus_with_options( spawn_target_width, ); } - let queue: std::sync::Arc>>> = - std::sync::Arc::new(Mutex::new( - groups - .into_iter() - .map(|g| g.iter().map(|&i| rows[i].clone()).collect()) - .collect(), - )); - let abort = std::sync::Arc::new(AtomicBool::new(false)); - let source_roots_owned = source_roots.to_vec(); - let selection_for_workers = options.node_frontier_selection; - let fast_lane_budget_for_workers = options.fast_lane_eval_budget_ms; - let mut handles = Vec::new(); - let mut worker_ordinal: usize = 0; - loop { - if abort.load(Ordering::SeqCst) || queue.lock().unwrap().is_empty() { - break; - } - match governor.try_admit() { - crate::memory_governor::AdmitDecision::Admit { .. } => {} - crate::memory_governor::AdmitDecision::Hold(_) => { - std::thread::sleep(std::time::Duration::from_millis(150)); - continue; - } - } - let queue_for_worker = queue.clone(); - let abort_for_worker = abort.clone(); - let governor_for_worker = governor.clone(); - let roots = source_roots_owned.clone(); - let seeds = diff_edits.clone(); - let paths = changed_paths.clone(); - let keys = whole_tree_published_keys.clone(); - let spawn_target_width = governor.current_target_width(); - let cross_worker_store_for_worker = if spawn_target_width > 1 { - Some(cross_worker_store.clone()) - } else { - None - }; - if worker_ordinal == 0 && spawn_target_width <= 1 { - eprintln!( + let queue: std::sync::Arc>>> = + std::sync::Arc::new(Mutex::new( + groups + .into_iter() + .map(|g| g.iter().map(|&i| rows[i].clone()).collect()) + .collect(), + )); + let abort = std::sync::Arc::new(AtomicBool::new(false)); + let source_roots_owned = source_roots.to_vec(); + let selection_for_workers = options.node_frontier_selection; + let fast_lane_budget_for_workers = options.fast_lane_eval_budget_ms; + let mut handles = Vec::new(); + let mut worker_ordinal: usize = 0; + loop { + if abort.load(Ordering::SeqCst) || queue.lock().unwrap().is_empty() { + break; + } + match governor.try_admit() { + crate::memory_governor::AdmitDecision::Admit { .. } => {} + crate::memory_governor::AdmitDecision::Hold(_) => { + std::thread::sleep(std::time::Duration::from_millis(150)); + continue; + } + } + let queue_for_worker = queue.clone(); + let abort_for_worker = abort.clone(); + let governor_for_worker = governor.clone(); + let roots = source_roots_owned.clone(); + let seeds = diff_edits.clone(); + let paths = changed_paths.clone(); + let keys = whole_tree_published_keys.clone(); + let spawn_target_width = governor.current_target_width(); + let cross_worker_store_for_worker = if spawn_target_width > 1 { + Some(cross_worker_store.clone()) + } else { + None + }; + if worker_ordinal == 0 && spawn_target_width <= 1 { + eprintln!( "run_discovery_corpus: cross_worker_store withheld (governor target_width={spawn_target_width}) — per-index typed cache until width > 1" ); - } - // Narration style for this worker: shard_id = spawn ordinal (a stable hue in the - // interleaved stream); spawn-time target width > 1 shows the ▎shard tag — a width-1 - // admission window has no interleaving to disambiguate. - let style = ShardStyle { - shard_id: worker_ordinal, - shard_count: governor.current_target_width(), - color: floor_color, - stream: floor_stream, - }; - worker_ordinal += 1; - handles.push(std::thread::spawn( - move || -> Result, String> { - // The slot was granted by try_admit on the pump thread; the guard owns the - // matching release so a panicking worker cannot wedge admissions. - let mut slot = crate::memory_governor::AdmittedSlot::from_admitted( - governor_for_worker.clone(), - ); - // Process-scoped typed_module_cache when governor width > 1; private cold - // index at width=1 (CI budget — cross-worker-typecheck-share-design §7). - let index = match cross_worker_store_for_worker { - Some(store) => build_multi_entry_index_with_shared_caches(&roots, store), - None => build_multi_entry_index(&roots), + } + // Narration style for this worker: shard_id = spawn ordinal (a stable hue in the + // interleaved stream); spawn-time target width > 1 shows the ▎shard tag — a width-1 + // admission window has no interleaving to disambiguate. + let style = ShardStyle { + shard_id: worker_ordinal, + shard_count: governor.current_target_width(), + color: floor_color, + stream: floor_stream, }; - let runner = if selection_for_workers != NodeFrontierSelectionMode::Off { - match resolve_entry_with_index(&index, FLOOR_RUNNER_ENTRY) { - Ok((graph, source_indices)) => { - Some(make_eval_context(&graph, source_indices, execution_mode)) - } - Err(msg) => { - abort_for_worker.store(true, Ordering::SeqCst); - return Err(format!( - "floor runner resolve failed in worker ({msg}) — declared \ + worker_ordinal += 1; + handles.push(std::thread::spawn( + move || -> Result, String> { + // The slot was granted by try_admit on the pump thread; the guard owns the + // matching release so a panicking worker cannot wedge admissions. + let mut slot = crate::memory_governor::AdmittedSlot::from_admitted( + governor_for_worker.clone(), + ); + // Process-scoped typed_module_cache when governor width > 1; private cold + // index at width=1 (CI budget — cross-worker-typecheck-share-design §7). + let index = match cross_worker_store_for_worker { + Some(store) => { + build_multi_entry_index_with_shared_caches(&roots, store) + } + None => build_multi_entry_index(&roots), + }; + let runner = if selection_for_workers != NodeFrontierSelectionMode::Off { + match resolve_entry_with_index(&index, FLOOR_RUNNER_ENTRY) { + Ok((graph, source_indices)) => { + Some(make_eval_context(&graph, source_indices, execution_mode)) + } + Err(msg) => { + abort_for_worker.store(true, Ordering::SeqCst); + return Err(format!( + "floor runner resolve failed in worker ({msg}) — declared \ affected-set machinery must resolve; no silent \ run-everything fallback" - )); - } - } - } else { - None - }; - // The front-loaded admission cost (index build + runner resolve) has - // landed and is visible to the creep signals: unblock admission pacing. - slot.note_first_cost_paid(); - let mut worker_summaries = Vec::new(); - loop { - // Multiplicative decrease drains here: a worker between groups retires - // when concurrency sits above the (possibly just-halved) window. - if governor_for_worker.should_retire() - || abort_for_worker.load(Ordering::SeqCst) - { - break; - } - let Some(group_rows) = queue_for_worker.lock().unwrap().pop_front() else { - break; - }; - match run_discovery_rows( - &group_rows, - &index, - execution_mode, - selection_for_workers, - &paths, - &seeds, - runner.as_ref(), - keys.clone(), - fast_lane_budget_for_workers, - style, - ) { - Ok(summary) => { - worker_summaries.push(summary); - slot.note_unit_complete(); - } - Err(e) => { - abort_for_worker.store(true, Ordering::SeqCst); - return Err(e); + )); + } + } + } else { + None + }; + // The front-loaded admission cost (index build + runner resolve) has + // landed and is visible to the creep signals: unblock admission pacing. + slot.note_first_cost_paid(); + let mut worker_summaries = Vec::new(); + loop { + // Multiplicative decrease drains here: a worker between groups retires + // when concurrency sits above the (possibly just-halved) window. + if governor_for_worker.should_retire() + || abort_for_worker.load(Ordering::SeqCst) + { + break; + } + let Some(group_rows) = queue_for_worker.lock().unwrap().pop_front() + else { + break; + }; + match run_discovery_rows( + &group_rows, + &index, + execution_mode, + selection_for_workers, + &paths, + &seeds, + runner.as_ref(), + keys.clone(), + fast_lane_budget_for_workers, + style, + ) { + Ok(summary) => { + worker_summaries.push(summary); + slot.note_unit_complete(); + } + Err(e) => { + abort_for_worker.store(true, Ordering::SeqCst); + return Err(e); + } + } } - } + Ok(worker_summaries) + }, + )); + } + let mut summaries = Vec::new(); + let mut first_err: Option = None; + for handle in handles { + match handle + .join() + .map_err(|_| "discovery corpus worker thread panicked".to_string()) + { + Ok(Ok(worker_summaries)) => summaries.extend(worker_summaries), + Ok(Err(e)) | Err(e) => first_err = first_err.or(Some(e)), } - Ok(worker_summaries) - }, - )); - } - let mut summaries = Vec::new(); - let mut first_err: Option = None; - for handle in handles { - match handle - .join() - .map_err(|_| "discovery corpus worker thread panicked".to_string()) - { - Ok(Ok(worker_summaries)) => summaries.extend(worker_summaries), - Ok(Err(e)) | Err(e) => first_err = first_err.or(Some(e)), - } - } - if let Some(e) = first_err { - return Err(e); - } - // The pump exits when the queue is empty OR on abort; with no error the queue must be - // fully drained (workers only exit early on retire/abort, and the pump re-admits while - // items remain), so an undrained queue here is a scheduler bug — refuse, never under-run. - let leftover = queue.lock().unwrap().len(); - if leftover > 0 { - return Err(format!( + } + if let Some(e) = first_err { + return Err(e); + } + // The pump exits when the queue is empty OR on abort; with no error the queue must be + // fully drained (workers only exit early on retire/abort, and the pump re-admits while + // items remain), so an undrained queue here is a scheduler bug — refuse, never under-run. + let leftover = queue.lock().unwrap().len(); + if leftover > 0 { + return Err(format!( "adaptive discovery pool exited with {leftover} undrained entry-group(s) and no \ worker error — scheduler invariant violated; refusing a partial corpus" )); - } - Ok(attach_deferred_discovery_rows( - merge_discovery_summaries(summaries), - deferred_rows, - )) + } + Ok(attach_deferred_discovery_rows( + merge_discovery_summaries(summaries), + deferred_rows, + )) } }; } From d11a0856650056810535b920f918016bcb3c1f54 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 15 Jul 2026 04:03:53 +0000 Subject: [PATCH 3/6] v1 resolve: collapse redundant per-module ancestry_str_bindings recompute to Rc-borrow build_type_env / build_type_env_unresolved recomputed ancestry_str_bindings as a fresh per-module map_merge(parent.interface.env.ancestry, parent.interface.env.str) in the single-import case. That value is byte-identical to ancestry_cache.str_bindings (already Rc-shared): interface.cache.str_bindings == map_merge(env.ancestry, env.str) by construction, and for count==1 the union of one parent cache IS that cache, Rc-shared unchanged. All three branches resolved to ancestry_cache.str_bindings, so the conditional collapses to one Rc-borrow -- killing the per-module fresh-HAMT allocation and the O(M^2) map_merge/SipHash churn on single-import chains (the 04_env.dag line-79 dissolution trigger). Regenerated seed. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/v1/04_env.dag | 2 +- src/v1/04_infer.dag | 26 +---------- src/v1/stage0/src/v1_compiler_infer.rs | 54 +--------------------- src/v1/stage0/src/v1_compiler_infer_env.rs | 2 +- 4 files changed, 6 insertions(+), 78 deletions(-) diff --git a/src/v1/04_env.dag b/src/v1/04_env.dag index 8731f415dd2..0e33f2cf502 100644 --- a/src/v1/04_env.dag +++ b/src/v1/04_env.dag @@ -76,7 +76,7 @@ fn scope_push(scope: Scope, binding: ScopeBinding) -> Scope { Scope { locals: map_insert(scope.locals, binding.name, binding) } } -data ancestry_cache_sharing_dissolution_trigger: String = "🟡 dissolve-on: single-parent ancestry_str_bindings borrows parent.type_env_cache.str_bindings (Rc-shared visible surface) instead of map_merge(parent.ancestry_str_bindings, parent.str_bindings) per module — kills O(M²) SipHash/map_merge churn on import chains; full SymbolIndex authority is the follow-on (type-env-single-authority-design.md)." +data ancestry_cache_sharing_dissolution_trigger: String = "DISSOLVED (v1-run-stability-throughline, single-parent ancestry share): build_type_env / build_type_env_unresolved formerly recomputed ancestry_str_bindings as a fresh per-module map_merge(parent.interface.env.ancestry_str_bindings, parent.interface.env.str_bindings) in the single-import case — provably byte-identical to parent.interface.cache.str_bindings (interface.cache.str_bindings == map_merge(env.ancestry_str_bindings, env.str_bindings) by construction at both build sites, line 5973/6081), which is exactly ancestry_cache.str_bindings when count==1 (union of one parent cache is that cache, Rc-shared unchanged). So all three branches resolved to ancestry_cache.str_bindings and the conditional collapsed to `let ancestry_str_bindings = ancestry_cache.str_bindings` — one Rc-borrow, no fresh HAMT per module. Kills the O(M²) SipHash/map_merge churn on single-import chains and lets chains Rc-share the parent's already-retained str_bindings spine (§2 redundancy, §6 bare-minimum-cost; construction not validation — the recompute is now unwritable). Full SymbolIndex authority is the follow-on (type-env-single-authority-design.md)." type TypeEnvCache { deps_map: Map> diff --git a/src/v1/04_infer.dag b/src/v1/04_infer.dag index 10f12594340..0353482054b 100644 --- a/src/v1/04_infer.dag +++ b/src/v1/04_infer.dag @@ -5926,18 +5926,7 @@ fn build_type_env(module: ResolvedModule, parent_index: Map Absent => empty_map() } let merged_inductive_fields = merge_inductive_fields(left: parent_inductive_fields, right: local_inductive_fields) - let ancestry_str_bindings = if module.resolved_imports |> count == 1 { - match module.resolved_imports |> first { - Present { value: imp } => - match map_get(parent_index, imp.module_path) { - Present { value: parent_mod } => map_merge(parent_mod.interface.env.ancestry_str_bindings, parent_mod.interface.env.str_bindings) - Absent => ancestry_cache.str_bindings - } - Absent => ancestry_cache.str_bindings - } - } else { - ancestry_cache.str_bindings - } + let ancestry_str_bindings = ancestry_cache.str_bindings let svn_local = fold(map_keys(local_str_bindings), init: empty_map(), f: (acc, n) => map_insert(acc, n, true)) let svn_kernel = fold(map_keys(kernel_type_set), init: svn_local, f: (acc, n) => map_insert(acc, n, true)) let source_visible_names = fold(module.resolved_imports, init: svn_kernel, f: (acc, imp) => @@ -6062,18 +6051,7 @@ fn build_type_env_unresolved(module: ResolvedModule, parent_index: Map empty_map() } let merged_inductive_fields = merge_inductive_fields(left: parent_inductive_fields, right: local_inductive_fields) - let ancestry_str_bindings = if module.resolved_imports |> count == 1 { - match module.resolved_imports |> first { - Present { value: imp } => - match map_get(parent_index, imp.module_path) { - Present { value: parent_mod } => map_merge(parent_mod.interface.env.ancestry_str_bindings, parent_mod.interface.env.str_bindings) - Absent => ancestry_cache.str_bindings - } - Absent => ancestry_cache.str_bindings - } - } else { - ancestry_cache.str_bindings - } + let ancestry_str_bindings = ancestry_cache.str_bindings let visible_str_bindings = map_merge(ancestry_str_bindings, local_str_bindings) let unresolved_env = TypeEnv { bindings: local_bindings, str_bindings: local_str_bindings, ancestry_str_bindings: ancestry_str_bindings, parents: scope_parents, recursive_types: cycle_set, recursive_type_set: cross_type_set, inductive_fields: merged_inductive_fields, source_indices: source_indices, intern_table: intern_table, source_visible_names: empty_map(), global_bare: empty_map(), symbol_index: empty_symbol_index() } let type_env_cache = TypeEnvCache { diff --git a/src/v1/stage0/src/v1_compiler_infer.rs b/src/v1/stage0/src/v1_compiler_infer.rs index e5924727516..d7ad28518ae 100644 --- a/src/v1/stage0/src/v1_compiler_infer.rs +++ b/src/v1/stage0/src/v1_compiler_infer.rs @@ -12994,32 +12994,7 @@ pub fn build_type_env( parent_inductive_fields.clone(), local_inductive_fields.clone(), ); - let ancestry_str_bindings = if ((module.resolved_imports.clone().len() as i64) == 1) { - match module.resolved_imports.clone().first().cloned() { - Some(imp) => match v1_rt::map_get(&parent_index, imp.module_path.clone()) { - Some(parent_mod) => v1_rt::rc_map_merge( - parent_mod - .interface - .clone() - .env - .clone() - .ancestry_str_bindings - .clone(), - parent_mod - .interface - .clone() - .env - .clone() - .str_bindings - .clone(), - ), - None => ancestry_cache.str_bindings.clone(), - }, - None => ancestry_cache.str_bindings.clone(), - } - } else { - ancestry_cache.str_bindings.clone() - }; + let ancestry_str_bindings = ancestry_cache.str_bindings.clone(); let svn_local = Rc::new(v1_rt::map_keys(&local_str_bindings)) .iter() .cloned() @@ -13562,32 +13537,7 @@ pub fn build_type_env_unresolved( parent_inductive_fields.clone(), local_inductive_fields.clone(), ); - let ancestry_str_bindings = if ((module.resolved_imports.clone().len() as i64) == 1) { - match module.resolved_imports.clone().first().cloned() { - Some(imp) => match v1_rt::map_get(&parent_index, imp.module_path.clone()) { - Some(parent_mod) => v1_rt::rc_map_merge( - parent_mod - .interface - .clone() - .env - .clone() - .ancestry_str_bindings - .clone(), - parent_mod - .interface - .clone() - .env - .clone() - .str_bindings - .clone(), - ), - None => ancestry_cache.str_bindings.clone(), - }, - None => ancestry_cache.str_bindings.clone(), - } - } else { - ancestry_cache.str_bindings.clone() - }; + let ancestry_str_bindings = ancestry_cache.str_bindings.clone(); let visible_str_bindings = v1_rt::rc_map_merge(ancestry_str_bindings.clone(), local_str_bindings.clone()); let unresolved_env = Rc::new(TypeEnv { diff --git a/src/v1/stage0/src/v1_compiler_infer_env.rs b/src/v1/stage0/src/v1_compiler_infer_env.rs index 68f239512dd..329b3d5c559 100644 --- a/src/v1/stage0/src/v1_compiler_infer_env.rs +++ b/src/v1/stage0/src/v1_compiler_infer_env.rs @@ -146,7 +146,7 @@ pub fn scope_push(scope: Rc, binding: Rc) -> Rc { pub fn ancestry_cache_sharing_dissolution_trigger() -> String { thread_local! { static CACHED: String = { - "🟡 dissolve-on: single-parent ancestry_str_bindings borrows parent.type_env_cache.str_bindings (Rc-shared visible surface) instead of map_merge(parent.ancestry_str_bindings, parent.str_bindings) per module — kills O(M²) SipHash/map_merge churn on import chains; full SymbolIndex authority is the follow-on (type-env-single-authority-design.md).".to_string() + "DISSOLVED (v1-run-stability-throughline, single-parent ancestry share): build_type_env / build_type_env_unresolved formerly recomputed ancestry_str_bindings as a fresh per-module map_merge(parent.interface.env.ancestry_str_bindings, parent.interface.env.str_bindings) in the single-import case — provably byte-identical to parent.interface.cache.str_bindings (interface.cache.str_bindings == map_merge(env.ancestry_str_bindings, env.str_bindings) by construction at both build sites, line 5973/6081), which is exactly ancestry_cache.str_bindings when count==1 (union of one parent cache is that cache, Rc-shared unchanged). So all three branches resolved to ancestry_cache.str_bindings and the conditional collapsed to `let ancestry_str_bindings = ancestry_cache.str_bindings` — one Rc-borrow, no fresh HAMT per module. Kills the O(M²) SipHash/map_merge churn on single-import chains and lets chains Rc-share the parent's already-retained str_bindings spine (§2 redundancy, §6 bare-minimum-cost; construction not validation — the recompute is now unwritable). Full SymbolIndex authority is the follow-on (type-env-single-authority-design.md).".to_string() }; } CACHED.with(|c: &String| c.clone()) From 044ba5a9384e6cf8ee0bc5273d758975cd61c6b2 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 15 Jul 2026 13:19:05 +0000 Subject: [PATCH 4/6] Regen cli_run.rs after main merge: clear generated-artifact drift The auto-committer's stale merge of an OLD origin/main left the committed stage0 seed inconsistent with the merged .dag authority, reding generated_artifact_drift_gate on CI. Merged current origin/main and regenerated stage0 from the merged tree; only cli_run.rs changed (main's committed copy was itself drifted from a fresh regen). regen --verify: regen_divergence_count=0. The ancestry_str_bindings collapse is unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/v1/stage0/src/cli_run.rs | 292 +++++++++++++++++------------------ 1 file changed, 146 insertions(+), 146 deletions(-) diff --git a/src/v1/stage0/src/cli_run.rs b/src/v1/stage0/src/cli_run.rs index 7bf71fe6b26..c80a55a946c 100644 --- a/src/v1/stage0/src/cli_run.rs +++ b/src/v1/stage0/src/cli_run.rs @@ -4863,9 +4863,7 @@ fn wall_budget_completion_outcome( wall_nanos: u128, ) -> ClaimOutcome { match (budget, outcome) { - (Some(budget_ms), ClaimOutcome::Pass) - if wall_nanos > u128::from(budget_ms) * 1_000_000 => - { + (Some(budget_ms), ClaimOutcome::Pass) if wall_nanos > u128::from(budget_ms) * 1_000_000 => { ClaimOutcome::RuntimeError { message: format!( "{}", @@ -7533,8 +7531,7 @@ fn test_claim_selection_has_node_corpus( ]), "RoundTripClaim" => all_nodes(&[variant_field(ctx, fields, "input").expect("input")]), "DiagnosticClaim" => { - variant_field(ctx, fields, "input") - .is_some_and(|v| value_is_node(v, ctx)) + variant_field(ctx, fields, "input").is_some_and(|v| value_is_node(v, ctx)) } _ => false, } @@ -8857,157 +8854,160 @@ pub fn run_discovery_corpus_with_options( spawn_target_width, ); } - let queue: std::sync::Arc>>> = - std::sync::Arc::new(Mutex::new( - groups - .into_iter() - .map(|g| g.iter().map(|&i| rows[i].clone()).collect()) - .collect(), - )); - let abort = std::sync::Arc::new(AtomicBool::new(false)); - let source_roots_owned = source_roots.to_vec(); - let selection_for_workers = options.node_frontier_selection; - let budget_policy_for_workers = options.witness_budget_policy(); - let mut handles = Vec::new(); - let mut worker_ordinal: usize = 0; - loop { - if abort.load(Ordering::SeqCst) || queue.lock().unwrap().is_empty() { - break; - } - match governor.try_admit() { - crate::memory_governor::AdmitDecision::Admit { .. } => {} - crate::memory_governor::AdmitDecision::Hold(_) => { - std::thread::sleep(std::time::Duration::from_millis(150)); - continue; - } - } - let queue_for_worker = queue.clone(); - let abort_for_worker = abort.clone(); - let governor_for_worker = governor.clone(); - let roots = source_roots_owned.clone(); - let seeds = diff_edits.clone(); - let paths = changed_paths.clone(); - let keys = whole_tree_published_keys.clone(); - let spawn_target_width = governor.current_target_width(); - let cross_worker_store_for_worker = if spawn_target_width > 1 { - Some(cross_worker_store.clone()) - } else { - None - }; - if worker_ordinal == 0 && spawn_target_width <= 1 { - eprintln!( + let queue: std::sync::Arc>>> = + std::sync::Arc::new(Mutex::new( + groups + .into_iter() + .map(|g| g.iter().map(|&i| rows[i].clone()).collect()) + .collect(), + )); + let abort = std::sync::Arc::new(AtomicBool::new(false)); + let source_roots_owned = source_roots.to_vec(); + let selection_for_workers = options.node_frontier_selection; + let budget_policy_for_workers = options.witness_budget_policy(); + let mut handles = Vec::new(); + let mut worker_ordinal: usize = 0; + loop { + if abort.load(Ordering::SeqCst) || queue.lock().unwrap().is_empty() { + break; + } + match governor.try_admit() { + crate::memory_governor::AdmitDecision::Admit { .. } => {} + crate::memory_governor::AdmitDecision::Hold(_) => { + std::thread::sleep(std::time::Duration::from_millis(150)); + continue; + } + } + let queue_for_worker = queue.clone(); + let abort_for_worker = abort.clone(); + let governor_for_worker = governor.clone(); + let roots = source_roots_owned.clone(); + let seeds = diff_edits.clone(); + let paths = changed_paths.clone(); + let keys = whole_tree_published_keys.clone(); + let spawn_target_width = governor.current_target_width(); + let cross_worker_store_for_worker = if spawn_target_width > 1 { + Some(cross_worker_store.clone()) + } else { + None + }; + if worker_ordinal == 0 && spawn_target_width <= 1 { + eprintln!( "run_discovery_corpus: cross_worker_store withheld (governor target_width={spawn_target_width}) — per-index typed cache until width > 1" ); - } - // Narration style for this worker: shard_id = spawn ordinal (a stable hue in the - // interleaved stream); spawn-time target width > 1 shows the ▎shard tag — a width-1 - // admission window has no interleaving to disambiguate. - let style = ShardStyle { - shard_id: worker_ordinal, - shard_count: governor.current_target_width(), - color: floor_color, - stream: floor_stream, - }; - worker_ordinal += 1; - handles.push(std::thread::spawn( - move || -> Result, String> { - // The slot was granted by try_admit on the pump thread; the guard owns the - // matching release so a panicking worker cannot wedge admissions. - let mut slot = crate::memory_governor::AdmittedSlot::from_admitted( - governor_for_worker.clone(), - ); - // Process-scoped typed_module_cache when governor width > 1; private cold - // index at width=1 (CI budget — cross-worker-typecheck-share-design §7). - let index = match cross_worker_store_for_worker { - Some(store) => build_multi_entry_index_with_shared_caches(&roots, store), - None => build_multi_entry_index(&roots), + } + // Narration style for this worker: shard_id = spawn ordinal (a stable hue in the + // interleaved stream); spawn-time target width > 1 shows the ▎shard tag — a width-1 + // admission window has no interleaving to disambiguate. + let style = ShardStyle { + shard_id: worker_ordinal, + shard_count: governor.current_target_width(), + color: floor_color, + stream: floor_stream, }; - let runner = if selection_for_workers != NodeFrontierSelectionMode::Off { - match resolve_entry_with_index(&index, FLOOR_RUNNER_ENTRY) { - Ok((graph, source_indices)) => { - Some(make_eval_context(&graph, source_indices, execution_mode)) - } - Err(msg) => { - abort_for_worker.store(true, Ordering::SeqCst); - return Err(format!( - "floor runner resolve failed in worker ({msg}) — declared \ + worker_ordinal += 1; + handles.push(std::thread::spawn( + move || -> Result, String> { + // The slot was granted by try_admit on the pump thread; the guard owns the + // matching release so a panicking worker cannot wedge admissions. + let mut slot = crate::memory_governor::AdmittedSlot::from_admitted( + governor_for_worker.clone(), + ); + // Process-scoped typed_module_cache when governor width > 1; private cold + // index at width=1 (CI budget — cross-worker-typecheck-share-design §7). + let index = match cross_worker_store_for_worker { + Some(store) => { + build_multi_entry_index_with_shared_caches(&roots, store) + } + None => build_multi_entry_index(&roots), + }; + let runner = if selection_for_workers != NodeFrontierSelectionMode::Off { + match resolve_entry_with_index(&index, FLOOR_RUNNER_ENTRY) { + Ok((graph, source_indices)) => { + Some(make_eval_context(&graph, source_indices, execution_mode)) + } + Err(msg) => { + abort_for_worker.store(true, Ordering::SeqCst); + return Err(format!( + "floor runner resolve failed in worker ({msg}) — declared \ affected-set machinery must resolve; no silent \ run-everything fallback" - )); - } - } - } else { - None - }; - // The front-loaded admission cost (index build + runner resolve) has - // landed and is visible to the creep signals: unblock admission pacing. - slot.note_first_cost_paid(); - let mut worker_summaries = Vec::new(); - loop { - // Multiplicative decrease drains here: a worker between groups retires - // when concurrency sits above the (possibly just-halved) window. - if governor_for_worker.should_retire() - || abort_for_worker.load(Ordering::SeqCst) - { - break; - } - let Some(group_rows) = queue_for_worker.lock().unwrap().pop_front() else { - break; - }; - match run_discovery_rows( - &group_rows, - &index, - execution_mode, - selection_for_workers, - &paths, - &seeds, - runner.as_ref(), - keys.clone(), - budget_policy_for_workers, - style, - ) { - Ok(summary) => { - worker_summaries.push(summary); - slot.note_unit_complete(); - } - Err(e) => { - abort_for_worker.store(true, Ordering::SeqCst); - return Err(e); + )); + } + } + } else { + None + }; + // The front-loaded admission cost (index build + runner resolve) has + // landed and is visible to the creep signals: unblock admission pacing. + slot.note_first_cost_paid(); + let mut worker_summaries = Vec::new(); + loop { + // Multiplicative decrease drains here: a worker between groups retires + // when concurrency sits above the (possibly just-halved) window. + if governor_for_worker.should_retire() + || abort_for_worker.load(Ordering::SeqCst) + { + break; + } + let Some(group_rows) = queue_for_worker.lock().unwrap().pop_front() + else { + break; + }; + match run_discovery_rows( + &group_rows, + &index, + execution_mode, + selection_for_workers, + &paths, + &seeds, + runner.as_ref(), + keys.clone(), + budget_policy_for_workers, + style, + ) { + Ok(summary) => { + worker_summaries.push(summary); + slot.note_unit_complete(); + } + Err(e) => { + abort_for_worker.store(true, Ordering::SeqCst); + return Err(e); + } + } } - } + Ok(worker_summaries) + }, + )); + } + let mut summaries = Vec::new(); + let mut first_err: Option = None; + for handle in handles { + match handle + .join() + .map_err(|_| "discovery corpus worker thread panicked".to_string()) + { + Ok(Ok(worker_summaries)) => summaries.extend(worker_summaries), + Ok(Err(e)) | Err(e) => first_err = first_err.or(Some(e)), } - Ok(worker_summaries) - }, - )); - } - let mut summaries = Vec::new(); - let mut first_err: Option = None; - for handle in handles { - match handle - .join() - .map_err(|_| "discovery corpus worker thread panicked".to_string()) - { - Ok(Ok(worker_summaries)) => summaries.extend(worker_summaries), - Ok(Err(e)) | Err(e) => first_err = first_err.or(Some(e)), - } - } - if let Some(e) = first_err { - return Err(e); - } - // The pump exits when the queue is empty OR on abort; with no error the queue must be - // fully drained (workers only exit early on retire/abort, and the pump re-admits while - // items remain), so an undrained queue here is a scheduler bug — refuse, never under-run. - let leftover = queue.lock().unwrap().len(); - if leftover > 0 { - return Err(format!( + } + if let Some(e) = first_err { + return Err(e); + } + // The pump exits when the queue is empty OR on abort; with no error the queue must be + // fully drained (workers only exit early on retire/abort, and the pump re-admits while + // items remain), so an undrained queue here is a scheduler bug — refuse, never under-run. + let leftover = queue.lock().unwrap().len(); + if leftover > 0 { + return Err(format!( "adaptive discovery pool exited with {leftover} undrained entry-group(s) and no \ worker error — scheduler invariant violated; refusing a partial corpus" )); - } - Ok(attach_deferred_discovery_rows( - merge_discovery_summaries(summaries), - deferred_rows, - )) + } + Ok(attach_deferred_discovery_rows( + merge_discovery_summaries(summaries), + deferred_rows, + )) } }; } From eaa71751ab6ff01cf548a3a2a4f12c97531c9a5e Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 15 Jul 2026 17:27:47 +0000 Subject: [PATCH 5/6] WIP: v1 resolve is still superlinear --- src/v1/stage0/src/v1_compiler_infer.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/v1/stage0/src/v1_compiler_infer.rs b/src/v1/stage0/src/v1_compiler_infer.rs index ce78276bd4c..8ed66e5b560 100644 --- a/src/v1/stage0/src/v1_compiler_infer.rs +++ b/src/v1/stage0/src/v1_compiler_infer.rs @@ -11406,8 +11406,23 @@ pub fn infer_items( ) -> Rc>> { Rc::new({ let mut __result = Vec::new(); + let __probe = std::env::var("GUNBC_ITEM_PROBE").is_ok(); for item in items.clone().iter().cloned() { - __result.push(infer_item(item.clone(), scope.clone())); + if __probe { + let __t = std::time::Instant::now(); + let __nm = authored_name_at( + scope.type_env.source_indices.clone(), + item.clone(), + ); + let __r = infer_item(item.clone(), scope.clone()); + let __ms = __t.elapsed().as_millis(); + if __ms >= 200 { + eprintln!("[item-probe] mod={} item={} ms={}", scope.module_name, __nm, __ms); + } + __result.push(__r); + } else { + __result.push(infer_item(item.clone(), scope.clone())); + } } __result }) From 02123283408fcc49359a1ac1f7ed6f6cb2ece605 Mon Sep 17 00:00:00 2001 From: Brian Searls Date: Wed, 15 Jul 2026 17:38:07 +0000 Subject: [PATCH 6/6] WIP: v1 resolve is still superlinear --- src/v1/stage0/src/v1_compiler_infer.rs | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/v1/stage0/src/v1_compiler_infer.rs b/src/v1/stage0/src/v1_compiler_infer.rs index 8ed66e5b560..ce78276bd4c 100644 --- a/src/v1/stage0/src/v1_compiler_infer.rs +++ b/src/v1/stage0/src/v1_compiler_infer.rs @@ -11406,23 +11406,8 @@ pub fn infer_items( ) -> Rc>> { Rc::new({ let mut __result = Vec::new(); - let __probe = std::env::var("GUNBC_ITEM_PROBE").is_ok(); for item in items.clone().iter().cloned() { - if __probe { - let __t = std::time::Instant::now(); - let __nm = authored_name_at( - scope.type_env.source_indices.clone(), - item.clone(), - ); - let __r = infer_item(item.clone(), scope.clone()); - let __ms = __t.elapsed().as_millis(); - if __ms >= 200 { - eprintln!("[item-probe] mod={} item={} ms={}", scope.module_name, __nm, __ms); - } - __result.push(__r); - } else { - __result.push(infer_item(item.clone(), scope.clone())); - } + __result.push(infer_item(item.clone(), scope.clone())); } __result })