diff --git a/GNUmakefile b/GNUmakefile index b0840b6814..8dc03fb5c8 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -347,6 +347,7 @@ TEST_PACKAGES_FAST = \ go/ast \ go/format \ go/scanner \ + go/token \ go/version \ hash \ hash/adler32 \ @@ -359,6 +360,7 @@ TEST_PACKAGES_FAST = \ math/cmplx \ net/http/internal/ascii \ net/mail \ + net/url \ os \ path \ reflect \ @@ -480,7 +482,7 @@ TEST_PACKAGES_HOST := $(TEST_PACKAGES_FAST) $(TEST_PACKAGES_WINDOWS) TEST_IOFS := false endif -TEST_SKIP_FLAG := -skip='TestExtraMethods|TestParseAndBytesRoundTrip/P256/Generic' +TEST_SKIP_FLAG := -skip='TestExtraMethods|TestParseAndBytesRoundTrip/P256/Generic|TestParseQueryLimits|TestParseStrictIpv6' TEST_ADDITIONAL_FLAGS ?= # Test known-working standard library packages. diff --git a/compiler/interface.go b/compiler/interface.go index e63666834f..5f7e7e345b 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -10,6 +10,7 @@ import ( "fmt" "go/token" "go/types" + "sort" "strconv" "strings" @@ -17,6 +18,12 @@ import ( "tinygo.org/x/go-llvm" ) +// numMethodHasMethodSet is a flag in bit 15 of the numMethod field (uint16) in +// Named, Pointer, and Struct type descriptors. When set, an inline method set +// is present in the type descriptor. Must match the constant in +// src/internal/reflectlite/type.go. +const numMethodHasMethodSet = 0x8000 + // Type kinds for basic types. // They must match the constants for the Kind type in src/reflect/type.go. var basicTypes = [...]uint8{ @@ -183,6 +190,16 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { typeFieldTypes := []*types.Var{ types.NewVar(token.NoPos, nil, "kind", types.Typ[types.Int8]), } + // Compute the method set value for types that support methods. + var methods []*types.Func + for i := 0; i < ms.Len(); i++ { + methods = append(methods, ms.At(i).Obj().(*types.Func)) + } + methodSetType := types.NewStruct([]*types.Var{ + types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]), + types.NewVar(token.NoPos, nil, "methods", types.NewArray(types.Typ[types.UnsafePointer], int64(len(methods)))), + }, nil) + methodSetValue := c.getMethodSetValue(methods) switch typ := typ.(type) { case *types.Basic: typeFieldTypes = append(typeFieldTypes, @@ -199,6 +216,13 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "underlying", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "pkgpath", types.Typ[types.UnsafePointer]), + ) + if len(methods) > 0 { + typeFieldTypes = append(typeFieldTypes, + types.NewVar(token.NoPos, nil, "methods", methodSetType), + ) + } + typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "name", types.NewArray(types.Typ[types.Int8], int64(len(pkgname)+1+len(name)+1))), ) case *types.Chan: @@ -218,6 +242,11 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]), ) + if len(methods) > 0 { + typeFieldTypes = append(typeFieldTypes, + types.NewVar(token.NoPos, nil, "methods", methodSetType), + ) + } case *types.Array: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), @@ -242,11 +271,16 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { types.NewVar(token.NoPos, nil, "numFields", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "fields", types.NewArray(c.getRuntimeType("structField"), int64(typ.NumFields()))), ) + if len(methods) > 0 { + typeFieldTypes = append(typeFieldTypes, + types.NewVar(token.NoPos, nil, "methods", methodSetType), + ) + } case *types.Interface: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), + types.NewVar(token.NoPos, nil, "methods", methodSetType), ) - // TODO: methods case *types.Signature: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), @@ -292,14 +326,24 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { pkgname = pkg.Name() } pkgPathPtr := c.pkgPathPtr(pkgpath) + namedNumMethods := uint64(numMethods) + if namedNumMethods&numMethodHasMethodSet != 0 { + panic("numMethods overflow: too many exported methods on named type " + name) + } + if len(methods) > 0 { + namedNumMethods |= numMethodHasMethodSet + } typeFields = []llvm.Value{ - llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods - c.getTypeCode(types.NewPointer(typ)), // ptrTo - c.getTypeCode(typ.Underlying()), // underlying - pkgPathPtr, // pkgpath pointer - c.ctx.ConstString(pkgname+"."+name+"\x00", false), // name + llvm.ConstInt(c.ctx.Int16Type(), namedNumMethods, false), // numMethods + c.getTypeCode(types.NewPointer(typ)), // ptrTo + c.getTypeCode(typ.Underlying()), // underlying + pkgPathPtr, // pkgpath pointer } - metabyte |= 1 << 5 // "named" flag + if len(methods) > 0 { + typeFields = append(typeFields, methodSetValue) // methods + } + typeFields = append(typeFields, c.ctx.ConstString(pkgname+"."+name+"\x00", false)) // name + metabyte |= 1 << 5 // "named" flag case *types.Chan: var dir reflectChanDir switch typ.Dir() { @@ -323,10 +367,20 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { c.getTypeCode(typ.Elem()), // elementType } case *types.Pointer: + ptrNumMethods := uint64(numMethods) + if ptrNumMethods&numMethodHasMethodSet != 0 { + panic("numMethods overflow: too many exported methods on pointer type") + } + if len(methods) > 0 { + ptrNumMethods |= numMethodHasMethodSet + } typeFields = []llvm.Value{ - llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods + llvm.ConstInt(c.ctx.Int16Type(), ptrNumMethods, false), // numMethods c.getTypeCode(typ.Elem()), } + if len(methods) > 0 { + typeFields = append(typeFields, methodSetValue) + } case *types.Array: typeFields = []llvm.Value{ llvm.ConstInt(c.ctx.Int16Type(), 0, false), // numMethods @@ -353,9 +407,16 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { llvmStructType := c.getLLVMType(typ) size := c.targetData.TypeStoreSize(llvmStructType) + structNumMethods := uint64(numMethods) + if structNumMethods&numMethodHasMethodSet != 0 { + panic("numMethods overflow: too many exported methods on struct type") + } + if len(methods) > 0 { + structNumMethods |= numMethodHasMethodSet + } typeFields = []llvm.Value{ - llvm.ConstInt(c.ctx.Int16Type(), uint64(numMethods), false), // numMethods - c.getTypeCode(types.NewPointer(typ)), // ptrTo + llvm.ConstInt(c.ctx.Int16Type(), structNumMethods, false), // numMethods + c.getTypeCode(types.NewPointer(typ)), // ptrTo pkgPathPtr, llvm.ConstInt(c.ctx.Int32Type(), uint64(size), false), // size llvm.ConstInt(c.ctx.Int16Type(), uint64(typ.NumFields()), false), // numFields @@ -407,9 +468,14 @@ func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { })) } typeFields = append(typeFields, llvm.ConstArray(structFieldType, fields)) + if len(methods) > 0 { + typeFields = append(typeFields, methodSetValue) + } case *types.Interface: - typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))} - // TODO: methods + typeFields = []llvm.Value{ + c.getTypeCode(types.NewPointer(typ)), + methodSetValue, + } case *types.Signature: typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))} // TODO: params, return values, etc @@ -696,17 +762,11 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value { // This type assertion always succeeds, so we can just set commaOk to true. commaOk = llvm.ConstInt(b.ctx.Int1Type(), 1, true) } else { - // Type assert on interface type with methods. - // 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 - fn := b.getInterfaceImplementsFunc(expr.AssertedType) - commaOk = b.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{actualTypeNum}, "") + // Type assert on an interface type with methods. + // Create a call to a declared-but-not-defined function that will + // be lowered by the interface lowering pass into a type-ID + // comparison chain. + commaOk = b.createInterfaceTypeAssert(intf, actualTypeNum) } } else { name, _ := getTypeCodeName(expr.AssertedType) @@ -783,20 +843,58 @@ func (c *compilerContext) getMethodsString(itf *types.Interface) string { return strings.Join(methods, "; ") } -// 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 { - s, _ := getTypeCodeName(assertedType.Underlying()) - fnName := s + ".$typeassert" - llvmFn := c.mod.NamedFunction(fnName) - if llvmFn.IsNil() { - llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.dataPtrType}, false) - llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) - c.addStandardDeclaredAttributes(llvmFn) - methods := c.getMethodsString(assertedType.Underlying().(*types.Interface)) - llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods)) +// getMethodSetValue creates the method set struct value for a list of methods. +// The struct contains a length and a sorted array of method signature pointers. +func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value { + // Create a sorted list of method signature global names. + type methodRef struct { + name string + value llvm.Value } - return llvmFn + var refs []methodRef + for _, method := range methods { + name := method.Name() + if !token.IsExported(name) { + name = method.Pkg().Path() + "." + name + } + s, _ := getTypeCodeName(method.Type()) + globalName := "reflect/types.signature:" + name + ":" + s + value := c.mod.NamedGlobal(globalName) + if value.IsNil() { + value = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName) + value.SetInitializer(llvm.ConstNull(c.ctx.Int8Type())) + value.SetGlobalConstant(true) + value.SetLinkage(llvm.LinkOnceODRLinkage) + value.SetAlignment(1) + if c.Debug { + file := c.getDIFile("") + diglobal := c.dibuilder.CreateGlobalVariableExpression(file, llvm.DIGlobalVariableExpression{ + Name: globalName, + File: file, + Line: 1, + Type: c.getDIType(types.Typ[types.Uint8]), + LocalToUnit: false, + Expr: c.dibuilder.CreateExpression(nil), + AlignInBits: 8, + }) + value.AddMetadata(0, diglobal) + } + } + refs = append(refs, methodRef{globalName, value}) + } + sort.Slice(refs, func(i, j int) bool { + return refs[i].name < refs[j].name + }) + + var values []llvm.Value + for _, ref := range refs { + values = append(values, ref.value) + } + + return c.ctx.ConstStruct([]llvm.Value{ + llvm.ConstInt(c.uintptrType, uint64(len(values)), false), + llvm.ConstArray(c.dataPtrType, values), + }, false) } // getInvokeFunction returns the thunk to call the given interface method. The @@ -823,6 +921,24 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value { return llvmFn } +// createInterfaceTypeAssert creates a call to a declared-but-not-defined +// $typeassert function for the given interface. This function will be defined +// by the interface lowering pass as a type-ID comparison chain, avoiding the +// need for runtime.typeImplementsMethodSet at compile time. +func (b *builder) createInterfaceTypeAssert(intf *types.Interface, actualType llvm.Value) llvm.Value { + s, _ := getTypeCodeName(intf) + fnName := s + ".$typeassert" + llvmFn := b.mod.NamedFunction(fnName) + if llvmFn.IsNil() { + llvmFnType := llvm.FunctionType(b.ctx.Int1Type(), []llvm.Type{b.dataPtrType}, false) + llvmFn = llvm.AddFunction(b.mod, fnName, llvmFnType) + b.addStandardDeclaredAttributes(llvmFn) + methods := b.getMethodsString(intf) + llvmFn.AddFunctionAttr(b.ctx.CreateStringAttribute("tinygo-methods", methods)) + } + return b.CreateCall(llvmFn.GlobalValueType(), llvmFn, []llvm.Value{actualType}, "") +} + // getInterfaceInvokeWrapper returns a wrapper for the given method so it can be // invoked from an interface. The wrapper takes in a pointer to the underlying // value, dereferences or unpacks it if necessary, and calls the real method. diff --git a/compiler/testdata/interface.ll b/compiler/testdata/interface.ll index fcde0158d4..4bec11c419 100644 --- a/compiler/testdata/interface.ll +++ b/compiler/testdata/interface.ll @@ -9,12 +9,14 @@ target triple = "wasm32-unknown-wasi" @"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 -62, ptr @"reflect/types.type:pointer:basic:int" }, align 4 @"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:basic:int" }, align 4 @"reflect/types.type:pointer:named:error" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:named:error" }, align 4 -@"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, [7 x i8] } { i8 116, i16 1, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", [7 x i8] c".error\00" }, align 4 +@"reflect/types.signature:Error:func:{}{basic:string}" = linkonce_odr constant i8 0, align 1 +@"reflect/types.type:named:error" = linkonce_odr constant { i8, i16, ptr, ptr, ptr, { i32, [1 x ptr] }, [7 x i8] } { i8 116, i16 -32767, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", ptr @"reflect/types.type.pkgpath.empty", { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:Error:func:{}{basic:string}"] }, [7 x i8] c".error\00" }, align 4 @"reflect/types.type.pkgpath.empty" = linkonce_odr unnamed_addr constant [1 x i8] zeroinitializer, align 1 -@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 84, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4 +@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr, { i32, [1 x ptr] } } { i8 84, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}", { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:Error:func:{}{basic:string}"] } }, align 4 @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4 @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, i16, ptr } { i8 -43, i16 0, ptr @"reflect/types.type:interface:{String:func:{}{basic:string}}" }, align 4 -@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 84, ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" }, align 4 +@"reflect/types.signature:String:func:{}{basic:string}" = linkonce_odr constant i8 0, align 1 +@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr, { i32, [1 x ptr] } } { i8 84, ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", { i32, [1 x ptr] } { i32 1, [1 x ptr] [ptr @"reflect/types.signature:String:func:{}{basic:string}"] } }, align 4 @"reflect/types.typeid:basic:int" = external constant i8 ; Function Attrs: allockind("alloc,zeroed") allocsize(0) diff --git a/interp/interpreter.go b/interp/interpreter.go index 137938d9f3..e8f5545d5d 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -413,64 +413,6 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // Special function that will trigger an error. // This is used to test error reporting. return nil, mem, r.errorAt(inst, errors.New("test error")) - case strings.HasSuffix(callFn.name, ".$typeassert"): - if r.debug { - fmt.Fprintln(os.Stderr, indent+"interface assert:", operands[1:]) - } - - // Load various values for the interface implements check below. - typecodePtr, err := operands[1].asPointer(r) - if err != nil { - return nil, mem, r.errorAt(inst, err) - } - // typecodePtr always point to the numMethod field in the type - // description struct. The methodSet, when present, comes right - // before the numMethod field (the compiler doesn't generate - // method sets for concrete types without methods). - // Considering that the compiler doesn't emit interface type - // asserts for interfaces with no methods (as the always succeed) - // then if the offset is zero, this assert must always fail. - if typecodePtr.offset() == 0 { - locals[inst.localIndex] = literalValue{uint8(0)} - break - } - typecodePtrOffset, err := typecodePtr.addOffset(-int64(r.pointerSize)) - if err != nil { - return nil, mem, r.errorAt(inst, err) - } - methodSetPtr, err := mem.load(typecodePtrOffset, r.pointerSize).asPointer(r) - if err != nil { - return nil, mem, r.errorAt(inst, err) - } - methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer() - numMethods := int(r.builder.CreateExtractValue(methodSet, 0, "").ZExtValue()) - 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. - concreteTypeMethods := map[string]struct{}{} - for i := 0; i < numMethods; i++ { - methodInfo := r.builder.CreateExtractValue(methodSet, 1, "") - name := r.builder.CreateExtractValue(methodInfo, i, "").Name() - concreteTypeMethods[name] = struct{}{} - } - - // Check whether all interface methods are also in the list - // of defined methods calculated above. This is the interface - // assert itself. - assertOk := uint8(1) // i1 true - 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. - assertOk = 0 // i1 false - break - } - } - // If assertOk is still 1, the assertion succeeded. - locals[inst.localIndex] = literalValue{assertOk} 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 diff --git a/src/internal/reflectlite/type.go b/src/internal/reflectlite/type.go index 6795a51b2c..e282f16f3b 100644 --- a/src/internal/reflectlite/type.go +++ b/src/internal/reflectlite/type.go @@ -157,6 +157,10 @@ const ( flagIsBinary = 128 // flag that is set if this type uses the hashmap binary algorithm ) +// Flag in the numMethod field (uint16) of Pointer and Struct type descriptors, +// indicating that an inline method set is present in the type descriptor. +const numMethodHasMethodSet = 0x8000 + // The base type struct. All type structs start with this. type RawType struct { meta uint8 // metadata byte, contains kind and flags (see constants above) @@ -171,16 +175,22 @@ type elemType struct { elem *RawType } +// ptrType is the type descriptor for pointer types. +// The numMethod field stores the number of exported methods in the lower bits, +// with bit 15 (numMethodHasMethodSet) indicating whether the methods field is +// present. When the flag is clear, the methods field does not exist in the +// actual type descriptor and must not be accessed. type ptrType struct { RawType numMethod uint16 elem *RawType + methods methodSet // only present when numMethod & numMethodHasMethodSet != 0 } type interfaceType struct { RawType - ptrTo *RawType - // TODO: methods + ptrTo *RawType + methods methodSet } type arrayType struct { @@ -200,13 +210,19 @@ type mapType struct { key *RawType } +// namedType is the type descriptor for named types. The numMethod field uses +// bit 15 (numMethodHasMethodSet) to indicate whether an inline method set is +// present after pkg. When the flag is set, a methodSet follows at +// unsafe.Sizeof(namedType{}), and the name string follows after the method +// set's entries. When clear, the name string starts directly at that offset. type namedType struct { RawType numMethod uint16 ptrTo *RawType elem *RawType pkg *byte - name [1]byte + // if numMethod & numMethodHasMethodSet != 0: methodSet follows here + // name (null-terminated "pkg.Name\0") follows after the method set (or directly here) } // Type for struct types. The numField value is intentionally put before ptrTo @@ -216,6 +232,10 @@ type namedType struct { // The fields array isn't necessarily 1 structField long, instead it is as long // as numFields. The array is given a length of 1 to satisfy the Go type // checker. +// The numMethod field stores the number of exported methods in the lower bits, +// with bit 15 (numMethodHasMethodSet) indicating whether an inline method set +// follows the fields array. When the flag is clear, no method set is present +// and the type descriptor ends after the last structField entry. type structType struct { RawType numMethod uint16 @@ -224,6 +244,7 @@ type structType struct { size uint32 numField uint16 fields [1]structField // the remaining fields are all of type structField + // methods methodSet follows after fields, only when numMethod & numMethodHasMethodSet != 0 } type structField struct { @@ -231,6 +252,12 @@ type structField struct { data unsafe.Pointer // various bits of information, packed in a byte array } +// Method set, as emitted by the compiler. +type methodSet struct { + length uintptr + methods [0]unsafe.Pointer // variable number of method signature pointers +} + // Equivalent to (go/types.Type).Underlying(): if this is a named type return // the underlying type, else just return the type itself. func (t *RawType) underlying() *RawType { @@ -733,21 +760,38 @@ func (t *RawType) FieldAlign() int { // AssignableTo returns whether a value of type t can be assigned to a variable // of type u. func (t *RawType) AssignableTo(u Type) bool { - if t == u.(*RawType) { + u_raw := u.(*RawType) + if t == u_raw { return true } - if t.underlying() == u.(*RawType).underlying() && (!t.isNamed() || !u.(*RawType).isNamed()) { - return true + if u.Kind() == Interface { + // T is an interface type and x implements T. + u_itf := (*interfaceType)(unsafe.Pointer(u_raw.underlying())) + return typeImplementsMethodSet(unsafe.Pointer(t), unsafe.Pointer(&u_itf.methods)) } - if u.Kind() == Interface && u.NumMethod() == 0 { + t_named := t.isNamed() + u_named := u_raw.isNamed() + if t_named && u_named { + return false + } + if t.underlying() == u_raw.underlying() { return true } - if u.Kind() == Interface { - panic("reflect: unimplemented: AssignableTo with interface") + if t.Kind() == Chan && u_raw.Kind() == Chan { + t_chan := (*elemType)(unsafe.Pointer(t.underlying())) + u_chan := (*elemType)(unsafe.Pointer(u_raw.underlying())) + if t_chan.elem != u_chan.elem { + return false + } + if t_chan.ChanDir() != BothDir { + return false + } + return true } + return false } @@ -755,7 +799,85 @@ func (t *RawType) Implements(u Type) bool { if u.Kind() != Interface { panic("reflect: non-interface type passed to Type.Implements") } - return t.AssignableTo(u) + u_itf := (*interfaceType)(unsafe.Pointer(u.(*RawType).underlying())) + return typeImplementsMethodSet(unsafe.Pointer(t), unsafe.Pointer(&u_itf.methods)) +} + +// typeImplementsMethodSet checks whether the concrete type (identified by its +// typecode pointer) implements the given method set. Both the concrete type's +// method set and the asserted method set are sorted arrays of method signature +// pointers, so comparison is O(n+m). +// +//go:linkname typeImplementsMethodSet runtime.typeImplementsMethodSet +func typeImplementsMethodSet(concreteType, assertedMethodSet unsafe.Pointer) bool { + if concreteType == nil { + return false + } + + const ptrSize = unsafe.Sizeof((*byte)(nil)) + itfNumMethod := *(*uintptr)(assertedMethodSet) + if itfNumMethod == 0 { + return true + } + + // Pull the method set out of the concrete type. + var methods *methodSet + metaByte := *(*uint8)(concreteType) + if metaByte&flagNamed != 0 { + ct := (*namedType)(concreteType) + if ct.numMethod&numMethodHasMethodSet == 0 { + return false + } + methods = (*methodSet)(unsafe.Add(unsafe.Pointer(ct), unsafe.Sizeof(*ct))) + } else if metaByte&kindMask == uint8(Interface) { + ct := (*interfaceType)(concreteType) + methods = &ct.methods + } else if metaByte&kindMask == uint8(Pointer) { + ct := (*ptrType)(concreteType) + if ct.numMethod&numMethodHasMethodSet == 0 { + return false + } + methods = &ct.methods + } else if metaByte&kindMask == uint8(Struct) { + ct := (*structType)(concreteType) + if ct.numMethod&numMethodHasMethodSet == 0 { + return false + } + // For struct types, the method set follows after the variable-length + // fields array. We need to compute its offset dynamically. + fieldSize := unsafe.Sizeof(structField{}) + methodsPtr := unsafe.Add(unsafe.Pointer(&ct.fields[0]), uintptr(ct.numField)*fieldSize) + methods = (*methodSet)(methodsPtr) + } else { + return false + } + + concreteTypePtr := unsafe.Pointer(&methods.methods) + concreteTypeEnd := unsafe.Add(concreteTypePtr, uintptr(methods.length)*ptrSize) + + // Iterate over each method in the interface method set, and check whether + // the method exists in the method set of the concrete type. + // Both method sets are sorted, so we can use a linear scan. + assertedTypePtr := unsafe.Add(assertedMethodSet, ptrSize) + assertedTypeEnd := unsafe.Add(assertedTypePtr, itfNumMethod*ptrSize) + for assertedTypePtr != assertedTypeEnd { + assertedMethod := *(*unsafe.Pointer)(assertedTypePtr) + + for { + if concreteTypePtr == concreteTypeEnd { + return false + } + concreteMethod := *(*unsafe.Pointer)(concreteTypePtr) + concreteTypePtr = unsafe.Add(concreteTypePtr, ptrSize) + if concreteMethod == assertedMethod { + break + } + } + + assertedTypePtr = unsafe.Add(assertedTypePtr, ptrSize) + } + + return true } // Comparable returns whether values of this type can be compared to each other. @@ -782,14 +904,14 @@ func (t *RawType) ChanDir() ChanDir { func (t *RawType) NumMethod() int { if t.isNamed() { - return int((*namedType)(unsafe.Pointer(t)).numMethod) + return int((*namedType)(unsafe.Pointer(t)).numMethod & ^uint16(numMethodHasMethodSet)) } switch t.Kind() { case Pointer: - return int((*ptrType)(unsafe.Pointer(t)).numMethod) + return int((*ptrType)(unsafe.Pointer(t)).numMethod & ^uint16(numMethodHasMethodSet)) case Struct: - return int((*structType)(unsafe.Pointer(t)).numMethod) + return int((*structType)(unsafe.Pointer(t)).numMethod & ^uint16(numMethodHasMethodSet)) case Interface: //FIXME: Use len(methods) return (*interfaceType)(unsafe.Pointer(t)).ptrTo.NumMethod() @@ -816,7 +938,14 @@ func readStringZ(data unsafe.Pointer) string { func (t *RawType) name() string { ntype := (*namedType)(unsafe.Pointer(t)) - return readStringZ(unsafe.Pointer(&ntype.name[0])) + // The name follows after the fixed fields (and optionally the method set). + ptr := unsafe.Add(unsafe.Pointer(ntype), unsafe.Sizeof(*ntype)) + if ntype.numMethod&numMethodHasMethodSet != 0 { + ms := (*methodSet)(ptr) + // Skip past the length field and the method pointer entries. + ptr = unsafe.Add(ptr, unsafe.Sizeof(uintptr(0))+uintptr(ms.length)*unsafe.Sizeof(unsafe.Pointer(nil))) + } + return readStringZ(ptr) } func (t *RawType) Name() string { diff --git a/src/runtime/interface.go b/src/runtime/interface.go index e1d263a7e3..e487c46c2f 100644 --- a/src/runtime/interface.go +++ b/src/runtime/interface.go @@ -90,6 +90,9 @@ func interfaceTypeAssert(ok bool) { } } +// Implemented in the internal/reflectlite package. +func typeImplementsMethodSet(actualTypeNum, assertedMethodSet unsafe.Pointer) bool + // The following declarations are only used during IR construction. They are // lowered to inline IR in the interface lowering pass. // See compiler/interface-lowering.go for details. diff --git a/testdata/reflect.go b/testdata/reflect.go index 6971866dbd..873d60f787 100644 --- a/testdata/reflect.go +++ b/testdata/reflect.go @@ -344,12 +344,8 @@ func main() { println("PtrTo failed for type myslice") } - if reflect.TypeOf(errorValue).Implements(errorType) != true { - println("errorValue.Implements(errorType) was false, expected true") - } - if reflect.TypeOf(errorValue).Implements(stringerType) != false { - println("errorValue.Implements(errorType) was true, expected false") - } + println("\ninterface implements") + testImplements() println("\nalignment / offset:") v2 := struct { @@ -573,6 +569,230 @@ func testInterfaceMethod() { } } +// Types for interface Implements/AssignableTo tests. + +type Reader interface { + Read(p []byte) (n int, err error) +} + +type Writer interface { + Write(p []byte) (n int, err error) +} + +type ReadWriter interface { + Read(p []byte) (n int, err error) + Write(p []byte) (n int, err error) +} + +type Closer interface { + Close() error +} + +type ReadCloser interface { + Read(p []byte) (n int, err error) + Close() error +} + +type myReader struct{} + +func (myReader) Read(p []byte) (int, error) { return 0, nil } + +type myWriter struct{} + +func (*myWriter) Write(p []byte) (int, error) { return 0, nil } + +type myReadWriter struct{} + +func (myReadWriter) Read(p []byte) (int, error) { return 0, nil } +func (*myReadWriter) Write(p []byte) (int, error) { return 0, nil } + +type myStringer struct{} + +func (myStringer) String() string { return "mystringer" } + +type myErrorStringer struct{} + +func (myErrorStringer) Error() string { return "err" } +func (myErrorStringer) String() string { return "str" } + +// Interface with unexported method (from upstream set_test.go). +type exprLike interface { + Pos() int + End() int + exprNode() +} + +type notAnExpr struct{} + +func (notAnExpr) Pos() int { return 0 } +func (notAnExpr) End() int { return 0 } +func (notAnExpr) exprNode() {} + +// Named types for assignability tests (from upstream set_test.go). +type IntPtr *int +type IntPtr1 *int +type Ch <-chan interface{} + +func testImplements() { + readerType := reflect.TypeOf((*Reader)(nil)).Elem() + writerType := reflect.TypeOf((*Writer)(nil)).Elem() + readWriterType := reflect.TypeOf((*ReadWriter)(nil)).Elem() + closerType := reflect.TypeOf((*Closer)(nil)).Elem() + readCloserType := reflect.TypeOf((*ReadCloser)(nil)).Elem() + emptyItf := reflect.TypeOf((*interface{})(nil)).Elem() + + // --- Concrete type implements interface --- + println("concrete implements:") + + // myReader has value receiver Read → implements Reader + println("myReader → Reader:", reflect.TypeOf(myReader{}).Implements(readerType)) // true + println("*myReader → Reader:", reflect.TypeOf(new(myReader)).Elem().Implements(readerType)) // true (value method in pointer set) + + // myWriter has pointer receiver Write → only *myWriter implements Writer + println("myWriter → Writer:", reflect.TypeOf(myWriter{}).Implements(writerType)) // false (pointer receiver) + println("*myWriter → Writer:", reflect.TypeOf(&myWriter{}).Implements(writerType)) // true + + // myReadWriter: Read on value, Write on pointer + println("myReadWriter → Reader:", reflect.TypeOf(myReadWriter{}).Implements(readerType)) // true + println("myReadWriter → Writer:", reflect.TypeOf(myReadWriter{}).Implements(writerType)) // false (Write is ptr recv) + println("myReadWriter → ReadWriter:", reflect.TypeOf(myReadWriter{}).Implements(readWriterType)) // false + println("*myReadWriter → Reader:", reflect.TypeOf(&myReadWriter{}).Implements(readerType)) // true + println("*myReadWriter → Writer:", reflect.TypeOf(&myReadWriter{}).Implements(writerType)) // true + println("*myReadWriter → ReadWriter:", reflect.TypeOf(&myReadWriter{}).Implements(readWriterType)) // true + + // Nothing implements Closer (none of our types have Close) + println("myReader → Closer:", reflect.TypeOf(myReader{}).Implements(closerType)) // false + println("*myReadWriter → Closer:", reflect.TypeOf(&myReadWriter{}).Implements(closerType)) // false + + // errorValue (*errors.errorString) implements error but not Stringer + println("errorValue → error:", reflect.TypeOf(errorValue).Implements(errorType)) // true + println("errorValue → Stringer:", reflect.TypeOf(errorValue).Implements(stringerType)) // false + + // myErrorStringer implements both error and Stringer + println("myErrorStringer → error:", reflect.TypeOf(myErrorStringer{}).Implements(errorType)) // true + println("myErrorStringer → Stringer:", reflect.TypeOf(myErrorStringer{}).Implements(stringerType)) // true + + // Everything implements empty interface + println("myReader → interface{}:", reflect.TypeOf(myReader{}).Implements(emptyItf)) // true + println("int → interface{}:", reflect.TypeOf(0).Implements(emptyItf)) // true + + // --- Interface implements interface (superset check, issue #3580) --- + println("interface implements interface:") + + // ReadWriter is a superset of Reader and Writer + println("ReadWriter → Reader:", readWriterType.Implements(readerType)) // true + println("ReadWriter → Writer:", readWriterType.Implements(writerType)) // true + println("Reader → ReadWriter:", readerType.Implements(readWriterType)) // false + println("Writer → ReadWriter:", writerType.Implements(readWriterType)) // false + + // ReadCloser has Read+Close, Reader has Read + println("ReadCloser → Reader:", readCloserType.Implements(readerType)) // true + println("ReadCloser → Closer:", readCloserType.Implements(closerType)) // true + println("ReadCloser → Writer:", readCloserType.Implements(writerType)) // false + println("Reader → ReadCloser:", readerType.Implements(readCloserType)) // false + + // Self-implements + println("Reader → Reader:", readerType.Implements(readerType)) // true + println("ReadWriter → ReadWriter:", readWriterType.Implements(readWriterType)) // true + + // error and Stringer are unrelated + println("error → Stringer:", errorType.Implements(stringerType)) // false + println("Stringer → error:", stringerType.Implements(errorType)) // false + + // Everything implements empty interface + println("Reader → interface{}:", readerType.Implements(emptyItf)) // true + println("ReadWriter → interface{}:", readWriterType.Implements(emptyItf)) // true + + // --- AssignableTo --- + println("assignable to:") + + // Identical types + println("int → int:", reflect.TypeOf(0).AssignableTo(reflect.TypeOf(0))) // true + println("string → string:", reflect.TypeOf("").AssignableTo(reflect.TypeOf(""))) // true + + // Different types + println("int → string:", reflect.TypeOf(0).AssignableTo(reflect.TypeOf(""))) // false + println("int → int64:", reflect.TypeOf(0).AssignableTo(reflect.TypeOf(int64(0)))) // false + + // Concrete assignable to interface (implements check) + println("myReader → Reader:", reflect.TypeOf(myReader{}).AssignableTo(readerType)) // true + println("*myWriter → Writer:", reflect.TypeOf(&myWriter{}).AssignableTo(writerType)) // true + println("myWriter → Writer:", reflect.TypeOf(myWriter{}).AssignableTo(writerType)) // false + println("*myReadWriter → ReadWriter:", reflect.TypeOf(&myReadWriter{}).AssignableTo(readWriterType)) // true + + // Interface assignable to interface + println("ReadWriter → Reader:", readWriterType.AssignableTo(readerType)) // true + println("Reader → ReadWriter:", readerType.AssignableTo(readWriterType)) // false + + // Everything assignable to empty interface + println("int → interface{}:", reflect.TypeOf(0).AssignableTo(emptyItf)) // true + println("Reader → interface{}:", readerType.AssignableTo(emptyItf)) // true + + // --- Upstream set_test.go: unexported method interfaces --- + println("unexported method interface:") + exprType := reflect.TypeOf((*exprLike)(nil)).Elem() + println("*notAnExpr → exprLike:", reflect.TypeOf(new(notAnExpr)).Implements(exprType)) // true + println("notAnExpr → exprLike:", reflect.TypeOf(notAnExpr{}).Implements(exprType)) // true + println("*notAnExpr → exprLike (AssignableTo):", reflect.TypeOf(new(notAnExpr)).AssignableTo(exprType)) // true + + // --- Upstream set_test.go: channel direction assignability --- + println("channel direction:") + println("chan int → <-chan int:", reflect.TypeOf(make(chan int)).AssignableTo(reflect.TypeOf(make(<-chan int)))) // true + println("<-chan int → chan int:", reflect.TypeOf(make(<-chan int)).AssignableTo(reflect.TypeOf(make(chan int)))) // false + + // --- Upstream set_test.go: named type assignability --- + println("named types:") + println("*int → IntPtr:", reflect.TypeOf(new(int)).AssignableTo(reflect.TypeOf(IntPtr(nil)))) // true + println("IntPtr → *int:", reflect.TypeOf(IntPtr(nil)).AssignableTo(reflect.TypeOf(new(int)))) // true + println("IntPtr → IntPtr1:", reflect.TypeOf(IntPtr(nil)).AssignableTo(reflect.TypeOf(IntPtr1(nil)))) // false + println("Ch → <-chan interface{}:", reflect.TypeOf(Ch(nil)).AssignableTo(reflect.TypeOf(make(<-chan interface{})))) // true + + // --- reflect.Value.Set with interface (issue #4277) --- + println("value set interface:") + type Node interface{ node() } + type FooNode struct{ V int } + type BarNode struct{ V int } + // Make FooNode and BarNode implement Node with pointer receivers + // (can't add methods to local types in function, use a different approach) + testValueSetInterface() +} + +type IfaceNode interface { + ifaceNode() +} +type FooNode struct{ V int } +type BarNode struct{ V int } + +func (*FooNode) ifaceNode() {} +func (*BarNode) ifaceNode() {} + +type NodeContainer struct { + Nodes []IfaceNode +} + +func testValueSetInterface() { + c := &NodeContainer{ + Nodes: []IfaceNode{&FooNode{V: 1}, &FooNode{V: 2}}, + } + + // Use reflect to replace elements + v := reflect.ValueOf(c).Elem().FieldByName("Nodes") + v.Index(0).Set(reflect.ValueOf(&BarNode{V: 10})) + + switch n := c.Nodes[0].(type) { + case *BarNode: + println("Set[0] to BarNode:", n.V) // 10 + default: + println("FAIL: expected *BarNode") + } + switch n := c.Nodes[1].(type) { + case *FooNode: + println("Set[1] still FooNode:", n.V) // 2 + default: + println("FAIL: expected *FooNode") + } +} + var xorshift32State uint32 = 1 func xorshift32(x uint32) uint32 { diff --git a/testdata/reflect.txt b/testdata/reflect.txt index 90ac42ac98..3024568c3d 100644 --- a/testdata/reflect.txt +++ b/testdata/reflect.txt @@ -439,6 +439,70 @@ offset for int64 matches: true offset for complex128 matches: true type assertion succeeded for unreferenced type +interface implements +concrete implements: +myReader → Reader: true +*myReader → Reader: true +myWriter → Writer: false +*myWriter → Writer: true +myReadWriter → Reader: true +myReadWriter → Writer: false +myReadWriter → ReadWriter: false +*myReadWriter → Reader: true +*myReadWriter → Writer: true +*myReadWriter → ReadWriter: true +myReader → Closer: false +*myReadWriter → Closer: false +errorValue → error: true +errorValue → Stringer: false +myErrorStringer → error: true +myErrorStringer → Stringer: true +myReader → interface{}: true +int → interface{}: true +interface implements interface: +ReadWriter → Reader: true +ReadWriter → Writer: true +Reader → ReadWriter: false +Writer → ReadWriter: false +ReadCloser → Reader: true +ReadCloser → Closer: true +ReadCloser → Writer: false +Reader → ReadCloser: false +Reader → Reader: true +ReadWriter → ReadWriter: true +error → Stringer: false +Stringer → error: false +Reader → interface{}: true +ReadWriter → interface{}: true +assignable to: +int → int: true +string → string: true +int → string: false +int → int64: false +myReader → Reader: true +*myWriter → Writer: true +myWriter → Writer: false +*myReadWriter → ReadWriter: true +ReadWriter → Reader: true +Reader → ReadWriter: false +int → interface{}: true +Reader → interface{}: true +unexported method interface: +*notAnExpr → exprLike: true +notAnExpr → exprLike: true +*notAnExpr → exprLike (AssignableTo): true +channel direction: +chan int → <-chan int: true +<-chan int → chan int: false +named types: +*int → IntPtr: true +IntPtr → *int: true +IntPtr → IntPtr1: false +Ch → <-chan interface{}: true +value set interface: +Set[0] to BarNode: 10 +Set[1] still FooNode: 2 + alignment / offset: struct{[0]func(); byte}: true diff --git a/transform/interface-lowering.go b/transform/interface-lowering.go index 7f0b6fdc57..eb954e1d03 100644 --- a/transform/interface-lowering.go +++ b/transform/interface-lowering.go @@ -36,6 +36,12 @@ import ( "tinygo.org/x/go-llvm" ) +// numMethodHasMethodSet is a flag in bit 15 of the numMethod field (uint16) in +// Named, Pointer, and Struct type descriptors. When set, an inline method set +// is present in the type descriptor. Must match the constant in +// src/internal/reflectlite/type.go. +const numMethodHasMethodSet = 0x8000 + // signatureInfo is a Go signature of an interface method. It does not represent // any method in particular. type signatureInfo struct { @@ -276,7 +282,7 @@ func (p *lowerInterfacesPass) run() error { for _, fn := range interfaceAssertFunctions { methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods") itf := p.interfaces[methodsAttr.GetStringValue()] - p.defineInterfaceImplementsFunc(fn, itf) + p.defineInterfaceAssertFunc(fn, itf) } // Replace each type assert with an actual type comparison or (if the type @@ -325,6 +331,49 @@ func (p *lowerInterfacesPass) run() error { } sort.Strings(typeNames) + // Check whether runtime.typeImplementsMethodSet still has uses. Now that + // interface type assertions have been lowered to type-ID comparison + // chains, the only remaining callers would be from reflect + // (AssignableTo/Implements). If none remain, we can strip the inline + // method-set data from type descriptors to save binary size. + stripMethodSets := false + typeImplementsFn := p.mod.NamedFunction("runtime.typeImplementsMethodSet") + if !typeImplementsFn.IsNil() && !hasUses(typeImplementsFn) { + stripMethodSets = true + } + + // Collect all method signatures that appear in any interface type + // descriptor. When reflect is imported and method sets are kept, + // concrete type method sets are pruned: individual methods not in any + // interface are removed, and types that can't fully satisfy at least + // one interface have their method sets emptied entirely. + // + // When method sets are stripped entirely (reflect not imported), + // methodFilter is nil and filterMethodSet replaces with empty. + var methodFilter map[string]struct{} + var ifaceMethodSets []map[string]struct{} + if !stripMethodSets { + methodFilter = make(map[string]struct{}) + for _, name := range typeNames { + if !strings.HasPrefix(name, "interface:") { + continue + } + t := p.types[name] + initializer := t.typecode.Initializer() + ifaceSet := make(map[string]struct{}) + for i := 0; i < initializer.Type().StructElementTypesCount(); i++ { + field := p.builder.CreateExtractValue(initializer, i, "") + for _, sig := range p.extractMethodSigs(field) { + methodFilter[sig] = struct{}{} + ifaceSet[sig] = struct{}{} + } + } + if len(ifaceSet) > 0 { + ifaceMethodSets = append(ifaceMethodSets, ifaceSet) + } + } + } + // Remove all method sets, which are now unnecessary and inhibit later // optimizations if they are left in place. zero := llvm.ConstInt(p.ctx.Int32Type(), 0, false) @@ -332,9 +381,39 @@ func (p *lowerInterfacesPass) run() error { t := p.types[name] if !t.methodSet.IsNil() { initializer := t.typecode.Initializer() + numFields := initializer.Type().StructElementTypesCount() + + // Read numMethods from the original type descriptor (index 2: + // after prefix pointer at 0 and kind byte at 1). For Named, + // Pointer, and Struct types, the numMethodHasMethodSet flag + // indicates that an inline method set is present. + var numMethodsConst uint64 + var numMethodsIsI16 bool + if numFields > 2 { + nmField := p.builder.CreateExtractValue(initializer, 2, "") + if nmField.Type() == p.ctx.Int16Type() { + numMethodsConst = nmField.ZExtValue() + numMethodsIsI16 = true + } + } + var newInitializerFields []llvm.Value - for i := 1; i < initializer.Type().StructElementTypesCount(); i++ { - newInitializerFields = append(newInitializerFields, p.builder.CreateExtractValue(initializer, i, "")) + for i := 1; i < numFields; i++ { + field := p.builder.CreateExtractValue(initializer, i, "") + field = p.filterMethodSet(field, methodFilter, ifaceMethodSets) + // Strip empty inline method sets for Named, Pointer, and + // Struct types. When the method set is pruned to empty, we + // remove it and clear the numMethodHasMethodSet flag (bit 15 + // of numMethod) so the runtime skips reading it. + if numMethodsIsI16 && numMethodsConst&numMethodHasMethodSet != 0 && p.isMethodSetType(field.Type()) { + elems := field.Type().StructElementTypes() + if elems[1].ArrayLength() == 0 { + clearedNumMethods := numMethodsConst & ^uint64(numMethodHasMethodSet) + newInitializerFields[1] = llvm.ConstInt(p.ctx.Int16Type(), clearedNumMethods, false) + continue + } + } + newInitializerFields = append(newInitializerFields, field) } newInitializer := p.ctx.ConstStruct(newInitializerFields, false) typecodeName := t.typecode.Name() @@ -428,66 +507,6 @@ func (p *lowerInterfacesPass) getSignature(name string) *signatureInfo { return p.signatures[name] } -// 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) defineInterfaceImplementsFunc(fn llvm.Value, itf *interfaceInfo) { - // Create the function and function signature. - fn.Param(0).SetName("actualType") - fn.SetLinkage(llvm.InternalLinkage) - fn.SetUnnamedAddr(true) - AddStandardAttributes(fn, p.config) - - // Start the if/else chain at the entry block. - entry := p.ctx.AddBasicBlock(fn, "entry") - thenBlock := p.ctx.AddBasicBlock(fn, "then") - p.builder.SetInsertPointAtEnd(entry) - - if p.dibuilder != nil { - difile := p.getDIFile("") - diFuncType := p.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ - File: difile, - }) - difunc := p.dibuilder.CreateFunction(difile, llvm.DIFunction{ - Name: "(Go interface assert)", - File: difile, - Line: 0, - Type: diFuncType, - LocalToUnit: true, - IsDefinition: true, - ScopeLine: 0, - Flags: llvm.FlagPrototyped, - Optimized: true, - }) - fn.SetSubprogram(difunc) - p.builder.SetCurrentDebugLocation(0, 0, difunc, llvm.Metadata{}) - } - - // Iterate over all possible types. Each iteration creates a new branch - // either to the 'then' block (success) or the .next block, for the next - // check. - actualType := fn.Param(0) - for _, typ := range itf.types { - nextBlock := p.ctx.AddBasicBlock(fn, typ.name+".next") - cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, typ.typecodeGEP, typ.name+".icmp") - p.builder.CreateCondBr(cmp, thenBlock, nextBlock) - p.builder.SetInsertPointAtEnd(nextBlock) - } - - // The builder is now inserting at the last *.next block. Once we reach - // this point, all types have been checked so the type assert will have - // failed. - p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 0, false)) - - // Fill 'then' block (type assert was successful). - p.builder.SetInsertPointAtEnd(thenBlock) - p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 1, false)) -} - // defineInterfaceMethodFunc defines this thunk by calling the concrete method // of the type that implements this interface. // @@ -592,3 +611,171 @@ func (p *lowerInterfacesPass) getDIFile(file string) llvm.Metadata { } return difile } + +// defineInterfaceAssertFunc defines a $typeassert function for the given +// interface. The function returns true if the concrete type (passed as a +// type-ID pointer) implements the interface, using a chain of type-ID +// comparisons. This avoids pulling in runtime.typeImplementsMethodSet for +// programs that don't use reflect. +func (p *lowerInterfacesPass) defineInterfaceAssertFunc(fn llvm.Value, itf *interfaceInfo) { + actualType := fn.FirstParam() + actualType.SetName("actualType") + fn.SetLinkage(llvm.InternalLinkage) + fn.SetUnnamedAddr(true) + AddStandardAttributes(fn, p.config) + + entry := p.ctx.AddBasicBlock(fn, "entry") + p.builder.SetInsertPointAtEnd(entry) + + if p.dibuilder != nil { + difile := p.getDIFile("") + diFuncType := p.dibuilder.CreateSubroutineType(llvm.DISubroutineType{ + File: difile, + }) + difunc := p.dibuilder.CreateFunction(difile, llvm.DIFunction{ + Name: "(Go interface type assert)", + File: difile, + Line: 0, + Type: diFuncType, + LocalToUnit: true, + IsDefinition: true, + ScopeLine: 0, + Flags: llvm.FlagPrototyped, + Optimized: true, + }) + fn.SetSubprogram(difunc) + p.builder.SetCurrentDebugLocation(0, 0, difunc, llvm.Metadata{}) + } + + // Build an OR chain: return (type == T1) || (type == T2) || ... + llvmFalse := llvm.ConstInt(p.ctx.Int1Type(), 0, false) + result := llvmFalse + for _, typ := range itf.types { + cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, typ.typecodeGEP, typ.name+".icmp") + result = p.builder.CreateOr(result, cmp, "") + } + p.builder.CreateRet(result) +} + +// isMethodSetType reports whether ty has the shape of a method-set struct: +// { uintptr, [N x ptr] }. +func (p *lowerInterfacesPass) isMethodSetType(ty llvm.Type) bool { + if ty.TypeKind() != llvm.StructTypeKind { + return false + } + elems := ty.StructElementTypes() + if len(elems) != 2 { + return false + } + if elems[0] != p.uintptrType { + return false + } + return elems[1].TypeKind() == llvm.ArrayTypeKind && elems[1].ElementType() == p.ptrType +} + +// extractMethodSigs returns the names of method signature globals inside a +// method-set field ({ uintptr, [N x ptr] }). Returns nil if field is not a +// method set. +func (p *lowerInterfacesPass) extractMethodSigs(field llvm.Value) []string { + if !p.isMethodSetType(field.Type()) { + return nil + } + methodArray := p.builder.CreateExtractValue(field, 1, "") + n := methodArray.Type().ArrayLength() + sigs := make([]string, 0, n) + for j := 0; j < n; j++ { + sig := p.builder.CreateExtractValue(methodArray, j, "") + sig = stripPointerCasts(sig) + sigs = append(sigs, sig.Name()) + } + return sigs +} + +// filterMethodSet processes a type-descriptor field that may be a method set. +// Non-method-set fields are returned unchanged. +// +// If keepSigs is nil, the method set is replaced with an empty one (strip mode, +// used when reflect is not imported). If keepSigs is non-nil, the method set is +// pruned in two stages: first, methods not in keepSigs (the union of all +// interface signatures) are removed; then, if the remaining methods cannot +// fully satisfy at least one interface in ifaceSets, the entire method set is +// emptied. +func (p *lowerInterfacesPass) filterMethodSet(field llvm.Value, keepSigs map[string]struct{}, ifaceSets []map[string]struct{}) llvm.Value { + if !p.isMethodSetType(field.Type()) { + return field + } + + methodArray := p.builder.CreateExtractValue(field, 1, "") + numMethods := methodArray.Type().ArrayLength() + + // Strip mode: replace with empty method set. + if keepSigs == nil { + return p.ctx.ConstStruct([]llvm.Value{ + llvm.ConstInt(p.uintptrType, 0, false), + llvm.ConstArray(p.ptrType, nil), + }, false) + } + + if numMethods == 0 { + return field + } + + // Extract all methods and their signature names. + type methodEntry struct { + value llvm.Value + name string + } + entries := make([]methodEntry, numMethods) + nameSet := make(map[string]struct{}, numMethods) + for j := 0; j < numMethods; j++ { + sig := p.builder.CreateExtractValue(methodArray, j, "") + stripped := stripPointerCasts(sig) + name := stripped.Name() + entries[j] = methodEntry{sig, name} + nameSet[name] = struct{}{} + } + + // Check whether this type can fully implement at least one interface. + // If not, its method set can never produce a true result from + // typeImplementsMethodSet, so we can empty it entirely. + implementsAny := false + for _, ifaceSet := range ifaceSets { + if isSubsetOf(ifaceSet, nameSet) { + implementsAny = true + break + } + } + if !implementsAny { + return p.ctx.ConstStruct([]llvm.Value{ + llvm.ConstInt(p.uintptrType, 0, false), + llvm.ConstArray(p.ptrType, nil), + }, false) + } + + // Prune: keep only methods whose signature appears in keepSigs. + var kept []llvm.Value + for _, e := range entries { + if _, ok := keepSigs[e.name]; ok { + kept = append(kept, e.value) + } + } + + if len(kept) == numMethods { + return field + } + + return p.ctx.ConstStruct([]llvm.Value{ + llvm.ConstInt(p.uintptrType, uint64(len(kept)), false), + llvm.ConstArray(p.ptrType, kept), + }, false) +} + +// isSubsetOf reports whether every key in sub is also in super. +func isSubsetOf(sub, super map[string]struct{}) bool { + for k := range sub { + if _, ok := super[k]; !ok { + return false + } + } + return true +} diff --git a/transform/optimizer.go b/transform/optimizer.go index 1209754310..ba8d1c12e9 100644 --- a/transform/optimizer.go +++ b/transform/optimizer.go @@ -65,7 +65,6 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error { // Run TinyGo-specific optimization passes. OptimizeStringToBytes(mod) - OptimizeReflectImplements(mod) maxStackSize := config.MaxStackAlloc() OptimizeAllocs(mod, nil, maxStackSize, nil) err = LowerInterfaces(mod, config) diff --git a/transform/rtcalls.go b/transform/rtcalls.go index 3abc1d3952..49b138bcc9 100644 --- a/transform/rtcalls.go +++ b/transform/rtcalls.go @@ -4,8 +4,6 @@ package transform // calls. import ( - "strings" - "tinygo.org/x/go-llvm" ) @@ -100,81 +98,3 @@ func OptimizeStringEqual(mod llvm.Module) { } } } - -// OptimizeReflectImplements optimizes the following code: -// -// implements := someType.Implements(someInterfaceType) -// -// where someType is an arbitrary reflect.Type and someInterfaceType is a -// reflect.Type of interface kind, to the following code: -// -// _, implements := someType.(interfaceType) -// -// if the interface type is known at compile time (that is, someInterfaceType is -// a LLVM constant aggregate). This optimization is especially important for the -// encoding/json package, which uses this method. -// -// As of this writing, the (reflect.Type).Interface method has not yet been -// implemented so this optimization is critical for the encoding/json package. -func OptimizeReflectImplements(mod llvm.Module) { - implementsSignature1 := mod.NamedGlobal("reflect/methods.Implements(reflect.Type) bool") - implementsSignature2 := mod.NamedGlobal("reflect/methods.Implements(internal/reflectlite.Type) bool") - if implementsSignature1.IsNil() && implementsSignature2.IsNil() { - return - } - - builder := mod.Context().NewBuilder() - defer builder.Dispose() - - // 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 - } - val := attr.GetStringValue() - if val == "reflect/methods.Implements(reflect.Type) bool" || val == "reflect/methods.Implements(internal/reflectlite.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 - } - interfaceType := stripPointerCasts(call.Operand(2)) - if interfaceType.IsAGlobalVariable().IsNil() { - // Interface is unknown at compile time. This can't be optimized. - continue - } - - if strings.HasPrefix(interfaceType.Name(), "reflect/types.type:named:") { - // Get the underlying type. - interfaceType = stripPointerCasts(builder.CreateExtractValue(interfaceType.Initializer(), 3, "")) - } - 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 - } - typeAssertFunction := mod.NamedFunction(strings.TrimPrefix(interfaceType.Name(), "reflect/types.type:") + ".$typeassert") - if typeAssertFunction.IsNil() { - continue - } - - // Replace Implements call with the type assert call. - builder.SetInsertPointBefore(call) - implements := builder.CreateCall(typeAssertFunction.GlobalValueType(), typeAssertFunction, []llvm.Value{ - call.Operand(0), // typecode to check - }, "") - call.ReplaceAllUsesWith(implements) - call.EraseFromParentAsInstruction() - } -} diff --git a/transform/rtcalls_test.go b/transform/rtcalls_test.go index 9073b0ea5b..2ae1603155 100644 --- a/transform/rtcalls_test.go +++ b/transform/rtcalls_test.go @@ -22,11 +22,3 @@ func TestOptimizeStringEqual(t *testing.T) { transform.OptimizeStringEqual(mod) }) } - -func TestOptimizeReflectImplements(t *testing.T) { - t.Parallel() - testTransform(t, "testdata/reflect-implements", func(mod llvm.Module) { - // Run optimization pass. - transform.OptimizeReflectImplements(mod) - }) -} diff --git a/transform/testdata/interface.ll b/transform/testdata/interface.ll index 76ed029b47..fd2ea9a7de 100644 --- a/transform/testdata/interface.ll +++ b/transform/testdata/interface.ll @@ -7,7 +7,6 @@ target triple = "armv7m-none-eabi" @"reflect/types.typeid:basic:int16" = external constant i8 @"reflect/types.type:basic:int" = linkonce_odr constant { i8, ptr } { i8 2, ptr @"reflect/types.type:pointer:basic:int" }, align 4 @"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, ptr } { i8 21, ptr @"reflect/types.type:basic:int" }, align 4 -@"reflect/methods.NeverImplementedMethod()" = linkonce_odr constant i8 0 @"reflect/methods.Double() int" = linkonce_odr constant i8 0 @"Number$methodset" = linkonce_odr unnamed_addr constant { i32, [1 x ptr], { ptr } } { i32 1, [1 x ptr] [ptr @"reflect/methods.Double() int"], { ptr } { ptr @"(Number).Double$invoke" } } @"reflect/types.type:named:Number" = linkonce_odr constant { ptr, i8, ptr, ptr } { ptr @"Number$methodset", i8 34, ptr @"reflect/types.type:pointer:named:Number", ptr @"reflect/types.type:basic:int" }, align 4 @@ -16,10 +15,7 @@ target triple = "armv7m-none-eabi" declare i1 @runtime.typeAssert(ptr, ptr) declare void @runtime.printuint8(i8) declare void @runtime.printint16(i16) -declare void @runtime.printint32(i32) -declare void @runtime.printptr(i32) declare void @runtime.printnl() -declare void @runtime.nilPanic(ptr) define void @printInterfaces() { call void @printInterface(ptr @"reflect/types.type:basic:int", ptr inttoptr (i32 5 to ptr)) @@ -30,25 +26,6 @@ define void @printInterfaces() { } define void @printInterface(ptr %typecode, ptr %value) { - %isUnmatched = call i1 @Unmatched$typeassert(ptr %typecode) - br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched - -typeswitch.Unmatched: - %unmatched = ptrtoint ptr %value to i32 - call void @runtime.printptr(i32 %unmatched) - call void @runtime.printnl() - ret void - -typeswitch.notUnmatched: - %isDoubler = call i1 @Doubler$typeassert(ptr %typecode) - br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler - -typeswitch.Doubler: - %doubler.result = call i32 @"Doubler.Double$invoke"(ptr %value, ptr %typecode, ptr undef) - call void @runtime.printint32(i32 %doubler.result) - ret void - -typeswitch.notDoubler: %isByte = call i1 @runtime.typeAssert(ptr %typecode, ptr nonnull @"reflect/types.typeid:basic:uint8") br i1 %isByte, label %typeswitch.byte, label %typeswitch.notByte @@ -86,10 +63,4 @@ define i32 @"(Number).Double$invoke"(ptr %receiverPtr, ptr %context) { declare i32 @"Doubler.Double$invoke"(ptr %receiver, ptr %typecode, ptr %context) #0 -declare i1 @Doubler$typeassert(ptr %typecode) #1 - -declare i1 @Unmatched$typeassert(ptr %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 cb041ab1db..bbbc5d9245 100644 --- a/transform/testdata/interface.out.ll +++ b/transform/testdata/interface.out.ll @@ -12,14 +12,8 @@ declare void @runtime.printuint8(i8) declare void @runtime.printint16(i16) -declare void @runtime.printint32(i32) - -declare void @runtime.printptr(i32) - declare void @runtime.printnl() -declare void @runtime.nilPanic(ptr) - define void @printInterfaces() { call void @printInterface(ptr @"reflect/types.type:basic:int", ptr inttoptr (i32 5 to ptr)) call void @printInterface(ptr @"reflect/types.type:basic:uint8", ptr inttoptr (i8 120 to ptr)) @@ -28,35 +22,16 @@ define void @printInterfaces() { } define void @printInterface(ptr %typecode, ptr %value) { - %isUnmatched = call i1 @"Unmatched$typeassert"(ptr %typecode) - br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched - -typeswitch.Unmatched: ; preds = %0 - %unmatched = ptrtoint ptr %value to i32 - call void @runtime.printptr(i32 %unmatched) - call void @runtime.printnl() - ret void - -typeswitch.notUnmatched: ; preds = %0 - %isDoubler = call i1 @"Doubler$typeassert"(ptr %typecode) - br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler - -typeswitch.Doubler: ; preds = %typeswitch.notUnmatched - %doubler.result = call i32 @"Doubler.Double$invoke"(ptr %value, ptr %typecode, ptr undef) - call void @runtime.printint32(i32 %doubler.result) - ret void - -typeswitch.notDoubler: ; preds = %typeswitch.notUnmatched %typeassert.ok = icmp eq ptr @"reflect/types.type:basic:uint8", %typecode br i1 %typeassert.ok, label %typeswitch.byte, label %typeswitch.notByte -typeswitch.byte: ; preds = %typeswitch.notDoubler +typeswitch.byte: ; preds = %0 %byte = ptrtoint ptr %value to i8 call void @runtime.printuint8(i8 %byte) call void @runtime.printnl() ret void -typeswitch.notByte: ; preds = %typeswitch.notDoubler +typeswitch.notByte: ; preds = %0 br i1 false, label %typeswitch.int16, label %typeswitch.notInt16 typeswitch.int16: ; preds = %typeswitch.notByte @@ -79,41 +54,3 @@ define i32 @"(Number).Double$invoke"(ptr %receiverPtr, ptr %context) { %ret = call i32 @"(Number).Double"(i32 %receiver, ptr undef) ret i32 %ret } - -define internal i32 @"Doubler.Double$invoke"(ptr %receiver, ptr %actualType, ptr %context) unnamed_addr #0 { -entry: - %"named:Number.icmp" = icmp eq ptr %actualType, @"reflect/types.type:named:Number" - br i1 %"named:Number.icmp", label %"named:Number", label %"named:Number.next" - -"named:Number": ; preds = %entry - %0 = call i32 @"(Number).Double$invoke"(ptr %receiver, ptr undef) - ret i32 %0 - -"named:Number.next": ; preds = %entry - call void @runtime.nilPanic(ptr undef) - unreachable -} - -define internal i1 @"Doubler$typeassert"(ptr %actualType) unnamed_addr #1 { -entry: - %"named:Number.icmp" = icmp eq ptr %actualType, @"reflect/types.type:named:Number" - br i1 %"named:Number.icmp", label %then, label %"named:Number.next" - -then: ; preds = %entry - ret i1 true - -"named:Number.next": ; preds = %entry - ret i1 false -} - -define internal i1 @"Unmatched$typeassert"(ptr %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 deleted file mode 100644 index 46536483b2..0000000000 --- a/transform/testdata/reflect-implements.ll +++ /dev/null @@ -1,41 +0,0 @@ -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._interface = type { ptr, ptr } - -@"reflect/types.type:named:error" = internal constant { i8, i16, ptr, ptr } { i8 52, i16 0, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4 -@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = internal constant { i8, ptr } { i8 20, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4 -@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = internal constant { i8, ptr } { i8 21, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4 -@"reflect/types.type:pointer:named:error" = internal constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:named:error" }, align 4 -@"reflect/types.type:pointer:named:reflect.rawType" = internal constant { ptr, i8, i16, ptr } { ptr null, i8 21, i16 0, ptr null }, align 4 -@"reflect/methods.Implements(reflect.Type) bool" = internal constant i8 0, align 1 - -; var errorType = reflect.TypeOf((*error)(nil)).Elem() -; func isError(typ reflect.Type) bool { -; return typ.Implements(errorType) -; } -; The type itself is stored in %typ.value, %typ.typecode just refers to the -; type of reflect.Type. This function can be optimized because errorType is -; known at compile time (after the interp pass has run). -define i1 @main.isError(ptr %typ.typecode, ptr %typ.value, ptr %context) { -entry: - %result = call i1 @"reflect.Type.Implements$invoke"(ptr %typ.value, ptr getelementptr inbounds ({ ptr, i8, ptr }, ptr @"reflect/types.type:pointer:named:reflect.rawType", i32 0, i32 1), ptr @"reflect/types.type:named:error", ptr %typ.typecode, ptr undef) - ret i1 %result -} - -; This Implements method call can not be optimized because itf is not known at -; compile time. -; func isUnknown(typ, itf reflect.Type) bool { -; return typ.Implements(itf) -; } -define i1 @main.isUnknown(ptr %typ.typecode, ptr %typ.value, ptr %itf.typecode, ptr %itf.value, ptr %context) { -entry: - %result = call i1 @"reflect.Type.Implements$invoke"(ptr %typ.value, ptr %itf.typecode, ptr %itf.value, ptr %typ.typecode, ptr undef) - ret i1 %result -} - -declare i1 @"reflect.Type.Implements$invoke"(ptr, ptr, ptr, ptr, ptr) #0 -declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr %0) #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 deleted file mode 100644 index b7b759c018..0000000000 --- a/transform/testdata/reflect-implements.out.ll +++ /dev/null @@ -1,28 +0,0 @@ -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" - -@"reflect/types.type:named:error" = internal constant { i8, i16, ptr, ptr } { i8 52, i16 0, ptr @"reflect/types.type:pointer:named:error", ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4 -@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = internal constant { i8, ptr } { i8 20, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" }, align 4 -@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = internal constant { i8, ptr } { i8 21, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4 -@"reflect/types.type:pointer:named:error" = internal constant { i8, i16, ptr } { i8 21, i16 0, ptr @"reflect/types.type:named:error" }, align 4 -@"reflect/types.type:pointer:named:reflect.rawType" = internal constant { ptr, i8, i16, ptr } { ptr null, i8 21, i16 0, ptr null }, align 4 -@"reflect/methods.Implements(reflect.Type) bool" = internal constant i8 0, align 1 - -define i1 @main.isError(ptr %typ.typecode, ptr %typ.value, ptr %context) { -entry: - %0 = call i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr %typ.value) - ret i1 %0 -} - -define i1 @main.isUnknown(ptr %typ.typecode, ptr %typ.value, ptr %itf.typecode, ptr %itf.value, ptr %context) { -entry: - %result = call i1 @"reflect.Type.Implements$invoke"(ptr %typ.value, ptr %itf.typecode, ptr %itf.value, ptr %typ.typecode, ptr undef) - ret i1 %result -} - -declare i1 @"reflect.Type.Implements$invoke"(ptr, ptr, ptr, ptr, ptr) #0 - -declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr) #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" }