Skip to content

refactor!: simplify runtime and normalize naming - #148

Merged
siyul-park merged 2 commits into
mainfrom
refactor/interp-simplify
Jul 15, 2026
Merged

refactor!: simplify runtime and normalize naming#148
siyul-park merged 2 commits into
mainfrom
refactor/interp-simplify

Conversation

@siyul-park

@siyul-park siyul-park commented Jul 15, 2026

Copy link
Copy Markdown
Owner

Changes Made

  • Simplified interpreter runtime state and helper ownership across execution, pooling, marshaling, tracing, and JIT planning.
  • Preserved and clarified runtime type boundaries for globals, stack slots, heap values, and host integration.
  • Fixed correctness gaps found during the review:
    • treat RETURN_CALL as a terminal CFG instruction
    • preserve type-pool entries referenced by ref and map instructions during deduplication
    • require full struct field-prefix compatibility in StructType.Cast
  • Normalized repository-wide naming while retaining meaningful suffixes and domain qualifiers:
    • BlocksAnalysis, GVNAnalysis, GVN, DCEPass, GVNPass, FoldPass, and DedupPass
    • optimize.New, Optimizer.Add, and Pipeline.Add
    • FunctionBuilder.Params/Returns/Locals/Captures, Function.Slots, and WithHeapLimit
    • shorter private phase and implementation names where call-site meaning remains clear
  • Kept NewMap, NewMapWithCapacity, NewTypedMap, and NewMapForType public and semantically distinct.
  • Updated tests, examples, benchmarks, coding rules, API documentation, and the package-by-package naming audit.

Related Issues

None.

Additional Information

This PR intentionally contains breaking API renames because the public API has not reached a stable release. No deprecated compatibility aliases were added.

Verification:

  • make check
  • (cd benchmarks && go test -race ./...)
  • (cd benchmarks && go vet ./...)
  • git diff --check
  • make benchmark-pr

All commands completed successfully on Darwin/ARM64 (Apple M4 Pro). The benchmark gate was used as a regression smoke test; this PR makes no controlled performance-improvement claim.

Summary by CodeRabbit

  • New Features

    • Added stronger program verification for global value type mismatches and reference compatibility.
    • Added support for correctly handling RETURN_CALL in control-flow analysis.
    • Improved struct field lookup and casting behavior.
  • API Updates

    • Simplified optimizer, analysis, transformation, pipeline, and function-builder naming.
    • Renamed the heap limit option to WithHeapLimit.
    • Simplified Verify to accept only a program.
  • Documentation

    • Updated optimization, pass, memory, host-integration, and naming guidance.
    • Added a symbol-naming audit and reading-guide entry.

Simplify interpreter state and helper ownership, preserve CFG and type-pool invariants, and apply consistent package-wide symbol names.

BREAKING CHANGE: rename optimizer, pass, analysis, transform, function-builder, heap-limit, slot, and register APIs as documented in docs/symbol-naming-audit.md.
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The pull request applies a broad naming audit across public APIs and internals, updates optimizer and builder usage, strengthens verifier type tracking, refactors interpreter/JIT state handling, and updates documentation and tests.

Changes

API and optimization pipeline

Layer / File(s) Summary
Renamed analyses, passes, and constructors
analysis/*, transform/*, optimize/*, pass/*
Shorter names such as BlocksAnalysis, GVNAnalysis, FoldPass, DCEPass, GVNPass, New, and Add replace prior long-form APIs.
Builder and runtime API updates
types/function.go, interp/interp.go, asm/reg.go
Builder methods, slot access, heap-limit configuration, and floating-register metadata use renamed public APIs.
Verifier and interpreter type flow
program/verify.go, interp/interp.go, types/struct.go
Verification and global assignment use concrete type information, while interpreter globals track declared types and struct casting compares field types.
Execution internals
interp/jit.go, interp/jit_arm64.go, interp/jit_plan.go, interp/trace.go
JIT local state uses bitmasks, slot layouts replace local-kind accessors, and native calls use journal pointers.
Documentation and tests
README*, docs/*, **/*_test.go
Examples, naming guidance, ownership references, and tests are updated for the renamed APIs and added type/CFG coverage.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.07% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately summarizes the main refactor and naming-normalization change.
Description check ✅ Passed The description follows the required template and includes changes, related issues, and additional verification details.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/interp-simplify

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.45533% with 47 lines in your changes missing coverage. Please review.
✅ Project coverage is 29.57%. Comparing base (3cd1955) to head (1356aee).

