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
28 changes: 25 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ go test -race -run TestFoo ./interp/...
## Agent Workflow

1. Check `git status --short`; do not overwrite unrelated user changes.
2. Read only task-relevant docs.
3. Mirror nearby tests in the edited package.
2. **Read task-relevant docs from the Documentation Index before writing any code or tests.**
3. Mirror nearby tests in the edited package; follow Test Conventions (one function per exported symbol, sub-cases as `t.Run`).
4. Update docs when behavior, invariants, commands, or recurring pitfalls change.
5. Run narrow tests first, then `go test ./...` or `make test` for broader risk.

Expand Down Expand Up @@ -138,9 +138,31 @@ Incorrect ordering crashes on Apple Silicon.
- Avoid unnecessary abstractions.
- Keep opcode handlers explicit and predictable.
- Preserve interpreter/JIT behavioral parity.
- Prefer table-driven tests where practical.
- Avoid hidden control flow.

## Test Conventions

**Before writing or modifying tests, read the relevant docs from the Documentation Index and Task Router above.**

**One test function per exported symbol.** Sub-cases go inside as `t.Run` subtests, not as separate top-level functions.

```go
// CORRECT
func TestAssembler_Take(t *testing.T) {
t.Run("from stack", func(t *testing.T) { ... })
t.Run("fresh alloc", func(t *testing.T) { ... })
t.Run("type mismatch", func(t *testing.T) { ... })
}

// WRONG — do not split into multiple top-level functions
func TestAssembler_Take_FromStack(t *testing.T) { ... }
func TestAssembler_Take_FreshAlloc(t *testing.T) { ... }
func TestAssembler_Take_TypeMismatch(t *testing.T) { ... }
```

- Name: `Test<Type>_<Method>` for methods, `Test<Func>` for functions.
- Use table-driven loops inside `t.Run` for repetitive cases.

## Documentation Maintenance

Update docs when an invariant caused a bug, a command became outdated, architecture changed, a recurring pitfall was found, or a new important doc should be indexed.
Expand Down
37 changes: 19 additions & 18 deletions asm/arm64/abi_arm64.s
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//
// argv layout:
// argv[0] : header (packed uint64)
// argv[1..nScratch] : scratch save slots (X10–X15), in/out
// argv[1..nScratch] : scratch save slots (X10–X14), in/out
// argv[1+nScratch+...] : param inputs / return outputs
//
// Header bit layout:
Expand All @@ -20,6 +20,7 @@
// R8 = argv base (reloaded after BL)
// R9 = header / call target
// R14 = values_base
// R15 = header register (in/out)
// R19 = param/return count
// R20 = scratch base pointer
// R21 = scratch count
Expand All @@ -44,7 +45,7 @@ TEXT ·invoke(SB), NOSPLIT, $0-16
ADD R10, R14, R14

// ---- Load params into ABI registers ----
CBZ R19, load_scratch
CBZ R19, load

AND $1, R22, R10; CBZ R10, p0_int
AND $1, R23, R10; CBZ R10, p0_f32
Expand All @@ -55,7 +56,7 @@ p0_int:
MOVD 0(R14), R0
p0_done:
LSR $1, R22, R22; LSR $1, R23, R23
SUB $1, R19; CBZ R19, load_scratch
SUB $1, R19; CBZ R19, load

AND $1, R22, R10; CBZ R10, p1_int
AND $1, R23, R10; CBZ R10, p1_f32
Expand All @@ -66,7 +67,7 @@ p1_int:
MOVD 8(R14), R1
p1_done:
LSR $1, R22, R22; LSR $1, R23, R23
SUB $1, R19; CBZ R19, load_scratch
SUB $1, R19; CBZ R19, load

AND $1, R22, R10; CBZ R10, p2_int
AND $1, R23, R10; CBZ R10, p2_f32
Expand All @@ -77,7 +78,7 @@ p2_int:
MOVD 16(R14), R2
p2_done:
LSR $1, R22, R22; LSR $1, R23, R23
SUB $1, R19; CBZ R19, load_scratch
SUB $1, R19; CBZ R19, load

AND $1, R22, R10; CBZ R10, p3_int
AND $1, R23, R10; CBZ R10, p3_f32
Expand All @@ -88,7 +89,7 @@ p3_int:
MOVD 24(R14), R3
p3_done:
LSR $1, R22, R22; LSR $1, R23, R23
SUB $1, R19; CBZ R19, load_scratch
SUB $1, R19; CBZ R19, load

