Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,19 @@ Use this file only for Claude-specific execution reminders.

## Claude Checklist

Before reporting done, re-read every touched code/test file and verify:

- `docs/coding-patterns.md` §0.7-§0.9 was applied: every touched symbol has a reason, simpler algorithms were considered, and another simplification pass found no safe improvement.
- Declaration order follows §1.3 and §2.4: callers before callees, with the allowed exception that `With*` option functions may sit immediately above the constructor they configure.
- Private package functions used by one type became methods on that type, unless they are constructors, shared by multiple types, or clearer inline (§1.5).
- Single-use helpers stayed inline unless extraction names real behavior or removes real duplication (§1.4).
- Struct fields follow the semantic layers in §2.5.
- Tests assert public behavior, use one top-level test per public symbol, inline setup/run/assertions by default, and use `require` (§6).
- Documentation, workflow, or convention changes updated the owning docs listed in §8.

If any checklist item is intentionally not applied, record the reason in the final summary.
Before reporting done, re-read every changed code and test file and verify:

- `docs/coding-patterns.md` §2 and §16 were applied.
- Top-down package/API/flow review and bottom-up review of every affected symbol are complete.
- Every symbol has a current owner and reason; duplicate, wrapper, dead, mergeable, or private candidates were resolved.
- Functions keep one abstraction level, single-use helpers remain inline unless they isolate real mechanics, and receiver-owned behavior is a method.
- Declarations form a caller-before-callee staircase; fields and groups follow specification §9.
- Public APIs remain minimal and compatible unless the user explicitly authorized a change.
- Tests use public observable behavior, isolated arrange per subtest, production-matched files, and `require` (specification §12).
- Performance work has a baseline, profile-guided owner, correctness checks, and reproducible before/after evidence (specification §14).
- Documentation, workflow, and conventions were updated in their specification §15 owner documents.

Record any intentionally skipped simplification, validation, or benchmark target and its reason in the final summary.

## Verification Commands

Expand Down
91 changes: 47 additions & 44 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,73 +10,76 @@ Keep this file terse and actionable. Put detailed coding rules in `docs/coding-p

1. Follow the user's latest explicit request first.
2. Follow the closest applicable repository instruction file.
3. Use this file as the root repository contract.
4. Use `docs/coding-patterns.md` as the coding-style authority.
5. Match nearby code when it is stricter than this guide.
3. Use this file as the root repository workflow contract.
4. Apply `docs/coding-patterns.md` as the normative coding specification.
5. Match nearby code only when it is more specific and specification-compliant.

If instructions conflict, choose the more specific instruction and mention the conflict in the final summary.
If instructions conflict, choose the more specific instruction and record the conflict in the final summary.

## Quick Commands

```bash
make init # install goimports/godoc and go install ./...
make test # go test -race ./...
make benchmark-pr # quick pull-request benchmark report
make benchmark-core # full canonical package + VM kernel suite
make init # install goimports/godoc and go install ./...
make test # go test -race ./...
make benchmark-pr # quick pull-request benchmark report
make benchmark-core # full canonical package + VM kernel suite
make benchmark-compare # optional external runtime comparisons
make fuzz # bounded trust-boundary fuzz smoke
make lint # goimports -w . && go vet ./...
make coverage # CI-style full test run with coverage.out
make coverage-check # enforce recorded total-coverage baseline
make build # build ./dist/minivm
make fuzz # bounded trust-boundary fuzz smoke
make lint # goimports -w . && go vet ./...
make coverage # CI-style full test run with coverage.out
make coverage-check # enforce recorded total-coverage baseline
make build # build ./dist/minivm

go test -race ./...
go test -race -run TestFoo ./interp/...
go test -race -run 'TestInterpreter_WithDebugger|TestDebugger_Breakpoints' ./interp

