Implemented struct decomposition
All checks were successful
/ test (push) Successful in 22s

This commit is contained in:
Eduard Urbach 2025-07-06 13:42:51 +02:00
parent 1a9381ed1b
commit e5aa006623
Signed by: akyoto
GPG key ID: 49226B848C78F6C8
7 changed files with 120 additions and 138 deletions

View file

@ -16,7 +16,8 @@ func (f *Function) CheckDeadCode() error {
continue continue
} }
return errors.New(&UnusedValue{Value: instr.String()}, f.File, instr.(ssa.HasSource).Start()) source := instr.(ssa.HasSource)
return errors.New(&UnusedValue{Value: string(f.File.Bytes[source.Start():source.End()])}, f.File, source.Start())
} }
return nil return nil

58
src/core/Decompose.go Normal file
View file

@ -0,0 +1,58 @@
package core
import (
"git.urbach.dev/cli/q/src/errors"
"git.urbach.dev/cli/q/src/expression"
"git.urbach.dev/cli/q/src/ssa"
"git.urbach.dev/cli/q/src/types"
)
// Decompose creates SSA values from expressions and decomposes structs into their individual fields.
func (f *Function) Decompose(nodes []*expression.Expression, typeCheck []*ssa.Parameter) ([]ssa.Value, error) {
args := make([]ssa.Value, 0, len(nodes))
for i, node := range nodes {
value, err := f.Evaluate(node)
if err != nil {
return nil, err
}
structure, isStruct := value.(*ssa.Struct)
if isStruct {
for _, field := range structure.Arguments {
args = append(args, field)
}
continue
}
args = append(args, value)
if typeCheck != nil && !types.Is(value.Type(), typeCheck[i].Typ) {
_, isPointer := typeCheck[i].Typ.(*types.Pointer)
if isPointer {
number, isInt := value.(*ssa.Int)
if isInt && number.Int == 0 {
continue
}
}
// Temporary hack to allow int64 -> uint32 conversion
if types.Is(value.Type(), types.AnyInt) && types.Is(typeCheck[i].Typ, types.AnyInt) {
continue
}
return nil, errors.New(&TypeMismatch{
Encountered: value.Type().Name(),
Expected: typeCheck[i].Typ.Name(),
ParameterName: typeCheck[i].Name,
}, f.File, value.(ssa.HasSource).Start())
}
}
return args, nil
}

View file

