133 lines
2.9 KiB
Go
133 lines
2.9 KiB
Go
// Package x64 models the small subset of x86-64 that the calculator emits,
|
|
// and can execute it.
|
|
//
|
|
// A simulator exists because the emitted assembly must be testable
|
|
// everywhere, including on machines that are not x86-64. Assembling and
|
|
// running the output only works on an x86-64 host with a toolchain; the
|
|
// simulator checks the same instructions' semantics anywhere, so the test
|
|
// suite is meaningful in CI regardless of the runner's architecture.
|
|
package x64
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Register is a 64-bit general purpose register.
|
|
type Register string
|
|
|
|
const (
|
|
RAX Register = "rax"
|
|
RBX Register = "rbx"
|
|
RCX Register = "rcx"
|
|
RDX Register = "rdx"
|
|
RSI Register = "rsi"
|
|
RDI Register = "rdi"
|
|
RBP Register = "rbp"
|
|
RSP Register = "rsp"
|
|
R8 Register = "r8"
|
|
R9 Register = "r9"
|
|
R10 Register = "r10"
|
|
R11 Register = "r11"
|
|
)
|
|
|
|
// Op is an opcode in the emitted subset.
|
|
type Op string
|
|
|
|
const (
|
|
MOV Op = "mov"
|
|
ADD Op = "add"
|
|
SUB Op = "sub"
|
|
IMUL Op = "imul"
|
|
IDIV Op = "idiv"
|
|
NEG Op = "neg"
|
|
PUSH Op = "push"
|
|
POP Op = "pop"
|
|
CQO Op = "cqo" // sign-extend rax into rdx:rax, required before idiv
|
|
XCHG Op = "xchg"
|
|
RET Op = "ret"
|
|
)
|
|
|
|
// Operand is a register, an immediate, or a memory reference.
|
|
type Operand struct {
|
|
Reg Register
|
|
Imm int64
|
|
IsImm bool
|
|
IsMem bool
|
|
// Offset is the displacement for a memory operand, as in [rbp-8].
|
|
Offset int
|
|
Base Register
|
|
}
|
|
|
|
// R returns a register operand.
|
|
func R(r Register) Operand { return Operand{Reg: r} }
|
|
|
|
// I returns an immediate operand.
|
|
func I(v int64) Operand { return Operand{Imm: v, IsImm: true} }
|
|
|
|
// M returns a memory operand [base+offset].
|
|
func M(base Register, offset int) Operand {
|
|
return Operand{IsMem: true, Base: base, Offset: offset}
|
|
}
|
|
|
|
func (o Operand) String() string {
|
|
switch {
|
|
case o.IsImm:
|
|
return strconv.FormatInt(o.Imm, 10)
|
|
case o.IsMem:
|
|
if o.Offset == 0 {
|
|
return fmt.Sprintf("[%s]", o.Base)
|
|
}
|
|
if o.Offset > 0 {
|
|
return fmt.Sprintf("[%s+%d]", o.Base, o.Offset)
|
|
}
|
|
return fmt.Sprintf("[%s%d]", o.Base, o.Offset)
|
|
default:
|
|
return string(o.Reg)
|
|
}
|
|
}
|
|
|
|
// Instruction is one emitted instruction, optionally with a comment
|
|
// explaining why it is there.
|
|
type Instruction struct {
|
|
Op Op
|
|
Operands []Operand
|
|
Comment string
|
|
}
|
|
|
|
func (i Instruction) String() string {
|
|
var sb strings.Builder
|
|
sb.WriteString(" ")
|
|
sb.WriteString(string(i.Op))
|
|
for n, o := range i.Operands {
|
|
if n == 0 {
|
|
sb.WriteString(" ")
|
|
} else {
|
|
sb.WriteString(", ")
|
|
}
|
|
sb.WriteString(o.String())
|
|
}
|
|
if i.Comment != "" {
|
|
// Pad so comments line up, which makes the output readable.
|
|
for sb.Len() < 36 {
|
|
sb.WriteByte(' ')
|
|
}
|
|
sb.WriteString("; " + i.Comment)
|
|
}
|
|
return sb.String()
|
|
}
|
|
|
|
// Program is a sequence of instructions.
|
|
type Program []Instruction
|
|
|
|
// String renders the program as NASM-syntax assembly.
|
|
func (p Program) String() string {
|
|
var sb strings.Builder
|
|
for _, i := range p {
|
|
sb.WriteString(i.String())
|
|
sb.WriteByte('\n')
|
|
}
|
|
return sb.String()
|
|
}
|