Files
zzg a22c99344e
ci / test (push) Canceled after 0s
initial commit
2026-09-25 22:12:55 -07:00

205 lines
5.0 KiB
Go

package x64
import (
"errors"
"testing"
)
func TestOperandRendering(t *testing.T) {
tests := []struct {
op Operand
want string
}{
{R(RAX), "rax"},
{I(42), "42"},
{I(-1), "-1"},
{M(RBP, -8), "[rbp-8]"},
{M(RBP, 16), "[rbp+16]"},
{M(RSP, 0), "[rsp]"},
}
for _, tt := range tests {
if got := tt.op.String(); got != tt.want {
t.Errorf("= %q, want %q", got, tt.want)
}
}
}
func TestInstructionRendering(t *testing.T) {
i := Instruction{Op: MOV, Operands: []Operand{R(RAX), I(5)}, Comment: "load"}
got := i.String()
if want := " mov rax, 5"; got[:len(want)] != want {
t.Errorf("= %q, want it to start with %q", got, want)
}
if got[len(got)-6:] != "; load" {
t.Errorf("comment missing: %q", got)
}
}
func TestMachineArithmetic(t *testing.T) {
m := NewMachine()
prog := Program{
{Op: MOV, Operands: []Operand{R(RAX), I(10)}},
{Op: MOV, Operands: []Operand{R(RCX), I(3)}},
{Op: ADD, Operands: []Operand{R(RAX), R(RCX)}},
}
got, err := m.Run(prog)
if err != nil {
t.Fatal(err)
}
if got != 13 {
t.Errorf("= %d, want 13", got)
}
}
func TestMachinePushPop(t *testing.T) {
m := NewMachine()
prog := Program{
{Op: MOV, Operands: []Operand{R(RAX), I(1)}},
{Op: PUSH, Operands: []Operand{R(RAX)}},
{Op: MOV, Operands: []Operand{R(RAX), I(2)}},
{Op: POP, Operands: []Operand{R(RCX)}},
}
if _, err := m.Run(prog); err != nil {
t.Fatal(err)
}
if got := m.Get(RCX); got != 1 {
t.Errorf("popped %d, want 1", got)
}
if m.StackDepth() != 0 {
t.Errorf("stack depth = %d, want 0", m.StackDepth())
}
}
func TestMachinePopEmptyStack(t *testing.T) {
m := NewMachine()
_, err := m.Run(Program{{Op: POP, Operands: []Operand{R(RAX)}}})
if !errors.Is(err, ErrStack) {
t.Errorf("err = %v, want ErrStack", err)
}
}
// cqo must sign-extend RAX into RDX, or idiv divides against garbage.
func TestCQOSignExtends(t *testing.T) {
m := NewMachine()
m.Run(Program{
{Op: MOV, Operands: []Operand{R(RAX), I(-1)}},
{Op: CQO},
})
if got := m.Get(RDX); got != -1 {
t.Errorf("rdx = %d after cqo of a negative value, want -1", got)
}
m2 := NewMachine()
m2.Run(Program{
{Op: MOV, Operands: []Operand{R(RAX), I(1)}},
{Op: CQO},
})
if got := m2.Get(RDX); got != 0 {
t.Errorf("rdx = %d after cqo of a positive value, want 0", got)
}
}
func TestMachineDivision(t *testing.T) {
m := NewMachine()
prog := Program{
{Op: MOV, Operands: []Operand{R(RAX), I(17)}},
{Op: MOV, Operands: []Operand{R(RCX), I(5)}},
{Op: CQO},
{Op: IDIV, Operands: []Operand{R(RCX)}},
}
got, err := m.Run(prog)
if err != nil {
t.Fatal(err)
}
if got != 3 {
t.Errorf("quotient = %d, want 3", got)
}
if rem := m.Get(RDX); rem != 2 {
t.Errorf("remainder = %d, want 2", rem)
}
}
func TestMachineDivideByZero(t *testing.T) {
m := NewMachine()
_, err := m.Run(Program{
{Op: MOV, Operands: []Operand{R(RAX), I(1)}},
{Op: MOV, Operands: []Operand{R(RCX), I(0)}},
{Op: CQO},
{Op: IDIV, Operands: []Operand{R(RCX)}},
})
if !errors.Is(err, ErrDivideByZero) {
t.Errorf("err = %v, want ErrDivideByZero", err)
}
}
// The most negative value divided by -1 has no representable quotient. A
// real CPU raises #DE; Go would panic. The simulator must report it.
func TestMachineDivisionOverflow(t *testing.T) {
m := NewMachine()
_, err := m.Run(Program{
{Op: MOV, Operands: []Operand{R(RAX), I(-1 << 63)}},
{Op: MOV, Operands: []Operand{R(RCX), I(-1)}},
{Op: CQO},
{Op: IDIV, Operands: []Operand{R(RCX)}},
})
if !errors.Is(err, ErrOverflow) {
t.Errorf("err = %v, want ErrOverflow", err)
}
}
func TestMachineMemory(t *testing.T) {
m := NewMachine()
prog := Program{
{Op: MOV, Operands: []Operand{R(RAX), I(99)}},
{Op: MOV, Operands: []Operand{M(RBP, -8), R(RAX)}},
{Op: MOV, Operands: []Operand{R(RAX), I(0)}},
{Op: MOV, Operands: []Operand{R(RAX), M(RBP, -8)}},
}
got, err := m.Run(prog)
if err != nil {
t.Fatal(err)
}
if got != 99 {
t.Errorf("= %d, want 99", got)
}
}
func TestMachineRejectsBadOperands(t *testing.T) {
m := NewMachine()
// mov with one operand.
if _, err := m.Run(Program{{Op: MOV, Operands: []Operand{R(RAX)}}}); err == nil {
t.Error("expected an operand count error")
}
// Writing to an immediate.
if _, err := m.Run(Program{{Op: MOV, Operands: []Operand{I(1), I(2)}}}); err == nil {
t.Error("expected an error writing to an immediate")
}
// Unknown opcode.
if _, err := m.Run(Program{{Op: Op("frobnicate")}}); err == nil {
t.Error("expected an unsupported instruction error")
}
}
func TestMachineStepLimit(t *testing.T) {
m := NewMachine()
m.MaxSteps = 3
prog := Program{
{Op: RET}, {Op: RET}, {Op: RET}, {Op: RET},
}
if _, err := m.Run(prog); err == nil {
t.Error("expected a step limit error")
}
}
func TestXCHG(t *testing.T) {
m := NewMachine()
m.Run(Program{
{Op: MOV, Operands: []Operand{R(RAX), I(1)}},
{Op: MOV, Operands: []Operand{R(RCX), I(2)}},
{Op: XCHG, Operands: []Operand{R(RAX), R(RCX)}},
})
if m.Get(RAX) != 2 || m.Get(RCX) != 1 {
t.Errorf("after xchg: rax=%d rcx=%d, want 2 and 1", m.Get(RAX), m.Get(RCX))
}
}