From c3054369e323724192d486c8a2cd55231f046dca Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Sun, 2 Mar 2025 17:53:18 +0100 Subject: [PATCH] Implemented register calls --- src/asm/CanSkip.go | 4 +-- src/asm/Instructions.go | 10 -------- src/asmc/call.go | 41 +++++++++++++++++-------------- src/compiler/eachFunction.go | 9 +++++-- src/core/CallSafe.go | 2 +- src/core/CompileCall.go | 10 +++++++- src/register/Call.go | 7 ------ src/register/Label.go | 8 ++++++ src/x86/Call.go | 23 +++++++++++++++-- src/x86/Call_test.go | 39 +++++++++++++++++++++++++++++ tests/errors/UnknownType.q | 5 ++++ tests/errors/UnknownType2.q | 5 ++++ tests/errors/UnusedVariable2.q | 3 +++ tests/errors_test.go | 3 +++ tests/programs/function-pointer.q | 6 +++++ tests/programs_test.go | 1 + 16 files changed, 133 insertions(+), 43 deletions(-) delete mode 100644 src/register/Call.go create mode 100644 src/register/Label.go create mode 100644 src/x86/Call_test.go create mode 100644 tests/errors/UnknownType.q create mode 100644 tests/errors/UnknownType2.q create mode 100644 tests/errors/UnusedVariable2.q create mode 100644 tests/programs/function-pointer.q diff --git a/src/asm/CanSkip.go b/src/asm/CanSkip.go index b0999e0..ab58c65 100644 --- a/src/asm/CanSkip.go +++ b/src/asm/CanSkip.go @@ -21,11 +21,11 @@ func (a *Assembler) CanSkip(mnemonic Mnemonic, left cpu.Register, right cpu.Regi return false } - if lastData.Destination == left && lastData.Source == right { + if lastData.Destination == right && lastData.Source == left { return true } - if lastData.Destination == right && lastData.Source == left { + if lastData.Destination == left && lastData.Source == right { return true } } diff --git a/src/asm/Instructions.go b/src/asm/Instructions.go index ab6a510..e203e3f 100644 --- a/src/asm/Instructions.go +++ b/src/asm/Instructions.go @@ -10,16 +10,6 @@ func (a *Assembler) Comment(text string) { }) } -// Call calls a function whose position is identified by a label. -func (a *Assembler) Call(name string) { - a.Instructions = append(a.Instructions, Instruction{ - Mnemonic: CALL, - Data: &Label{ - Name: name, - }, - }) -} - // DLLCall calls a function in a DLL file. func (a *Assembler) DLLCall(name string) { a.Instructions = append(a.Instructions, Instruction{ diff --git a/src/asmc/call.go b/src/asmc/call.go index 6c78a09..e5d08a7 100644 --- a/src/asmc/call.go +++ b/src/asmc/call.go @@ -6,26 +6,31 @@ import ( ) func (c *compiler) call(x asm.Instruction) { - c.code = x86.Call(c.code, 0x00_00_00_00) - size := 4 - label := x.Data.(*asm.Label) + switch data := x.Data.(type) { + case *asm.Label: + c.code = x86.Call(c.code, 0x00_00_00_00) + size := 4 - pointer := &pointer{ - Position: Address(len(c.code) - size), - OpSize: 1, - Size: uint8(size), - } - - pointer.Resolve = func() Address { - destination, exists := c.codeLabels[label.Name] - - if !exists { - panic("unknown jump label") + pointer := &pointer{ + Position: Address(len(c.code) - size), + OpSize: 1, + Size: uint8(size), } - distance := destination - (pointer.Position + Address(pointer.Size)) - return distance - } + pointer.Resolve = func() Address { + destination, exists := c.codeLabels[data.Name] - c.codePointers = append(c.codePointers, pointer) + if !exists { + panic("unknown jump label") + } + + distance := destination - (pointer.Position + Address(pointer.Size)) + return distance + } + + c.codePointers = append(c.codePointers, pointer) + + case *asm.Register: + c.code = x86.CallRegister(c.code, data.Register) + } } diff --git a/src/compiler/eachFunction.go b/src/compiler/eachFunction.go index de7fc63..f3d6215 100644 --- a/src/compiler/eachFunction.go +++ b/src/compiler/eachFunction.go @@ -16,8 +16,13 @@ func (r *Result) eachFunction(caller *core.Function, traversed map[*core.Functio continue } - name := x.Data.(*asm.Label).Name - callee, exists := r.Functions[name] + label, isLabel := x.Data.(*asm.Label) + + if !isLabel { + continue + } + + callee, exists := r.Functions[label.Name] if !exists { continue diff --git a/src/core/CallSafe.go b/src/core/CallSafe.go index 20f4676..924d209 100644 --- a/src/core/CallSafe.go +++ b/src/core/CallSafe.go @@ -19,7 +19,7 @@ func (f *Function) CallSafe(fn *Function, registers []cpu.Register) { } } - f.Call(fn.UniqueName) + f.Label(asm.CALL, fn.UniqueName) for _, register := range slices.Backward(f.CPU.General) { if f.RegisterIsUsed(register) { diff --git a/src/core/CompileCall.go b/src/core/CompileCall.go index 3d4ad40..fec090a 100644 --- a/src/core/CompileCall.go +++ b/src/core/CompileCall.go @@ -1,6 +1,7 @@ package core import ( + "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/types" @@ -45,13 +46,20 @@ func (f *Function) CompileCall(root *expression.Expression) ([]types.Type, error } if f.UniqueName == "core.init" && pkg == "main" && name == "main" { - f.Call("main.main") + f.Label(asm.CALL, "main.main") return nil, nil } fn, exists = f.All.Functions[pkg+"."+name] if !exists { + variable := f.VariableByName(name) + + if variable != nil { + f.Register(asm.CALL, variable.Value.Register) + return nil, nil + } + return nil, errors.New(&errors.UnknownFunction{Name: name}, f.File, nameNode.Token.Position) } diff --git a/src/register/Call.go b/src/register/Call.go deleted file mode 100644 index 6008dbd..0000000 --- a/src/register/Call.go +++ /dev/null @@ -1,7 +0,0 @@ -package register - -func (f *Machine) Call(label string) { - f.Assembler.Call(label) - f.UseRegister(f.CPU.Output[0]) - f.postInstruction() -} diff --git a/src/register/Label.go b/src/register/Label.go new file mode 100644 index 0000000..4dcea1a --- /dev/null +++ b/src/register/Label.go @@ -0,0 +1,8 @@ +package register + +import "git.urbach.dev/cli/q/src/asm" + +func (f *Machine) Label(mnemonic asm.Mnemonic, label string) { + f.Assembler.Label(mnemonic, label) + f.postInstruction() +} diff --git a/src/x86/Call.go b/src/x86/Call.go index 812b484..b6c9194 100644 --- a/src/x86/Call.go +++ b/src/x86/Call.go @@ -1,5 +1,7 @@ package x86 +import "git.urbach.dev/cli/q/src/cpu" + // Call places the return address on the top of the stack and continues // program flow at the new address. // The address is relative to the next instruction. @@ -14,8 +16,25 @@ func Call(code []byte, address uint32) []byte { ) } -// CallAtAddress places the return address on the top of the stack and -// continues program flow at the address stored at the given memory address. +// Calls a function whose address is stored in the given register. +func CallRegister(code []byte, register cpu.Register) []byte { + if register > 0b111 { + return append( + code, + 0x41, + 0xFF, + 0xD0+byte(register&0b111), + ) + } + + return append( + code, + 0xFF, + 0xD0+byte(register), + ) +} + +// CallAtAddress calls a function at the address stored at the given memory address. // The memory address is relative to the next instruction. func CallAtAddress(code []byte, address uint32) []byte { return append( diff --git a/src/x86/Call_test.go b/src/x86/Call_test.go new file mode 100644 index 0000000..5aae357 --- /dev/null +++ b/src/x86/Call_test.go @@ -0,0 +1,39 @@ +package x86_test + +import ( + "testing" + + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/x86" + "git.urbach.dev/go/assert" +) + +func TestCallRegister(t *testing.T) { + usagePatterns := []struct { + Register cpu.Register + Code []byte + }{ + {x86.RAX, []byte{0xFF, 0xD0}}, + {x86.RCX, []byte{0xFF, 0xD1}}, + {x86.RDX, []byte{0xFF, 0xD2}}, + {x86.RBX, []byte{0xFF, 0xD3}}, + {x86.RSP, []byte{0xFF, 0xD4}}, + {x86.RBP, []byte{0xFF, 0xD5}}, + {x86.RSI, []byte{0xFF, 0xD6}}, + {x86.RDI, []byte{0xFF, 0xD7}}, + {x86.R8, []byte{0x41, 0xFF, 0xD0}}, + {x86.R9, []byte{0x41, 0xFF, 0xD1}}, + {x86.R10, []byte{0x41, 0xFF, 0xD2}}, + {x86.R11, []byte{0x41, 0xFF, 0xD3}}, + {x86.R12, []byte{0x41, 0xFF, 0xD4}}, + {x86.R13, []byte{0x41, 0xFF, 0xD5}}, + {x86.R14, []byte{0x41, 0xFF, 0xD6}}, + {x86.R15, []byte{0x41, 0xFF, 0xD7}}, + } + + for _, pattern := range usagePatterns { + t.Logf("call %s", pattern.Register) + code := x86.CallRegister(nil, pattern.Register) + assert.DeepEqual(t, code, pattern.Code) + } +} diff --git a/tests/errors/UnknownType.q b/tests/errors/UnknownType.q new file mode 100644 index 0000000..6b4f652 --- /dev/null +++ b/tests/errors/UnknownType.q @@ -0,0 +1,5 @@ +main() {} + +f(x unknown) -> int { + return x +} \ No newline at end of file diff --git a/tests/errors/UnknownType2.q b/tests/errors/UnknownType2.q new file mode 100644 index 0000000..260b358 --- /dev/null +++ b/tests/errors/UnknownType2.q @@ -0,0 +1,5 @@ +main() {} + +f(x int) -> unknown { + return x +} \ No newline at end of file diff --git a/tests/errors/UnusedVariable2.q b/tests/errors/UnusedVariable2.q new file mode 100644 index 0000000..d1d7272 --- /dev/null +++ b/tests/errors/UnusedVariable2.q @@ -0,0 +1,3 @@ +main() {} + +f(x int) {} \ No newline at end of file diff --git a/tests/errors_test.go b/tests/errors_test.go index e672684..95a5424 100644 --- a/tests/errors_test.go +++ b/tests/errors_test.go @@ -58,10 +58,13 @@ var errs = []struct { {"UnknownIdentifier2.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownIdentifier3.q", &errors.UnknownIdentifier{Name: "x"}}, {"UnknownPackage.q", &errors.UnknownPackage{Name: "sys"}}, + {"UnknownType.q", &errors.UnknownType{Name: "unknown"}}, + {"UnknownType2.q", &errors.UnknownType{Name: "unknown"}}, {"UnknownStructField.q", &errors.UnknownStructField{StructName: "A", FieldName: "x"}}, {"UntypedExpression.q", errors.UntypedExpression}, {"UnusedImport.q", &errors.UnusedImport{Package: "sys"}}, {"UnusedVariable.q", &errors.UnusedVariable{Name: "x"}}, + {"UnusedVariable2.q", &errors.UnusedVariable{Name: "x"}}, {"VariableAlreadyExists.q", &errors.VariableAlreadyExists{Name: "x"}}, } diff --git a/tests/programs/function-pointer.q b/tests/programs/function-pointer.q new file mode 100644 index 0000000..20f8c8a --- /dev/null +++ b/tests/programs/function-pointer.q @@ -0,0 +1,6 @@ +import core + +main() { + exit := core.exit + exit() +} \ No newline at end of file diff --git a/tests/programs_test.go b/tests/programs_test.go index 3979fc9..369c4af 100644 --- a/tests/programs_test.go +++ b/tests/programs_test.go @@ -68,6 +68,7 @@ var programs = []struct { {"struct", 0}, {"len", 0}, {"cast", 0}, + {"function-pointer", 0}, } func TestPrograms(t *testing.T) {