diff --git a/examples/hello/hello.q b/examples/hello/hello.q index d3e8c5c..60f65e4 100644 --- a/examples/hello/hello.q +++ b/examples/hello/hello.q @@ -3,9 +3,8 @@ main() { } hello() { - one := 1 - write := one - stdout := one + write := 1 + stdout := 1 address := 4194305 length := 3 diff --git a/src/build/Function.go b/src/build/Function.go index 3ba0646..efdac96 100644 --- a/src/build/Function.go +++ b/src/build/Function.go @@ -142,13 +142,18 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error return err } - // All expressions are returned to the memory pool. - // To avoid losing variable values, we will detach it from the expression. - expr.RemoveChild(value) + reg, exists := f.CPU.FindFree() + + if !exists { + panic("no free registers") + } + + f.ExpressionToRegister(value, reg) + f.CPU.Use(reg) f.Variables[name] = &Variable{ - Name: name, - Value: value, + Name: name, + Register: reg, } return nil @@ -197,7 +202,8 @@ func (f *Function) TokenToRegister(t token.Token, register cpu.Register) error { return errors.New(&errors.UnknownIdentifier{Name: name}, f.File, t.Position) } - return f.ExpressionToRegister(variable.Value, register) + f.Assembler.MoveRegisterRegister(register, variable.Register) + return nil case token.Number: value := t.Text() diff --git a/src/build/Variable.go b/src/build/Variable.go index a37b2b5..c8189f1 100644 --- a/src/build/Variable.go +++ b/src/build/Variable.go @@ -1,9 +1,11 @@ package build -import "git.akyoto.dev/cli/q/src/build/expression" +import ( + "git.akyoto.dev/cli/q/src/build/cpu" +) // Variable represents a variable in a function. type Variable struct { - Name string - Value *expression.Expression + Name string + Register cpu.Register } diff --git a/src/build/arch/x64/ModRM.go b/src/build/arch/x64/ModRM.go new file mode 100644 index 0000000..b6ab57f --- /dev/null +++ b/src/build/arch/x64/ModRM.go @@ -0,0 +1,9 @@ +package x64 + +// ModRM is used to generate a ModRM suffix. +// - mod: 2 bits +// - reg: 3 bits +// - rm: 3 bits +func ModRM(mod byte, reg byte, rm byte) byte { + return (mod << 6) | (reg << 3) | rm +} diff --git a/src/build/arch/x64/ModRM_test.go b/src/build/arch/x64/ModRM_test.go new file mode 100644 index 0000000..e9d470f --- /dev/null +++ b/src/build/arch/x64/ModRM_test.go @@ -0,0 +1,34 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/go/assert" +) + +func TestModRM(t *testing.T) { + testData := []struct{ mod, reg, rm, expected byte }{ + {0b_00, 0b_111, 0b_000, 0b_00_111_000}, + {0b_00, 0b_110, 0b_001, 0b_00_110_001}, + {0b_00, 0b_101, 0b_010, 0b_00_101_010}, + {0b_00, 0b_100, 0b_011, 0b_00_100_011}, + {0b_00, 0b_011, 0b_100, 0b_00_011_100}, + {0b_00, 0b_010, 0b_101, 0b_00_010_101}, + {0b_00, 0b_001, 0b_110, 0b_00_001_110}, + {0b_00, 0b_000, 0b_111, 0b_00_000_111}, + {0b_11, 0b_111, 0b_000, 0b_11_111_000}, + {0b_11, 0b_110, 0b_001, 0b_11_110_001}, + {0b_11, 0b_101, 0b_010, 0b_11_101_010}, + {0b_11, 0b_100, 0b_011, 0b_11_100_011}, + {0b_11, 0b_011, 0b_100, 0b_11_011_100}, + {0b_11, 0b_010, 0b_101, 0b_11_010_101}, + {0b_11, 0b_001, 0b_110, 0b_11_001_110}, + {0b_11, 0b_000, 0b_111, 0b_11_000_111}, + } + + for _, test := range testData { + modRM := x64.ModRM(test.mod, test.reg, test.rm) + assert.Equal(t, modRM, test.expected) + } +} diff --git a/src/build/arch/x64/Move.go b/src/build/arch/x64/Move.go index 6d9fd57..63ffaf0 100644 --- a/src/build/arch/x64/Move.go +++ b/src/build/arch/x64/Move.go @@ -1,13 +1,43 @@ package x64 +import "git.akyoto.dev/cli/q/src/build/cpu" + // MoveRegNum32 moves a 32 bit integer into the given register. -func MoveRegNum32(code []byte, register uint8, number uint32) []byte { +func MoveRegNum32(code []byte, destination cpu.Register, number uint32) []byte { + if destination >= 8 { + code = append(code, REX(0, 0, 0, 1)) + destination -= 8 + } + return append( code, - 0xb8+register, + 0xb8+byte(destination), byte(number), byte(number>>8), byte(number>>16), byte(number>>24), ) } + +// MoveRegReg64 moves a register value into another register. +func MoveRegReg64(code []byte, destination cpu.Register, source cpu.Register) []byte { + r := byte(0) // Extension to the "reg" field in ModRM. + b := byte(0) // Extension to the "rm" field in ModRM or the SIB base (r8 up to r15 use this). + + if source >= 8 { + r = 1 + source -= 8 + } + + if destination >= 8 { + b = 1 + destination -= 8 + } + + return append( + code, + REX(1, r, 0, b), + 0x89, + ModRM(0b11, byte(source), byte(destination)), + ) +} diff --git a/src/build/arch/x64/REX.go b/src/build/arch/x64/REX.go new file mode 100644 index 0000000..ba1fa1a --- /dev/null +++ b/src/build/arch/x64/REX.go @@ -0,0 +1,7 @@ +package x64 + +// REX is used to generate a REX prefix. +// w, r, x and b can only be set to either 0 or 1. +func REX(w, r, x, b byte) byte { + return 0b_0100_0000 | (w << 3) | (r << 2) | (x << 1) | b +} diff --git a/src/build/arch/x64/REX_test.go b/src/build/arch/x64/REX_test.go new file mode 100644 index 0000000..e48e3c5 --- /dev/null +++ b/src/build/arch/x64/REX_test.go @@ -0,0 +1,34 @@ +package x64_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/build/arch/x64" + "git.akyoto.dev/go/assert" +) + +func TestREX(t *testing.T) { + testData := []struct{ w, r, x, b, expected byte }{ + {0, 0, 0, 0, 0b_0100_0000}, + {0, 0, 0, 1, 0b_0100_0001}, + {0, 0, 1, 0, 0b_0100_0010}, + {0, 0, 1, 1, 0b_0100_0011}, + {0, 1, 0, 0, 0b_0100_0100}, + {0, 1, 0, 1, 0b_0100_0101}, + {0, 1, 1, 0, 0b_0100_0110}, + {0, 1, 1, 1, 0b_0100_0111}, + {1, 0, 0, 0, 0b_0100_1000}, + {1, 0, 0, 1, 0b_0100_1001}, + {1, 0, 1, 0, 0b_0100_1010}, + {1, 0, 1, 1, 0b_0100_1011}, + {1, 1, 0, 0, 0b_0100_1100}, + {1, 1, 0, 1, 0b_0100_1101}, + {1, 1, 1, 0, 0b_0100_1110}, + {1, 1, 1, 1, 0b_0100_1111}, + } + + for _, test := range testData { + rex := x64.REX(test.w, test.r, test.x, test.b) + assert.Equal(t, rex, test.expected) + } +} diff --git a/src/build/asm/Assembler.go b/src/build/asm/Assembler.go index 5af72a6..f159bc4 100644 --- a/src/build/asm/Assembler.go +++ b/src/build/asm/Assembler.go @@ -28,8 +28,13 @@ func (a *Assembler) Finalize() ([]byte, []byte) { for _, x := range a.Instructions { switch x.Mnemonic { case MOVE: - regNum := x.Data.(*RegisterNumber) - code = x64.MoveRegNum32(code, uint8(regNum.Register), uint32(regNum.Number)) + switch operands := x.Data.(type) { + case *RegisterNumber: + code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number)) + + case *RegisterRegister: + code = x64.MoveRegReg64(code, operands.Destination, operands.Source) + } case RETURN: code = x64.Return(code) diff --git a/src/build/asm/Instructions.go b/src/build/asm/Instructions.go index aa74d0e..82aebdb 100644 --- a/src/build/asm/Instructions.go +++ b/src/build/asm/Instructions.go @@ -13,6 +13,17 @@ func (a *Assembler) MoveRegisterNumber(reg cpu.Register, number uint64) { }) } +// MoveRegisterRegister moves a register value into another register. +func (a *Assembler) MoveRegisterRegister(destination cpu.Register, source cpu.Register) { + a.Instructions = append(a.Instructions, Instruction{ + Mnemonic: MOVE, + Data: &RegisterRegister{ + Destination: destination, + Source: source, + }, + }) +} + // Label adds a label at the current position. func (a *Assembler) Label(name string) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/build/asm/RegisterRegister.go b/src/build/asm/RegisterRegister.go new file mode 100644 index 0000000..5acb084 --- /dev/null +++ b/src/build/asm/RegisterRegister.go @@ -0,0 +1,18 @@ +package asm + +import ( + "fmt" + + "git.akyoto.dev/cli/q/src/build/cpu" +) + +// RegisterRegister operates with two registers. +type RegisterRegister struct { + Destination cpu.Register + Source cpu.Register +} + +// String returns a human readable version. +func (data *RegisterRegister) String() string { + return fmt.Sprintf("%s, %s", data.Destination, data.Source) +}