Implemented register to register instructions

This commit is contained in:
Eduard Urbach 2024-06-23 20:26:53 +02:00
parent 31845dbc48
commit 4fc1935183
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
11 changed files with 171 additions and 16 deletions

View File

@ -3,9 +3,8 @@ main() {
} }
hello() { hello() {
one := 1 write := 1
write := one stdout := 1
stdout := one
address := 4194305 address := 4194305
length := 3 length := 3

View File

@ -142,13 +142,18 @@ func (f *Function) CompileVariableDefinition(expr *expression.Expression) error
return err return err
} }
// All expressions are returned to the memory pool. reg, exists := f.CPU.FindFree()
// To avoid losing variable values, we will detach it from the expression.
expr.RemoveChild(value) if !exists {
panic("no free registers")
}
f.ExpressionToRegister(value, reg)
f.CPU.Use(reg)
f.Variables[name] = &Variable{ f.Variables[name] = &Variable{
Name: name, Name: name,
Value: value, Register: reg,
} }
return nil 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 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: case token.Number:
value := t.Text() value := t.Text()

View File

@ -1,9 +1,11 @@
package build 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. // Variable represents a variable in a function.
type Variable struct { type Variable struct {
Name string Name string
Value *expression.Expression Register cpu.Register
} }

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -1,13 +1,43 @@
package x64 package x64
import "git.akyoto.dev/cli/q/src/build/cpu"
// MoveRegNum32 moves a 32 bit integer into the given register. // 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( return append(
code, code,
0xb8+register, 0xb8+byte(destination),
byte(number), byte(number),
byte(number>>8), byte(number>>8),
byte(number>>16), byte(number>>16),
byte(number>>24), 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)),
)
}

View File

@ -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
}

View File

@ -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)
}
}

View File

@ -28,8 +28,13 @@ func (a *Assembler) Finalize() ([]byte, []byte) {
for _, x := range a.Instructions { for _, x := range a.Instructions {
switch x.Mnemonic { switch x.Mnemonic {
case MOVE: case MOVE:
regNum := x.Data.(*RegisterNumber) switch operands := x.Data.(type) {
code = x64.MoveRegNum32(code, uint8(regNum.Register), uint32(regNum.Number)) case *RegisterNumber:
code = x64.MoveRegNum32(code, operands.Register, uint32(operands.Number))
case *RegisterRegister:
code = x64.MoveRegReg64(code, operands.Destination, operands.Source)
}
case RETURN: case RETURN:
code = x64.Return(code) code = x64.Return(code)

View File

@ -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. // Label adds a label at the current position.
func (a *Assembler) Label(name string) { func (a *Assembler) Label(name string) {
a.Instructions = append(a.Instructions, Instruction{ a.Instructions = append(a.Instructions, Instruction{

View File

@ -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)
}