./dist/minivm # interactive assembly REPL
./dist/minivm # interactive assembly REPL
```

## Required Workflow

1. Run `git status --short`; never overwrite unrelated user changes.
2. Prefer `codegraph` MCP tools for structural exploration; fall back to grep/read only for literal text or stale indexes.
1. Run `git status --short`; never overwrite or commit unrelated user changes.
2. Prefer structural tools for symbol ownership and call-flow exploration; use grep/read for literal text and final verification.
3. Read task-relevant docs from the Task Router before changing code or tests.
4. Read `docs/coding-patterns.md` through its Fast Path: always apply §0, then the task-specific sections from its When to Read table.
5. Make the smallest correct change. Avoid speculative cleanup outside the task.
6. Validate with the narrowest relevant tests first, then broader tests when the change warrants it.
7. Run the Completion Gate before reporting done, opening a PR, or updating a PR.
4. Apply `docs/coding-patterns.md` §2 and §16 to every code/test change, plus sections selected by §1.3.
5. Review top-down from package contract to mechanics and bottom-up across every affected symbol. Repository-wide refactors MUST inventory every production and test symbol.
6. Make the smallest correct change. Do not add speculative structure or preserve obsolete compatibility without an explicit contract.
7. Validate the narrowest relevant behavior first, then race, static, generated, architecture, and benchmark checks warranted by the change.
8. Run the Completion Gate before reporting done, committing a logical stage, opening a PR, or updating a PR.

## Completion Gate

Do not call work complete until every item is true:

1. Every touched code/test file was re-read against `docs/coding-patterns.md` §0.7-§0.9 plus the task-specific sections.
2. Every touched symbol has a current reason to exist.
3. Removable symbols were removed, inlined, merged, narrowed, made private, renamed by role, or replaced by direct local code.
4. A simpler algorithm or control flow was considered; the chosen shape is the simplest correct option found.
1. Every changed file was re-read against `docs/coding-patterns.md` §2 and the task-specific sections.
2. Top-down ownership and bottom-up symbol reviews are complete at the requested scope.
3. Every affected symbol has a current reason to exist; removable symbols were removed, inlined, merged, narrowed, privatized, or renamed by role.
4. The chosen algorithm and control flow are the simplest correct options found without a measured performance regression.
5. Another simplification pass found no safe improvement.
6. Declaration order follows `docs/coding-patterns.md` §1.3 and §2.4: callers before callees, except `With*` option functions may sit immediately above the constructor they configure.
7. Tests follow `docs/coding-patterns.md` §6 and assert behavior rather than private shape.
8. PR, commit, and documentation expectations follow `docs/coding-patterns.md` §7-§8.
9. Any intentionally skipped simplification is recorded in the final summary with the reason.
6. Declarations form a caller-before-callee staircase and follow specification §9.
7. Tests follow specification §12 and assert only public observable behavior.
8. Performance claims include the reproducible evidence required by specification §14.
9. Commits and documentation follow specification §15, and unrelated user changes are absent.
10. Any intentionally skipped simplification or validation is recorded with its reason.

## Coding Pattern Map
## Coding Standard Map

`docs/coding-patterns.md` is the authority. Use this map only to choose what to read.
`docs/coding-patterns.md` is normative. Use this map only for routing.

| Need | Read in `docs/coding-patterns.md` |
|---|---|
| Before any code/test edit | When to Read, §0 |
| Removing unnecessary structure | §0.1, §0.7-§0.9 |
| Naming, helper extraction, method ownership | §1.2, §1.4, §1.5 |
| File order, type/interface shape, struct fields | §2.1-§2.5 |
| Public API, options, builders, parsers | §3 |
| Errors, panic, recover | §4 |
| Architecture build tags | §5 |
| Tests | §6 |
| Commits, PRs, final review | §7 |
| Documentation updates | §8 |
| Every code/test change | §2, §16 |
| Functions, helpers, naming | §3-§4 |
| Types, constructors, public APIs | §5 |
| Package and runtime ownership | §6-§8 |
| Declaration and field order | §9 |
| Errors and panic/recover | §10 |
| Concurrency and lifecycle | §11 |
| Tests and public specifications | §12 |
| Generated and architecture code | §13 |
| Performance and benchmarks | §14 |
| Commits and documentation | §15 |

## Task Router

Expand Down Expand Up @@ -169,7 +172,7 @@ Violations cause silent corruption or invalid execution.
- Compile-time threaded code advances `c.ip`; runtime threaded execution advances `f.ip`.
- JIT handlers return `true` only after lowering the opcode and advancing `s.ip` by its exact width.
- On JIT type mismatch or unsupported lowering, return `false` without mutating IR, stack, params, facts, or labels.
- Executable buffers must follow `Unseal -> Append -> Seal -> Call`; `Seal()` must sync the instruction cache on Darwin/ARM64.
- Executable buffers own their write/execute transition inside `asm.Buffer.install`, which must sync the instruction cache on Darwin/ARM64 before resealing; a full buffer is replaced by a larger mapping and the old one is retained so entry pointers stay valid.
- Offset-preserving passes must preserve byte offsets; `GVNPass` and `DCEPass` are the known exceptions and must repair branches/handlers.
- `asm.Relaxer.Relax` implementations must return a replacement sequence that is already in range; `asm.Assembler.encode`'s fixpoint loop relies on this to relax each branch at most once and terminate.
- A JIT trace fragment's own `status`, not the root trace's, decides how its ops lower when they run out; `tracePlan` must skip any `aborted` root or branch, so a fragment that recorded a partial, unsupported prefix is never planned or inlined into a parent trace.
Expand All @@ -179,16 +182,16 @@ Violations cause silent corruption or invalid execution.

## Tests

Use `docs/testing.md` for ownership and opcode coverage status. Before writing or modifying tests, read relevant docs from the Task Router and apply `docs/coding-patterns.md` §6.
Use `docs/testing.md` for ownership and opcode coverage status. Before writing or modifying tests, read relevant docs from the Task Router and apply `docs/coding-patterns.md` §12.

- One top-level test per public symbol: `Test<Func>` or `Test<Type>_<Method>`.
- Put sub-cases under `t.Run`; do not split them into parallel top-level tests.
- Inline setup, run sequence, and assertions unless §6.8 allows a helper.
- Keep setup, execution, and assertions visible unless specification §12 permits a real reusable abstraction.
- Use `require`, not `assert`.

## Documentation Maintenance

Update docs when behavior, invariants, commands, architecture, pitfalls, workflow, or conventions change. Use the owner matrix in `docs/coding-patterns.md` §8:
Update docs when behavior, invariants, commands, architecture, pitfalls, workflow, or conventions change. Use the owner matrix in `docs/coding-patterns.md` §15:

- workflow / convention rules -> update both `AGENTS.md` and `.claude/CLAUDE.md`
- invariants / pitfalls -> update `docs/architecture.md`
Expand Down
13 changes: 8 additions & 5 deletions asm/arch.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ type ABI interface {
// Relaxer is an optional Arch capability implemented by architectures that
// can rewrite a branch instruction with an out-of-range immediate
// displacement into an equivalent multi-instruction sequence that fits.
// Assembler.encode type-asserts Arch for Relaxer and, when present, runs a
// fixpoint pass over intra-Code label branches before final encoding.
// Build type-asserts Arch for Relaxer and, when present, runs a fixpoint
// pass over label branches before final encoding.
type Relaxer interface {
// Relax inspects a PC-relative label-branch instruction and its
// resolved byte displacement (target - instruction address). It
Expand Down Expand Up @@ -62,8 +62,9 @@ type Frame interface {
// Leave releases the spill area. Emitted immediately before every
// instruction Returns reports true for. Returns nil when slots == 0.
Leave(slots int) []Instruction
// Resume reserves the spill area again after an intra-Code call returns
// through the shared epilogue. It must preserve the current spill base.
// Resume reserves the spill area again after a call to a label in the
// same code returns through the shared epilogue. It must preserve the
// current spill base.
Resume(slots int) []Instruction
// Store writes reg into spill slot.
Store(slot int, reg PReg) Instruction
Expand All @@ -72,7 +73,9 @@ type Frame interface {
// Returns reports whether op transfers control out of the callable, so
// the allocator must restore the stack with Leave before it.
Returns(op uint16) bool
// Calls reports whether op calls another label in the same Code.
// Calls reports whether op calls a label rather than an absolute
// address, so a target bound in the same code returns through the
// shared epilogue.
Calls(op uint16) bool
}

Expand Down
4 changes: 2 additions & 2 deletions asm/arm64/abi_arm64_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ func TestNew(t *testing.T) {
buf, err := asm.NewBuffer(4096)
require.NoError(t, err)
defer buf.Free()
linked, err := asm.Link(buf, New(), []*asm.Code{code}, nil)
linked, err := asm.Link(buf, New(), code)
require.NoError(t, err)
// Use the concrete caller so escape analysis keeps the fresh goroutine's
// context on its stack while invoke grows and relocates that stack.
callable, ok := linked[0].Callable.(*caller)
callable, ok := linked.(*caller)
require.True(t, ok)

errs := make(chan error, 1)
Expand Down
33 changes: 20 additions & 13 deletions asm/arm64/frame_test.go
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
package arm64
package arm64_test

import (
"testing"

"github.com/siyul-park/minivm/asm"
"github.com/stretchr/testify/require"

"github.com/siyul-park/minivm/asm"
"github.com/siyul-park/minivm/asm/arm64"
)

func TestFrame(t *testing.T) {
func TestArch_Frame(t *testing.T) {
t.Run("chunks large spill areas", func(t *testing.T) {
// 512 slots need 4096 bytes, one byte past the unshifted add/sub
// immediate range, so each adjustment splits into two steps that keep
// SP 16-byte aligned throughout.
frame := arm64.New().Frame()

require.Equal(t, []asm.Instruction{
SUBI(SP, SP, maxFrameAdjust),
SUBI(SP, SP, 16),
ADDI(X26, SP, 0),
}, frame{}.Enter(512))
arm64.SUBI(arm64.SP, arm64.SP, 4080),
arm64.SUBI(arm64.SP, arm64.SP, 16),
arm64.ADDI(arm64.X26, arm64.SP, 0),
}, frame.Enter(512))
require.Equal(t, []asm.Instruction{
SUBI(SP, SP, maxFrameAdjust),
SUBI(SP, SP, 16),
}, frame{}.Resume(512))
arm64.SUBI(arm64.SP, arm64.SP, 4080),
arm64.SUBI(arm64.SP, arm64.SP, 16),
}, frame.Resume(512))
require.Equal(t, []asm.Instruction{
ADDI(SP, SP, maxFrameAdjust),
ADDI(SP, SP, 16),
}, frame{}.Leave(512))
arm64.ADDI(arm64.SP, arm64.SP, 4080),
arm64.ADDI(arm64.SP, arm64.SP, 16),
}, frame.Leave(512))
})
}
15 changes: 11 additions & 4 deletions asm/arm64/instr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,19 @@ func TestInstructionFactories(t *testing.T) {
if !ok {
return true
}
name, ok := call.Fun.(*ast.Ident)
if !ok {
// Tests live both in this package (Name) and beside it
// (arm64.Name), so accept either call shape.
var name string
switch fn := call.Fun.(type) {
case *ast.Ident:
name = fn.Name
case *ast.SelectorExpr:
name = fn.Sel.Name
default:
return true
}
if _, ok := factories[name.Name]; ok {
covered[name.Name] = struct{}{}
if _, ok := factories[name]; ok {
covered[name] = struct{}{}
}
return true
})
Expand Down
Loading
Loading