diff --git a/compiler/compiler.go b/compiler/compiler.go index a4c6f026..afc8eb9d 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1344,9 +1344,9 @@ func (b *builder) createBuiltin(argTypes []types.Type, argValues []llvm.Value, c // // This is also where compiler intrinsics are implemented. func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) { - if instr.IsInvoke() { - fnCast, args := b.getInvokeCall(instr) - return b.createCall(fnCast, args, ""), nil + var params []llvm.Value + for _, param := range instr.Args { + params = append(params, b.getValue(param)) } // Try to call the function directly for trivially static calls. @@ -1417,12 +1417,20 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) } else if call, ok := instr.Value.(*ssa.Builtin); ok { // Builtin function (append, close, delete, etc.).) var argTypes []types.Type - var argValues []llvm.Value for _, arg := range instr.Args { argTypes = append(argTypes, arg.Type()) - argValues = append(argValues, b.getValue(arg)) } - return b.createBuiltin(argTypes, argValues, call.Name(), instr.Pos()) + return b.createBuiltin(argTypes, params, call.Name(), instr.Pos()) + } else if instr.IsInvoke() { + // Interface method call (aka invoke call). + itf := b.getValue(instr.Value) // interface value (runtime._interface) + typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode") + value := b.CreateExtractValue(itf, 1, "invoke.func.value") // receiver + // Prefix the params with receiver value and suffix with typecode. + params = append([]llvm.Value{value}, params...) + params = append(params, typecode) + callee = b.getInvokeFunction(instr) + context = llvm.Undef(b.i8ptrType) } else { // Function pointer. value := b.getValue(instr.Value) @@ -1432,11 +1440,6 @@ func (b *builder) createFunctionCall(instr *ssa.CallCommon) (llvm.Value, error) b.createNilCheck(instr.Value, callee, "fpcall") } - var params []llvm.Value - for _, param := range instr.Args { - params = append(params, b.getValue(param)) - } - if !exported { // This function takes a context parameter. // Add it to the end of the parameter list. diff --git a/compiler/defer.go b/compiler/defer.go index f8ebb9f0..0687f4d1 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -331,10 +331,10 @@ func (b *builder) createRunDefers() { //Pass context forwardParams = append(forwardParams, context) } else { - // Isolate the typecode. - typecode := forwardParams[0] - forwardParams = forwardParams[1:] - fnPtr = b.getInvokePtr(callback, typecode) + // Move typecode from the start to the end of the list of + // parameters. + forwardParams = append(forwardParams[1:], forwardParams[0]) + fnPtr = b.getInvokeFunction(callback) // Add the context parameter. An interface call cannot also be a // closure but we have to supply the parameter anyway for platforms diff --git a/compiler/interface.go b/compiler/interface.go index 1657a371..07ed0bb7 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -47,6 +47,7 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { var length int64 var methodSet llvm.Value var ptrTo llvm.Value + var typeAssert llvm.Value switch typ := typ.(type) { case *types.Named: references = c.getTypeCode(typ.Underlying()) @@ -69,6 +70,9 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { } if _, ok := typ.Underlying().(*types.Interface); !ok { methodSet = c.getTypeMethodSet(typ) + } else { + typeAssert = c.getInterfaceImplementsFunc(typ) + typeAssert = llvm.ConstPtrToInt(typeAssert, c.uintptrType) } if _, ok := typ.Underlying().(*types.Pointer); !ok { ptrTo = c.getTypeCode(types.NewPointer(typ)) @@ -87,6 +91,9 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { if !ptrTo.IsNil() { globalValue = llvm.ConstInsertValue(globalValue, ptrTo, []uint32{3}) } + if !typeAssert.IsNil() { + globalValue = llvm.ConstInsertValue(globalValue, typeAssert, []uint32{4}) + } global.SetInitializer(globalValue) global.SetLinkage(llvm.LinkOnceODRLinkage) global.SetGlobalConstant(true) @@ -193,7 +200,11 @@ func getTypeCodeName(t types.Type) string { case *types.Interface: methods := make([]string, t.NumMethods()) for i := 0; i < t.NumMethods(); i++ { - methods[i] = t.Method(i).Name() + ":" + getTypeCodeName(t.Method(i).Type()) + name := t.Method(i).Name() + if !token.IsExported(name) { + name = t.Method(i).Pkg().Path() + "." + name + } + methods[i] = name + ":" + getTypeCodeName(t.Method(i).Type()) } return "interface:" + "{" + strings.Join(methods, ",") + "}" case *types.Map: @@ -306,10 +317,9 @@ func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value { return llvm.ConstGEP(global, []llvm.Value{zero, zero}) } -// getMethodSignature returns a global variable which is a reference to an -// external *i8 indicating the indicating the signature of this method. It is -// used during the interface lowering pass. -func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value { +// getMethodSignatureName returns a unique name (that can be used as the name of +// a global) for the given method. +func (c *compilerContext) getMethodSignatureName(method *types.Func) string { signature := methodSignature(method) var globalName string if token.IsExported(method.Name()) { @@ -317,6 +327,14 @@ func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value { } else { globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature } + return globalName +} + +// getMethodSignature returns a global variable which is a reference to an +// external *i8 indicating the indicating the signature of this method. It is +// used during the interface lowering pass. +func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value { + globalName := c.getMethodSignatureName(method) signatureGlobal := c.mod.NamedGlobal(globalName) if signatureGlobal.IsNil() { // TODO: put something useful in these globals, such as the method @@ -345,15 +363,16 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value { commaOk := llvm.Value{} if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok { // Type assert on interface type. - // This pseudo call will be lowered in the interface lowering pass to a - // real call which checks whether the provided typecode is any of the - // concrete types that implements this interface. + // This is a call to an interface type assert function. + // The interface lowering pass will define this function by filling it + // with a type switch over all concrete types that implement this + // interface, and returning whether it's one of the matched types. // This is very different from how interface asserts are implemented in // the main Go compiler, where the runtime checks whether the type // implements each method of the interface. See: // https://research.swtch.com/interfaces - methodSet := b.getInterfaceMethodSet(expr.AssertedType) - commaOk = b.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "") + fn := b.getInterfaceImplementsFunc(expr.AssertedType) + commaOk = b.CreateCall(fn, []llvm.Value{actualTypeNum}, "") } else { globalName := "reflect/types.typeid:" + getTypeCodeName(expr.AssertedType) @@ -420,39 +439,50 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value { } } -// getInvokePtr creates an interface function pointer lookup for the specified invoke instruction, using a specified typecode. -func (b *builder) getInvokePtr(instr *ssa.CallCommon, typecode llvm.Value) llvm.Value { - llvmFnType := b.getRawFuncType(instr.Method.Type().(*types.Signature)) - values := []llvm.Value{ - typecode, - b.getInterfaceMethodSet(instr.Value.Type()), - b.getMethodSignature(instr.Method), +// getMethodsString returns a string to be used in the "tinygo-methods" string +// attribute for interface functions. +func (c *compilerContext) getMethodsString(itf *types.Interface) string { + methods := make([]string, itf.NumMethods()) + for i := range methods { + methods[i] = c.getMethodSignatureName(itf.Method(i)) } - fn := b.createRuntimeCall("interfaceMethod", values, "invoke.func") - return b.CreateIntToPtr(fn, llvmFnType, "invoke.func.cast") + return strings.Join(methods, "; ") } -// getInvokeCall creates and returns the function pointer and parameters of an -// interface call. -func (b *builder) getInvokeCall(instr *ssa.CallCommon) (llvm.Value, []llvm.Value) { - // Call an interface method with dynamic dispatch. - itf := b.getValue(instr.Value) // interface - - typecode := b.CreateExtractValue(itf, 0, "invoke.typecode") - fnCast := b.getInvokePtr(instr, typecode) - receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver") - - args := []llvm.Value{receiverValue} - for _, arg := range instr.Args { - args = append(args, b.getValue(arg)) +// getInterfaceImplementsfunc returns a declared function that works as a type +// switch. The interface lowering pass will define this function. +func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) llvm.Value { + fnName := getTypeCodeName(assertedType.Underlying()) + ".$typeassert" + llvmFn := c.mod.NamedFunction(fnName) + if llvmFn.IsNil() { + llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.uintptrType}, false) + llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) + methods := c.getMethodsString(assertedType.Underlying().(*types.Interface)) + llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods)) } - // Add the context parameter. An interface call never takes a context but we - // have to supply the parameter anyway. - args = append(args, llvm.Undef(b.i8ptrType)) - // Add the parent goroutine handle. - args = append(args, llvm.Undef(b.i8ptrType)) + return llvmFn +} - return fnCast, args +// getInvokeFunction returns the thunk to call the given interface method. The +// thunk is declared, not defined: it will be defined by the interface lowering +// pass. +func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value { + fnName := getTypeCodeName(instr.Value.Type().Underlying()) + "." + instr.Method.Name() + "$invoke" + llvmFn := c.mod.NamedFunction(fnName) + if llvmFn.IsNil() { + sig := instr.Method.Type().(*types.Signature) + var paramTuple []*types.Var + for i := 0; i < sig.Params().Len(); i++ { + paramTuple = append(paramTuple, sig.Params().At(i)) + } + paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.Uintptr])) + llvmFnType := c.getRawFuncType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false)).ElementType() + llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) + llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method))) + methods := c.getMethodsString(instr.Value.Type().Underlying().(*types.Interface)) + llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods)) + } + return llvmFn } // getInterfaceInvokeWrapper returns a wrapper for the given method so it can be diff --git a/compiler/testdata/interface.go b/compiler/testdata/interface.go index 049869e2..2833ff77 100644 --- a/compiler/testdata/interface.go +++ b/compiler/testdata/interface.go @@ -49,6 +49,15 @@ func isStringer(itf interface{}) bool { return ok } +type fooInterface interface { + String() string + foo(int) byte +} + +func callFooMethod(itf fooInterface) uint8 { + return itf.foo(3) +} + func callErrorMethod(itf error) string { return itf.Error() } diff --git a/compiler/testdata/interface.ll b/compiler/testdata/interface.ll index 2c5d64e3..21d00fdb 100644 --- a/compiler/testdata/interface.ll +++ b/compiler/testdata/interface.ll @@ -3,25 +3,24 @@ source_filename = "interface.go" target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target triple = "wasm32-unknown-wasi" -%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID* } +%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 } %runtime.interfaceMethodInfo = type { i8*, i32 } %runtime._interface = type { i32, i8* } %runtime._string = type { i8*, i32 } -@"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* null, i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:basic:int" } -@"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null } -@"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null } -@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:named:error" } -@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" } +@"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* null, i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:basic:int", i32 0 } +@"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 } +@"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:named:error", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 } +@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:named:error", i32 ptrtoint (i1 (i32)* @"interface:{Error:func:{}{basic:string}}.$typeassert" to i32) } +@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}", i32 ptrtoint (i1 (i32)* @"interface:{Error:func:{}{basic:string}}.$typeassert" to i32) } @"reflect/methods.Error() string" = linkonce_odr constant i8 0, align 1 @"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"] -@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null } -@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null } -@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" } +@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 } +@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 } +@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{String() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", i32 ptrtoint (i1 (i32)* @"interface:{String:func:{}{basic:string}}.$typeassert" to i32) } @"reflect/methods.String() string" = linkonce_odr constant i8 0, align 1 @"reflect/types.interface:interface{String() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.String() string"] @"reflect/types.typeid:basic:int" = external constant i8 -@"error$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"] declare noalias nonnull i8* @runtime.alloc(i32, i8*, i8*) @@ -49,12 +48,16 @@ entry: ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:named:error" to i32), i8* null } } +declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i32) #1 + ; Function Attrs: nounwind define hidden %runtime._interface @main.anonymousInterfaceType(i8* %context, i8* %parentHandle) unnamed_addr #0 { entry: ret %runtime._interface { i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" to i32), i8* null } } +declare i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(i32) #2 + ; Function Attrs: nounwind define hidden i1 @main.isInt(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 { entry: @@ -73,7 +76,7 @@ declare i1 @runtime.typeAssert(i32, i8* dereferenceable_or_null(1), i8*, i8*) ; Function Attrs: nounwind define hidden i1 @main.isError(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 { entry: - %0 = call i1 @runtime.interfaceImplements(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* undef, i8* null) #0 + %0 = call i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i32 %itf.typecode) #0 br i1 %0, label %typeassert.ok, label %typeassert.next typeassert.ok: ; preds = %entry @@ -83,12 +86,10 @@ typeassert.next: ; preds = %typeassert.ok, %ent ret i1 %0 } -declare i1 @runtime.interfaceImplements(i32, i8** dereferenceable_or_null(4), i8*, i8*) - ; Function Attrs: nounwind define hidden i1 @main.isStringer(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 { entry: - %0 = call i1 @runtime.interfaceImplements(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"reflect/types.interface:interface{String() string}$interface", i32 0, i32 0), i8* undef, i8* null) #0 + %0 = call i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(i32 %itf.typecode) #0 br i1 %0, label %typeassert.ok, label %typeassert.next typeassert.ok: ; preds = %entry @@ -98,15 +99,26 @@ typeassert.next: ; preds = %typeassert.ok, %ent ret i1 %0 } +; Function Attrs: nounwind +define hidden i8 @main.callFooMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 { +entry: + %0 = call i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(i8* %itf.value, i32 3, i32 %itf.typecode, i8* undef, i8* undef) #0 + ret i8 %0 +} + +declare i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(i8*, i32, i32, i8*, i8*) #3 + ; Function Attrs: nounwind define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) unnamed_addr #0 { entry: - %invoke.func = call i32 @runtime.interfaceMethod(i32 %itf.typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"error$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Error() string", i8* undef, i8* null) #0 - %invoke.func.cast = inttoptr i32 %invoke.func to %runtime._string (i8*, i8*, i8*)* - %0 = call %runtime._string %invoke.func.cast(i8* %itf.value, i8* undef, i8* undef) #0 + %0 = call %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(i8* %itf.value, i32 %itf.typecode, i8* undef, i8* undef) #0 ret %runtime._string %0 } -declare i32 @runtime.interfaceMethod(i32, i8** dereferenceable_or_null(4), i8* dereferenceable_or_null(1), i8*, i8*) +declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(i8*, i32, i8*, i8*) #4 attributes #0 = { nounwind } +attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" } +attributes #2 = { "tinygo-methods"="reflect/methods.String() string" } +attributes #3 = { "tinygo-invoke"="main.$methods.foo(int) byte" "tinygo-methods"="reflect/methods.String() string; main.$methods.foo(int) byte" } +attributes #4 = { "tinygo-invoke"="reflect/methods.Error() string" "tinygo-methods"="reflect/methods.Error() string" } diff --git a/interp/interpreter.go b/interp/interpreter.go index db121cc7..7fe5052d 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -378,7 +378,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } else { locals[inst.localIndex] = literalValue{uint8(0)} } - case callFn.name == "runtime.interfaceImplements": + case strings.HasSuffix(callFn.name, ".$typeassert"): if r.debug { fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:]) } @@ -393,11 +393,9 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent return nil, mem, r.errorAt(inst, err) } methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer() - interfaceMethodSetPtr, err := operands[2].asPointer(r) - if err != nil { - return nil, mem, r.errorAt(inst, err) - } - interfaceMethodSet := mem.get(interfaceMethodSetPtr.index()).llvmGlobal.Initializer() + llvmFn := inst.llvmInst.CalledValue() + methodSetAttr := llvmFn.GetStringAttributeAtIndex(-1, "tinygo-methods") + methodSetString := methodSetAttr.GetStringValue() // Make a set of all the methods on the concrete type, for // easier checking in the next step. @@ -412,8 +410,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // of defined methods calculated above. This is the interface // assert itself. assertOk := uint8(1) // i1 true - for i := 0; i < interfaceMethodSet.Type().ArrayLength(); i++ { - name := llvm.ConstExtractValue(interfaceMethodSet, []uint32{uint32(i)}).Name() + for _, name := range strings.Split(methodSetString, "; ") { if _, ok := concreteTypeMethods[name]; !ok { // There is a method on the interface that is not // implemented by the type. The assertion will fail. @@ -423,20 +420,20 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } // If assertOk is still 1, the assertion succeeded. locals[inst.localIndex] = literalValue{assertOk} - case callFn.name == "runtime.interfaceMethod": - // This builtin returns the function (which may be a thunk) to - // invoke a method on an interface. It does not call the method. + case strings.HasSuffix(callFn.name, "$invoke"): + // This thunk is the interface method dispatcher: it is called + // with all regular parameters and a type code. It will then + // call the concrete method for it. if r.debug { - fmt.Fprintln(os.Stderr, indent+"interface method:", operands[1:]) + fmt.Fprintln(os.Stderr, indent+"invoke method:", operands[1:]) } - // Load the first param, which is the type code (ptrtoint of the - // type code global). - typecodeIDPtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem) + // Load the type code of the interface value. + typecodeIDBitCast, err := operands[len(operands)-3].toLLVMValue(inst.llvmInst.Operand(len(operands)-4).Type(), &mem) if err != nil { return nil, mem, r.errorAt(inst, err) } - typecodeID := typecodeIDPtrToInt.Operand(0).Initializer() + typecodeID := typecodeIDBitCast.Operand(0).Initializer() // Load the method set, which is part of the typecodeID object. methodSet := llvm.ConstExtractValue(typecodeID, []uint32{2}).Operand(0).Initializer() @@ -444,7 +441,10 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // We don't need to load the interface method set. // Load the signature of the to-be-called function. - signature := inst.llvmInst.Operand(2) + llvmFn := inst.llvmInst.CalledValue() + invokeAttr := llvmFn.GetStringAttributeAtIndex(-1, "tinygo-invoke") + invokeName := invokeAttr.GetStringValue() + signature := r.mod.NamedGlobal(invokeName) // Iterate through all methods, looking for the one method that // should be returned. @@ -457,9 +457,13 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } } if method.IsNil() { - return nil, mem, r.errorAt(inst, errors.New("could not find method: "+signature.Name())) + return nil, mem, r.errorAt(inst, errors.New("could not find method: "+invokeName)) } - locals[inst.localIndex] = r.getValue(method) + + // Change the to-be-called function to the underlying method to + // be called and fall through to the default case. + callFn = r.getFunction(method) + fallthrough default: if len(callFn.blocks) == 0 { // Call to a function declaration without a definition diff --git a/src/runtime/interface.go b/src/runtime/interface.go index 10faa4ec..440bc652 100644 --- a/src/runtime/interface.go +++ b/src/runtime/interface.go @@ -117,6 +117,10 @@ type typecodeID struct { // Keeping the type struct alive here is important so that values from // reflect.New (which uses reflect.PtrTo) can be used in type asserts etc. ptrTo *typecodeID + + // typeAssert is a ptrtoint of a declared interface assert function. + // It only exists to make the rtcalls pass easier. + typeAssert uintptr } // structField is used by the compiler to pass information to the interface @@ -133,11 +137,3 @@ type structField struct { // asserts. Also, it is replaced with const false if this type assert can never // happen. func typeAssert(actualType uintptr, assertedType *uint8) bool - -// Pseudo function call that returns whether a given type implements all methods -// of the given interface. -func interfaceImplements(typecode uintptr, interfaceMethodSet **uint8) bool - -// Pseudo function that returns a function pointer to the method to call. -// See the interface lowering pass for how this is lowered to a real call. -func interfaceMethod(typecode uintptr, interfaceMethodSet **uint8, signature *uint8) uintptr diff --git a/transform/interface-lowering.go b/transform/interface-lowering.go index af3aa8c2..e1dfd510 100644 --- a/transform/interface-lowering.go +++ b/transform/interface-lowering.go @@ -3,32 +3,27 @@ package transform // This file provides function to lower interface intrinsics to their final LLVM // form, optimizing them in the process. // -// During SSA construction, the following pseudo-calls are created: +// During SSA construction, the following pseudo-call is created (see +// src/runtime/interface.go): // runtime.typeAssert(typecode, assertedType) -// runtime.interfaceImplements(typecode, interfaceMethodSet) -// runtime.interfaceMethod(typecode, interfaceMethodSet, signature) -// See src/runtime/interface.go for details. -// These calls are to declared but not defined functions, so the optimizer will -// leave them alone. +// Additionally, interface type asserts and interface invoke functions are +// declared but not defined, so the optimizer will leave them alone. // -// This pass lowers the above functions to their final form: +// This pass lowers these functions to their final form: // // typeAssert: // Replaced with an icmp instruction so it can be directly used in a type // switch. This is very easy to optimize for LLVM: it will often translate a // type switch into a regular switch statement. // -// interfaceImplements: -// This call is translated into a call that checks whether the underlying -// type is one of the types implementing this interface. +// interface type assert: +// These functions are defined by creating a big type switch over all the +// concrete types implementing this interface. // -// interfaceMethod: -// This call is replaced with a call to a function that calls the -// appropriate method depending on the underlying type. -// When there is only one type implementing this interface, this call is -// translated into a direct call of that method. -// When there is no type implementing this interface, this code is marked -// unreachable as there is no way such an interface could be constructed. +// interface invoke: +// These functions are defined with a similar type switch, but instead of +// checking for the appropriate type, these functions will call the +// underlying method instead. // // Note that this way of implementing interfaces is very different from how the // main Go compiler implements them. For more details on how the main Go @@ -45,29 +40,10 @@ import ( // any method in particular. type signatureInfo struct { name string - global llvm.Value methods []*methodInfo interfaces []*interfaceInfo } -// methodName takes a method name like "func String()" and returns only the -// name, which is "String" in this case. -func (s *signatureInfo) methodName() string { - var methodName string - if strings.HasPrefix(s.name, "reflect/methods.") { - methodName = s.name[len("reflect/methods."):] - } else if idx := strings.LastIndex(s.name, ".$methods."); idx >= 0 { - methodName = s.name[idx+len(".$methods."):] - } else { - panic("could not find method name") - } - if openingParen := strings.IndexByte(methodName, '('); openingParen < 0 { - panic("no opening paren in signature name") - } else { - return methodName[:openingParen] - } -} - // methodInfo describes a single method on a concrete type. type methodInfo struct { *signatureInfo @@ -98,21 +74,9 @@ func (t *typeInfo) getMethod(signature *signatureInfo) *methodInfo { // interfaceInfo keeps information about a Go interface type, including all // methods it has. type interfaceInfo struct { - name string // name with $interface suffix - methodSet llvm.Value // global which this interfaceInfo describes - signatures []*signatureInfo // method set - types []*typeInfo // types this interface implements - assertFunc llvm.Value // runtime.interfaceImplements replacement - methodFuncs map[*signatureInfo]llvm.Value // runtime.interfaceMethod replacements for each signature -} - -// id removes the $interface suffix from the name and returns the clean -// interface name including import path. -func (itf *interfaceInfo) id() string { - if !strings.HasSuffix(itf.name, "$interface") { - panic("interface type does not have $interface suffix: " + itf.name) - } - return itf.name[:len(itf.name)-len("$interface")] + name string // "tinygo-methods" attribute + signatures map[string]*signatureInfo // method set + types []*typeInfo // types this interface implements } // lowerInterfacesPass keeps state related to the interface lowering pass. The @@ -172,25 +136,26 @@ func (p *lowerInterfacesPass) run() error { } } - // Find all interface method calls. - interfaceMethod := p.mod.NamedFunction("runtime.interfaceMethod") - interfaceMethodUses := getUses(interfaceMethod) - for _, use := range interfaceMethodUses { - methodSet := use.Operand(1).Operand(0) - name := methodSet.Name() - if _, ok := p.interfaces[name]; !ok { - p.addInterface(methodSet) + // Find all interface type asserts and interface method thunks. + var interfaceAssertFunctions []llvm.Value + var interfaceInvokeFunctions []llvm.Value + for fn := p.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { + methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods") + if methodsAttr.IsNil() { + continue } - } - - // Find all interface type asserts. - interfaceImplements := p.mod.NamedFunction("runtime.interfaceImplements") - interfaceImplementsUses := getUses(interfaceImplements) - for _, use := range interfaceImplementsUses { - methodSet := use.Operand(1).Operand(0) - name := methodSet.Name() - if _, ok := p.interfaces[name]; !ok { - p.addInterface(methodSet) + if !hasUses(fn) { + // Don't bother defining this function. + continue + } + p.addInterface(methodsAttr.GetStringValue()) + invokeAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-invoke") + if invokeAttr.IsNil() { + // Type assert. + interfaceAssertFunctions = append(interfaceAssertFunctions, fn) + } else { + // Interface invoke. + interfaceInvokeFunctions = append(interfaceInvokeFunctions, fn) } } @@ -254,63 +219,20 @@ func (p *lowerInterfacesPass) run() error { }) } - // Replace all interface methods with their uses, if possible. - for _, use := range interfaceMethodUses { - typecode := use.Operand(0) - signature := p.signatures[use.Operand(2).Name()] - - methodSet := use.Operand(1).Operand(0) // global variable - itf := p.interfaces[methodSet.Name()] - - // Delegate calling the right function to a special wrapper function. - inttoptrs := getUses(use) - if len(inttoptrs) != 1 || inttoptrs[0].IsAIntToPtrInst().IsNil() { - return errorAt(use, "internal error: expected exactly one inttoptr use of runtime.interfaceMethod") - } - inttoptr := inttoptrs[0] - calls := getUses(inttoptr) - for _, call := range calls { - // Set up parameters for the call. First copy the regular params... - params := make([]llvm.Value, call.OperandsCount()) - paramTypes := make([]llvm.Type, len(params)) - for i := 0; i < len(params)-1; i++ { - params[i] = call.Operand(i) - paramTypes[i] = params[i].Type() - } - // then add the typecode to the end of the list. - params[len(params)-1] = typecode - paramTypes[len(params)-1] = p.uintptrType - - // Create a function that redirects the call to the destination - // call, after selecting the right concrete type. - redirector := p.getInterfaceMethodFunc(itf, signature, call.Type(), paramTypes) - - // Replace the old lookup/inttoptr/call with the new call. - p.builder.SetInsertPointBefore(call) - retval := p.builder.CreateCall(redirector, append(params, llvm.ConstNull(llvm.PointerType(p.ctx.Int8Type(), 0))), "") - if retval.Type().TypeKind() != llvm.VoidTypeKind { - call.ReplaceAllUsesWith(retval) - } - call.EraseFromParentAsInstruction() - } - inttoptr.EraseFromParentAsInstruction() - use.EraseFromParentAsInstruction() + // Define all interface invoke thunks. + for _, fn := range interfaceInvokeFunctions { + methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods") + invokeAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-invoke") + itf := p.interfaces[methodsAttr.GetStringValue()] + signature := itf.signatures[invokeAttr.GetStringValue()] + p.defineInterfaceMethodFunc(fn, itf, signature) } - // Replace all typeasserts on interface types with matches on their concrete - // types, if possible. - for _, use := range interfaceImplementsUses { - actualType := use.Operand(0) - - methodSet := use.Operand(1).Operand(0) // global variable - itf := p.interfaces[methodSet.Name()] - // Create a function that does a type switch on all available types - // that implement this interface. - fn := p.getInterfaceImplementsFunc(itf) - p.builder.SetInsertPointBefore(use) - commaOk := p.builder.CreateCall(fn, []llvm.Value{actualType}, "typeassert.ok") - use.ReplaceAllUsesWith(commaOk) - use.EraseFromParentAsInstruction() + // Define all interface type assert functions. + for _, fn := range interfaceAssertFunctions { + methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods") + itf := p.interfaces[methodsAttr.GetStringValue()] + p.defineInterfaceImplementsFunc(fn, itf) } // Replace each type assert with an actual type comparison or (if the type @@ -337,11 +259,14 @@ func (p *lowerInterfacesPass) run() error { } // Remove all method sets, which are now unnecessary and inhibit later - // optimizations if they are left in place. + // optimizations if they are left in place. Also remove references to the + // interface type assert functions just to be sure. + zeroUintptr := llvm.ConstNull(p.uintptrType) for _, t := range p.types { initializer := t.typecode.Initializer() methodSet := llvm.ConstExtractValue(initializer, []uint32{2}) initializer = llvm.ConstInsertValue(initializer, llvm.ConstNull(methodSet.Type()), []uint32{2}) + initializer = llvm.ConstInsertValue(initializer, zeroUintptr, []uint32{4}) t.typecode.SetInitializer(initializer) } @@ -366,7 +291,7 @@ func (p *lowerInterfacesPass) addTypeMethods(t *typeInfo, methodSet llvm.Value) signatureGlobal := llvm.ConstExtractValue(methodData, []uint32{0}) signatureName := signatureGlobal.Name() function := llvm.ConstExtractValue(methodData, []uint32{1}).Operand(0) - signature := p.getSignature(signatureName, signatureGlobal) + signature := p.getSignature(signatureName) method := &methodInfo{ function: function, signatureInfo: signature, @@ -378,53 +303,43 @@ func (p *lowerInterfacesPass) addTypeMethods(t *typeInfo, methodSet llvm.Value) // addInterface reads information about an interface, which is the // fully-qualified name and the signatures of all methods it has. -func (p *lowerInterfacesPass) addInterface(methodSet llvm.Value) { - name := methodSet.Name() - t := &interfaceInfo{ - name: name, - methodSet: methodSet, +func (p *lowerInterfacesPass) addInterface(methodsString string) { + if _, ok := p.interfaces[methodsString]; ok { + return } - p.interfaces[name] = t - methodSet = methodSet.Initializer() // get global value from getelementptr - for i := 0; i < methodSet.Type().ArrayLength(); i++ { - signatureGlobal := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)}) - signatureName := signatureGlobal.Name() - signature := p.getSignature(signatureName, signatureGlobal) + t := &interfaceInfo{ + name: methodsString, + signatures: make(map[string]*signatureInfo), + } + p.interfaces[methodsString] = t + for _, method := range strings.Split(methodsString, "; ") { + signature := p.getSignature(method) signature.interfaces = append(signature.interfaces, t) - t.signatures = append(t.signatures, signature) + t.signatures[method] = signature } } // getSignature returns a new *signatureInfo, creating it if it doesn't already // exist. -func (p *lowerInterfacesPass) getSignature(name string, global llvm.Value) *signatureInfo { +func (p *lowerInterfacesPass) getSignature(name string) *signatureInfo { if _, ok := p.signatures[name]; !ok { p.signatures[name] = &signatureInfo{ - name: name, - global: global, + name: name, } } return p.signatures[name] } -// getInterfaceImplementsFunc returns a function that checks whether a given -// interface type implements a given interface, by checking all possible types -// that implement this interface. +// defineInterfaceImplementsFunc defines the interface type assert function. It +// checks whether the given interface type (passed as an argument) is one of the +// types it implements. // // The type match is implemented using an if/else chain over all possible types. // This if/else chain is easily converted to a big switch over all possible // types by the LLVM simplifycfg pass. -func (p *lowerInterfacesPass) getInterfaceImplementsFunc(itf *interfaceInfo) llvm.Value { - if !itf.assertFunc.IsNil() { - return itf.assertFunc - } - +func (p *lowerInterfacesPass) defineInterfaceImplementsFunc(fn llvm.Value, itf *interfaceInfo) { // Create the function and function signature. // TODO: debug info - fnName := itf.id() + "$typeassert" - fnType := llvm.FunctionType(p.ctx.Int1Type(), []llvm.Type{p.uintptrType}, false) - fn := llvm.AddFunction(p.mod, fnName, fnType) - itf.assertFunc = fn fn.Param(0).SetName("actualType") fn.SetLinkage(llvm.InternalLinkage) fn.SetUnnamedAddr(true) @@ -456,33 +371,21 @@ func (p *lowerInterfacesPass) getInterfaceImplementsFunc(itf *interfaceInfo) llv // Fill 'then' block (type assert was successful). p.builder.SetInsertPointAtEnd(thenBlock) p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 1, false)) - - return itf.assertFunc } -// getInterfaceMethodFunc returns a thunk for calling a method on an interface. +// defineInterfaceMethodFunc defines this thunk by calling the concrete method +// of the type that implements this interface. // // Matching the actual type is implemented using an if/else chain over all // possible types. This is later converted to a switch statement by the LLVM // simplifycfg pass. -func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signature *signatureInfo, returnType llvm.Type, paramTypes []llvm.Type) llvm.Value { - if fn, ok := itf.methodFuncs[signature]; ok { - // This function has already been created. - return fn - } - if itf.methodFuncs == nil { - // initialize the above map - itf.methodFuncs = make(map[*signatureInfo]llvm.Value) - } - - // Construct the function name, which is of the form: - // (main.Stringer).String - fnName := "(" + itf.id() + ")." + signature.methodName() - fnType := llvm.FunctionType(returnType, append(paramTypes, llvm.PointerType(p.ctx.Int8Type(), 0)), false) - fn := llvm.AddFunction(p.mod, fnName, fnType) - llvm.PrevParam(fn.LastParam()).SetName("actualType") - fn.LastParam().SetName("parentHandle") - itf.methodFuncs[signature] = fn +func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *interfaceInfo, signature *signatureInfo) { + parentHandle := fn.LastParam() + context := llvm.PrevParam(parentHandle) + actualType := llvm.PrevParam(context) + context.SetName("context") + actualType.SetName("actualType") + parentHandle.SetName("parentHandle") fn.SetLinkage(llvm.InternalLinkage) fn.SetUnnamedAddr(true) if p.sizeLevel >= 2 { @@ -494,17 +397,20 @@ func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signatu // Collect the params that will be passed to the functions to call. // These params exclude the receiver (which may actually consist of multiple // parts). - params := make([]llvm.Value, fn.ParamsCount()-3) + params := make([]llvm.Value, fn.ParamsCount()-4) for i := range params { params[i] = fn.Param(i + 1) } + params = append(params, + llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)), + llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)), + ) // Start chain in the entry block. entry := p.ctx.AddBasicBlock(fn, "entry") p.builder.SetInsertPointAtEnd(entry) // Define all possible functions that can be called. - actualType := llvm.PrevParam(fn.LastParam()) for _, typ := range itf.types { // Create type check (if/else). bb := p.ctx.AddBasicBlock(fn, typ.name) @@ -562,6 +468,4 @@ func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signatu llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)), }, "") p.builder.CreateUnreachable() - - return fn } diff --git a/transform/rtcalls.go b/transform/rtcalls.go index c10b3c28..49f7f0d9 100644 --- a/transform/rtcalls.go +++ b/transform/rtcalls.go @@ -109,77 +109,64 @@ func OptimizeReflectImplements(mod llvm.Module) { if implementsSignature.IsNil() { return } - interfaceMethod := mod.NamedFunction("runtime.interfaceMethod") - if interfaceMethod.IsNil() { - return - } - interfaceImplements := mod.NamedFunction("runtime.interfaceImplements") - if interfaceImplements.IsNil() { - return - } builder := mod.Context().NewBuilder() defer builder.Dispose() // Get a few useful object for use later. - zero := llvm.ConstInt(mod.Context().Int32Type(), 0, false) uintptrType := mod.Context().IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8) - defer llvm.VerifyModule(mod, llvm.PrintMessageAction) - - for _, use := range getUses(implementsSignature) { - if use.IsACallInst().IsNil() { + // Look up the (reflect.Value).Implements() method. + var implementsFunc llvm.Value + for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { + attr := fn.GetStringAttributeAtIndex(-1, "tinygo-invoke") + if attr.IsNil() { continue } - if use.CalledValue() != interfaceMethod { - continue - } - for _, bitcast := range getUses(use) { - if !bitcast.IsABitCastInst().IsNil() { - continue - } - for _, call := range getUses(bitcast) { - // Try to get the interface method set. - interfaceTypeBitCast := call.Operand(2) - if interfaceTypeBitCast.IsAConstantExpr().IsNil() || interfaceTypeBitCast.Opcode() != llvm.BitCast { - continue - } - interfaceType := interfaceTypeBitCast.Operand(0) - if strings.HasPrefix(interfaceType.Name(), "reflect/types.type:named:") { - // Get the underlying type. - interfaceType = llvm.ConstExtractValue(interfaceType.Initializer(), []uint32{0}) - } - if !strings.HasPrefix(interfaceType.Name(), "reflect/types.type:interface:") { - // This is an error. The Type passed to Implements should be - // of interface type. Ignore it here (don't report it), it - // will be reported at runtime. - continue - } - if interfaceType.IsAGlobalVariable().IsNil() { - // Interface is unknown at compile time. This can't be - // optimized. - continue - } - // Get the 'references' field of the runtime.typecodeID, which - // is a bitcast of an interface method set. - interfaceMethodSet := llvm.ConstExtractValue(interfaceType.Initializer(), []uint32{0}).Operand(0) - - builder.SetInsertPointBefore(call) - implements := builder.CreateCall(interfaceImplements, []llvm.Value{ - builder.CreatePtrToInt(call.Operand(0), uintptrType, ""), // typecode to check - llvm.ConstGEP(interfaceMethodSet, []llvm.Value{zero, zero}), // method set to check against - llvm.Undef(llvm.PointerType(mod.Context().Int8Type(), 0)), - llvm.Undef(llvm.PointerType(mod.Context().Int8Type(), 0)), - }, "") - call.ReplaceAllUsesWith(implements) - call.EraseFromParentAsInstruction() - } - if !hasUses(bitcast) { - bitcast.EraseFromParentAsInstruction() - } - } - if !hasUses(use) { - use.EraseFromParentAsInstruction() + if attr.GetStringValue() == "reflect/methods.Implements(reflect.Type) bool" { + implementsFunc = fn + break } } + if implementsFunc.IsNil() { + // Doesn't exist in the program, so nothing to do. + return + } + + for _, call := range getUses(implementsFunc) { + if call.IsACallInst().IsNil() { + continue + } + interfaceTypeBitCast := call.Operand(2) + if interfaceTypeBitCast.IsAConstantExpr().IsNil() || interfaceTypeBitCast.Opcode() != llvm.BitCast { + // The asserted interface is not constant, so can't optimize this + // code. + continue + } + + interfaceType := interfaceTypeBitCast.Operand(0) + if strings.HasPrefix(interfaceType.Name(), "reflect/types.type:named:") { + // Get the underlying type. + interfaceType = llvm.ConstExtractValue(interfaceType.Initializer(), []uint32{0}) + } + if !strings.HasPrefix(interfaceType.Name(), "reflect/types.type:interface:") { + // This is an error. The Type passed to Implements should be of + // interface type. Ignore it here (don't report it), it will be + // reported at runtime. + continue + } + if interfaceType.IsAGlobalVariable().IsNil() { + // Interface is unknown at compile time. This can't be optimized. + continue + } + typeAssertFunction := llvm.ConstExtractValue(interfaceType.Initializer(), []uint32{4}).Operand(0) + + // Replace Implements call with the type assert call. + builder.SetInsertPointBefore(call) + implements := builder.CreateCall(typeAssertFunction, []llvm.Value{ + builder.CreatePtrToInt(call.Operand(0), uintptrType, ""), // typecode to check + }, "") + call.ReplaceAllUsesWith(implements) + call.EraseFromParentAsInstruction() + } } diff --git a/transform/testdata/interface.ll b/transform/testdata/interface.ll index 2101725a..14ab6be8 100644 --- a/transform/testdata/interface.ll +++ b/transform/testdata/interface.ll @@ -1,7 +1,7 @@ target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" target triple = "armv7m-none-eabi" -%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* } +%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 } %runtime.interfaceMethodInfo = type { i8*, i32 } @"reflect/types.type:basic:uint8" = private constant %runtime.typecodeID zeroinitializer @@ -9,15 +9,11 @@ target triple = "armv7m-none-eabi" @"reflect/types.typeid:basic:int16" = external constant i8 @"reflect/types.type:basic:int" = private constant %runtime.typecodeID zeroinitializer @"reflect/methods.NeverImplementedMethod()" = linkonce_odr constant i8 0 -@"Unmatched$interface" = private constant [1 x i8*] [i8* @"reflect/methods.NeverImplementedMethod()"] @"reflect/methods.Double() int" = linkonce_odr constant i8 0 -@"Doubler$interface" = private constant [1 x i8*] [i8* @"reflect/methods.Double() int"] -@"Number$methodset" = private constant [1 x %runtime.interfaceMethodInfo] [%runtime.interfaceMethodInfo { i8* @"reflect/methods.Double() int", i32 ptrtoint (i32 (i8*, i8*)* @"(Number).Double$invoke" to i32) }] -@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([1 x %runtime.interfaceMethodInfo], [1 x %runtime.interfaceMethodInfo]* @"Number$methodset", i32 0, i32 0) } +@"Number$methodset" = private constant [1 x %runtime.interfaceMethodInfo] [%runtime.interfaceMethodInfo { i8* @"reflect/methods.Double() int", i32 ptrtoint (i32 (i8*, i8*, i8*)* @"(Number).Double$invoke" to i32) }] +@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([1 x %runtime.interfaceMethodInfo], [1 x %runtime.interfaceMethodInfo]* @"Number$methodset", i32 0, i32 0), %runtime.typecodeID* null, i32 0 } -declare i1 @runtime.interfaceImplements(i32, i8**) declare i1 @runtime.typeAssert(i32, i8*) -declare i32 @runtime.interfaceMethod(i32, i8**, i8*) declare void @runtime.printuint8(i8) declare void @runtime.printint16(i16) declare void @runtime.printint32(i32) @@ -34,7 +30,7 @@ define void @printInterfaces() { } define void @printInterface(i32 %typecode, i8* %value) { - %isUnmatched = call i1 @runtime.interfaceImplements(i32 %typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"Unmatched$interface", i32 0, i32 0)) + %isUnmatched = call i1 @Unmatched$typeassert(i32 %typecode) br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched typeswitch.Unmatched: @@ -44,13 +40,11 @@ typeswitch.Unmatched: ret void typeswitch.notUnmatched: - %isDoubler = call i1 @runtime.interfaceImplements(i32 %typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"Doubler$interface", i32 0, i32 0)) + %isDoubler = call i1 @Doubler$typeassert(i32 %typecode) br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler typeswitch.Doubler: - %doubler.func = call i32 @runtime.interfaceMethod(i32 %typecode, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"Doubler$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Double() int") - %doubler.func.cast = inttoptr i32 %doubler.func to i32 (i8*, i8*)* - %doubler.result = call i32 %doubler.func.cast(i8* %value, i8* null) + %doubler.result = call i32 @"Doubler.Double$invoke"(i8* %value, i32 %typecode, i8* undef, i8* undef) call void @runtime.printint32(i32 %doubler.result) ret void @@ -79,13 +73,23 @@ typeswitch.notInt16: ret void } -define i32 @"(Number).Double"(i32 %receiver, i8* %parentHandle) { +define i32 @"(Number).Double"(i32 %receiver, i8* %context, i8* %parentHandle) { %ret = mul i32 %receiver, 2 ret i32 %ret } -define i32 @"(Number).Double$invoke"(i8* %receiverPtr, i8* %parentHandle) { +define i32 @"(Number).Double$invoke"(i8* %receiverPtr, i8* %context, i8* %parentHandle) { %receiver = ptrtoint i8* %receiverPtr to i32 - %ret = call i32 @"(Number).Double"(i32 %receiver, i8* null) + %ret = call i32 @"(Number).Double"(i32 %receiver, i8* undef, i8* null) ret i32 %ret } + +declare i32 @"Doubler.Double$invoke"(i8* %receiver, i32 %typecode, i8* %context, i8* %parentHandle) #0 + +declare i1 @Doubler$typeassert(i32 %typecode) #1 + +declare i1 @Unmatched$typeassert(i32 %typecode) #2 + +attributes #0 = { "tinygo-invoke"="reflect/methods.Double() int" "tinygo-methods"="reflect/methods.Double() int" } +attributes #1 = { "tinygo-methods"="reflect/methods.Double() int" } +attributes #2 = { "tinygo-methods"="reflect/methods.NeverImplementedMethod()" } diff --git a/transform/testdata/interface.out.ll b/transform/testdata/interface.out.ll index 69ccfb6d..f0115c54 100644 --- a/transform/testdata/interface.out.ll +++ b/transform/testdata/interface.out.ll @@ -1,12 +1,12 @@ target datalayout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" target triple = "armv7m-none-eabi" -%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* } +%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 } %runtime.interfaceMethodInfo = type { i8*, i32 } @"reflect/types.type:basic:uint8" = private constant %runtime.typecodeID zeroinitializer @"reflect/types.type:basic:int" = private constant %runtime.typecodeID zeroinitializer -@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null } +@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 0 } declare void @runtime.printuint8(i8) @@ -28,8 +28,8 @@ define void @printInterfaces() { } define void @printInterface(i32 %typecode, i8* %value) { - %typeassert.ok1 = call i1 @"Unmatched$typeassert"(i32 %typecode) - br i1 %typeassert.ok1, label %typeswitch.Unmatched, label %typeswitch.notUnmatched + %isUnmatched = call i1 @"Unmatched$typeassert"(i32 %typecode) + br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched typeswitch.Unmatched: ; preds = %0 %unmatched = ptrtoint i8* %value to i32 @@ -38,17 +38,17 @@ typeswitch.Unmatched: ; preds = %0 ret void typeswitch.notUnmatched: ; preds = %0 - %typeassert.ok = call i1 @"Doubler$typeassert"(i32 %typecode) - br i1 %typeassert.ok, label %typeswitch.Doubler, label %typeswitch.notDoubler + %isDoubler = call i1 @"Doubler$typeassert"(i32 %typecode) + br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler typeswitch.Doubler: ; preds = %typeswitch.notUnmatched - %1 = call i32 @"(Doubler).Double"(i8* %value, i8* null, i32 %typecode, i8* null) - call void @runtime.printint32(i32 %1) + %doubler.result = call i32 @"Doubler.Double$invoke"(i8* %value, i32 %typecode, i8* undef, i8* undef) + call void @runtime.printint32(i32 %doubler.result) ret void typeswitch.notDoubler: ; preds = %typeswitch.notUnmatched - %typeassert.ok2 = icmp eq i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:uint8" to i32), %typecode - br i1 %typeassert.ok2, label %typeswitch.byte, label %typeswitch.notByte + %typeassert.ok = icmp eq i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:uint8" to i32), %typecode + br i1 %typeassert.ok, label %typeswitch.byte, label %typeswitch.notByte typeswitch.byte: ; preds = %typeswitch.notDoubler %byte = ptrtoint i8* %value to i8 @@ -69,32 +69,32 @@ typeswitch.notInt16: ; preds = %typeswitch.notByte ret void } -define i32 @"(Number).Double"(i32 %receiver, i8* %parentHandle) { +define i32 @"(Number).Double"(i32 %receiver, i8* %context, i8* %parentHandle) { %ret = mul i32 %receiver, 2 ret i32 %ret } -define i32 @"(Number).Double$invoke"(i8* %receiverPtr, i8* %parentHandle) { +define i32 @"(Number).Double$invoke"(i8* %receiverPtr, i8* %context, i8* %parentHandle) { %receiver = ptrtoint i8* %receiverPtr to i32 - %ret = call i32 @"(Number).Double"(i32 %receiver, i8* null) + %ret = call i32 @"(Number).Double"(i32 %receiver, i8* undef, i8* null) ret i32 %ret } -define internal i32 @"(Doubler).Double"(i8* %0, i8* %1, i32 %actualType, i8* %parentHandle) unnamed_addr { +define internal i32 @"Doubler.Double$invoke"(i8* %receiver, i32 %actualType, i8* %context, i8* %parentHandle) unnamed_addr #0 { entry: %"named:Number.icmp" = icmp eq i32 %actualType, ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32) br i1 %"named:Number.icmp", label %"named:Number", label %"named:Number.next" "named:Number": ; preds = %entry - %2 = call i32 @"(Number).Double$invoke"(i8* %0, i8* %1) - ret i32 %2 + %0 = call i32 @"(Number).Double$invoke"(i8* %receiver, i8* undef, i8* undef) + ret i32 %0 "named:Number.next": ; preds = %entry call void @runtime.nilPanic(i8* undef, i8* undef) unreachable } -define internal i1 @"Doubler$typeassert"(i32 %actualType) unnamed_addr { +define internal i1 @"Doubler$typeassert"(i32 %actualType) unnamed_addr #1 { entry: %"named:Number.icmp" = icmp eq i32 %actualType, ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32) br i1 %"named:Number.icmp", label %then, label %"named:Number.next" @@ -106,10 +106,14 @@ then: ; preds = %entry ret i1 false } -define internal i1 @"Unmatched$typeassert"(i32 %actualType) unnamed_addr { +define internal i1 @"Unmatched$typeassert"(i32 %actualType) unnamed_addr #2 { entry: ret i1 false then: ; No predecessors! ret i1 true } + +attributes #0 = { "tinygo-invoke"="reflect/methods.Double() int" "tinygo-methods"="reflect/methods.Double() int" } +attributes #1 = { "tinygo-methods"="reflect/methods.Double() int" } +attributes #2 = { "tinygo-methods"="reflect/methods.NeverImplementedMethod()" } diff --git a/transform/testdata/reflect-implements.ll b/transform/testdata/reflect-implements.ll index 29f38243..a7630220 100644 --- a/transform/testdata/reflect-implements.ll +++ b/transform/testdata/reflect-implements.ll @@ -1,24 +1,20 @@ target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128" target triple = "i686--linux" -%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* } +%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 } %runtime.interfaceMethodInfo = type { i8*, i32 } -@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null } -@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null } +@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 ptrtoint (i1 (i32)* @"error.$typeassert" to i32) } +@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 ptrtoint (i1 (i32)* @"error.$typeassert" to i32) } @"reflect/methods.Error() string" = linkonce_odr constant i8 0 @"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"] @"reflect/methods.Align() int" = linkonce_odr constant i8 0 @"reflect/methods.Implements(reflect.Type) bool" = linkonce_odr constant i8 0 @"reflect.Type$interface" = linkonce_odr constant [2 x i8*] [i8* @"reflect/methods.Align() int", i8* @"reflect/methods.Implements(reflect.Type) bool"] -@"reflect/types.type:named:reflect.rawType" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:uintptr", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([20 x %runtime.interfaceMethodInfo], [20 x %runtime.interfaceMethodInfo]* @"reflect.rawType$methodset", i32 0, i32 0) } +@"reflect/types.type:named:reflect.rawType" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:uintptr", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([20 x %runtime.interfaceMethodInfo], [20 x %runtime.interfaceMethodInfo]* @"reflect.rawType$methodset", i32 0, i32 0), %runtime.typecodeID* null, i32 0 } @"reflect.rawType$methodset" = linkonce_odr constant [20 x %runtime.interfaceMethodInfo] zeroinitializer @"reflect/types.type:basic:uintptr" = linkonce_odr constant %runtime.typecodeID zeroinitializer -declare i1 @runtime.interfaceImplements(i32, i8**, i8*, i8*) - -declare i32 @runtime.interfaceMethod(i32, i8**, i8*, i8*, i8*) - ; var errorType = reflect.TypeOf((*error)(nil)).Elem() ; func isError(typ reflect.Type) bool { ; return typ.Implements(errorType) @@ -28,9 +24,7 @@ declare i32 @runtime.interfaceMethod(i32, i8**, i8*, i8*, i8*) ; known at compile time (after the interp pass has run). define i1 @main.isError(i32 %typ.typecode, i8* %typ.value, i8* %context, i8* %parentHandle) { entry: - %invoke.func = call i32 @runtime.interfaceMethod(i32 %typ.typecode, i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"reflect.Type$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Implements(reflect.Type) bool", i8* undef, i8* null) - %invoke.func.cast = inttoptr i32 %invoke.func to i1 (i8*, i32, i8*, i8*, i8*)* - %result = call i1 %invoke.func.cast(i8* %typ.value, i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:reflect.rawType" to i32), i8* bitcast (%runtime.typecodeID* @"reflect/types.type:named:error" to i8*), i8* undef, i8* undef) + %result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:reflect.rawType" to i32), i8* bitcast (%runtime.typecodeID* @"reflect/types.type:named:error" to i8*), i32 %typ.typecode, i8* undef, i8* undef) ret i1 %result } @@ -41,8 +35,12 @@ entry: ; } define i1 @main.isUnknown(i32 %typ.typecode, i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) { entry: - %invoke.func = call i32 @runtime.interfaceMethod(i32 %typ.typecode, i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"reflect.Type$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Implements(reflect.Type) bool", i8* undef, i8* null) - %invoke.func.cast = inttoptr i32 %invoke.func to i1 (i8*, i32, i8*, i8*, i8*)* - %result = call i1 %invoke.func.cast(i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* undef, i8* undef) + %result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i32 %typ.typecode, i8* undef, i8* undef) ret i1 %result } + +declare i1 @"reflect.Type.Implements$invoke"(i8*, i32, i8*, i32, i8*, i8*) #0 +declare i1 @"error.$typeassert"(i32) #1 + +attributes #0 = { "tinygo-invoke"="reflect/methods.Implements(reflect.Type) bool" "tinygo-methods"="reflect/methods.Align() int; reflect/methods.Implements(reflect.Type) bool" } +attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" } diff --git a/transform/testdata/reflect-implements.out.ll b/transform/testdata/reflect-implements.out.ll index f1bc92c1..70199ef5 100644 --- a/transform/testdata/reflect-implements.out.ll +++ b/transform/testdata/reflect-implements.out.ll @@ -1,35 +1,36 @@ target datalayout = "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-f64:32:64-f80:32-n8:16:32-S128" target triple = "i686--linux" -%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* } +%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo*, %runtime.typecodeID*, i32 } %runtime.interfaceMethodInfo = type { i8*, i32 } -@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null } -@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null } +@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 ptrtoint (i1 (i32)* @"error.$typeassert" to i32) } +@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* bitcast ([1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface" to %runtime.typecodeID*), i32 0, %runtime.interfaceMethodInfo* null, %runtime.typecodeID* null, i32 ptrtoint (i1 (i32)* @"error.$typeassert" to i32) } @"reflect/methods.Error() string" = linkonce_odr constant i8 0 @"reflect/types.interface:interface{Error() string}$interface" = linkonce_odr constant [1 x i8*] [i8* @"reflect/methods.Error() string"] @"reflect/methods.Align() int" = linkonce_odr constant i8 0 @"reflect/methods.Implements(reflect.Type) bool" = linkonce_odr constant i8 0 @"reflect.Type$interface" = linkonce_odr constant [2 x i8*] [i8* @"reflect/methods.Align() int", i8* @"reflect/methods.Implements(reflect.Type) bool"] -@"reflect/types.type:named:reflect.rawType" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:uintptr", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([20 x %runtime.interfaceMethodInfo], [20 x %runtime.interfaceMethodInfo]* @"reflect.rawType$methodset", i32 0, i32 0) } +@"reflect/types.type:named:reflect.rawType" = linkonce_odr constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:uintptr", i32 0, %runtime.interfaceMethodInfo* getelementptr inbounds ([20 x %runtime.interfaceMethodInfo], [20 x %runtime.interfaceMethodInfo]* @"reflect.rawType$methodset", i32 0, i32 0), %runtime.typecodeID* null, i32 0 } @"reflect.rawType$methodset" = linkonce_odr constant [20 x %runtime.interfaceMethodInfo] zeroinitializer @"reflect/types.type:basic:uintptr" = linkonce_odr constant %runtime.typecodeID zeroinitializer -declare i1 @runtime.interfaceImplements(i32, i8**, i8*, i8*) - -declare i32 @runtime.interfaceMethod(i32, i8**, i8*, i8*, i8*) - define i1 @main.isError(i32 %typ.typecode, i8* %typ.value, i8* %context, i8* %parentHandle) { entry: %0 = ptrtoint i8* %typ.value to i32 - %1 = call i1 @runtime.interfaceImplements(i32 %0, i8** getelementptr inbounds ([1 x i8*], [1 x i8*]* @"reflect/types.interface:interface{Error() string}$interface", i32 0, i32 0), i8* undef, i8* undef) + %1 = call i1 @"error.$typeassert"(i32 %0) ret i1 %1 } define i1 @main.isUnknown(i32 %typ.typecode, i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* %context, i8* %parentHandle) { entry: - %invoke.func = call i32 @runtime.interfaceMethod(i32 %typ.typecode, i8** getelementptr inbounds ([2 x i8*], [2 x i8*]* @"reflect.Type$interface", i32 0, i32 0), i8* nonnull @"reflect/methods.Implements(reflect.Type) bool", i8* undef, i8* null) - %invoke.func.cast = inttoptr i32 %invoke.func to i1 (i8*, i32, i8*, i8*, i8*)* - %result = call i1 %invoke.func.cast(i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* undef, i8* undef) + %result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i32 %typ.typecode, i8* undef, i8* undef) ret i1 %result } + +declare i1 @"reflect.Type.Implements$invoke"(i8*, i32, i8*, i32, i8*, i8*) #0 + +declare i1 @"error.$typeassert"(i32) #1 + +attributes #0 = { "tinygo-invoke"="reflect/methods.Implements(reflect.Type) bool" "tinygo-methods"="reflect/methods.Align() int; reflect/methods.Implements(reflect.Type) bool" } +attributes #1 = { "tinygo-methods"="reflect/methods.Error() string" } diff --git a/transform/wasm-abi.go b/transform/wasm-abi.go index 85e41914..7c4d3700 100644 --- a/transform/wasm-abi.go +++ b/transform/wasm-abi.go @@ -38,6 +38,12 @@ func ExternalInt64AsPtr(mod llvm.Module) error { // coroutine lowering. continue } + if !fn.GetStringAttributeAtIndex(-1, "tinygo-methods").IsNil() { + // These are internal functions (interface method call, interface + // type assert) that will be lowered by the interface lowering pass. + // Don't transform them. + continue + } hasInt64 := false paramTypes := []llvm.Type{}