aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAyke van Laethem <[email protected]>2022-06-24 00:09:03 +0200
committerRon Evans <[email protected]>2023-02-17 22:54:34 +0100
commit4e8453167f42976aad87099ffdb3746fc540d6a6 (patch)
treeb3acee7dc97a19219fd1a84cabaf9b9d8eba1f3a
parentebb410afd916047ee17f0e51dfba36ad3a6c002b (diff)
downloadtinygo-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.
-rw-r--r--builder/sizes.go11
-rw-r--r--compiler/compiler.go7
-rw-r--r--compiler/defer.go4
-rw-r--r--compiler/interface.go450
-rw-r--r--compiler/testdata/defer-cortex-m-qemu.ll2
-rw-r--r--compiler/testdata/gc.ll10
-rw-r--r--compiler/testdata/goroutine-cortex-m-qemu-tasks.ll22
-rw-r--r--compiler/testdata/goroutine-wasm-asyncify.ll22
-rw-r--r--compiler/testdata/interface.ll69
-rw-r--r--interp/interpreter.go86
-rw-r--r--interp/memory.go4
-rw-r--r--interp/testdata/interface.ll15
-rw-r--r--src/reflect/deepequal.go2
-rw-r--r--src/reflect/sidetables.go61
-rw-r--r--src/reflect/type.go379
-rw-r--r--src/reflect/value.go22
-rw-r--r--src/runtime/hashmap.go2
-rw-r--r--src/runtime/interface.go49
-rw-r--r--transform/interface-lowering.go118
-rw-r--r--transform/optimizer.go2
-rw-r--r--transform/reflect.go567
-rw-r--r--transform/reflect_test.go77
-rw-r--r--transform/rtcalls.go17
-rw-r--r--transform/testdata/interface.ll40
-rw-r--r--transform/testdata/interface.out.ll38
-rw-r--r--transform/testdata/reflect-implements.ll31
-rw-r--r--transform/testdata/reflect-implements.out.ll34
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" }