diff --git a/asm/amd64/amd64.go b/asm/amd64/amd64.go index 89853dc..3026291 100644 --- a/asm/amd64/amd64.go +++ b/asm/amd64/amd64.go @@ -49,15 +49,24 @@ func (f *Function) Emit() string { return builder.String() } +type CondCode string + +const ( + Equal CondCode = "e" + NotEqual CondCode = "ne" +) + 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" + Mov Opcode = "mov" // Lhs: dst, Rhs: src, or better said intel syntax + Add Opcode = "add" + Sub Opcode = "sub" + Imul Opcode = "imul" + Cmp Opcode = "cmp" + SetCC Opcode = "setcc" // One operand Idiv Opcode = "idiv" @@ -67,7 +76,11 @@ const ( Cdq Opcode = "cdq" ) -type Instruction struct { +type Instruction interface { + InstructionString() string +} + +type SimpleInstruction struct { Opcode Opcode // Dst Lhs Operand @@ -75,8 +88,7 @@ type Instruction struct { Rhs Operand } -func (i *Instruction) InstructionString() string { - +func (i *SimpleInstruction) InstructionString() string { if i.Opcode == Ret { return fmt.Sprintf("mov rsp, rbp\n pop rbp\n ret\n") } @@ -95,6 +107,15 @@ func (i *Instruction) InstructionString() string { return fmt.Sprintf("%s %s, %s", i.Opcode, i.Lhs.OperandString(Eight), i.Rhs.OperandString(Eight)) } +type SetCCInstruction struct { + Cond CondCode + Dst Operand +} + +func (si *SetCCInstruction) InstructionString() string { + return fmt.Sprintf("set%s %s", si.Cond, si.Dst.OperandString(Eight)) +} + type OperandSize int type Operand interface { diff --git a/asm/amd64/amd64_test.go b/asm/amd64/amd64_test.go index 4f1cc9c..f556ded 100644 --- a/asm/amd64/amd64_test.go +++ b/asm/amd64/amd64_test.go @@ -1,6 +1,7 @@ package amd64 import ( + _ "embed" "strings" "testing" @@ -21,6 +22,9 @@ func TestOperands(t *testing.T) { } } +//go:embed basic_test.txt +var basicTest string + func TestCodegen(t *testing.T) { program := &ttir.Program{ Functions: []ttir.Function{ @@ -55,8 +59,7 @@ func TestCodegen(t *testing.T) { expectProgram(t, expectedProgram, actualProgram) actual := actualProgram.Emit() - - expected := executableAsmHeader + "main:\n mov rax, 0\n ret\n" + expected := basicTest if strings.Trim(actual, " \n\t") != strings.Trim(expected, " \n\t") { t.Errorf("Expected program to be:\n>>%s<<\nbut got:\n>>%s<<\n", expected, actual) } @@ -130,6 +133,26 @@ func expectOperand(t *testing.T, expected Operand, actual Operand) { if expected != actual { t.Errorf("Expected Immediate %q but got %q", expected, actual) } + case Stack: + actual, ok := actual.(Stack) + + if !ok { + t.Errorf("Expected Stack but got %T", actual) + } + + if expected != actual { + t.Errorf("Expected Stack value %q but got %q", expected, actual) + } + case Pseudo: + actual, ok := actual.(Pseudo) + + if !ok { + t.Errorf("Expected Stack but got %T", actual) + } + + if expected != actual { + t.Errorf("Expected Stack value %q but got %q", expected, actual) + } default: t.Errorf("Unknown operand type %T", expected) } diff --git a/asm/amd64/basic_test.txt b/asm/amd64/basic_test.txt new file mode 100644 index 0000000..573ff7e --- /dev/null +++ b/asm/amd64/basic_test.txt @@ -0,0 +1,17 @@ +format ELF64 executable +segment readable executable +entry _start +_start: + call main + mov rdi, rax + mov rax, 60 + syscall +main: + push rbp + mov rbp, rsp + add rsp, 0 + mov rax, 0 + mov rsp, rbp + pop rbp + ret + diff --git a/asm/amd64/codegen.go b/asm/amd64/codegen.go index bde3a6f..a9f65de 100644 --- a/asm/amd64/codegen.go +++ b/asm/amd64/codegen.go @@ -52,12 +52,12 @@ func cgInstruction(i ttir.Instruction) []Instruction { switch i := i.(type) { case *ttir.Ret: return []Instruction{ - { + &SimpleInstruction{ Opcode: Mov, Lhs: AX, Rhs: toAsmOperand(i.Op), }, - { + &SimpleInstruction{ Opcode: Ret, }, } @@ -70,6 +70,32 @@ func cgInstruction(i ttir.Instruction) []Instruction { func cgBinary(b *ttir.Binary) []Instruction { switch b.Operator { + case ast.Equal, ast.NotEqual: + var condCode CondCode + + switch b.Operator { + case ast.Equal: + condCode = Equal + case ast.NotEqual: + condCode = NotEqual + } + + return []Instruction{ + &SimpleInstruction{ + Opcode: Cmp, + Lhs: toAsmOperand(b.Lhs), + Rhs: toAsmOperand(b.Rhs), + }, + &SimpleInstruction{ + Opcode: Mov, + Lhs: toAsmOperand(b.Dst), + Rhs: Imm(0), + }, + &SetCCInstruction{ + Cond: condCode, + Dst: toAsmOperand(b.Dst), + }, + } case ast.Add, ast.Subtract, ast.Multiply: var opcode Opcode switch b.Operator { @@ -82,15 +108,15 @@ func cgBinary(b *ttir.Binary) []Instruction { } return []Instruction{ - {Opcode: Mov, Lhs: toAsmOperand(b.Dst), Rhs: toAsmOperand(b.Lhs)}, - {Opcode: opcode, Lhs: toAsmOperand(b.Dst), Rhs: toAsmOperand(b.Rhs)}, + &SimpleInstruction{Opcode: Mov, Lhs: toAsmOperand(b.Dst), Rhs: toAsmOperand(b.Lhs)}, + &SimpleInstruction{Opcode: opcode, Lhs: toAsmOperand(b.Dst), Rhs: toAsmOperand(b.Rhs)}, } case ast.Divide: return []Instruction{ - {Opcode: Mov, Lhs: Register(AX), Rhs: toAsmOperand(b.Lhs)}, - {Opcode: Cdq}, - {Opcode: Idiv, Lhs: toAsmOperand(b.Rhs)}, - {Opcode: Mov, Lhs: toAsmOperand(b.Dst), Rhs: Register(AX)}, + &SimpleInstruction{Opcode: Mov, Lhs: Register(AX), Rhs: toAsmOperand(b.Lhs)}, + &SimpleInstruction{Opcode: Cdq}, + &SimpleInstruction{Opcode: Idiv, Lhs: toAsmOperand(b.Rhs)}, + &SimpleInstruction{Opcode: Mov, Lhs: toAsmOperand(b.Dst), Rhs: Register(AX)}, } } @@ -128,15 +154,26 @@ func rpFunction(f Function) Function { func rpInstruction(i Instruction, r *replacePseudoPass) Instruction { - newInstruction := Instruction{Opcode: i.Opcode} - if i.Lhs != nil { - newInstruction.Lhs = pseudoToStack(i.Lhs, r) - } - if i.Rhs != nil { - newInstruction.Rhs = pseudoToStack(i.Rhs, r) + switch i := i.(type) { + case *SimpleInstruction: + + newInstruction := &SimpleInstruction{Opcode: i.Opcode} + if i.Lhs != nil { + newInstruction.Lhs = pseudoToStack(i.Lhs, r) + } + if i.Rhs != nil { + newInstruction.Rhs = pseudoToStack(i.Rhs, r) + } + + return newInstruction + case *SetCCInstruction: + return &SetCCInstruction{ + Cond: i.Cond, + Dst: pseudoToStack(i.Dst, r), + } } - return newInstruction + panic("invalid instruction") } func pseudoToStack(op Operand, r *replacePseudoPass) Operand { @@ -177,40 +214,70 @@ func fixupFunction(f Function) Function { func fixupInstruction(i Instruction) []Instruction { - switch i.Opcode { - case Mov: - if lhs, ok := i.Lhs.(Stack); ok { - if rhs, ok := i.Rhs.(Stack); ok { + switch i := i.(type) { + case *SimpleInstruction: + switch i.Opcode { + case Mov: + if lhs, ok := i.Lhs.(Stack); ok { + if rhs, ok := i.Rhs.(Stack); ok { + return []Instruction{ + &SimpleInstruction{Opcode: Mov, Lhs: Register(R10), Rhs: rhs}, + &SimpleInstruction{Opcode: Mov, Lhs: lhs, Rhs: Register(R10)}, + } + } + } + case Imul: + if lhs, ok := i.Lhs.(Stack); ok { return []Instruction{ - {Opcode: Mov, Lhs: Register(R10), Rhs: rhs}, - {Opcode: Mov, Lhs: lhs, Rhs: Register(R10)}, + &SimpleInstruction{Opcode: Mov, Lhs: Register(R11), Rhs: lhs}, + &SimpleInstruction{Opcode: Imul, Lhs: Register(R11), Rhs: i.Rhs}, + &SimpleInstruction{Opcode: Mov, Lhs: lhs, Rhs: Register(R11)}, + } + } + fallthrough + case Add, Sub, Idiv /* Imul (fallthrough) */ : + if lhs, ok := i.Lhs.(Stack); ok { + if rhs, ok := i.Rhs.(Stack); ok { + return []Instruction{ + &SimpleInstruction{Opcode: Mov, Lhs: Register(R10), Rhs: rhs}, + &SimpleInstruction{Opcode: i.Opcode, Lhs: lhs, Rhs: Register(R10)}, + } + } + } else if lhs, ok := i.Lhs.(Imm); ok && i.Opcode == Idiv { + return []Instruction{ + &SimpleInstruction{Opcode: Mov, Lhs: Register(R10), Rhs: lhs}, + &SimpleInstruction{Opcode: Idiv, Lhs: Register(R10)}, + } + } + case Cmp: + if lhs, ok := i.Lhs.(Stack); ok { + if rhs, ok := i.Rhs.(Stack); ok { + return []Instruction{ + &SimpleInstruction{Opcode: Mov, Lhs: Register(R10), Rhs: rhs}, + &SimpleInstruction{Opcode: i.Opcode, Lhs: lhs, Rhs: Register(R10)}, + } + } + } else if rhs, ok := i.Rhs.(Imm); ok { + return []Instruction{ + &SimpleInstruction{ + Opcode: Mov, + Lhs: Register(R11), + Rhs: Imm(rhs), + }, + &SimpleInstruction{ + Opcode: Cmp, + Lhs: i.Lhs, + Rhs: Register(R11), + }, } } } - case Imul: - if lhs, ok := i.Lhs.(Stack); ok { - return []Instruction{ - {Opcode: Mov, Lhs: Register(R11), Rhs: lhs}, - {Opcode: Imul, Lhs: Register(R11), Rhs: i.Rhs}, - {Opcode: Mov, Lhs: lhs, Rhs: Register(R11)}, - } - } - fallthrough - case Add, Sub, Idiv /* Imul (fallthrough) */ : - if lhs, ok := i.Lhs.(Stack); ok { - if rhs, ok := i.Rhs.(Stack); ok { - return []Instruction{ - {Opcode: Mov, Lhs: Register(R10), Rhs: rhs}, - {Opcode: i.Opcode, Lhs: lhs, Rhs: Register(R10)}, - } - } - } else if lhs, ok := i.Lhs.(Imm); ok && i.Opcode == Idiv { - return []Instruction{ - {Opcode: Mov, Lhs: Register(R10), Rhs: lhs}, - {Opcode: Idiv, Lhs: Register(R10)}, - } - } + + return []Instruction{i} + case *SetCCInstruction: + + return []Instruction{i} } - return []Instruction{i} + panic("invalid instruction") } diff --git a/ast/ast.go b/ast/ast.go index 35a328e..6a287ab 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -82,6 +82,8 @@ const ( Subtract Multiply Divide + Equal + NotEqual ) func (bo BinaryOperator) SymbolString() string { @@ -94,6 +96,10 @@ func (bo BinaryOperator) SymbolString() string { return "*" case Divide: return "/" + case Equal: + return "==" + case NotEqual: + return "!=" } return "" } diff --git a/language.md b/language.md new file mode 100644 index 0000000..980ad74 --- /dev/null +++ b/language.md @@ -0,0 +1,12 @@ +# tt + +## Syntax + +```tt + +// Return type is i64 +fn main() = { + let i = 34; + i +}; +``` diff --git a/lexer/lexer.go b/lexer/lexer.go index 649d1e8..69acd52 100644 --- a/lexer/lexer.go +++ b/lexer/lexer.go @@ -66,6 +66,14 @@ func (l *Lexer) NextToken() token.Token { case ';': tok = l.newToken(token.Semicolon) case '=': + if l.peekByte() == '=' { + pos := l.position + l.readChar() + l.readChar() + tok.Type = token.DoubleEqual + tok.Literal = l.input[pos:l.position] + return tok + } tok = l.newToken(token.Equal) case '(': tok = l.newToken(token.OpenParen) @@ -79,6 +87,16 @@ func (l *Lexer) NextToken() token.Token { tok = l.newToken(token.Asterisk) case '/': tok = l.newToken(token.Slash) + case '!': + if l.peekByte() == '=' { + pos := l.position + l.readChar() + l.readChar() + tok.Type = token.NotEqual + tok.Literal = l.input[pos:l.position] + return tok + } + tok = l.newToken(token.Illegal) case -1: tok.Literal = "" tok.Type = token.Eof @@ -140,6 +158,14 @@ func (l *Lexer) readChar() (err error) { return } +func (l *Lexer) peekByte() byte { + if l.readPosition < len(l.input) { + return l.input[l.readPosition] + } else { + return 0 + } +} + func (l *Lexer) readIdentifier() string { startPos := l.position diff --git a/lexer/lexer_test.go b/lexer/lexer_test.go index 3f5453c..fa444ed 100644 --- a/lexer/lexer_test.go +++ b/lexer/lexer_test.go @@ -40,7 +40,7 @@ func runLexerTest(t *testing.T, test lexerTest) { func TestBasicFunctionality(t *testing.T) { runLexerTest(t, lexerTest{ - input: "fn main() = 0;", + input: "fn main() = 0 + 3;", expectedToken: []token.Token{ {Type: token.Fn, Literal: "fn"}, {Type: token.Ident, Literal: "main"}, @@ -48,6 +48,8 @@ func TestBasicFunctionality(t *testing.T) { {Type: token.CloseParen, Literal: ")"}, {Type: token.Equal, Literal: "="}, {Type: token.Int, Literal: "0"}, + {Type: token.Plus, Literal: "+"}, + {Type: token.Int, Literal: "3"}, {Type: token.Semicolon, Literal: ";"}, {Type: token.Eof, Literal: ""}, }, diff --git a/parser/parser.go b/parser/parser.go index a9c5be2..5f72c0e 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -51,6 +51,8 @@ func New(l *lexer.Lexer) *Parser { p.registerInfixFn(token.Minus, p.parseBinaryExpression) p.registerInfixFn(token.Asterisk, p.parseBinaryExpression) p.registerInfixFn(token.Slash, p.parseBinaryExpression) + p.registerInfixFn(token.Equal, p.parseBinaryExpression) + p.registerInfixFn(token.NotEqual, p.parseBinaryExpression) p.nextToken() p.nextToken() @@ -239,6 +241,10 @@ func (p *Parser) parseBinaryExpression(lhs ast.Expression) ast.Expression { op = ast.Multiply case token.Slash: op = ast.Divide + case token.DoubleEqual: + op = ast.Equal + case token.NotEqual: + op = ast.NotEqual default: return p.exprError(p.curToken, "invalid token for binary expression %s", p.curToken.Type) } diff --git a/token/token.go b/token/token.go index bc1c128..85d31b7 100644 --- a/token/token.go +++ b/token/token.go @@ -30,10 +30,14 @@ const ( Equal TokenType = "=" OpenParen TokenType = "(" CloseParen TokenType = ")" - Plus TokenType = "+" - Minus TokenType = "-" - Asterisk TokenType = "*" - Slash TokenType = "/" + + // Binary Operators + Plus TokenType = "+" + Minus TokenType = "-" + Asterisk TokenType = "*" + Slash TokenType = "/" + DoubleEqual TokenType = "==" + NotEqual TokenType = "!=" // Keywords Fn TokenType = "FN" diff --git a/ttir/emit.go b/ttir/emit.go index 0c65b40..c685f68 100644 --- a/ttir/emit.go +++ b/ttir/emit.go @@ -3,6 +3,7 @@ package ttir import ( "fmt" + "robaertschi.xyz/robaertschi/tt/ast" "robaertschi.xyz/robaertschi/tt/tast" ) @@ -41,13 +42,15 @@ func emitExpression(expr tast.Expression) (Operand, []Instruction) { case *tast.IntegerExpression: return &Constant{Value: expr.Value}, []Instruction{} case *tast.BinaryExpression: - lhsDst, instructions := emitExpression(expr.Lhs) - rhsDst, rhsInstructions := emitExpression(expr.Rhs) - instructions = append(instructions, rhsInstructions...) - dst := &Var{Value: temp()} - instructions = append(instructions, &Binary{Operator: expr.Operator, Lhs: lhsDst, Rhs: rhsDst, Dst: dst}) - return dst, instructions - + switch expr.Operator { + default: + lhsDst, instructions := emitExpression(expr.Lhs) + rhsDst, rhsInstructions := emitExpression(expr.Rhs) + instructions = append(instructions, rhsInstructions...) + dst := &Var{Value: temp()} + instructions = append(instructions, &Binary{Operator: expr.Operator, Lhs: lhsDst, Rhs: rhsDst, Dst: dst}) + return dst, instructions + } } panic("unhandled tast.Expression case in ir emitter") } diff --git a/ttir/ttir.go b/ttir/ttir.go index 61caf9a..360baa7 100644 --- a/ttir/ttir.go +++ b/ttir/ttir.go @@ -60,6 +60,40 @@ func (b *Binary) String() string { } func (b *Binary) instruction() {} +type JumpIfZero struct { + Value Operand + Label string +} + +func (jiz *JumpIfZero) String() string { + return fmt.Sprintf("jz %v, %v\n", jiz.Value, jiz.Label) +} +func (jiz *JumpIfZero) instruction() {} + +type JumpIfNotZero struct { + Value Operand + Label string +} + +func (jiz *JumpIfNotZero) String() string { + return fmt.Sprintf("jnz %v, %v\n", jiz.Value, jiz.Label) +} +func (jiz *JumpIfNotZero) instruction() {} + +type Jump string + +func (j Jump) String() string { + return fmt.Sprintf("jmp %v\n", string(j)) +} +func (j Jump) instruction() {} + +type Label string + +func (l Label) String() string { + return fmt.Sprintf("%v:\n", string(l)) +} +func (l Label) instruction() {} + type Operand interface { String() string operand()