refactor: Remove ARM64 and Compiler Implementation#53
Conversation
- Deleted the ARM64 specific register initialization code. - Removed the entire compiler implementation, including the Compiler struct and its methods. - Eliminated the Lowerer interface and its Context struct, which were responsible for opcode lowering. - Removed the Module struct and its associated methods for managing compiled functions. - Deleted the Slots management code that handled indirection for callable entries. - Cleaned up the segment handling and invocation logic, including the Call and Outcome types. - Updated the boxed types to improve the range checks for boxable integers. - Added new test cases for the IsBoxable function to cover edge cases for integer bounds.
siyul-park
left a comment
There was a problem hiding this comment.
PR Review — Refactor JIT Compiler: Remove ARM64 and Compiler Implementation
Decision
Request Changes
This PR should not merge in its current state due to insufficient test coverage of new JIT code and missing context about the refactoring rationale.
Merge Readiness Summary
The PR successfully removes the old jit/ package architecture and moves JIT compilation inline into interp/, with all local tests passing and the build succeeding. However, the new JIT implementation lacks adequate test coverage (5.67% vs 51.42% target), and the PR description provides insufficient context about why this major refactor was undertaken and what acceptance criteria it meets.
Blocking Findings
1. Insufficient Test Coverage for New JIT Code
- Severity: Blocking
- Evidence: Codecov reports only 5.67% of the diff is covered by tests (target: 51.42%). The new
interp/jit.go(771 lines) andinterp/jit_arm64.go(1535 lines) contain critical VM runtime code that generates native machine code. - Why it matters:
- JIT is a high-risk component; bugs here cause VM crashes, data corruption, or silent incorrect results
- This is a major refactor moving the architecture from a separate package to inline
- 2,306 lines of new/heavily modified JIT code with low test coverage increases regression risk significantly
- The project's stated target is 51.42% coverage for diffs
- Minimal fix:
- Add test cases that exercise the new JIT code paths in
interp/interp_test.go - Focus on: opcode lowering logic, frame metadata handling, scratch register management, fallback paths
- Run
go test ./interp/...to verify coverage improves
- Add test cases that exercise the new JIT code paths in
2. Missing PR Context and Linked Issue
- Severity: Blocking
- Evidence: PR description lists what was deleted but provides no rationale, no linked issue, and no explanation of the new architecture's benefits
- Why it matters:
- This is a 5,494-line deletion of core functionality; reviewers need to understand why
- Without context, it's unclear if this is an incremental step in a larger plan or a standalone refactor
- No acceptance criteria are stated
- Minimal fix:
- Update PR description to explain: why this refactor improves the codebase, what architectural benefits it provides, what issue(s) it addresses
- Link any related issue(s) (e.g., if this is part of a larger effort like #50)
- State the acceptance criteria clearly
Important Findings
1. Overall Project Coverage Actually Improved
- Evidence: codecov/project shows 55.87% coverage, a +4.44% improvement over the base
- Implication: The refactor itself is not harmful to overall coverage; the issue is specific to untested new code paths
2. Mergeable State is "Unstable"
- Evidence: GitHub reports
mergeable_state: "unstable" - Status: This appears to be due to pending codecov checks, not a merge conflict; should resolve once coverage is addressed
Questions
- Is this refactor part of the larger JIT redesign effort from PR #50? If so, what does this PR add beyond that work?
- Are there specific JIT code paths that are intentionally not covered by tests (e.g., rare fallback scenarios)? If so, please document this in the PR description.
Readiness Checklist
- Scope control: Pass — PR is focused on removing old architecture and moving to new inline approach
- Correctness: Pass — Local tests pass, build succeeds, no broken functionality detected
- Test coverage: Needs work — New JIT code (2,306 lines) has only 5.67% coverage; target is 51.42%
- Architecture consistency: Pass — Documentation is updated; new architecture aligns with stated design
- Risk / compatibility: Pass — No public API breakage detected; internal refactor
- Maintainability: Pass — Code structure is reasonable; architecture is documented
- CI / validation: Needs work — codecov/patch check failing due to insufficient coverage
Minimal Path to Merge
-
Add test coverage for new JIT code paths:
- Expand
interp/interp_test.gowith cases exercising opcode lowering, frame metadata, scratch registers - Target: increase diff coverage from 5.67% to ≥51.42%
- Run
go test ./interp/...to verify
- Expand
-
Update PR description to include:
- Rationale: why this refactor improves the codebase
- Context: link to related issues (if any) or explain as standalone effort
- Acceptance criteria: what success looks like
-
Re-run CI to verify codecov/patch passes
Final Recommendation
Fix blocking findings and resubmit. The PR is architecturally sound and local tests pass, but the new JIT code must have adequate test coverage before merge. Adding context to the PR description will also help reviewers understand the scope and rationale.
Generated by Claude Code
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #53 +/- ##
==========================================
+ Coverage 51.42% 56.03% +4.60%
==========================================
Files 68 62 -6
Lines 11700 10726 -974
==========================================
- Hits 6017 6010 -7
+ Misses 5086 4111 -975
- Partials 597 605 +8 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Design note: keep native execution across partial JIT call entriesThe current direct-call JIT work proves that complete JIT entries can call each other with native The target behavior should be broader:
Current limitationThe existing native direct-call path runs nested calls on the host stack and does not push a real VM frame for each native callee. That works only while the whole call subtree stays native. It is unsafe for partial entries because fallback needs interpreter-visible frame metadata:
The Go-side segment/entry wrappers can restore this metadata only at wrapper boundaries. A nested native callee entered by Proposed implementationMake native direct CALL frame-aware.
Acceptance criteria
Why this is the right directionThis keeps the policy simple: native execution should continue whenever the VM has a compiled entry and can prove/recover the interpreter state needed for fallback. The important invariant is not “complete JIT only”; it is “every native call that may fallback must have a recoverable VM frame”. Once that invariant holds, the JIT can keep execution native for the longest valid prefix and safely return to the interpreter only at real unsupported boundaries. |
Follow-up design: i64 guard-free complete JIT vs native deopt/frame reconstructionThere are two related ways to close the remaining recursive numeric gap, especially for the i64 factorial-style benchmark where the function entry can be JIT-compiled but complete JIT is rejected because i64 operations emit guard fallback paths. These are not mutually exclusive. The range/fact approach is a narrower Phase 1 optimization. Native deopt + VM frame reconstruction is the broader mechanism that also enables partial-entry native calls and maximal native execution across fallback boundaries. Option A: i64 range/fact analysis for guard-free complete JITGoalAllow complete JIT for i64 code when the compiler can prove that selected i64 operations stay inside the inline 49-bit boxed i64 range and therefore do not need a native fallback path. Today, i64 lowering emits fallback for cases such as:
Because complete JIT rejects reachable native fallback, these fallback-capable i64 ops can prevent whole-function entry compilation even when the concrete benchmark values are safe. Proposed modelAdd a lightweight fact/range analysis for JIT compilation:
Lowering changeWhen facts prove safety:
When facts are absent or too weak:
Scope limitsKeep this deliberately small at first:
Acceptance criteria
TradeoffThis is the smaller implementation and likely improves the exact i64 benchmark quickly. It does not solve native-to-partial-entry calls, nested fallback, or general “stay native as long as possible” behavior. Option B: native deopt + VM frame reconstructionGoalMake native direct calls safe even when the callee is only partially JIT-compiled or may fallback later. This lets the JIT keep execution native for the longest possible prefix and return to the interpreter only at real unsupported boundaries. This generalizes the earlier direct-call design: the key invariant should be “every native call that can fallback has a recoverable VM frame,” not “only complete JIT calls can use native Proposed modelNative direct CALL should become VM-frame-aware:
Trap kindsUse explicit trap kinds rather than overloading only one bit:
This will make nested deopt easier to reason about than relying on one Acceptance criteria
TradeoffThis is the more complete and more invasive design. It solves the general “maximal native prefix” problem and subsumes the partial-entry call design. It also reduces the need to prove every i64 path statically, because unsafe paths can deopt safely. Recommended pathImplement both in phases:
If implementation effort must be spent only once, prefer Option B. It is the standard long-term mechanism. Option A is best as a quick, low-risk performance win for proven numeric kernels. |
Clarification: integrate Option B into the partial-entry designOption B from the follow-up note should not be treated as a separate competing design. It is the concrete implementation strategy for the partial-entry native-call design above. The integrated design is:
With this invariant, “complete JIT only” is no longer the boundary for native calls. The boundary becomes: native may continue as long as every possible fallback has a recoverable VM frame. Where i64 range/fact analysis fitsThe i64 range/fact design remains useful, but as an optional fast path rather than the core mechanism.
The general path should be native deopt + VM frame reconstruction. Then i64 overflow / heap-promoted operands can safely fallback when facts are not strong enough, while proven-safe i64 paths can still skip guards for speed. Practical phase split
This keeps the main design unified with the earlier partial-entry proposal and avoids making i64 facts a prerequisite for preserving native execution across calls. |
Frame-aware native direct calls + nested deopt (PR #53 Option B)ContextPR #53 design notes (comment 4642665810 + follow-ups 4642692961) ask for one Today (verified)
Decisions (from user)
The frame journalAdd Flat layout (indices, all
Invariant
Native protocol
Go-side deopt reconstruction (in the entry/segment wrapper)After
The Phased workPhase 1 — ABI reshape, no behavior change
Phase 2 — frame-aware CALL + nested deopt
Phase 3 — enter partial ip=0 entries
Phase 4 — tests & benchmarks
Risks
Verification
|
interp, removing the old standalonejit/package and slot indirection.types.IsBoxable.Changes Made
jitpackage (Compiler,Lowerer,Module,Slots, segment call/result wrappers) and inlined the remaining JIT orchestration ininterp/jit.go.interp/jit_arm64.goplus a non-ARM64 stub that disables native JIT cleanly.asm.Callableto accept scratch argv values directly and updated the ARM64 trampoline to load/store X10-X14 from that buffer.asm.Frame/asm/arm64.Frameand expanded rewriter tests for spill/frame behavior.Rationale
The old design split opcode lowering, segment linking, callable indirection, and interpreter installation across
jit/,asm/, andinterp/. That made frame ownership and fallback semantics harder to audit. This refactor keeps interpreter-specific behavior insideinterp, whileasmnow owns only architecture-neutral assembly/linking and the scratch-register callable boundary.Verification
go test ./...go test -race ./...go vet ./...GOARCH=amd64 go test ./...git diff --check origin/main...HEADCI Note
codecov/patchis expected to under-report this PR because the remote CI job runs onubuntu-24.04amd64, while the new native JIT and ARM64 trampoline paths are guarded or skipped off ARM64. The main Go test job passes and project coverage improves.Related Issues
No linked issue.
Additional Information
This PR intentionally preserves interpreter fallback for unsupported or unsafe native paths. Non-ARM64 platforms keep running the threaded interpreter with JIT unavailable rather than failing startup.