From 0ece1b092e6536c7101966acbf44e6fe7581e3d8 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Fri, 4 Jul 2025 19:06:47 +0200 Subject: [PATCH] Implemented liveness analysis --- lib/core/core_linux_arm.q | 12 -- lib/core/core_linux_x86.q | 12 -- lib/core/core_mac.q | 12 -- lib/os/os_linux_arm.q | 3 + lib/os/os_linux_x86.q | 3 + lib/os/os_mac.q | 3 + lib/os/os_windows.q | 9 ++ lib/run/run_unix.q | 10 ++ .../core_windows.q => run/run_windows.q} | 11 +- src/compiler/Compile.go | 2 +- src/core/CheckDeadCode.go | 2 +- src/core/Evaluate.go | 2 +- src/cpu/Register.go | 4 + src/linker/WriteFile.go | 2 +- src/scanner/Scan.go | 2 +- src/ssa/Field.go | 6 +- src/ssa/IR.go | 38 ++++++- src/ssa2asm/Compiler.go | 10 +- src/ssa2asm/CreateSteps.go | 103 ++++++++++++++++++ src/ssa2asm/{ValueToRegister.go => Exec.go} | 85 +++++++++------ src/ssa2asm/GenerateAssembly.go | 26 +---- src/ssa2asm/Step.go | 26 +++++ tests/sum.q | 4 +- 23 files changed, 274 insertions(+), 113 deletions(-) delete mode 100644 lib/core/core_linux_arm.q delete mode 100644 lib/core/core_linux_x86.q delete mode 100644 lib/core/core_mac.q create mode 100644 lib/os/os_linux_arm.q create mode 100644 lib/os/os_linux_x86.q create mode 100644 lib/os/os_mac.q create mode 100644 lib/os/os_windows.q create mode 100644 lib/run/run_unix.q rename lib/{core/core_windows.q => run/run_windows.q} (67%) create mode 100644 src/ssa2asm/CreateSteps.go rename src/ssa2asm/{ValueToRegister.go => Exec.go} (50%) create mode 100644 src/ssa2asm/Step.go diff --git a/lib/core/core_linux_arm.q b/lib/core/core_linux_arm.q deleted file mode 100644 index f20e626..0000000 --- a/lib/core/core_linux_arm.q +++ /dev/null @@ -1,12 +0,0 @@ -init() { - main.main() - exit() -} - -exit() { - syscall(93, 0) -} - -crash() { - syscall(93, 1) -} \ No newline at end of file diff --git a/lib/core/core_linux_x86.q b/lib/core/core_linux_x86.q deleted file mode 100644 index 9c8f519..0000000 --- a/lib/core/core_linux_x86.q +++ /dev/null @@ -1,12 +0,0 @@ -init() { - main.main() - exit() -} - -exit() { - syscall(60, 0) -} - -crash() { - syscall(60, 1) -} \ No newline at end of file diff --git a/lib/core/core_mac.q b/lib/core/core_mac.q deleted file mode 100644 index 91618b4..0000000 --- a/lib/core/core_mac.q +++ /dev/null @@ -1,12 +0,0 @@ -init() { - main.main() - exit() -} - -exit() { - syscall(0x2000001, 0) -} - -crash() { - syscall(0x2000001, 1) -} \ No newline at end of file diff --git a/lib/os/os_linux_arm.q b/lib/os/os_linux_arm.q new file mode 100644 index 0000000..2f5fad0 --- /dev/null +++ b/lib/os/os_linux_arm.q @@ -0,0 +1,3 @@ +exit(code int) { + syscall(93, code) +} \ No newline at end of file diff --git a/lib/os/os_linux_x86.q b/lib/os/os_linux_x86.q new file mode 100644 index 0000000..a219c09 --- /dev/null +++ b/lib/os/os_linux_x86.q @@ -0,0 +1,3 @@ +exit(code int) { + syscall(60, code) +} \ No newline at end of file diff --git a/lib/os/os_mac.q b/lib/os/os_mac.q new file mode 100644 index 0000000..2a10091 --- /dev/null +++ b/lib/os/os_mac.q @@ -0,0 +1,3 @@ +exit(code int) { + syscall(0x2000001, code) +} \ No newline at end of file diff --git a/lib/os/os_windows.q b/lib/os/os_windows.q new file mode 100644 index 0000000..904bbdd --- /dev/null +++ b/lib/os/os_windows.q @@ -0,0 +1,9 @@ +exit(code int) { + kernel32.ExitProcess(code) +} + +extern { + kernel32 { + ExitProcess(code uint) + } +} \ No newline at end of file diff --git a/lib/run/run_unix.q b/lib/run/run_unix.q new file mode 100644 index 0000000..71b23ee --- /dev/null +++ b/lib/run/run_unix.q @@ -0,0 +1,10 @@ +import os + +init() { + main.main() + os.exit(0) +} + +crash() { + os.exit(1) +} \ No newline at end of file diff --git a/lib/core/core_windows.q b/lib/run/run_windows.q similarity index 67% rename from lib/core/core_windows.q rename to lib/run/run_windows.q index db8093b..19770c1 100644 --- a/lib/core/core_windows.q +++ b/lib/run/run_windows.q @@ -1,23 +1,20 @@ +import os + init() { utf8 := 65001 kernel32.SetConsoleCP(utf8) kernel32.SetConsoleOutputCP(utf8) main.main() - exit() -} - -exit() { - kernel32.ExitProcess(0) + os.exit(0) } crash() { - kernel32.ExitProcess(1) + os.exit(1) } extern { kernel32 { SetConsoleCP(cp uint) SetConsoleOutputCP(cp uint) - ExitProcess(code uint) } } \ No newline at end of file diff --git a/src/compiler/Compile.go b/src/compiler/Compile.go index 616f82c..f2935c3 100644 --- a/src/compiler/Compile.go +++ b/src/compiler/Compile.go @@ -18,7 +18,7 @@ func Compile(b *build.Build) (*core.Environment, error) { } // Check for existence of `init` - init, exists := all.Functions["core.init"] + init, exists := all.Functions["run.init"] if !exists { return nil, MissingInitFunction diff --git a/src/core/CheckDeadCode.go b/src/core/CheckDeadCode.go index 66abd02..deacd46 100644 --- a/src/core/CheckDeadCode.go +++ b/src/core/CheckDeadCode.go @@ -7,7 +7,7 @@ import ( // CheckDeadCode checks for dead values. func (f *Function) CheckDeadCode() error { - for instr := range f.Values { + for _, instr := range f.Values { if !instr.IsConst() { continue } diff --git a/src/core/Evaluate.go b/src/core/Evaluate.go index 2e75bd1..d28338c 100644 --- a/src/core/Evaluate.go +++ b/src/core/Evaluate.go @@ -180,7 +180,7 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) { } v := f.Append(&ssa.Field{ - Struct: identifier, + Object: identifier, Field: field, Source: ssa.Source(expr.Source), }) diff --git a/src/cpu/Register.go b/src/cpu/Register.go index 547c330..f403a89 100644 --- a/src/cpu/Register.go +++ b/src/cpu/Register.go @@ -7,5 +7,9 @@ type Register int8 // String returns the human readable name of the register. func (r Register) String() string { + if r < 0 { + return "r?" + } + return fmt.Sprintf("r%d", r) } \ No newline at end of file diff --git a/src/linker/WriteFile.go b/src/linker/WriteFile.go index 88fc7d4..5edc0e1 100644 --- a/src/linker/WriteFile.go +++ b/src/linker/WriteFile.go @@ -20,7 +20,7 @@ func WriteFile(executable string, b *build.Build, env *core.Environment) error { return err } - init := env.Functions["core.init"] + init := env.Functions["run.init"] traversed := make(map[*core.Function]bool, len(env.Functions)) final := asm.Assembler{ diff --git a/src/scanner/Scan.go b/src/scanner/Scan.go index 79d2d01..4da223e 100644 --- a/src/scanner/Scan.go +++ b/src/scanner/Scan.go @@ -19,7 +19,7 @@ func Scan(b *build.Build) (*core.Environment, error) { } go func() { - s.queueDirectory(filepath.Join(global.Library, "core"), "core") + s.queueDirectory(filepath.Join(global.Library, "run"), "run") s.queue(b.Files...) s.group.Wait() close(s.functions) diff --git a/src/ssa/Field.go b/src/ssa/Field.go index 6cc0cde..83bc54c 100644 --- a/src/ssa/Field.go +++ b/src/ssa/Field.go @@ -7,7 +7,7 @@ import ( ) type Field struct { - Struct Value + Object Value Field *types.Field Liveness Source @@ -15,8 +15,8 @@ type Field struct { func (v *Field) IsConst() bool { return true } func (v *Field) Type() types.Type { return v.Field.Type } -func (v *Field) String() string { return fmt.Sprintf("%s.%s", v.Struct, v.Field) } -func (v *Field) Inputs() []Value { return []Value{v.Struct} } +func (v *Field) String() string { return fmt.Sprintf("%s.%s", v.Object, v.Field) } +func (v *Field) Inputs() []Value { return []Value{v.Object} } func (a *Field) Equals(v Value) bool { b, sameType := v.(*Field) diff --git a/src/ssa/IR.go b/src/ssa/IR.go index 84cc362..f4e1c03 100644 --- a/src/ssa/IR.go +++ b/src/ssa/IR.go @@ -1,5 +1,7 @@ package ssa +import "slices" + // IR is a list of basic blocks. type IR struct { Blocks []*Block @@ -30,13 +32,24 @@ func (f *IR) Append(instr Value) Value { return f.Blocks[len(f.Blocks)-1].Append(instr) } +// CountValues returns the total number of values. +func (f *IR) CountValues() int { + count := 0 + + for _, block := range f.Blocks { + count += len(block.Instructions) + } + + return count +} + // FindExisting returns an equal instruction that's already appended or `nil` if none could be found. func (f *IR) FindExisting(instr Value) Value { if !instr.IsConst() { return nil } - for existing := range f.Values { + for _, existing := range f.Values { if existing.IsConst() && instr.Equals(existing) { return existing } @@ -46,10 +59,29 @@ func (f *IR) FindExisting(instr Value) Value { } // Values yields on each value. -func (f *IR) Values(yield func(Value) bool) { +func (f *IR) Values(yield func(int, Value) bool) { + index := 0 + for _, block := range f.Blocks { for _, instr := range block.Instructions { - if !yield(instr) { + if !yield(index, instr) { + return + } + + index++ + } + } +} + +// ValuesBackward yields on each value from the end towards the start. +func (f *IR) ValuesBackward(yield func(int, Value) bool) { + index := f.CountValues() + + for _, block := range slices.Backward(f.Blocks) { + for _, instr := range slices.Backward(block.Instructions) { + index-- + + if !yield(index, instr) { return } } diff --git a/src/ssa2asm/Compiler.go b/src/ssa2asm/Compiler.go index ba6394e..17959c9 100644 --- a/src/ssa2asm/Compiler.go +++ b/src/ssa2asm/Compiler.go @@ -3,11 +3,13 @@ package ssa2asm import ( "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/ssa" ) type Compiler struct { - UniqueName string - Assembler asm.Assembler - CPU *cpu.CPU - Count Count + UniqueName string + Assembler asm.Assembler + ValueToStep map[ssa.Value]*Step + CPU *cpu.CPU + Count Count } \ No newline at end of file diff --git a/src/ssa2asm/CreateSteps.go b/src/ssa2asm/CreateSteps.go new file mode 100644 index 0000000..3a9e558 --- /dev/null +++ b/src/ssa2asm/CreateSteps.go @@ -0,0 +1,103 @@ +package ssa2asm + +import ( + "slices" + + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/ssa" +) + +func (f *Compiler) CreateSteps(ir ssa.IR) []Step { + count := ir.CountValues() + steps := make([]Step, count) + f.ValueToStep = make(map[ssa.Value]*Step, count) + + for i, instr := range ir.Values { + steps[i].Index = i + steps[i].Value = instr + steps[i].Register = -1 + f.ValueToStep[instr] = &steps[i] + } + + for i, instr := range ir.Values { + switch instr := instr.(type) { + case *ssa.Call: + offset := 0 + + for r, param := range instr.Arguments[1:] { + structure, isStruct := param.(*ssa.Struct) + + if isStruct { + for _, field := range structure.Arguments { + f.ValueToStep[field].Hint(f.CPU.Call[offset+r]) + offset++ + } + + offset-- + } else { + f.ValueToStep[param].Hint(f.CPU.Call[offset+r]) + } + } + + case *ssa.CallExtern: + for r, param := range instr.Arguments[1:] { + f.ValueToStep[param].Hint(f.CPU.ExternCall[r]) + } + + case *ssa.Parameter: + f.ValueToStep[instr].Register = f.CPU.Call[instr.Index] + + case *ssa.Return: + for r, param := range instr.Arguments { + f.ValueToStep[param].Hint(f.CPU.Return[r]) + } + + case *ssa.Syscall: + for r, param := range slices.Backward(instr.Arguments) { + f.ValueToStep[param].Hint(f.CPU.Syscall[r]) + } + } + + users := instr.Users() + + if len(users) == 0 { + continue + } + + liveStart := i + liveEnd := f.ValueToStep[users[len(users)-1]].Index + instrStep := f.ValueToStep[instr] + + for live := liveStart; live < liveEnd; live++ { + steps[live].Live = append(steps[live].Live, instrStep) + } + } + + for _, step := range steps { + for liveIndex, live := range step.Live { + if live.Register == -1 { + continue + } + + for _, existing := range step.Live[:liveIndex] { + if existing.Register == -1 { + continue + } + + if existing.Register == live.Register { + a := existing.Index + b := live.Index + freeRegister := cpu.Register(15) + + if a < b { + existing.Register = freeRegister + } else { + live.Register = freeRegister + } + } + } + } + } + + return steps +} \ No newline at end of file diff --git a/src/ssa2asm/ValueToRegister.go b/src/ssa2asm/Exec.go similarity index 50% rename from src/ssa2asm/ValueToRegister.go rename to src/ssa2asm/Exec.go index 89c4d51..9d109ed 100644 --- a/src/ssa2asm/ValueToRegister.go +++ b/src/ssa2asm/Exec.go @@ -1,25 +1,19 @@ package ssa2asm import ( - "slices" "strings" "git.urbach.dev/cli/q/src/asm" - "git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/ssa" ) -// ValueToRegister moves a value into the given `destination` register. -func (f *Compiler) ValueToRegister(instr ssa.Value, destination cpu.Register) { - switch instr := instr.(type) { +func (f *Compiler) Exec(step *Step) { + switch instr := step.Value.(type) { case *ssa.BinaryOp: - f.ValueToRegister(instr.Left, destination) - f.ValueToRegister(instr.Right, 7) - f.Assembler.Append(&asm.AddRegisterRegister{ - Destination: destination, - Source: destination, - Operand: 7, + Destination: step.Register, + Source: f.ValueToStep[instr.Left].Register, + Operand: f.ValueToStep[instr.Right].Register, }) case *ssa.Bytes: @@ -28,106 +22,129 @@ func (f *Compiler) ValueToRegister(instr ssa.Value, destination cpu.Register) { f.Assembler.SetData(label.Name, instr.Bytes) f.Assembler.Append(&asm.MoveRegisterLabel{ - Destination: destination, + Destination: step.Register, Label: label.Name, }) case *ssa.Call: - fn := instr.Arguments[0].(*ssa.Function) args := instr.Arguments[1:] offset := 0 - for i := range slices.Backward(args) { + for i, arg := range args { structure, isStruct := args[i].(*ssa.Struct) if isStruct { for _, field := range structure.Arguments { - f.ValueToRegister(field, f.CPU.Call[offset+i]) - i++ + if f.ValueToStep[field].Register != f.CPU.Call[offset+i] { + f.Assembler.Append(&asm.MoveRegisterRegister{ + Destination: f.CPU.Call[offset+i], + Source: f.ValueToStep[field].Register, + }) + } + + offset++ } + + offset-- } else { - f.ValueToRegister(args[i], f.CPU.Call[offset+i]) + if f.ValueToStep[arg].Register != f.CPU.Call[offset+i] { + f.Assembler.Append(&asm.MoveRegisterRegister{ + Destination: f.CPU.Call[offset+i], + Source: f.ValueToStep[arg].Register, + }) + } } } + fn := instr.Arguments[0].(*ssa.Function) f.Assembler.Append(&asm.Call{Label: fn.UniqueName}) - if destination == f.CPU.Return[0] { + if step.Register == -1 || step.Register == f.CPU.Return[0] { return } f.Assembler.Append(&asm.MoveRegisterRegister{ - Destination: destination, + Destination: step.Register, Source: f.CPU.Return[0], }) case *ssa.CallExtern: - fn := instr.Arguments[0].(*ssa.Function) args := instr.Arguments[1:] - for i := range slices.Backward(args) { - f.ValueToRegister(args[i], f.CPU.ExternCall[i]) + for i, arg := range args { + if f.ValueToStep[arg].Register != f.CPU.ExternCall[i] { + f.Assembler.Append(&asm.MoveRegisterRegister{ + Destination: f.CPU.ExternCall[i], + Source: f.ValueToStep[arg].Register, + }) + } } + fn := instr.Arguments[0].(*ssa.Function) dot := strings.IndexByte(fn.UniqueName, '.') library := fn.UniqueName[:dot] function := fn.UniqueName[dot+1:] f.Assembler.Append(&asm.CallExtern{Library: library, Function: function}) - if destination == f.CPU.Return[0] { + if step.Register == -1 || step.Register == f.CPU.Return[0] { return } f.Assembler.Append(&asm.MoveRegisterRegister{ - Destination: destination, + Destination: step.Register, Source: f.CPU.Return[0], }) case *ssa.Int: f.Assembler.Append(&asm.MoveRegisterNumber{ - Destination: destination, + Destination: step.Register, Number: instr.Int, }) case *ssa.Parameter: source := f.CPU.Call[instr.Index] - if source == destination { + if step.Register == -1 || step.Register == source { return } f.Assembler.Append(&asm.MoveRegisterRegister{ - Destination: destination, + Destination: step.Register, Source: source, }) case *ssa.Field: - parameter := instr.Struct.(*ssa.Parameter) + parameter := instr.Object.(*ssa.Parameter) field := instr.Field source := f.CPU.Call[parameter.Index+field.Index] - if source == destination { + if step.Register == -1 || step.Register == source { return } f.Assembler.Append(&asm.MoveRegisterRegister{ - Destination: destination, + Destination: step.Register, Source: source, }) case *ssa.Syscall: - for i := range slices.Backward(instr.Arguments) { - f.ValueToRegister(instr.Arguments[i], f.CPU.Syscall[i]) + for i, arg := range instr.Arguments { + if f.ValueToStep[arg].Register != f.CPU.Syscall[i] { + f.Assembler.Append(&asm.MoveRegisterRegister{ + Destination: f.CPU.Syscall[i], + Source: f.ValueToStep[arg].Register, + }) + } } f.Assembler.Append(&asm.Syscall{}) - if destination == f.CPU.Return[0] { + if step.Register == -1 || step.Register == f.CPU.Return[0] { return } f.Assembler.Append(&asm.MoveRegisterRegister{ - Destination: destination, + Destination: step.Register, Source: f.CPU.Return[0], }) } diff --git a/src/ssa2asm/GenerateAssembly.go b/src/ssa2asm/GenerateAssembly.go index 0337fa5..671d8cf 100644 --- a/src/ssa2asm/GenerateAssembly.go +++ b/src/ssa2asm/GenerateAssembly.go @@ -1,8 +1,6 @@ package ssa2asm import ( - "slices" - "git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/ssa" ) @@ -11,33 +9,21 @@ import ( func (f *Compiler) GenerateAssembly(ir ssa.IR, isLeaf bool) { f.Assembler.Append(&asm.Label{Name: f.UniqueName}) - if !isLeaf && f.UniqueName != "core.init" { + if !isLeaf && f.UniqueName != "run.init" { f.Assembler.Append(&asm.FunctionStart{}) } - for instr := range ir.Values { - if len(instr.Users()) > 0 { - continue - } + steps := f.CreateSteps(ir) - switch instr := instr.(type) { - case *ssa.Call, *ssa.CallExtern, *ssa.Syscall: - f.ValueToRegister(instr, f.CPU.Return[0]) - - case *ssa.Return: - for i := range slices.Backward(instr.Arguments) { - f.ValueToRegister(instr.Arguments[i], f.CPU.Return[i]) - } - - f.Assembler.Append(&asm.Return{}) - } + for _, step := range steps { + f.Exec(&step) } - if !isLeaf && f.UniqueName != "core.init" { + if !isLeaf && f.UniqueName != "run.init" { f.Assembler.Append(&asm.FunctionEnd{}) } - if f.UniqueName != "core.exit" { + if f.UniqueName != "os.exit" { f.Assembler.Append(&asm.Return{}) } } \ No newline at end of file diff --git a/src/ssa2asm/Step.go b/src/ssa2asm/Step.go new file mode 100644 index 0000000..734c03c --- /dev/null +++ b/src/ssa2asm/Step.go @@ -0,0 +1,26 @@ +package ssa2asm + +import ( + "git.urbach.dev/cli/q/src/cpu" + "git.urbach.dev/cli/q/src/ssa" +) + +type Step struct { + Index int + Value ssa.Value + Live []*Step + Hints []cpu.Register + Register cpu.Register +} + +func (s *Step) Hint(reg cpu.Register) { + if len(s.Hints) == 0 { + s.Register = reg + } + + s.Hints = append(s.Hints, reg) +} + +func (s *Step) String() string { + return s.Value.String() +} \ No newline at end of file diff --git a/tests/sum.q b/tests/sum.q index abd95d6..ca99c6c 100644 --- a/tests/sum.q +++ b/tests/sum.q @@ -1,8 +1,10 @@ +import os + main() { t1 := sum(1, 2) t2 := sum(3, 4) t3 := sum(t1, t2) - syscall(60, t3) + os.exit(t3) } sum(a int, b int) -> int {