Skip to content

perf(compiler2): interface-method-name pre-filter in MIR dispatch + is_subtype_of assumption fast path - #4044

Closed
hellovai wants to merge 1 commit into
canaryfrom
perf/reland-mir-dispatch-prefilter
Closed

perf(compiler2): interface-method-name pre-filter in MIR dispatch + is_subtype_of assumption fast path#4044
hellovai wants to merge 1 commit into
canaryfrom
perf/reland-mir-dispatch-prefilter

Conversation

@hellovai

@hellovai hellovai commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Re-lands two slices of the cold-compile performance audit (items 12 and 7 of PR #4016), re-derived against current canary. Both are pure fast paths: dispatch results and subtype verdicts are unchanged.

What was being recomputed

1. MIR dispatch pre-filter (baml_compiler2_mir/src/lower.rs)

dispatch_target_for_concrete enumerated every impl block in the package closure (l1_impls_for_recv, which probes each impl's pattern against the receiver, invoking alias normalization / subtype checks per probe) for every method call and field access — almost always just to conclude "no impl provides this member". Since #4032 deleted the TIR structural algebra, each of those probes goes through AliasEquivCtx / baml_type::normalize, making the per-call enumeration even more expensive.

Fix: collect every interface-declared method name (required + default, own package + dependency closure) once per package into the existing Salsa-tracked PackageLoweringData, and have dispatch_target_for_concrete return None in one hash lookup when the member name is absent. The set is package-wide, not per receiver type, so any name declared by any reachable interface still takes the full enumeration — the filter cannot change dispatch results.

2. is_subtype_of assumption fast path (baml_type/src/normalize.rs)

The co-inductive assumption bookkeeping (deep NormalTy clone + full-tree hash of the (lhs, rhs) pair, insert + remove) ran on every recursive step of the subtype check. Canary already had the reflexivity short-circuit, so only the bookkeeping split was needed.

Fix: split the structural rules into is_subtype_of_inner; the outer function performs the pair bookkeeping only for the expanding arms — Mu, TypeVar, AssociatedTypeProjection on the left, or Mu on the right.

Termination argument (also documented as a comment at the split): assumption tracking exists solely to terminate cycles, and a cycle can only regress through arms that expand a type — μ-unfolding (substitution can regenerate the same pair) and variable/projection bound lookup (a bound can mention the variable). Purely structural arms descend into strictly smaller subterms of finite trees, so no cycle can form through them; any infinite path must pass through an expanding arm infinitely often, and those still record assumptions (drawn from the finite subterm closure of the regular operands).

Measurements

Profiler from PR #4038 (tools_compile_profile, not part of this PR), crates/baml_tests/baml_src (77 files, 25k lines), BAML_NO_BYTECODE_CACHE=1, fresh Salsa db per run, --repeat 5, Apple Silicon:

check (median) emit (median) total (median)
canary (2660b8b) 1.454s 1.791s 3.250s
this PR 1.358s 1.295s 2.675s

Emit (where MIR lowering and dispatch run) drops ~28%; total cold compile ~18%.

Testing

  • cargo test --workspace: all compiler/runtime suites pass, including the 442-test baml_tests suite — no snapshot diffs, so dispatch results and subtype verdicts are byte-identical.
  • Only failures were environmental, unrelated to this change: sdk_test_typescript_node (missing node_modules in the test sandbox — tsc/vitest/attw not installed) and 4 sdk_test_python_pydantic2 cancellation tests (fixture uses ExceptionGroup, undefined on the sandbox's Python 3.10, plus asyncio-timing asserts).
  • Pre-commit hooks (cargo fmt, workspace clippy -D warnings) pass.

Provenance: re-derivation of c1466f3 (PR #4016) items 12 and 7.

Made with Cursor

Summary by CodeRabbit

  • Bug Fixes
    • Improved type compatibility checks involving recursive types, type variables, and associated types, helping prevent incorrect results or excessive processing.
    • Optimized method dispatch checks by quickly rejecting methods that are not declared by applicable interfaces, improving compilation performance.

…s_subtype_of assumption fast path

dispatch_target_for_concrete enumerated every impl block in the package
closure for every method call and field access, only to conclude "no
impl provides this member" for the overwhelmingly common plain member.
Collect all interface-declared method names (own package + dependency
closure) once per package into PackageLoweringData and answer that case
with a single hash lookup. The set is package-wide, so any name declared
by any reachable interface still takes the full enumeration — a pure
fast path.

baml_type::normalize::is_subtype_of ran the co-inductive assumption
bookkeeping (deep clone + full-tree hash of the (lhs, rhs) pair) on
every recursive step. Only the expanding arms (mu-unfolding,
type-variable / associated-projection bound lookup) can revisit a pair;
purely structural arms descend into strictly smaller subterms, so the
bookkeeping is now restricted to the expanding arms via an
is_subtype_of_inner split (termination argument documented at the
split).

Re-derivation of items 12 and 7 from the cold-compile audit (#4016)
against post-#4032 canary, where equivalence goes exclusively through
baml_type::normalize.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 16, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
beps Ready Ready Preview, Comment Jul 16, 2026 12:22am
promptfiddle Ready Ready Preview, Comment Jul 16, 2026 12:22am
promptfiddle2 Ready Ready Preview, Comment Jul 16, 2026 12:22am

Request Review

@github-actions

Copy link
Copy Markdown

⏭️ Performance benchmarks were skipped

Perf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to canary/main.

To run them on this PR, do any of the following, then push a commit (or re-run CI):

  • Add RUN_CODSPEED=1 to the PR description, or
  • Include run-perf or /perf in the PR title or any commit message.

@vercel
vercel Bot temporarily deployed to Preview – beps July 16, 2026 00:01 Inactive
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 9286994e-b522-4ff0-b6d4-caee8d866821

📥 Commits

Reviewing files that changed from the base of the PR and between 408b2be and 5a1bf3f.

📒 Files selected for processing (2)
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_type/src/normalize.rs

📝 Walkthrough

Walkthrough

The changes optimize concrete interface dispatch by precomputing reachable method names and restructure equirecursive subtype cycle tracking around expansion-aware comparisons.

Changes

MIR dispatch prefilter

Layer / File(s) Summary
Interface method dispatch prefilter
baml_language/crates/baml_compiler2_mir/src/lower.rs
Collects method names from reachable interfaces, threads them through lowering contexts, and returns early from concrete dispatch when a method name is absent.

Subtype cycle handling

Layer / File(s) Summary
Expansion-aware subtype assumptions
baml_language/crates/baml_type/src/normalize.rs
Moves co-inductive assumption insertion, detection, and cleanup into a wrapper used for expanding subtype comparisons.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: codeshaunted

Poem

I hop through methods, a fast dispatch hare,
With names precomputed and neatly laid bare.
Subtype loops now know when to pause,
Tracking expanding paths by their laws.
Thump, thump—the compiler grows wise!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly names both performance optimizations and matches the main changes in MIR dispatch and subtype assumption handling.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/reland-mir-dispatch-prefilter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 July 16, 2026 00:09 Inactive
@github-actions

Copy link
Copy Markdown

Binary size checks passed

7 passed

Artifact Platform File Gzip Gated on Baseline Delta Status
baml-cli Linux 🔒 22.8 MB 9.7 MB file 22.8 MB +51.6 KB (+0.2%) OK
packed-program Linux 🔒 16.3 MB 6.8 MB file 16.3 MB +20.5 KB (+0.1%) OK
baml-cli macOS 🔒 17.5 MB 8.5 MB file 17.5 MB +33.2 KB (+0.2%) OK
packed-program macOS 🔒 12.6 MB 6.0 MB file 12.6 MB +32 B (+0.0%) OK
baml-cli Windows 🔒 19.1 MB 8.7 MB file 18.7 MB +408.1 KB (+2.2%) OK
packed-program Windows 🔒 13.5 MB 6.1 MB file 13.5 MB -539 B (-0.0%) OK
bridge_wasm WASM 15.2 MB 🔒 4.3 MB gzip 4.3 MB +1.8 KB (+0.0%) OK

🔒 = the size this artifact is GATED on (ceiling + delta). Binaries gate on file size (installed binary); WASM gates on gzip (download size). The other size is shown for information only.


Generated by cargo size-gate · workflow run

@hellovai

Copy link
Copy Markdown
Contributor Author

Folded into the combined re-landing PR #4054 (per maintainer preference for a single PR post-tool-merge). Branch kept for provenance; individual before/after measurements remain in this PR's description.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant