reflect: add support for linked lists

Linked lists are usually implemented as follows:

    type linkedList struct {
        next *linkedList
        data int // whatever
    }

This caused a stack overflow while writing out the reflect run-time type
information. This has now been fixed by splitting the allocation of a
named type number from setting the underlying type in the sidetable.
Этот коммит содержится в:
Ayke van Laethem 2019-08-10 21:52:14 +02:00 коммит произвёл Ron Evans
родитель 0818a125c0
коммит ea8e4079bc
7 изменённых файлов: 128 добавлений и 84 удалений

Просмотреть файл

@ -92,7 +92,7 @@ func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
for i := 0; i < typ.NumFields(); i++ { for i := 0; i < typ.NumFields(); i++ {
fieldGlobalValue := c.getZeroValue(runtimeStructField) fieldGlobalValue := c.getZeroValue(runtimeStructField)
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, c.getTypeCode(typ.Field(i).Type()), []uint32{0}) fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, c.getTypeCode(typ.Field(i).Type()), []uint32{0})
fieldName := c.makeGlobalBytes([]byte(typ.Field(i).Name()), "reflect/types.structFieldName") fieldName := c.makeGlobalArray([]byte(typ.Field(i).Name()), "reflect/types.structFieldName", c.ctx.Int8Type())
fieldName.SetLinkage(llvm.PrivateLinkage) fieldName.SetLinkage(llvm.PrivateLinkage)
fieldName.SetUnnamedAddr(true) fieldName.SetUnnamedAddr(true)
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{ fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
@ -101,7 +101,7 @@ func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
}) })
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1}) fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
if typ.Tag(i) != "" { if typ.Tag(i) != "" {
fieldTag := c.makeGlobalBytes([]byte(typ.Tag(i)), "reflect/types.structFieldTag") fieldTag := c.makeGlobalArray([]byte(typ.Tag(i)), "reflect/types.structFieldTag", c.ctx.Int8Type())
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{ fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 0, false), llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false), llvm.ConstInt(llvm.Int32Type(), 0, false),

Просмотреть файл

@ -1,6 +1,8 @@
package compiler package compiler
import ( import (
"reflect"
"tinygo.org/x/go-llvm" "tinygo.org/x/go-llvm"
) )
@ -153,24 +155,26 @@ func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicB
return newBlock return newBlock
} }
// makeGlobalBytes creates a new LLVM global with the given name and bytes as // makeGlobalArray creates a new LLVM global with the given name and integers as
// contents, and returns the global. // contents, and returns the global.
// Note that it is left with the default linkage etc., you should set // Note that it is left with the default linkage etc., you should set
// linkage/constant/etc properties yourself. // linkage/constant/etc properties yourself.
func (c *Compiler) makeGlobalBytes(buf []byte, name string) llvm.Value { func (c *Compiler) makeGlobalArray(bufItf interface{}, name string, elementType llvm.Type) llvm.Value {
globalType := llvm.ArrayType(c.ctx.Int8Type(), len(buf)) buf := reflect.ValueOf(bufItf)
globalType := llvm.ArrayType(elementType, buf.Len())
global := llvm.AddGlobal(c.mod, globalType, name) global := llvm.AddGlobal(c.mod, globalType, name)
value := llvm.Undef(globalType) value := llvm.Undef(globalType)
for i, ch := range buf { for i := 0; i < buf.Len(); i++ {
value = llvm.ConstInsertValue(value, llvm.ConstInt(c.ctx.Int8Type(), uint64(ch), false), []uint32{uint32(i)}) ch := buf.Index(i).Uint()
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
} }
global.SetInitializer(value) global.SetInitializer(value)
return global return global
} }
// getGlobalBytes returns the byte slice contained in the i8 array of the // getGlobalBytes returns the slice contained in the array of the provided
// provided global. It can recover the bytes originally created using // global. It can recover the bytes originally created using makeGlobalArray, if
// makeGlobalBytes. // makeGlobalArray was given a byte slice.
func getGlobalBytes(global llvm.Value) []byte { func getGlobalBytes(global llvm.Value) []byte {
value := global.Initializer() value := global.Initializer()
buf := make([]byte, value.Type().ArrayLength()) buf := make([]byte, value.Type().ArrayLength())
@ -180,12 +184,12 @@ func getGlobalBytes(global llvm.Value) []byte {
return buf return buf
} }
// replaceGlobalByteWithArray replaces a global i8 in the module with a byte // replaceGlobalByteWithArray replaces a global integer type in the module with
// array, using a GEP to make the types match. It is a convenience function used // an integer array, using a GEP to make the types match. It is a convenience
// for creating reflection sidetables, for example. // function used for creating reflection sidetables, for example.
func (c *Compiler) replaceGlobalByteWithArray(name string, buf []byte) llvm.Value { func (c *Compiler) replaceGlobalIntWithArray(name string, buf interface{}) llvm.Value {
global := c.makeGlobalBytes(buf, name+".tmp")
oldGlobal := c.mod.NamedGlobal(name) oldGlobal := c.mod.NamedGlobal(name)
global := c.makeGlobalArray(buf, name+".tmp", oldGlobal.Type().ElementType())
gep := llvm.ConstGEP(global, []llvm.Value{ gep := llvm.ConstGEP(global, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false), llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false), llvm.ConstInt(c.ctx.Int32Type(), 0, false),

Просмотреть файл

@ -37,7 +37,7 @@ import (
) )
// A list of basic types and their numbers. This list should be kept in sync // 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 runtime package. // with the list of Kind constants of type.go in the reflect package.
var basicTypes = map[string]int64{ var basicTypes = map[string]int64{
"bool": 1, "bool": 1,
"int": 2, "int": 2,
@ -59,6 +59,19 @@ var basicTypes = map[string]int64{
"unsafeptr": 18, "unsafeptr": 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 // typeCodeAssignmentState keeps some global state around for type code
// assignments, used to assign one unique type code to each Go type. // assignments, used to assign one unique type code to each Go type.
type typeCodeAssignmentState struct { type typeCodeAssignmentState struct {
@ -96,7 +109,7 @@ type typeCodeAssignmentState struct {
// Note that this byte buffer is not created when it is not needed // Note that this byte buffer is not created when it is not needed
// (reflect.namedNonBasicTypesSidetable has no uses), see // (reflect.namedNonBasicTypesSidetable has no uses), see
// needsNamedTypesSidetable. // needsNamedTypesSidetable.
namedNonBasicTypesSidetable []byte namedNonBasicTypesSidetable []uint64
// This indicates whether namedNonBasicTypesSidetable needs to be created at // This indicates whether namedNonBasicTypesSidetable needs to be created at
// all. If it is false, namedNonBasicTypesSidetable will contain simple // all. If it is false, namedNonBasicTypesSidetable will contain simple
@ -144,17 +157,17 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
// Only create this sidetable when it is necessary. // Only create this sidetable when it is necessary.
if state.needsNamedNonBasicTypesSidetable { if state.needsNamedNonBasicTypesSidetable {
global := c.replaceGlobalByteWithArray("reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable) global := c.replaceGlobalIntWithArray("reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
global.SetLinkage(llvm.InternalLinkage) global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true) global.SetUnnamedAddr(true)
} }
if state.needsStructTypesSidetable { if state.needsStructTypesSidetable {
global := c.replaceGlobalByteWithArray("reflect.structTypesSidetable", state.structTypesSidetable) global := c.replaceGlobalIntWithArray("reflect.structTypesSidetable", state.structTypesSidetable)
global.SetLinkage(llvm.InternalLinkage) global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true) global.SetUnnamedAddr(true)
} }
if state.needsStructNamesSidetable { if state.needsStructNamesSidetable {
global := c.replaceGlobalByteWithArray("reflect.structNamesSidetable", state.structNamesSidetable) global := c.replaceGlobalIntWithArray("reflect.structNamesSidetable", state.structNamesSidetable)
global.SetLinkage(llvm.InternalLinkage) global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true) global.SetUnnamedAddr(true)
} }
@ -194,49 +207,76 @@ func (state *typeCodeAssignmentState) getTypeCodeNum(typecode llvm.Value) *big.I
// (channel, interface, pointer, slice) just contain the bits of the // (channel, interface, pointer, slice) just contain the bits of the
// wrapped type. Other types (like struct) need more fields and thus // wrapped type. Other types (like struct) need more fields and thus
// cannot be encoded as a simple prefix. // cannot be encoded as a simple prefix.
var num *big.Int
var classNumber int64 var classNumber int64
switch class { if n, ok := nonBasicTypes[class]; ok {
case "chan": classNumber = n
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0}) } else {
num = state.getTypeCodeNum(sub)
classNumber = 0
case "interface":
num = big.NewInt(int64(state.fallbackIndex))
state.fallbackIndex++
classNumber = 1
case "pointer":
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
num = state.getTypeCodeNum(sub)
classNumber = 2
case "slice":
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
num = state.getTypeCodeNum(sub)
classNumber = 3
case "array":
num = big.NewInt(int64(state.fallbackIndex))
state.fallbackIndex++
classNumber = 4
case "func":
num = big.NewInt(int64(state.fallbackIndex))
state.fallbackIndex++
classNumber = 5
case "map":
num = big.NewInt(int64(state.fallbackIndex))
state.fallbackIndex++
classNumber = 6
case "struct":
num = big.NewInt(int64(state.getStructTypeNum(typecode)))
classNumber = 7
default:
panic("unknown type kind: " + class) panic("unknown type kind: " + class)
} }
var num *big.Int
lowBits := (classNumber << 1) + 1 // the 5 low bits of the typecode
if name == "" { if name == "" {
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1)) num = state.getNonBasicTypeCode(class, typecode)
} else { } else {
num = big.NewInt(int64(state.getNonBasicNamedTypeNum(name, num))<<1 | 1) // We must return a named type here. But first check whether it
num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1)) // 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 := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
return state.getTypeCodeNum(sub)
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 return num
} }
} }
@ -272,29 +312,6 @@ func (state *typeCodeAssignmentState) getBasicNamedTypeNum(name string) int {
return num return num
} }
// getNonBasicNamedTypeNum returns a number unique for this named type. It tries
// to return the smallest number possible to make encoding of this type code
// easier.
func (state *typeCodeAssignmentState) getNonBasicNamedTypeNum(name string, value *big.Int) int {
if num, ok := state.namedNonBasicTypes[name]; ok {
return num
}
if !state.needsNamedNonBasicTypesSidetable {
// Use simple small integers in this case, to make these numbers
// smaller.
num := len(state.namedNonBasicTypes) + 1
state.namedNonBasicTypes[name] = num
return num
}
num := len(state.namedNonBasicTypesSidetable)
if value.BitLen() > state.uintptrLen || !value.IsUint64() {
panic("cannot store value in sidetable")
}
state.namedNonBasicTypesSidetable = append(state.namedNonBasicTypesSidetable, makeVarint(value.Uint64())...)
state.namedNonBasicTypes[name] = num
return num
}
// getStructTypeNum returns the struct type number, which is an index into // getStructTypeNum returns the struct type number, which is an index into
// reflect.structTypesSidetable or an unique number for every struct if this // reflect.structTypesSidetable or an unique number for every struct if this
// sidetable is not needed in the to-be-compiled program. // sidetable is not needed in the to-be-compiled program.

