From bb74c0cf50b5a42c37c1018d08bdb4293caae3b7 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 28 Jul 2024 15:42:51 +0200 Subject: [PATCH] Implemented negation --- src/build/arch/x64/Negate.go | 8 +++++ src/build/arch/x64/Negate_test.go | 39 ++++++++++++++++++++++ src/build/asm/Finalize.go | 6 ++++ src/build/asm/Mnemonic.go | 45 +++++++++++++++++--------- src/build/core/ExecuteRegister.go | 21 ++++++++++++ src/build/core/ExpressionToRegister.go | 17 ++++++++-- src/build/token/Kind.go | 6 ++-- src/build/token/Token.go | 5 +++ src/build/token/Tokenize.go | 2 +- tests/programs/negation.q | 7 ++++ tests/programs_test.go | 1 + 11 files changed, 136 insertions(+), 21 deletions(-) create mode 100644 src/build/arch/x64/Negate.go create mode 100644 src/build/arch/x64/Negate_test.go create mode 100644 src/build/core/ExecuteRegister.go create mode 100644 tests/programs/negation.q diff --git a/src/build/arch/x64/Negate.go b/src/build/arch/x64/Negate.go new file mode 100644 index 0000000..00ae530 --- /dev/null +++ b/src/build/arch/x64/Negate.go @@ -0,0 +1,8 @@ +package x64 + +import "git.akyoto.dev/cli/q/src/build/cpu" + +// NegateRegister negates the value in the register. +func NegateRegister(code []byte, register cpu.Register) []byte { + return encode(code, AddressDirect, 0b011, register, 8, 0xF7) +} diff --git a/src/build/arch/x64/Negate_test.go b/src/build/arch/x64/Negate_test.go new file mode 100644 index 0000000..dd64518 --- /dev/null +++ b/src/build/arch/x64/Negate_test.go @@ -0,0 +1,39 @@ +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 TestNegateRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x64.RAX, []byte{0x48, 0xF7, 0xD8}}, + {x64.RCX, []byte{0x48, 0xF7, 0xD9}}, + {x64.RDX, []byte{0x48, 0xF7, 0xDA}}, + {x64.RBX, []byte{0x48, 0xF7, 0xDB}}, + {x64.RSP, []byte{0x48, 0xF7, 0xDC}}, + {x64.RBP, []byte{0x48, 0xF7, 0xDD}}, + {x64.RSI, []byte{0x48, 0xF7, 0xDE}}, + {x64.RDI, []byte{0x48, 0xF7, 0xDF}}, + {x64.R8, []byte{0x49, 0xF7, 0xD8}}, + {x64.R9, []byte{0x49, 0xF7, 0xD9}}, + {x64.R10, []byte{0x49, 0xF7, 0xDA}}, + {x64.R11, []byte{0x49, 0xF7, 0xDB}}, + {x64.R12, []byte{0x49, 0xF7, 0xDC}}, + {x64.R13, []byte{0x49, 0xF7, 0xDD}}, + {x64.R14, []byte{0x49, 0xF7, 0xDE}}, + {x64.R15, []byte{0x49, 0xF7, 0xDF}}, + } + + for _, pattern := range usagePatterns { + t.Logf("neg %s", pattern.Register) + code := x64.NegateRegister(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/src/build/asm/Finalize.go b/src/build/asm/Finalize.go index 5c3397d..e38a83f 100644 --- a/src/build/asm/Finalize.go +++ b/src/build/asm/Finalize.go @@ -165,6 +165,12 @@ func (a Assembler) Finalize() ([]byte, []byte) { }) } + case NEGATE: + switch operands := x.Data.(type) { + case *Register: + code = x64.NegateRegister(code, operands.Register) + } + case OR: switch operands := x.Data.(type) { case *RegisterNumber: diff --git a/src/build/asm/Mnemonic.go b/src/build/asm/Mnemonic.go index 78070f8..e8c778b 100644 --- a/src/build/asm/Mnemonic.go +++ b/src/build/asm/Mnemonic.go @@ -4,12 +4,31 @@ type Mnemonic uint8 const ( NONE Mnemonic = iota + + // Arithmetic ADD - AND - CALL - COMMENT - COMPARE DIV + MODULO + MUL + NEGATE + SUB + + // Bitwise operations + AND + OR + SHIFTL + SHIFTRS + XOR + + // Data movement + MOVE + LOAD + POP + PUSH + STORE + + // Control flow + CALL JE JNE JG @@ -17,21 +36,13 @@ const ( JL JLE JUMP - MUL LABEL - LOAD - MODULO - MOVE - OR - POP - PUSH RETURN - SHIFTL - SHIFTRS - STORE - SUB SYSCALL - XOR + + // Others + COMMENT + COMPARE ) // String returns a human readable version. @@ -73,6 +84,8 @@ func (m Mnemonic) String() string { return "move" case MUL: return "mul" + case NEGATE: + return "negate" case OR: return "or" case POP: diff --git a/src/build/core/ExecuteRegister.go b/src/build/core/ExecuteRegister.go new file mode 100644 index 0000000..6627acf --- /dev/null +++ b/src/build/core/ExecuteRegister.go @@ -0,0 +1,21 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/build/asm" + "git.akyoto.dev/cli/q/src/build/cpu" + "git.akyoto.dev/cli/q/src/build/errors" + "git.akyoto.dev/cli/q/src/build/token" +) + +// ExecuteRegister performs an operation on a single register. +func (f *Function) ExecuteRegister(operation token.Token, register cpu.Register) error { + switch operation.Kind { + case token.Negate: + f.Register(asm.NEGATE, register) + + default: + return errors.New(&errors.InvalidOperator{Operator: operation.Text(f.File.Bytes)}, f.File, operation.Position) + } + + return nil +} diff --git a/src/build/core/ExpressionToRegister.go b/src/build/core/ExpressionToRegister.go index 212b7ed..a0ab61f 100644 --- a/src/build/core/ExpressionToRegister.go +++ b/src/build/core/ExpressionToRegister.go @@ -1,6 +1,8 @@ package core import ( + "fmt" + "git.akyoto.dev/cli/q/src/build/asm" "git.akyoto.dev/cli/q/src/build/ast" "git.akyoto.dev/cli/q/src/build/cpu" @@ -24,8 +26,19 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return err } - if len(node.Children) < 2 { - return errors.New(errors.MissingOperand, f.File, node.Token.End()) + if len(node.Children) == 1 { + if !node.Token.IsUnaryOperator() { + fmt.Println(node.String(f.File.Bytes), node.Token.Kind) + return errors.New(errors.MissingOperand, f.File, node.Token.End()) + } + + err := f.ExpressionToRegister(node.Children[0], register) + + if err != nil { + return err + } + + return f.ExecuteRegister(node.Token, register) } left := node.Children[0] diff --git a/src/build/token/Kind.go b/src/build/token/Kind.go index 871e49f..44f0132 100644 --- a/src/build/token/Kind.go +++ b/src/build/token/Kind.go @@ -38,8 +38,6 @@ const ( Shr // >> LogicalAnd // && LogicalOr // || - Not // ! (unary) - Negate // - (unary) Equal // == Less // < Greater // > @@ -51,6 +49,10 @@ const ( Call // x() Array // [x] Separator // , + _unary // + Not // ! (unary) + Negate // - (unary) + _unaryEnd // _assignments // Assign // = AddAssign // += diff --git a/src/build/token/Token.go b/src/build/token/Token.go index 99cbe97..caac815 100644 --- a/src/build/token/Token.go +++ b/src/build/token/Token.go @@ -43,6 +43,11 @@ func (t Token) IsOperator() bool { return t.Kind > _operators && t.Kind < _operatorsEnd } +// IsUnaryOperator returns true if the token is a unary operator. +func (t Token) IsUnaryOperator() bool { + return t.Kind > _unary && t.Kind < _unaryEnd +} + // Reset resets the token to default values. func (t *Token) Reset() { t.Position = 0 diff --git a/src/build/token/Tokenize.go b/src/build/token/Tokenize.go index 52b3122..9e0f4fa 100644 --- a/src/build/token/Tokenize.go +++ b/src/build/token/Tokenize.go @@ -27,7 +27,7 @@ func Tokenize(buffer []byte) List { case '\n': tokens = append(tokens, Token{Kind: NewLine, Position: i, Length: 1}) case '-': - if len(tokens) == 0 || tokens[len(tokens)-1].IsOperator() || tokens[len(tokens)-1].IsExpressionStart() { + if len(tokens) == 0 || tokens[len(tokens)-1].IsOperator() || tokens[len(tokens)-1].IsExpressionStart() || tokens[len(tokens)-1].IsKeyword() { tokens = append(tokens, Token{Kind: Negate, Position: i, Length: 1}) } else { if i+1 < Position(len(buffer)) && buffer[i+1] == '=' { diff --git a/tests/programs/negation.q b/tests/programs/negation.q new file mode 100644 index 0000000..aea1133 --- /dev/null +++ b/tests/programs/negation.q @@ -0,0 +1,7 @@ +main() { + syscall(60, f(-32)) +} + +f(x) { + return -x +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 82a9d42..204f314 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -43,6 +43,7 @@ var programs = []struct { {"loop-lifetime", "", "", 0}, {"assert", "", "", 1}, {"negative", "", "", 32}, + {"negation", "", "", 32}, } func TestPrograms(t *testing.T) {