diff --git a/asm/amd64/amd64.go b/asm/amd64/amd64.go index 527942a..a43d16f 100644 --- a/asm/amd64/amd64.go +++ b/asm/amd64/amd64.go @@ -101,6 +101,7 @@ const ( // One operand Idiv Opcode = "idiv" + Push Opcode = "push" // No operands Ret Opcode = "ret" @@ -159,6 +160,12 @@ func (j JmpInstruction) InstructionString() string { return fmt.Sprintf("jmp %s", j) } +type Call string + +func (c Call) InstructionString() string { + return fmt.Sprintf("call %s", c) +} + type SetCCInstruction struct { Cond CondCode Dst Operand @@ -168,6 +175,18 @@ func (si *SetCCInstruction) InstructionString() string { return fmt.Sprintf("set%s %s", si.Cond, si.Dst.OperandString(One)) } +type AllocateStack uint + +func (as AllocateStack) InstructionString() string { + return fmt.Sprintf("sub rsp, %d", as) +} + +type DeallocateStack uint + +func (ds DeallocateStack) InstructionString() string { + return fmt.Sprintf("add rsp, %d", ds) +} + type OperandSize int type Operand interface { @@ -202,6 +221,54 @@ func (r Register) OperandString(size OperandSize) string { return "eax" } return "rax" + case CX: + switch size { + case One: + return "cl" + case Four: + return "ecx" + } + return "rcx" + case DX: + switch size { + case One: + return "dl" + case Four: + return "edx" + } + return "rdx" + case DI: + switch size { + case One: + return "dil" + case Four: + return "edi" + } + return "rdi" + case SI: + switch size { + case One: + return "sil" + case Four: + return "esi" + } + return "rsi" + case R8: + switch size { + case One: + return "r8b" + case Four: + return "r8d" + } + return "r8" + case R9: + switch size { + case One: + return "r9b" + case Four: + return "r9d" + } + return "r9" case R10: switch size { case One: @@ -218,8 +285,9 @@ func (r Register) OperandString(size OperandSize) string { return "r11d" } return "r11" + default: + panic(fmt.Sprintf("unexpected amd64.Register: %#v", r)) } - return "INVALID_REGISTER" } type Imm int64 diff --git a/asm/amd64/codegen.go b/asm/amd64/codegen.go index d93ec52..56ae148 100644 --- a/asm/amd64/codegen.go +++ b/asm/amd64/codegen.go @@ -2,6 +2,7 @@ package amd64 import ( "fmt" + "slices" "robaertschi.xyz/robaertschi/tt/ast" "robaertschi.xyz/robaertschi/tt/ttir" @@ -122,6 +123,67 @@ func cgInstruction(i ttir.Instruction) []Instruction { return []Instruction{JmpInstruction(i)} case *ttir.Copy: return []Instruction{&SimpleInstruction{Opcode: Mov, Lhs: toAsmOperand(i.Dst), Rhs: toAsmOperand(i.Src)}} + case *ttir.Call: + registerArgs := i.Arguments[0:min(len(callConvArgs), len(i.Arguments))] + stackArgs := []ttir.Operand{} + if len(callConvArgs) < len(i.Arguments) { + stackArgs = i.Arguments[len(callConvArgs):len(i.Arguments)] + } + + stackPadding := 0 + if len(stackArgs)%2 != 0 { + stackPadding = 8 + } + + instructions := []Instruction{} + + if stackPadding > 0 { + instructions = append(instructions, AllocateStack(stackPadding)) + } + + for i, arg := range registerArgs { + instructions = append(instructions, + &SimpleInstruction{ + Opcode: Mov, + Rhs: toAsmOperand(arg), + Lhs: callConvArgs[i], + }, + ) + } + + for _, arg := range slices.Backward(stackArgs) { + switch arg := toAsmOperand(arg).(type) { + case Imm: + instructions = append(instructions, &SimpleInstruction{Opcode: Push, Lhs: arg}) + case Register: + instructions = append(instructions, &SimpleInstruction{Opcode: Push, Lhs: arg}) + case Pseudo: + instructions = append(instructions, + &SimpleInstruction{Opcode: Mov, Rhs: arg, Lhs: AX}, + &SimpleInstruction{Opcode: Push, Lhs: AX}, + ) + case Stack: + instructions = append(instructions, + &SimpleInstruction{Opcode: Mov, Rhs: arg, Lhs: AX}, + &SimpleInstruction{Opcode: Push, Lhs: AX}, + ) + default: + panic(fmt.Sprintf("unexpected amd64.Operand: %#v", arg)) + } + } + + instructions = append(instructions, Call(i.FunctionName)) + bytesToRemove := 8*len(stackArgs) + stackPadding + if bytesToRemove != 0 { + instructions = append(instructions, DeallocateStack(bytesToRemove)) + } + + if i.ReturnValue != nil { + asmDst := toAsmOperand(i.ReturnValue) + instructions = append(instructions, &SimpleInstruction{Opcode: Mov, Rhs: AX, Lhs: asmDst}) + } + + return instructions default: panic(fmt.Sprintf("unexpected ttir.Instruction: %#v", i)) } @@ -239,11 +301,11 @@ func rpInstruction(i Instruction, r *replacePseudoPass) Instruction { Cond: i.Cond, Dst: pseudoToStack(i.Dst, r), } - case *JumpCCInstruction, JmpInstruction, Label: + case *JumpCCInstruction, JmpInstruction, Label, AllocateStack, DeallocateStack, Call: return i + default: + panic(fmt.Sprintf("unexpected amd64.Instruction: %#v", i)) } - - panic("invalid instruction") } func pseudoToStack(op Operand, r *replacePseudoPass) Operand { @@ -347,9 +409,9 @@ func fixupInstruction(i Instruction) []Instruction { case *SetCCInstruction: return []Instruction{i} - case *JumpCCInstruction, JmpInstruction, Label: + case *JumpCCInstruction, JmpInstruction, Label, AllocateStack, DeallocateStack, Call: return []Instruction{i} + default: + panic(fmt.Sprintf("unexpected amd64.Instruction: %#v", i)) } - - panic("invalid instruction") } diff --git a/design.md b/design.md index c5d71a9..ccd36b6 100644 --- a/design.md +++ b/design.md @@ -22,3 +22,24 @@ fn main() = { }; ``` + +## ABI + +ABI Considarations: + +Each file is a module. Everything inside a module is prefixed with the module name. For Example: + +```asm +# - In module1 folder +# mod module1; +# fn test(): i64 = ... +module1_test: +# - In module1/module2 folder +# mod module2; +# fn test(): i64 = ... +module1_module2_test: +``` + +Except the main module, all members of the main module are exposed with their concrete name. + +If we ever add something like generics, that will have to be encoded in the function name too. diff --git a/flake.nix b/flake.nix index 4eccdc3..c0d1846 100644 --- a/flake.nix +++ b/flake.nix @@ -44,8 +44,8 @@ qbe ( if system == "x86_64-linux" - then [fasm] - else [] + then [fasm gdb] + else [lldb] ) ]; };