205 lines
5.0 KiB
Go
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))
|
|
}
|
|
}
|