diff options
author | Ayke van Laethem <[email protected]> | 2022-06-24 00:09:03 +0200 |
---|---|---|
committer | Ron Evans <[email protected]> | 2023-02-17 22:54:34 +0100 |
commit | 4e8453167f42976aad87099ffdb3746fc540d6a6 (patch) | |
tree | b3acee7dc97a19219fd1a84cabaf9b9d8eba1f3a | |
parent | ebb410afd916047ee17f0e51dfba36ad3a6c002b (diff) | |
download | tinygo-4e8453167f42976aad87099ffdb3746fc540d6a6.tar.gz tinygo-4e8453167f42976aad87099ffdb3746fc540d6a6.zip |
all: refactor reflect package
This is a big commit that changes the way runtime type information is stored in
the binary. Instead of compressing it and storing it in a number of sidetables,
it is stored similar to how the Go compiler toolchain stores it (but still more
compactly).
This has a number of advantages:
* It is much easier to add new features to reflect support. They can simply
be added to these structs without requiring massive changes (especially in
the reflect lowering pass).
* It removes the reflect lowering pass, which was a large amount of hard to
understand and debug code.
* The reflect lowering pass also required merging all LLVM IR into one
module, which is terrible for performance especially when compiling large
amounts of code. See issue 2870 for details.
* It is (probably!) easier to reason about for the compiler.
The downside is that it increases code size a bit, especially when reflect is
involved. I hope to fix some of that in later patches.
27 files changed, 793 insertions, 1348 deletions
diff --git a/builder/sizes.go b/builder/sizes.go index e55970f24..dd2c5ccc9 100644 --- a/builder/sizes.go +++ b/builder/sizes.go @@ -118,10 +118,6 @@ var ( // pack: data created when storing a constant in an interface for example // string: buffer behind strings packageSymbolRegexp = regexp.MustCompile(`\$(alloc|embedfsfiles|embedfsslice|embedslice|pack|string)(\.[0-9]+)?$`) - - // Reflect sidetables. Created by the reflect lowering pass. - // See src/reflect/sidetables.go. - reflectDataRegexp = regexp.MustCompile(`^reflect\.[a-zA-Z]+Sidetable$`) ) // readProgramSizeFromDWARF reads the source location for each line of code and @@ -375,7 +371,7 @@ func loadProgramSize(path string, packagePathMap map[string]string) (*programSiz if section.Flags&elf.SHF_ALLOC == 0 { continue } - if packageSymbolRegexp.MatchString(symbol.Name) || reflectDataRegexp.MatchString(symbol.Name) { + if packageSymbolRegexp.MatchString(symbol.Name) { addresses = append(addresses, addressLine{ Address: symbol.Value, Length: symbol.Size, @@ -836,9 +832,8 @@ func findPackagePath(path string, packagePathMap map[string]string) string { } else if packageSymbolRegexp.MatchString(path) { // Parse symbol names like main$alloc or runtime$string. packagePath = path[:strings.LastIndex(path, "$")] - } else if reflectDataRegexp.MatchString(path) { - // Parse symbol names like reflect.structTypesSidetable. - packagePath = "Go reflect data" + } else if path == "<Go type>" { + packagePath = "Go types" } else if path == "<Go interface assert>" { // Interface type assert, generated by the interface lowering pass. packagePath = "Go interface assert" diff --git a/compiler/compiler.go b/compiler/compiler.go index 92e95a05a..458be2cb5 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -340,12 +340,15 @@ func CompilePackage(moduleName string, pkg *loader.Package, ssaPkg *ssa.Package, return c.mod, c.diagnostics } +func (c *compilerContext) getRuntimeType(name string) types.Type { + return c.runtimePkg.Scope().Lookup(name).(*types.TypeName).Type() +} + // getLLVMRuntimeType obtains a named type from the runtime package and returns // it as a LLVM type, creating it if necessary. It is a shorthand for // getLLVMType(getRuntimeType(name)). func (c *compilerContext) getLLVMRuntimeType(name string) llvm.Type { - typ := c.runtimePkg.Scope().Lookup(name).(*types.TypeName).Type() - return c.getLLVMType(typ) + return c.getLLVMType(c.getRuntimeType(name)) } // getLLVMType returns a LLVM type for a Go type. It doesn't recreate already diff --git a/compiler/defer.go b/compiler/defer.go index 5ec3ef7e2..99b314353 100644 --- a/compiler/defer.go +++ b/compiler/defer.go @@ -271,7 +271,7 @@ func (b *builder) createDefer(instr *ssa.Defer) { typecode := b.CreateExtractValue(itf, 0, "invoke.func.typecode") receiverValue := b.CreateExtractValue(itf, 1, "invoke.func.receiver") values = []llvm.Value{callback, next, typecode, receiverValue} - valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType) + valueTypes = append(valueTypes, b.i8ptrType, b.i8ptrType) for _, arg := range instr.Call.Args { val := b.getValue(arg) values = append(values, val) @@ -476,7 +476,7 @@ func (b *builder) createRunDefers() { valueTypes = append(valueTypes, b.getFuncType(callback.Signature())) } else { //Expect typecode - valueTypes = append(valueTypes, b.uintptrType, b.i8ptrType) + valueTypes = append(valueTypes, b.i8ptrType, b.i8ptrType) } for _, arg := range callback.Args { diff --git a/compiler/interface.go b/compiler/interface.go index 2007b7d7c..a359f33a4 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -6,6 +6,7 @@ package compiler // interface-lowering.go for more details. import ( + "fmt" "go/token" "go/types" "strconv" @@ -15,6 +16,49 @@ import ( "tinygo.org/x/go-llvm" ) +// Type kinds for basic types. +// They must match the constants for the Kind type in src/reflect/type.go. +var basicTypes = [...]uint8{ + types.Bool: 1, + types.Int: 2, + types.Int8: 3, + types.Int16: 4, + types.Int32: 5, + types.Int64: 6, + types.Uint: 7, + types.Uint8: 8, + types.Uint16: 9, + types.Uint32: 10, + types.Uint64: 11, + types.Uintptr: 12, + types.Float32: 13, + types.Float64: 14, + types.Complex64: 15, + types.Complex128: 16, + types.String: 17, + types.UnsafePointer: 18, +} + +// These must also match the constants for the Kind type in src/reflect/type.go. +const ( + typeKindChan = 19 + typeKindInterface = 20 + typeKindPointer = 21 + typeKindSlice = 22 + typeKindArray = 23 + typeKindSignature = 24 + typeKindMap = 25 + typeKindStruct = 26 +) + +// Flags stored in the first byte of the struct field byte array. Must be kept +// up to date with src/reflect/type.go. +const ( + structFieldFlagAnonymous = 1 << iota + structFieldFlagHasTag + structFieldFlagIsExported +) + // createMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction. // It tries to put the type in the interface value, but if that's not possible, // it will do an allocation of the right size and put that in the interface @@ -23,10 +67,9 @@ import ( // An interface value is a {typecode, value} tuple named runtime._interface. func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value { itfValue := b.emitPointerPack([]llvm.Value{val}) - itfTypeCodeGlobal := b.getTypeCode(typ) - itfTypeCode := b.CreatePtrToInt(itfTypeCodeGlobal, b.uintptrType, "") + itfType := b.getTypeCode(typ) itf := llvm.Undef(b.getLLVMRuntimeType("_interface")) - itf = b.CreateInsertValue(itf, itfTypeCode, 0, "") + itf = b.CreateInsertValue(itf, itfType, 0, "") itf = b.CreateInsertValue(itf, itfValue, 1, "") return itf } @@ -41,118 +84,236 @@ func (b *builder) extractValueFromInterface(itf llvm.Value, llvmType llvm.Type) } // getTypeCode returns a reference to a type code. -// It returns a pointer to an external global which should be replaced with the -// real type in the interface lowering pass. +// A type code is a pointer to a constant global that describes the type. +// This function returns a pointer to the 'kind' field (which might not be the +// first field in the struct). func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { + ms := c.program.MethodSets.MethodSet(typ) + hasMethodSet := ms.Len() != 0 + if _, ok := typ.Underlying().(*types.Interface); ok { + hasMethodSet = false + } globalName := "reflect/types.type:" + getTypeCodeName(typ) global := c.mod.NamedGlobal(globalName) if global.IsNil() { - // Create a new typecode global. - global = llvm.AddGlobal(c.mod, c.getLLVMRuntimeType("typecodeID"), globalName) - // Some type classes contain more information for underlying types or - // element types. Store it directly in the typecode global to make - // reflect lowering simpler. - var references llvm.Value - var length int64 - var methodSet llvm.Value - var ptrTo llvm.Value - var typeAssert llvm.Value + var typeFields []llvm.Value + // Define the type fields. These must match the structs in + // src/reflect/type.go (ptrType, arrayType, etc). See the comment at the + // top of src/reflect/type.go for more information on the layout of these structs. + typeFieldTypes := []*types.Var{ + types.NewVar(token.NoPos, nil, "kind", types.Typ[types.Int8]), + } switch typ := typ.(type) { + case *types.Basic: + typeFieldTypes = append(typeFieldTypes, + types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), + ) case *types.Named: - references = c.getTypeCode(typ.Underlying()) - case *types.Chan: - references = c.getTypeCode(typ.Elem()) + typeFieldTypes = append(typeFieldTypes, + types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), + types.NewVar(token.NoPos, nil, "underlying", types.Typ[types.UnsafePointer]), + ) + case *types.Chan, *types.Slice: + typeFieldTypes = append(typeFieldTypes, + types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), + types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]), + ) case *types.Pointer: - references = c.getTypeCode(typ.Elem()) - case *types.Slice: - references = c.getTypeCode(typ.Elem()) + typeFieldTypes = append(typeFieldTypes, + types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]), + ) case *types.Array: - references = c.getTypeCode(typ.Elem()) - length = typ.Len() + typeFieldTypes = append(typeFieldTypes, + types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), + types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]), + types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]), + ) + case *types.Map: + typeFieldTypes = append(typeFieldTypes, + types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), + ) case *types.Struct: - // Take a pointer to the typecodeID of the first field (if it exists). - structGlobal := c.makeStructTypeFields(typ) - references = llvm.ConstBitCast(structGlobal, global.Type()) + typeFieldTypes = append(typeFieldTypes, + types.NewVar(token.NoPos, nil, "numFields", types.Typ[types.Uint16]), + types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), + types.NewVar(token.NoPos, nil, "fields", types.NewArray(c.getRuntimeType("structField"), int64(typ.NumFields()))), + ) case *types.Interface: - methodSetGlobal := c.getInterfaceMethodSet(typ) - references = llvm.ConstBitCast(methodSetGlobal, global.Type()) - } - 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)) - } - globalValue := llvm.ConstNull(global.GlobalValueType()) - if !references.IsNil() { - globalValue = c.builder.CreateInsertValue(globalValue, references, 0, "") + typeFieldTypes = append(typeFieldTypes, + types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), + ) + // TODO: methods + case *types.Signature: + typeFieldTypes = append(typeFieldTypes, + types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), + ) + // TODO: signature params and return values } - if length != 0 { - lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false) - globalValue = c.builder.CreateInsertValue(globalValue, lengthValue, 1, "") + if hasMethodSet { + // This method set is appended at the start of the struct. It is + // removed in the interface lowering pass. + // TODO: don't remove these and instead do what upstream Go is doing + // instead. See: https://research.swtch.com/interfaces. This can + // likely be optimized in LLVM using + // https://llvm.org/docs/TypeMetadata.html. + typeFieldTypes = append([]*types.Var{ + types.NewVar(token.NoPos, nil, "methodSet", types.Typ[types.UnsafePointer]), + }, typeFieldTypes...) } - if !methodSet.IsNil() { - globalValue = c.builder.CreateInsertValue(globalValue, methodSet, 2, "") - } - if !ptrTo.IsNil() { - globalValue = c.builder.CreateInsertValue(globalValue, ptrTo, 3, "") + globalType := types.NewStruct(typeFieldTypes, nil) + global = llvm.AddGlobal(c.mod, c.getLLVMType(globalType), globalName) + metabyte := getTypeKind(typ) + switch typ := typ.(type) { + case *types.Basic: + typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))} + case *types.Named: + typeFields = []llvm.Value{ + c.getTypeCode(types.NewPointer(typ)), // ptrTo + c.getTypeCode(typ.Underlying()), // underlying + } + metabyte |= 1 << 5 // "named" flag + case *types.Chan: + typeFields = []llvm.Value{ + c.getTypeCode(types.NewPointer(typ)), // ptrTo + c.getTypeCode(typ.Elem()), // elementType + } + case *types.Slice: + typeFields = []llvm.Value{ + c.getTypeCode(types.NewPointer(typ)), // ptrTo + c.getTypeCode(typ.Elem()), // elementType + } + case *types.Pointer: + typeFields = []llvm.Value{c.getTypeCode(typ.Elem())} + case *types.Array: + typeFields = []llvm.Value{ + c.getTypeCode(types.NewPointer(typ)), // ptrTo + c.getTypeCode(typ.Elem()), // elementType + llvm.ConstInt(c.uintptrType, uint64(typ.Len()), false), // length + } + case *types.Map: + typeFields = []llvm.Value{ + c.getTypeCode(types.NewPointer(typ)), // ptrTo + } + case *types.Struct: + typeFields = []llvm.Value{ + llvm.ConstInt(c.ctx.Int16Type(), uint64(typ.NumFields()), false), // numFields + c.getTypeCode(types.NewPointer(typ)), // ptrTo + } + structFieldType := c.getLLVMRuntimeType("structField") + var fields []llvm.Value + for i := 0; i < typ.NumFields(); i++ { + field := typ.Field(i) + var flags uint8 + if field.Anonymous() { + flags |= structFieldFlagAnonymous + } + if typ.Tag(i) != "" { + flags |= structFieldFlagHasTag + } + if token.IsExported(field.Name()) { + flags |= structFieldFlagIsExported + } + data := string(flags) + field.Name() + "\x00" + if typ.Tag(i) != "" { + if len(typ.Tag(i)) > 0xff { + c.addError(field.Pos(), fmt.Sprintf("struct tag is %d bytes which is too long, max is 255", len(typ.Tag(i)))) + } + data += string([]byte{byte(len(typ.Tag(i)))}) + typ.Tag(i) + } + dataInitializer := c.ctx.ConstString(data, false) + dataGlobal := llvm.AddGlobal(c.mod, dataInitializer.Type(), globalName+"."+field.Name()) + dataGlobal.SetInitializer(dataInitializer) + dataGlobal.SetAlignment(1) + dataGlobal.SetUnnamedAddr(true) + dataGlobal.SetLinkage(llvm.InternalLinkage) + dataGlobal.SetGlobalConstant(true) + fieldType := c.getTypeCode(field.Type()) + fields = append(fields, llvm.ConstNamedStruct(structFieldType, []llvm.Value{ + fieldType, + llvm.ConstGEP(dataGlobal.GlobalValueType(), dataGlobal, []llvm.Value{ + llvm.ConstInt(c.ctx.Int32Type(), 0, false), + llvm.ConstInt(c.ctx.Int32Type(), 0, false), + }), + })) + } + typeFields = append(typeFields, llvm.ConstArray(structFieldType, fields)) + case *types.Interface: + typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))} + // TODO: methods + case *types.Signature: + typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))} + // TODO: params, return values, etc } - if !typeAssert.IsNil() { - globalValue = c.builder.CreateInsertValue(globalValue, typeAssert, 4, "") + // Prepend metadata byte. + typeFields = append([]llvm.Value{ + llvm.ConstInt(c.ctx.Int8Type(), uint64(metabyte), false), + }, typeFields...) + if hasMethodSet { + typeFields = append([]llvm.Value{ + llvm.ConstBitCast(c.getTypeMethodSet(typ), c.i8ptrType), + }, typeFields...) } + alignment := c.targetData.TypeAllocSize(c.i8ptrType) + globalValue := c.ctx.ConstStruct(typeFields, false) global.SetInitializer(globalValue) global.SetLinkage(llvm.LinkOnceODRLinkage) global.SetGlobalConstant(true) + global.SetAlignment(int(alignment)) + if c.Debug { + file := c.getDIFile("<Go type>") + diglobal := c.dibuilder.CreateGlobalVariableExpression(file, llvm.DIGlobalVariableExpression{ + Name: "type " + typ.String(), + File: file, + Line: 1, + Type: c.getDIType(globalType), + LocalToUnit: false, + Expr: c.dibuilder.CreateExpression(nil), + AlignInBits: uint32(alignment * 8), + }) + global.AddMetadata(0, diglobal) + } } - return global + offset := uint64(0) + if hasMethodSet { + // The pointer to the method set is always the first element of the + // global (if there is a method set). However, the pointer we return + // should point to the 'kind' field not the method set. + offset = 1 + } + return llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{ + llvm.ConstInt(llvm.Int32Type(), 0, false), + llvm.ConstInt(llvm.Int32Type(), offset, false), + }) } -// makeStructTypeFields creates a new global that stores all type information -// related to this struct type, and returns the resulting global. This global is -// actually an array of all the fields in the structs. -func (c *compilerContext) makeStructTypeFields(typ *types.Struct) llvm.Value { - // The global is an array of runtime.structField structs. - runtimeStructField := c.getLLVMRuntimeType("structField") - structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields()) - structGlobal := llvm.AddGlobal(c.mod, structGlobalType, "reflect/types.structFields") - structGlobalValue := llvm.ConstNull(structGlobalType) - for i := 0; i < typ.NumFields(); i++ { - fieldGlobalValue := llvm.ConstNull(runtimeStructField) - fieldGlobalValue = c.builder.CreateInsertValue(fieldGlobalValue, c.getTypeCode(typ.Field(i).Type()), 0, "") - fieldNameType, fieldName := c.makeGlobalArray([]byte(typ.Field(i).Name()), "reflect/types.structFieldName", c.ctx.Int8Type()) - fieldName.SetLinkage(llvm.PrivateLinkage) - fieldName.SetUnnamedAddr(true) - fieldName = llvm.ConstGEP(fieldNameType, fieldName, []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - }) - fieldGlobalValue = c.builder.CreateInsertValue(fieldGlobalValue, fieldName, 1, "") - if typ.Tag(i) != "" { - fieldTagType, fieldTag := c.makeGlobalArray([]byte(typ.Tag(i)), "reflect/types.structFieldTag", c.ctx.Int8Type()) - fieldTag.SetLinkage(llvm.PrivateLinkage) - fieldTag.SetUnnamedAddr(true) - fieldTag = llvm.ConstGEP(fieldTagType, fieldTag, []llvm.Value{ - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - llvm.ConstInt(c.ctx.Int32Type(), 0, false), - }) - fieldGlobalValue = c.builder.CreateInsertValue(fieldGlobalValue, fieldTag, 2, "") - } - if typ.Field(i).Embedded() { - fieldEmbedded := llvm.ConstInt(c.ctx.Int1Type(), 1, false) - fieldGlobalValue = c.builder.CreateInsertValue(fieldGlobalValue, fieldEmbedded, 3, "") - } - structGlobalValue = c.builder.CreateInsertValue(structGlobalValue, fieldGlobalValue, i, "") +// getTypeKind returns the type kind for the given type, as defined by +// reflect.Kind. +func getTypeKind(t types.Type) uint8 { + switch t := t.Underlying().(type) { + case *types.Basic: + return basicTypes[t.Kind()] + case *types.Chan: + return typeKindChan + case *types.Interface: + return typeKindInterface + case *types.Pointer: + return typeKindPointer + case *types.Slice: + return typeKindSlice + case *types.Array: + return typeKindArray + case *types.Signature: + return typeKindSignature + case *types.Map: + return typeKindMap + case *types.Struct: + return typeKindStruct + default: + panic("unknown type") } - structGlobal.SetInitializer(structGlobalValue) - structGlobal.SetUnnamedAddr(true) - structGlobal.SetLinkage(llvm.PrivateLinkage) - return structGlobal } -var basicTypes = [...]string{ +var basicTypeNames = [...]string{ types.Bool: "bool", types.Int: "int", types.Int8: "int8", @@ -183,7 +344,7 @@ func getTypeCodeName(t types.Type) string { case *types.Array: return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem()) case *types.Basic: - return "basic:" + basicTypes[t.Kind()] + return "basic:" + basicTypeNames[t.Kind()] case *types.Chan: return "chan:" + getTypeCodeName(t.Elem()) case *types.Interface: @@ -235,75 +396,40 @@ func getTypeCodeName(t types.Type) string { // getTypeMethodSet returns a reference (GEP) to a global method set. This // method set should be unreferenced after the interface lowering pass. func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { - global := c.mod.NamedGlobal(typ.String() + "$methodset") - zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) - if !global.IsNil() { - // the method set already exists - return llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{zero, zero}) - } - - ms := c.program.MethodSets.MethodSet(typ) - if ms.Len() == 0 { - // no methods, so can leave that one out - return llvm.ConstPointerNull(llvm.PointerType(c.getLLVMRuntimeType("interfaceMethodInfo"), 0)) - } - - methods := make([]llvm.Value, ms.Len()) - interfaceMethodInfoType := c.getLLVMRuntimeType("interfaceMethodInfo") - for i := 0; i < ms.Len(); i++ { - method := ms.At(i) - signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func)) - fn := c.program.MethodValue(method) - llvmFnType, llvmFn := c.getFunction(fn) - if llvmFn.IsNil() { - // compiler error, so panic - panic("cannot find function: " + c.getFunctionInfo(fn).linkName) + globalName := typ.String() + "$methodset" + global := c.mod.NamedGlobal(globalName) + if global.IsNil() { + ms := c.program.MethodSets.MethodSet(typ) + + // Create method set. + var signatures, wrappers []llvm.Value + for i := 0; i < ms.Len(); i++ { + method := ms.At(i) + signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func)) + signatures = append(signatures, signatureGlobal) + fn := c.program.MethodValue(method) + llvmFnType, llvmFn := c.getFunction(fn) + if llvmFn.IsNil() { + // compiler error, so panic + panic("cannot find function: " + c.getFunctionInfo(fn).linkName) + } + wrapper := c.getInterfaceInvokeWrapper(fn, llvmFnType, llvmFn) + wrappers = append(wrappers, wrapper) } - wrapper := c.getInterfaceInvokeWrapper(fn, llvmFnType, llvmFn) - methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{ - signatureGlobal, - llvm.ConstPtrToInt(wrapper, c.uintptrType), - }) - methods[i] = methodInfo - } - arrayType := llvm.ArrayType(interfaceMethodInfoType, len(methods)) - value := llvm.ConstArray(interfaceMethodInfoType, methods) - global = llvm.AddGlobal(c.mod, arrayType, typ.String()+"$methodset") - global.SetInitializer(value) - global.SetGlobalConstant(true) - global.SetLinkage(llvm.LinkOnceODRLinkage) - return llvm.ConstGEP(arrayType, global, []llvm.Value{zero, zero}) -} -// getInterfaceMethodSet returns a global variable with the method set of the -// given named interface type. This method set is used by the interface lowering -// pass. -func (c *compilerContext) getInterfaceMethodSet(typ types.Type) llvm.Value { - name := typ.String() - if _, ok := typ.(*types.Named); !ok { - // Anonymous interface. - name = "reflect/types.interface:" + name - } - global := c.mod.NamedGlobal(name + "$interface") - zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false) - if !global.IsNil() { - // method set already exist, return it - return llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{zero, zero}) - } - - // Every method is a *i8 reference indicating the signature of this method. - methods := make([]llvm.Value, typ.Underlying().(*types.Interface).NumMethods()) - for i := range methods { - method := typ.Underlying().(*types.Interface).Method(i) - methods[i] = c.getMethodSignature(method) + // Construct global value. + globalValue := c.ctx.ConstStruct([]llvm.Value{ + llvm.ConstInt(c.uintptrType, uint64(ms.Len()), false), + llvm.ConstArray(c.i8ptrType, signatures), + c.ctx.ConstStruct(wrappers, false), + }, false) + global = llvm.AddGlobal(c.mod, globalValue.Type(), globalName) + global.SetInitializer(globalValue) + global.SetGlobalConstant(true) + global.SetUnnamedAddr(true) + global.SetLinkage(llvm.LinkOnceODRLinkage) } - - value := llvm.ConstArray(c.i8ptrType, methods) - global = llvm.AddGlobal(c.mod, value.Type(), name+"$interface") - global.SetInitializer(value) - global.SetGlobalConstant(true) - global.SetLinkage(llvm.LinkOnceODRLinkage) - return llvm.ConstGEP(value.Type(), global, []llvm.Value{zero, zero}) + return global } // getMethodSignatureName returns a unique name (that can be used as the name of @@ -443,7 +569,7 @@ func (c *compilerContext) getInterfaceImplementsFunc(assertedType types.Type) ll fnName := getTypeCodeName(assertedType.Underlying()) + ".$typeassert" llvmFn := c.mod.NamedFunction(fnName) if llvmFn.IsNil() { - llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.uintptrType}, false) + llvmFnType := llvm.FunctionType(c.ctx.Int1Type(), []llvm.Type{c.i8ptrType}, false) llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) c.addStandardDeclaredAttributes(llvmFn) methods := c.getMethodsString(assertedType.Underlying().(*types.Interface)) @@ -464,7 +590,7 @@ func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value { 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])) + paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.UnsafePointer])) llvmFnType := c.getRawFuncType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false)) llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) c.addStandardDeclaredAttributes(llvmFn) @@ -601,7 +727,7 @@ func typestring(t types.Type) string { case *types.Array: return "[" + strconv.FormatInt(t.Len(), 10) + "]" + typestring(t.Elem()) case *types.Basic: - return basicTypes[t.Kind()] + return basicTypeNames[t.Kind()] case *types.Chan: switch t.Dir() { case types.SendRecv: diff --git a/compiler/testdata/defer-cortex-m-qemu.ll b/compiler/testdata/defer-cortex-m-qemu.ll index 0841e2550..a99a64edc 100644 --- a/compiler/testdata/defer-cortex-m-qemu.ll +++ b/compiler/testdata/defer-cortex-m-qemu.ll @@ -4,7 +4,7 @@ target datalayout = "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64" target triple = "thumbv7m-unknown-unknown-eabi" %runtime.deferFrame = type { ptr, ptr, [0 x ptr], ptr, i1, %runtime._interface } -%runtime._interface = type { i32, ptr } +%runtime._interface = type { ptr, ptr } %runtime._defer = type { i32, ptr } declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 diff --git a/compiler/testdata/gc.ll b/compiler/testdata/gc.ll index a59a546fb..b833958ab 100644 --- a/compiler/testdata/gc.ll +++ b/compiler/testdata/gc.ll @@ -3,8 +3,7 @@ source_filename = "gc.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" -%runtime.typecodeID = type { ptr, i32, ptr, ptr, i32 } -%runtime._interface = type { i32, ptr } +%runtime._interface = type { ptr, ptr } @main.scalar1 = hidden global ptr null, align 4 @main.scalar2 = hidden global ptr null, align 4 @@ -22,8 +21,8 @@ target triple = "wasm32-unknown-wasi" @main.slice3 = hidden global { ptr, i32, i32 } zeroinitializer, align 8 @"runtime/gc.layout:62-2000000000000001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00 " } @"runtime/gc.layout:62-0001" = linkonce_odr unnamed_addr constant { i32, [8 x i8] } { i32 62, [8 x i8] c"\01\00\00\00\00\00\00\00" } -@"reflect/types.type:basic:complex128" = linkonce_odr constant %runtime.typecodeID { ptr null, i32 0, ptr null, ptr @"reflect/types.type:pointer:basic:complex128", i32 0 } -@"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant %runtime.typecodeID { ptr @"reflect/types.type:basic:complex128", i32 0, ptr null, ptr null, i32 0 } +@"reflect/types.type:basic:complex128" = linkonce_odr constant { i8, ptr } { i8 16, ptr @"reflect/types.type:pointer:basic:complex128" }, align 4 +@"reflect/types.type:pointer:basic:complex128" = linkonce_odr constant { i8, ptr } { i8 21, ptr @"reflect/types.type:basic:complex128" }, align 4 declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 @@ -129,7 +128,8 @@ entry: store double %v.r, ptr %0, align 8 %.repack1 = getelementptr inbounds { double, double }, ptr %0, i32 0, i32 1 store double %v.i, ptr %.repack1, align 8 - %1 = insertvalue %runtime._interface { i32 ptrtoint (ptr @"reflect/types.type:basic:complex128" to i32), ptr undef }, ptr %0, 1 + %1 = insertvalue %runtime._interface { ptr @"reflect/types.type:basic:complex128", ptr undef }, ptr %0, 1 + call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:complex128", ptr nonnull %stackalloc, ptr undef) #2 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #2 ret %runtime._interface %1 } diff --git a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll index ac1adaffe..2fe1b06af 100644 --- a/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll +++ b/compiler/testdata/goroutine-cortex-m-qemu-tasks.ll @@ -145,34 +145,34 @@ entry: declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #0 ; Function Attrs: nounwind -define hidden void @main.startInterfaceMethod(i32 %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { +define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { entry: %0 = call ptr @runtime.alloc(i32 16, ptr null, ptr undef) #8 store ptr %itf.value, ptr %0, align 4 - %1 = getelementptr inbounds { ptr, %runtime._string, i32 }, ptr %0, i32 0, i32 1 + %1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1 store ptr @"main$string", ptr %1, align 4 - %.repack1 = getelementptr inbounds { ptr, %runtime._string, i32 }, ptr %0, i32 0, i32 1, i32 1 + %.repack1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1, i32 1 store i32 4, ptr %.repack1, align 4 - %2 = getelementptr inbounds { ptr, %runtime._string, i32 }, ptr %0, i32 0, i32 2 - store i32 %itf.typecode, ptr %2, align 4 + %2 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 2 + store ptr %itf.typecode, ptr %2, align 4 %stacksize = call i32 @"internal/task.getGoroutineStackSize"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr undef) #8 call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 %stacksize, ptr undef) #8 ret void } -declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, i32, ptr) #6 +declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, ptr, ptr) #6 ; Function Attrs: nounwind define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #7 { entry: %1 = load ptr, ptr %0, align 4 - %2 = getelementptr inbounds { ptr, ptr, i32, i32 }, ptr %0, i32 0, i32 1 + %2 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 1 %3 = load ptr, ptr %2, align 4 - %4 = getelementptr inbounds { ptr, ptr, i32, i32 }, ptr %0, i32 0, i32 2 + %4 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 2 %5 = load i32, ptr %4, align 4 - %6 = getelementptr inbounds { ptr, ptr, i32, i32 }, ptr %0, i32 0, i32 3 - %7 = load i32, ptr %6, align 4 - call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, i32 %7, ptr undef) #8 + %6 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 3 + %7 = load ptr, ptr %6, align 4 + call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #8 ret void } diff --git a/compiler/testdata/goroutine-wasm-asyncify.ll b/compiler/testdata/goroutine-wasm-asyncify.ll index 0f38e181e..d3ec398a4 100644 --- a/compiler/testdata/goroutine-wasm-asyncify.ll +++ b/compiler/testdata/goroutine-wasm-asyncify.ll @@ -154,35 +154,35 @@ entry: declare void @runtime.chanClose(ptr dereferenceable_or_null(32), ptr) #0 ; Function Attrs: nounwind -define hidden void @main.startInterfaceMethod(i32 %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { +define hidden void @main.startInterfaceMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 %0 = call ptr @runtime.alloc(i32 16, ptr null, ptr undef) #8 call void @runtime.trackPointer(ptr nonnull %0, ptr nonnull %stackalloc, ptr undef) #8 store ptr %itf.value, ptr %0, align 4 - %1 = getelementptr inbounds { ptr, %runtime._string, i32 }, ptr %0, i32 0, i32 1 + %1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1 store ptr @"main$string", ptr %1, align 4 - %.repack1 = getelementptr inbounds { ptr, %runtime._string, i32 }, ptr %0, i32 0, i32 1, i32 1 + %.repack1 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 1, i32 1 store i32 4, ptr %.repack1, align 4 - %2 = getelementptr inbounds { ptr, %runtime._string, i32 }, ptr %0, i32 0, i32 2 - store i32 %itf.typecode, ptr %2, align 4 + %2 = getelementptr inbounds { ptr, %runtime._string, ptr }, ptr %0, i32 0, i32 2 + store ptr %itf.typecode, ptr %2, align 4 call void @"internal/task.start"(i32 ptrtoint (ptr @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper" to i32), ptr nonnull %0, i32 16384, ptr undef) #8 ret void } -declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, i32, ptr) #6 +declare void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr, ptr, i32, ptr, ptr) #6 ; Function Attrs: nounwind define linkonce_odr void @"interface:{Print:func:{basic:string}{}}.Print$invoke$gowrapper"(ptr %0) unnamed_addr #7 { entry: %1 = load ptr, ptr %0, align 4 - %2 = getelementptr inbounds { ptr, ptr, i32, i32 }, ptr %0, i32 0, i32 1 + %2 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 1 %3 = load ptr, ptr %2, align 4 - %4 = getelementptr inbounds { ptr, ptr, i32, i32 }, ptr %0, i32 0, i32 2 + %4 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 2 %5 = load i32, ptr %4, align 4 - %6 = getelementptr inbounds { ptr, ptr, i32, i32 }, ptr %0, i32 0, i32 3 - %7 = load i32, ptr %6, align 4 - call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, i32 %7, ptr undef) #8 + %6 = getelementptr inbounds { ptr, ptr, i32, ptr }, ptr %0, i32 0, i32 3 + %7 = load ptr, ptr %6, align 4 + call void @"interface:{Print:func:{basic:string}{}}.Print$invoke"(ptr %1, ptr %3, i32 %5, ptr %7, ptr undef) #8 call void @runtime.deadlock(ptr undef) #8 unreachable } diff --git a/compiler/testdata/interface.ll b/compiler/testdata/interface.ll index 2ddaf4ec3..55f189cdf 100644 --- a/compiler/testdata/interface.ll +++ b/compiler/testdata/interface.ll @@ -3,22 +3,17 @@ source_filename = "interface.go" target datalayout = "e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-n32:64-S128-ni:1:10:20" target triple = "wasm32-unknown-wasi" -%runtime.typecodeID = type { ptr, i32, ptr, ptr, i32 } -%runtime._interface = type { i32, ptr } +%runtime._interface = type { ptr, ptr } %runtime._string = type { ptr, i32 } -@"reflect/types.type:basic:int" = linkonce_odr constant %runtime.typecodeID { ptr null, i32 0, ptr null, ptr @"reflect/types.type:pointer:basic:int", i32 0 } -@"reflect/types.type:pointer:basic:int" = linkonce_odr constant %runtime.typecodeID { ptr @"reflect/types.type:basic:int", i32 0, ptr null, ptr null, i32 0 } -@"reflect/types.type:pointer:named:error" = linkonce_odr constant %runtime.typecodeID { ptr @"reflect/types.type:named:error", i32 0, ptr null, ptr null, i32 0 } -@"reflect/types.type:named:error" = linkonce_odr constant %runtime.typecodeID { ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, ptr null, ptr @"reflect/types.type:pointer:named:error", i32 ptrtoint (ptr @"interface:{Error:func:{}{basic:string}}.$typeassert" to i32) } -@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { ptr @"reflect/types.interface:interface{Error() string}$interface", i32 0, ptr null, ptr @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}", i32 ptrtoint (ptr @"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 ptr] [ptr @"reflect/methods.Error() string"] -@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, ptr null, ptr null, i32 0 } -@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { ptr @"reflect/types.type:interface:{String:func:{}{basic:string}}", i32 0, ptr null, ptr null, i32 0 } -@"reflect/types.type:interface:{String:func:{}{basic:string}}" = linkonce_odr constant %runtime.typecodeID { ptr @"reflect/types.interface:interface{String() string}$interface", i32 0, ptr null, ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", i32 ptrtoint (ptr @"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 ptr] [ptr @"reflect/methods.String() string"] +@"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/types.type:pointer:named:error" = linkonce_odr constant { i8, ptr } { i8 21, ptr @"reflect/types.type:named:error" }, align 4 +@"reflect/types.type:named:error" = linkonce_odr constant { i8, ptr, ptr } { i8 52, 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}}" = linkonce_odr 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}}" = linkonce_odr constant { i8, ptr } { i8 21, ptr @"reflect/types.type:interface:{Error:func:{}{basic:string}}" }, align 4 +@"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" = linkonce_odr constant { i8, ptr } { i8 21, 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 20, ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" }, align 4 @"reflect/types.typeid:basic:int" = external constant i8 declare noalias nonnull ptr @runtime.alloc(i32, ptr, ptr) #0 @@ -35,42 +30,42 @@ entry: define hidden %runtime._interface @main.simpleType(ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 + call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:basic:int", ptr nonnull %stackalloc, ptr undef) #6 call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #6 - ret %runtime._interface { i32 ptrtoint (ptr @"reflect/types.type:basic:int" to i32), ptr null } + ret %runtime._interface { ptr @"reflect/types.type:basic:int", ptr null } } ; Function Attrs: nounwind define hidden %runtime._interface @main.pointerType(ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 + call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:pointer:basic:int", ptr nonnull %stackalloc, ptr undef) #6 call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #6 - ret %runtime._interface { i32 ptrtoint (ptr @"reflect/types.type:pointer:basic:int" to i32), ptr null } + ret %runtime._interface { ptr @"reflect/types.type:pointer:basic:int", ptr null } } ; Function Attrs: nounwind define hidden %runtime._interface @main.interfaceType(ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 + call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:pointer:named:error", ptr nonnull %stackalloc, ptr undef) #6 call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #6 - ret %runtime._interface { i32 ptrtoint (ptr @"reflect/types.type:pointer:named:error" to i32), ptr null } + ret %runtime._interface { ptr @"reflect/types.type:pointer:named:error", ptr null } } -declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i32) #2 - ; Function Attrs: nounwind define hidden %runtime._interface @main.anonymousInterfaceType(ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 + call void @runtime.trackPointer(ptr nonnull @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", ptr nonnull %stackalloc, ptr undef) #6 call void @runtime.trackPointer(ptr null, ptr nonnull %stackalloc, ptr undef) #6 - ret %runtime._interface { i32 ptrtoint (ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}" to i32), ptr null } + ret %runtime._interface { ptr @"reflect/types.type:pointer:interface:{String:func:{}{basic:string}}", ptr null } } -declare i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(i32) #3 - ; Function Attrs: nounwind -define hidden i1 @main.isInt(i32 %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { +define hidden i1 @main.isInt(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { entry: - %typecode = call i1 @runtime.typeAssert(i32 %itf.typecode, ptr nonnull @"reflect/types.typeid:basic:int", ptr undef) #6 + %typecode = call i1 @runtime.typeAssert(ptr %itf.typecode, ptr nonnull @"reflect/types.typeid:basic:int", ptr undef) #6 br i1 %typecode, label %typeassert.ok, label %typeassert.next typeassert.next: ; preds = %typeassert.ok, %entry @@ -80,12 +75,12 @@ typeassert.ok: ; preds = %entry br label %typeassert.next } -declare i1 @runtime.typeAssert(i32, ptr dereferenceable_or_null(1), ptr) #0 +declare i1 @runtime.typeAssert(ptr, ptr dereferenceable_or_null(1), ptr) #0 ; Function Attrs: nounwind -define hidden i1 @main.isError(i32 %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { +define hidden i1 @main.isError(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { entry: - %0 = call i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i32 %itf.typecode) #6 + %0 = call i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr %itf.typecode) #6 br i1 %0, label %typeassert.ok, label %typeassert.next typeassert.next: ; preds = %typeassert.ok, %entry @@ -95,10 +90,12 @@ typeassert.ok: ; preds = %entry br label %typeassert.next } +declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(ptr) #2 + ; Function Attrs: nounwind -define hidden i1 @main.isStringer(i32 %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { +define hidden i1 @main.isStringer(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { entry: - %0 = call i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(i32 %itf.typecode) #6 + %0 = call i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(ptr %itf.typecode) #6 br i1 %0, label %typeassert.ok, label %typeassert.next typeassert.next: ; preds = %typeassert.ok, %entry @@ -108,26 +105,28 @@ typeassert.ok: ; preds = %entry br label %typeassert.next } +declare i1 @"interface:{String:func:{}{basic:string}}.$typeassert"(ptr) #3 + ; Function Attrs: nounwind -define hidden i8 @main.callFooMethod(i32 %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { +define hidden i8 @main.callFooMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { entry: - %0 = call i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(ptr %itf.value, i32 3, i32 %itf.typecode, ptr undef) #6 + %0 = call i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(ptr %itf.value, i32 3, ptr %itf.typecode, ptr undef) #6 ret i8 %0 } -declare i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(ptr, i32, i32, ptr) #4 +declare i8 @"interface:{String:func:{}{basic:string},main.foo:func:{basic:int}{basic:uint8}}.foo$invoke"(ptr, i32, ptr, ptr) #4 ; Function Attrs: nounwind -define hidden %runtime._string @main.callErrorMethod(i32 %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { +define hidden %runtime._string @main.callErrorMethod(ptr %itf.typecode, ptr %itf.value, ptr %context) unnamed_addr #1 { entry: %stackalloc = alloca i8, align 1 - %0 = call %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(ptr %itf.value, i32 %itf.typecode, ptr undef) #6 + %0 = call %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(ptr %itf.value, ptr %itf.typecode, ptr undef) #6 %1 = extractvalue %runtime._string %0, 0 call void @runtime.trackPointer(ptr %1, ptr nonnull %stackalloc, ptr undef) #6 ret %runtime._string %0 } -declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(ptr, i32, ptr) #5 +declare %runtime._string @"interface:{Error:func:{}{basic:string}}.Error$invoke"(ptr, ptr, ptr) #5 attributes #0 = { "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } attributes #1 = { nounwind "target-features"="+bulk-memory,+nontrapping-fptoint,+sign-ext" } diff --git a/interp/interpreter.go b/interp/interpreter.go index c61ce7cf3..7c58a5d20 100644 --- a/interp/interpreter.go +++ b/interp/interpreter.go @@ -238,7 +238,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // which case this call won't even get to this point but will // already be emitted in initAll. continue - case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet" || + case strings.HasPrefix(callFn.name, "runtime.print") || callFn.name == "runtime._panic" || callFn.name == "runtime.hashmapGet" || callFn.name == "runtime.hashmapInterfaceHash" || callFn.name == "os.runtime_args" || callFn.name == "internal/task.start" || callFn.name == "internal/task.Current": // These functions should be run at runtime. Specifically: // * Print and panic functions are best emitted directly without @@ -378,42 +378,6 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent copy(dstBuf.buf[dst.offset():dst.offset()+nBytes], srcBuf.buf[src.offset():]) dstObj.buffer = dstBuf mem.put(dst.index(), dstObj) - case callFn.name == "(reflect.rawType).elem": - if r.debug { - fmt.Fprintln(os.Stderr, indent+"call (reflect.rawType).elem:", operands[1:]) - } - // Extract the type code global from the first parameter. - typecodeIDPtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem) - if err != nil { - return nil, mem, r.errorAt(inst, err) - } - typecodeID := typecodeIDPtrToInt.Operand(0) - - // Get the type class. - // See also: getClassAndValueFromTypeCode in transform/reflect.go. - typecodeName := typecodeID.Name() - const prefix = "reflect/types.type:" - if !strings.HasPrefix(typecodeName, prefix) { - panic("unexpected typecode name: " + typecodeName) - } - id := typecodeName[len(prefix):] - class := id[:strings.IndexByte(id, ':')] - value := id[len(class)+1:] - if class == "named" { - // Get the underlying type. - class = value[:strings.IndexByte(value, ':')] - value = value[len(class)+1:] - } - - // Elem() is only valid for certain type classes. - switch class { - case "chan", "pointer", "slice", "array": - elementType := r.builder.CreateExtractValue(typecodeID.Initializer(), 0, "") - uintptrType := r.mod.Context().IntType(int(mem.r.pointerSize) * 8) - locals[inst.localIndex] = r.getValue(llvm.ConstPtrToInt(elementType, uintptrType)) - default: - return nil, mem, r.errorAt(inst, fmt.Errorf("(reflect.Type).Elem() called on %s type", class)) - } case callFn.name == "runtime.typeAssert": // This function must be implemented manually as it is normally // implemented by the interface lowering pass. @@ -424,15 +388,22 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent if err != nil { return nil, mem, r.errorAt(inst, err) } - actualTypePtrToInt, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem) + actualType, err := operands[1].toLLVMValue(inst.llvmInst.Operand(0).Type(), &mem) if err != nil { return nil, mem, r.errorAt(inst, err) } - if !actualTypePtrToInt.IsAConstantInt().IsNil() && actualTypePtrToInt.ZExtValue() == 0 { + if !actualType.IsAConstantInt().IsNil() && actualType.ZExtValue() == 0 { locals[inst.localIndex] = literalValue{uint8(0)} break } - actualType := actualTypePtrToInt.Operand(0) + // Strip pointer casts (bitcast, getelementptr). + for !actualType.IsAConstantExpr().IsNil() { + opcode := actualType.Opcode() + if opcode != llvm.GetElementPtr && opcode != llvm.BitCast { + break + } + actualType = actualType.Operand(0) + } if strings.TrimPrefix(actualType.Name(), "reflect/types.type:") == strings.TrimPrefix(assertedType.Name(), "reflect/types.typeid:") { locals[inst.localIndex] = literalValue{uint8(1)} } else { @@ -448,11 +419,12 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent if err != nil { return nil, mem, r.errorAt(inst, err) } - methodSetPtr, err := mem.load(typecodePtr.addOffset(r.pointerSize*2), r.pointerSize).asPointer(r) + methodSetPtr, err := mem.load(typecodePtr.addOffset(-int64(r.pointerSize)), 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() @@ -460,9 +432,9 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // 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 < methodSet.Type().ArrayLength(); i++ { - methodInfo := r.builder.CreateExtractValue(methodSet, i, "") - name := r.builder.CreateExtractValue(methodInfo, 0, "").Name() + for i := 0; i < numMethods; i++ { + methodInfo := r.builder.CreateExtractValue(methodSet, 1, "") + name := r.builder.CreateExtractValue(methodInfo, i, "").Name() concreteTypeMethods[name] = struct{}{} } @@ -488,15 +460,16 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent fmt.Fprintln(os.Stderr, indent+"invoke method:", operands[1:]) } - // Load the type code of the interface value. - typecodeIDBitCast, err := operands[len(operands)-2].toLLVMValue(inst.llvmInst.Operand(len(operands)-3).Type(), &mem) + // Load the type code and method set of the interface value. + typecodePtr, err := operands[len(operands)-2].asPointer(r) if err != nil { return nil, mem, r.errorAt(inst, err) } - typecodeID := typecodeIDBitCast.Operand(0).Initializer() - - // Load the method set, which is part of the typecodeID object. - methodSet := stripPointerCasts(r.builder.CreateExtractValue(typecodeID, 2, "")).Initializer() + methodSetPtr, err := mem.load(typecodePtr.addOffset(-int64(r.pointerSize)), r.pointerSize).asPointer(r) + if err != nil { + return nil, mem, r.errorAt(inst, err) + } + methodSet := mem.get(methodSetPtr.index()).llvmGlobal.Initializer() // We don't need to load the interface method set. @@ -508,13 +481,14 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent // Iterate through all methods, looking for the one method that // should be returned. - numMethods := methodSet.Type().ArrayLength() + numMethods := int(r.builder.CreateExtractValue(methodSet, 0, "").ZExtValue()) var method llvm.Value for i := 0; i < numMethods; i++ { - methodSignatureAgg := r.builder.CreateExtractValue(methodSet, i, "") - methodSignature := r.builder.CreateExtractValue(methodSignatureAgg, 0, "") + methodSignatureAgg := r.builder.CreateExtractValue(methodSet, 1, "") + methodSignature := r.builder.CreateExtractValue(methodSignatureAgg, i, "") if methodSignature == signature { - method = r.builder.CreateExtractValue(methodSignatureAgg, 1, "").Operand(0) + methodAgg := r.builder.CreateExtractValue(methodSet, 2, "") + method = r.builder.CreateExtractValue(methodAgg, i, "") } } if method.IsNil() { @@ -685,7 +659,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent } continue } - ptr = ptr.addOffset(uint32(offset)) + ptr = ptr.addOffset(int64(offset)) locals[inst.localIndex] = ptr if r.debug { fmt.Fprintln(os.Stderr, indent+"gep:", operands, "->", ptr) @@ -784,7 +758,7 @@ func (r *runner) run(fn *function, params []value, parentMem *memoryView, indent case llvm.Add: // This likely means this is part of a // unsafe.Pointer(uintptr(ptr) + offset) pattern. - lhsPtr = lhsPtr.addOffset(uint32(rhs.Uint())) + lhsPtr = lhsPtr.addOffset(int64(rhs.Uint())) locals[inst.localIndex] = lhsPtr continue case llvm.Xor: diff --git a/interp/memory.go b/interp/memory.go index 1f9ed99f3..9a28f1d49 100644 --- a/interp/memory.go +++ b/interp/memory.go @@ -501,7 +501,7 @@ func (v pointerValue) offset() uint32 { // addOffset essentially does a GEP operation (pointer arithmetic): it adds the // offset to the pointer. It also checks that the offset doesn't overflow the // maximum offset size (which is 4GB). -func (v pointerValue) addOffset(offset uint32) pointerValue { +func (v pointerValue) addOffset(offset int64) pointerValue { result := pointerValue{v.pointer + uint64(offset)} if checks && v.index() != result.index() { panic("interp: offset out of range") @@ -815,7 +815,7 @@ func (v rawValue) rawLLVMValue(mem *memoryView) (llvm.Value, error) { // as a ptrtoint, so that they can be used in certain // optimizations. name := elementType.StructName() - if name == "runtime.typecodeID" || name == "runtime.funcValueWithSignature" { + if name == "runtime.funcValueWithSignature" { uintptrType := ctx.IntType(int(mem.r.pointerSize) * 8) field = llvm.ConstPtrToInt(field, uintptrType) } diff --git a/interp/testdata/interface.ll b/interp/testdata/interface.ll index 6520efc5c..da27ad8a0 100644 --- a/interp/testdata/interface.ll +++ b/interp/testdata/interface.ll @@ -1,17 +1,16 @@ target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128" target triple = "x86_64--linux" -%runtime.typecodeID = type { %runtime.typecodeID*, i64, %runtime.interfaceMethodInfo* } -%runtime.interfaceMethodInfo = type { i8*, i64 } - @main.v1 = global i1 0 @main.v2 = global i1 0 -@"reflect/types.type:named:main.foo" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i64 0, %runtime.interfaceMethodInfo* null } +@"reflect/types.type:named:main.foo" = private constant { i8, i8*, i8* } { i8 34, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:named:main.foo", i32 0, i32 0), i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:int", i32 0, i32 0) }, align 4 +@"reflect/types.type:pointer:named:main.foo" = external constant { i8, i8* } @"reflect/types.typeid:named:main.foo" = external constant i8 -@"reflect/types.type:basic:int" = external constant %runtime.typecodeID +@"reflect/types.type:basic:int" = private constant { i8, i8* } { i8 2, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:basic:int", i32 0, i32 0) }, align 4 +@"reflect/types.type:pointer:basic:int" = external constant { i8, i8* } -declare i1 @runtime.typeAssert(i64, i8*, i8*, i8*) +declare i1 @runtime.typeAssert(i8*, i8*, i8*, i8*) define void @runtime.initAll() unnamed_addr { entry: @@ -22,9 +21,9 @@ entry: define internal void @main.init() unnamed_addr { entry: ; Test type asserts. - %typecode = call i1 @runtime.typeAssert(i64 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:main.foo" to i64), i8* @"reflect/types.typeid:named:main.foo", i8* undef, i8* null) + %typecode = call i1 @runtime.typeAssert(i8* getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:main.foo", i32 0, i32 0), i8* @"reflect/types.typeid:named:main.foo", i8* undef, i8* null) store i1 %typecode, i1* @main.v1 - %typecode2 = call i1 @runtime.typeAssert(i64 0, i8* @"reflect/types.typeid:named:main.foo", i8* undef, i8* null) + %typecode2 = call i1 @runtime.typeAssert(i8* null, i8* @"reflect/types.typeid:named:main.foo", i8* undef, i8* null) store i1 %typecode2, i1* @main.v2 ret void } diff --git a/src/reflect/deepequal.go b/src/reflect/deepequal.go index f84ddc8b5..18a728458 100644 --- a/src/reflect/deepequal.go +++ b/src/reflect/deepequal.go @@ -15,7 +15,7 @@ import "unsafe" type visit struct { a1 unsafe.Pointer a2 unsafe.Pointer - typ rawType + typ *rawType } // Tests for deep equality using reflected types. The map argument tracks diff --git a/src/reflect/sidetables.go b/src/reflect/sidetables.go deleted file mode 100644 index ea26ff767..000000000 --- a/src/reflect/sidetables.go +++ /dev/null @@ -1,61 +0,0 @@ -package reflect - -import ( - "unsafe" -) - -// This stores a varint for each named type. Named types are identified by their -// name instead of by their type. The named types stored in this struct are -// non-basic types: pointer, struct, and channel. -// -//go:extern reflect.namedNonBasicTypesSidetable -var namedNonBasicTypesSidetable uintptr - -//go:extern reflect.structTypesSidetable -var structTypesSidetable byte - -//go:extern reflect.structNamesSidetable -var structNamesSidetable byte - -//go:extern reflect.arrayTypesSidetable -var arrayTypesSidetable byte - -// readStringSidetable reads a string from the given table (like -// structNamesSidetable) and returns this string. No heap allocation is -// necessary because it makes the string point directly to the raw bytes of the -// table. -func readStringSidetable(table unsafe.Pointer, index uintptr) string { - nameLen, namePtr := readVarint(unsafe.Pointer(uintptr(table) + index)) - return *(*string)(unsafe.Pointer(&stringHeader{ - data: namePtr, - len: nameLen, - })) -} - -// readVarint decodes a varint as used in the encoding/binary package. -// It has an input pointer and returns the read varint and the pointer -// incremented to the next field in the data structure, just after the varint. -// -// Details: -// https://github.com/golang/go/blob/e37a1b1c/src/encoding/binary/varint.go#L7-L25 -func readVarint(buf unsafe.Pointer) (uintptr, unsafe.Pointer) { - var n uintptr - shift := uintptr(0) - for { - // Read the next byte in the buffer. - c := *(*byte)(buf) - - // Decode the bits from this byte and add them to the output number. - n |= uintptr(c&0x7f) << shift - shift += 7 - - // Increment the buf pointer (pointer arithmetic!). - buf = unsafe.Pointer(uintptr(buf) + 1) - - // Check whether this is the last byte of this varint. The upper bit - // (msb) indicates whether any bytes follow. - if c>>7 == 0 { - return n, buf - } - } -} diff --git a/src/reflect/type.go b/src/reflect/type.go index a5e63f355..cc106a70d 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -2,36 +2,72 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// Type information of an interface is stored as a pointer to a global in the +// interface type (runtime._interface). This is called a type struct. +// It always starts with a byte that contains both the type kind and a few +// flags. In most cases it also contains a pointer to another type struct +// (ptrTo), that is the pointer type of the current type (for example, type int +// also has a pointer to the type *int). The exception is pointer types, to +// avoid infinite recursion. +// +// The layouts specifically look like this: +// - basic types (Bool..UnsafePointer): +// meta uint8 // actually: kind + flags +// ptrTo *typeStruct +// - named types (see elemType): +// meta uint8 +// ptrTo *typeStruct +// underlying *typeStruct // the underlying, non-named type +// - channels and slices (see elemType): +// meta uint8 +// ptrTo *typeStruct +// elementType *typeStruct // the type that you get with .Elem() +// - pointer types (see ptrType, this doesn't include chan, map, etc): +// meta uint8 +// elementType *typeStruct +// - array types (see arrayType) +// meta uint8 +// ptrTo *typeStruct +// elem *typeStruct // element type of the array +// arrayLen uintptr // length of the array (this is part of the type) +// - map types (this is still missing the key and element types) +// meta uint8 +// ptrTo *typeStruct +// - struct types (see structType): +// meta uint8 +// numField uint16 +// ptrTo *typeStruct +// fields [...]structField // the remaining fields are all of type structField +// - interface types (this is missing the interface methods): +// meta uint8 +// ptrTo *typeStruct +// - signature types (this is missing input and output parameters): +// meta uint8 +// ptrTo *typeStruct +// +// The type struct is essentially a union of all the above types. Which it is, +// can be determined by looking at the meta byte. + package reflect import ( "unsafe" ) -// The compiler uses a compact encoding to store type information. Unlike the -// main Go compiler, most of the types are stored directly in the type code. -// -// Type code bit allocation: -// xxxxx0: basic types, where xxxxx is the basic type number (never 0). -// The higher bits indicate the named type, if any. -// nxxx1: complex types, where n indicates whether this is a named type (named -// if set) and xxx contains the type kind number: -// 0 (0001): Chan -// 1 (0011): Interface -// 2 (0101): Pointer -// 3 (0111): Slice -// 4 (1001): Array -// 5 (1011): Func -// 6 (1101): Map -// 7 (1111): Struct -// The higher bits are either the contents of the type depending on the -// type (if n is clear) or indicate the number of the named type (if n -// is set). - -type Kind uintptr +// Flags stored in the first byte of the struct field byte array. Must be kept +// up to date with compiler/interface.go. +const ( + structFieldFlagAnonymous = 1 << iota + structFieldFlagHasTag + structFieldFlagIsExported +) + +type Kind uint8 // Copied from reflect/type.go // https://golang.org/src/reflect/type.go?s=8302:8316#L217 +// These constants must match basicTypes and the typeKind* constants in +// compiler/interface.go const ( Invalid Kind = iota Bool @@ -124,11 +160,6 @@ func (k Kind) String() string { } } -// basicType returns a new Type for this kind if Kind is a basic type. -func (k Kind) basicType() rawType { - return rawType(k << 1) -} - // Copied from reflect/type.go // https://go.dev/src/reflect/type.go?#L348 @@ -346,8 +377,64 @@ type Type interface { Out(i int) Type } -// The typecode as used in an interface{}. -type rawType uintptr +// Constants for the 'meta' byte. +const ( + kindMask = 31 // mask to apply to the meta byte to get the Kind value + flagNamed = 32 // flag that is set if this is a named type +) + +// The base type struct. All type structs start with this. +type rawType struct { + meta uint8 // metadata byte, contains kind and flags (see contants above) +} + +// All types that have an element type: named, chan, slice, array, map (but not +// pointer because it doesn't have ptrTo). +type elemType struct { + rawType + ptrTo *rawType + elem *rawType +} + +type ptrType struct { + rawType + elem *rawType +} + +type arrayType struct { + rawType + ptrTo *rawType + elem *rawType + arrayLen uintptr +} + +// Type for struct types. The numField value is intentionally put before ptrTo +// for better struct packing on 32-bit and 64-bit architectures. On these +// architectures, the ptrTo field still has the same offset as in all the other +// type structs. +// 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. +type structType struct { + rawType + numField uint16 + ptrTo *rawType + fields [1]structField // the remaining fields are all of type structField +} + +type structField struct { + fieldType *rawType + data unsafe.Pointer // various bits of information, packed in a byte array +} + +// 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 { + if t.meta&flagNamed != 0 { + return (*elemType)(unsafe.Pointer(t)).elem + } + return t +} func TypeOf(i interface{}) Type { return ValueOf(i).typecode @@ -356,70 +443,45 @@ func TypeOf(i interface{}) Type { func PtrTo(t Type) Type { return PointerTo(t) } func PointerTo(t Type) Type { - if t.Kind() == Pointer { + switch t.Kind() { + case Pointer: panic("reflect: cannot make **T type") + case Struct: + return (*structType)(unsafe.Pointer(t.(*rawType))).ptrTo + default: + return (*elemType)(unsafe.Pointer(t.(*rawType))).ptrTo } - ptrType := t.(rawType)<<5 | 5 // 0b0101 == 5 - if ptrType>>5 != t { - panic("reflect: PointerTo type does not fit") - } - return ptrType } -func (t rawType) String() string { +func (t *rawType) String() string { return "T" } -func (t rawType) Kind() Kind { - if t%2 == 0 { - // basic type - return Kind((t >> 1) % 32) - } else { - return Kind(t>>1)%8 + 19 - } +func (t *rawType) Kind() Kind { + return Kind(t.meta & kindMask) } // Elem returns the element type for channel, slice and array types, the // pointed-to value for pointer types, and the key type for map types. -func (t rawType) Elem() Type { +func (t *rawType) Elem() Type { return t.elem() } -func (t rawType) elem() rawType { - switch t.Kind() { - case Chan, Pointer, Slice: - return t.stripPrefix() - case Array: - index := t.stripPrefix() - elem, _ := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&arrayTypesSidetable)) + uintptr(index))) - return rawType(elem) +func (t *rawType) elem() *rawType { + underlying := t.underlying() + switch underlying.Kind() { + case Pointer: + return (*ptrType)(unsafe.Pointer(underlying)).elem + case Chan, Slice, Array: + return (*elemType)(unsafe.Pointer(underlying)).elem default: // not implemented: Map panic("unimplemented: (reflect.Type).Elem()") } } -// stripPrefix removes the "prefix" (the low 5 bits of the type code) from -// the type code. If this is a named type, it will resolve the underlying type -// (which is the data for this named type). If it is not, the lower bits are -// simply shifted off. -// -// The behavior is only defined for non-basic types. -func (t rawType) stripPrefix() rawType { - // Look at the 'n' bit in the type code (see the top of this file) to see - // whether this is a named type. - if (t>>4)%2 != 0 { - // This is a named type. The data is stored in a sidetable. - namedTypeNum := t >> 5 - n := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&namedNonBasicTypesSidetable)) + uintptr(namedTypeNum)*unsafe.Sizeof(uintptr(0)))) - return rawType(n) - } - // Not a named type, so the value is stored directly in the type code. - return t >> 5 -} - // Field returns the type of the i'th field of this struct type. It panics if t // is not a struct type. -func (t rawType) Field(i int) StructField { +func (t *rawType) Field(i int) StructField { field := t.rawField(i) return StructField{ Name: field.Name, @@ -435,82 +497,87 @@ func (t rawType) Field(i int) StructField { // Type member to an interface. // // For internal use only. -func (t rawType) rawField(i int) rawStructField { +func (t *rawType) rawField(n int) rawStructField { if t.Kind() != Struct { panic(&TypeError{"Field"}) } - structIdentifier := t.stripPrefix() - - numField, p := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&structTypesSidetable)) + uintptr(structIdentifier))) - if uint(i) >= uint(numField) { + descriptor := (*structType)(unsafe.Pointer(t.underlying())) + if uint(n) >= uint(descriptor.numField) { panic("reflect: field index out of range") } - // Iterate over every field in the struct and update the StructField each - // time, until the target field has been reached. This is very much not - // efficient, but it is easy to implement. - // Adding a jump table at the start to jump to the field directly would - // make this much faster, but that would also impact code size. - field := rawStructField{} - offset := uintptr(0) - for fieldNum := 0; fieldNum <= i; fieldNum++ { - // Read some flags of this field, like whether the field is an - // embedded field. - flagsByte := *(*uint8)(p) - p = unsafe.Pointer(uintptr(p) + 1) - - // Read the type of this struct field. - var fieldTypeVal uintptr - fieldTypeVal, p = readVarint(p) - fieldType := rawType(fieldTypeVal) - field.Type = fieldType - - // Move Offset forward to align it to this field's alignment. - // Assume alignment is a power of two. - offset = align(offset, uintptr(fieldType.Align())) - field.Offset = offset - offset += fieldType.Size() // starting (unaligned) offset for next field - - // Read the field name. - var nameNum uintptr - nameNum, p = readVarint(p) - field.Name = readStringSidetable(unsafe.Pointer(&structNamesSidetable), nameNum) - - // The first bit in the flagsByte indicates whether this is an embedded - // field. - field.Anonymous = flagsByte&1 != 0 - - // The second bit indicates whether there is a tag. - if flagsByte&2 != 0 { - // There is a tag. - var tagNum uintptr - tagNum, p = readVarint(p) - field.Tag = StructTag(readStringSidetable(unsafe.Pointer(&structNamesSidetable), tagNum)) - } else { - // There is no tag. - field.Tag = "" - } + // Iterate over all the fields to calculate the offset. + // This offset could have been stored directly in the array (to make the + // lookup faster), but by calculating it on-the-fly a bit of storage can be + // saved. + field := &descriptor.fields[0] + var offset uintptr = 0 + for i := 0; i < n; i++ { + offset += field.fieldType.Size() - // The third bit indicates whether this field is exported. - if flagsByte&4 != 0 { - // This field is exported. - field.PkgPath = "" - } else { - // This field is unexported. - // TODO: list the real package path here. Storing it should not - // significantly impact binary size as there is only a limited - // number of packages in any program. - field.PkgPath = "<unimplemented>" - } + // Increment pointer to the next field. + field = (*structField)(unsafe.Add(unsafe.Pointer(field), unsafe.Sizeof(structField{}))) + + // Align the offset for the next field. + offset = align(offset, uintptr(field.fieldType.Align())) } - return field + data := field.data + + // Read some flags of this field, like whether the field is an embedded + // field. See structFieldFlagAnonymous and similar flags. + flagsByte := *(*byte)(data) + data = unsafe.Add(data, 1) + + // Read the field name. + nameStart := data + var nameLen uintptr + for *(*byte)(data) != 0 { + nameLen++ + data = unsafe.Add(data, 1) // C: data++ + } + name := *(*string)(unsafe.Pointer(&stringHeader{ + data: nameStart, + len: nameLen, + })) + + // Read the field tag, if there is one. + var tag string + if flagsByte&structFieldFlagHasTag != 0 { + data = unsafe.Add(data, 1) // C: data+1 + tagLen := uintptr(*(*byte)(data)) + data = unsafe.Add(data, 1) // C: data+1 + tag = *(*string)(unsafe.Pointer(&stringHeader{ + data: data, + len: tagLen, + })) + } + + // Set the PkgPath to some (arbitrary) value if the package path is not + // exported. + pkgPath := "" + if flagsByte&structFieldFlagIsExported == 0 { + // This field is unexported. + // TODO: list the real package path here. Storing it should not + // significantly impact binary size as there is only a limited + // number of packages in any program. + pkgPath = "<unimplemented>" + } + + return rawStructField{ + Name: name, + PkgPath: pkgPath, + Type: field.fieldType, + Tag: StructTag(tag), + Anonymous: flagsByte&structFieldFlagAnonymous != 0, + Offset: offset, + } } // Bits returns the number of bits that this type uses. It is only valid for // arithmetic types (integers, floats, and complex numbers). For other types, it // will panic. -func (t rawType) Bits() int { +func (t *rawType) Bits() int { kind := t.Kind() if kind >= Int && kind <= Complex128 { return int(t.Size()) * 8 @@ -520,34 +587,26 @@ func (t rawType) Bits() int { // Len returns the number of elements in this array. It panics of the type kind // is not Array. -func (t rawType) Len() int { +func (t *rawType) Len() int { if t.Kind() != Array { panic(TypeError{"Len"}) } - // skip past the element type - arrayIdentifier := t.stripPrefix() - _, p := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&arrayTypesSidetable)) + uintptr(arrayIdentifier))) - - // Read the array length. - arrayLen, _ := readVarint(p) - return int(arrayLen) + return int((*arrayType)(unsafe.Pointer(t.underlying())).arrayLen) } // NumField returns the number of fields of a struct type. It panics for other // type kinds. -func (t rawType) NumField() int { +func (t *rawType) NumField() int { if t.Kind() != Struct { panic(&TypeError{"NumField"}) } - structIdentifier := t.stripPrefix() - n, _ := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&structTypesSidetable)) + uintptr(structIdentifier))) - return int(n) + return int((*structType)(unsafe.Pointer(t.underlying())).numField) } // Size returns the size in bytes of a given type. It is similar to // unsafe.Sizeof. -func (t rawType) Size() uintptr { +func (t *rawType) Size() uintptr { switch t.Kind() { case Bool, Int8, Uint8: return 1 @@ -596,7 +655,7 @@ func (t rawType) Size() uintptr { // Align returns the alignment of this type. It is similar to calling // unsafe.Alignof. -func (t rawType) Align() int { +func (t *rawType) Align() int { switch t.Kind() { case Bool, Int8, Uint8: return int(unsafe.Alignof(int8(0))) @@ -648,14 +707,14 @@ func (t rawType) Align() int { // FieldAlign returns the alignment if this type is used in a struct field. It // is currently an alias for Align() but this might change in the future. -func (t rawType) FieldAlign() int { +func (t *rawType) FieldAlign() int { return t.Align() } // 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) { +func (t *rawType) AssignableTo(u Type) bool { + if t == u.(*rawType) { return true } if u.Kind() == Interface { @@ -664,7 +723,7 @@ func (t rawType) AssignableTo(u Type) bool { return false } -func (t rawType) Implements(u Type) bool { +func (t *rawType) Implements(u Type) bool { if u.Kind() != Interface { panic("reflect: non-interface type passed to Type.Implements") } @@ -672,7 +731,7 @@ func (t rawType) Implements(u Type) bool { } // Comparable returns whether values of this type can be compared to each other. -func (t rawType) Comparable() bool { +func (t *rawType) Comparable() bool { switch t.Kind() { case Bool, Int, Int8, Int16, Int32, Int64, Uint, Uint8, Uint16, Uint32, Uint64, Uintptr: return true @@ -713,31 +772,31 @@ func (t rawType) ChanDir() ChanDir { panic("unimplemented: (reflect.Type).ChanDir()") } -func (t rawType) ConvertibleTo(u Type) bool { +func (t *rawType) ConvertibleTo(u Type) bool { panic("unimplemented: (reflect.Type).ConvertibleTo()") } -func (t rawType) IsVariadic() bool { +func (t *rawType) IsVariadic() bool { panic("unimplemented: (reflect.Type).IsVariadic()") } -func (t rawType) NumIn() int { +func (t *rawType) NumIn() int { panic("unimplemented: (reflect.Type).NumIn()") } -func (t rawType) NumOut() int { +func (t *rawType) NumOut() int { panic("unimplemented: (reflect.Type).NumOut()") } -func (t rawType) NumMethod() int { +func (t *rawType) NumMethod() int { panic("unimplemented: (reflect.Type).NumMethod()") } -func (t rawType) Name() string { +func (t *rawType) Name() string { panic("unimplemented: (reflect.Type).Name()") } -func (t rawType) Key() Type { +func (t *rawType) Key() Type { panic("unimplemented: (reflect.Type).Key()") } @@ -792,7 +851,7 @@ func (f StructField) IsExported() bool { type rawStructField struct { Name string PkgPath string - Type rawType + Type *rawType Tag StructTag Anonymous bool Offset uintptr diff --git a/src/reflect/value.go b/src/reflect/value.go index fee321721..32898f0d2 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -17,7 +17,7 @@ const ( ) type Value struct { - typecode rawType + typecode *rawType value unsafe.Pointer flags valueFlags } @@ -44,15 +44,15 @@ func Indirect(v Value) Value { } //go:linkname composeInterface runtime.composeInterface -func composeInterface(rawType, unsafe.Pointer) interface{} +func composeInterface(unsafe.Pointer, unsafe.Pointer) interface{} //go:linkname decomposeInterface runtime.decomposeInterface -func decomposeInterface(i interface{}) (rawType, unsafe.Pointer) +func decomposeInterface(i interface{}) (unsafe.Pointer, unsafe.Pointer) func ValueOf(i interface{}) Value { typecode, value := decomposeInterface(i) return Value{ - typecode: typecode, + typecode: (*rawType)(typecode), value: value, flags: valueFlagExported, } @@ -85,7 +85,7 @@ func valueInterfaceUnsafe(v Value) interface{} { } v.value = unsafe.Pointer(value) } - return composeInterface(v.typecode, v.value) + return composeInterface(unsafe.Pointer(v.typecode), v.value) } func (v Value) Type() Type { @@ -136,7 +136,7 @@ func (v Value) IsZero() bool { // // RawType returns the raw, underlying type code. It is used in the runtime // package and needs to be exported for the runtime package to access it. -func (v Value) RawType() rawType { +func (v Value) RawType() *rawType { return v.typecode } @@ -205,7 +205,7 @@ func (v Value) pointer() unsafe.Pointer { } func (v Value) IsValid() bool { - return v.typecode != 0 + return v.typecode != nil } func (v Value) CanInterface() bool { @@ -453,7 +453,7 @@ func (v Value) Elem() Value { case Interface: typecode, value := decomposeInterface(*(*interface{})(v.value)) return Value{ - typecode: typecode, + typecode: (*rawType)(typecode), value: value, flags: v.flags &^ valueFlagIndirect, } @@ -523,6 +523,8 @@ func (v Value) Field(i int) Value { } } +var uint8Type = TypeOf(uint8(0)).(*rawType) + func (v Value) Index(i int) Value { switch v.Kind() { case Slice: @@ -550,7 +552,7 @@ func (v Value) Index(i int) Value { panic("reflect: string index out of range") } return Value{ - typecode: Uint8.basicType(), + typecode: uint8Type, value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(uintptr(s.data) + uintptr(i))))), flags: v.flags & valueFlagExported, } @@ -803,7 +805,7 @@ func Zero(typ Type) Value { // new value of the given type. func New(typ Type) Value { return Value{ - typecode: PtrTo(typ).(rawType), + typecode: PtrTo(typ).(*rawType), value: alloc(typ.Size(), nil), flags: valueFlagExported, } diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index 684d9cfbf..2db09f590 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -506,7 +506,7 @@ func hashmapFloat64Hash(ptr unsafe.Pointer, seed uintptr) uint32 { func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 { x := reflect.ValueOf(itf) - if x.RawType() == 0 { + if x.RawType() == nil { return 0 // nil interface } diff --git a/src/runtime/interface.go b/src/runtime/interface.go index 63fd69ec8..8718c140a 100644 --- a/src/runtime/interface.go +++ b/src/runtime/interface.go @@ -11,17 +11,17 @@ import ( ) type _interface struct { - typecode uintptr + typecode unsafe.Pointer value unsafe.Pointer } //go:inline -func composeInterface(typecode uintptr, value unsafe.Pointer) _interface { +func composeInterface(typecode, value unsafe.Pointer) _interface { return _interface{typecode, value} } //go:inline -func decomposeInterface(i _interface) (uintptr, unsafe.Pointer) { +func decomposeInterface(i _interface) (unsafe.Pointer, unsafe.Pointer) { return i.typecode, i.value } @@ -34,7 +34,7 @@ func reflectValueEqual(x, y reflect.Value) bool { // Note: doing a x.Type() == y.Type() comparison would not work here as that // would introduce an infinite recursion: comparing two reflect.Type values // is done with this reflectValueEqual runtime call. - if x.RawType() == 0 || y.RawType() == 0 { + if x.RawType() == nil || y.RawType() == nil { // One of them is nil. return x.RawType() == y.RawType() } @@ -94,48 +94,13 @@ func interfaceTypeAssert(ok bool) { // lowered to inline IR in the interface lowering pass. // See compiler/interface-lowering.go for details. -type interfaceMethodInfo struct { - signature *uint8 // external *i8 with a name identifying the Go function signature - funcptr uintptr // bitcast from the actual function pointer -} - -type typecodeID struct { - // Depending on the type kind of this typecodeID, this pointer is something - // different: - // * basic types: null - // * named type: the underlying type - // * interface: null - // * chan/pointer/slice/array: the element type - // * struct: bitcast of global with structField array - // * func/map: TODO - references *typecodeID - - // The array length, for array types. - length uintptr - - methodSet *interfaceMethodInfo // nil or a GEP of an array - - // The type that's a pointer to this type, nil if it is already a pointer. - // 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 -// lowering pass. It is not used in the final binary. type structField struct { - typecode *typecodeID // type of this struct field - name *uint8 // pointer to char array - tag *uint8 // pointer to char array, or nil - embedded bool + typecode unsafe.Pointer // type of this struct field + data *uint8 // pointer to byte array containing name, tag, and 'embedded' flag } // Pseudo function call used during a type assert. It is used during interface // lowering, to assign the lowest type numbers to the types with the most type // asserts. Also, it is replaced with const false if this type assert can never // happen. -func typeAssert(actualType uintptr, assertedType *uint8) bool +func typeAssert(actualType unsafe.Pointer, assertedType *uint8) bool diff --git a/transform/interface-lowering.go b/transform/interface-lowering.go index 55d1af39b..e57463715 100644 --- a/transform/interface-lowering.go +++ b/transform/interface-lowering.go @@ -13,8 +13,7 @@ package transform // // 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. +// switch. // // interface type assert: // These functions are defined by creating a big type switch over all the @@ -54,10 +53,11 @@ type methodInfo struct { // typeInfo describes a single concrete Go type, which can be a basic or a named // type. If it is a named type, it may have methods. type typeInfo struct { - name string - typecode llvm.Value - methodSet llvm.Value - methods []*methodInfo + name string + typecode llvm.Value + typecodeGEP llvm.Value + methodSet llvm.Value + methods []*methodInfo } // getMethod looks up the method on this type with the given signature and @@ -91,6 +91,8 @@ type lowerInterfacesPass struct { difiles map[string]llvm.Metadata ctx llvm.Context uintptrType llvm.Type + targetData llvm.TargetData + i8ptrType llvm.Type types map[string]*typeInfo signatures map[string]*signatureInfo interfaces map[string]*interfaceInfo @@ -101,14 +103,17 @@ type lowerInterfacesPass struct { // before LLVM can work on them. This is done so that a few cleanup passes can // run before assigning the final type codes. func LowerInterfaces(mod llvm.Module, config *compileopts.Config) error { + ctx := mod.Context() targetData := llvm.NewTargetData(mod.DataLayout()) defer targetData.Dispose() p := &lowerInterfacesPass{ mod: mod, config: config, - builder: mod.Context().NewBuilder(), - ctx: mod.Context(), + builder: ctx.NewBuilder(), + ctx: ctx, + targetData: targetData, uintptrType: mod.Context().IntType(targetData.PointerSize() * 8), + i8ptrType: llvm.PointerType(ctx.Int8Type(), 0), types: make(map[string]*typeInfo), signatures: make(map[string]*signatureInfo), interfaces: make(map[string]*interfaceInfo), @@ -151,11 +156,26 @@ func (p *lowerInterfacesPass) run() error { } p.types[name] = t initializer := global.Initializer() - if initializer.IsNil() { - continue + firstField := p.builder.CreateExtractValue(initializer, 0, "") + if firstField.Type() != p.ctx.Int8Type() { + // This type has a method set at index 0. Change the GEP to + // point to index 1 (the meta byte). + t.typecodeGEP = llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{ + llvm.ConstInt(p.ctx.Int32Type(), 0, false), + llvm.ConstInt(p.ctx.Int32Type(), 1, false), + }) + methodSet := stripPointerCasts(firstField) + if !strings.HasSuffix(methodSet.Name(), "$methodset") { + panic("expected method set") + } + p.addTypeMethods(t, methodSet) + } else { + // This type has no method set. + t.typecodeGEP = llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{ + llvm.ConstInt(p.ctx.Int32Type(), 0, false), + llvm.ConstInt(p.ctx.Int32Type(), 0, false), + }) } - methodSet := p.builder.CreateExtractValue(initializer, 2, "") - p.addTypeMethods(t, methodSet) } } } @@ -266,10 +286,10 @@ func (p *lowerInterfacesPass) run() error { actualType := use.Operand(0) name := strings.TrimPrefix(use.Operand(1).Name(), "reflect/types.typeid:") if t, ok := p.types[name]; ok { - // The type exists in the program, so lower to a regular integer + // The type exists in the program, so lower to a regular pointer // comparison. p.builder.SetInsertPointBefore(use) - commaOk := p.builder.CreateICmp(llvm.IntEQ, llvm.ConstPtrToInt(t.typecode, p.uintptrType), actualType, "typeassert.ok") + commaOk := p.builder.CreateICmp(llvm.IntEQ, t.typecodeGEP, actualType, "typeassert.ok") use.ReplaceAllUsesWith(commaOk) } else { // The type does not exist in the program, so lower to a constant @@ -283,15 +303,45 @@ func (p *lowerInterfacesPass) run() error { } // Remove all method sets, which are now unnecessary and inhibit later - // 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) + // optimizations if they are left in place. + zero := llvm.ConstInt(p.ctx.Int32Type(), 0, false) for _, t := range p.types { - initializer := t.typecode.Initializer() - methodSet := p.builder.CreateExtractValue(initializer, 2, "") - initializer = p.builder.CreateInsertValue(initializer, llvm.ConstNull(methodSet.Type()), 2, "") - initializer = p.builder.CreateInsertValue(initializer, zeroUintptr, 4, "") - t.typecode.SetInitializer(initializer) + if !t.methodSet.IsNil() { + initializer := t.typecode.Initializer() + var newInitializerFields []llvm.Value + for i := 1; i < initializer.Type().StructElementTypesCount(); i++ { + newInitializerFields = append(newInitializerFields, p.builder.CreateExtractValue(initializer, i, "")) + } + newInitializer := p.ctx.ConstStruct(newInitializerFields, false) + typecodeName := t.typecode.Name() + newGlobal := llvm.AddGlobal(p.mod, newInitializer.Type(), typecodeName+".tmp") + newGlobal.SetInitializer(newInitializer) + newGlobal.SetLinkage(t.typecode.Linkage()) + newGlobal.SetGlobalConstant(true) + newGlobal.SetAlignment(t.typecode.Alignment()) + for _, use := range getUses(t.typecode) { + if !use.IsAConstantExpr().IsNil() { + opcode := use.Opcode() + if opcode == llvm.GetElementPtr && use.OperandsCount() == 3 { + if use.Operand(1).ZExtValue() == 0 && use.Operand(2).ZExtValue() == 1 { + gep := p.builder.CreateInBoundsGEP(newGlobal.GlobalValueType(), newGlobal, []llvm.Value{zero, zero}, "") + use.ReplaceAllUsesWith(gep) + } + } + } + } + // Fallback. + if hasUses(t.typecode) { + bitcast := llvm.ConstBitCast(newGlobal, p.i8ptrType) + negativeOffset := -int64(p.targetData.TypeAllocSize(p.i8ptrType)) + gep := p.builder.CreateInBoundsGEP(p.ctx.Int8Type(), bitcast, []llvm.Value{llvm.ConstInt(p.ctx.Int32Type(), uint64(negativeOffset), true)}, "") + bitcast2 := llvm.ConstBitCast(gep, t.typecode.Type()) + t.typecode.ReplaceAllUsesWith(bitcast2) + } + t.typecode.EraseFromParentAsGlobal() + newGlobal.SetName(typecodeName) + t.typecode = newGlobal + } } return nil @@ -301,22 +351,22 @@ func (p *lowerInterfacesPass) run() error { // retrieves the signatures and the references to the method functions // themselves for later type<->interface matching. func (p *lowerInterfacesPass) addTypeMethods(t *typeInfo, methodSet llvm.Value) { - if !t.methodSet.IsNil() || methodSet.IsNull() { + if !t.methodSet.IsNil() { // no methods or methods already read return } - if !methodSet.IsAConstantExpr().IsNil() && methodSet.Opcode() == llvm.GetElementPtr { - methodSet = methodSet.Operand(0) // get global from GEP, for LLVM 14 (non-opaque pointers) - } // This type has methods, collect all methods of this type. t.methodSet = methodSet set := methodSet.Initializer() // get value from global - for i := 0; i < set.Type().ArrayLength(); i++ { - methodData := p.builder.CreateExtractValue(set, i, "") - signatureGlobal := p.builder.CreateExtractValue(methodData, 0, "") + signatures := p.builder.CreateExtractValue(set, 1, "") + wrappers := p.builder.CreateExtractValue(set, 2, "") + numMethods := signatures.Type().ArrayLength() + for i := 0; i < numMethods; i++ { + signatureGlobal := p.builder.CreateExtractValue(signatures, i, "") + function := p.builder.CreateExtractValue(wrappers, i, "") + function = stripPointerCasts(function) // strip bitcasts signatureName := signatureGlobal.Name() - function := p.builder.CreateExtractValue(methodData, 1, "").Operand(0) signature := p.getSignature(signatureName) method := &methodInfo{ function: function, @@ -401,7 +451,7 @@ func (p *lowerInterfacesPass) defineInterfaceImplementsFunc(fn llvm.Value, itf * actualType := fn.Param(0) for _, typ := range itf.types { nextBlock := p.ctx.AddBasicBlock(fn, typ.name+".next") - cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, llvm.ConstPtrToInt(typ.typecode, p.uintptrType), typ.name+".icmp") + cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, typ.typecodeGEP, typ.name+".icmp") p.builder.CreateCondBr(cmp, thenBlock, nextBlock) p.builder.SetInsertPointAtEnd(nextBlock) } @@ -440,7 +490,7 @@ func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *inte params[i] = fn.Param(i + 1) } params = append(params, - llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)), + llvm.Undef(p.i8ptrType), ) // Start chain in the entry block. @@ -472,7 +522,7 @@ func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *inte // Create type check (if/else). bb := p.ctx.AddBasicBlock(fn, typ.name) next := p.ctx.AddBasicBlock(fn, typ.name+".next") - cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, llvm.ConstPtrToInt(typ.typecode, p.uintptrType), typ.name+".icmp") + cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, typ.typecodeGEP, typ.name+".icmp") p.builder.CreateCondBr(cmp, bb, next) // The function we will redirect to when the interface has this type. @@ -522,7 +572,7 @@ func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *inte // method on a nil interface. nilPanic := p.mod.NamedFunction("runtime.nilPanic") p.builder.CreateCall(nilPanic.GlobalValueType(), nilPanic, []llvm.Value{ - llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)), + llvm.Undef(p.i8ptrType), }, "") p.builder.CreateUnreachable() } diff --git a/transform/optimizer.go b/transform/optimizer.go index 80be63191..20258ef4f 100644 --- a/transform/optimizer.go +++ b/transform/optimizer.go @@ -116,7 +116,6 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i goPasses.Run(mod) // Run TinyGo-specific interprocedural optimizations. - LowerReflect(mod) OptimizeAllocs(mod, config.Options.PrintAllocs, func(pos token.Position, msg string) { fmt.Fprintln(os.Stderr, pos.String()+": "+msg) }) @@ -129,7 +128,6 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i if err != nil { return []error{err} } - LowerReflect(mod) errs := LowerInterrupts(mod) if len(errs) > 0 { return errs diff --git a/transform/reflect.go b/transform/reflect.go deleted file mode 100644 index b994df61c..000000000 --- a/transform/reflect.go +++ /dev/null @@ -1,567 +0,0 @@ -package transform - -// This file has some compiler support for run-time reflection using the reflect -// package. In particular, it encodes type information in type codes in such a -// way that the reflect package can decode the type from this information. -// Where needed, it also adds some side tables for looking up more information -// about a type, when that information cannot be stored directly in the type -// code. -// -// Go has 26 different type kinds. -// -// Type kinds are subdivided in basic types (see the list of basicTypes below) -// that are mostly numeric literals and non-basic (or "complex") types that are -// more difficult to encode. These non-basic types come in two forms: -// * Prefix types (pointer, slice, interface, channel): these just add -// something to an existing type. For example, a pointer like *int just adds -// the fact that it's a pointer to an existing type (int). -// These are encoded efficiently by adding a prefix to a type code. -// * Types with multiple fields (struct, array, func, map). All of these have -// multiple fields contained within. Most obviously structs can contain many -// types as fields. Also arrays contain not just the element type but also -// the length parameter which can be any arbitrary number and thus may not -// fit in a type code. -// These types are encoded using side tables. -// -// This distinction is also important for how named types are encoded. At the -// moment, named basic type just get a unique number assigned while named -// non-basic types have their underlying type stored in a sidetable. - -import ( - "encoding/binary" - "go/ast" - "math/big" - "sort" - "strings" - - "tinygo.org/x/go-llvm" -) - -// A list of basic types and their numbers. This list should be kept in sync -// with the list of Kind constants of type.go in the reflect package. -var basicTypes = map[string]int64{ - "bool": 1, - "int": 2, - "int8": 3, - "int16": 4, - "int32": 5, - "int64": 6, - "uint": 7, - "uint8": 8, - "uint16": 9, - "uint32": 10, - "uint64": 11, - "uintptr": 12, - "float32": 13, - "float64": 14, - "complex64": 15, - "complex128": 16, - "string": 17, - "unsafe.Pointer": 18, -} - -// A list of non-basic types. Adding 19 to this number will give the Kind as -// used in src/reflect/types.go, and it must be kept in sync with that list. -var nonBasicTypes = map[string]int64{ - "chan": 0, - "interface": 1, - "pointer": 2, - "slice": 3, - "array": 4, - "func": 5, - "map": 6, - "struct": 7, -} - -// typeCodeAssignmentState keeps some global state around for type code -// assignments, used to assign one unique type code to each Go type. -type typeCodeAssignmentState struct { - // Builder used purely for constant operations (because LLVM 15 removed many - // llvm.Const* functions). - builder llvm.Builder - - // An integer that's incremented each time it's used to give unique IDs to - // type codes that are not yet fully supported otherwise by the reflect - // package (or are simply unused in the compiled program). - fallbackIndex int - - // This is the length of an uintptr. Only used occasionally to know whether - // a given number can be encoded as a varint. - uintptrLen int - - // Map of named types to their type code. It is important that named types - // get unique IDs for each type. - namedBasicTypes map[string]int - namedNonBasicTypes map[string]int - - // Map of array types to their type code. - arrayTypes map[string]int - arrayTypesSidetable []byte - needsArrayTypesSidetable bool - - // Map of struct types to their type code. - structTypes map[string]int - structTypesSidetable []byte - needsStructNamesSidetable bool - - // Map of struct names and tags to their name string. - structNames map[string]int - structNamesSidetable []byte - needsStructTypesSidetable bool - - // This byte array is stored in reflect.namedNonBasicTypesSidetable and is - // used at runtime to get details about a named non-basic type. - // Entries are varints (see makeVarint below and readVarint in - // reflect/sidetables.go for the encoding): one varint per entry. The - // integers in namedNonBasicTypes are indices into this array. Because these - // are varints, most type codes are really small (just one byte). - // - // Note that this byte buffer is not created when it is not needed - // (reflect.namedNonBasicTypesSidetable has no uses), see - // needsNamedTypesSidetable. - namedNonBasicTypesSidetable []uint64 - - // This indicates whether namedNonBasicTypesSidetable needs to be created at - // all. If it is false, namedNonBasicTypesSidetable will contain simple - // monotonically increasing numbers. - needsNamedNonBasicTypesSidetable bool -} - -// LowerReflect is used to assign a type code to each type in the program -// that is ever stored in an interface. It tries to use the smallest possible -// numbers to make the code that works with interfaces as small as possible. -func LowerReflect(mod llvm.Module) { - // if reflect were not used, we could skip generating the sidetable - // this does not help in practice, and is difficult to do correctly - - // Obtain slice of all types in the program. - type typeInfo struct { - typecode llvm.Value - name string - numUses int - } - var types []*typeInfo - for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) { - if strings.HasPrefix(global.Name(), "reflect/types.type:") { - types = append(types, &typeInfo{ - typecode: global, - name: global.Name(), - numUses: len(getUses(global)), - }) - } - } - - // Sort the slice in a way that often used types are assigned a type code - // first. - sort.Slice(types, func(i, j int) bool { - if types[i].numUses != types[j].numUses { - return types[i].numUses < types[j].numUses - } - // It would make more sense to compare the name in the other direction, - // but for some reason that increases binary size. Could be a fluke, but - // could also have some good reason (and possibly hint at a small - // optimization). - return types[i].name > types[j].name - }) - - // Assign typecodes the way the reflect package expects. - targetData := llvm.NewTargetData(mod.DataLayout()) - defer targetData.Dispose() - uintptrType := mod.Context().IntType(targetData.PointerSize() * 8) - state := typeCodeAssignmentState{ - builder: mod.Context().NewBuilder(), - fallbackIndex: 1, - uintptrLen: targetData.PointerSize() * 8, - namedBasicTypes: make(map[string]int), - namedNonBasicTypes: make(map[string]int), - arrayTypes: make(map[string]int), - structTypes: make(map[string]int), - structNames: make(map[string]int), - needsNamedNonBasicTypesSidetable: len(getUses(mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0, - needsStructTypesSidetable: len(getUses(mod.NamedGlobal("reflect.structTypesSidetable"))) != 0, - needsStructNamesSidetable: len(getUses(mod.NamedGlobal("reflect.structNamesSidetable"))) != 0, - needsArrayTypesSidetable: len(getUses(mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0, - } - defer state.builder.Dispose() - for _, t := range types { - num := state.getTypeCodeNum(t.typecode) - if num.BitLen() > state.uintptrLen || !num.IsUint64() { - // TODO: support this in some way, using a side table for example. - // That's less efficient but better than not working at all. - // Particularly important on systems with 16-bit pointers (e.g. - // AVR). - panic("compiler: could not store type code number inside interface type code") - } - - // Replace each use of the type code global with the constant type code. - for _, use := range getUses(t.typecode) { - if use.IsAConstantExpr().IsNil() { - continue - } - typecode := llvm.ConstInt(uintptrType, num.Uint64(), false) - switch use.Opcode() { - case llvm.PtrToInt: - // Already of the correct type. - case llvm.BitCast: - // Could happen when stored in an interface (which is of type - // i8*). - typecode = llvm.ConstIntToPtr(typecode, use.Type()) - default: - panic("unexpected constant expression") - } - use.ReplaceAllUsesWith(typecode) - } - } - - // Only create this sidetable when it is necessary. - if state.needsNamedNonBasicTypesSidetable { - global := replaceGlobalIntWithArray(mod, "reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable) - global.SetLinkage(llvm.InternalLinkage) - global.SetUnnamedAddr(true) - global.SetGlobalConstant(true) - } - if state.needsArrayTypesSidetable { - global := replaceGlobalIntWithArray(mod, "reflect.arrayTypesSidetable", state.arrayTypesSidetable) - global.SetLinkage(llvm.InternalLinkage) - global.SetUnnamedAddr(true) - global.SetGlobalConstant(true) - } - if state.needsStructTypesSidetable { - global := replaceGlobalIntWithArray(mod, "reflect.structTypesSidetable", state.structTypesSidetable) - global.SetLinkage(llvm.InternalLinkage) - global.SetUnnamedAddr(true) - global.SetGlobalConstant(true) - } - if state.needsStructNamesSidetable { - global := replaceGlobalIntWithArray(mod, "reflect.structNamesSidetable", state.structNamesSidetable) - global.SetLinkage(llvm.InternalLinkage) - global.SetUnnamedAddr(true) - global.SetGlobalConstant(true) - } - - // Remove most objects created for interface and reflect lowering. - // They would normally be removed anyway in later passes, but not always. - // It also cleans up the IR for testing. - for _, typ := range types { - initializer := typ.typecode.Initializer() - references := state.builder.CreateExtractValue(initializer, 0, "") - typ.typecode.SetInitializer(llvm.ConstNull(initializer.Type())) - if strings.HasPrefix(typ.name, "reflect/types.type:struct:") { - // Structs have a 'references' field that is not a typecode but - // a pointer to a runtime.structField array and therefore a - // bitcast. This global should be erased separately, otherwise - // typecode objects cannot be erased. - structFields := references - if !structFields.IsAConstantExpr().IsNil() && structFields.Opcode() == llvm.BitCast { - structFields = structFields.Operand(0) // get global from bitcast, for LLVM 14 compatibility (non-opaque pointers) - } - structFields.EraseFromParentAsGlobal() - } - } -} - -// getTypeCodeNum returns the typecode for a given type as expected by the -// reflect package. Also see getTypeCodeName, which serializes types to a string -// based on a types.Type value for this function. -func (state *typeCodeAssignmentState) getTypeCodeNum(typecode llvm.Value) *big.Int { - // Note: see src/reflect/type.go for bit allocations. - class, value := getClassAndValueFromTypeCode(typecode) - name := "" - if class == "named" { - name = value - typecode = state.builder.CreateExtractValue(typecode.Initializer(), 0, "") - class, value = getClassAndValueFromTypeCode(typecode) - } - if class == "basic" { - // Basic types follow the following bit pattern: - // ...xxxxx0 - // where xxxxx is allocated for the 18 possible basic types and all the - // upper bits are used to indicate the named type. - num, ok := basicTypes[value] - if !ok { - panic("invalid basic type: " + value) - } - if name != "" { - // This type is named, set the upper bits to the name ID. - num |= int64(state.getBasicNamedTypeNum(name)) << 5 - } - return big.NewInt(num << 1) - } else { - // Non-baisc types use the following bit pattern: - // ...nxxx1 - // where xxx indicates the non-basic type. The upper bits contain - // whatever the type contains. Types that wrap a single other type - // (channel, interface, pointer, slice) just contain the bits of the - // wrapped type. Other types (like struct) need more fields and thus - // cannot be encoded as a simple prefix. - var classNumber int64 - if n, ok := nonBasicTypes[class]; ok { - classNumber = n - } else { - panic("unknown type kind: " + class) - } - var num *big.Int - lowBits := (classNumber << 1) + 1 // the 5 low bits of the typecode - if name == "" { - num = state.getNonBasicTypeCode(class, typecode) - } else { - // We must return a named type here. But first check whether it - // has already been defined. - if index, ok := state.namedNonBasicTypes[name]; ok { - num := big.NewInt(int64(index)) - num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1+(1<<4))) - return num - } - lowBits |= 1 << 4 // set the 'n' bit (see above) - if !state.needsNamedNonBasicTypesSidetable { - // Use simple small integers in this case, to make these numbers - // smaller. - index := len(state.namedNonBasicTypes) + 1 - state.namedNonBasicTypes[name] = index - num = big.NewInt(int64(index)) - } else { - // We need to store full type information. - // First allocate a number in the named non-basic type - // sidetable. - index := len(state.namedNonBasicTypesSidetable) - state.namedNonBasicTypesSidetable = append(state.namedNonBasicTypesSidetable, 0) - state.namedNonBasicTypes[name] = index - // Get the typecode of the underlying type (which could be the - // element type in the case of pointers, for example). - num = state.getNonBasicTypeCode(class, typecode) - if num.BitLen() > state.uintptrLen || !num.IsUint64() { - panic("cannot store value in sidetable") - } - // Now update the side table with the number we just - // determined. We need this multi-step approach to avoid stack - // overflow due to adding types recursively in the case of - // linked lists (a pointer which points to a struct that - // contains that same pointer). - state.namedNonBasicTypesSidetable[index] = num.Uint64() - num = big.NewInt(int64(index)) - } - } - // Concatenate the 'num' and 'lowBits' bitstrings. - num.Lsh(num, 5).Or(num, big.NewInt(lowBits)) - return num - } -} - -// getNonBasicTypeCode is used by getTypeCodeNum. It returns the upper bits of -// the type code used there in the type code. -func (state *typeCodeAssignmentState) getNonBasicTypeCode(class string, typecode llvm.Value) *big.Int { - switch class { - case "chan", "pointer", "slice": - // Prefix-style type kinds. The upper bits contain the element type. - sub := state.builder.CreateExtractValue(typecode.Initializer(), 0, "") - return state.getTypeCodeNum(sub) - case "array": - // An array is basically a pair of (typecode, length) stored in a - // sidetable. - return big.NewInt(int64(state.getArrayTypeNum(typecode))) - case "struct": - // More complicated type kind. The upper bits contain the index to the - // struct type in the struct types sidetable. - return big.NewInt(int64(state.getStructTypeNum(typecode))) - default: - // Type has not yet been implemented, so fall back by using a unique - // number. - num := big.NewInt(int64(state.fallbackIndex)) - state.fallbackIndex++ - return num - } -} - -// getClassAndValueFromTypeCode takes a typecode (a llvm.Value of type -// runtime.typecodeID), looks at the name, and extracts the typecode class and -// value from it. For example, for a typecode with the following name: -// -// reflect/types.type:pointer:named:reflect.ValueError -// -// It extracts: -// -// class = "pointer" -// value = "named:reflect.ValueError" -func getClassAndValueFromTypeCode(typecode llvm.Value) (class, value string) { - typecodeName := typecode.Name() - const prefix = "reflect/types.type:" - if !strings.HasPrefix(typecodeName, prefix) { - panic("unexpected typecode name: " + typecodeName) - } - id := typecodeName[len(prefix):] - class = id[:strings.IndexByte(id, ':')] - value = id[len(class)+1:] - return -} - -// getBasicNamedTypeNum returns an appropriate (unique) number for the given -// named type. If the name already has a number that number is returned, else a -// new number is returned. The number is always non-zero. -func (state *typeCodeAssignmentState) getBasicNamedTypeNum(name string) int { - if num, ok := state.namedBasicTypes[name]; ok { - return num - } - num := len(state.namedBasicTypes) + 1 - state.namedBasicTypes[name] = num - return num -} - -// getArrayTypeNum returns the array type number, which is an index into the -// reflect.arrayTypesSidetable or a unique number for this type if this table is -// not used. -func (state *typeCodeAssignmentState) getArrayTypeNum(typecode llvm.Value) int { - name := typecode.Name() - if num, ok := state.arrayTypes[name]; ok { - // This array type already has an entry in the sidetable. Don't store - // it twice. - return num - } - - if !state.needsArrayTypesSidetable { - // We don't need array sidetables, so we can just assign monotonically - // increasing numbers to each array type. - num := len(state.arrayTypes) - state.arrayTypes[name] = num - return num - } - - elemTypeCode := state.builder.CreateExtractValue(typecode.Initializer(), 0, "") - elemTypeNum := state.getTypeCodeNum(elemTypeCode) - if elemTypeNum.BitLen() > state.uintptrLen || !elemTypeNum.IsUint64() { - // TODO: make this a regular error - panic("array element type has a type code that is too big") - } - - // The array side table is a sequence of {element type, array length}. - arrayLength := state.builder.CreateExtractValue(typecode.Initializer(), 1, "").ZExtValue() - buf := makeVarint(elemTypeNum.Uint64()) - buf = append(buf, makeVarint(arrayLength)...) - - index := len(state.arrayTypesSidetable) - state.arrayTypes[name] = index - state.arrayTypesSidetable = append(state.arrayTypesSidetable, buf...) - return index -} - -// getStructTypeNum returns the struct type number, which is an index into -// reflect.structTypesSidetable or an unique number for every struct if this -// sidetable is not needed in the to-be-compiled program. -func (state *typeCodeAssignmentState) getStructTypeNum(typecode llvm.Value) int { - name := typecode.Name() - if num, ok := state.structTypes[name]; ok { - // This struct already has an assigned type code. - return num - } - - if !state.needsStructTypesSidetable { - // We don't need struct sidetables, so we can just assign monotonically - // increasing numbers to each struct type. - num := len(state.structTypes) - state.structTypes[name] = num - return num - } - - // Get the fields this struct type contains. - // The struct number will be the start index of - structTypeGlobal := stripPointerCasts(state.builder.CreateExtractValue(typecode.Initializer(), 0, "")).Initializer() - numFields := structTypeGlobal.Type().ArrayLength() - - // The first data that is stored in the struct sidetable is the number of - // fields this struct contains. This is usually just a single byte because - // most structs don't contain that many fields, but make it a varint just - // to be sure. - buf := makeVarint(uint64(numFields)) - - // Iterate over every field in the struct. - // Every field is stored sequentially in the struct sidetable. Fields can - // be retrieved from this list of fields at runtime by iterating over all - // of them until the right field has been found. - // Perhaps adding some index would speed things up, but it would also make - // the sidetable bigger. - for i := 0; i < numFields; i++ { - // Collect some information about this field. - field := state.builder.CreateExtractValue(structTypeGlobal, i, "") - - nameGlobal := state.builder.CreateExtractValue(field, 1, "") - if nameGlobal == llvm.ConstPointerNull(nameGlobal.Type()) { - panic("compiler: no name for this struct field") - } - fieldNameBytes := getGlobalBytes(stripPointerCasts(nameGlobal), state.builder) - fieldNameNumber := state.getStructNameNumber(fieldNameBytes) - - // See whether this struct field has an associated tag, and if so, - // store that tag in the tags sidetable. - tagGlobal := state.builder.CreateExtractValue(field, 2, "") - hasTag := false - tagNumber := 0 - if tagGlobal != llvm.ConstPointerNull(tagGlobal.Type()) { - hasTag = true - tagBytes := getGlobalBytes(stripPointerCasts(tagGlobal), state.builder) - tagNumber = state.getStructNameNumber(tagBytes) - } - - // The 'embedded' or 'anonymous' flag for this field. - embedded := state.builder.CreateExtractValue(field, 3, "").ZExtValue() != 0 - - // The first byte in the struct types sidetable is a flags byte with - // two bits in it. - flagsByte := byte(0) - if embedded { - flagsByte |= 1 - } - if hasTag { - flagsByte |= 2 - } - if ast.IsExported(string(fieldNameBytes)) { - flagsByte |= 4 - } - buf = append(buf, flagsByte) - - // Get the type number and add it to the buffer. - // All fields have a type, so include it directly here. - typeNum := state.getTypeCodeNum(state.builder.CreateExtractValue(field, 0, "")) - if typeNum.BitLen() > state.uintptrLen || !typeNum.IsUint64() { - // TODO: make this a regular error - panic("struct field has a type code that is too big") - } - buf = append(buf, makeVarint(typeNum.Uint64())...) - - // Add the name. - buf = append(buf, makeVarint(uint64(fieldNameNumber))...) - - // Add the tag, if there is one. - if hasTag { - buf = append(buf, makeVarint(uint64(tagNumber))...) - } - } - - num := len(state.structTypesSidetable) - state.structTypes[name] = num - state.structTypesSidetable = append(state.structTypesSidetable, buf...) - return num -} - -// getStructNameNumber stores this string (name or tag) onto the struct names -// sidetable. The format is a varint of the length of the struct, followed by -// the raw bytes of the name. Multiple identical strings are stored under the -// same name for space efficiency. -func (state *typeCodeAssignmentState) getStructNameNumber(nameBytes []byte) int { - name := string(nameBytes) - if n, ok := state.structNames[name]; ok { - // This name was used before, re-use it now (for space efficiency). - return n - } - // This name is not yet in the names sidetable. Add it now. - n := len(state.structNamesSidetable) - state.structNames[name] = n - state.structNamesSidetable = append(state.structNamesSidetable, makeVarint(uint64(len(nameBytes)))...) - state.structNamesSidetable = append(state.structNamesSidetable, nameBytes...) - return n -} - -// makeVarint is a small helper function that returns the bytes of the number in -// varint encoding. -func makeVarint(n uint64) []byte { - buf := make([]byte, binary.MaxVarintLen64) - return buf[:binary.PutUvarint(buf, n)] -} diff --git a/transform/reflect_test.go b/transform/reflect_test.go deleted file mode 100644 index 242337024..000000000 --- a/transform/reflect_test.go +++ /dev/null @@ -1,77 +0,0 @@ -package transform_test - -import ( - "testing" - - "github.com/tinygo-org/tinygo/transform" - "tinygo.org/x/go-llvm" -) - -type reflectAssert struct { - call llvm.Value - name string - expectedNumber uint64 -} - -// Test reflect lowering. This code looks at IR like this: -// -// call void @main.assertType(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* inttoptr (i32 3 to i8*), i32 4, i8* undef, i8* undef) -// -// and verifies that the ptrtoint constant (the first parameter of -// @main.assertType) is replaced with the correct type code. The expected -// output is this: -// -// call void @main.assertType(i32 4, i8* inttoptr (i32 3 to i8*), i32 4, i8* undef, i8* undef) -// -// The first and third parameter are compared and must match, the second -// parameter is ignored. -func TestReflect(t *testing.T) { - t.Parallel() - - mod := compileGoFileForTesting(t, "./testdata/reflect.go") - - // Run the instcombine pass, to clean up the IR a bit (especially - // insertvalue/extractvalue instructions). - pm := llvm.NewPassManager() - defer pm.Dispose() - pm.AddInstructionCombiningPass() - pm.Run(mod) - - // Get a list of all the asserts in the source code. - assertType := mod.NamedFunction("main.assertType") - var asserts []reflectAssert - for user := assertType.FirstUse(); !user.IsNil(); user = user.NextUse() { - use := user.User() - if use.IsACallInst().IsNil() { - t.Fatal("expected call use of main.assertType") - } - global := use.Operand(0).Operand(0) - expectedNumber := use.Operand(2).ZExtValue() - asserts = append(asserts, reflectAssert{ - call: use, - name: global.Name(), - expectedNumber: expectedNumber, - }) - } - - // Sanity check to show that the test is actually testing anything. - if len(asserts) < 3 { - t.Errorf("expected at least 3 test cases, got %d", len(asserts)) - } - - // Now lower the type codes. - transform.LowerReflect(mod) - - // Check whether the values are as expected. - for _, assert := range asserts { - actualNumberValue := assert.call.Operand(0) - if actualNumberValue.IsAConstantInt().IsNil() { - t.Errorf("expected to see a constant for %s, got something else", assert.name) - continue - } - actualNumber := actualNumberValue.ZExtValue() - if actualNumber != assert.expectedNumber { - t.Errorf("%s: expected number 0b%b, got 0b%b", assert.name, assert.expectedNumber, actualNumber) - } - } -} diff --git a/transform/rtcalls.go b/transform/rtcalls.go index 209e15ae1..b7192ad4f 100644 --- a/transform/rtcalls.go +++ b/transform/rtcalls.go @@ -113,11 +113,6 @@ func OptimizeReflectImplements(mod llvm.Module) { builder := mod.Context().NewBuilder() defer builder.Dispose() - // Get a few useful object for use later. - targetData := llvm.NewTargetData(mod.DataLayout()) - defer targetData.Dispose() - uintptrType := mod.Context().IntType(targetData.PointerSize() * 8) - // Look up the (reflect.Value).Implements() method. var implementsFunc llvm.Value for fn := mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) { @@ -141,14 +136,13 @@ func OptimizeReflectImplements(mod llvm.Module) { } interfaceType := stripPointerCasts(call.Operand(2)) if interfaceType.IsAGlobalVariable().IsNil() { - // The asserted interface is not constant, so can't optimize this - // code. + // 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 = builder.CreateExtractValue(interfaceType.Initializer(), 0, "") + interfaceType = stripPointerCasts(builder.CreateExtractValue(interfaceType.Initializer(), 2, "")) } if !strings.HasPrefix(interfaceType.Name(), "reflect/types.type:interface:") { // This is an error. The Type passed to Implements should be of @@ -156,16 +150,15 @@ func OptimizeReflectImplements(mod llvm.Module) { // reported at runtime. continue } - if interfaceType.IsAGlobalVariable().IsNil() { - // Interface is unknown at compile time. This can't be optimized. + typeAssertFunction := mod.NamedFunction(strings.TrimPrefix(interfaceType.Name(), "reflect/types.type:") + ".$typeassert") + if typeAssertFunction.IsNil() { continue } - typeAssertFunction := builder.CreateExtractValue(interfaceType.Initializer(), 4, "").Operand(0) // Replace Implements call with the type assert call. builder.SetInsertPointBefore(call) implements := builder.CreateCall(typeAssertFunction.GlobalValueType(), typeAssertFunction, []llvm.Value{ - builder.CreatePtrToInt(call.Operand(0), uintptrType, ""), // typecode to check + call.Operand(0), // typecode to check }, "") call.ReplaceAllUsesWith(implements) call.EraseFromParentAsInstruction() diff --git a/transform/testdata/interface.ll b/transform/testdata/interface.ll index 4d8e818de..c7c42243a 100644 --- a/transform/testdata/interface.ll +++ b/transform/testdata/interface.ll @@ -1,19 +1,19 @@ 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*, i32 } -%runtime.interfaceMethodInfo = type { i8*, i32 } - -@"reflect/types.type:basic:uint8" = private constant %runtime.typecodeID zeroinitializer +@"reflect/types.type:basic:uint8" = linkonce_odr constant { i8, i8* } { i8 8, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:basic:uint8", i32 0, i32 0) }, align 4 +@"reflect/types.type:pointer:basic:uint8" = linkonce_odr constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:uint8", i32 0, i32 0) }, align 4 @"reflect/types.typeid:basic:uint8" = external constant i8 @"reflect/types.typeid:basic:int16" = external constant i8 -@"reflect/types.type:basic:int" = private constant %runtime.typecodeID zeroinitializer +@"reflect/types.type:basic:int" = linkonce_odr constant { i8, i8* } { i8 2, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:basic:int", i32 0, i32 0) }, align 4 +@"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:int", i32 0, i32 0) }, align 4 @"reflect/methods.NeverImplementedMethod()" = linkonce_odr constant i8 0 @"reflect/methods.Double() int" = linkonce_odr constant i8 0 -@"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), %runtime.typecodeID* null, i32 0 } +@"Number$methodset" = linkonce_odr unnamed_addr constant { i32, [1 x i8*], { i32 (i8*, i8*)* } } { i32 1, [1 x i8*] [i8* @"reflect/methods.Double() int"], { i32 (i8*, i8*)* } { i32 (i8*, i8*)* @"(Number).Double$invoke" } } +@"reflect/types.type:named:Number" = linkonce_odr constant { i8*, i8, i8*, i8* } { i8* bitcast ({ i32, [1 x i8*], { i32 (i8*, i8*)* } }* @"Number$methodset" to i8*), i8 34, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:named:Number", i32 0, i32 0), i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:int", i32 0, i32 0) }, align 4 +@"reflect/types.type:pointer:named:Number" = linkonce_odr constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8*, i8, i8*, i8* }, { i8*, i8, i8*, i8* }* @"reflect/types.type:named:Number", i32 0, i32 1) }, align 4 -declare i1 @runtime.typeAssert(i32, i8*) +declare i1 @runtime.typeAssert(i8*, i8*) declare void @runtime.printuint8(i8) declare void @runtime.printint16(i16) declare void @runtime.printint32(i32) @@ -22,15 +22,15 @@ declare void @runtime.printnl() declare void @runtime.nilPanic(i8*) define void @printInterfaces() { - call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* inttoptr (i32 5 to i8*)) - call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:uint8" to i32), i8* inttoptr (i8 120 to i8*)) - call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32), i8* inttoptr (i32 3 to i8*)) + call void @printInterface(i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:int", i32 0, i32 0), i8* inttoptr (i32 5 to i8*)) + call void @printInterface(i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:uint8", i32 0, i32 0), i8* inttoptr (i8 120 to i8*)) + call void @printInterface(i8* getelementptr inbounds ({ i8*, i8, i8*, i8* }, { i8*, i8, i8*, i8* }* @"reflect/types.type:named:Number", i32 0, i32 1), i8* inttoptr (i32 3 to i8*)) ret void } -define void @printInterface(i32 %typecode, i8* %value) { - %isUnmatched = call i1 @Unmatched$typeassert(i32 %typecode) +define void @printInterface(i8* %typecode, i8* %value) { + %isUnmatched = call i1 @Unmatched$typeassert(i8* %typecode) br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched typeswitch.Unmatched: @@ -40,16 +40,16 @@ typeswitch.Unmatched: ret void typeswitch.notUnmatched: - %isDoubler = call i1 @Doubler$typeassert(i32 %typecode) + %isDoubler = call i1 @Doubler$typeassert(i8* %typecode) br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler typeswitch.Doubler: - %doubler.result = call i32 @"Doubler.Double$invoke"(i8* %value, i32 %typecode, i8* undef) + %doubler.result = call i32 @"Doubler.Double$invoke"(i8* %value, i8* %typecode, i8* undef) call void @runtime.printint32(i32 %doubler.result) ret void typeswitch.notDoubler: - %isByte = call i1 @runtime.typeAssert(i32 %typecode, i8* nonnull @"reflect/types.typeid:basic:uint8") + %isByte = call i1 @runtime.typeAssert(i8* %typecode, i8* nonnull @"reflect/types.typeid:basic:uint8") br i1 %isByte, label %typeswitch.byte, label %typeswitch.notByte typeswitch.byte: @@ -60,7 +60,7 @@ typeswitch.byte: typeswitch.notByte: ; this is a type assert that always fails - %isInt16 = call i1 @runtime.typeAssert(i32 %typecode, i8* nonnull @"reflect/types.typeid:basic:int16") + %isInt16 = call i1 @runtime.typeAssert(i8* %typecode, i8* nonnull @"reflect/types.typeid:basic:int16") br i1 %isInt16, label %typeswitch.int16, label %typeswitch.notInt16 typeswitch.int16: @@ -84,11 +84,11 @@ define i32 @"(Number).Double$invoke"(i8* %receiverPtr, i8* %context) { ret i32 %ret } -declare i32 @"Doubler.Double$invoke"(i8* %receiver, i32 %typecode, i8* %context) #0 +declare i32 @"Doubler.Double$invoke"(i8* %receiver, i8* %typecode, i8* %context) #0 -declare i1 @Doubler$typeassert(i32 %typecode) #1 +declare i1 @Doubler$typeassert(i8* %typecode) #1 -declare i1 @Unmatched$typeassert(i32 %typecode) #2 +declare i1 @Unmatched$typeassert(i8* %typecode) #2 attributes #0 = { "tinygo-invoke"="reflect/methods.Double() int" "tinygo-methods"="reflect/methods.Double() int" } attributes #1 = { "tinygo-methods"="reflect/methods.Double() int" } diff --git a/transform/testdata/interface.out.ll b/transform/testdata/interface.out.ll index 262df2108..fe4f79bbf 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*, 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, %runtime.typecodeID* null, i32 0 } +@"reflect/types.type:basic:uint8" = linkonce_odr constant { i8, i8* } { i8 8, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:basic:uint8", i32 0, i32 0) }, align 4 +@"reflect/types.type:pointer:basic:uint8" = linkonce_odr constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:uint8", i32 0, i32 0) }, align 4 +@"reflect/types.type:basic:int" = linkonce_odr constant { i8, i8* } { i8 2, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:basic:int", i32 0, i32 0) }, align 4 +@"reflect/types.type:pointer:basic:int" = linkonce_odr constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:int", i32 0, i32 0) }, align 4 +@"reflect/types.type:pointer:named:Number" = linkonce_odr constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:Number", i32 0, i32 0) }, align 4 +@"reflect/types.type:named:Number" = linkonce_odr constant { i8, i8*, i8* } { i8 34, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:named:Number", i32 0, i32 0), i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:int", i32 0, i32 0) }, align 4 declare void @runtime.printuint8(i8) @@ -21,14 +21,14 @@ declare void @runtime.printnl() declare void @runtime.nilPanic(i8*) define void @printInterfaces() { - call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* inttoptr (i32 5 to i8*)) - call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:uint8" to i32), i8* inttoptr (i8 120 to i8*)) - call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32), i8* inttoptr (i32 3 to i8*)) + call void @printInterface(i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:int", i32 0, i32 0), i8* inttoptr (i32 5 to i8*)) + call void @printInterface(i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:uint8", i32 0, i32 0), i8* inttoptr (i8 120 to i8*)) + call void @printInterface(i8* getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:Number", i32 0, i32 0), i8* inttoptr (i32 3 to i8*)) ret void } -define void @printInterface(i32 %typecode, i8* %value) { - %isUnmatched = call i1 @"Unmatched$typeassert"(i32 %typecode) +define void @printInterface(i8* %typecode, i8* %value) { + %isUnmatched = call i1 @"Unmatched$typeassert"(i8* %typecode) br i1 %isUnmatched, label %typeswitch.Unmatched, label %typeswitch.notUnmatched typeswitch.Unmatched: ; preds = %0 @@ -38,16 +38,16 @@ typeswitch.Unmatched: ; preds = %0 ret void typeswitch.notUnmatched: ; preds = %0 - %isDoubler = call i1 @"Doubler$typeassert"(i32 %typecode) + %isDoubler = call i1 @"Doubler$typeassert"(i8* %typecode) br i1 %isDoubler, label %typeswitch.Doubler, label %typeswitch.notDoubler typeswitch.Doubler: ; preds = %typeswitch.notUnmatched - %doubler.result = call i32 @"Doubler.Double$invoke"(i8* %value, i32 %typecode, i8* undef) + %doubler.result = call i32 @"Doubler.Double$invoke"(i8* %value, i8* %typecode, i8* undef) call void @runtime.printint32(i32 %doubler.result) ret void typeswitch.notDoubler: ; preds = %typeswitch.notUnmatched - %typeassert.ok = icmp eq i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:uint8" to i32), %typecode + %typeassert.ok = icmp eq i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:basic:uint8", i32 0, i32 0), %typecode br i1 %typeassert.ok, label %typeswitch.byte, label %typeswitch.notByte typeswitch.byte: ; preds = %typeswitch.notDoubler @@ -80,9 +80,9 @@ define i32 @"(Number).Double$invoke"(i8* %receiverPtr, i8* %context) { ret i32 %ret } -define internal i32 @"Doubler.Double$invoke"(i8* %receiver, i32 %actualType, i8* %context) unnamed_addr #0 { +define internal i32 @"Doubler.Double$invoke"(i8* %receiver, i8* %actualType, i8* %context) unnamed_addr #0 { entry: - %"named:Number.icmp" = icmp eq i32 %actualType, ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32) + %"named:Number.icmp" = icmp eq i8* %actualType, getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:Number", i32 0, i32 0) br i1 %"named:Number.icmp", label %"named:Number", label %"named:Number.next" "named:Number": ; preds = %entry @@ -94,9 +94,9 @@ entry: unreachable } -define internal i1 @"Doubler$typeassert"(i32 %actualType) unnamed_addr #1 { +define internal i1 @"Doubler$typeassert"(i8* %actualType) unnamed_addr #1 { entry: - %"named:Number.icmp" = icmp eq i32 %actualType, ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32) + %"named:Number.icmp" = icmp eq i8* %actualType, getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:Number", i32 0, i32 0) br i1 %"named:Number.icmp", label %then, label %"named:Number.next" then: ; preds = %entry @@ -106,7 +106,7 @@ then: ; preds = %entry ret i1 false } -define internal i1 @"Unmatched$typeassert"(i32 %actualType) unnamed_addr #2 { +define internal i1 @"Unmatched$typeassert"(i8* %actualType) unnamed_addr #2 { entry: ret i1 false diff --git a/transform/testdata/reflect-implements.ll b/transform/testdata/reflect-implements.ll index ca6dcb8c5..0919a4851 100644 --- a/transform/testdata/reflect-implements.ll +++ b/transform/testdata/reflect-implements.ll @@ -1,19 +1,14 @@ 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*, i32 } -%runtime.interfaceMethodInfo = type { i8*, i32 } +%runtime._interface = type { i8*, i8* } -@"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), %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 +@"reflect/types.type:named:error" = internal constant { i8, i8*, i8* } { i8 52, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:named:error", i32 0, i32 0), i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, i32 0) }, align 4 +@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = internal constant { i8, i8* } { i8 20, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}", i32 0, i32 0) }, align 4 +@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = internal constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, i32 0) }, align 4 +@"reflect/types.type:pointer:named:error" = internal constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:error", i32 0, i32 0) }, align 4 +@"reflect/types.type:pointer:named:reflect.rawType" = internal constant { i8*, i8, i8* } { i8* null, i8 21, i8* 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 { @@ -22,9 +17,9 @@ target triple = "i686--linux" ; 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(i32 %typ.typecode, i8* %typ.value, i8* %context) { +define i1 @main.isError(i8* %typ.typecode, i8* %typ.value, i8* %context) { entry: - %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) + %result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i8* getelementptr inbounds ({ i8*, i8, i8* }, { i8*, i8, i8* }* @"reflect/types.type:pointer:named:reflect.rawType", i32 0, i32 1), i8* getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:error", i32 0, i32 0), i8* %typ.typecode, i8* undef) ret i1 %result } @@ -33,14 +28,14 @@ entry: ; func isUnknown(typ, itf reflect.Type) bool { ; return typ.Implements(itf) ; } -define i1 @main.isUnknown(i32 %typ.typecode, i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* %context) { +define i1 @main.isUnknown(i8* %typ.typecode, i8* %typ.value, i8* %itf.typecode, i8* %itf.value, i8* %context) { entry: - %result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i32 %typ.typecode, i8* undef) + %result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i8* %itf.typecode, i8* %itf.value, i8* %typ.typecode, i8* undef) ret i1 %result } -declare i1 @"reflect.Type.Implements$invoke"(i8*, i32, i8*, i32, i8*) #0 -declare i1 @"error.$typeassert"(i32) #1 +declare i1 @"reflect.Type.Implements$invoke"(i8*, i8*, i8*, i8*, i8*) #0 +declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i8* %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 index 0093e2b0a..11cc3f6d1 100644 --- a/transform/testdata/reflect-implements.out.ll +++ b/transform/testdata/reflect-implements.out.ll @@ -1,36 +1,28 @@ 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*, i32 } -%runtime.interfaceMethodInfo = type { i8*, i32 } +@"reflect/types.type:named:error" = internal constant { i8, i8*, i8* } { i8 52, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:named:error", i32 0, i32 0), i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, i32 0) }, align 4 +@"reflect/types.type:interface:{Error:func:{}{basic:string}}" = internal constant { i8, i8* } { i8 20, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}", i32 0, i32 0) }, align 4 +@"reflect/types.type:pointer:interface:{Error:func:{}{basic:string}}" = internal constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8* }, { i8, i8* }* @"reflect/types.type:interface:{Error:func:{}{basic:string}}", i32 0, i32 0) }, align 4 +@"reflect/types.type:pointer:named:error" = internal constant { i8, i8* } { i8 21, i8* getelementptr inbounds ({ i8, i8*, i8* }, { i8, i8*, i8* }* @"reflect/types.type:named:error", i32 0, i32 0) }, align 4 +@"reflect/types.type:pointer:named:reflect.rawType" = internal constant { i8*, i8, i8* } { i8* null, i8 21, i8* null }, align 4 +@"reflect/methods.Implements(reflect.Type) bool" = internal constant i8 0, align 1 -@"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), %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 - -define i1 @main.isError(i32 %typ.typecode, i8* %typ.value, i8* %context) { +define i1 @main.isError(i8* %typ.typecode, i8* %typ.value, i8* %context) { entry: - %0 = ptrtoint i8* %typ.value to i32 - %1 = call i1 @"error.$typeassert"(i32 %0) - ret i1 %1 + %0 = call i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i8* %typ.value) + ret i1 %0 } -define i1 @main.isUnknown(i32 %typ.typecode, i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i8* %context) { +define i1 @main.isUnknown(i8* %typ.typecode, i8* %typ.value, i8* %itf.typecode, i8* %itf.value, i8* %context) { entry: - %result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i32 %itf.typecode, i8* %itf.value, i32 %typ.typecode, i8* undef) + %result = call i1 @"reflect.Type.Implements$invoke"(i8* %typ.value, i8* %itf.typecode, i8* %itf.value, i8* %typ.typecode, i8* undef) ret i1 %result } -declare i1 @"reflect.Type.Implements$invoke"(i8*, i32, i8*, i32, i8*) #0 +declare i1 @"reflect.Type.Implements$invoke"(i8*, i8*, i8*, i8*, i8*) #0 -declare i1 @"error.$typeassert"(i32) #1 +declare i1 @"interface:{Error:func:{}{basic:string}}.$typeassert"(i8*) #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" } |