From e6462266ef9e75bb8ad14ab94c73cadea0dd84cb Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Tue, 25 Jun 2024 20:50:06 +0200 Subject: [PATCH] Implemented simple expressions --- examples/hello/hello.q | 1 + src/build/Assignment.go | 5 +- src/build/Function.go | 79 ++++++++++++++++++++++++------ src/build/arch/x64/Add.go | 34 ++++++++++++- src/build/arch/x64/Add_test.go | 31 ++++++++++++ src/build/arch/x64/Mul.go | 7 ++- src/build/arch/x64/Mul_test.go | 31 ++++++++++++ src/build/arch/x64/Sub.go | 7 ++- src/build/arch/x64/numRegReg.go | 34 ++----------- src/build/asm/Assembler.go | 16 ++++++ src/build/asm/Instructions.go | 11 +++++ src/build/expression/Expression.go | 28 +++++++++-- 12 files changed, 232 insertions(+), 52 deletions(-) diff --git a/examples/hello/hello.q b/examples/hello/hello.q index a49c249..7eddb30 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -16,6 +16,7 @@ hello() { length -= 20 length *= 10 length /= 100 + length = (0 + 50 - 20) * 10 / 100 loop { syscall(write, stdout, address, length) diff --git a/src/build/Assignment.go b/src/build/Assignment.go index a5a91bb..96ebd79 100644 --- a/src/build/Assignment.go +++ b/src/build/Assignment.go @@ -11,7 +11,6 @@ import ( // CompileAssignment compiles an assignment. func (f *Function) CompileAssignment(expr *expression.Expression) error { name := expr.Children[0].Token.Text() - number, _ := strconv.Atoi(expr.Children[1].Token.Text()) register := f.Variables[name].Register switch expr.Token.Text() { @@ -19,15 +18,19 @@ func (f *Function) CompileAssignment(expr *expression.Expression) error { f.ExpressionToRegister(expr.Children[1], register) case "+=": + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) f.Assembler.RegisterNumber(asm.ADD, register, number) case "-=": + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) f.Assembler.RegisterNumber(asm.SUB, register, number) case "*=": + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) f.Assembler.RegisterNumber(asm.MUL, register, number) case "/=": + number, _ := strconv.Atoi(expr.Children[1].Token.Text()) f.Assembler.RegisterNumber(asm.DIV, register, number) default: diff --git a/src/build/Function.go b/src/build/Function.go index 5d7666d..f99bc2d 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -120,27 +120,61 @@ func (f *Function) CompileInstruction(line token.List) error { return errors.New(&errors.InvalidInstruction{Instruction: expr.Token.Text()}, f.File, expr.Token.Position) } -// isAssignment returns true if the expression is an assignment. -func isAssignment(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' -} - -// isFunctionCall returns true if the expression is a function call. -func isFunctionCall(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" -} - -// isVariableDefinition returns true if the expression is a variable definition. -func isVariableDefinition(expr *expression.Expression) bool { - return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" -} - // ExpressionToRegister moves the result of an expression into the given register. func (f *Function) ExpressionToRegister(root *expression.Expression, register cpu.Register) error { if root.IsLeaf() { return f.TokenToRegister(root.Token, register) } + left := root.Children[0] + right := root.Children[1] + + f.ExpressionToRegister(left, register) + + if right.IsLeaf() { + value := right.Token.Text() + n, err := strconv.Atoi(value) + + if err != nil { + return err + } + + switch root.Token.Text() { + case "+": + f.Assembler.RegisterNumber(asm.ADD, register, n) + + case "-": + f.Assembler.RegisterNumber(asm.SUB, register, n) + + case "*": + f.Assembler.RegisterNumber(asm.MUL, register, n) + + case "/": + f.Assembler.RegisterNumber(asm.DIV, register, n) + } + + return nil + } else { + temporary, _ := f.CPU.FindFree() + f.CPU.Use(temporary) + f.ExpressionToRegister(right, temporary) + f.CPU.Free(temporary) + + switch root.Token.Text() { + case "+": + f.Assembler.RegisterRegister(asm.ADD, register, temporary) + + case "-": + f.Assembler.RegisterRegister(asm.SUB, register, temporary) + + case "*": + f.Assembler.RegisterRegister(asm.MUL, register, temporary) + + case "/": + f.Assembler.RegisterRegister(asm.DIV, register, temporary) + } + } + return errors.New(errors.NotImplemented, f.File, root.Token.Position) } @@ -207,3 +241,18 @@ func (f *Function) identifierExists(name string) bool { _, exists := f.Variables[name] return exists } + +// isAssignment returns true if the expression is an assignment. +func isAssignment(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Bytes[len(expr.Token.Bytes)-1] == '=' +} + +// isFunctionCall returns true if the expression is a function call. +func isFunctionCall(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == "λ" +} + +// isVariableDefinition returns true if the expression is a variable definition. +func isVariableDefinition(expr *expression.Expression) bool { + return expr.Token.Kind == token.Operator && expr.Token.Text() == ":=" +} diff --git a/src/build/arch/x64/Add.go b/src/build/arch/x64/Add.go index f67094d..5e28c0b 100644 --- a/src/build/arch/x64/Add.go +++ b/src/build/arch/x64/Add.go @@ -6,5 +6,37 @@ import ( // AddRegNum adds a number to the given register. func AddRegNum(code []byte, destination cpu.Register, number int) []byte { - return numRegReg(code, 0x83, 0x81, 0, byte(destination), number) + return numRegReg(code, 0, byte(destination), number, 0x83, 0x81) +} + +// AddRegReg adds a number to the given register. +func AddRegReg(code []byte, destination cpu.Register, operand cpu.Register) []byte { + return regReg(code, byte(operand), byte(destination), 0x01) +} + +func regReg(code []byte, reg byte, rm byte, opCodes ...byte) []byte { + w := byte(1) // Indicates a 64-bit register. + r := byte(0) // Extension to the "reg" field in ModRM. + x := byte(0) // Extension to the SIB index field. + b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). + mod := byte(0b11) // Direct addressing mode, no register offsets. + + if reg > 0b111 { + r = 1 + reg &= 0b111 + } + + if rm > 0b111 { + b = 1 + rm &= 0b111 + } + + rex := REX(w, r, x, b) + modRM := ModRM(mod, reg, rm) + + code = append(code, rex) + code = append(code, opCodes...) + code = append(code, modRM) + + return code } diff --git a/src/build/arch/x64/Add_test.go b/src/build/arch/x64/Add_test.go index 80abc27..ca21dc7 100644 --- a/src/build/arch/x64/Add_test.go +++ b/src/build/arch/x64/Add_test.go @@ -55,3 +55,34 @@ func TestAddRegisterNumber(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestAddRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x4C, 0x01, 0xF8}}, + {x64.RCX, x64.R14, []byte{0x4C, 0x01, 0xF1}}, + {x64.RDX, x64.R13, []byte{0x4C, 0x01, 0xEA}}, + {x64.RBX, x64.R12, []byte{0x4C, 0x01, 0xE3}}, + {x64.RSP, x64.R11, []byte{0x4C, 0x01, 0xDC}}, + {x64.RBP, x64.R10, []byte{0x4C, 0x01, 0xD5}}, + {x64.RSI, x64.R9, []byte{0x4C, 0x01, 0xCE}}, + {x64.RDI, x64.R8, []byte{0x4C, 0x01, 0xC7}}, + {x64.R8, x64.RDI, []byte{0x49, 0x01, 0xF8}}, + {x64.R9, x64.RSI, []byte{0x49, 0x01, 0xF1}}, + {x64.R10, x64.RBP, []byte{0x49, 0x01, 0xEA}}, + {x64.R11, x64.RSP, []byte{0x49, 0x01, 0xE3}}, + {x64.R12, x64.RBX, []byte{0x49, 0x01, 0xDC}}, + {x64.R13, x64.RDX, []byte{0x49, 0x01, 0xD5}}, + {x64.R14, x64.RCX, []byte{0x49, 0x01, 0xCE}}, + {x64.R15, x64.RAX, []byte{0x49, 0x01, 0xC7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("add %s, %s", pattern.Left, pattern.Right) + code := x64.AddRegReg(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Mul.go b/src/build/arch/x64/Mul.go index d952b2b..45254b2 100644 --- a/src/build/arch/x64/Mul.go +++ b/src/build/arch/x64/Mul.go @@ -4,5 +4,10 @@ import "git.akyoto.dev/cli/q/src/build/cpu" // MulRegNum multiplies a register with a number. func MulRegNum(code []byte, destination cpu.Register, number int) []byte { - return numRegReg(code, 0x6B, 0x69, byte(destination), byte(destination), number) + return numRegReg(code, byte(destination), byte(destination), number, 0x6B, 0x69) +} + +// MulRegReg multiplies a register with another register. +func MulRegReg(code []byte, destination cpu.Register, operand cpu.Register) []byte { + return regReg(code, byte(destination), byte(operand), 0x0F, 0xAF) } diff --git a/src/build/arch/x64/Mul_test.go b/src/build/arch/x64/Mul_test.go index 1a81fd3..ffea393 100644 --- a/src/build/arch/x64/Mul_test.go +++ b/src/build/arch/x64/Mul_test.go @@ -55,3 +55,34 @@ func TestMulRegisterNumber(t *testing.T) { assert.DeepEqual(t, code, pattern.Code) } } + +func TestMulRegisterRegister(t *testing.T) { + usagePatterns := []struct { + Left cpu.Register + Right cpu.Register + Code []byte + }{ + {x64.RAX, x64.R15, []byte{0x49, 0x0F, 0xAF, 0xC7}}, + {x64.RCX, x64.R14, []byte{0x49, 0x0F, 0xAF, 0xCE}}, + {x64.RDX, x64.R13, []byte{0x49, 0x0F, 0xAF, 0xD5}}, + {x64.RBX, x64.R12, []byte{0x49, 0x0F, 0xAF, 0xDC}}, + {x64.RSP, x64.R11, []byte{0x49, 0x0F, 0xAF, 0xE3}}, + {x64.RBP, x64.R10, []byte{0x49, 0x0F, 0xAF, 0xEA}}, + {x64.RSI, x64.R9, []byte{0x49, 0x0F, 0xAF, 0xF1}}, + {x64.RDI, x64.R8, []byte{0x49, 0x0F, 0xAF, 0xF8}}, + {x64.R8, x64.RDI, []byte{0x4C, 0x0F, 0xAF, 0xC7}}, + {x64.R9, x64.RSI, []byte{0x4C, 0x0F, 0xAF, 0xCE}}, + {x64.R10, x64.RBP, []byte{0x4C, 0x0F, 0xAF, 0xD5}}, + {x64.R11, x64.RSP, []byte{0x4C, 0x0F, 0xAF, 0xDC}}, + {x64.R12, x64.RBX, []byte{0x4C, 0x0F, 0xAF, 0xE3}}, + {x64.R13, x64.RDX, []byte{0x4C, 0x0F, 0xAF, 0xEA}}, + {x64.R14, x64.RCX, []byte{0x4C, 0x0F, 0xAF, 0xF1}}, + {x64.R15, x64.RAX, []byte{0x4C, 0x0F, 0xAF, 0xF8}}, + } + + for _, pattern := range usagePatterns { + t.Logf("mul %s, %s", pattern.Left, pattern.Right) + code := x64.MulRegReg(nil, pattern.Left, pattern.Right) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/arch/x64/Sub.go b/src/build/arch/x64/Sub.go index f2d5e9b..60fc81d 100644 --- a/src/build/arch/x64/Sub.go +++ b/src/build/arch/x64/Sub.go @@ -6,5 +6,10 @@ import ( // SubRegNum subtracts a number from the given register. func SubRegNum(code []byte, destination cpu.Register, number int) []byte { - return numRegReg(code, 0x83, 0x81, 0b101, byte(destination), number) + return numRegReg(code, 0b101, byte(destination), number, 0x83, 0x81) +} + +// SubRegReg subtracts a register value from another register. +func SubRegReg(code []byte, destination cpu.Register, operand cpu.Register) []byte { + return regReg(code, byte(operand), byte(destination), 0x29) } diff --git a/src/build/arch/x64/numRegReg.go b/src/build/arch/x64/numRegReg.go index c67889e..bb7fa96 100644 --- a/src/build/arch/x64/numRegReg.go +++ b/src/build/arch/x64/numRegReg.go @@ -1,41 +1,15 @@ package x64 // numRegReg encodes an instruction with up to two registers and a number parameter. -func numRegReg(code []byte, opCode8 byte, opCode32 byte, reg byte, rm byte, number int) []byte { - w := byte(1) // Indicates a 64-bit register. - r := byte(0) // Extension to the "reg" field in ModRM. - x := byte(0) // Extension to the SIB index field. - b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). - mod := byte(0b11) // Direct addressing mode, no register offsets. - - if reg > 0b111 { - r = 1 - reg &= 0b111 - } - - if rm > 0b111 { - b = 1 - rm &= 0b111 - } - - rex := REX(w, r, x, b) - modRM := ModRM(mod, reg, rm) - +func numRegReg(code []byte, reg byte, rm byte, number int, opCode8 byte, opCode32 byte) []byte { if sizeOf(number) == 1 { - return append( - code, - rex, - opCode8, - modRM, - byte(number), - ) + code = regReg(code, reg, rm, opCode8) + return append(code, byte(number)) } + code = regReg(code, reg, rm, opCode32) return append( code, - rex, - opCode32, - modRM, byte(number), byte(number>>8), byte(number>>16), diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 81d5299..f3529ec 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -31,18 +31,24 @@ func (a *Assembler) Finalize() ([]byte, []byte) { switch operands := x.Data.(type) { case *RegisterNumber: code = x64.AddRegNum(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.AddRegReg(code, operands.Destination, operands.Source) } case SUB: switch operands := x.Data.(type) { case *RegisterNumber: code = x64.SubRegNum(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.SubRegReg(code, operands.Destination, operands.Source) } case MUL: switch operands := x.Data.(type) { case *RegisterNumber: code = x64.MulRegNum(code, operands.Register, operands.Number) + case *RegisterRegister: + code = x64.MulRegReg(code, operands.Destination, operands.Source) } case DIV: @@ -65,6 +71,16 @@ func (a *Assembler) Finalize() ([]byte, []byte) { code = x64.PopReg(code, x64.RDX) code = x64.PopReg(code, x64.RAX) } + + case *RegisterRegister: + code = x64.PushReg(code, x64.RAX) + code = x64.PushReg(code, x64.RDX) + code = x64.MoveRegReg64(code, x64.RAX, operands.Destination) + code = x64.ExtendRAXToRDX(code) + code = x64.DivReg(code, operands.Source) + code = x64.MoveRegReg64(code, operands.Destination, x64.RAX) + code = x64.PopReg(code, x64.RDX) + code = x64.PopReg(code, x64.RAX) } case CALL: diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index 7eb041f..92842e2 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -13,6 +13,17 @@ func (a *Assembler) RegisterNumber(mnemonic Mnemonic, reg cpu.Register, number i }) } +// RegisterRegister adds an instruction using two registers. +func (a *Assembler) RegisterRegister(mnemonic Mnemonic, left cpu.Register, right cpu.Register) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: mnemonic, + Data: &RegisterRegister{ + Destination: left, + Source: right, + }, + }) +} + // MoveRegisterRegister moves a register value into another register. func (a *Assembler) MoveRegisterRegister(destination cpu.Register, source cpu.Register) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/expression/Expression.go b/src/build/expression/Expression.go index 37b7719..dc5a206 100644 --- a/src/build/expression/Expression.go +++ b/src/build/expression/Expression.go @@ -55,13 +55,13 @@ func (expr *Expression) Close() { } // EachLeaf iterates through all leaves in the tree. -func (expr *Expression) EachLeaf(callBack func(*Expression) error) error { +func (expr *Expression) EachLeaf(call func(*Expression) error) error { if expr.IsLeaf() { - return callBack(expr) + return call(expr) } for _, child := range expr.Children { - err := child.EachLeaf(callBack) + err := child.EachLeaf(call) if err != nil { return err @@ -71,6 +71,28 @@ func (expr *Expression) EachLeaf(callBack func(*Expression) error) error { return nil } +// EachOperation iterates through all the operations in the tree. +func (expr *Expression) EachOperation(call func(*Expression) error) error { + if expr.IsLeaf() { + return nil + } + + // Don't descend into the parameters of function calls + if expr.Token.Text() == "λ" { + return call(expr) + } + + for _, child := range expr.Children { + err := child.EachOperation(call) + + if err != nil { + return err + } + } + + return call(expr) +} + // RemoveChild removes a child from the expression. func (expr *Expression) RemoveChild(child *Expression) { for i, c := range expr.Children {