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
2 changes: 1 addition & 1 deletion .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ Before reporting done, re-read every changed code and test file and verify:
- 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.
- Unit tests use the production package, tests use public observable behavior except for specification §12's narrow internal-invariant allowance, arrange is isolated per subtest, files match production owners, and assertions use `require`.
- Every test package uses the production package name plus `_test`, accesses no private symbol or representation, uses public observable behavior or a real artifact boundary, isolates arrange per subtest, matches production owners, and uses `require`.
- 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.

Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,10 @@ Violations cause silent corruption or invalid execution.
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>`.
- Unit tests use the production package; reserve `_test` packages for public examples and genuine external conformance tests.
- Every test package uses the production package name plus `_test` and acts as an importing client.
- Put sub-cases under `t.Run`; do not split them into parallel top-level tests.
- Keep setup, execution, and assertions visible unless specification §12 permits a real reusable abstraction.
- Same-package white-box tests are limited to safety invariants or deterministic mechanics that stable public behavior cannot isolate.
- Tests access no private symbol or representation; internal invariants are asserted through public observable behavior, generated output, or executable boundaries.
- Use `require`, not `assert`.

## Documentation Maintenance
Expand Down
51 changes: 26 additions & 25 deletions analysis/blocks_test.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,37 @@
package analysis
package analysis_test

import (
"testing"

"github.com/siyul-park/minivm/analysis"
"github.com/siyul-park/minivm/instr"
"github.com/siyul-park/minivm/pass"
"github.com/siyul-park/minivm/types"
"github.com/stretchr/testify/require"
)

func TestNewBlocksAnalysis(t *testing.T) {
require.NotNil(t, NewBlocksAnalysis())
require.NotNil(t, analysis.NewBlocksAnalysis())
}

func TestBlocks(t *testing.T) {
fn := types.NewFunctionBuilder(nil).Emit(instr.New(instr.NOP)).MustBuild()
got, err := Blocks(fn)
got, err := analysis.Blocks(fn)
require.NoError(t, err)
require.Equal(t, []*BasicBlock{{Start: 0, End: 1}}, got)
require.Equal(t, []*analysis.BasicBlock{{Start: 0, End: 1}}, got)
}

func TestBlocksAnalysis_Run(t *testing.T) {
tests := []struct {
fn *types.Function
blocks []*BasicBlock
blocks []*analysis.BasicBlock
err error
}{
{
fn: types.NewFunctionBuilder(nil).Emit(
instr.New(instr.NOP),
).MustBuild(),
blocks: []*BasicBlock{
blocks: []*analysis.BasicBlock{
{
Start: 0,
End: 1,
Expand All @@ -43,7 +44,7 @@ func TestBlocksAnalysis_Run(t *testing.T) {
fn: types.NewFunctionBuilder(nil).Emit(
instr.New(instr.UNREACHABLE),
).MustBuild(),
blocks: []*BasicBlock{
blocks: []*analysis.BasicBlock{
{
Start: 0,
End: 1,
Expand All @@ -56,7 +57,7 @@ func TestBlocksAnalysis_Run(t *testing.T) {
fn: types.NewFunctionBuilder(nil).Emit(
instr.New(instr.RETURN),
).MustBuild(),
blocks: []*BasicBlock{
blocks: []*analysis.BasicBlock{
{
Start: 0,
End: 1,
Expand All @@ -70,7 +71,7 @@ func TestBlocksAnalysis_Run(t *testing.T) {
instr.New(instr.RETURN_CALL),
instr.New(instr.I32_CONST, 1),
).MustBuild(),
blocks: []*BasicBlock{
blocks: []*analysis.BasicBlock{
{Start: 0, End: 1},
{Start: 1, End: 6},
},
Expand All @@ -81,7 +82,7 @@ func TestBlocksAnalysis_Run(t *testing.T) {
instr.New(instr.I32_CONST, 1),
instr.New(instr.I32_CONST, 2),
).MustBuild(),
blocks: []*BasicBlock{
blocks: []*analysis.BasicBlock{
{
Start: 0,
End: 3,
Expand Down Expand Up @@ -109,7 +110,7 @@ func TestBlocksAnalysis_Run(t *testing.T) {
instr.New(instr.I32_CONST, 2),
instr.New(instr.I32_CONST, 3),
).MustBuild(),
blocks: []*BasicBlock{
blocks: []*analysis.BasicBlock{
{
Start: 0,
End: 8,
Expand Down Expand Up @@ -138,7 +139,7 @@ func TestBlocksAnalysis_Run(t *testing.T) {
instr.New(instr.I32_CONST, 2),
instr.New(instr.I32_CONST, 3),
).MustBuild(),
blocks: []*BasicBlock{
blocks: []*analysis.BasicBlock{
{
Start: 0,
End: 11,
Expand All @@ -165,7 +166,7 @@ func TestBlocksAnalysis_Run(t *testing.T) {
instr.New(instr.I32_CONST, 1),
instr.New(instr.BR, uint64(uint16(-9+1<<16))),
).MustBuild(),
blocks: []*BasicBlock{
blocks: []*analysis.BasicBlock{
{
Start: 0,
End: 9,
Expand All @@ -181,7 +182,7 @@ func TestBlocksAnalysis_Run(t *testing.T) {
instr.New(instr.BR_IF, uint64(uint16(-9+1<<16))),
instr.New(instr.I32_CONST, 2),
).MustBuild(),
blocks: []*BasicBlock{
blocks: []*analysis.BasicBlock{
{
Start: 0,
End: 9,
Expand All @@ -202,7 +203,7 @@ func TestBlocksAnalysis_Run(t *testing.T) {
instr.New(instr.BR_TABLE, 1, uint64(uint16(-11+1<<16)), 0),
instr.New(instr.I32_CONST, 2),
).MustBuild(),
blocks: []*BasicBlock{
blocks: []*analysis.BasicBlock{
{
Start: 0,
End: 11,
Expand All @@ -221,13 +222,13 @@ func TestBlocksAnalysis_Run(t *testing.T) {
fn: types.NewFunctionBuilder(nil).Emit(
instr.New(instr.BR, 10),
).MustBuild(),
err: ErrInvalidJump,
err: analysis.ErrInvalidJump,
},
{
fn: types.NewFunctionBuilder(nil).Emit(
instr.New(instr.BR_TABLE, 1, 0, 10),
).MustBuild(),
err: ErrInvalidJump,
err: analysis.ErrInvalidJump,
},
{
fn: types.NewFunctionBuilder(nil).Emit(
Expand All @@ -236,7 +237,7 @@ func TestBlocksAnalysis_Run(t *testing.T) {
instr.New(instr.I32_CONST, 2),
instr.New(instr.I32_CONST, 3),
).MustBuild(),
blocks: []*BasicBlock{
blocks: []*analysis.BasicBlock{
{Start: 0, End: 18, Succs: []int{1}, Preds: nil},
{Start: 18, End: 23, Succs: nil, Preds: []int{0}},
},
Expand All @@ -248,7 +249,7 @@ func TestBlocksAnalysis_Run(t *testing.T) {
instr.New(instr.I32_CONST, 2),
instr.New(instr.I32_CONST, 3),
).MustBuild(),
blocks: []*BasicBlock{
blocks: []*analysis.BasicBlock{
{Start: 0, End: 8, Succs: []int{1}, Preds: nil},
{Start: 8, End: 18, Succs: nil, Preds: []int{0}},
},
Expand All @@ -259,7 +260,7 @@ func TestBlocksAnalysis_Run(t *testing.T) {
instr.New(instr.BR_IF, 5),
instr.New(instr.I32_CONST, 2),
).MustBuild(),
blocks: []*BasicBlock{
blocks: []*analysis.BasicBlock{
{Start: 0, End: 8, Succs: []int{1}, Preds: nil},
{Start: 8, End: 13, Succs: nil, Preds: []int{0}},
},
Expand All @@ -268,16 +269,16 @@ func TestBlocksAnalysis_Run(t *testing.T) {
fn: types.NewFunctionBuilder(nil).Emit(
instr.New(instr.BR, 100),
).MustBuild(),
err: ErrInvalidJump,
err: analysis.ErrInvalidJump,
},
}

for _, tt := range tests {
m := pass.NewManager()
pass.Register[*types.Function, []*BasicBlock](m, NewBlocksAnalysis())
pass.Register[*types.Function, []*analysis.BasicBlock](m, analysis.NewBlocksAnalysis())

t.Run(tt.fn.String(), func(t *testing.T) {
actual, err := pass.GetResult[[]*BasicBlock](m, tt.fn)
actual, err := pass.GetResult[[]*analysis.BasicBlock](m, tt.fn)
if tt.err != nil {
require.ErrorIs(t, err, tt.err)
return
Expand All @@ -298,12 +299,12 @@ func BenchmarkBlocksAnalysis_Run(b *testing.B) {
fn := types.NewFunctionBuilder(nil).Emit(emit...).MustBuild()

m := pass.NewManager()
analysis := NewBlocksAnalysis()
blocksAnalysis := analysis.NewBlocksAnalysis()

var err error
b.ResetTimer()
for b.Loop() {
_, err = analysis.Run(m, fn)
_, err = blocksAnalysis.Run(m, fn)
if err != nil {
break
}
Expand Down
69 changes: 35 additions & 34 deletions analysis/gvn_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package analysis
package analysis_test

import (
"testing"

"github.com/siyul-park/minivm/analysis"
"github.com/siyul-park/minivm/instr"
"github.com/siyul-park/minivm/pass"
"github.com/siyul-park/minivm/types"
"github.com/stretchr/testify/require"
)

func TestNewGVNAnalysis(t *testing.T) {
require.NotNil(t, NewGVNAnalysis())
require.NotNil(t, analysis.NewGVNAnalysis())
}

func TestGVNAnalysis_Run(t *testing.T) {
Expand All @@ -28,9 +29,9 @@ func TestGVNAnalysis_Run(t *testing.T) {
).MustBuild()

m := pass.NewManager()
pass.Register(m, NewBlocksAnalysis())
pass.Register(m, NewGVNAnalysis())
gvn, err := pass.GetResult[*GVN](m, fn)
pass.Register(m, analysis.NewBlocksAnalysis())
pass.Register(m, analysis.NewGVNAnalysis())
gvn, err := pass.GetResult[*analysis.GVN](m, fn)
require.NoError(t, err)
require.Len(t, gvn.Redundant, 1)
r := gvn.Redundant[9]
Expand All @@ -55,12 +56,12 @@ func TestGVNAnalysis_Run(t *testing.T) {
fn := fb.MustBuild()

m := pass.NewManager()
pass.Register(m, NewBlocksAnalysis())
pass.Register(m, NewGVNAnalysis())
gvn, err := pass.GetResult[*GVN](m, fn)
pass.Register(m, analysis.NewBlocksAnalysis())
pass.Register(m, analysis.NewGVNAnalysis())
gvn, err := pass.GetResult[*analysis.GVN](m, fn)
require.NoError(t, err)
require.Len(t, gvn.Redundant, 1)
var r Redundancy
var r analysis.Redundancy
for _, v := range gvn.Redundant {
r = v
}
Expand All @@ -80,12 +81,12 @@ func TestGVNAnalysis_Run(t *testing.T) {
fn := fb.MustBuild()

m := pass.NewManager()
pass.Register(m, NewBlocksAnalysis())
pass.Register(m, NewGVNAnalysis())
gvn, err := pass.GetResult[*GVN](m, fn)
pass.Register(m, analysis.NewBlocksAnalysis())
pass.Register(m, analysis.NewGVNAnalysis())
gvn, err := pass.GetResult[*analysis.GVN](m, fn)
require.NoError(t, err)
require.Len(t, gvn.Redundant, 1)
var r Redundancy
var r analysis.Redundancy
for _, v := range gvn.Redundant {
r = v
}
Expand All @@ -107,9 +108,9 @@ func TestGVNAnalysis_Run(t *testing.T) {
fn := fb.MustBuild()

m := pass.NewManager()
pass.Register(m, NewBlocksAnalysis())
pass.Register(m, NewGVNAnalysis())
gvn, err := pass.GetResult[*GVN](m, fn)
pass.Register(m, analysis.NewBlocksAnalysis())
pass.Register(m, analysis.NewGVNAnalysis())
gvn, err := pass.GetResult[*analysis.GVN](m, fn)
require.NoError(t, err)
require.Empty(t, gvn.Redundant)
})
Expand All @@ -125,9 +126,9 @@ func TestGVNAnalysis_Run(t *testing.T) {
fn := fb.MustBuild()

m := pass.NewManager()
pass.Register(m, NewBlocksAnalysis())
pass.Register(m, NewGVNAnalysis())
gvn, err := pass.GetResult[*GVN](m, fn)
pass.Register(m, analysis.NewBlocksAnalysis())
pass.Register(m, analysis.NewGVNAnalysis())
gvn, err := pass.GetResult[*analysis.GVN](m, fn)
require.NoError(t, err)
require.Empty(t, gvn.Redundant, "slot 2 is reassigned, so its value has no stable cross-block identity")
})
Expand All @@ -145,9 +146,9 @@ func TestGVNAnalysis_Run(t *testing.T) {
).MustBuild()

m := pass.NewManager()
pass.Register(m, NewBlocksAnalysis())
pass.Register(m, NewGVNAnalysis())
gvn, err := pass.GetResult[*GVN](m, fn)
pass.Register(m, analysis.NewBlocksAnalysis())
pass.Register(m, analysis.NewGVNAnalysis())
gvn, err := pass.GetResult[*analysis.GVN](m, fn)
require.NoError(t, err)
require.Len(t, gvn.Redundant, 1)
for _, r := range gvn.Redundant {
Expand All @@ -167,9 +168,9 @@ func TestGVNAnalysis_Run(t *testing.T) {
).MustBuild()

m := pass.NewManager()
pass.Register(m, NewBlocksAnalysis())
pass.Register(m, NewGVNAnalysis())
gvn, err := pass.GetResult[*GVN](m, fn)
pass.Register(m, analysis.NewBlocksAnalysis())
pass.Register(m, analysis.NewGVNAnalysis())
gvn, err := pass.GetResult[*analysis.GVN](m, fn)
require.NoError(t, err)
require.Len(t, gvn.Redundant, 1)
})
Expand All @@ -186,9 +187,9 @@ func TestGVNAnalysis_Run(t *testing.T) {
).MustBuild()

m := pass.NewManager()
pass.Register(m, NewBlocksAnalysis())
pass.Register(m, NewGVNAnalysis())
gvn, err := pass.GetResult[*GVN](m, fn)
pass.Register(m, analysis.NewBlocksAnalysis())
pass.Register(m, analysis.NewGVNAnalysis())
gvn, err := pass.GetResult[*analysis.GVN](m, fn)
require.NoError(t, err)
require.Empty(t, gvn.Redundant)
})
Expand All @@ -208,9 +209,9 @@ func TestGVNAnalysis_Run(t *testing.T) {
).MustBuild()

m := pass.NewManager()
pass.Register(m, NewBlocksAnalysis())
pass.Register(m, NewGVNAnalysis())
gvn, err := pass.GetResult[*GVN](m, fn)
pass.Register(m, analysis.NewBlocksAnalysis())
pass.Register(m, analysis.NewGVNAnalysis())
gvn, err := pass.GetResult[*analysis.GVN](m, fn)
require.NoError(t, err)
require.Empty(t, gvn.Redundant)
})
Expand All @@ -229,9 +230,9 @@ func TestGVNAnalysis_Run(t *testing.T) {
).MustBuild()

m := pass.NewManager()
pass.Register(m, NewBlocksAnalysis())
pass.Register(m, NewGVNAnalysis())
gvn, err := pass.GetResult[*GVN](m, fn)
pass.Register(m, analysis.NewBlocksAnalysis())
pass.Register(m, analysis.NewGVNAnalysis())
gvn, err := pass.GetResult[*analysis.GVN](m, fn)
require.NoError(t, err)
require.Empty(t, gvn.Redundant)
})
Expand Down
Loading
Loading