Skip to content

perf(compile): parallel check/MIR/emit + inference dedup (0.63s → 0.27s cold, byte-identical) - #4073

Merged
hellovai merged 6 commits into
canaryfrom
perf/reland-cold-compile-v2
Jul 17, 2026
Merged

perf(compile): parallel check/MIR/emit + inference dedup (0.63s → 0.27s cold, byte-identical)#4073
hellovai merged 6 commits into
canaryfrom
perf/reland-cold-compile-v2

Conversation

@hellovai

@hellovai hellovai commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Makes BAML compilation parallel by default — no flags, no env gates. `RAYON_NUM_THREADS=1` gives the serial reference path. Plus three single-threaded fixes for redundant work found by profiling.

Commits

  1. parallel check + parallel emit codegen — per-file `check_file` and per-function stackification on rayon's global pool, cloned shared-storage salsa handles (rust-analyzer model). Emit workers compile into watermark-based fragment pools; the serial merge replays cross-function `GenericFunction` interning in original order, reproducing the serial pool layout exactly.
  2. scope-inference dedup — `check_file` drove diagnostics with HIR-minted ScopeIds while TIR/MIR key by PPIR's expanded-index ScopeIds, so every scope in a `$stream`-expanded file was inferred twice and re-inferred during emit. Now keyed by PPIR ScopeIds (HIR-prefix gated): `infer_scope_types` 13,331 → 8,719 executions.
  3. function_body discriminant — step 6 deep-cloned every function's ExprBody arena just to test its kind; reads the item tree instead (~2,256 clones gone).
  4. `is_subtype` fast paths — reflexivity + narrow head-mismatch reject (interface/interface pairs excluded — cross-name interface subtyping via `requires` is real).
  5. parallel MIR lowering — the emit `Db` trait gains a defaulted `parallel_db_handle()`; `ProjectDatabase` mints cloned handles so Stage A's `lower_function` fans out too.

Byte-identity

Permanent test `parallel_emit_is_byte_identical_to_serial`: full corpus compiled on a 1-thread pool vs the default pool, identical serialized bytes (covers the `ns_instantiation_expr` cross-function interning fixtures). Parallel check verified byte-identical diagnostics over two corpora. All suites green: 444/444 lsp2, 1,658/1,658 tir/diagnostics/compiles/emit/mir/instantiation.

Numbers (cold, disk cache off, medians of 5)

corpus serial before this PR
baml_tests (77 files, 25k lines) 0.63 s 0.265 s (check 0.18, emit 0.08)
agent-tries-baml (53 files, 13k lines, check-only) 0.21 s 0.126 s

With #4058 merged earlier this week: cold compile 2.39 s → 0.27 s (~9×).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Performance
    • Improved compilation and diagnostics performance by parallelizing bytecode emission and per-file diagnostic collection for larger projects.
  • Bug Fixes
    • Fixed class-field metadata and pooled object index handling to ensure correct field/type resolution during compilation.
    • Improved determinism guarantees so serial and parallel output remains byte-identical.
    • Corrected scope diagnostics to use canonical scope IDs and streamlined function signature checks.
    • Improved subtype compatibility checks with early fast-path decisions.
  • Tests
    • Added determinism coverage comparing serial vs parallel emit results.

…l output

Check: collect_compiler2_diagnostics fans per-file check_file out on
rayon's global pool (first file serial to warm package-level queries;
each task owns a cloned shared-storage salsa handle — ProjectDatabase is
Send but deliberately !Sync, so handles are pre-cloned and moved in;
projects of <=8 files stay serial). Output unchanged by construction —
diagnostics are sorted at the end; verified byte-identical on two corpora.

Emit: Pass 4's MIR lowering (salsa) stays serial; the pure codegen core
(lambda + function stackification) runs across rayon workers, each
compiling into a watermark-based fragment pool (objects_base threaded
through MirCodegenContext; class-field reads served by a precomputed
ClassFieldSnapshot instead of live pool reads). The serial merge replays
fragments in original function order with per-object index mapping and
cross-function GenericFunction interning replay, then rewrites operands
via the relink visitors — reproducing the serial pool layout exactly.

A 1-thread rayon pool (RAYON_NUM_THREADS=1) takes the serial reference
paths; the emit_determinism suite gains a permanent test asserting the
parallel output is byte-identical to serial over the full test corpus
(including the ns_instantiation_expr cross-function interning fixtures).

