This commit is contained in:
parent
1a9381ed1b
commit
e5aa006623
7 changed files with 120 additions and 138 deletions
|
@ -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
58
src/core/Decompose.go
Normal 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
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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]
|
||||||
}
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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{})
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue