tt/asm/amd64/amd64.go
2025-02-02 14:15:16 +01:00

238 lines
4.1 KiB
Go

package amd64
import (
"fmt"
"strings"
)
type Program struct {
Functions []Function
MainFunction *Function
}
func (p *Program) executableAsmHeader() string {
if p.MainFunction.HasReturnValue {
return executableAsmHeader
}
return executableAsmHeaderNoReturnValue
}
// This calls the main function and uses it's return value to exit
const executableAsmHeader = "format ELF64 executable\n" +
"segment readable executable\n" +
"entry _start\n" +
"_start:\n" +
" call main\n" +
" mov rdi, rax\n" +
" mov rax, 60\n" +
" syscall\n"
const executableAsmHeaderNoReturnValue = "format ELF64 executable\n" +
"segment readable executable\n" +
"entry _start\n" +
"_start:\n" +
" call main\n" +
" mov rdi, 0\n" +
" mov rax, 60\n" +
" syscall\n"
func (p *Program) Emit() string {
var builder strings.Builder
builder.WriteString(p.executableAsmHeader())
for _, function := range p.Functions {
builder.WriteString(function.Emit())
builder.WriteString("\n")
}
return builder.String()
}
type Function struct {
StackOffset int64
Name string
HasReturnValue bool
Instructions []Instruction
}
func (f *Function) Emit() string {
var builder strings.Builder
builder.WriteString(fmt.Sprintf("%s:\n push rbp\n mov rbp, rsp\n add rsp, %d\n", f.Name, f.StackOffset))
for _, inst := range f.Instructions {
builder.WriteString(fmt.Sprintf(" %s\n", inst.InstructionString()))
}
return builder.String()
}
type CondCode string
const (
Equal CondCode = "e"
NotEqual CondCode = "ne"
Greater CondCode = "g"
GreaterEqual CondCode = "ge"
Less CondCode = "l"
LessEqual CondCode = "le"
)
type Opcode string
const (
// Two operands
Mov Opcode = "mov" // Lhs: dst, Rhs: src, or better said intel syntax
Add Opcode = "add"
Sub Opcode = "sub"
Imul Opcode = "imul"
Cmp Opcode = "cmp"
// One operand
Idiv Opcode = "idiv"
// No operands
Ret Opcode = "ret"
Cdq Opcode = "cdq"
)
type Instruction interface {
InstructionString() string
}
type SimpleInstruction struct {
Opcode Opcode
// Dst
Lhs Operand
// Src
Rhs Operand
}
func (i *SimpleInstruction) InstructionString() string {
if i.Opcode == Ret {
return fmt.Sprintf("mov rsp, rbp\n pop rbp\n ret\n")
}
// No operands
if i.Lhs == nil {
return fmt.Sprintf("%s", i.Opcode)
}
// One operand
if i.Rhs == nil {
return fmt.Sprintf("%s %s", i.Opcode, i.Lhs.OperandString(Eight))
}
// Two operands
return fmt.Sprintf("%s %s, %s", i.Opcode, i.Lhs.OperandString(Eight), i.Rhs.OperandString(Eight))
}
type Label string
func (l Label) InstructionString() string {
return fmt.Sprintf("%s:", l)
}
type JumpCCInstruction struct {
Cond CondCode
Dst string
}
func (j *JumpCCInstruction) InstructionString() string {
return fmt.Sprintf("j%s %s", j.Cond, j.Dst)
}
type JmpInstruction string
func (j JmpInstruction) InstructionString() string {
return fmt.Sprintf("jmp %s", j)
}
type SetCCInstruction struct {
Cond CondCode
Dst Operand
}
func (si *SetCCInstruction) InstructionString() string {
return fmt.Sprintf("set%s %s", si.Cond, si.Dst.OperandString(One))
}
type OperandSize int
type Operand interface {
OperandString(OperandSize) string
}
type Register int
const (
AX Register = iota
R10
R11
One OperandSize = iota
Four
Eight
)
func (r Register) OperandString(size OperandSize) string {
switch r {
case AX:
switch size {
case One:
return "al"
case Four:
return "eax"
}
return "rax"
case R10:
switch size {
case One:
return "r10b"
case Four:
return "r10d"
}
return "r10"
case R11:
switch size {
case One:
return "r11b"
case Four:
return "r11d"
}
return "r11"
}
return "INVALID_REGISTER"
}
type Imm int64
func (i Imm) OperandString(size OperandSize) string {
return fmt.Sprintf("%d", i)
}
type Stack int64
func (s Stack) OperandString(size OperandSize) string {
var sizeString string
switch size {
case One:
sizeString = "byte"
case Four:
sizeString = "dword"
case Eight:
sizeString = "qword"
}
return fmt.Sprintf("%s [rsp %+d]", sizeString, s)
}
type Pseudo string
func (s Pseudo) OperandString(size OperandSize) string {
panic("Pseudo Operands cannot be represented in asm")
}