From a4afc3b4b085b78d9da6038388eae54a7b1776be Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 24 Oct 2021 14:29:40 +0200 Subject: [PATCH] compiler: simplify interface lowering This commit simplifies the IR a little bit: instead of calling pseudo-functions runtime.interfaceImplements and runtime.interfaceMethod, real declared functions are being called that are then defined in the interface lowering pass. This should simplify the interaction between various transformation passes. It also reduces the number of lines of code, which is generally a good thing. --- compiler/compiler.go | 25 +- compiler/defer.go | 8 +- compiler/interface.go | 106 +++++--- compiler/testdata/interface.go | 9 + compiler/testdata/interface.ll | 48 ++-- interp/interpreter.go | 42 +-- src/runtime/interface.go | 12 +- transform/interface-lowering.go | 260 ++++++------------- transform/rtcalls.go | 111 ++++---- transform/testdata/interface.ll | 34 +-- transform/testdata/interface.out.ll | 40 +-- transform/testdata/reflect-implements.ll | 26 +- transform/testdata/reflect-implements.out.ll | 25 +- transform/wasm-abi.go | 6 + 14 files changed, 355 insertions(+), 397 deletions(-) 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{}