diff --git a/src/asmc/compile.go b/src/asmc/compile.go index 1e76cf4..7cec3b2 100644 --- a/src/asmc/compile.go +++ b/src/asmc/compile.go @@ -93,10 +93,7 @@ func (c *compiler) compile(x asm.Instruction) { c.codeLabels[x.Data.(*asm.Label).Name] = Address(len(c.code)) case asm.LOAD: - switch operands := x.Data.(type) { - case *asm.MemoryRegister: - c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) - } + c.load(x) case asm.MOVE: c.move(x) diff --git a/src/asmc/load.go b/src/asmc/load.go new file mode 100644 index 0000000..e460a82 --- /dev/null +++ b/src/asmc/load.go @@ -0,0 +1,19 @@ +package asmc + +import ( + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/x86" +) + +func (c *compiler) load(x asm.Instruction) { + switch operands := x.Data.(type) { + case *asm.MemoryRegister: + if operands.Address.OffsetRegister == math.MaxUint8 { + c.code = x86.LoadRegister(c.code, operands.Register, operands.Address.Offset, operands.Address.Length, operands.Address.Base) + } else { + c.code = x86.LoadDynamicRegister(c.code, operands.Register, operands.Address.OffsetRegister, operands.Address.Length, operands.Address.Base) + } + } +} diff --git a/src/core/ArrayElementToRegister.go b/src/core/ArrayElementToRegister.go new file mode 100644 index 0000000..46e24f7 --- /dev/null +++ b/src/core/ArrayElementToRegister.go @@ -0,0 +1,70 @@ +package core + +import ( + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/token" + "git.akyoto.dev/cli/q/src/types" +) + +// ArrayElementToRegister moves the value of an array element into the given register. +func (f *Function) ArrayElementToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { + name := node.Children[0].Token.Text(f.File.Bytes) + array := f.VariableByName(name) + + if array == nil { + return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Children[0].Token.Position) + } + + index := node.Children[1] + + memory := asm.Memory{ + Base: array.Register, + Offset: 0, + OffsetRegister: math.MaxUint8, + Length: byte(1), + } + + if index.Token.IsNumeric() { + offset, err := f.ToNumber(index.Token) + + if err != nil { + return nil, err + } + + memory.Offset = int8(offset) + + } else if index.Token.Kind == token.Identifier { + indexName := index.Token.Text(f.File.Bytes) + indexVariable := f.VariableByName(indexName) + + if indexVariable == nil { + return nil, errors.New(&errors.UnknownIdentifier{Name: indexName}, f.File, index.Token.Position) + } + + if !types.Is(indexVariable.Type, types.Int) { + return nil, errors.New(&errors.TypeMismatch{Encountered: indexVariable.Type.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) + } + + memory.OffsetRegister = indexVariable.Register + } else { + typ, err := f.ExpressionToRegister(index, register) + + if err != nil { + return nil, err + } + + if !types.Is(typ, types.Int) { + return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) + } + + memory.OffsetRegister = register + } + + f.MemoryRegister(asm.LOAD, memory, register) + return types.Int, nil +} diff --git a/src/core/CallToRegister.go b/src/core/CallToRegister.go new file mode 100644 index 0000000..5b9901e --- /dev/null +++ b/src/core/CallToRegister.go @@ -0,0 +1,27 @@ +package core + +import ( + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" +) + +// CallToRegister moves the result of a function call into the given register. +func (f *Function) CallToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { + types, err := f.CompileCall(node) + + if err != nil { + return nil, err + } + + if register != f.CPU.Output[0] { + f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) + } + + if len(types) == 0 { + return nil, nil + } + + return types[0], err +} diff --git a/src/core/CompileAssignArray.go b/src/core/CompileAssignArray.go index ec13b4c..2e5d8be 100644 --- a/src/core/CompileAssignArray.go +++ b/src/core/CompileAssignArray.go @@ -5,7 +5,6 @@ import ( "git.akyoto.dev/cli/q/src/asm" "git.akyoto.dev/cli/q/src/ast" - "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/types" ) @@ -27,7 +26,7 @@ func (f *Function) CompileAssignArray(node *ast.Assign) error { memory := asm.Memory{ Base: variable.Register, Offset: 0, - OffsetRegister: cpu.Register(math.MaxUint8), + OffsetRegister: math.MaxUint8, Length: byte(1), } diff --git a/src/core/ExpressionToRegister.go b/src/core/ExpressionToRegister.go index 56ce93e..056ebe1 100644 --- a/src/core/ExpressionToRegister.go +++ b/src/core/ExpressionToRegister.go @@ -1,11 +1,7 @@ package core import ( - "fmt" - "math" - "git.akyoto.dev/cli/q/src/asm" - "git.akyoto.dev/cli/q/src/ast" "git.akyoto.dev/cli/q/src/cpu" "git.akyoto.dev/cli/q/src/errors" "git.akyoto.dev/cli/q/src/expression" @@ -24,96 +20,13 @@ func (f *Function) ExpressionToRegister(node *expression.Expression, register cp return f.TokenToRegister(node.Token, register) } - if ast.IsFunctionCall(node) { - types, err := f.CompileCall(node) - - if err != nil { - return nil, err - } - - if register != f.CPU.Output[0] { - f.RegisterRegister(asm.MOVE, register, f.CPU.Output[0]) - } - - if len(types) == 0 { - return nil, nil - } - - return types[0], err - } - - if node.Token.Kind == token.Array { - name := node.Children[0].Token.Text(f.File.Bytes) - array := f.VariableByName(name) - - if array == nil { - return nil, errors.New(&errors.UnknownIdentifier{Name: name}, f.File, node.Children[0].Token.Position) - } - - index := node.Children[1] - - memory := asm.Memory{ - Base: array.Register, - Offset: 0, - OffsetRegister: cpu.Register(math.MaxUint8), - Length: byte(1), - } - - if index.Token.IsNumeric() { - offset, err := f.ToNumber(index.Token) - - if err != nil { - return nil, err - } - - memory.Offset = int8(offset) - } else { - typ, err := f.ExpressionToRegister(index, register) - - if err != nil { - return nil, err - } - - if !types.Is(typ, types.Int) { - return nil, errors.New(&errors.TypeMismatch{Encountered: typ.Name(), Expected: types.Int.Name()}, f.File, index.Token.Position) - } - - memory.OffsetRegister = register - } - - f.MemoryRegister(asm.LOAD, memory, register) - return types.Int, nil - } - - if node.Token.Kind == token.Period { - left := node.Children[0] - leftText := left.Token.Text(f.File.Bytes) - right := node.Children[1] - rightText := right.Token.Text(f.File.Bytes) - variable := f.VariableByName(leftText) - - if variable != nil { - field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) - f.MemoryRegister(asm.LOAD, asm.Memory{Base: variable.Register, Offset: int8(field.Offset), Length: byte(field.Type.Size())}, register) - return field.Type, nil - } - - constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] - - if isConst { - return f.TokenToRegister(constant.Value, register) - } - - uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) - function, exists := f.Functions[uniqueName] - - if exists { - f.File.Imports[leftText].Used = true - f.RegisterLabel(asm.MOVE, register, function.UniqueName) - return types.AnyPointer, nil - } - - return nil, errors.New(&errors.UnknownIdentifier{Name: leftText}, f.File, left.Token.Position) + switch node.Token.Kind { + case token.Call: + return f.CallToRegister(node, register) + case token.Array: + return f.ArrayElementToRegister(node, register) + case token.Period: + return f.PeriodToRegister(node, register) } if len(node.Children) == 1 { diff --git a/src/core/PeriodToRegister.go b/src/core/PeriodToRegister.go new file mode 100644 index 0000000..d915f85 --- /dev/null +++ b/src/core/PeriodToRegister.go @@ -0,0 +1,52 @@ +package core + +import ( + "fmt" + "math" + + "git.akyoto.dev/cli/q/src/asm" + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/errors" + "git.akyoto.dev/cli/q/src/expression" + "git.akyoto.dev/cli/q/src/types" +) + +// PeriodToRegister moves a constant or a function address into the given register. +func (f *Function) PeriodToRegister(node *expression.Expression, register cpu.Register) (types.Type, error) { + left := node.Children[0] + leftText := left.Token.Text(f.File.Bytes) + right := node.Children[1] + rightText := right.Token.Text(f.File.Bytes) + variable := f.VariableByName(leftText) + + if variable != nil { + field := variable.Type.(*types.Pointer).To.(*types.Struct).FieldByName(rightText) + + memory := asm.Memory{ + Base: variable.Register, + Offset: int8(field.Offset), + OffsetRegister: math.MaxUint8, + Length: byte(field.Type.Size()), + } + + f.MemoryRegister(asm.LOAD, memory, register) + return field.Type, nil + } + + constant, isConst := f.Constants[f.Package+"."+leftText+"."+rightText] + + if isConst { + return f.TokenToRegister(constant.Value, register) + } + + uniqueName := fmt.Sprintf("%s.%s", leftText, rightText) + function, exists := f.Functions[uniqueName] + + if exists { + f.File.Imports[leftText].Used = true + f.RegisterLabel(asm.MOVE, register, function.UniqueName) + return types.AnyPointer, nil + } + + return nil, errors.New(&errors.UnknownIdentifier{Name: leftText}, f.File, left.Token.Position) +} diff --git a/src/x86/LoadDynamic.go b/src/x86/LoadDynamic.go new file mode 100644 index 0000000..d1ad253 --- /dev/null +++ b/src/x86/LoadDynamic.go @@ -0,0 +1,8 @@ +package x86 + +import "git.akyoto.dev/cli/q/src/cpu" + +// LoadDynamicRegister loads from memory with a register offset into a register. +func LoadDynamicRegister(code []byte, destination cpu.Register, offset cpu.Register, length byte, source cpu.Register) []byte { + return memoryAccessDynamic(code, 0x8A, 0x8B, source, offset, length, destination) +} diff --git a/src/x86/LoadDynamic_test.go b/src/x86/LoadDynamic_test.go new file mode 100644 index 0000000..cdd46f2 --- /dev/null +++ b/src/x86/LoadDynamic_test.go @@ -0,0 +1,90 @@ +package x86_test + +import ( + "testing" + + "git.akyoto.dev/cli/q/src/cpu" + "git.akyoto.dev/cli/q/src/x86" + "git.akyoto.dev/go/assert" +) + +func TestLoadDynamicRegister(t *testing.T) { + usagePatterns := []struct { + Destination cpu.Register + Length byte + Source cpu.Register + OffsetRegister cpu.Register + Code []byte + }{ + {x86.R15, 8, x86.RAX, x86.R15, []byte{0x4E, 0x8B, 0x3C, 0x38}}, + {x86.R15, 4, x86.RAX, x86.R15, []byte{0x46, 0x8B, 0x3C, 0x38}}, + {x86.R15, 2, x86.RAX, x86.R15, []byte{0x66, 0x46, 0x8B, 0x3C, 0x38}}, + {x86.R15, 1, x86.RAX, x86.R15, []byte{0x46, 0x8A, 0x3C, 0x38}}, + {x86.R14, 8, x86.RCX, x86.R14, []byte{0x4E, 0x8B, 0x34, 0x31}}, + {x86.R14, 4, x86.RCX, x86.R14, []byte{0x46, 0x8B, 0x34, 0x31}}, + {x86.R14, 2, x86.RCX, x86.R14, []byte{0x66, 0x46, 0x8B, 0x34, 0x31}}, + {x86.R14, 1, x86.RCX, x86.R14, []byte{0x46, 0x8A, 0x34, 0x31}}, + {x86.R13, 8, x86.RDX, x86.R13, []byte{0x4E, 0x8B, 0x2C, 0x2A}}, + {x86.R13, 4, x86.RDX, x86.R13, []byte{0x46, 0x8B, 0x2C, 0x2A}}, + {x86.R13, 2, x86.RDX, x86.R13, []byte{0x66, 0x46, 0x8B, 0x2C, 0x2A}}, + {x86.R13, 1, x86.RDX, x86.R13, []byte{0x46, 0x8A, 0x2C, 0x2A}}, + {x86.R12, 8, x86.RBX, x86.R12, []byte{0x4E, 0x8B, 0x24, 0x23}}, + {x86.R12, 4, x86.RBX, x86.R12, []byte{0x46, 0x8B, 0x24, 0x23}}, + {x86.R12, 2, x86.RBX, x86.R12, []byte{0x66, 0x46, 0x8B, 0x24, 0x23}}, + {x86.R12, 1, x86.RBX, x86.R12, []byte{0x46, 0x8A, 0x24, 0x23}}, + {x86.R11, 8, x86.RSP, x86.R11, []byte{0x4E, 0x8B, 0x1C, 0x1C}}, + {x86.R11, 4, x86.RSP, x86.R11, []byte{0x46, 0x8B, 0x1C, 0x1C}}, + {x86.R11, 2, x86.RSP, x86.R11, []byte{0x66, 0x46, 0x8B, 0x1C, 0x1C}}, + {x86.R11, 1, x86.RSP, x86.R11, []byte{0x46, 0x8A, 0x1C, 0x1C}}, + {x86.R10, 8, x86.RBP, x86.R10, []byte{0x4E, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R10, 4, x86.RBP, x86.R10, []byte{0x46, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R10, 2, x86.RBP, x86.R10, []byte{0x66, 0x46, 0x8B, 0x54, 0x15, 0x00}}, + {x86.R10, 1, x86.RBP, x86.R10, []byte{0x46, 0x8A, 0x54, 0x15, 0x00}}, + {x86.R9, 8, x86.RSI, x86.R9, []byte{0x4E, 0x8B, 0x0C, 0x0E}}, + {x86.R9, 4, x86.RSI, x86.R9, []byte{0x46, 0x8B, 0x0C, 0x0E}}, + {x86.R9, 2, x86.RSI, x86.R9, []byte{0x66, 0x46, 0x8B, 0x0C, 0x0E}}, + {x86.R9, 1, x86.RSI, x86.R9, []byte{0x46, 0x8A, 0x0C, 0x0E}}, + {x86.R8, 8, x86.RDI, x86.R8, []byte{0x4E, 0x8B, 0x04, 0x07}}, + {x86.R8, 4, x86.RDI, x86.R8, []byte{0x46, 0x8B, 0x04, 0x07}}, + {x86.R8, 2, x86.RDI, x86.R8, []byte{0x66, 0x46, 0x8B, 0x04, 0x07}}, + {x86.R8, 1, x86.RDI, x86.R8, []byte{0x46, 0x8A, 0x04, 0x07}}, + {x86.RDI, 8, x86.R8, x86.RDI, []byte{0x49, 0x8B, 0x3C, 0x38}}, + {x86.RDI, 4, x86.R8, x86.RDI, []byte{0x41, 0x8B, 0x3C, 0x38}}, + {x86.RDI, 2, x86.R8, x86.RDI, []byte{0x66, 0x41, 0x8B, 0x3C, 0x38}}, + {x86.RDI, 1, x86.R8, x86.RDI, []byte{0x41, 0x8A, 0x3C, 0x38}}, + {x86.RSI, 8, x86.R9, x86.RSI, []byte{0x49, 0x8B, 0x34, 0x31}}, + {x86.RSI, 4, x86.R9, x86.RSI, []byte{0x41, 0x8B, 0x34, 0x31}}, + {x86.RSI, 2, x86.R9, x86.RSI, []byte{0x66, 0x41, 0x8B, 0x34, 0x31}}, + {x86.RSI, 1, x86.R9, x86.RSI, []byte{0x41, 0x8A, 0x34, 0x31}}, + {x86.RBP, 8, x86.R10, x86.RBP, []byte{0x49, 0x8B, 0x2C, 0x2A}}, + {x86.RBP, 4, x86.R10, x86.RBP, []byte{0x41, 0x8B, 0x2C, 0x2A}}, + {x86.RBP, 2, x86.R10, x86.RBP, []byte{0x66, 0x41, 0x8B, 0x2C, 0x2A}}, + {x86.RBP, 1, x86.R10, x86.RBP, []byte{0x41, 0x8A, 0x2C, 0x2A}}, + {x86.RSP, 8, x86.R11, x86.RSP, []byte{0x4A, 0x8B, 0x24, 0x1C}}, + {x86.RSP, 4, x86.R11, x86.RSP, []byte{0x42, 0x8B, 0x24, 0x1C}}, + {x86.RSP, 2, x86.R11, x86.RSP, []byte{0x66, 0x42, 0x8B, 0x24, 0x1C}}, + {x86.RSP, 1, x86.R11, x86.RSP, []byte{0x42, 0x8A, 0x24, 0x1C}}, + {x86.RBX, 8, x86.R12, x86.RBX, []byte{0x49, 0x8B, 0x1C, 0x1C}}, + {x86.RBX, 4, x86.R12, x86.RBX, []byte{0x41, 0x8B, 0x1C, 0x1C}}, + {x86.RBX, 2, x86.R12, x86.RBX, []byte{0x66, 0x41, 0x8B, 0x1C, 0x1C}}, + {x86.RBX, 1, x86.R12, x86.RBX, []byte{0x41, 0x8A, 0x1C, 0x1C}}, + {x86.RDX, 8, x86.R13, x86.RDX, []byte{0x49, 0x8B, 0x54, 0x15, 0x00}}, + {x86.RDX, 4, x86.R13, x86.RDX, []byte{0x41, 0x8B, 0x54, 0x15, 0x00}}, + {x86.RDX, 2, x86.R13, x86.RDX, []byte{0x66, 0x41, 0x8B, 0x54, 0x15, 0x00}}, + {x86.RDX, 1, x86.R13, x86.RDX, []byte{0x41, 0x8A, 0x54, 0x15, 0x00}}, + {x86.RCX, 8, x86.R14, x86.RCX, []byte{0x49, 0x8B, 0x0C, 0x0E}}, + {x86.RCX, 4, x86.R14, x86.RCX, []byte{0x41, 0x8B, 0x0C, 0x0E}}, + {x86.RCX, 2, x86.R14, x86.RCX, []byte{0x66, 0x41, 0x8B, 0x0C, 0x0E}}, + {x86.RCX, 1, x86.R14, x86.RCX, []byte{0x41, 0x8A, 0x0C, 0x0E}}, + {x86.RAX, 8, x86.R15, x86.RAX, []byte{0x49, 0x8B, 0x04, 0x07}}, + {x86.RAX, 4, x86.R15, x86.RAX, []byte{0x41, 0x8B, 0x04, 0x07}}, + {x86.RAX, 2, x86.R15, x86.RAX, []byte{0x66, 0x41, 0x8B, 0x04, 0x07}}, + {x86.RAX, 1, x86.R15, x86.RAX, []byte{0x41, 0x8A, 0x04, 0x07}}, + } + + for _, pattern := range usagePatterns { + t.Logf("load %dB %s, [%s+%s]", pattern.Length, pattern.Destination, pattern.Source, pattern.OffsetRegister) + code := x86.LoadDynamicRegister(nil, pattern.Destination, pattern.OffsetRegister, pattern.Length, pattern.Source) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/tests/programs/array-index-dynamic.q b/tests/programs/array-index-dynamic.q new file mode 100644 index 0000000..4e4c03f --- /dev/null +++ b/tests/programs/array-index-dynamic.q @@ -0,0 +1,23 @@ +import mem + +main() { + a := mem.alloc(4) + i := 0 + + a[i] = i * 2 + i += 1 + a[i] = i * 2 + i += 1 + a[i] = i * 2 + i += 1 + a[i] = i * 2 + + i = 0 + assert a[i] == i * 2 + i += 1 + assert a[i] == i * 2 + i += 1 + assert a[i] == i * 2 + i += 1 + assert a[i] == i * 2 +} \ No newline at end of file diff --git a/tests/programs/array-index-static.q b/tests/programs/array-index-static.q new file mode 100644 index 0000000..3120816 --- /dev/null +++ b/tests/programs/array-index-static.q @@ -0,0 +1,20 @@ +import mem + +main() { + a := mem.alloc(4) + + assert a[0] == 0 + assert a[1] == 0 + assert a[2] == 0 + assert a[3] == 0 + + a[0] = 0 + a[1] = 1 + a[2] = 2 + a[3] = 3 + + assert a[0] == 0 + assert a[1] == 1 + assert a[2] == 2 + assert a[3] == 3 +} \ No newline at end of file diff --git a/tests/programs/array.q b/tests/programs/array.q deleted file mode 100644 index 6b4d16a..0000000 --- a/tests/programs/array.q +++ /dev/null @@ -1,44 +0,0 @@ -import mem - -main() { - a := mem.alloc(5) - - assert a[0] == 0 - assert a[1] == 0 - assert a[2] == 0 - assert a[3] == 0 - assert a[4] == 0 - - a[0] = 0 - a[1] = 1 - a[2] = 2 - a[3] = 3 - a[4] = 4 - - assert a[0] == 0 - assert a[1] == 1 - assert a[2] == 2 - assert a[3] == 3 - assert a[4] == 4 - - i := 0 - a[i] = i * 2 - - i += 1 - a[i] = i * 2 - - i += 1 - a[i] = i * 2 - - i += 1 - a[i] = i * 2 - - i += 1 - a[i] = i * 2 - - assert a[0] == 0 - assert a[1] == 2 - assert a[2] == 4 - assert a[3] == 6 - assert a[4] == 8 -} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 2b3b55e..32bf77e 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -32,7 +32,6 @@ var programs = []struct { {"octal", "", "", 0}, {"hexadecimal", "", "", 0}, {"const", "", "", 0}, - {"array", "", "", 0}, {"escape-rune", "", "", 0}, {"escape-string", "", "", 0}, {"bitwise-and", "", "", 0}, @@ -62,6 +61,8 @@ var programs = []struct { {"loop-lifetime", "", "", 0}, {"memory-free", "", "", 0}, {"out-of-memory", "", "", 0}, + {"array-index-static", "", "", 0}, + {"array-index-dynamic", "", "", 0}, {"struct", "", "", 0}, {"len", "", "", 0}, }