Repository navigation
Escape analysis: bytecode skips locks for thread-local objects - #5407
harivansh-afk wants to merge 1 commit into
Conversation
The bytecode consumer of the escape analysis. The emitter picks lock-free opcodes where the analysis proves an object thread-local: LoadArrayElementLocal, StoreArrayElementLocal and ContainerLenLocal; LoadFieldLocal and StoreFieldLocal with Relaxed ordering; and CallLocal, which makes the call path select a builtin's $local twin (FunctionKind::Native carries the twin). A for-loop over an array the loop alone walks compiles to ArrayIterNext over a hidden index slot instead of an iterator object and a next() call; ArrayIterNextLocal reads a thread-local array without its lock.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Warning Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption. Comment |
⏭️ Performance benchmarks were skippedPerf benchmarks (CodSpeed) are opt-in on pull requests — they no longer run on every push. They always run automatically after merge to To run them on this PR, do any of the following, then push a commit (or re-run CI):
|
Binary size checks failed❌ 2 violations · ✅ 1 passed
Details & how to fixViolations:
Add/update baselines:
[artifacts.bridge_wasm]
file_bytes = 28382010
gzip_bytes = 8239553
[artifacts.packed-program]
file_bytes = 40975848
gzip_bytes = 16725065Generated by |
|
Closing: the interpreter half of escape analysis is dropped. Only the native backend keeps lock elision (#5395, rebased onto canary with the interpreter, bytecode emitter and opcode table byte-identical to canary). |
Containers gain lock_thread_local / lock_mut_thread_local, which skip the container lock for an object no other task can reach; debug builds still try the lock and panic if another holder has it. Instance fields gain Relaxed load and store. The compiled runtime exposes _local accessors for element, length and field access. Builtins declare their memory effects with //baml:effects(...): what they keep, what they return, whether they compare keys. A declared array or map method gets a generated $local twin that reads its receiver without the lock; the map driver behind has/get/set/delete/index/get_or_insert takes its guards without the lock when a twin started it. (rebased onto canary) Applied without conflicts. Two comments that said both backends select the $local twins now say the native backend does, since the bytecode half (#5407) is not part of this branch: baml_builtins2_codegen/src/types.rs, bex_vm/src/package_baml/map.rs.
TLDR
Stacked on #5395. The bytecode consumer of the escape analysis: the interpreter skips container locks and field atomics on objects the analysis proves task-local, and compiles a
forover an array the loop alone walks as an index walk instead of an iterator object plus anext()call.How
escape::EscapeFactsproves the object thread-local:LOAD_ARRAY_ELEMENT_LOCAL,STORE_ARRAY_ELEMENT_LOCAL,CONTAINER_LEN_LOCAL, andLOAD_FIELD_LOCAL/STORE_FIELD_LOCAL(Relaxed ordering). All are appended to preserve discriminants.CALL_LOCALmakes the call path select a builtin's$localtwin, the same lock-free glue the native backend calls.FunctionKind::Nativecarries the twin pointer.ARRAY_ITER_NEXT{iterator, index}steps an array walk over a hidden index slot, re-reading the length each step asArrayIterator.nextdoes.ARRAY_ITER_NEXT_LOCALskips the lock on a thread-local array.Results (Spark, aarch64,
vm-off, vs the merge base)Most of this is the index walk, which is structural, not lock removal. cycles-into-live shows 0.81× in a 3-job window, but per job it is 2× faster with 33% less peak memory. A major collection lands inside the candidate's window and outside the base's.
Not included
A bytecode locality mask (parameter accesses that depend on what the caller proved) is designed but not built: 9 opcodes for a small expected gain, since interpreter dispatch dominates. Notes in
perf/bytecode_consumer.md.Testing
rust_backendacceptsCALL_LOCAL.brr/escape-analysis-full), which passed the full rust_differential suite with debug assertions, ThreadSanitizer on all escape probes in both modes, and the BAML test suite at O2.