aboutsummaryrefslogtreecommitdiffhomepage
path: root/compiler/calls.go
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2018-09-23 02:31:16 +0200
committerAyke van Laethem <[email protected]>2018-09-23 02:39:05 +0200
commitfdfa81006035512039ddc5e86e8d6620bb80b6b4 (patch)
tree5df43e9fe25301eb991843c03b8a7d5f9da88691 /compiler/calls.go
parenta86739d2351a8e68ba7f68a16179a2f0cc2d01d9 (diff)
downloadtinygo-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.go159
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:]
+ }
+}