Repository navigation
Conversation
|
Thanks for the careful work here. The change is memory-safe in everything we tried, and it does remove the join sink from #1512 (on Metal at depth 20, 8.6 s down to 0.11 s). We're closing it anyway, because it goes against a deliberate rule of the borrow inference (9015fe1): "a fork's join keeps a rooted read only when the body uses it". Readers of a fork borrow, and the owner lets go after the join. Making the last readers owners changes that for the whole program. Ownership is decided once per def and counting once per type, so every call of those readers and every node of that type pays for it. On the cluster (M4, median of 7 interleaved runs):
None of this shows up in the perf gate, because no bench's C changes. The fast path already works under the current rule: free the tree after the join with a forked walk ( |
Fixes #1512.
The fork emitter retained a locally owned tree until its last borrowed readers joined, then erased the entire tree in one lane. Last fork readers now receive counted ownership together through the existing
val_ownfact gate. Argument materialization accounts for the still-pending calls and continuation, so earlier readers obtain their references before the last argument consumes the original. The obsolete fork-specific argument-counting mode is removed. A retained continuation owner remains live; an outside borrow follows the existing loan propagation. No one-owner/one-borrower handoff, leak, unsafe mode, GPU guard bypass, or new runtime scheduler.Permanent lifetime coverage uses two different recursively forked readers, a returned tree consumed after the join, and a bang. Existing mixed consumer/dead-reader/array/fork cases remain. Obsolete comments promising a whole-value join sink were updated; hand-written GUIDE.md, CHANGELOG.md, protected bend.ts and all gates/caps are unchanged.
Real behavior proof
Linux x86_64, Bun CLI 1.3.13, Node v22.23.1, clang 22.1.8. Refreshed onto main
381dc877, including the merged #1509 budget reduction and #1506 number seam.4268752896. These are local CPU whole-process measurements, not GPU/kernel timings; all samples retained, including two slower early fixed runs.term_sink(e, _h_3); the fixed join captures only the two U32 results. The readers own and free their references instead; the sink was not merely deleted.6144,11264,6144.clang -std=c11 -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer ... -lpthread -lm; original shared-tree workload at depth 16, 40 rounds, 8 workers prints4292345856, with no sanitizer findings observed. The custom heap pool means ASan alone does not prove zero pool leaks.bun gates/repo.ts: 54/54. Existing 64,000-token compiler cap unchanged.Exact workload command:
./fork_drop 0 20 25 --gpu off --threads 8. Safety commands:bend tests/reg/fork_last_readers_tree.bend -o safety, then./safety --gpu off --threads 1and--threads 8.Limits and tradeoff
No CUDA/Metal execution or GPU performance claim; an actual
--gpu onattempt reports that this Linux binary has no CUDA or Metal support. The 48-mini test/perf gates and graphical shader benchmarks were not run. Borrow/own calling convention is inferred per definition, so ownership facts can affect other calls to a reader; retained-owner and mixed-reader paths are covered locally, but device performance still requires upstream hardware qualification. No flaky permanent wall-clock threshold was added: interleaved benchmark evidence covers the performance change; permanent regressions cover consumer-visible lifetime correctness.