Implemented liveness analysis
All checks were successful
/ test (push) Successful in 15s

This commit is contained in:
Eduard Urbach 2025-07-04 19:06:47 +02:00
parent f357285045
commit 0ece1b092e
Signed by: akyoto
GPG key ID: 49226B848C78F6C8
23 changed files with 274 additions and 113 deletions

View file

@ -1,12 +0,0 @@
init() {
main.main()
exit()
}
exit() {
syscall(93, 0)
}
crash() {
syscall(93, 1)
}

View file

@ -1,12 +0,0 @@
init() {
main.main()
exit()
}
exit() {
syscall(60, 0)
}
crash() {
syscall(60, 1)
}

View file

@ -1,12 +0,0 @@
init() {
main.main()
exit()
}
exit() {
syscall(0x2000001, 0)
}
crash() {
syscall(0x2000001, 1)
}

3
lib/os/os_linux_arm.q Normal file
View file

@ -0,0 +1,3 @@
exit(code int) {
syscall(93, code)
}

3
lib/os/os_linux_x86.q Normal file
View file

@ -0,0 +1,3 @@
exit(code int) {
syscall(60, code)
}

3
lib/os/os_mac.q Normal file
View file

@ -0,0 +1,3 @@
exit(code int) {
syscall(0x2000001, code)
}

9
lib/os/os_windows.q Normal file
View file

@ -0,0 +1,9 @@
exit(code int) {
kernel32.ExitProcess(code)
}
extern {
kernel32 {
ExitProcess(code uint)
}
}

10
lib/run/run_unix.q Normal file
View file

@ -0,0 +1,10 @@
import os
init() {
main.main()
os.exit(0)
}
crash() {
os.exit(1)
}

View file

@ -1,23 +1,20 @@
import os
init() { init() {
utf8 := 65001 utf8 := 65001
kernel32.SetConsoleCP(utf8) kernel32.SetConsoleCP(utf8)
kernel32.SetConsoleOutputCP(utf8) kernel32.SetConsoleOutputCP(utf8)
main.main() main.main()
exit() os.exit(0)
}
exit() {
kernel32.ExitProcess(0)
} }
crash() { crash() {
kernel32.ExitProcess(1) os.exit(1)
} }
extern { extern {
kernel32 { kernel32 {
SetConsoleCP(cp uint) SetConsoleCP(cp uint)
SetConsoleOutputCP(cp uint) SetConsoleOutputCP(cp uint)
ExitProcess(code uint)
} }
} }

View file

@ -18,7 +18,7 @@ func Compile(b *build.Build) (*core.Environment, error) {
} }
// Check for existence of `init` // Check for existence of `init`
init, exists := all.Functions["core.init"] init, exists := all.Functions["run.init"]
if !exists { if !exists {
return nil, MissingInitFunction return nil, MissingInitFunction

View file

@ -7,7 +7,7 @@ import (
// CheckDeadCode checks for dead values. // CheckDeadCode checks for dead values.
func (f *Function) CheckDeadCode() error { func (f *Function) CheckDeadCode() error {
for instr := range f.Values { for _, instr := range f.Values {
if !instr.IsConst() { if !instr.IsConst() {
continue continue
} }

View file

@ -180,7 +180,7 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
} }
v := f.Append(&ssa.Field{ v := f.Append(&ssa.Field{
Struct: identifier, Object: identifier,
Field: field, Field: field,
Source: ssa.Source(expr.Source), Source: ssa.Source(expr.Source),
}) })

View file

@ -7,5 +7,9 @@ type Register int8
// String returns the human readable name of the register. // String returns the human readable name of the register.
func (r Register) String() string { func (r Register) String() string {
if r < 0 {
return "r?"
}
return fmt.Sprintf("r%d", r) return fmt.Sprintf("r%d", r)
} }

View file

@ -20,7 +20,7 @@ func WriteFile(executable string, b *build.Build, env *core.Environment) error {
return err return err
} }
init := env.Functions["core.init"] init := env.Functions["run.init"]
traversed := make(map[*core.Function]bool, len(env.Functions)) traversed := make(map[*core.Function]bool, len(env.Functions))
final := asm.Assembler{ final := asm.Assembler{

View file

@ -19,7 +19,7 @@ func Scan(b *build.Build) (*core.Environment, error) {
} }
go func() { go func() {
s.queueDirectory(filepath.Join(global.Library, "core"), "core") s.queueDirectory(filepath.Join(global.Library, "run"), "run")
s.queue(b.Files...) s.queue(b.Files...)
s.group.Wait() s.group.Wait()
close(s.functions) close(s.functions)

View file

@ -7,7 +7,7 @@ import (
) )
type Field struct { type Field struct {
Struct Value Object Value
Field *types.Field Field *types.Field
Liveness Liveness
Source Source
@ -15,8 +15,8 @@ type Field struct {
func (v *Field) IsConst() bool { return true } func (v *Field) IsConst() bool { return true }
func (v *Field) Type() types.Type { return v.Field.Type } 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) String() string { return fmt.Sprintf("%s.%s", v.Object, v.Field) }
func (v *Field) Inputs() []Value { return []Value{v.Struct} } func (v *Field) Inputs() []Value { return []Value{v.Object} }
func (a *Field) Equals(v Value) bool { func (a *Field) Equals(v Value) bool {
b, sameType := v.(*Field) b, sameType := v.(*Field)

View file

@ -1,5 +1,7 @@
package ssa package ssa
import "slices"
// IR is a list of basic blocks. // IR is a list of basic blocks.
type IR struct { type IR struct {
Blocks []*Block Blocks []*Block
@ -30,13 +32,24 @@ func (f *IR) Append(instr Value) Value {
return f.Blocks[len(f.Blocks)-1].Append(instr) 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. // FindExisting returns an equal instruction that's already appended or `nil` if none could be found.
func (f *IR) FindExisting(instr Value) Value { func (f *IR) FindExisting(instr Value) Value {
if !instr.IsConst() { if !instr.IsConst() {
return nil return nil
} }
for existing := range f.Values { for _, existing := range f.Values {
if existing.IsConst() && instr.Equals(existing) { if existing.IsConst() && instr.Equals(existing) {
return existing return existing
} }
@ -46,10 +59,29 @@ func (f *IR) FindExisting(instr Value) Value {
} }
// Values yields on each 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 _, block := range f.Blocks {
for _, instr := range block.Instructions { 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 return
} }
} }

View file

@ -3,11 +3,13 @@ package ssa2asm
import ( import (
"git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/asm"
"git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/cpu"
"git.urbach.dev/cli/q/src/ssa"
) )
type Compiler struct { type Compiler struct {
UniqueName string UniqueName string
Assembler asm.Assembler Assembler asm.Assembler
CPU *cpu.CPU ValueToStep map[ssa.Value]*Step
Count Count CPU *cpu.CPU
Count Count
} }

103
src/ssa2asm/CreateSteps.go Normal file
View file

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

View file

@ -1,25 +1,19 @@
package ssa2asm package ssa2asm
import ( import (
"slices"
"strings" "strings"
"git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/asm"
"git.urbach.dev/cli/q/src/cpu"
"git.urbach.dev/cli/q/src/ssa" "git.urbach.dev/cli/q/src/ssa"
) )
// ValueToRegister moves a value into the given `destination` register. func (f *Compiler) Exec(step *Step) {
func (f *Compiler) ValueToRegister(instr ssa.Value, destination cpu.Register) { switch instr := step.Value.(type) {
switch instr := instr.(type) {
case *ssa.BinaryOp: case *ssa.BinaryOp:
f.ValueToRegister(instr.Left, destination)
f.ValueToRegister(instr.Right, 7)
f.Assembler.Append(&asm.AddRegisterRegister{ f.Assembler.Append(&asm.AddRegisterRegister{
Destination: destination, Destination: step.Register,
Source: destination, Source: f.ValueToStep[instr.Left].Register,
Operand: 7, Operand: f.ValueToStep[instr.Right].Register,
}) })
case *ssa.Bytes: 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.SetData(label.Name, instr.Bytes)
f.Assembler.Append(&asm.MoveRegisterLabel{ f.Assembler.Append(&asm.MoveRegisterLabel{
Destination: destination, Destination: step.Register,
Label: label.Name, Label: label.Name,
}) })
case *ssa.Call: case *ssa.Call:
fn := instr.Arguments[0].(*ssa.Function)
args := instr.Arguments[1:] args := instr.Arguments[1:]
offset := 0 offset := 0
for i := range slices.Backward(args) { for i, arg := range args {
structure, isStruct := args[i].(*ssa.Struct) structure, isStruct := args[i].(*ssa.Struct)
if isStruct { if isStruct {
for _, field := range structure.Arguments { for _, field := range structure.Arguments {
f.ValueToRegister(field, f.CPU.Call[offset+i]) if f.ValueToStep[field].Register != f.CPU.Call[offset+i] {
i++ f.Assembler.Append(&asm.MoveRegisterRegister{
Destination: f.CPU.Call[offset+i],
Source: f.ValueToStep[field].Register,
})
}
offset++
} }
offset--
} else { } 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}) 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 return
} }
f.Assembler.Append(&asm.MoveRegisterRegister{ f.Assembler.Append(&asm.MoveRegisterRegister{
Destination: destination, Destination: step.Register,
Source: f.CPU.Return[0], Source: f.CPU.Return[0],
}) })
case *ssa.CallExtern: case *ssa.CallExtern:
fn := instr.Arguments[0].(*ssa.Function)
args := instr.Arguments[1:] args := instr.Arguments[1:]
for i := range slices.Backward(args) { for i, arg := range args {
f.ValueToRegister(args[i], f.CPU.ExternCall[i]) 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, '.') dot := strings.IndexByte(fn.UniqueName, '.')
library := fn.UniqueName[:dot] library := fn.UniqueName[:dot]
function := fn.UniqueName[dot+1:] function := fn.UniqueName[dot+1:]
f.Assembler.Append(&asm.CallExtern{Library: library, Function: function}) 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 return
} }
f.Assembler.Append(&asm.MoveRegisterRegister{ f.Assembler.Append(&asm.MoveRegisterRegister{
Destination: destination, Destination: step.Register,
Source: f.CPU.Return[0], Source: f.CPU.Return[0],
}) })
case *ssa.Int: case *ssa.Int:
f.Assembler.Append(&asm.MoveRegisterNumber{ f.Assembler.Append(&asm.MoveRegisterNumber{
Destination: destination, Destination: step.Register,
Number: instr.Int, Number: instr.Int,
}) })
case *ssa.Parameter: case *ssa.Parameter:
source := f.CPU.Call[instr.Index] source := f.CPU.Call[instr.Index]
if source == destination { if step.Register == -1 || step.Register == source {
return return
} }
f.Assembler.Append(&asm.MoveRegisterRegister{ f.Assembler.Append(&asm.MoveRegisterRegister{
Destination: destination, Destination: step.Register,
Source: source, Source: source,
}) })
case *ssa.Field: case *ssa.Field:
parameter := instr.Struct.(*ssa.Parameter) parameter := instr.Object.(*ssa.Parameter)
field := instr.Field field := instr.Field
source := f.CPU.Call[parameter.Index+field.Index] source := f.CPU.Call[parameter.Index+field.Index]
if source == destination { if step.Register == -1 || step.Register == source {
return return
} }
f.Assembler.Append(&asm.MoveRegisterRegister{ f.Assembler.Append(&asm.MoveRegisterRegister{
Destination: destination, Destination: step.Register,
Source: source, Source: source,
}) })
case *ssa.Syscall: case *ssa.Syscall:
for i := range slices.Backward(instr.Arguments) { for i, arg := range instr.Arguments {
f.ValueToRegister(instr.Arguments[i], f.CPU.Syscall[i]) 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{}) f.Assembler.Append(&asm.Syscall{})
if destination == f.CPU.Return[0] { if step.Register == -1 || step.Register == f.CPU.Return[0] {
return return
} }
f.Assembler.Append(&asm.MoveRegisterRegister{ f.Assembler.Append(&asm.MoveRegisterRegister{
Destination: destination, Destination: step.Register,
Source: f.CPU.Return[0], Source: f.CPU.Return[0],
}) })
} }

View file

@ -1,8 +1,6 @@
package ssa2asm package ssa2asm
import ( import (
"slices"
"git.urbach.dev/cli/q/src/asm" "git.urbach.dev/cli/q/src/asm"
"git.urbach.dev/cli/q/src/ssa" "git.urbach.dev/cli/q/src/ssa"
) )
@ -11,33 +9,21 @@ import (
func (f *Compiler) GenerateAssembly(ir ssa.IR, isLeaf bool) { func (f *Compiler) GenerateAssembly(ir ssa.IR, isLeaf bool) {
f.Assembler.Append(&asm.Label{Name: f.UniqueName}) 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{}) f.Assembler.Append(&asm.FunctionStart{})
} }
for instr := range ir.Values { steps := f.CreateSteps(ir)
if len(instr.Users()) > 0 {
continue
}
switch instr := instr.(type) { for _, step := range steps {
case *ssa.Call, *ssa.CallExtern, *ssa.Syscall: f.Exec(&step)
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{})
}
} }
if !isLeaf && f.UniqueName != "core.init" { if !isLeaf && f.UniqueName != "run.init" {
f.Assembler.Append(&asm.FunctionEnd{}) f.Assembler.Append(&asm.FunctionEnd{})
} }
if f.UniqueName != "core.exit" { if f.UniqueName != "os.exit" {
f.Assembler.Append(&asm.Return{}) f.Assembler.Append(&asm.Return{})
} }
} }

26
src/ssa2asm/Step.go Normal file
View file

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

View file

@ -1,8 +1,10 @@
import os
main() { main() {
t1 := sum(1, 2) t1 := sum(1, 2)
t2 := sum(3, 4) t2 := sum(3, 4)
t3 := sum(t1, t2) t3 := sum(t1, t2)
syscall(60, t3) os.exit(t3)
} }
sum(a int, b int) -> int { sum(a int, b int) -> int {