Skip to content

Commit ea8e407

Browse files
aykevldeadprogram
authored andcommitted
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.
1 parent 0818a12 commit ea8e407

File tree

7 files changed

+128
-84
lines changed

7 files changed

+128
-84
lines changed

compiler/interface.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
9292
for i := 0; i < typ.NumFields(); i++ {
9393
fieldGlobalValue := c.getZeroValue(runtimeStructField)
9494
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, c.getTypeCode(typ.Field(i).Type()), []uint32{0})
95-
fieldName := c.makeGlobalBytes([]byte(typ.Field(i).Name()), "reflect/types.structFieldName")
95+
fieldName := c.makeGlobalArray([]byte(typ.Field(i).Name()), "reflect/types.structFieldName", c.ctx.Int8Type())
9696
fieldName.SetLinkage(llvm.PrivateLinkage)
9797
fieldName.SetUnnamedAddr(true)
9898
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
@@ -101,7 +101,7 @@ func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
101101
})
102102
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
103103
if typ.Tag(i) != "" {
104-
fieldTag := c.makeGlobalBytes([]byte(typ.Tag(i)), "reflect/types.structFieldTag")
104+
fieldTag := c.makeGlobalArray([]byte(typ.Tag(i)), "reflect/types.structFieldTag", c.ctx.Int8Type())
105105
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
106106
llvm.ConstInt(llvm.Int32Type(), 0, false),
107107
llvm.ConstInt(llvm.Int32Type(), 0, false),

compiler/llvm.go

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package compiler
22

33
import (
4+
"reflect"
5+
46
"tinygo.org/x/go-llvm"
57
)
68

@@ -153,24 +155,26 @@ func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicB
153155
return newBlock
154156
}
155157

