refactor(interp): minimize complexity across runtime, JIT, and generator#161
Conversation
- drop Interpreter.keep alias; emit alloc from geninterp - inline Cache.rearm into its only caller - rename trace outcome type/field to status; Tracer receiver r -> t - rename globalReprs -> globalDecls; document decl-vs-observed split - rename rc-slice locals shadowed as hits
- fold function-metadata pass into the constant boxing pass in New - extract seed() shared by New and Reset - reuse alive() for constant retain guards - reuse zeroBoxed for skipCall zero returns
compile, exit, and shared repeated Compile + recordCompile + error metric; attempt() now owns that core while acquisition and delivery stay local.
bind repeated six grow blocks; grow() now owns extension of every per-function table. natives stays fixed at its New-time size because a native frame suspended across a trap fallback may cache the journal base, so install skips natives slots beyond it instead of panicking on dynamically bound functions.
- merge the four const emitters into constant() - share the CMP+BEQ skip shell via unlessEqual - inline single-caller enterBlock into emitBlock
- accessor descriptor table replaces parallel per-op switches in loader.decode/read and slotField; threaderFunc shares the closure wrapper; dead guard() removed - new interp/threader.go compile-time helpers (local/global/upval/ constant) absorb per-variant decode+bounds+kind boilerplate; runtime closures stay fully specialized - threaded.go 65,188 -> 64,323 lines; compile+fused benchmarks neutral
spillSafe/tree.branchIPs/arm64Lowerer.walk no longer exist; describe the plan-level noSpill/noSpillArch contract and aborted-fragment planning rule instead. Track globalDecls rename in the naming audit.
|
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 (22)
🔥 Files not summarized due to errors (1)
📝 WalkthroughWalkthroughThe change refactors codec and host-object integration, interpreter allocation and cache ownership, generated threader lowering, trace status handling and planning, ARM64 lowering, and related tests. Documentation updates describe the revised APIs, JIT invariants, initialization layout, and testing ownership. ChangesInterpreter runtime and host integration
JIT pipeline
Documentation and validation
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ast-grep (0.44.1)interp/threaded.goast-grep timed out on this file 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 #161 +/- ##
==========================================
+ Coverage 29.74% 30.13% +0.38%
==========================================
Files 86 86
Lines 60377 59742 -635
==========================================
+ Hits 17960 18004 +44
+ Misses 41148 40467 -681
- Partials 1269 1271 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
internal/cmd/geninterp/lower.go (1)
3316-3369: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRedundant
if/elseinmapNewnow generates identical code in both branches.After the
keep(m)path was removed, both theTraceKeys || TraceValuesbranch and itsElse()branch calli.alloc(m)— the generated Go will be:if typ.TraceKeys || typ.TraceValues { addr = i.alloc(m) } else { addr = i.alloc(m) }The branch condition no longer has any effect; this should collapse to a single unconditional assignment.
As per coding guidelines,
**/*.goshould followdocs/coding-patterns.mdfor "simplification," which this dead branch violates.♻️ Proposed fix
- jen.Var().Add(jen.List(jen.Id("addr"))).Add(jen.Id("int")), - jen.If(jen.Id("typ").Dot("TraceKeys").Op("||").Add(jen.Id("typ").Dot("TraceValues"))).Block(jen.List(jen.Id("addr")).Op("=").List(jen.Id("i").Dot("alloc").Call(jen.Id("m")))).Else().Block(jen.List(jen.Id("addr")).Op("=").List(jen.Id("i").Dot("alloc").Call(jen.Id("m")))), + jen.List(jen.Id("addr")).Op(":=").List(jen.Id("i").Dot("alloc").Call(jen.Id("m"))),🤖 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 `@internal/cmd/geninterp/lower.go` around lines 3316 - 3369, Remove the redundant TraceKeys/TraceValues conditional in mapNew and replace it with one unconditional assignment of i.alloc(m) to addr. Preserve the surrounding map construction and stack-pointer logic unchanged.Source: Coding guidelines
🤖 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 `@interp/interp.go`:
- Around line 901-906: Update the JIT native-call path in the code generating
the `LDR(callee, natives, op.callee*8)` instruction to check whether `op.callee`
is within the native table length before reading the slot. When it is out of
range, use the existing fallback path instead of emitting or executing the
native-slot load; preserve the current native dispatch for valid indices.
---
Nitpick comments:
In `@internal/cmd/geninterp/lower.go`:
- Around line 3316-3369: Remove the redundant TraceKeys/TraceValues conditional
in mapNew and replace it with one unconditional assignment of i.alloc(m) to
addr. Preserve the surrounding map construction and stack-pointer logic
unchanged.
🪄 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: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: b35a3ffa-1af6-4aff-91d4-98144cfef16e
📒 Files selected for processing (15)
AGENTS.mddocs/coding-patterns.mddocs/symbol-naming-audit.mdinstr/parse.gointernal/cmd/geninterp/generate.gointernal/cmd/geninterp/lower.gointerp/cache.gointerp/interp.gointerp/jit_arm64.gointerp/jit_plan.gointerp/jit_plan_test.gointerp/marshal.gointerp/threaded.gointerp/trace.gointerp/trace_test.go
💤 Files with no reviewable changes (1)
- interp/cache.go
👮 Files not reviewed due to content moderation or server errors (1)
- interp/threaded.go
| // natives keeps its New-time size: growing it in bind could dangle the | ||
| // journal base cached by a native frame suspended across a trap | ||
| // fallback, so dynamically bound functions never get a natives slot. | ||
| if entry.kind == entryFunction && a.addr < len(i.natives) { | ||
| atomic.StorePointer(&i.natives[a.addr], entry.callable.Addr()) | ||
| } |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify all readers of i.natives[...] bounds-check the index before access.
rg -nP -C3 '\.natives\[' --type=goRepository: siyul-park/minivm
Length of output: 155
🏁 Script executed:
#!/bin/bash
set -euo pipefail
git status --short
printf '\n--- interp/interp.go excerpt ---\n'
sed -n '860,940p' interp/interp.go
printf '\n--- all natives references ---\n'
rg -n -C 2 'natives' interp --type=go || trueRepository: siyul-park/minivm
Length of output: 5604
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- interp/interp.go around native handling ---'
sed -n '1,240p' interp/interp.go
printf '\n%s\n' '--- broader natives search ---'
rg -n -C 2 'natives' . --type=go || trueRepository: siyul-park/minivm
Length of output: 7963
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '%s\n' '--- journalNatives uses ---'
rg -n -C 3 'journalNatives|op\.callee|callee > 4095|LDR\(callee, natives' interp --type=go
printf '\n%s\n' '--- anchor/address definitions ---'
rg -n -C 2 'type anchor|struct .*anchor|a\.addr|addr int' interp --type=go
printf '\n%s\n' '--- call emission around native slot access ---'
sed -n '1380,1450p' interp/jit_arm64.goRepository: siyul-park/minivm
Length of output: 23900
Guard the JIT native-slot read too. interp/jit_arm64.go:1421-1427 still emits LDR(callee, natives, op.callee*8) without a len(i.natives) check, so dynamically bound functions can still read past the table and crash or dispatch to garbage. Fall back when the slot is out of range.
🤖 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 `@interp/interp.go` around lines 901 - 906, Update the JIT native-call path in
the code generating the `LDR(callee, natives, op.callee*8)` instruction to check
whether `op.callee` is within the native table length before reading the slot.
When it is out of range, use the existing fallback path instead of emitting or
executing the native-slot load; preserve the current native dispatch for valid
indices.
Summary
Complexity-minimizing refactor of
interp/plus its code generator, perdocs/coding-patterns.md§0 (no compatibility guarantees; internal package).keep(pure alias ofalloc) deleted everywhere including generator emission;Cache.rearminlined into its only caller; traceoutcometype +kindfield unified asstatus, removing the word collision withtypes.Kindandprof.CaptureOutcome; four ARM64 const emitters merged into oneconstant(); CMP+BEQ skip shells shareunlessEqual.seed()shared byNew/Reset; function-metadata pass folded into the constant boxing pass; retain guards reusealive();skipCallreuseszeroBoxed;attempt()owns the Compile → recordCompile → error-metric core repeated bycompile/exit/shared;grow()centralizes the six per-function table extensions inbind.installwrotenatives[a.addr]guarded only bylen(code), panicking on a JIT compile of a dynamically bound function.nativesdeliberately keeps itsNew-time size (a native frame suspended across a trap fallback may cache the journal base), soinstallnow skips out-of-range slots and documents why.r→t;globalReprs→globalDeclswith the declared-vs-observed distinction documented;rc-slice locals no longer shadow-namedhits.internal/cmd/geninterp): accessor descriptor table replaces the parallel per-op switches inloader.decode/read/slotField; deadguard()removed; new hand-writteninterp/threader.gocompile-time helpers absorb per-variant decode+bounds+kind boilerplate —threaded.go65,188 → 64,323 lines with runtime closures fully specialized as before.spillSafe,tree.branchIPs(),arm64Lowerer.walk) rewritten to the currentnoSpill/noSpillArch/aborted-fragment-planning contracts.Net: −779 lines across 13 files.
Intentionally skipped (recorded per §0.9)
zero/zeroBoxedmerge: different dispatch (Repr()vs exact kind) and retain semantics.work/sideExitsnapshot embed,next/pathprologue extract: pure churn, hides mutation.slot/loweringvaluemerge andemitExits/retainDeferreddedup: architecture boundary / deferred-ref invariant.Test plan
go test -race ./...all passmake generate && make check-generatedcleanmake lintcleanmake fuzzsmoke passmake benchmark-prbefore/after: neutral (Reset 48→32ns, TypedArraySum 7.3→6.1µs improved; Fused/JITWarm/New within noise)🤖 Generated with Claude Code
Summary by CodeRabbit
.savenow rejects host-only constants (host functions/objects).WithCodec,NewHostObject).