diff --git a/examples/hello/hello.q b/examples/hello/hello.q index 95cb219..53d906b 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -1,5 +1,11 @@ main() { - exit(f(1) + f(2) + f(3)) + x := f(1) + f(2) + f(3) + + if x != 9 { + exit(42) + } + + exit(0) } exit(code) { diff --git a/src/build/arch/x64/Compare.go b/src/build/arch/x64/Compare.go new file mode 100644 index 0000000..5e49f7b --- /dev/null +++ b/src/build/arch/x64/Compare.go @@ -0,0 +1,13 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// Compares the register with the number and sets the status flags in the EFLAGS register. +func CompareRegisterNumber(code []byte, register cpu.Register, number int) []byte { + return regRegNum(code, 0b111, byte(register), number, 0x83, 0x81) +} + +// CompareRegisterRegister compares a register with a register and sets the status flags in the EFLAGS register. +func CompareRegisterRegister(code []byte, registerA cpu.Register, registerB cpu.Register) []byte { + return regReg(code, byte(registerB), byte(registerA), 0x39) +} diff --git a/src/build/arch/x64/Compare_test.go b/src/build/arch/x64/Compare_test.go new file mode 100644 index 0000000..cef49b3 --- /dev/null +++ b/src/build/arch/x64/Compare_test.go @@ -0,0 +1,88 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/go/assert" +) + +func TestCompareRegisterNumber(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Number int + Code []byte + }{ + {x64.RAX, 1, []byte{0x48, 0x83, 0xF8, 0x01}}, + {x64.RCX, 1, []byte{0x48, 0x83, 0xF9, 0x01}}, + {x64.RDX, 1, []byte{0x48, 0x83, 0xFA, 0x01}}, + {x64.RBX, 1, []byte{0x48, 0x83, 0xFB, 0x01}}, + {x64.RSP, 1, []byte{0x48, 0x83, 0xFC, 0x01}}, + {x64.RBP, 1, []byte{0x48, 0x83, 0xFD, 0x01}}, + {x64.RSI, 1, []byte{0x48, 0x83, 0xFE, 0x01}}, + {x64.RDI, 1, []byte{0x48, 0x83, 0xFF, 0x01}}, + {x64.R8, 1, []byte{0x49, 0x83, 0xF8, 0x01}}, + {x64.R9, 1, []byte{0x49, 0x83, 0xF9, 0x01}}, + {x64.R10, 1, []byte{0x49, 0x83, 0xFA, 0x01}}, + {x64.R11, 1, []byte{0x49, 0x83, 0xFB, 0x01}}, + {x64.R12, 1, []byte{0x49, 0x83, 0xFC, 0x01}}, + {x64.R13, 1, []byte{0x49, 0x83, 0xFD, 0x01}}, + {x64.R14, 1, []byte{0x49, 0x83, 0xFE, 0x01}}, + {x64.R15, 1, []byte{0x49, 0x83, 0xFF, 0x01}}, + + {x64.RAX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RCX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBX, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RBP, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RSI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.RDI, 0x7FFFFFFF, []byte{0x48, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R8, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF8, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R9, 0x7FFFFFFF, []byte{0x49, 0x81, 0xF9, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R10, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFA, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R11, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFB, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R12, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFC, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R13, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFD, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R14, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFE, 0xFF, 0xFF, 0xFF, 0x7F}}, + {x64.R15, 0x7FFFFFFF, []byte{0x49, 0x81, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F}}, + } + + for _, pattern := range usagePatterns { + t.Logf("cmp %s, %x", pattern.Register, pattern.Number) + code := x64.CompareRegisterNumber(nil, pattern.Register, pattern.Number) + assert.DeepEqual(t, code, pattern.Code) + } +} + +func TestCompareRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x4C, 0x39, 0xF8}}, + {x64.RCX, x64.R14, []byte{0x4C, 0x39, 0xF1}}, + {x64.RDX, x64.R13, []byte{0x4C, 0x39, 0xEA}}, + {x64.RBX, x64.R12, []byte{0x4C, 0x39, 0xE3}}, + {x64.RSP, x64.R11, []byte{0x4C, 0x39, 0xDC}}, + {x64.RBP, x64.R10, []byte{0x4C, 0x39, 0xD5}}, + {x64.RSI, x64.R9, []byte{0x4C, 0x39, 0xCE}}, + {x64.RDI, x64.R8, []byte{0x4C, 0x39, 0xC7}}, + {x64.R8, x64.RDI, []byte{0x49, 0x39, 0xF8}}, + {x64.R9, x64.RSI, []byte{0x49, 0x39, 0xF1}}, + {x64.R10, x64.RBP, []byte{0x49, 0x39, 0xEA}}, + {x64.R11, x64.RSP, []byte{0x49, 0x39, 0xE3}}, + {x64.R12, x64.RBX, []byte{0x49, 0x39, 0xDC}}, + {x64.R13, x64.RDX, []byte{0x49, 0x39, 0xD5}}, + {x64.R14, x64.RCX, []byte{0x49, 0x39, 0xCE}}, + {x64.R15, x64.RAX, []byte{0x49, 0x39, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("cmp %s, %s", pattern.Left, pattern.Right) + code := x64.CompareRegisterRegister(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Jump.go b/src/build/arch/x64/Jump.go index 5b3f125..86d2c48 100644 --- a/src/build/arch/x64/Jump.go +++ b/src/build/arch/x64/Jump.go @@ -3,9 +3,43 @@ package x64 // Jump continues program flow at the new address. // The address is relative to the next instruction. func Jump8(code []byte, address int8) []byte { + return jump8(code, 0xEB, address) +} + +// JumpIfLess jumps if the result was less. +func Jump8IfLess(code []byte, address int8) []byte { + return jump8(code, 0x7C, address) +} + +// JumpIfLessOrEqual jumps if the result was less or equal. +func Jump8IfLessOrEqual(code []byte, address int8) []byte { + return jump8(code, 0x7E, address) +} + +// JumpIfGreater jumps if the result was greater. +func Jump8IfGreater(code []byte, address int8) []byte { + return jump8(code, 0x7F, address) +} + +// JumpIfGreaterOrEqual jumps if the result was greater or equal. +func Jump8IfGreaterOrEqual(code []byte, address int8) []byte { + return jump8(code, 0x7D, address) +} + +// JumpIfEqual jumps if the result was equal. +func Jump8IfEqual(code []byte, address int8) []byte { + return jump8(code, 0x74, address) +} + +// JumpIfNotEqual jumps if the result was not equal. +func Jump8IfNotEqual(code []byte, address int8) []byte { + return jump8(code, 0x75, address) +} + +func jump8(code []byte, opCode byte, address int8) []byte { return append( code, - 0xEB, + opCode, byte(address), ) } diff --git a/src/build/arch/x64/Registers.go b/src/build/arch/x64/Registers.go index fafea37..6968338 100644 --- a/src/build/arch/x64/Registers.go +++ b/src/build/arch/x64/Registers.go @@ -22,8 +22,8 @@ const ( ) var ( - CallRegisters = SyscallRegisters - GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} SyscallRegisters = []cpu.Register{RAX, RDI, RSI, RDX, R10, R8, R9} - ReturnValueRegisters = []cpu.Register{RAX, RCX, R11} + GeneralRegisters = []cpu.Register{RCX, RBX, RBP, R11, R12, R13, R14, R15} + CallRegisters = SyscallRegisters + ReturnValueRegisters = SyscallRegisters ) diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 9a9f19e..caf600a 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -66,8 +66,32 @@ func (a Assembler) Finalize() ([]byte, []byte) { case COMMENT: continue - case JUMP: - code = x64.Jump8(code, 0x00) + case COMPARE: + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.CompareRegisterNumber(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.CompareRegisterRegister(code, operands.Destination, operands.Source) + } + + case JUMP, JE, JNE, JG, JL, JGE, JLE: + switch x.Mnemonic { + case JUMP: + code = x64.Jump8(code, 0x00) + case JE: + code = x64.Jump8IfEqual(code, 0x00) + case JNE: + code = x64.Jump8IfNotEqual(code, 0x00) + case JG: + code = x64.Jump8IfGreater(code, 0x00) + case JL: + code = x64.Jump8IfLess(code, 0x00) + case JGE: + code = x64.Jump8IfGreaterOrEqual(code, 0x00) + case JLE: + code = x64.Jump8IfLessOrEqual(code, 0x00) + } + size := 1 label := x.Data.(*Label) nextInstructionAddress := Address(len(code)) diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 6b547a2..1adcd9d 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -34,10 +34,10 @@ func (a *Assembler) Register(mnemonic Mnemonic, register cpu.Register) { }) } -// Label adds a label at the current position. -func (a *Assembler) Label(name string) { +// Label adds an instruction using a label. +func (a *Assembler) Label(mnemonic Mnemonic, name string) { a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: LABEL, + Mnemonic: mnemonic, Data: &Label{ Name: name, }, diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 315af19..06bd87a 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -7,7 +7,14 @@ const ( ADD CALL COMMENT + COMPARE DIV + JE + JNE + JG + JGE + JL + JLE JUMP MUL LABEL @@ -28,10 +35,24 @@ func (m Mnemonic) String() string { return "call" case COMMENT: return "comment" + case COMPARE: + return "compare" case DIV: return "div" case JUMP: return "jump" + case JE: + return "jump ==" + case JNE: + return "jump !=" + case JL: + return "jump <" + case JG: + return "jump >" + case JLE: + return "jump <=" + case JGE: + return "jump >=" case LABEL: return "label" case MOVE: diff --git a/src/build/ast/AST.go b/src/build/ast/AST.go index d1b991b..7def683 100644 --- a/src/build/ast/AST.go +++ b/src/build/ast/AST.go @@ -37,6 +37,15 @@ func (node *Define) String() string { return fmt.Sprintf("(= %s %s)", node.Name.Text(), node.Value) } +type If struct { + Condition *expression.Expression + Body AST +} + +func (node *If) String() string { + return fmt.Sprintf("(if %s %s)", node.Condition, node.Body) +} + type Loop struct { Body AST } diff --git a/src/build/ast/Parse.go b/src/build/ast/Parse.go index 760302e..4f00224 100644 --- a/src/build/ast/Parse.go +++ b/src/build/ast/Parse.go @@ -47,6 +47,22 @@ func toASTNode(tokens token.List) (Node, error) { tree, err := Parse(tokens[blockStart:blockEnd]) return &Loop{Body: tree}, err + case keyword.If: + blockStart := tokens.IndexKind(token.BlockStart) + 1 + blockEnd := tokens.LastIndexKind(token.BlockEnd) + + if blockStart == -1 { + return nil, errors.New(errors.MissingBlockStart, nil, tokens[0].End()) + } + + if blockEnd == -1 { + return nil, errors.New(errors.MissingBlockEnd, nil, tokens[len(tokens)-1].End()) + } + + condition := expression.Parse(tokens[1:token.BlockStart]) + tree, err := Parse(tokens[blockStart:blockEnd]) + return &If{Condition: condition, Body: tree}, err + default: return nil, errors.New(&errors.KeywordNotImplemented{Keyword: tokens[0].Text()}, nil, tokens[0].Position) } diff --git a/src/build/core/CompileIf.go b/src/build/core/CompileIf.go new file mode 100644 index 0000000..2f058e3 --- /dev/null +++ b/src/build/core/CompileIf.go @@ -0,0 +1,48 @@ +package core + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/ast" +) + +// CompileIf compiles a branch instruction. +func (f *Function) CompileIf(branch *ast.If) error { + condition := branch.Condition + tmpRight := f.cpu.Input[1] + err := f.ExpressionToRegister(condition.Children[1], tmpRight) + + if err != nil { + return err + } + + tmpLeft := f.cpu.Input[0] + err = f.ExpressionToRegister(condition.Children[0], tmpLeft) + + if err != nil { + return err + } + + f.assembler.RegisterRegister(asm.COMPARE, tmpLeft, tmpRight) + elseLabel := fmt.Sprintf("%s_if_%d_else", f.Name, f.count.branch) + + switch condition.Token.Text() { + case "==": + f.assembler.Label(asm.JNE, elseLabel) + case "!=": + f.assembler.Label(asm.JE, elseLabel) + case ">": + f.assembler.Label(asm.JLE, elseLabel) + case "<": + f.assembler.Label(asm.JGE, elseLabel) + case ">=": + f.assembler.Label(asm.JL, elseLabel) + case "<=": + f.assembler.Label(asm.JG, elseLabel) + } + + defer f.assembler.Label(asm.LABEL, elseLabel) + f.count.branch++ + return f.CompileAST(branch.Body) +} diff --git a/src/build/core/CompileLoop.go b/src/build/core/CompileLoop.go index 37a3c94..b8fd1d8 100644 --- a/src/build/core/CompileLoop.go +++ b/src/build/core/CompileLoop.go @@ -3,13 +3,14 @@ package core import ( "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" ) // CompileLoop compiles a loop instruction. func (f *Function) CompileLoop(loop *ast.Loop) error { label := fmt.Sprintf("%s_loop_%d", f.Name, f.count.loop) - f.assembler.Label(label) + f.assembler.Label(asm.LABEL, label) defer f.assembler.Jump(label) f.count.loop++ return f.CompileAST(loop.Body) diff --git a/src/build/core/Function.go b/src/build/core/Function.go index be45db3..e19cf0f 100644 --- a/src/build/core/Function.go +++ b/src/build/core/Function.go @@ -45,7 +45,7 @@ func NewFunction(name string, file *fs.File, body token.List) *Function { // Compile turns a function into machine code. func (f *Function) Compile() { defer close(f.finished) - f.assembler.Label(f.Name) + f.assembler.Label(asm.LABEL, f.Name) f.err = f.CompileTokens(f.Body) f.assembler.Return() } @@ -90,6 +90,9 @@ func (f *Function) CompileASTNode(node ast.Node) error { case *ast.Return: return f.CompileReturn(node) + case *ast.If: + return f.CompileIf(node) + case *ast.Loop: return f.CompileLoop(node) diff --git a/src/build/core/state.go b/src/build/core/state.go index 31313d0..8ae6838 100644 --- a/src/build/core/state.go +++ b/src/build/core/state.go @@ -21,7 +21,8 @@ type state struct { // counter stores how often a certain statement appeared so we can generate a unique label from it. type counter struct { - loop int + loop int + branch int } // PrintInstructions shows the assembly instructions. diff --git a/src/build/keyword/Keyword.go b/src/build/keyword/Keyword.go index d3d9b18..d744384 100644 --- a/src/build/keyword/Keyword.go +++ b/src/build/keyword/Keyword.go @@ -1,12 +1,14 @@ package keyword const ( + If = "if" Loop = "loop" Return = "return" ) // Map is a map of all keywords used in the language. var Map = map[string][]byte{ + If: []byte(If), Loop: []byte(Loop), Return: []byte(Return), } diff --git a/tests/examples_test.go b/tests/examples_test.go index ab00191..be7590d 100644 --- a/tests/examples_test.go +++ b/tests/examples_test.go @@ -10,7 +10,7 @@ var examples = []struct { ExpectedOutput string ExpectedExitCode int }{ - {"hello", "", 9}, + {"hello", "", 0}, {"write", "ELF", 0}, }