Cold check+emit on the 25k-line corpus: 0.63s -> 0.41s (check 0.38->0.21,
emit 0.27->0.19; medians of 5, disk cache off). The 13k-line
agent-tries-baml corpus checks in ~140ms.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cursor

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

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.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Rayon dependencies enable parallel compiler function emission and large-project diagnostics. Compiler code snapshots class fields, tracks absolute object-pool offsets, merges worker fragments, and validates serial/parallel byte identity. Diagnostic scope handling and subtype checks also gain targeted fast paths.

Changes

Parallel compiler emission

Layer / File(s) Summary
Emission context and pooled-object indexing
baml_language/Cargo.toml, baml_language/crates/baml_compiler2_emit/Cargo.toml, baml_language/crates/baml_compiler2_emit/src/emit.rs, baml_language/crates/baml_compiler2_emit/src/lib.rs
Codegen uses class-field snapshots and objects_base to resolve fields and mint absolute pooled-object indices.
Serial and parallel Pass 4 emission
baml_language/crates/baml_compiler2_emit/src/lib.rs, baml_language/crates/baml_project/src/db.rs
Pass 4 selects serial or parallel compilation, merges worker fragments, registers function metadata, and obtains movable database handles.
Lambda and init helper propagation
baml_language/crates/baml_compiler2_emit/src/lib.rs
Nested lambdas and $init helpers receive class-field snapshots and object-pool bases.
Serial-versus-parallel output validation
baml_language/crates/baml_tests/Cargo.toml, baml_language/crates/baml_tests/tests/emit_determinism.rs
Serialized output from single-threaded and four-threaded compilation is compared byte-for-byte.

Parallel project diagnostics

Layer / File(s) Summary
Chunked file diagnostics
baml_language/crates/baml_project/Cargo.toml, baml_language/crates/baml_project/src/check.rs
Projects with more than eight source files use chunked Rayon tasks, cloned database handles, deterministic sorting, and total-order tests.

Scope and subtype checks

Layer / File(s) Summary
Canonical scope and subtype fast paths
baml_language/crates/baml_lsp2_actions/src/check.rs, baml_language/crates/baml_type/src/normalize.rs
Scope diagnostics use canonical PPIR scope IDs, expression-body detection avoids an extra query, and subtype checks add equality and definite-mismatch short circuits.

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

Sequence Diagram(s)

sequenceDiagram
  participant CompilerPass4
  participant RayonWorkers
  participant WorkerPools
  participant ProgramObjectPool
  CompilerPass4->>RayonWorkers: compile function bodies
  RayonWorkers->>WorkerPools: emit bytecode and pooled objects
  WorkerPools->>ProgramObjectPool: merge fragments with absolute indices
  ProgramObjectPool-->>CompilerPass4: register compiled functions
Loading
sequenceDiagram
  participant ProjectDatabase
  participant RayonTasks
  participant FileChecker
  participant DiagnosticsCollector
  ProjectDatabase->>RayonTasks: clone database per chunk
  RayonTasks->>FileChecker: check files in parallel
  FileChecker->>DiagnosticsCollector: send chunk diagnostics
  DiagnosticsCollector-->>ProjectDatabase: append package diagnostics and sort
Loading

Possibly related PRs

  • BoundaryML/baml#3924: Uses the compiler emission and reuse APIs whose database interfaces changed here.

Suggested reviewers: antoniosarosi

Poem

A rabbit packed objects in rows,
With Rayon humming as it goes.
Fields stayed clear, indices bright,
Serial matched parallel byte.
“Hop!” cried the tests, “the output’s right!”

🚥 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 accurately summarizes the main changes: parallel check/MIR/emit work plus inference dedup and determinism improvements.
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-cold-compile-v2

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 17, 2026 06:14 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 July 17, 2026 06:21 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.

Actionable comments posted: 3

🤖 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.

Inline comments:
In `@baml_language/crates/baml_compiler2_emit/src/lib.rs`:
- Around line 4194-4195: Update the documentation comment for Pass 4 near
emit_functions_serial to remove the obsolete BAML_PARALLEL_EMIT=1 requirement,
and describe parallel emission as being selected automatically based on the
Rayon thread count.

In `@baml_language/crates/baml_project/src/check.rs`:
- Line 76: Update sort_diagnostics to define a total deterministic ordering by
adding tie-breakers after file, start, and message, including available range
end, diagnostic ID, severity, phase, annotations, and related information. Add a
unit test using diagnostics with identical primary sort keys to verify
consistent ordering, then run cargo test --lib.