Files with missing lines Patch % Lines
interp/interp.go 79.77% 17 Missing and 1 partial ⚠️
program/verify.go 73.17% 7 Missing and 4 partials ⚠️
interp/jit_plan.go 28.57% 2 Missing and 3 partials ⚠️
interp/jit.go 0.00% 4 Missing ⚠️
asm/regalloc.go 66.66% 3 Missing ⚠️
asm/assembler.go 33.33% 2 Missing ⚠️
asm/link.go 0.00% 2 Missing ⚠️
analysis/gvn.go 96.29% 1 Missing ⚠️
cli/repl.go 94.11% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #148      +/-   ##
==========================================
+ Coverage   29.54%   29.57%   +0.02%     
==========================================
  Files          86       86              
  Lines       60158    60181      +23     
==========================================
+ Hits        17775    17798      +23     
- Misses      41116    41117       +1     
+ Partials     1267     1266       -1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (7)
interp/jit_plan.go (1)

108-123: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

input as a function name shadows the conventional *compileInput parameter name.

staticPlan(input *compileInput) (Line 198) and likely other functions in this file take a *compileInput parameter also named input, which now shadows the package-level input() constructor within those scopes. It compiles fine today, but it's confusing to read and would silently break if any of those functions ever needed to call the constructor.

♻️ Suggested rename to avoid the collision
-func input(i *Interpreter, addr int) (*compileInput, bool) {
+func newInput(i *Interpreter, addr int) (*compileInput, bool) {
🤖 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/jit_plan.go` around lines 108 - 123, Rename the package-level input
constructor to a distinct name and update every call site, including staticPlan
and any other references, so it no longer collides with *compileInput parameters
named input.
interp/jit_arm64.go (1)

870-891: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Ref-kind localLoaded clear happens before loadLocal's success is known.

f.state[idx] &^= localLoaded runs unconditionally for KindRef locals before l.loadLocal(...) is called and checked. Today loadLocal cannot fail for KindRef (its only internal failure branch is gated on KindI64), so this is currently safe, but it violates the stated invariant that failed lowering must not mutate frame facts, and would silently break if a Ref-specific failure path is ever added to loadLocal.

🛡️ Defensive reordering
 func (l arm64Lowerer) localGet(ctx *lowering, op step) bool {
 	f := ctx.frame()
 	idx := int(op.args[0])
 	if idx >= len(f.kinds) {
 		return false
 	}
-	if f.kinds[idx] == types.KindRef {
-		f.state[idx] &^= localLoaded
-	}
 	if !l.loadLocal(ctx, f, idx, op.ip) {
 		return false
 	}
+	if f.kinds[idx] == types.KindRef {
+		f.state[idx] &^= localLoaded
+	}

Based on learnings, this is not tied to a specific retrieved-learning note; the concern is grounded directly in the interp/**/*.go coding guideline on JIT handler mutation-on-failure.

🤖 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/jit_arm64.go` around lines 870 - 891, Move the Ref-local f.state[idx]
&^= localLoaded mutation in arm64Lowerer.localGet to occur only after
l.loadLocal succeeds. Preserve the existing early return on loadLocal failure so
failed lowering leaves frame facts unchanged, while retaining the current
reference-retention and push behavior on success.

Source: Coding guidelines

analysis/gvn.go (1)

109-109: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Place newNumbering after its caller.

Run calls newNumbering, but newNumbering is declared earlier in the file. Move the helper below Run so callers precede callees.

As per coding guidelines, callers must be declared before callees.

🤖 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 `@analysis/gvn.go` at line 109, Move the newNumbering function declaration
below Run in analysis/gvn.go, leaving its implementation unchanged so the caller
appears before the callee.

Source: Coding guidelines

transform/cd_test.go (1)

66-84: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise type deduplication and remapping here.

This case omits MAP_NEW and uses unique type indices, so it would pass even if the new scan or rewrite logic failed to remap operands. Add duplicate ref/map types and assert the compacted type table plus rewritten indices.

Suggested coverage update
 		{
 			name: "all type operands",
 			program: program.New(
 				[]instr.Instruction{
 					instr.New(instr.REF_TEST, 0),
-					instr.New(instr.REF_CAST, 1),
-					instr.New(instr.MAP_NEW_DEFAULT, 2),
+					instr.New(instr.REF_CAST, 0),
+					instr.New(instr.MAP_NEW, 1),
+					instr.New(instr.MAP_NEW_DEFAULT, 1),
 				},
 				program.WithTypes(
-					types.TypeString, types.TypeError, types.NewMapType(types.TypeI32, types.TypeI32),
+					types.TypeString,
+					types.NewMapType(types.TypeI32, types.TypeI32),
 				),
 			),
 			expected: program.New(
 				[]instr.Instruction{
 					instr.New(instr.REF_TEST, 0),
-					instr.New(instr.REF_CAST, 1),
-					instr.New(instr.MAP_NEW_DEFAULT, 2),
+					instr.New(instr.REF_CAST, 0),
+					instr.New(instr.MAP_NEW, 1),
+					instr.New(instr.MAP_NEW_DEFAULT, 1),
 				},
🤖 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 `@transform/cd_test.go` around lines 66 - 84, Update the “all type operands”
case in the transform tests to include duplicate reference and map types,
including MAP_NEW, so deduplication and operand remapping are exercised. Assert
the expected compacted type table and rewritten indices for REF_TEST, REF_CAST,
MAP_NEW_DEFAULT, and MAP_NEW rather than retaining unique indices.
asm/regalloc.go (1)

109-113: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove or wire the unused block helper.

golangci-lint reports (*allocator).block as unused at Line 109. Unless a generated or build-tagged caller is intentionally omitted, delete this dead helper or add the missing call path.

🤖 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 `@asm/regalloc.go` around lines 109 - 113, Remove the unused allocator.block
method unless a valid generated or build-tagged caller is required; otherwise
wire it into the intended register-blocking path. Verify references to
(*allocator).block and preserve the existing blocked and avail updates wherever
that behavior is needed.

Source: Linters/SAST tools

program/verify.go (1)

81-108: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

newChecker is defined before its caller Verify.

Elsewhere in this file callers consistently precede their callees (stepcall, mergeunify, stepacceptsType), but here newChecker (81) is defined above Verify (93), which calls it. As per coding guidelines, **/*.go files should place "callers before callees."

🤖 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 `@program/verify.go` around lines 81 - 108, Reorder the declarations in
program/verify.go so Verify appears before newChecker, keeping both
implementations and behavior unchanged. Verify should remain the caller and
newChecker its following callee, matching the file’s callers-before-callees
convention.

Source: Coding guidelines

interp/interp.go (1)

1790-1821: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove unused globalKinds() interp/interp.go:1790-1807 can be dropped.

🤖 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 1790 - 1821, Remove the unused
Interpreter.globalKinds method from interp/interp.go. Leave globalReprs and all
other global-kind handling unchanged.
🤖 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 `@analysis/blocks.go`:
- Around line 137-139: Move the BlocksAnalysis.Run method so it is declared
before the Blocks function, preserving its existing implementation and behavior
while satisfying the repository’s caller-before-callee declaration order.

In `@interp/jit.go`:
- Line 451: Update the frame snapshot logic around frames[i].state so it
preserves every localState value from f.state, including localLoaded and
localDirty bits, instead of leaving the newly allocated entries zeroed. Keep the
independent snapshot allocation while copying the source state contents into it
before later lowering uses the snapshot.

In `@program/verify.go`:
- Around line 458-470: Update the LOCAL_SET and LOCAL_TEE handling in the
verifier to validate the stack top with acceptsType against
c.locals[inst.Operand(0)] after the underflow check, matching GLOBAL_SET and
GLOBAL_TEE. Return ErrTypeMismatch on failure while preserving the existing
success and underflow behavior.

---

Nitpick comments:
In `@analysis/gvn.go`:
- Line 109: Move the newNumbering function declaration below Run in
analysis/gvn.go, leaving its implementation unchanged so the caller appears
before the callee.

In `@asm/regalloc.go`:
- Around line 109-113: Remove the unused allocator.block method unless a valid
generated or build-tagged caller is required; otherwise wire it into the
intended register-blocking path. Verify references to (*allocator).block and
preserve the existing blocked and avail updates wherever that behavior is
needed.

In `@interp/interp.go`:
- Around line 1790-1821: Remove the unused Interpreter.globalKinds method from
interp/interp.go. Leave globalReprs and all other global-kind handling
unchanged.

In `@interp/jit_arm64.go`:
- Around line 870-891: Move the Ref-local f.state[idx] &^= localLoaded mutation
in arm64Lowerer.localGet to occur only after l.loadLocal succeeds. Preserve the
existing early return on loadLocal failure so failed lowering leaves frame facts
unchanged, while retaining the current reference-retention and push behavior on
success.

In `@interp/jit_plan.go`:
- Around line 108-123: Rename the package-level input constructor to a distinct
name and update every call site, including staticPlan and any other references,
so it no longer collides with *compileInput parameters named input.

In `@program/verify.go`:
- Around line 81-108: Reorder the declarations in program/verify.go so Verify
appears before newChecker, keeping both implementations and behavior unchanged.
Verify should remain the caller and newChecker its following callee, matching
the file’s callers-before-callees convention.

In `@transform/cd_test.go`:
- Around line 66-84: Update the “all type operands” case in the transform tests
to include duplicate reference and map types, including MAP_NEW, so
deduplication and operand remapping are exercised. Assert the expected compacted
type table and rewritten indices for REF_TEST, REF_CAST, MAP_NEW_DEFAULT, and
MAP_NEW rather than retaining unique indices.
🪄 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: cadb540f-3d11-4b80-8890-659fb1d85a8d

📥 Commits

Reviewing files that changed from the base of the PR and between 3cd1955 and 1356aee.

📒 Files selected for processing (68)
  • AGENTS.md
  • README.ko.md
  • README.md
  • analysis/blocks.go
  • analysis/blocks_test.go
  • analysis/gvn.go
  • analysis/gvn_test.go
  • asm/assembler.go
  • asm/link.go
  • asm/reg.go
  • asm/reg_test.go
  • asm/regalloc.go
  • asm/rewriter.go
  • benchmarks/call.go
  • cli/repl.go
  • cli/repl_test.go
  • debug/debugger.go
  • docs/README.md
  • docs/architecture.md
  • docs/coding-patterns.md
  • docs/host-integration.md
  • docs/jit-internals.md
  • docs/memory-model.md
  • docs/pass-system.md
  • docs/roadmap.md
  • docs/symbol-naming-audit.md
  • docs/testing.md
  • docs/verification.md
  • interp/host.go
  • interp/interp.go
  • interp/interp_test.go
  • interp/jit.go
  • interp/jit_arm64.go
  • interp/jit_arm64_test.go
  • interp/jit_plan.go
  • interp/marshal.go
  • interp/pool.go
  • interp/pool_test.go
  • interp/trace.go
  • optimize/example_test.go
  • optimize/fuzz_test.go
  • optimize/optimizer.go
  • optimize/optimizer_test.go
  • pass/pipeline.go
  • pass/pipeline_test.go
  • prof/collector.go
  • prof/jit.go
  • prof/jit_metrics.go
  • program/builder.go
  • program/parse.go
  • program/parse_test.go
  • program/verify.go
  • program/verify_test.go
  • transform/as.go
  • transform/as_test.go
  • transform/cd.go
  • transform/cd_test.go
  • transform/cf.go
  • transform/cf_test.go
  • transform/dce.go
  • transform/dce_test.go
  • transform/gvn.go
  • transform/gvn_test.go
  • types/function.go
  • types/function_test.go
  • types/parse_test.go
  • types/struct.go
  • types/struct_test.go

Comment thread analysis/blocks.go
Comment on lines +137 to 139
func (p *BlocksAnalysis) Run(m *pass.Manager, fn *types.Function) ([]*BasicBlock, error) {
return Blocks(fn)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Move BlocksAnalysis.Run before Blocks.

Run calls Blocks, but it is declared afterward. Move this wrapper above Blocks to satisfy the repository rule that callers precede callees.

Suggested reorder
+func (p *BlocksAnalysis) Run(m *pass.Manager, fn *types.Function) ([]*BasicBlock, error) {
+	return Blocks(fn)
+}
+
 func Blocks(fn *types.Function) ([]*BasicBlock, error) {
   ...
 }
-
-func (p *BlocksAnalysis) Run(m *pass.Manager, fn *types.Function) ([]*BasicBlock, error) {
-	return Blocks(fn)
-}

As per coding guidelines, declaration order should place callers before callees.

🤖 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 `@analysis/blocks.go` around lines 137 - 139, Move the BlocksAnalysis.Run
method so it is declared before the Blocks function, preserving its existing
implementation and behavior while satisfying the repository’s
caller-before-callee declaration order.

Source: Coding guidelines

Comment thread interp/jit.go
frames[i].locals = make([]value, len(f.locals))
frames[i].loaded = make([]bool, len(f.loaded))
frames[i].dirty = make([]bool, len(f.dirty))
frames[i].state = make([]localState, len(f.state))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Copy local-state bits into snapshots.

frames[i] = f copies only the slice header; this make replaces it with zeroed entries and discards every localLoaded/localDirty bit from f.state. Later lowering can therefore omit required local reload/flush handling and diverge from threaded execution.

Proposed fix
-		frames[i].state = make([]localState, len(f.state))
+		frames[i].state = make([]localState, len(f.state))
+		copy(frames[i].state, f.state)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
frames[i].state = make([]localState, len(f.state))
frames[i].state = make([]localState, len(f.state))
copy(frames[i].state, f.state)
🤖 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/jit.go` at line 451, Update the frame snapshot logic around
frames[i].state so it preserves every localState value from f.state, including
localLoaded and localDirty bits, instead of leaving the newly allocated entries
zeroed. Keep the independent snapshot allocation while copying the source state
contents into it before later lowering uses the snapshot.

Comment thread program/verify.go
Comment on lines +458 to +470
case instr.LOCAL_TEE:
if st.len() == 0 {
return false, c.fail(ip, op, ErrStackUnderflow)
}
return false, nil
case instr.GLOBAL_TEE:
if st.len() == 0 {
return false, c.fail(ip, op, ErrStackUnderflow)
}
if !acceptsType(st.top(), c.prog.Globals[inst.Operand(0)]) {
return false, c.fail(ip, op, ErrTypeMismatch)
}
return false, nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm whether locals get any equivalent runtime type-cast safety net
# (mirroring SetGlobal's Cast check), and whether LOCAL_SET/LOCAL_TEE were
# already exempt from type checking before this diff.
rg -nP -C3 '\bLOCAL_SET\b|\bLOCAL_TEE\b' program/verify.go interp/interp.go
rg -nP -C3 'func.*SetLocal' interp/interp.go

Repository: siyul-park/minivm

Length of output: 1249


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the verifier around local/global get/set/tee handling.
sed -n '180,230p' program/verify.go
echo '---'
sed -n '450,500p' program/verify.go
echo '---'
sed -n '680,705p' program/verify.go

# Inspect the interpreter's local write path for any runtime type enforcement.
echo '--- interp ---'
sed -n '470,540p' interp/interp.go

Repository: siyul-park/minivm

Length of output: 6175


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect verifier call/type resolution and the interpreter's corresponding call path.
sed -n '560,620p' program/verify.go
echo '---'
sed -n '620,700p' program/verify.go
echo '--- interp call ---'
rg -n -C4 '\bCALL\b|func .*Call\(' interp/interp.go program/verify.go

Repository: siyul-park/minivm

Length of output: 7696


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect local slot typing/boxing and any verifier rules that constrain locals.
rg -n -C3 '\btypes\.Boxed\b|\bKindRef\b|\bLOCAL_SET\b|\bLOCAL_TEE\b|\bLocal\(' types interp program
echo '--- types ---'
sed -n '1,260p' types/*.go

Repository: siyul-park/minivm

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect LOCAL_SET and the declared stack effects for local opcodes.
sed -n '230,280p' program/verify.go
echo '---'
sed -n '500,560p' program/verify.go
echo '--- instr ---'
rg -n -C2 '\bLOCAL_SET\b|\bLOCAL_TEE\b' instr

Repository: siyul-park/minivm

Length of output: 3548


LOCAL_SET and LOCAL_TEE still skip the declared local type check.
LOCAL_GET now attaches c.locals[...] to the stack slot, but both local write paths only enforce underflow/kind. That lets a typed local hold an incompatible value and later be consumed as if it matched the declaration, which is the same unsoundness GLOBAL_SET/GLOBAL_TEE now avoid. Add the same acceptsType(..., c.locals[inst.Operand(0)]) check to both local writes.

🤖 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 `@program/verify.go` around lines 458 - 470, Update the LOCAL_SET and LOCAL_TEE
handling in the verifier to validate the stack top with acceptsType against
c.locals[inst.Operand(0)] after the underflow check, matching GLOBAL_SET and
GLOBAL_TEE. Return ErrTypeMismatch on failure while preserving the existing
success and underflow behavior.

@siyul-park
siyul-park merged commit 7be9930 into main Jul 15, 2026
6 checks passed
@siyul-park
siyul-park deleted the refactor/interp-simplify branch July 15, 2026 14:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant