tt/asm/amd64/amd64_test.go

268 lines
6.2 KiB
Go

package amd64
import (
_ "embed"
"strings"
"testing"
"robaertschi.xyz/robaertschi/tt/ast"
"robaertschi.xyz/robaertschi/tt/ttir"
)
func expectProgram(t *testing.T, expected Program, actual Program) {
t.Helper()
if len(expected.Functions) != len(actual.Functions) {
t.Errorf("Expected %d functions but got %d", len(expected.Functions), len(actual.Functions))
return
}
for i := range expected.Functions {
expectFunction(t, expected.Functions[i], actual.Functions[i])
}
}
func expectFunction(t *testing.T, expected Function, actual Function) {
t.Helper()
if expected.Name != actual.Name {
t.Errorf("Expected function name %q but got %q", expected.Name, actual.Name)
}
if len(expected.Instructions) != len(actual.Instructions) {
t.Errorf("Expected %d instructions but got %d, expected: %v, actual: %v", len(expected.Instructions), len(actual.Instructions), expected.Instructions, actual.Instructions)
return
}
for i := range expected.Instructions {
expectInstruction(t, expected.Instructions[i], actual.Instructions[i])
}
}
func expectInstruction(t *testing.T, expected Instruction, actual Instruction) {
t.Helper()
switch expected := expected.(type) {
case *SimpleInstruction:
actual, ok := actual.(*SimpleInstruction)
if !ok {
t.Errorf("Expected SimpleInstruction but got %T", actual)
return
}
if expected.Opcode != actual.Opcode {
t.Errorf("Expected opcode %q but got %q", expected.Opcode, actual.Opcode)
}
switch expected.Opcode {
case Mov:
expectOperand(t, expected.Lhs, actual.Lhs)
expectOperand(t, expected.Rhs, actual.Rhs)
case Ret:
// nothing to do
}
case *SetCCInstruction:
actual, ok := actual.(*SetCCInstruction)
if !ok {
t.Errorf("Expected SetCCInstruction but got %T", actual)
return
}
if expected.Cond != actual.Cond {
t.Errorf("Expected condition %q but got %q", expected.Cond, actual.Cond)
}
expectOperand(t, expected.Dst, actual.Dst)
}
}
func expectOperand(t *testing.T, expected Operand, actual Operand) {
t.Helper()
switch expected := expected.(type) {
case Register:
actual, ok := actual.(Register)
if !ok {
t.Errorf("Expected Register but got %T", actual)
return
}
if expected != actual {
t.Errorf("Expected Register %q but got %q", expected, actual)
}
case Imm:
actual, ok := actual.(Imm)
if !ok {
t.Errorf("Expected Immediate but got %T", actual)
return
}
if expected != actual {
t.Errorf("Expected Immediate %q but got %q", expected, actual)
}
case Stack:
actual, ok := actual.(Stack)
if !ok {
t.Errorf("Expected Stack but got %T", actual)
}
if expected != actual {
t.Errorf("Expected Stack value %q but got %q", expected, actual)
}
case Pseudo:
actual, ok := actual.(Pseudo)
if !ok {
t.Errorf("Expected Stack but got %T", actual)
}
if expected != actual {
t.Errorf("Expected Stack value %q but got %q", expected, actual)
}
default:
t.Errorf("Unknown operand type %T", expected)
}
}
func trim(s string) string {
return strings.Trim(s, " \n\t")
}
func TestOperands(t *testing.T) {
var op Operand
op = AX
if str := op.OperandString(One); str != "al" {
t.Errorf("The register AX should be \"al\" but got %q", str)
}
op = Imm(3)
if str := op.OperandString(One); str != "3" {
t.Errorf("The immediate value 3 should be \"3\" but got %q", str)
}
}
//go:embed basic_test.txt
var basicTest string
func TestCodegen(t *testing.T) {
program := &ttir.Program{
Functions: []ttir.Function{
{
Name: "main",
HasReturnValue: true,
Instructions: []ttir.Instruction{
&ttir.Ret{Op: &ttir.Constant{Value: 0}},
},
},
},
}
expectedProgram := Program{
Functions: []Function{
{
Name: "main",
Instructions: []Instruction{
&SimpleInstruction{
Opcode: Mov,
Lhs: AX,
Rhs: Imm(0),
},
&SimpleInstruction{
Opcode: Ret,
},
},
HasReturnValue: true,
},
},
}
actualProgram := CgProgram(program)
expectProgram(t, expectedProgram, *actualProgram)
actual := actualProgram.Emit()
expected := basicTest
if trim(actual) != trim(expected) {
t.Errorf("Expected program to be:\n>>%s<<\nbut got:\n>>%s<<\n", trim(expected), trim(actual))
}
}
//go:embed binary_test.txt
var binaryTest string
func TestBinary(t *testing.T) {
program := &ttir.Program{
Functions: []ttir.Function{
{
Name: "main",
Instructions: []ttir.Instruction{
&ttir.Binary{
Lhs: &ttir.Constant{Value: 3},
Rhs: &ttir.Constant{Value: 3},
Operator: ast.Add,
Dst: &ttir.Var{Value: "temp.1"},
},
&ttir.Ret{Op: &ttir.Var{Value: "temp.1"}},
},
HasReturnValue: true,
},
},
}
actual := CgProgram(program).Emit()
if trim(actual) != trim(binaryTest) {
t.Errorf("Expected program to be:\n>>%s<<\nbut got:\n>>%s<<\n", trim(binaryTest), trim(actual))
}
}
//go:embed equality_test.txt
var equalityTest string
// There was once a bug with how the cmp instructions were generated, this check should fail if it happens again
func TestEqualityOperators(t *testing.T) {
program := ttir.Program{
Functions: []ttir.Function{
{
Name: "main",
HasReturnValue: false,
Instructions: []ttir.Instruction{
&ttir.Binary{
Lhs: &ttir.Constant{Value: 5},
Rhs: &ttir.Constant{Value: 4},
Dst: &ttir.Var{Value: "temp.1"},
Operator: ast.LessThanEqual,
},
&ttir.Binary{
Lhs: &ttir.Constant{Value: 5},
Rhs: &ttir.Constant{Value: 4},
Dst: &ttir.Var{Value: "temp.2"},
Operator: ast.LessThan,
},
&ttir.Binary{
Lhs: &ttir.Constant{Value: 5},
Rhs: &ttir.Constant{Value: 4},
Dst: &ttir.Var{Value: "temp.3"},
Operator: ast.GreaterThanEqual,
},
&ttir.Binary{
Lhs: &ttir.Constant{Value: 5},
Rhs: &ttir.Constant{Value: 4},
Dst: &ttir.Var{Value: "temp.4"},
Operator: ast.GreaterThan,
},
&ttir.Ret{},
},
},
},
}
actual := CgProgram(&program).Emit()
actualTrimmed := trim(actual)
expectedTrimmed := trim(actualTrimmed)
if expectedTrimmed != actualTrimmed {
t.Errorf("Expected program to be:\n>>%s<<\nbut got:\n>>%s<<\n", expectedTrimmed, actualTrimmed)
}
}