mirror of https://github.com/tinygo-org/tinygo.git
Browse Source
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.pull/3459/head
Ayke van Laethem
2 years ago
committed by
Ron Evans
27 changed files with 793 additions and 1348 deletions
@ -1,61 +0,0 @@ |
|||
package reflect |
|||
|
|||
import ( |
|||
"unsafe" |
|||
) |
|||
|
|||
// This stores a varint for each named type. Named types are identified by their
|
|||
// name instead of by their type. The named types stored in this struct are
|
|||
// non-basic types: pointer, struct, and channel.
|
|||
//
|
|||
//go:extern reflect.namedNonBasicTypesSidetable
|
|||
var namedNonBasicTypesSidetable uintptr |
|||
|
|||
//go:extern reflect.structTypesSidetable
|
|||
var structTypesSidetable byte |
|||
|
|||
//go:extern reflect.structNamesSidetable
|
|||
var structNamesSidetable byte |
|||
|
|||
//go:extern reflect.arrayTypesSidetable
|
|||
var arrayTypesSidetable byte |
|||
|
|||
// readStringSidetable reads a string from the given table (like
|
|||
// structNamesSidetable) and returns this string. No heap allocation is
|
|||
// necessary because it makes the string point directly to the raw bytes of the
|
|||
// table.
|
|||
func readStringSidetable(table unsafe.Pointer, index uintptr) string { |
|||
nameLen, namePtr := readVarint(unsafe.Pointer(uintptr(table) + index)) |
|||
return *(*string)(unsafe.Pointer(&stringHeader{ |
|||
data: namePtr, |
|||
len: nameLen, |
|||
})) |
|||
} |
|||
|
|||
// readVarint decodes a varint as used in the encoding/binary package.
|
|||
// It has an input pointer and returns the read varint and the pointer
|
|||
// incremented to the next field in the data structure, just after the varint.
|
|||
//
|
|||
// Details:
|
|||
// https://github.com/golang/go/blob/e37a1b1c/src/encoding/binary/varint.go#L7-L25
|
|||
func readVarint(buf unsafe.Pointer) (uintptr, unsafe.Pointer) { |
|||
var n uintptr |
|||
shift := uintptr(0) |
|||
for { |
|||
// Read the next byte in the buffer.
|
|||
c := *(*byte)(buf) |
|||
|
|||
// Decode the bits from this byte and add them to the output number.
|
|||
n |= uintptr(c&0x7f) << shift |
|||
shift += 7 |
|||
|
|||
// Increment the buf pointer (pointer arithmetic!).
|
|||
buf = unsafe.Pointer(uintptr(buf) + 1) |
|||
|
|||
// Check whether this is the last byte of this varint. The upper bit
|
|||
// (msb) indicates whether any bytes follow.
|
|||
if c>>7 == 0 { |
|||
return n, buf |
|||
} |
|||
} |
|||
} |
@ -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)] |
|||
} |
@ -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) |
|||
} |
|||
} |
|||
} |
@ -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" } |
|||
|
Loading…
Reference in new issue