156-
// makeGlobalBytes creates a new LLVM global with the given name and bytes as
158+
// makeGlobalArray creates a new LLVM global with the given name and integers as
157159
// contents, and returns the global.
158160
// Note that it is left with the default linkage etc., you should set
159161
// linkage/constant/etc properties yourself.
160-
func (c *Compiler) makeGlobalBytes(buf []byte, name string) llvm.Value {
161-
globalType := llvm.ArrayType(c.ctx.Int8Type(), len(buf))
162+
func (c *Compiler) makeGlobalArray(bufItf interface{}, name string, elementType llvm.Type) llvm.Value {
163+
buf := reflect.ValueOf(bufItf)
164+
globalType := llvm.ArrayType(elementType, buf.Len())
162165
global := llvm.AddGlobal(c.mod, globalType, name)
163166
value := llvm.Undef(globalType)
164-
for i, ch := range buf {
165-
value = llvm.ConstInsertValue(value, llvm.ConstInt(c.ctx.Int8Type(), uint64(ch), false), []uint32{uint32(i)})
167+
for i := 0; i < buf.Len(); i++ {
168+
ch := buf.Index(i).Uint()
169+
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
166170
}
167171
global.SetInitializer(value)
168172
return global
169173
}
170174

171-
// getGlobalBytes returns the byte slice contained in the i8 array of the
172-
// provided global. It can recover the bytes originally created using
173-
// makeGlobalBytes.
175+
// getGlobalBytes returns the slice contained in the array of the provided
176+
// global. It can recover the bytes originally created using makeGlobalArray, if
177+
// makeGlobalArray was given a byte slice.
174178
func getGlobalBytes(global llvm.Value) []byte {
175179
value := global.Initializer()
176180
buf := make([]byte, value.Type().ArrayLength())
@@ -180,12 +184,12 @@ func getGlobalBytes(global llvm.Value) []byte {
180184
return buf
181185
}
182186

183-
// replaceGlobalByteWithArray replaces a global i8 in the module with a byte
184-
// array, using a GEP to make the types match. It is a convenience function used
185-
// for creating reflection sidetables, for example.
186-
func (c *Compiler) replaceGlobalByteWithArray(name string, buf []byte) llvm.Value {
187-
global := c.makeGlobalBytes(buf, name+".tmp")
187+
// replaceGlobalByteWithArray replaces a global integer type in the module with
188+
// an integer array, using a GEP to make the types match. It is a convenience
189+
// function used for creating reflection sidetables, for example.
190+
func (c *Compiler) replaceGlobalIntWithArray(name string, buf interface{}) llvm.Value {
188191
oldGlobal := c.mod.NamedGlobal(name)
192+
global := c.makeGlobalArray(buf, name+".tmp", oldGlobal.Type().ElementType())
189193
gep := llvm.ConstGEP(global, []llvm.Value{
190194
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
191195
llvm.ConstInt(c.ctx.Int32Type(), 0, false),

compiler/reflect.go

Lines changed: 82 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import (
3737
)
3838

3939
// A list of basic types and their numbers. This list should be kept in sync
40-
// with the list of Kind constants of type.go in the runtime package.
40+
// with the list of Kind constants of type.go in the reflect package.
4141
var basicTypes = map[string]int64{
4242
"bool": 1,
4343
"int": 2,
@@ -59,6 +59,19 @@ var basicTypes = map[string]int64{
5959
"unsafeptr": 18,
6060
}
6161

62+
// A list of non-basic types. Adding 19 to this number will give the Kind as
63+
// used in src/reflect/types.go, and it must be kept in sync with that list.
64+
var nonBasicTypes = map[string]int64{
65+
"chan": 0,
66+
"interface": 1,
67+
"pointer": 2,
68+
"slice": 3,
69+
"array": 4,
70+
"func": 5,
71+
"map": 6,
72+
"struct": 7,
73+
}
74+
6275
// typeCodeAssignmentState keeps some global state around for type code
6376
// assignments, used to assign one unique type code to each Go type.
6477
type typeCodeAssignmentState struct {
@@ -96,7 +109,7 @@ type typeCodeAssignmentState struct {
96109
// Note that this byte buffer is not created when it is not needed
97110
// (reflect.namedNonBasicTypesSidetable has no uses), see
98111
// needsNamedTypesSidetable.
99-
namedNonBasicTypesSidetable []byte
112+
namedNonBasicTypesSidetable []uint64
100113

101114
// This indicates whether namedNonBasicTypesSidetable needs to be created at
102115
// all. If it is false, namedNonBasicTypesSidetable will contain simple
@@ -144,17 +157,17 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
144157

145158
// Only create this sidetable when it is necessary.
146159
if state.needsNamedNonBasicTypesSidetable {
147-
global := c.replaceGlobalByteWithArray("reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
160+
global := c.replaceGlobalIntWithArray("reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
148161
global.SetLinkage(llvm.InternalLinkage)
149162
global.SetUnnamedAddr(true)
150163
}
151164
if state.needsStructTypesSidetable {
152-
global := c.replaceGlobalByteWithArray("reflect.structTypesSidetable", state.structTypesSidetable)
165+
global := c.replaceGlobalIntWithArray("reflect.structTypesSidetable", state.structTypesSidetable)
153166
global.SetLinkage(llvm.InternalLinkage)
154167
global.SetUnnamedAddr(true)
155168
}
156169
if state.needsStructNamesSidetable {
157-
global := c.replaceGlobalByteWithArray("reflect.structNamesSidetable", state.structNamesSidetable)
170+
global := c.replaceGlobalIntWithArray("reflect.structNamesSidetable", state.structNamesSidetable)
158171
global.SetLinkage(llvm.InternalLinkage)
159172
global.SetUnnamedAddr(true)
160173
}
@@ -194,49 +207,76 @@ func (state *typeCodeAssignmentState) getTypeCodeNum(typecode llvm.Value) *big.I
194207
// (channel, interface, pointer, slice) just contain the bits of the
195208
// wrapped type. Other types (like struct) need more fields and thus
196209
// cannot be encoded as a simple prefix.
197-
var num *big.Int
198210
var classNumber int64
199-
switch class {
200-
case "chan":
201-
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
202-
num = state.getTypeCodeNum(sub)
203-
classNumber = 0
204-
case "interface":
205-
num = big.NewInt(int64(state.fallbackIndex))
206-
state.fallbackIndex++
207-
classNumber = 1
208-
case "pointer":
209-
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
210-
num = state.getTypeCodeNum(sub)
211-
classNumber = 2
212-
case "slice":
213-
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
214-
num = state.getTypeCodeNum(sub)
215-
classNumber = 3
216-
case "array":
217-
num = big.NewInt(int64(state.fallbackIndex))
218-
state.fallbackIndex++
219-
classNumber = 4
220-
case "func":
221-
num = big.NewInt(int64(state.fallbackIndex))
222-
state.fallbackIndex++
223-
classNumber = 5
224-
case "map":
225-
num = big.NewInt(int64(state.fallbackIndex))
226-
state.fallbackIndex++
227-
classNumber = 6
228-
case "struct":
229-
num = big.NewInt(int64(state.getStructTypeNum(typecode)))
230-
classNumber = 7
231-
default:
211+
if n, ok := nonBasicTypes[class]; ok {
212+
classNumber = n
213+
} else {
232214
panic("unknown type kind: " + class)
233215
}
216+
var num *big.Int
217+
lowBits := (classNumber << 1) + 1 // the 5 low bits of the typecode
234218
if name == "" {
235-
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
219+
num = state.getNonBasicTypeCode(class, typecode)
236220
} else {
237-
num = big.NewInt(int64(state.getNonBasicNamedTypeNum(name, num))<<1 | 1)
238-
num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1))
221+
// We must return a named type here. But first check whether it
222+
// has already been defined.
223+
if index, ok := state.namedNonBasicTypes[name]; ok {
224+
num := big.NewInt(int64(index))
225+
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1+(1<<4)))
226+
return num
227+
}
228+
lowBits |= 1 << 4 // set the 'n' bit (see above)
229+
if !state.needsNamedNonBasicTypesSidetable {
230+
// Use simple small integers in this case, to make these numbers
231+
// smaller.
232+
index := len(state.namedNonBasicTypes) + 1
233+
state.namedNonBasicTypes[name] = index
234+
num = big.NewInt(int64(index))
235+
} else {
236+
// We need to store full type information.
237+
// First allocate a number in the named non-basic type
238+
// sidetable.
239+
index := len(state.namedNonBasicTypesSidetable)
240+
state.namedNonBasicTypesSidetable = append(state.namedNonBasicTypesSidetable, 0)
241+
state.namedNonBasicTypes[name] = index
242+
// Get the typecode of the underlying type (which could be the
243+
// element type in the case of pointers, for example).
244+
num = state.getNonBasicTypeCode(class, typecode)
245+
if num.BitLen() > state.uintptrLen || !num.IsUint64() {
246+
panic("cannot store value in sidetable")
247+
}
248+
// Now update the side table with the number we just
249+
// determined. We need this multi-step approach to avoid stack
250+
// overflow due to adding types recursively in the case of
251+
// linked lists (a pointer which points to a struct that
252+
// contains that same pointer).
253+
state.namedNonBasicTypesSidetable[index] = num.Uint64()
254+
num = big.NewInt(int64(index))
255+
}
239256
}
257+
// Concatenate the 'num' and 'lowBits' bitstrings.
258+
num.Lsh(num, 5).Or(num, big.NewInt(lowBits))
259+
return num
260+
}
261+
}
262+
263+
// getNonBasicTypeCode is used by getTypeCodeNum. It returns the upper bits of
264+
// the type code used there in the type code.
265+
func (state *typeCodeAssignmentState) getNonBasicTypeCode(class string, typecode llvm.Value) *big.Int {
266+
switch class {
267+
case "chan", "pointer", "slice":
268+
// Prefix-style type kinds. The upper bits contain the element type.
269+
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
270+
return state.getTypeCodeNum(sub)
271+
case "struct":
272+
// More complicated type kind. The upper bits contain the index to the
273+
// struct type in the struct types sidetable.
274+
return big.NewInt(int64(state.getStructTypeNum(typecode)))
275+
default:
276+
// Type has not yet been implemented, so fall back by using a unique
277+
// number.
278+
num := big.NewInt(int64(state.fallbackIndex))
279+
state.fallbackIndex++
240280
return num
241281
}
242282
}
@@ -272,29 +312,6 @@ func (state *typeCodeAssignmentState) getBasicNamedTypeNum(name string) int {
272312
return num
273313
}
274314

275-
// getNonBasicNamedTypeNum returns a number unique for this named type. It tries
276-
// to return the smallest number possible to make encoding of this type code
277-
// easier.
278-
func (state *typeCodeAssignmentState) getNonBasicNamedTypeNum(name string, value *big.Int) int {
279-
if num, ok := state.namedNonBasicTypes[name]; ok {
280-
return num
281-
}
282-
if !state.needsNamedNonBasicTypesSidetable {
283-
// Use simple small integers in this case, to make these numbers
284-
// smaller.
285-
num := len(state.namedNonBasicTypes) + 1
286-
state.namedNonBasicTypes[name] = num
287-
return num
288-
}
289-
num := len(state.namedNonBasicTypesSidetable)
290-
if value.BitLen() > state.uintptrLen || !value.IsUint64() {
291-
panic("cannot store value in sidetable")
292-
}
293-
state.namedNonBasicTypesSidetable = append(state.namedNonBasicTypesSidetable, makeVarint(value.Uint64())...)
294-
state.namedNonBasicTypes[name] = num
295-
return num
296-
}
297-
298315
// getStructTypeNum returns the struct type number, which is an index into
299316
// reflect.structTypesSidetable or an unique number for every struct if this
300317
// sidetable is not needed in the to-be-compiled program.

src/reflect/sidetables.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ import (
55
)
66

77
// This stores a varint for each named type. Named types are identified by their
8-
// name instead of by their type. The named types stored in this struct are the
9-
// simpler non-basic types: pointer, struct, and channel.
8+
// name instead of by their type. The named types stored in this struct are
9+
// non-basic types: pointer, struct, and channel.
1010
//go:extern reflect.namedNonBasicTypesSidetable
11-
var namedNonBasicTypesSidetable byte
11+
var namedNonBasicTypesSidetable uintptr
1212

1313
//go:extern reflect.structTypesSidetable
1414
var structTypesSidetable byte

src/reflect/type.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ func (t Type) stripPrefix() Type {
163163
if (t>>4)%2 != 0 {
164164
// This is a named type. The data is stored in a sidetable.
165165
namedTypeNum := t >> 5
166-
n, _ := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&namedNonBasicTypesSidetable)) + uintptr(namedTypeNum)))
166+
n := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&namedNonBasicTypesSidetable)) + uintptr(namedTypeNum)*unsafe.Sizeof(uintptr(0))))
167167
return Type(n)
168168
}
169169
// Not a named type, so the value is stored directly in the type code.

testdata/reflect.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ type (
2222
buf []byte
2323
Buf []byte
2424
}
25+
linkedList struct {
26+
next *linkedList `description:"chain"`
27+
foo int
28+
}
2529
)
2630

2731
func main() {
@@ -103,6 +107,9 @@ func main() {
103107
c int8
104108
}{42, 321, 123},
105109
mystruct{5, point{-5, 3}, struct{}{}, []byte{'G', 'o'}, []byte{'X'}},
110+
&linkedList{
111+
foo: 42,
112+
},
106113
} {
107114
showValue(reflect.ValueOf(v), "")
108115
}

testdata/reflect.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,22 @@ reflect type: struct
293293
indexing: 0
294294
reflect type: uint8 settable=true
295295
uint: 88
296+
reflect type: ptr
297+
pointer: true struct
298+
nil: false
299+
reflect type: struct settable=true
300+
struct: 2
301+
field: 0 next
302+
tag: description:"chain"
303+
embedded: false
304+
reflect type: ptr
305+
pointer: false struct
306+
nil: true
307+
field: 1 foo
308+
tag:
309+
embedded: false
310+
reflect type: int
311+
int: 42
296312

297313
sizes:
298314
int8 1 8

0 commit comments

Comments
 (0)