AND $1, R22, R10; CBZ R10, p4_int
AND $1, R23, R10; CBZ R10, p4_f32
Expand All @@ -99,7 +100,7 @@ p4_int:
MOVD 32(R14), R4
p4_done:
LSR $1, R22, R22; LSR $1, R23, R23
SUB $1, R19; CBZ R19, load_scratch
SUB $1, R19; CBZ R19, load

AND $1, R22, R10; CBZ R10, p5_int
AND $1, R23, R10; CBZ R10, p5_f32
Expand All @@ -110,7 +111,7 @@ p5_int:
MOVD 40(R14), R5
p5_done:
LSR $1, R22, R22; LSR $1, R23, R23
SUB $1, R19; CBZ R19, load_scratch
SUB $1, R19; CBZ R19, load

AND $1, R22, R10; CBZ R10, p6_int
AND $1, R23, R10; CBZ R10, p6_f32
Expand All @@ -121,18 +122,19 @@ p6_int:
MOVD 48(R14), R6
p6_done:
LSR $1, R22, R22; LSR $1, R23, R23
SUB $1, R19; CBZ R19, load_scratch
SUB $1, R19; CBZ R19, load

AND $1, R22, R10; CBZ R10, p7_int
AND $1, R23, R10; CBZ R10, p7_f32
FMOVD 56(R14), F7; B load_scratch
FMOVD 56(R14), F7; B load
p7_f32:
FMOVS 56(R14), F7; B load_scratch
FMOVS 56(R14), F7; B load
p7_int:
MOVD 56(R14), R7

// ---- Load scratch registers (X10–X15) ----
load_scratch:
// ---- Load header and scratch registers (X10–X14) ----
load:
MOVD (R8), R15
ADD $8, R8, R20
MOVD R21, R10
CBZ R10, call
Expand All @@ -141,16 +143,16 @@ load_scratch:
MOVD 8(R20), R11; SUB $1, R21; CBZ R21, call
MOVD 16(R20), R12; SUB $1, R21; CBZ R21, call
MOVD 24(R20), R13; SUB $1, R21; CBZ R21, call
MOVD 32(R20), R14; SUB $1, R21; CBZ R21, call
MOVD 40(R20), R15
MOVD 32(R20), R14

call:
MOVD addr+0(FP), R9
BL (R9)

// ---- Save scratch registers ----
MOVD argv+8(FP), R8
MOVD (R8), R9
MOVD R15, (R8)
MOVD R15, R9
UBFX $16, R9, $8, R21
ADD $8, R8, R20

Expand All @@ -159,8 +161,7 @@ call:
MOVD R11, 8(R20); SUB $1, R21; CBZ R21, store
MOVD R12, 16(R20); SUB $1, R21; CBZ R21, store
MOVD R13, 24(R20); SUB $1, R21; CBZ R21, store
MOVD R14, 32(R20); SUB $1, R21; CBZ R21, store
MOVD R15, 40(R20)
MOVD R14, 32(R20)

// ---- Store return values ----
store:
Expand Down
9 changes: 5 additions & 4 deletions asm/arm64/arch.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import "github.com/siyul-park/minivm/asm"
var Arch = &asm.Arch{
// 31 integer registers (X0–X30) + 32 float registers (V0–V31).
// FP (X29) and LR (X30) are reserved by the Go ABI.
Registers: asm.NewRegInfo(31, 32, []uint8{FP.ID(), LR.ID()}, nil),
Registers: asm.NewRegInfo(31, 32, []uint8{FP.ID(), LR.ID(), X15.ID()}, nil),
Encoder: NewEncoder(),
ABI: NewABI(),

// X10–X15: caller-saved scratch registers preserved across the invoke
// X10–X14: caller-saved scratch registers preserved across the invoke
// trampoline call. X0–X7 are ABI param/return registers. X8/X9 are
// invoke temporaries. X16/X17 (IP0/IP1), X18–X21 are invoke internals.
// invoke temporaries. X15 is reserved for the trampoline header.
// X16/X17 (IP0/IP1), X18–X21 are invoke internals.
// X22–X28 are callee-saved and must not be used as scratch here.
Scratch: asm.NewRegMask([]uint8{10, 11, 12, 13, 14, 15}),
Scratch: asm.NewRegMask([]uint8{10, 11, 12, 13, 14}),
}
4 changes: 2 additions & 2 deletions asm/arm64/arch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestAssembler_Compile(t *testing.T) {
require.Len(t, callers, 1)
require.NotNil(t, callers[0])

out, err := callers[0].Call([]uint64{3, 5}, nil)
out, err := callers[0].Call([]asm.Value{asm.I64(3), asm.I64(5)}, nil)
require.NoError(t, err)
require.Equal(t, []uint64{8}, out)
require.Equal(t, []asm.Value{asm.I64(8)}, out)
}
Loading
Loading