@ -2,7 +2,6 @@ package core
import ( import (
"fmt" "fmt"
"slices"
"git.urbach.dev/cli/q/src/errors" "git.urbach.dev/cli/q/src/errors"
"git.urbach.dev/cli/q/src/expression" "git.urbach.dev/cli/q/src/expression"
@ -68,11 +67,11 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
Source: ssa.Source(expr.Source), Source: ssa.Source(expr.Source),
}) })
v := f.Append(&ssa.Struct{ v := &ssa.Struct{
Arguments: []ssa.Value{pointer, length}, Arguments: []ssa.Value{pointer, length},
Typ: types.String, Typ: types.String,
Source: ssa.Source(expr.Source), Source: ssa.Source(expr.Source),
}) }
return v, nil return v, nil
} }
@ -82,31 +81,13 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
switch expr.Token.Kind { switch expr.Token.Kind {
case token.Call: case token.Call:
children := expr.Children if expr.Children[0].Token.Kind == token.Identifier && expr.Children[0].String(f.File.Bytes) == "syscall" {
isSyscall := false args, err := f.Decompose(expr.Children[1:], nil)
if children[0].Token.Kind == token.Identifier {
funcName := children[0].String(f.File.Bytes)
if funcName == "syscall" {
children = children[1:]
isSyscall = true
}
}
args := make([]ssa.Value, len(children))
for i, child := range slices.Backward(children) {
value, err := f.Evaluate(child)
if err != nil { if err != nil {
return nil, err return nil, err
} }
args[i] = value
}
if isSyscall {
syscall := &ssa.Syscall{ syscall := &ssa.Syscall{
Arguments: args, Arguments: args,
Source: ssa.Source(expr.Source), Source: ssa.Source(expr.Source),
@ -115,41 +96,29 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
return f.Append(syscall), nil return f.Append(syscall), nil
} }
name := args[0].(*ssa.Function).UniqueName funcValue, err := f.Evaluate(expr.Children[0])
fn := f.All.Functions[name]
parameters := args[1:]
if len(parameters) != len(fn.Input) { if err != nil {
return nil, errors.New(&ParameterCountMismatch{Function: name, Count: len(parameters), ExpectedCount: len(fn.Input)}, f.File, expr.Source[0].Position) return nil, err
} }
for i, param := range slices.Backward(parameters) { ssaFunc := funcValue.(*ssa.Function)
if !types.Is(param.Type(), fn.Input[i].Typ) { fn := f.All.Functions[ssaFunc.UniqueName]
_, isPointer := fn.Input[i].Typ.(*types.Pointer) inputExpressions := expr.Children[1:]
if isPointer { if len(inputExpressions) != len(fn.Input) {
number, isInt := param.(*ssa.Int) return nil, errors.New(&ParameterCountMismatch{Function: fn.UniqueName, Count: len(inputExpressions), ExpectedCount: len(fn.Input)}, f.File, expr.Source[0].Position)
}
if isInt && number.Int == 0 { args, err := f.Decompose(inputExpressions, fn.Input)
continue
}
}
// Temporary hack to allow int64 -> uint32 conversion if err != nil {
if types.Is(param.Type(), types.AnyInt) && types.Is(fn.Input[i].Typ, types.AnyInt) { return nil, err
continue
}
return nil, errors.New(&TypeMismatch{
Encountered: param.Type().Name(),
Expected: fn.Input[i].Typ.Name(),
ParameterName: fn.Input[i].Name,
}, f.File, param.(ssa.HasSource).Start())
}
} }
if fn.IsExtern() { if fn.IsExtern() {
v := f.Append(&ssa.CallExtern{Call: ssa.Call{ v := f.Append(&ssa.CallExtern{Call: ssa.Call{
Func: ssaFunc,
Arguments: args, Arguments: args,
Source: ssa.Source(expr.Source), Source: ssa.Source(expr.Source),
}}) }})
@ -158,6 +127,7 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
} }
v := f.Append(&ssa.Call{ v := f.Append(&ssa.Call{
Func: ssaFunc,
Arguments: args, Arguments: args,
Source: ssa.Source(expr.Source), Source: ssa.Source(expr.Source),
}) })
@ -176,25 +146,6 @@ func (f *Function) Evaluate(expr *expression.Expression) (ssa.Value, error) {
return identifier, nil return identifier, nil
} }
// identifier, exists := f.Identifiers[leftText]
// if exists {
// structType := identifier.Type().(*types.Struct)
// field := structType.FieldByName(rightText)
// if field == nil {
// return nil, errors.New(&UnknownStructField{StructName: structType.Name(), FieldName: rightText}, f.File, right.Token.Position)
// }
// v := f.Append(&ssa.Field{
// Object: identifier,
// Field: field,
// Source: ssa.Source(expr.Source),
// })
// return v, nil
// }
function, exists := f.All.Functions[fullName] function, exists := f.All.Functions[fullName]
if exists { if exists {

View file

@ -7,6 +7,7 @@ import (
) )
type Call struct { type Call struct {
Func *Function
Arguments Arguments
Liveness Liveness
Source Source
@ -25,15 +26,13 @@ func (a *Call) Equals(v Value) bool {
} }
func (v *Call) String() string { func (v *Call) String() string {
return fmt.Sprintf("%s(%s)", v.Arguments[0].String(), v.Arguments[1:].String()) return fmt.Sprintf("%s(%s)", v.Func.UniqueName, v.Arguments.String())
} }
func (v *Call) Type() types.Type { func (v *Call) Type() types.Type {
typ := v.Arguments[0].(*Function).Typ if len(v.Func.Typ.Output) == 0 {
if len(typ.Output) == 0 {
return types.Void return types.Void
} }
return typ.Output[0] return v.Func.Typ.Output[0]
} }

View file

@ -10,10 +10,10 @@ import (
func TestCall(t *testing.T) { func TestCall(t *testing.T) {
fn := ssa.IR{} fn := ssa.IR{}
myfunc := fn.Append(&ssa.Function{UniqueName: "myfunc", Typ: &types.Function{}}) myfunc := &ssa.Function{UniqueName: "myfunc", Typ: &types.Function{}}
call := fn.Append(&ssa.Call{Arguments: ssa.Arguments{myfunc}}) call := fn.Append(&ssa.Call{Func: myfunc, Arguments: ssa.Arguments{}})
one := fn.Append(&ssa.Int{Int: 1}) one := fn.Append(&ssa.Int{Int: 1})
call2 := fn.Append(&ssa.Call{Arguments: ssa.Arguments{myfunc, one}}) call2 := fn.Append(&ssa.Call{Func: myfunc, Arguments: ssa.Arguments{one}})
assert.True(t, call.Type() == types.Void) assert.True(t, call.Type() == types.Void)
assert.Equal(t, call.String(), "myfunc()") assert.Equal(t, call.String(), "myfunc()")
@ -23,18 +23,18 @@ func TestCall(t *testing.T) {
func TestCallEquals(t *testing.T) { func TestCallEquals(t *testing.T) {
fn := ssa.IR{} fn := ssa.IR{}
sum := fn.Append(&ssa.Function{ sum := &ssa.Function{
UniqueName: "sum", UniqueName: "sum",
Typ: &types.Function{ Typ: &types.Function{
Input: []types.Type{types.Int, types.Int}, Input: []types.Type{types.Int, types.Int},
Output: []types.Type{types.Int}, Output: []types.Type{types.Int},
}, },
}) }
one := fn.Append(&ssa.Int{Int: 1}) one := fn.Append(&ssa.Int{Int: 1})
two := fn.Append(&ssa.Int{Int: 2}) two := fn.Append(&ssa.Int{Int: 2})
call1 := fn.Append(&ssa.Call{Arguments: ssa.Arguments{sum, one, two}}) call1 := fn.Append(&ssa.Call{Func: sum, Arguments: ssa.Arguments{one, two}})
call2 := fn.Append(&ssa.Call{Arguments: ssa.Arguments{sum, one, two}}) call2 := fn.Append(&ssa.Call{Func: sum, Arguments: ssa.Arguments{one, two}})
assert.False(t, call1.Equals(one)) assert.False(t, call1.Equals(one))
assert.True(t, call1.Equals(call2)) assert.True(t, call1.Equals(call2))
@ -43,17 +43,17 @@ func TestCallEquals(t *testing.T) {
func TestCallReturnType(t *testing.T) { func TestCallReturnType(t *testing.T) {
fn := ssa.IR{} fn := ssa.IR{}
sum := fn.Append(&ssa.Function{ sum := &ssa.Function{
UniqueName: "sum", UniqueName: "sum",
Typ: &types.Function{ Typ: &types.Function{
Input: []types.Type{types.Int, types.Int}, Input: []types.Type{types.Int, types.Int},
Output: []types.Type{types.Int}, Output: []types.Type{types.Int},
}, },
}) }
one := fn.Append(&ssa.Int{Int: 1}) one := fn.Append(&ssa.Int{Int: 1})
two := fn.Append(&ssa.Int{Int: 2}) two := fn.Append(&ssa.Int{Int: 2})
call := fn.Append(&ssa.Call{Arguments: ssa.Arguments{sum, one, two}}) call := fn.Append(&ssa.Call{Func: sum, Arguments: ssa.Arguments{one, two}})
assert.Equal(t, call.String(), "sum(1, 2)") assert.Equal(t, call.String(), "sum(1, 2)")
assert.True(t, call.Type() == types.Int) assert.True(t, call.Type() == types.Int)

View file

@ -1,8 +1,6 @@
package ssa2asm package ssa2asm
import ( import (
"slices"
"git.urbach.dev/cli/q/src/cpu" "git.urbach.dev/cli/q/src/cpu"
"git.urbach.dev/cli/q/src/ssa" "git.urbach.dev/cli/q/src/ssa"
) )
@ -22,25 +20,12 @@ func (f *Compiler) CreateSteps(ir ssa.IR) []Step {
for i, instr := range ir.Values { for i, instr := range ir.Values {
switch instr := instr.(type) { switch instr := instr.(type) {
case *ssa.Call: case *ssa.Call:
offset := 0 for paramIndex, param := range instr.Arguments {
f.ValueToStep[param].Hint(f.CPU.Call.In[paramIndex])
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.In[offset+r])
offset++
}
offset--
} else {
f.ValueToStep[param].Hint(f.CPU.Call.In[offset+r])
}
} }
case *ssa.CallExtern: case *ssa.CallExtern:
for r, param := range instr.Arguments[1:] { for r, param := range instr.Arguments {
if r >= len(f.CPU.ExternCall.In) { if r >= len(f.CPU.ExternCall.In) {
// Temporary hack to allow arguments 5 and 6 to be hinted as r10 and r11, then pushed later. // Temporary hack to allow arguments 5 and 6 to be hinted as r10 and r11, then pushed later.
f.ValueToStep[param].Hint(f.CPU.ExternCall.Volatile[1+r]) f.ValueToStep[param].Hint(f.CPU.ExternCall.Volatile[1+r])
@ -59,7 +44,7 @@ func (f *Compiler) CreateSteps(ir ssa.IR) []Step {
} }
case *ssa.Syscall: case *ssa.Syscall:
for r, param := range slices.Backward(instr.Arguments) { for r, param := range instr.Arguments {
f.ValueToStep[param].Hint(f.CPU.Syscall.In[r]) f.ValueToStep[param].Hint(f.CPU.Syscall.In[r])
} }
} }

View file

@ -27,37 +27,20 @@ func (f *Compiler) Exec(step *Step) {
}) })
case *ssa.Call: case *ssa.Call:
args := instr.Arguments[1:] args := instr.Arguments
offset := 0
for i, arg := range args { for i, arg := range args {
structure, isStruct := args[i].(*ssa.Struct) if f.ValueToStep[arg].Register == f.CPU.Call.In[i] {
continue
if isStruct {
for _, field := range structure.Arguments {
if f.ValueToStep[field].Register != f.CPU.Call.In[offset+i] {
f.Assembler.Append(&asm.MoveRegisterRegister{
Destination: f.CPU.Call.In[offset+i],
Source: f.ValueToStep[field].Register,
})
}
offset++
}
offset--
} else {
if f.ValueToStep[arg].Register != f.CPU.Call.In[offset+i] {
f.Assembler.Append(&asm.MoveRegisterRegister{
Destination: f.CPU.Call.In[offset+i],
Source: f.ValueToStep[arg].Register,
})
}
} }
f.Assembler.Append(&asm.MoveRegisterRegister{
Destination: f.CPU.Call.In[i],
Source: f.ValueToStep[arg].Register,
})
} }
fn := instr.Arguments[0].(*ssa.Function) f.Assembler.Append(&asm.Call{Label: instr.Func.UniqueName})
f.Assembler.Append(&asm.Call{Label: fn.UniqueName})
if step.Register == -1 || step.Register == f.CPU.Call.Out[0] { if step.Register == -1 || step.Register == f.CPU.Call.Out[0] {
return return
@ -70,25 +53,30 @@ func (f *Compiler) Exec(step *Step) {
case *ssa.CallExtern: case *ssa.CallExtern:
f.Assembler.Append(&asm.CallExternStart{}) f.Assembler.Append(&asm.CallExternStart{})
args := instr.Arguments[1:] args := instr.Arguments
for i, arg := range args { for i, arg := range args {
if i >= len(f.CPU.ExternCall.In) { if i >= len(f.CPU.ExternCall.In) {
f.Assembler.Append(&asm.PushRegister{ f.Assembler.Append(&asm.PushRegister{
Register: f.ValueToStep[arg].Register, Register: f.ValueToStep[arg].Register,
}) })
} else if f.ValueToStep[arg].Register != f.CPU.ExternCall.In[i] {
f.Assembler.Append(&asm.MoveRegisterRegister{ continue
Destination: f.CPU.ExternCall.In[i],
Source: f.ValueToStep[arg].Register,
})
} }
if f.ValueToStep[arg].Register == f.CPU.ExternCall.In[i] {
continue
}
f.Assembler.Append(&asm.MoveRegisterRegister{
Destination: f.CPU.ExternCall.In[i],
Source: f.ValueToStep[arg].Register,
})
} }
fn := instr.Arguments[0].(*ssa.Function) dot := strings.IndexByte(instr.Func.UniqueName, '.')
dot := strings.IndexByte(fn.UniqueName, '.') library := instr.Func.UniqueName[:dot]
library := fn.UniqueName[:dot] function := instr.Func.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})
f.Assembler.Append(&asm.CallExternEnd{}) f.Assembler.Append(&asm.CallExternEnd{})