Skip to content

perf(compiler2+cli): re-land #4016 cold-compile optimizations — tracks A, D, E combined - #4054

Closed
hellovai wants to merge 9 commits into
canaryfrom
perf/cold-compile-reland
Closed

hellovai wants to merge 9 commits into
canaryfrom
perf/cold-compile-reland

Conversation

@hellovai

@hellovai hellovai commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Combined re-landing of the still-orthogonal optimizations from #4016, re-derived against current canary and measured individually with the profiler from #4038. Folds in three reviewed track branches (their PRs are closed as superseded by this one, branches kept):

Plus a one-line docs fix labeling the pre-audit warm-run figures in the profiler README as historical (last open CodeRabbit item from #4038).

Two further tracks from the re-landing plan (TIR package-level queries; lambda double-inference fix) are still in progress and will follow separately.

Performance: canary (0e58a20) vs this branch

Pipeline profiler (#4038's tools_compile_profile, BAML_NO_BYTECODE_CACHE=1, fresh cache dir, corpus baml_language/crates/baml_tests/baml_src, 77 files / 25,212 lines). Measured as 7 interleaved baseline/combined pairs on the same machine; the machine carried background load, so the paired per-run ratio is the robust statistic:

metric canary median combined median paired speedup (median)
check 2.552 s 1.638 s ~1.6×
emit 3.414 s 2.173 s ~1.6×
total 6.017 s 3.853 s ~1.62× (per-pair ratios 1.52–1.67 across 6 of 7 pairs)

On the quietest pairs (machine idle-ish), combined total was ~2.2 s vs ~3.6 s baseline. Query executions: 25,972 → 26,054 (+82: the new tracked queries themselves; 37 → 39 unique).

End-to-end CLI (hyperfine --warmup 1 --runs 7, baml check over the corpus, disk cache disabled) — this additionally captures the mimalloc win the profiler can't see (it uses mimalloc itself):

mean ± σ user CPU
canary 3.959 s ± 0.219 s 3.510 s
combined 1.178 s ± 0.055 s 1.011 s

3.36× ± 0.24 faster (user CPU 3.47×, load-independent).

Safety checks

Provenance: #4016 (original audit; see tools_compile_profile/README.md). Superseded findings not re-landed: recursive-alias hoisting (fixed by #4032), builtin-diagnostics skip and counter-based class type tags (superseded by #3924's caching and content-addressed tags).

Made with Cursor

Summary by CodeRabbit

  • Performance
    • Improved cold compilation performance via shared, memoized CST→AST lowering and reused diagnostics/data across compiler stages.
    • Accelerated interface dispatch using package-wide pre-filtering, and faster Ariadne diagnostic rendering via a reusable source cache.
  • Bug Fixes
    • Improved subtype checking for recursive/assumed relationships and better consistency across compiler validation and language-server type-alias checks.
  • Documentation
    • Updated compile-profile documentation wording for the “cold by default” timing explanation.

hellovai and others added 7 commits July 15, 2026 13:15
On canary the CST→AST lowering (baml_compiler2_ast::lower_file) is a plain,
untracked function that six consumers each re-ran from scratch: HIR's
file_semantic_index, PPIR's file_semantic_index, ppir_expansion_items, the
two project-wide expansion-map collectors (collect_block_attrs /
collect_alias_bodies), and the lsp2 check pass. Repeated CST traversal was
~31% of cold-compile CPU on the test corpus.

Changes:
- Add a salsa-tracked baml_compiler2_hir::file_ast(db, file) query that
  performs lowering once per file and shares items + lowering diagnostics +
  env var refs. Point all six consumers at it.
- PPIR file_semantic_index now delegates to HIR's file_semantic_index when a
  file has no expansion items: the post-expansion index is byte-for-byte the
  pre-expansion one for such files, so rebuilding it was wasted work (this
  also dedups scope identity, cutting infer_scope_types executions).
- Make PPIR function_body a tracked query returning Arc<FunctionBody> so MIR's
  per-call-site body fetches don't re-clone the ExprBody arena each time.

Re-derived against current canary; reference: PR #4016 (perf/compiler2-cold-compile).

Co-authored-by: Cursor <[email protected]>
…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 <[email protected]>
… per diagnostic batch

Two independent wins for cold  /  wall time,
re-derived from the compiler2 cold-compile audit (#4016):

- baml_cli now sets mimalloc as its global allocator. The compiler
  workload is dominated by small short-lived allocations (Ty trees,
  Vecs, SmolStrs); system malloc measured ~35% of remaining
  single-threaded CPU in the original audit.

- render_diagnostics builds the ariadne SourceCache (a line index over
  every project file) once per batch instead of once per diagnostic,
  threading it as &mut through render_ariadne /
  render_report_to_string. Rendered bytes are unchanged, so cached
  diagnostics from the #3924 disk cache replay identically
  (BAML_CACHE_VERIFY verified).

Co-authored-by: Cursor <[email protected]>
@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 8:01am
promptfiddle Ready Ready Preview, Comment Jul 16, 2026 8:01am
promptfiddle2 Ready Ready Preview, Comment Jul 16, 2026 8:01am

Request Review

@github-actions

Copy link
Copy Markdown

🏎️ Performance benchmarks are running for this PR

CodSpeed perf benchmarks were triggered because this PR opted in. Results will appear in the CodSpeed check / dashboard once they finish.

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

coderabbitai Bot commented Jul 16, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The compiler now memoizes CST-to-AST lowering, package aliases, implementation locations, and callee generic facts. It also adds interface-dispatch filtering, scoped subtype assumptions, reusable diagnostic caches, mimalloc allocation, and updated compile-profile documentation.

Changes

Compiler performance and analysis

Layer / File(s) Summary
Memoized AST lowering and consumers
baml_language/crates/baml_compiler2_ast/..., baml_language/crates/baml_compiler2_hir/src/lib.rs, baml_language/crates/baml_compiler2_ppir/src/lib.rs, baml_language/crates/baml_lsp2_actions/src/check.rs
Adds equality derives to EnvVarRef and reuses Salsa-tracked FileAst data across HIR, PPIR, and LSP paths.
Memoized aliases and generic facts
baml_language/crates/baml_compiler2_tir/...
Introduces cached package aliases, implementation locations, and callee generic facts, then wires them through type inference, projections, callable analysis, and tests.
Interface dispatch pre-filter
baml_language/crates/baml_compiler2_mir/src/lower.rs
Collects reachable interface method names and skips dispatch candidate enumeration for absent names.
Reusable Ariadne source cache
baml_language/crates/baml_compiler_diagnostics/src/render.rs
Shares a SourceCache across Ariadne diagnostic rendering operations.
Scoped subtype assumptions
baml_language/crates/baml_type/src/normalize.rs
Limits co-inductive assumption tracking to expanding subtype checks.
CLI allocator and profile notes
baml_language/crates/baml_cli/..., baml_language/crates/tools_compile_profile/README.md
Adds mimalloc as the CLI global allocator and updates historical timing documentation.

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

Sequence Diagram(s)

sequenceDiagram
  participant SourceFile
  participant HIR
  participant PPIR
  participant TIR
  participant LSP
  SourceFile->>HIR: request file_ast
  HIR-->>PPIR: cached AST items
  PPIR->>PPIR: generate synthetic items
  PPIR-->>HIR: expanded semantic index
  TIR->>TIR: read cached aliases and generic facts
  SourceFile->>LSP: check_file
  LSP->>HIR: read cached file_ast items
  HIR-->>LSP: AST items for validation
Loading

Possibly related PRs

Suggested reviewers: 2kai2kai2, codeshaunted, antoniosarosi

Poem

I’m a rabbit with a faster compile,
Caching ASTs in a tidy file.
Mimalloc hops, diagnostics gleam,
Dispatch skips through the compiler stream.
Type cycles curl, then safely unwind.

🚥 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 is specific and accurately summarizes the main performance-focused compiler and CLI optimizations in this PR.
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/cold-compile-reland

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 06:43 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
baml_language/crates/baml_compiler_diagnostics/src/render.rs (1)

237-242: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the match expression.

Since ariadne_cache is guaranteed to be Some if and only if config.format is DiagnosticFormat::Ariadne, matching on config.format inside the closure is redundant. You can simplify the code by matching directly on the ariadne_cache Option.

♻️ Proposed refactor
-        .map(|d| match (&config.format, &mut ariadne_cache) {
-            (DiagnosticFormat::Ariadne, Some(cache)) => {
-                render_ariadne(d, sources, cache, config.color)
-            }
-            _ => render_concise(d, sources, file_paths),
+        .map(|d| match &mut ariadne_cache {
+            Some(cache) => render_ariadne(d, sources, cache, config.color),
+            None => render_concise(d, sources, file_paths),
         })
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/baml_compiler_diagnostics/src/render.rs` around lines
237 - 242, Update the closure’s match in the diagnostic rendering flow to match
directly on ariadne_cache rather than pairing it with config.format. Render
Ariadne output when the cache is Some, passing the cached value to
render_ariadne, and use render_concise for None while preserving the existing
arguments and behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@baml_language/crates/baml_compiler_diagnostics/src/render.rs`:
- Around line 237-242: Update the closure’s match in the diagnostic rendering
flow to match directly on ariadne_cache rather than pairing it with
config.format. Render Ariadne output when the cache is Some, passing the cached
value to render_ariadne, and use render_concise for None while preserving the
existing arguments and behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b3d5aad6-94e8-47ee-b7df-474939c1ca62

📥 Commits

Reviewing files that changed from the base of the PR and between bd55eab and fae8abb.

⛔ Files ignored due to path filters (1)
  • baml_language/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • baml_language/crates/baml_cli/Cargo.toml
  • baml_language/crates/baml_cli/src/main.rs
  • baml_language/crates/baml_compiler2_ast/src/lower_expr_body.rs
  • baml_language/crates/baml_compiler2_hir/src/lib.rs
  • baml_language/crates/baml_compiler2_mir/src/lower.rs
  • baml_language/crates/baml_compiler2_ppir/src/lib.rs
  • baml_language/crates/baml_compiler_diagnostics/src/render.rs
  • baml_language/crates/baml_lsp2_actions/src/check.rs
  • baml_language/crates/baml_type/src/normalize.rs
  • baml_language/crates/tools_compile_profile/README.md

@github-actions

github-actions Bot commented Jul 16, 2026 •

Copy link
Copy Markdown

Binary size checks passed

✅ 7 passed

Artifact Platform File Gzip Gated on Baseline Delta Status
✅ baml-cli Linux 🔒 23.1 MB 9.8 MB file 22.8 MB +281.8 KB (+1.2%) OK
✅ packed-program Linux 🔒 16.3 MB 6.9 MB file 16.3 MB +81.9 KB (+0.5%) OK
✅ baml-cli macOS 🔒 17.7 MB 8.5 MB file 17.5 MB +236.0 KB (+1.3%) OK
✅ packed-program macOS 🔒 12.7 MB 6.0 MB file 12.6 MB +33.5 KB (+0.3%) OK
✅ baml-cli Windows 🔒 19.2 MB 8.7 MB file 18.7 MB +454.7 KB (+2.4%) OK
✅ packed-program Windows 🔒 13.6 MB 6.1 MB file 13.5 MB +52.7 KB (+0.4%) OK
✅ bridge_wasm WASM 15.2 MB 🔒 4.3 MB gzip 4.3 MB +9.6 KB (+0.2%) 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

@codspeed

codspeed Bot commented Jul 16, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

⚡ 2 improved benchmarks
❌ 3 regressed benchmarks
✅ 33 untouched benchmarks

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
❌ WallTime vm_speedtest_concurrency_parallel_sum_4x250k 37.3 ms 43.8 ms -14.87%
❌ WallTime vm_speedtest_compute_class_instances_100k 60.3 ms 70 ms -13.76%
❌ WallTime vm_speedtest_compute_divide_guard_1m 898 ms 1,010.6 ms -11.14%
⚡ WallTime vm_speedtest_concurrency_spawn_fan_out_100 1.7 ms 1.5 ms +14.26%
⚡ WallTime vm_speedtest_concurrency_shared_array_1w_4r 61.7 ms 54 ms +14.15%

Tip

Investigate this regression by commenting @codspeedbot fix this regression on this PR, or directly use the CodSpeed MCP with your agent.


Comparing perf/cold-compile-reland (b5b53e5) with canary (bd55eab)

Open in CodSpeed

@vercel
vercel Bot temporarily deployed to Preview – promptfiddle July 16, 2026 06:57 Inactive
hellovai and others added 2 commits July 16, 2026 00:28
… / callee_generics_for_func as tracked queries

Three package-invariant computations were being redone inside hot
per-scope/per-call query bodies (re-derived from PR #4016 against
current canary, post-#4032):

- package_resolved_aliases: the package alias map (a full clone of
  every alias Ty plus a project walk) was rebuilt inside every
  infer_scope_types execution (~15.6k executions on the baml_tests
  corpus), plus per callable_throws / validate_impl_signatures /
  projection-resolution call. Now a salsa-tracked query keyed by
  PackageId. It sits in a legitimate cycle (an alias whose RHS contains
  an associated-type projection resolves through inference, which reads
  the alias map), handled via cycle_initial seeding an EMPTY alias
  environment, mirroring infer_scope_types' cycle seed.
  TypeInferenceBuilder now borrows the cached map instead of owning a
  per-scope clone.

- package_impl_locs: existed as a plain function walking every file in
  the project per call, called from every impl-resolution loop,
  coherence checking and type-expr lowering. Now
  #[salsa::tracked(returns(ref))] per PackageId.

- callee_generics_for_func: every call site re-derived the callee's
  declared generic params (item-tree scan for the enclosing class) and
  re-lowered its bound type exprs. Now a tracked query per FunctionLoc.

Cold compile of crates/baml_tests/baml_src (tools_compile_profile from
PR #4038, BAML_NO_BYTECODE_CACHE=1, --repeat 5, median): total
2.347s -> 1.608s; check 1.06s -> 0.74s; emit 1.28s -> 0.86s. Cycle
iters unchanged at 13; identical diagnostics (0 errors / 100 warnings).

Co-authored-by: Cursor <[email protected]>
… into perf/cold-compile-reland

Co-authored-by: Cursor <[email protected]>

# Conflicts:
#	baml_language/crates/baml_lsp2_actions/src/check.rs
@vercel
vercel Bot temporarily deployed to Preview – beps July 16, 2026 07:40 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 July 16, 2026 07:48 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
baml_language/crates/baml_compiler2_tir/src/builder.rs (1)

140-224: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Manual salsa::Update impl is correct; consider a small unit test for callee_generics_for_func.

The unsafe impl salsa::Update for CalleeGenerics signature (maybe_update(old_pointer: *mut Self, new_value: Self) -> bool) matches Salsa's actual Update trait contract, and comparing via PartialEq before drop_in_place/write is a standard, safe early-cutoff pattern given CalleeGenerics holds only plain owned data (no 'db pointers) — the safety comment is accurate.

callee_generics_for_func itself (class-param scan + bound lowering) has no dedicated unit test. Given it's now a memoized, reusable query consumed at every call site, a focused test (similar to the existing compile()-based tests in lower_type_expr.rs) asserting class_params/user_params/user_bounds for a method inside a generic class would guard against regressions in this extraction.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@baml_language/crates/baml_compiler2_tir/src/builder.rs` around lines 140 -
224, Add a focused unit test for callee_generics_for_func using the existing
compile()-based test pattern from lower_type_expr.rs. Define a method inside a
generic class with a user generic parameter and bound referencing the class
parameter, then assert the returned CalleeGenerics class_params, user_params,
and user_bounds values. Keep the test scoped to verifying the memoized query’s
extraction and bound lowering.

Source: Path instructions

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@baml_language/crates/baml_compiler2_tir/src/builder.rs`:
- Around line 140-224: Add a focused unit test for callee_generics_for_func
using the existing compile()-based test pattern from lower_type_expr.rs. Define
a method inside a generic class with a user generic parameter and bound
referencing the class parameter, then assert the returned CalleeGenerics
class_params, user_params, and user_bounds values. Keep the test scoped to
verifying the memoized query’s extraction and bound lowering.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: cb73f1ae-1604-40af-952e-81b6b976aa8a

📥 Commits

Reviewing files that changed from the base of the PR and between fae8abb and b5b53e5.

📒 Files selected for processing (9)
  • baml_language/crates/baml_compiler2_tir/src/builder.rs
  • baml_language/crates/baml_compiler2_tir/src/builder/associated_projection.rs
  • baml_language/crates/baml_compiler2_tir/src/builder/interface_resolution.rs
  • baml_language/crates/baml_compiler2_tir/src/callable.rs
  • baml_language/crates/baml_compiler2_tir/src/inference.rs
  • baml_language/crates/baml_compiler2_tir/src/interfaces/coherence.rs
  • baml_language/crates/baml_compiler2_tir/src/interfaces/impl_rules.rs
  • baml_language/crates/baml_compiler2_tir/src/lower_type_expr.rs
  • baml_language/crates/baml_lsp2_actions/src/check.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • baml_language/crates/baml_lsp2_actions/src/check.rs

This branch was previously deployed

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