Skip to content

Compiler perf: thread ResolvedAliases through type comparisons (482ms → 281ms) - #3912

Open
hellovai wants to merge 3 commits into
canaryfrom
hellovai/compiler-perfo-alias-env
Open

hellovai wants to merge 3 commits into
canaryfrom
hellovai/compiler-perfo-alias-env

Conversation

@hellovai

@hellovai hellovai commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Stacked on #3911. Second PR in the incremental compiler-perf series: after #3911 lands, this one takes the empty-project cold compile from 482ms to 281ms median (−42%). Cumulative from the 1.41s baseline: 5× faster.

The problem

Every type-comparison entry point (is_same_normalized_type, is_subtype_of, the match_ty_pattern family) took a bare alias HashMap and re-derived the recursive-alias set internally, on every call:

pub fn is_same_normalized_type(lhs: &Ty, rhs: &Ty, aliases: &HashMap<..>) -> bool {
    let recursive = find_recursive_aliases(aliases);   // full DFS over the alias graph, per comparison
    ...
}

These are called from the hottest loops in the compiler: interface-dispatch candidate resolution in MIR lowering, pattern matching in TIR inference, coherence checking. Profile of the 482ms compile: find_recursive_aliases at 35% inclusive (~168ms), almost all of it under is_same_normalized_type (37%).

The irony: MIR lowering already carried the precomputed set in ResolvedAliases { aliases, recursive }, the old signatures just couldn't accept it.

The change (architectural)

The alias environment's currency is now &ResolvedAliases (the existing baml_type struct: alias map + its recursive set) instead of a bare &HashMap. The recursive-alias analysis is computed exactly once per environment, at construction, via the new sanctioned constructor normalize::resolved_aliases_from_map:

Construction site When the set is computed
TypeInferenceBuilder::new once per scope inference
MIR LoweringContext already had it per package (now actually used)
coherence registry build once per tracked-query invocation
callable-throws analysis once per callable
LSP check.rs alias helper once per check pass

Signature ripple: 6 params in interfaces.rs + 15 in coherence.rs + 4 in impl_rules.rs + 2 in callable.rs + 6 in MIR lower.rs + 23 in LSP check.rs; ~130 call sites converted. Purely a plumbing change: no comparison logic was touched, and normalize's internal machinery still works on the raw map (it is the thing that computes the set).

Also included: registry dispatch-target memo

registry_dispatch_target_for_concrete (blanket-impl method lookup for concrete receivers) scans every impl rule of every package per call site, and its negative answer ("no interface provides this method") is both the common case and the most expensive (the scan never short-circuits). It is now memoized in the package-shared DispatchCandidateCache keyed (receiver type, method), caching misses too.

Honest note: on the empty-project benchmark this adds only ~1% (~3ms), because the currency change above already removed the per-call DFS that dominated its cost. It is included because it bounds the rule scan for real projects with more impl rules and call sites, and it reuses the cache infrastructure from #3911 verbatim.

Numbers

State Median (empty project, cold)
baseline (canary) 1410 ms
after #3911 482 ms
after this PR 281 ms

Benchmark: cargo bench --bench compiler_benchmark -- compile_empty_project (100 samples, idle machine).

Validation

Not in this PR (next in series)

  • tracked package_alias_env salsa query (kills the per-scope alias-map rebuild in infer_scope_types)
  • type interning (SipHash string hashing is now the top remaining leaf)

🤖 Generated with Claude Code

Four independent fixes to the compile pipeline, found by profiling
compile_empty_project (which compiles the full 55-file stdlib):

1. normalize: lhs==rhs fast path in is_same_normalized_type /
   is_subtype_of, and find_recursive_aliases rewritten as a single
   memoized tri-color DFS (O(nodes+edges) instead of a fresh DFS per
   alias).

2. emit Pass 1: read intrinsic-kind off the item tree instead of fully
   MIR-lowering every function just to inspect one field. Previously
   every function in the project was lowered twice (Pass 1 + Pass 4).

3. mir: package-shared DispatchCandidateCache memoizing
   interface_method_candidates_for across all functions lowered in a
   package. Key includes the function's generic_param_bounds - the only
   per-function input of the resolution family (verified by transitive
   self-field audit).

4. mir: LoweringContext no longer deep-copies all nine TIR inference
   tables into per-function merged maps; it now holds &'db ScopeInference
   views per FileScopeId and reads through tir_* point accessors.

Co-Authored-By: Claude Fable 5 <[email protected]>
@cursor

