Repository navigation
PR-2 (Fix-1): reconcile-first — kill the merge-side O(M²) parent-surface copy (byte-identical) - #6360
Conversation
e7999c1 to
10c5b73
Compare
13285a1 to
10c5b73
Compare
6b86e6b to
49aee96
Compare
Byte-identical merge O(M^2) cut — single-import shares parent cache instead of merge(empty_acc, parent.cache). Seed regenerated to match 04_infer.dag @ e1674c0. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Thanks — approval confirmed, and one strengthening for the record on the invariant you (correctly) flagged as the crux. You noted byte-identity holds iff Two orientation notes for the merge decision: (1) the |
PR-2 (Fix-1) — reconcile-first: kill the merge-side O(M²) parent-surface copy
Deliverable: Fix-1 — the merge quadratic is dead, byte-identically (
union_parent_type_env_cachesfirst-parent-share; 14.4×→4.0×, 103× fewer entries, one-function seed diff). Plus thedocs/plans/type-env-single-authority-design.md§3.1 correction and both profile tables. Realization lane lively-raven-355.SCOPE DECISION — wall-flattening moved to a named follow-up (a decision, review may veto)
This PR is re-scoped from "flatten the reconcile wall" to "kill the merge bucket." Wall-flattening (Fix-2, the
source_visible_names/rewire iteration bucket) moves OUT to a named follow-up lane, for three reasons discovered by profiling (all validated by execution, none by argument):UnlistedImportUse), notregen --verify— a different receipt than Fix-1's.svn:5750+rewire:6732) and needs a per-site split before targeting.04_resolve.dag:377) — heavy optimization now risks pre-paying a redesign.Fix-1 is independently valuable and fully validated; holding it hostage to a Fix-2 whose scope just grew serves nobody. The follow-up brief (per-site profile → targeted incremental set or rewire analysis, diagnostic-parity oracle) is filed separately. The
SymbolIndexfill/consume is PR-3's consumer-side reform — this PR adds no inert fill code (unconsumed scaffold would be parallel-representation debt; Fix-1 is independent of it).Plan map (for readers of the older combined-vehicle docs): #6360 = profile receipts + Fix-1 (merge bucket); PR-3 =
SymbolIndexfill +ResolutionPolicy+ first consumer; Fix-2 lane = iteration bucket (per-site split → targeted set, diagnostic-parity oracle); PR-4 = namespace-only pivot.The measured root (three plausible mechanisms refuted by execution, not argument)
The reform's target evolved through disciplined prove-by-execution — each refutation prevented a wrong implementation:
im_rc::HashMap(v1_rt.rs:6), so the copy already structurally shares. A no-op, caught before implementing.Profile (throwaway entry-count probe, reverted; 4× depth steps so O(M²)≈16×, O(M)≈4×/step):
Root (accepted): reprocess the parent's full surface per module, in three forms — iterate (
map_keys), merge (map_mergewith large parent-cache overlay), lookup (map_get). Residue check clean: reconcile-wall 4×-ratio (~11.6) ≤ every op's ratio, so the named ops explain the wall — no faster-growing unnamed site.Fix-1 — merge bucket (LANDED,
f4c37e5a48)union_parent_type_env_cachesnow uses the first parent's cache as the fold base (structurally shared) instead ofmerge(empty_acc, parent.cache)— the large-overlay copy. Single-import → returns the parent cache directly (O(1) share); multi-import → first shared, rest merged. Same precedence (later overlays earlier) → byte-identical. This makes04_env.dag:68's described-but-unimplementedancestry_cache_sharingclaim TRUE — a lie in the tree becoming a fact.Receipt A — surgical-confinement (gold standard): the whole compiler corpus recompiled, and the emitted-seed diff vs main is exactly one function body (
union_parent_type_env_caches,v1_compiler_infer.rs, +35/−10) — zero ripple across the other 91 seed files. Corpus-wide behavior preservation by construction.Receipt B — byte-identity:
regen_divergence_count=0(committed stage0 == fresh self-compile).Receipt C — sequential attribution (the merge bucket collapsed, iteration untouched):
Merge doubling ratio 14.4× → 4.0×, a 103× reduction in entries at d800. The iteration bucket is identical (13.7×) — proving Fix-1 was confined to the merge; Fix-2 owns iteration. Exactly the pre-registered prediction.
Receipt D — RSS/memory (mechanism; magnitude lands with Fix-2): 658K fewer map inserts per compile at d800 — the memory the merge quadratic was ballooning; a direct mechanism-corroboration of neat-stag's whole-tree-emit OOM. Peak-RSS number (getrusage/
/proc) lands with Fix-2's whole-corpus re-measure.Follow-up lane (Fix-2 — iteration bucket, NOT in this PR)
source_visible_names(04_infer.dag:5750) re-enumeratesmap_keys(parent.ancestry_str_bindings)per is_all import → O(M²) (the 2.0M-entrymap_keysbucket, still 13.7×). The follow-up brief: per-site split first (svn:5750vsrewire:6732vs residue) → then either a dedicated incrementally-built reexport-name-set (if svn dominates, minimum-viable-size + update the04_resolve.dag:377scaffold note) or a separate rewire analysis (if rewire dominates). Oracle: diagnostic-parity onUnlistedImportUse, sharpened — verify it fires in the corpus today (else add a discriminating synthetic fixture so parity isn't vacuous). RSS peak number (getrusage//proc) lands with that lane's whole-corpus re-measure.Atomicity-hazard receipt (recurrence)
Mid-surgery the auto-committer landed
04_infer.dag(Fix-1) without its regenerated seed (e1674c0), producing a dag/seed drift theregengate caught loudly (CI red @e1674c0) — the fail-closed system working.f4c37e5a48committed the matching seed (regen_divergence_count=0). Recorded as a new instance of the documented multi-file-snapshot atomicity hazard (defork-audit §3): the fix (.dag+ regenerated seed) must land as one atomic commit.Day-0 receipt (settled reconcile-first)
Whole-corpus
--target dagcold baseline is reconcile-bound (not emit, as §7.5's--target rustprofile assumed): reconcile 89.1s / 87%, emit(dag) 8.4s / 8%. Synthetic resolve curve d50→d800 climbs monotonically (2.47→3.41) — genuine super-linear. → reconcile-first.Acceptance criteria (this PR — Fix-1 only)
map_mergedoubling ratio 14.4×→4.0× ✓ (Receipt C).regen_divergence_count=0✓ (Receipt B) + one-function seed diff ✓ (Receipt A).std.typesfilter, single-exporter canonical pick,variant_surfacesre-export chain.(Wall-flattening / synthetic-curve-toward-2.0 is the follow-up lane's acceptance, not this PR's — see the Scope Decision above.)
Isolation note
Branched off
main. Independent of the CI double-compile fix (Lever A, quick-tern-682, #6361) — that's the gate-shell missing-Share; this is the algorithmic reconcile cut. Both attack the--target dagwall.