Compare commits

...

3 Commits

9 changed files with 248 additions and 45 deletions

View File

@ -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

View File

@ -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")
}

View File

@ -22,7 +22,12 @@ func extraLabel() string {
var Stub string
func emitf(w io.Writer, format string, args ...any) error {
_, err := w.Write([]byte(fmt.Sprintf(format, args...)))
_, err := w.Write(fmt.Appendf(nil, format, args...))
return err
}
func emit(w io.Writer, content string) error {
_, err := w.Write(fmt.Append(nil, content))
return err
}
@ -101,14 +106,9 @@ func emitInstruction(w io.Writer, i ttir.Instruction) error {
switch i := i.(type) {
case *ttir.Ret:
if op := i.Op; op != nil {
if err := emitf(w, "\tret %s\n", emitOperand(i.Op)); err != nil {
return err
}
return emitf(w, "\tret %s\n", emitOperand(i.Op))
} else {
if err := emitf(w, "\tret\n"); err != nil {
return err
}
return emitf(w, "\tret\n")
}
case *ttir.Binary:
var inst string
@ -134,31 +134,36 @@ func emitInstruction(w io.Writer, i ttir.Instruction) error {
case ast.LessThanEqual:
inst = "cslel"
}
if err := emitf(w, "\t%s =l %s %s, %s\n", emitOperand(i.Dst), inst, emitOperand(i.Lhs), emitOperand(i.Rhs)); err != nil {
return err
}
return emitf(w, "\t%s =l %s %s, %s\n", emitOperand(i.Dst), inst, emitOperand(i.Lhs), emitOperand(i.Rhs))
case *ttir.Copy:
if err := emitf(w, "\t%s =l copy %s\n", emitOperand(i.Dst), emitOperand(i.Src)); err != nil {
return err
}
emitf(w, "\t%s =l copy %s\n", emitOperand(i.Dst), emitOperand(i.Src))
case ttir.Label:
if err := emitf(w, "@%s\n", string(i)); err != nil {
return err
}
return emitf(w, "@%s\n", string(i))
case ttir.Jump:
if err := emitf(w, "\tjmp @%s\n", string(i)); err != nil {
return err
}
return emitf(w, "\tjmp @%s\n", string(i))
case *ttir.JumpIfNotZero:
after := extraLabel()
if err := emitf(w, "\tjnz %s, @%s, @%s\n@%s\n", emitOperand(i.Value), i.Label, after, after); err != nil {
return err
}
return emitf(w, "\tjnz %s, @%s, @%s\n@%s\n", emitOperand(i.Value), i.Label, after, after)
case *ttir.JumpIfZero:
after := extraLabel()
if err := emitf(w, "\tjnz %s, @%s, @%s\n@%s\n", emitOperand(i.Value), after, i.Label, after); err != nil {
return err
return emitf(w, "\tjnz %s, @%s, @%s\n@%s\n", emitOperand(i.Value), after, i.Label, after)
case *ttir.Call:
b := strings.Builder{}
b.WriteRune('\t')
if i.ReturnValue != nil {
b.WriteString(emitOperand(i.ReturnValue) + " =l ")
}
b.WriteString("call $" + i.FunctionName + "(")
for j, arg := range i.Arguments {
b.WriteString("l " + emitOperand(arg))
if j < (len(i.Arguments) - 1) {
b.WriteString(", ")
}
}
b.WriteString(")\n")
return emit(w, b.String())
default:
panic("unkown instruction")
}

View File

@ -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.

View File

@ -44,8 +44,8 @@
qbe
(
if system == "x86_64-linux"
then [fasm]
else []
then [fasm gdb]
else [lldb]
)
];
};

View File

@ -454,6 +454,10 @@ func (p *Parser) parseFunctionCall() ast.Expression {
p.nextToken()
args = append(args, p.parseExpression(PrecLowest))
if !p.peekTokenIs(token.Comma) {
break
}
p.nextToken()
}
// Move onto the ')'

17
test.tt
View File

@ -1,17 +1,12 @@
fn main(): i64 = {
i := 5;
test2(3, 0)
};
if i == 5 {
fn test2(until: i64, i: i64): i64 = {
if i >= until {
0
} else {
1
};
test2(3)
};
fn test2(hello: i64): i64 = {
hello // Comment test
test2(until, i+1) + 1
}
};

View File

@ -148,6 +148,24 @@ func emitExpression(expr tast.Expression) (Operand, []Instruction) {
return nil, instructions
case *tast.VariableReference:
return &Var{Value: expr.Identifier}, []Instruction{}
case *tast.FunctionCall:
var dst Operand
if !expr.ReturnType.IsSameType(types.Unit) {
dst = &Var{Value: temp()}
}
args := []Operand{}
instructions := []Instruction{}
for _, arg := range expr.Arguments {
dst, argInstructions := emitExpression(arg)
instructions = append(instructions, argInstructions...)
args = append(args, dst)
}
instructions = append(instructions, &Call{FunctionName: expr.Identifier, Arguments: args, ReturnValue: dst})
return dst, instructions
default:
panic(fmt.Sprintf("unexpected tast.Expression: %#v", expr))
}

View File

@ -116,6 +116,36 @@ func (l Label) String() string {
}
func (l Label) instruction() {}
type Call struct {
FunctionName string
Arguments []Operand
// NOTE: Nullable
ReturnValue Operand
}
func (c *Call) String() string {
b := strings.Builder{}
if c.ReturnValue != nil {
b.WriteString(c.ReturnValue.String() + " = ")
}
b.WriteString("call " + c.FunctionName + " ")
for i, arg := range c.Arguments {
b.WriteString(arg.String())
if i < (len(c.Arguments) - 1) {
b.WriteString(", ")
}
}
b.WriteRune('\n')
return b.String()
}
func (c *Call) instruction() {}
type Operand interface {
String() string
operand()