From 539de9db9e71d0a56a6f088699cbd64fe6fd9326 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 26 Aug 2018 23:31:29 +0200 Subject: [PATCH] Move interface method calls in Go from LLVM IR + documentation This commit moves the itfmethod call implemented directly in LLVM IR to a Go implementation in the runtime. Additionally, it fixes variable names to be more obvious and adds a lot of documentation to explain how interfaces actually work in TinyGo. Code size changes for src/examples/hello: nrf: -144 unix: -93 I'm guessing this code size reduction is a result of removing the 'noinline' function attribute. --- compiler.go | 56 ++++++++++++++++++++------------------ src/runtime/interface.go | 59 ++++++++++++++++++++++++++++++++++++++++ src/runtime/runtime.ll | 42 ---------------------------- 3 files changed, 88 insertions(+), 69 deletions(-) create mode 100644 src/runtime/interface.go diff --git a/compiler.go b/compiler.go index 478574d3..ee7ff33e 100644 --- a/compiler.go +++ b/compiler.go @@ -368,23 +368,24 @@ func (c *Compiler) Parse(mainPath string, buildTags []string) error { } // Initialize runtime type information, for interfaces. + // See src/runtime/interface.go for more details. dynamicTypes := c.ir.AllDynamicTypes() numDynamicTypes := 0 for _, meta := range dynamicTypes { numDynamicTypes += len(meta.Methods) } - tuples := make([]llvm.Value, 0, len(dynamicTypes)) + ranges := make([]llvm.Value, 0, len(dynamicTypes)) funcPointers := make([]llvm.Value, 0, numDynamicTypes) signatures := make([]llvm.Value, 0, numDynamicTypes) startIndex := 0 - tupleType := c.mod.GetTypeByName("interface_tuple") + rangeType := c.mod.GetTypeByName("runtime.methodSetRange") for _, meta := range dynamicTypes { - tupleValues := []llvm.Value{ + rangeValues := []llvm.Value{ llvm.ConstInt(llvm.Int32Type(), uint64(startIndex), false), llvm.ConstInt(llvm.Int32Type(), uint64(len(meta.Methods)), false), } - tuple := llvm.ConstNamedStruct(tupleType, tupleValues) - tuples = append(tuples, tuple) + rangeValue := llvm.ConstNamedStruct(rangeType, rangeValues) + ranges = append(ranges, rangeValue) for _, method := range meta.Methods { f := c.ir.GetFunction(program.MethodValue(method)) if f.llvmFn.IsNil() { @@ -398,33 +399,34 @@ func (c *Compiler) Parse(mainPath string, buildTags []string) error { } startIndex += len(meta.Methods) } + // Replace the pre-created arrays with the generated arrays. - tupleArray := llvm.ConstArray(tupleType, tuples) - tupleArrayNewGlobal := llvm.AddGlobal(c.mod, tupleArray.Type(), "interface_tuples.tmp") - tupleArrayNewGlobal.SetInitializer(tupleArray) - tupleArrayNewGlobal.SetLinkage(llvm.PrivateLinkage) - tupleArrayOldGlobal := c.mod.NamedGlobal("interface_tuples") - tupleArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(tupleArrayNewGlobal, tupleArrayOldGlobal.Type())) - tupleArrayOldGlobal.EraseFromParentAsGlobal() - tupleArrayNewGlobal.SetName("interface_tuples") + rangeArray := llvm.ConstArray(rangeType, ranges) + rangeArrayNewGlobal := llvm.AddGlobal(c.mod, rangeArray.Type(), "runtime.methodSetRanges.tmp") + rangeArrayNewGlobal.SetInitializer(rangeArray) + rangeArrayNewGlobal.SetLinkage(llvm.PrivateLinkage) + rangeArrayOldGlobal := c.mod.NamedGlobal("runtime.methodSetRanges") + rangeArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(rangeArrayNewGlobal, rangeArrayOldGlobal.Type())) + rangeArrayOldGlobal.EraseFromParentAsGlobal() + rangeArrayNewGlobal.SetName("runtime.methodSetRanges") funcArray := llvm.ConstArray(c.i8ptrType, funcPointers) - funcArrayNewGlobal := llvm.AddGlobal(c.mod, funcArray.Type(), "interface_functions.tmp") + funcArrayNewGlobal := llvm.AddGlobal(c.mod, funcArray.Type(), "runtime.methodSetFunctions.tmp") funcArrayNewGlobal.SetInitializer(funcArray) funcArrayNewGlobal.SetLinkage(llvm.PrivateLinkage) - funcArrayOldGlobal := c.mod.NamedGlobal("interface_functions") + funcArrayOldGlobal := c.mod.NamedGlobal("runtime.methodSetFunctions") funcArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(funcArrayNewGlobal, funcArrayOldGlobal.Type())) funcArrayOldGlobal.EraseFromParentAsGlobal() - funcArrayNewGlobal.SetName("interface_functions") + funcArrayNewGlobal.SetName("runtime.methodSetFunctions") signatureArray := llvm.ConstArray(llvm.Int32Type(), signatures) - signatureArrayNewGlobal := llvm.AddGlobal(c.mod, signatureArray.Type(), "interface_signatures.tmp") + signatureArrayNewGlobal := llvm.AddGlobal(c.mod, signatureArray.Type(), "runtime.methodSetSignatures.tmp") signatureArrayNewGlobal.SetInitializer(signatureArray) signatureArrayNewGlobal.SetLinkage(llvm.PrivateLinkage) - signatureArrayOldGlobal := c.mod.NamedGlobal("interface_signatures") + signatureArrayOldGlobal := c.mod.NamedGlobal("runtime.methodSetSignatures") signatureArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(signatureArrayNewGlobal, signatureArrayOldGlobal.Type())) signatureArrayOldGlobal.EraseFromParentAsGlobal() - signatureArrayNewGlobal.SetName("interface_signatures") + signatureArrayNewGlobal.SetName("runtime.methodSetSignatures") - c.mod.NamedGlobal("first_interface_num").SetInitializer(llvm.ConstInt(llvm.Int32Type(), uint64(c.ir.FirstDynamicType()), false)) + c.mod.NamedGlobal("runtime.firstInterfaceNum").SetInitializer(llvm.ConstInt(llvm.Int32Type(), uint64(c.ir.FirstDynamicType()), false)) return nil } @@ -461,7 +463,7 @@ func (c *Compiler) getLLVMType(goType types.Type) (llvm.Type, error) { return llvm.Type{}, errors.New("todo: unknown basic type: " + typ.String()) } case *types.Interface: - return c.mod.GetTypeByName("interface"), nil + return c.mod.GetTypeByName("runtime._interface"), nil case *types.Map: return llvm.PointerType(c.mod.GetTypeByName("runtime.hashmap"), 0), nil case *types.Named: @@ -500,7 +502,7 @@ func (c *Compiler) getLLVMType(goType types.Type) (llvm.Type, error) { if err != nil { return llvm.Type{}, err } - if recv.StructName() == "interface" { + if recv.StructName() == "runtime._interface" { recv = c.i8ptrType } paramTypes = append(paramTypes, recv) @@ -748,7 +750,7 @@ func (c *Compiler) getInterpretedValue(value Value) (llvm.Value, error) { llvm.ConstInt(llvm.Int32Type(), uint64(itfTypeNum), false), llvm.Undef(c.i8ptrType), } - itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("interface"), fields) + itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._interface"), fields) return itf, nil case *MapValue: @@ -1334,7 +1336,7 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon, parentHandle l itf, llvm.ConstInt(llvm.Int32Type(), uint64(c.ir.MethodNum(instr.Method)), false), } - fn := c.builder.CreateCall(c.mod.NamedFunction("itfmethod"), values, "invoke.func") + fn := c.builder.CreateCall(c.mod.NamedFunction("runtime.itfmethod"), values, "invoke.func") fnCast := c.builder.CreateBitCast(fn, llvmFnType, "invoke.func.cast") receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver") args := []llvm.Value{receiverValue} @@ -1501,7 +1503,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { // TODO: runtime.lookupBoundsCheck is undefined in packages imported by // package runtime, so we have to remove it. This should be fixed. lookupBoundsCheck := c.mod.NamedFunction("runtime.lookupBoundsCheck") - if !lookupBoundsCheck.IsNil() { + if !lookupBoundsCheck.IsNil() && frame.fn.llvmFn.Name() != "runtime.itfmethod" { c.builder.CreateCall(lookupBoundsCheck, []llvm.Value{buflen, index}, "") } @@ -1607,7 +1609,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { } } itfTypeNum, _ := c.ir.TypeNum(expr.X.Type()) - itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("interface"), []llvm.Value{llvm.ConstInt(llvm.Int32Type(), uint64(itfTypeNum), false), llvm.Undef(c.i8ptrType)}) + itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._interface"), []llvm.Value{llvm.ConstInt(llvm.Int32Type(), uint64(itfTypeNum), false), llvm.Undef(c.i8ptrType)}) itf = c.builder.CreateInsertValue(itf, itfValue, 1, "") return itf, nil case *ssa.MakeMap: @@ -1938,7 +1940,7 @@ func (c *Compiler) parseConst(expr *ssa.Const) (llvm.Value, error) { llvm.ConstInt(llvm.Int32Type(), uint64(itfTypeNum), false), llvm.Undef(c.i8ptrType), } - itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("interface"), fields) + itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._interface"), fields) return itf, nil case *types.Pointer: if expr.Value != nil { diff --git a/src/runtime/interface.go b/src/runtime/interface.go new file mode 100644 index 00000000..2b494bb6 --- /dev/null +++ b/src/runtime/interface.go @@ -0,0 +1,59 @@ +package runtime + +// This file implements Go interfaces. +// +// Interfaces are represented as a pair of {typecode, value}, where value can be +// anything (including non-pointers). +// +// Signatures itself are not matched on strings, but on uniqued numbers that +// contain the name and the signature of the function (to save space), think of +// signatures as interned strings at compile time. +// +// The typecode is a small number unique for the Go type. All typecodes < +// firstInterfaceNum do not have any methods and typecodes >= firstInterfaceNum +// all have at least one method. This means that methodSetRanges does not need +// to contain types without methods and is thus indexed starting at a typecode +// with number firstInterfaceNum. +// +// To further conserve some space, the methodSetRange (as the name indicates) +// doesn't contain a list of methods and function pointers directly, but instead +// just indexes into methodSetSignatures and methodSetFunctions which contains +// the mapping from uniqued signature to function pointer. + +type _interface struct { + typecode uint32 + value *uint8 +} + +// This struct indicates the range of methods in the methodSetSignatures and +// methodSetFunctions arrays that belong to this named type. +type methodSetRange struct { + index uint32 // start index into interfaceSignatures and interfaceFunctions + length uint32 // number of methods +} + +// Global constants that will be set by the compiler. The arrays are of size 0, +// which is a dummy value, but will be bigger after the compiler has filled them +// in. +var ( + methodSetRanges [0]methodSetRange // indexes into methodSetSignatures and methodSetFunctions + methodSetSignatures [0]uint32 // uniqued method ID + methodSetFunctions [0]*uint8 // function pointer of method + firstInterfaceNum uint32 // the lowest typecode that has at least one method +) + +// Get the function pointer for the method on the interface. +// This is a compiler intrinsic. +func itfmethod(itf _interface, method uint32) *uint8 { + // This function doesn't do bounds checking as the supplied method must be + // in the list of signatures. The compiler will only emit runtime.itfmethod + // calls when the method actually exists on this interface (proven by the + // typechecker). + i := methodSetRanges[itf.typecode-firstInterfaceNum].index + for { + if methodSetSignatures[i] == method { + return methodSetFunctions[i] + } + i++ + } +} diff --git a/src/runtime/runtime.ll b/src/runtime/runtime.ll index 5b797aea..67a62b49 100644 --- a/src/runtime/runtime.ll +++ b/src/runtime/runtime.ll @@ -1,7 +1,5 @@ source_filename = "runtime/runtime.ll" -%interface = type { i32, i8* } - declare void @runtime.initAll() declare void @main.main() declare i8* @main.main$async(i8*) @@ -10,15 +8,6 @@ declare void @runtime.scheduler(i8*) ; Will be changed to true if there are 'go' statements in the compiled program. @has_scheduler = private unnamed_addr constant i1 false -; Will be changed by the compiler to the first type number with methods. -@first_interface_num = private unnamed_addr constant i32 0 - -; Will be filled by the compiler with runtime type information. -%interface_tuple = type { i32, i32 } ; { index, len } -@interface_tuples = external global [0 x %interface_tuple] -@interface_signatures = external global [0 x i32] ; array of method IDs -@interface_functions = external global [0 x i8*] ; array of function pointers - define i32 @main() { call void @runtime.initAll() %has_scheduler = load i1, i1* @has_scheduler @@ -36,34 +25,3 @@ without_scheduler: call void @main.main() ret i32 0 } - -; Get the function pointer for the method on the interface. -; This function only reads constant global data and it's own arguments so it can -; be 'readnone' (a pure function). -define i8* @itfmethod(%interface %itf, i32 %method) noinline readnone { -entry: - ; Calculate the index in @interface_tuples - %concrete_type_num = extractvalue %interface %itf, 0 - %first_interface_num = load i32, i32* @first_interface_num - %index = sub i32 %concrete_type_num, %first_interface_num - - ; Calculate the index for @interface_signatures and @interface_functions - %itf_index_ptr = getelementptr inbounds [0 x %interface_tuple], [0 x %interface_tuple]* @interface_tuples, i32 0, i32 %index, i32 0 - %itf_index = load i32, i32* %itf_index_ptr - br label %find_method - - ; This is a while loop until the method has been found. - ; It must be in here, so avoid checking the length. -find_method: - %itf_index.phi = phi i32 [ %itf_index, %entry], [ %itf_index.phi.next, %find_method] - %m_ptr = getelementptr inbounds [0 x i32], [0 x i32]* @interface_signatures, i32 0, i32 %itf_index.phi - %m = load i32, i32* %m_ptr - %found = icmp eq i32 %m, %method - %itf_index.phi.next = add i32 %itf_index.phi, 1 - br i1 %found, label %found_method, label %find_method - -found_method: - %fp_ptr = getelementptr inbounds [0 x i8*], [0 x i8*]* @interface_functions, i32 0, i32 %itf_index.phi - %fp = load i8*, i8** %fp_ptr - ret i8* %fp -}