Decision-tree match compiler#9992
Draft
jaredramirez wants to merge 11 commits into
Draft
Conversation
Collaborator
What's up with this? do we have a GH issue tracking these? |
Plan for projects/big/decision-tree-match-compiler.md: shared Maranget-style match compiler at the LIR-lowering boundary, consumed by both lowerers.
28 hand-written match cases locking in branch-chain semantics across all executors before replacing the match compiler (projects/big/decision-tree-match-compiler.md): tag dispatch, guards, int/Dec literals, strings, records/tuples, lists with rests, as-patterns, nominal patterns (PR 9849 shapes), and exhaustiveness. Two findings recorded as skipped expected-correct tests: the string-group special case sends a guard failure to the group miss join, skipping later branches with the same string pattern (violates source-order fallthrough; the decision-tree compiler must fix this). Also noted: plain fractional literal patterns are broken on main independent of this project (small Dec literals panic in Monotype numeric finalization; F32/F64 literal patterns are checker-rejected); only long-form Dec literal patterns work and are covered here.
…art 1) Pattern matrix normalization, occurrence interning, necessity-based column selection, and grouped Maranget specialization with exit-based sharing, generic over an accessor Ctx so both LIR lowerers can drive it. Tree construction only; LIR emission lands next.
Emit the decision tree as LIR through a generic emit context: multiway switch_stmt for tags and switch-safe ints, str_match_set string arms (string literals become exact set arms), list length dispatch, exit joins for shared continuations, occurrence locals materialized once per dominating scope, and checker-verdict defaults (exhaustive tag tests emit the last arm as the switch default; single-variant unions emit no dispatch at all — the dev backend rejects zero-branch switches). Narrow signed int literals only use switch_stmt when the sign bit is clear: the interpreter zero-extends the condition read while the dev backend sign-extends, so negative values keep the equality-chain lowering. Integration is behind match_tree.lowering_mode (default .chain until the migration completes). Includes the debug statement-count lint (machinery statements bounded by 16*pattern_nodes+24), LIR-shape tests (one multiway switch + one discriminant read per N-branch tag match), the PR 9707 list-family linear-growth test, ARC certification coverage for guard/ string/list/nominal matches, determinism, and a seeded differential corpus (six families, chain vs tree, compile-time dev-backend evaluation plus interpreter) that passes. The full five-executor eval suite passes under tree mode: 1546 passed, 0 failed, 0 crashed.
Flip match_tree.lowering_mode to .tree and un-skip the two conformance tests documenting the string-group guard-fallthrough hazard the chain could not pass (a guard failure inside a grouped string arm skipped later branches with the same string pattern; the tree merges duplicate string shapes into one arm and retries later rows in source order). Validated by the full eval suite across executors.
Adapt the Lambda-Mono debug/test lowerer to the shared decision-tree match compiler with its own accessor context (callable patterns dispatch like tags via callableVariantIndex; callable payload locals take the committed union payload layout from the source local, since callable payloads may be boxed).
Collaborator
|
I'll rebase this to clean up the conflicts. |
Both LIR lowerers now compile matches exclusively through match_tree. DELETED: lowerBranchChain, the string-grouping special case (strPatternBranchGroupStart / lowerStrPatternBranchGroup), and match_tree.lowering_mode from both lowerers, plus chain-only helpers that became unreachable. The single-pattern refutable path (bindPatternOrCrash for destructures and try-sequences) keeps lowerPatternThen and discriminantSwitch — a decision tree for one row is exactly that chain. The migration-gate differential test becomes a tree-only generated corpus (compile + interpret; cross-engine comparison lives in run-test-eval), and the LIR-shape test asserts per-proc discriminant-read counts from proc dumps instead of chain-vs-tree deltas.
design.md Pattern Lowering: the shared match_tree module, the Monotype DAG re-lowering/sharing invariant (join points, never re-lowering), and the debug statement-count lint.
Lint/audit conformance for the new match-compiler code: unnamed parameters instead of unused-variable suppressions, explicit error sets in the corpus test, OccKey -> OccDigest and ScopeKey -> Scope (banned type-name suffix), and precise wording instead of the banned vague term. Measurements (recorded in match_tree.zig's column-selection comment and here): - Column heuristic: longest same-kind run vs plain first-column selection produced identical statement totals (5776) on the generated corpus; longest-run is kept because it dominates by construction when the two differ. - tag_reachability re-measured on a match-heavy program at --opt=speed: the pass removed ZERO switch edges under both the old chain (39 one-armed switches) and the tree (17 switches, 27 branches — 56% fewer switch statements than the chain), at ~3ms per build either way. The pass stays enabled per the project doc (cross-function narrowing can still fire on other shapes), but the decision tree removes the intra-function shapes PR 9726 targeted at the source.
Rebase fallout: main's mutable-store borrow guards changed the lambda_mono/monotype_lifted span accessors and the type-store field accessors to return GuardedList borrow spans instead of plain slices. Adapt the MatchTreeCtx accessors in both lowerers with GuardedList.at, dupe the spans that cross into slice-typed helpers (strShapeKey, lowerBranchTree), and query store lengths through len(). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
77f7dd1 to
6950edb
Compare
Collaborator
There was a problem hiding this comment.
TODO Remove this file that was accidentally committed... if we do this in a follow up it will skip expensive CI runs
lukewilliamboswell
approved these changes
Jul 7, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements projects/big/decision-tree-match-compiler.md: a Maranget-style decision-tree match compiler at the LIR-lowering boundary, as one shared module (
src/postcheck/match_tree.zig) consumed by both lowerers. The per-branch backtracking chains and the string-grouping special case are deleted.What changed
src/postcheck/match_tree.zig— pattern matrix normalization + occurrence interning + tree construction + LIR emission, generic over an accessor context sosolved_lir_lower.zig(Lifted) andlir_lower.zig(Lambda Mono, incl.callablepatterns) share one implementation.switch_stmtwith one discriminant read (asserted by tests). String literals and interpolation patterns becomestr_match_setarms; duplicate string shapes merge into one arm with source-order guard retry. Lists dispatch on one length read with exact-length switch arms. Switch-safe int literals form multiway switches; Dec/i128/floats/negative narrow signed ints keep equality chains (executors widen narrow signed switch conditions differently: the interpreter zero-extends, the dev backend sign-extends).comptime_exhaustiveness_failed/runtime_errorterminal.Testing
match-dt:insrc/eval/test/eval_match_tests.zig); full five-executor suite green: 1546 passed, 0 failed, 0 crashed.Measurements
--opt=speed: 39 one-armed switches (chain) → 17 switches / 27 branches (tree), −56% switch statements.tag_reachabilityre-measured per the project doc: removes zero switch edges under either lowering on that program at ~3ms/build; kept enabled (cross-function narrowing can still fire), but the tree removes the intra-function shapes PR 9726 targeted at the source.Notes
zig build minici: every step passes exceptrun-test-cli's 5 RustGlue cases, which are infrastructure errors (cargo is not installed on this machine; they run on CI) — 547 CLI tests passed, 0 failed. All other steps (format, lints, tidy, semantic audit, snapshots, every module test, eval suite, playground, serialization, wasm/dylib/archive, parser coverage) are green.🤖 Generated with Claude Code