In `@baml_language/crates/baml_tests/tests/emit_determinism.rs`:
- Around line 102-107: Update the parallel case in the determinism test to
execute compile_to_bytes within an explicit multi-thread Rayon thread pool,
using the pool’s install method. Keep the existing one-thread pool for the
serial case and ensure the parallel pool has more than one worker so
emit_functions_parallel is exercised.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: feff115d-6525-466e-a55a-45966a0c17b2

📥 Commits

Reviewing files that changed from the base of the PR and between 7d9a665 and af73021.

⛔ Files ignored due to path filters (1)
  • baml_language/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • baml_language/Cargo.toml
  • baml_language/crates/baml_compiler2_emit/Cargo.toml
  • baml_language/crates/baml_compiler2_emit/src/emit.rs
  • baml_language/crates/baml_compiler2_emit/src/lib.rs
  • baml_language/crates/baml_project/Cargo.toml
  • baml_language/crates/baml_project/src/check.rs
  • baml_language/crates/baml_tests/Cargo.toml
  • baml_language/crates/baml_tests/tests/emit_determinism.rs

Comment thread baml_language/crates/baml_compiler2_emit/src/lib.rs Outdated
Comment thread baml_language/crates/baml_project/src/check.rs
Comment thread baml_language/crates/baml_tests/tests/emit_determinism.rs Outdated
@github-actions

github-actions Bot commented Jul 17, 2026

Copy link
Copy Markdown

Binary size checks failed

2 violations · ✅ 5 passed

⚠️ Please fix the size gate issues or acknowledge them by updating baselines.

Artifact Platform File Gzip Gated on Baseline Delta Status
baml-cli Linux 🔒 25.2 MB 10.7 MB file 24.5 MB +727.9 KB (+3.0%) OK
packed-program Linux 🔒 17.0 MB 7.0 MB file 17.0 MB -4.1 KB (-0.0%) OK
baml-cli macOS 🔒 19.5 MB 9.3 MB file 18.9 MB +611.9 KB (+3.2%) FAIL
packed-program macOS 🔒 13.2 MB 6.2 MB file 13.2 MB +0 B (+0.0%) OK
baml-cli Windows 🔒 21.0 MB 9.5 MB file 20.4 MB +612.4 KB (+3.0%) OK
packed-program Windows 🔒 14.1 MB 6.2 MB file 14.1 MB -512 B (-0.0%) OK
bridge_wasm WASM 16.2 MB 🔒 4.4 MB gzip 4.3 MB +130.5 KB (+3.1%) FAIL

🔒 = 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.

Details & how to fix

Violations:

  • baml-cli (macOS) file_bytes: 19.5 MB exceeds limit of 19.5 MB (exceeded by +44.9 KB, policy: max_file_bytes)
  • baml-cli (macOS) file_delta_pct: +3.2% exceeds limit of 3.0% (exceeded by +0.2pp, policy: max_delta_pct)
  • bridge_wasm (WASM) gzip_bytes: 4.4 MB exceeds limit of 4.4 MB (exceeded by +2.3 KB, policy: max_gzip_bytes)
  • bridge_wasm (WASM) gzip_delta_pct: +3.1% exceeds limit of 3.0% (exceeded by +0.1pp, policy: max_delta_pct)

Add/update baselines:

.ci/size-gate/aarch64-apple-darwin.toml:

[artifacts.baml-cli]
file_bytes = 19512048
stripped_bytes = 19512096
gzip_bytes = 9320305

.ci/size-gate/wasm32-unknown-unknown.toml:

[artifacts.bridge_wasm]
file_bytes = 16160508
gzip_bytes = 4402884

Generated by cargo size-gate · workflow run

@vercel
vercel Bot temporarily deployed to Preview – promptfiddle July 17, 2026 06:36 Inactive
hellovai and others added 4 commits July 16, 2026 23:40
check_file drove its per-scope diagnostics loop with HIR-minted salsa
ScopeIds while TIR internals and MIR lowering key infer_scope_types by
PPIR's expanded-index ScopeIds — so every scope in a $stream-expanded
file was type-inferred twice (once per key space; ~4.3k duplicate
executions on the test corpus, re-run during emit). The loop now
iterates the PPIR index's scope_ids, gated to the HIR prefix so
synthetic $stream scopes surface no new diagnostics. The prefix
alignment of original scopes in the expanded index is already
load-bearing in TIR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Step 6 called hir::body::function_body — which deep-clones the whole
ExprBody arena per execution — for every function, only to test the
body's discriminant. The item tree in hand already carries it; test
func_data.body directly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…subtype

