diff options
author | Ayke van Laethem <[email protected]> | 2018-09-23 02:31:16 +0200 |
---|---|---|
committer | Ayke van Laethem <[email protected]> | 2018-09-23 02:39:05 +0200 |
commit | fdfa81006035512039ddc5e86e8d6620bb80b6b4 (patch) | |
tree | 5df43e9fe25301eb991843c03b8a7d5f9da88691 /compiler/calls.go | |
parent | a86739d2351a8e68ba7f68a16179a2f0cc2d01d9 (diff) | |
download | tinygo-fdfa81006035512039ddc5e86e8d6620bb80b6b4.tar.gz tinygo-fdfa81006035512039ddc5e86e8d6620bb80b6b4.zip |
compiler: expand small structs
Use fields of small structs (3 or less fields when flattened
recursively) directly as parameter values.
Advantages:
* Code size is slightly reduced, both on unix and nrf.
* AVR can finally deal with struct parameters - at least the small
ones. examples/test now compiles. A real fix for struct parameters
should go into upstream LLVM, but this is a nice win.
fixes #20
Diffstat (limited to 'compiler/calls.go')
-rw-r--r-- | compiler/calls.go | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/compiler/calls.go b/compiler/calls.go new file mode 100644 index 000000000..dcfc4375f --- /dev/null +++ b/compiler/calls.go @@ -0,0 +1,159 @@ +package compiler + +import ( + "go/types" + + "github.com/aykevl/llvm/bindings/go/llvm" + "github.com/aykevl/tinygo/ir" + "golang.org/x/tools/go/ssa" +) + +// This file implements the calling convention used by Go. +// The calling convention is like the C calling convention (or, whatever LLVM +// makes of it) with the following modifications: +// * Struct parameters are fully expanded to individual fields (recursively), +// when the number of fields (combined) is 3 or less. +// Examples: +// {i8*, i32} -> i8*, i32 +// {{i8*, i32}, i16} -> i8*, i32, i16 +// {{i64}} -> i64 +// {i8*, i32, i8, i8} -> {i8*, i32, i8, i8} +// Note that all native Go data types that don't exist in LLVM (string, +// slice, interface, fat function pointer) can be expanded this way, making +// the work of LLVM optimizers easier. +// * Closures have an extra paramter appended at the end of the argument list, +// which is a pointer to a struct containing free variables. +// * Blocking functions have a coroutine pointer prepended to the argument +// list, see src/runtime/scheduler.go for details. + +const MaxFieldsPerParam = 3 + +func (c *Compiler) createRuntimeCall(fnName string, args []llvm.Value, name string) llvm.Value { + runtimePkg := c.ir.Program.ImportedPackage("runtime") + member := runtimePkg.Members[fnName] + if member == nil { + panic("trying to call runtime." + fnName) + } + fn := c.ir.GetFunction(member.(*ssa.Function)) + return c.createCall(fn, args, name) +} + +func (c *Compiler) createCall(fn *ir.Function, args []llvm.Value, name string) llvm.Value { + return c.createIndirectCall(fn.Signature, fn.LLVMFn, args, name) +} + +func (c *Compiler) createIndirectCall(sig *types.Signature, fn llvm.Value, args []llvm.Value, name string) llvm.Value { + expanded := make([]llvm.Value, 0, len(args)) + for _, arg := range args { + fragments := c.expandFormalParam(arg) + expanded = append(expanded, fragments...) + } + return c.builder.CreateCall(fn, expanded, name) +} + +func (c *Compiler) getLLVMParamTypes(t types.Type) ([]llvm.Type, error) { + llvmType, err := c.getLLVMType(t) + if err != nil { + return nil, err + } + return c.expandFormalParamType(llvmType), nil +} + +func (c *Compiler) expandFormalParamType(t llvm.Type) []llvm.Type { + switch t.TypeKind() { + case llvm.StructTypeKind: + fields := c.flattenAggregateType(t) + if len(fields) <= MaxFieldsPerParam { + return fields + } else { + // failed to lower + return []llvm.Type{t} + } + default: + // TODO: split small arrays + return []llvm.Type{t} + } +} + +// Convert an argument to one that can be passed in a parameter. +func (c *Compiler) expandFormalParam(v llvm.Value) []llvm.Value { + switch v.Type().TypeKind() { + case llvm.StructTypeKind: + fieldTypes := c.flattenAggregateType(v.Type()) + if len(fieldTypes) <= MaxFieldsPerParam { + fields := c.flattenAggregate(v) + if len(fields) != len(fieldTypes) { + panic("type and value param lowering don't match") + } + return fields + } else { + // failed to lower + return []llvm.Value{v} + } + default: + // TODO: split small arrays + return []llvm.Value{v} + } +} + +func (c *Compiler) flattenAggregateType(t llvm.Type) []llvm.Type { + switch t.TypeKind() { + case llvm.StructTypeKind: + fields := make([]llvm.Type, 0, len(t.Subtypes())) + for _, subfield := range t.Subtypes() { + subfields := c.flattenAggregateType(subfield) + fields = append(fields, subfields...) + } + return fields + default: + return []llvm.Type{t} + } +} + +// Break down a struct into its elementary types for argument passing. +func (c *Compiler) flattenAggregate(v llvm.Value) []llvm.Value { + switch v.Type().TypeKind() { + case llvm.StructTypeKind: + fields := make([]llvm.Value, 0, len(v.Type().Subtypes())) + for i := range v.Type().Subtypes() { + subfield := c.builder.CreateExtractValue(v, i, "") + subfields := c.flattenAggregate(subfield) + fields = append(fields, subfields...) + } + return fields + default: + return []llvm.Value{v} + } +} + +func (c *Compiler) collapseFormalParam(t llvm.Type, fields []llvm.Value) llvm.Value { + param, remaining := c.collapseFormalParamInternal(t, fields) + if len(remaining) != 0 { + panic("failed to expand back all fields") + } + return param +} + +// Returns (value, remainingFields). +func (c *Compiler) collapseFormalParamInternal(t llvm.Type, fields []llvm.Value) (llvm.Value, []llvm.Value) { + switch t.TypeKind() { + case llvm.StructTypeKind: + if len(c.flattenAggregateType(t)) <= MaxFieldsPerParam { + value, err := getZeroValue(t) + if err != nil { + panic("could not get zero value of struct: " + err.Error()) + } + for i, subtyp := range t.Subtypes() { + structField, remaining := c.collapseFormalParamInternal(subtyp, fields) + fields = remaining + value = c.builder.CreateInsertValue(value, structField, i, "") + } + return value, fields + } else { + // this struct was not flattened + return fields[0], fields[1:] + } + default: + return fields[0], fields[1:] + } +} |