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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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
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],
})
}

View file

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

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() {
t1 := sum(1, 2)
t2 := sum(3, 4)
t3 := sum(t1, t2)
syscall(60, t3)
os.exit(t3)
}
sum(a int, b int) -> int {