Просмотреть файл

@ -5,10 +5,10 @@ import (
) )
// This stores a varint for each named type. Named types are identified by their // 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 the // name instead of by their type. The named types stored in this struct are
// simpler non-basic types: pointer, struct, and channel. // non-basic types: pointer, struct, and channel.
//go:extern reflect.namedNonBasicTypesSidetable //go:extern reflect.namedNonBasicTypesSidetable
var namedNonBasicTypesSidetable byte var namedNonBasicTypesSidetable uintptr
//go:extern reflect.structTypesSidetable //go:extern reflect.structTypesSidetable
var structTypesSidetable byte var structTypesSidetable byte

Просмотреть файл

@ -163,7 +163,7 @@ func (t Type) stripPrefix() Type {
if (t>>4)%2 != 0 { if (t>>4)%2 != 0 {
// This is a named type. The data is stored in a sidetable. // This is a named type. The data is stored in a sidetable.
namedTypeNum := t >> 5 namedTypeNum := t >> 5
n, _ := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&namedNonBasicTypesSidetable)) + uintptr(namedTypeNum))) n := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&namedNonBasicTypesSidetable)) + uintptr(namedTypeNum)*unsafe.Sizeof(uintptr(0))))
return Type(n) return Type(n)
} }
// Not a named type, so the value is stored directly in the type code. // Not a named type, so the value is stored directly in the type code.

