diff options
Diffstat (limited to 'transform')
-rw-r--r-- | transform/interface-lowering.go | 118 | ||||
-rw-r--r-- | transform/optimizer.go | 2 | ||||
-rw-r--r-- | transform/reflect.go | 567 | ||||
-rw-r--r-- | transform/reflect_test.go | 77 | ||||
-rw-r--r-- | transform/rtcalls.go | 17 | ||||
-rw-r--r-- | transform/testdata/interface.ll | 40 | ||||
-rw-r--r-- | transform/testdata/interface.out.ll | 38 | ||||
-rw-r--r-- | transform/testdata/reflect-implements.ll | 31 | ||||
-rw-r--r-- | transform/testdata/reflect-implements.out.ll | 34 |
9 files changed, 154 insertions, 770 deletions
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" } |