tt/asm/amd64/amd64.go
2025-01-21 14:24:32 +01:00

116 lines
1.8 KiB
Go

package amd64
import (
"fmt"
"strings"
)
type Program struct {
Functions []Function
}
func (p *Program) Emit() string {
var builder strings.Builder
for _, function := range p.Functions {
builder.WriteString(function.Emit())
builder.WriteString("\n")
}
return builder.String()
}
type Function struct {
Name string
Instructions []Instruction
}
// 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"
func (f *Function) Emit() string {
var builder strings.Builder
builder.WriteString(executableAsmHeader)
builder.WriteString(fmt.Sprintf("%s:\n", f.Name))
for _, inst := range f.Instructions {
builder.WriteString(fmt.Sprintf(" %s\n", inst.InstructionString()))
}
return builder.String()
}
type Opcode string
const (
Mov Opcode = "mov"
Ret Opcode = "ret"
)
type Instruction struct {
Opcode Opcode
Lhs Operand
Rhs Operand
}
func (i *Instruction) InstructionString() string {
if i.Lhs == nil {
return fmt.Sprintf("%s", i.Opcode)
}
return fmt.Sprintf("%s %s, %s", i.Opcode, i.Lhs.OperandString(Eight), i.Rhs.OperandString(Eight))
}
type OperandSize int
type Operand interface {
OperandString(OperandSize) string
}
type Register int
const (
AX Register = iota
R10
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"
}
return "INVALID_REGISTER"
}
type Imm int64
func (i Imm) OperandString(size OperandSize) string {
return fmt.Sprintf("%d", i)
}