aboutsummaryrefslogtreecommitdiffhomepage
path: root/transform
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 /transform
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.
Diffstat (limited to 'transform')
-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
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" }