7
testdata/reflect.go предоставленный
Просмотреть файл

@ -22,6 +22,10 @@ type (
buf []byte buf []byte
Buf []byte Buf []byte
} }
linkedList struct {
next *linkedList `description:"chain"`
foo int
}
) )
func main() { func main() {
@ -103,6 +107,9 @@ func main() {
c int8 c int8
}{42, 321, 123}, }{42, 321, 123},
mystruct{5, point{-5, 3}, struct{}{}, []byte{'G', 'o'}, []byte{'X'}}, mystruct{5, point{-5, 3}, struct{}{}, []byte{'G', 'o'}, []byte{'X'}},
&linkedList{
foo: 42,
},
} { } {
showValue(reflect.ValueOf(v), "") showValue(reflect.ValueOf(v), "")
} }

16
testdata/reflect.txt предоставленный
Просмотреть файл

@ -293,6 +293,22 @@ reflect type: struct
indexing: 0 indexing: 0
reflect type: uint8 settable=true reflect type: uint8 settable=true
uint: 88 uint: 88
reflect type: ptr
pointer: true struct
nil: false
reflect type: struct settable=true
struct: 2
field: 0 next
tag: description:"chain"
embedded: false
reflect type: ptr
pointer: false struct
nil: true
field: 1 foo
tag:
embedded: false
reflect type: int
int: 42
sizes: sizes:
int8 1 8 int8 1 8