is_subtype ran two allocating canonicalization walks on every call even
when both sides are the identical type. Add the same reflexivity
short-circuit equivalent() has, plus a conservative head-mismatch reject
that deliberately excludes interface/interface pairs (cross-name
interface subtyping via requires is real); the remaining same-kind
nominal pairs are provably false in is_subtype_of_inner.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stage A of the parallel emit pass still ran lower_function serially —
the emit crate only sees &dyn Db, which cannot be cloned for worker
threads. The (previously empty) emit::Db marker trait gains a defaulted
parallel_db_handle() -> Option<Box<dyn mir::Db + Send>>; ProjectDatabase
overrides it with a cloned shared-storage handle. Stage A now lowers the
first function serially (memo warm-up) and fans the rest out in chunks
on rayon::scope, one pre-cloned handle moved per chunk (the same pattern
as parallel check), results reassembled in original order. Serial
fallback for small function counts, 1-thread pools, or dbs that mint no
handles — the byte-identity reference path is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@hellovai hellovai changed the title perf(compile): parallel check + parallel emit on rayon (0.63s → 0.41s cold, byte-identical) perf(compile): parallel check/MIR/emit + inference dedup (0.63s → 0.27s cold, byte-identical) Jul 17, 2026
@vercel
vercel Bot temporarily deployed to Preview – beps July 17, 2026 06:45 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 July 17, 2026 06:52 Inactive
… emit test

Addresses CodeRabbit review on #4073:

- sort_diagnostics only sorted by (file, start, message), leaving distinct
  diagnostics tied on those in arrival order. Under parallel check that
  order is nondeterministic, so output could vary between runs. The primary
  key is unchanged (non-tied output is byte-identical), and exact ties are
  now broken by span end + a structural Debug encoding of the remaining
  fields (id/severity/phase/annotations/related_info) — a total order.
  Adds a unit test that sorts every permutation of primary-key-tied
  diagnostics to the same sequence.
- emit_determinism's parallel case ran in the ambient rayon pool, which is
  single-threaded on a 1-core runner — silently taking the serial path and
  testing nothing. Both cases now use explicit pools (1 vs 4 threads).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel
vercel Bot temporarily deployed to Preview – beps July 17, 2026 06:59 Inactive
@hellovai
hellovai enabled auto-merge July 17, 2026 07:01

@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_project/src/check.rs (1)

218-218: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Consider using sort_unstable_by.

Since sort_diagnostics now establishes a total order where ties are completely identical, stable sorting is no longer required. Replacing sort_by with sort_unstable_by will avoid allocating a temporary buffer and generally improve performance.

♻️ Proposed refactor
-    diagnostics.sort_by(|a, b| {
+    diagnostics.sort_unstable_by(|a, b| {
🤖 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_project/src/check.rs` at line 218, In
sort_diagnostics, replace diagnostics.sort_by with diagnostics.sort_unstable_by
while preserving the existing comparator and total ordering 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_project/src/check.rs`:
- Line 218: In sort_diagnostics, replace diagnostics.sort_by with
diagnostics.sort_unstable_by while preserving the existing comparator and total
ordering behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fd253f73-1e76-4212-b72b-f41bca452f81

📥 Commits

Reviewing files that changed from the base of the PR and between 5e0a94c and 4ec32b4.

📒 Files selected for processing (2)
  • baml_language/crates/baml_project/src/check.rs
  • baml_language/crates/baml_tests/tests/emit_determinism.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • baml_language/crates/baml_tests/tests/emit_determinism.rs

@vercel
vercel Bot temporarily deployed to Preview – promptfiddle2 July 17, 2026 07:06 Inactive
@vercel
vercel Bot temporarily deployed to Preview – promptfiddle July 17, 2026 07:20 Inactive
@hellovai
hellovai added this pull request to the merge queue Jul 17, 2026
Merged via the queue into canary with commit 1ebf901 Jul 17, 2026
62 of 63 checks passed
@hellovai
hellovai deleted the perf/reland-cold-compile-v2 branch July 17, 2026 07:34
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