refactor(asm)!: collapse linking and cut fused dispatch overhead - #162
Conversation
Assembler.Build now returns the finished machine code for one block, so the Code container, its external relocation table, and the multi-entry Link contract are gone. Link installs those bytes into a Buffer and binds them through the ABI, and Buffer owns the write/execute transition around every install instead of exposing Unseal/Append/Seal to callers. Register allocation moves into the rewriter that already rewrote virtual operands, so one linear-scan pass owns the physical bank, the spill frame, and the label rebasing it forces. A build containing a back-edge runs without a spill frame, since linear-scan lifetimes only describe a forward-only stream. prof keeps one counter table per JIT metric family, each keyed by the row's label set, and rows order themselves through a self-referencing constraint rather than an interface type assertion per comparison. Drop asm.Code, asm.LabelOp, OpPseudoLabel, RegMask.PopFirst/Count, and the memory ptr/within helpers left unused by the collapse. BREAKING CHANGE: asm.Link takes one []byte and returns one Callable.
A fused source stays in a temporary instead of being pushed, so a fused handler grows the operand stack by exactly one slot no matter how many sources it folded. Emitting a source's overflow check only when it pushes on its own, and checking the consumer's net push once, removes 5,424 generated lines and every duplicated check. Bounds, segmentation, and underflow checks stay per source. Trapping arithmetic still materializes its operands on the stack, so it keeps a check per push. Interleaved A/B, median of five, threaded mode: BranchTree(96) -4.3%, TypedArraySum(256) -4.1%, IterativeFib(30) -3.6%, Sieve(256) -2.8%. Native and adaptive modes are unchanged within noise.
Compile emits one handler per code byte, so a rethreaded table describes the same function at the same length as the one already installed. Copying into that table instead of replacing it rewires every frame currently executing the function, which drops the frame scan that existed only to repoint each active frame at the new slice.
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (42)
📝 WalkthroughWalkthroughThe PR replaces relocation-based assembler linking with direct byte encoding and single-callable installation, restructures ARM64 register rewriting, updates interpreter fusion and JIT execution paths, refactors profiling counters, and rewrites repository coding and testing guidance. ChangesAssembler and executable-code pipeline
Interpreter and JIT execution
JIT profiling metrics
Repository guidance and documentation
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related issues
Possibly related PRs
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #162 +/- ##
==========================================
+ Coverage 30.29% 32.13% +1.84%
==========================================
Files 86 85 -1
Lines 59886 55920 -3966
==========================================
- Hits 18141 17970 -171
+ Misses 40467 36813 -3654
+ Partials 1278 1137 -141 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Summary
Continues the in-flight
asm/profrefactor to a working state, then takes onepass of quality -> performance -> quality over the runtime.
asmlinking collapses onto a single code block.Assembler.Buildreturns thefinished machine code for one block, so the
Codecontainer, its externalrelocation table, and the multi-entry
Linkcontract are gone.Bufferowns thewrite/execute transition around every install rather than exposing
Unseal/Append/Seal. Register allocation folds into the rewriter that alreadyrewrote virtual operands, so one linear-scan pass owns the physical bank, the
spill frame, and the label rebasing it forces.
profkeeps one counter table per JIT metric family, keyed by the row's labelset, with rows ordering themselves through a self-referencing constraint instead
of an interface type assertion per comparison.
Fused handlers check stack room once. A fused source stays in a temporary
instead of being pushed, so a fused handler grows the operand stack by exactly
one slot no matter how many sources it folded. Emitting a source's overflow check
only when it pushes on its own removes 5,424 generated lines and every duplicated
check.
Performance
Interleaved A/B, median of five, threaded mode:
BranchTree(96)TypedArraySum(256)IterativeFib(30)Sieve(256)Native and adaptive modes are unchanged within noise. No kernel regressed.
docs/benchmarks.mdcarries a full re-measured cross-runtime table (90 rows).Rejected simplification
Narrowing
dispatchto reload the frame's handler table only on a frametransition measured 15% slower (724 -> 843 ns on
IterativeFib(30)): thebranch misprediction costs more than the unconditional slice reload. Reverted;
the unconditional reload stays.
Testing
make check(generated-code parity, tidy, fmt, vet, tests, arm64 cross-build)go test -race ./...interp/interp_test.gopin the fused stack-room contractinternal/jitcheckis a temporary black-box harness proving the JIT stilllowers, spills, links, and executes through
interp's public API after theasmcollapseBreaking changes
asm.Linktakes one[]byteand returns oneCallable.asm.Code,asm.LabelOp,OpPseudoLabel, andRegMask.PopFirst/Countare removed.Both are within the
asm/profsurface where public API changes are in scope.Summary by CodeRabbit
New Features
Improvements
Documentation