cursor Bot commented Jul 4, 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 4, 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 4, 2026 1:43am
promptfiddle Ready Ready Preview, Comment Jul 4, 2026 1:43am
promptfiddle2 Ready Ready Preview, Comment Jul 4, 2026 1:43am

Request Review

@coderabbitai

coderabbitai Bot commented Jul 4, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: cd2af23b-204b-4d60-b105-0f407d7e8252

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch hellovai/compiler-perfo-alias-env

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 – beps July 4, 2026 01:02 Inactive
@github-actions

github-actions Bot commented Jul 4, 2026

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 – promptfiddle2 July 4, 2026 01:08 Inactive
@github-actions

github-actions Bot commented Jul 4, 2026 •

Copy link
Copy Markdown

Binary size checks passed

✅ 7 passed

Artifact Platform File Gzip Gated on Baseline Delta Status
✅ baml-cli Linux 🔒 21.6 MB 9.2 MB file 21.5 MB +77.6 KB (+0.4%) OK
✅ packed-program Linux 🔒 15.7 MB 6.6 MB file 15.6 MB +128.1 KB (+0.8%) OK
✅ baml-cli macOS 🔒 16.6 MB 8.0 MB file 16.6 MB +66.3 KB (+0.4%) OK
✅ packed-program macOS 🔒 12.2 MB 5.8 MB file 12.1 MB +99.6 KB (+0.8%) OK
✅ baml-cli Windows 🔒 18.2 MB 8.2 MB file 18.1 MB +55.8 KB (+0.3%) OK
✅ packed-program Windows 🔒 13.1 MB 5.9 MB file 13.0 MB +99.4 KB (+0.8%) OK
✅ bridge_wasm WASM 14.5 MB 🔒 4.1 MB gzip 4.1 MB +53.0 KB (+1.3%) 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 and others added 2 commits July 3, 2026 18:17
The pub struct's doc linked the private
LoweringContext::interface_method_candidates_for, which fails
`-D rustdoc::private-intra-doc-links` in CI. Plain code formatting
instead of an intra-doc link.

Co-Authored-By: Claude Fable 5 <[email protected]>
… compile)

Type comparisons (is_same_normalized_type, is_subtype_of, and the
match_ty_pattern family) took a bare alias HashMap and re-derived the
recursive-alias set on every call: a full DFS over the alias graph per
type comparison, called from the interface-dispatch and inference inner
loops. Profile: find_recursive_aliases was 35% inclusive (168ms) of the
482ms compile, almost all of it under is_same_normalized_type (37%).

The comparison entry points now take &ResolvedAliases (the existing
baml_type struct carrying the alias map plus its precomputed recursive
set), and every environment construction site computes the set exactly
once via the new normalize::resolved_aliases_from_map:

- TypeInferenceBuilder wraps its map once per scope at construction
- MIR lowering passes its existing per-package ResolvedAliases (which
  always had the set; the old signature just threw it away)
- coherence registry build, callable-throws analysis, and the LSP
  check-pass helper each wrap once at their construction points

Also memoizes registry_dispatch_target_for_concrete in the
package-shared DispatchCandidateCache keyed (receiver ty, method),
caching negative answers too (the "no interface provides this method"
scan over every impl rule is the common case and never short-circuits).
On the empty-project benchmark this adds only ~1% (its cost was
dominated by the per-call DFS the currency change already removed); it
bounds the rule scan for projects with more impl rules and call sites.

Empty-project cold compile: 482ms -> 281ms median (-42%); cumulative
from the 1.41s baseline: 5x. Validated by the tir/mir/emit/lsp2_actions
unit suites (337 tests); full integration suite deferred to CI.

Co-Authored-By: Claude Fable 5 <[email protected]>
@hellovai
hellovai force-pushed the hellovai/compiler-perfo-alias-env branch from 9ff0898 to 4fd0548 Compare July 4, 2026 01:19
@cursor

cursor Bot commented Jul 4, 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 temporarily deployed to Preview – beps July 4, 2026 01:20 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 July 4, 2026 01:26 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle July 4, 2026 01:43 Inactive
Base automatically changed from hellovai/compiler-perfo to canary July 4, 2026 01:48

This branch was previously deployed

3 inactive deployments
Preview – promptfiddle — 4fd0548b Deployed Jul 4, 2026 by vercel[bot]
Preview – promptfiddle2 — 4fd0548b Deployed Jul 4, 2026 by vercel[bot]
Preview – beps — 4fd0548b Deployed Jul 4, 2026 by vercel[bot]
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