Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions compiler/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
for i := 0; i < typ.NumFields(); i++ {
fieldGlobalValue := c.getZeroValue(runtimeStructField)
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.SetUnnamedAddr(true)
fieldName = llvm.ConstGEP(fieldName, []llvm.Value{
Expand All @@ -101,7 +101,7 @@ func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
})
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
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{
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
Expand Down
30 changes: 17 additions & 13 deletions compiler/llvm.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package compiler

import (
"reflect"

"tinygo.org/x/go-llvm"
)

Expand Down Expand Up @@ -153,24 +155,26 @@ func (c *Compiler) splitBasicBlock(afterInst llvm.Value, insertAfter llvm.BasicB
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.
// Note that it is left with the default linkage etc., you should set
// linkage/constant/etc properties yourself.
func (c *Compiler) makeGlobalBytes(buf []byte, name string) llvm.Value {
globalType := llvm.ArrayType(c.ctx.Int8Type(), len(buf))
func (c *Compiler) makeGlobalArray(bufItf interface{}, name string, elementType llvm.Type) llvm.Value {
buf := reflect.ValueOf(bufItf)
globalType := llvm.ArrayType(elementType, buf.Len())
global := llvm.AddGlobal(c.mod, globalType, name)
value := llvm.Undef(globalType)
for i, ch := range buf {
value = llvm.ConstInsertValue(value, llvm.ConstInt(c.ctx.Int8Type(), uint64(ch), false), []uint32{uint32(i)})
for i := 0; i < buf.Len(); i++ {
ch := buf.Index(i).Uint()
value = llvm.ConstInsertValue(value, llvm.ConstInt(elementType, ch, false), []uint32{uint32(i)})
}
global.SetInitializer(value)
return global
}

// getGlobalBytes returns the byte slice contained in the i8 array of the
// provided global. It can recover the bytes originally created using
// makeGlobalBytes.
// getGlobalBytes returns the slice contained in the array of the provided
// global. It can recover the bytes originally created using makeGlobalArray, if
// makeGlobalArray was given a byte slice.
func getGlobalBytes(global llvm.Value) []byte {
value := global.Initializer()
buf := make([]byte, value.Type().ArrayLength())
Expand All @@ -180,12 +184,12 @@ func getGlobalBytes(global llvm.Value) []byte {
return buf
}

// replaceGlobalByteWithArray replaces a global i8 in the module with a byte
// array, using a GEP to make the types match. It is a convenience function used
// for creating reflection sidetables, for example.
func (c *Compiler) replaceGlobalByteWithArray(name string, buf []byte) llvm.Value {
global := c.makeGlobalBytes(buf, name+".tmp")
// replaceGlobalByteWithArray replaces a global integer type in the module with
// an integer array, using a GEP to make the types match. It is a convenience
// function used for creating reflection sidetables, for example.
func (c *Compiler) replaceGlobalIntWithArray(name string, buf interface{}) llvm.Value {
oldGlobal := c.mod.NamedGlobal(name)
global := c.makeGlobalArray(buf, name+".tmp", oldGlobal.Type().ElementType())
gep := llvm.ConstGEP(global, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
Expand Down
147 changes: 82 additions & 65 deletions compiler/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import (
)

// 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{
"bool": 1,
"int": 2,
Expand All @@ -59,6 +59,19 @@ var basicTypes = map[string]int64{
"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
// assignments, used to assign one unique type code to each Go type.
type typeCodeAssignmentState struct {
Expand Down Expand Up @@ -96,7 +109,7 @@ type typeCodeAssignmentState struct {
// Note that this byte buffer is not created when it is not needed
// (reflect.namedNonBasicTypesSidetable has no uses), see
// needsNamedTypesSidetable.
namedNonBasicTypesSidetable []byte
namedNonBasicTypesSidetable []uint64

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

// Only create this sidetable when it is necessary.
if state.needsNamedNonBasicTypesSidetable {
global := c.replaceGlobalByteWithArray("reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
global := c.replaceGlobalIntWithArray("reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
if state.needsStructTypesSidetable {
global := c.replaceGlobalByteWithArray("reflect.structTypesSidetable", state.structTypesSidetable)
global := c.replaceGlobalIntWithArray("reflect.structTypesSidetable", state.structTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
if state.needsStructNamesSidetable {
global := c.replaceGlobalByteWithArray("reflect.structNamesSidetable", state.structNamesSidetable)
global := c.replaceGlobalIntWithArray("reflect.structNamesSidetable", state.structNamesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
Expand Down Expand Up @@ -194,49 +207,76 @@ func (state *typeCodeAssignmentState) getTypeCodeNum(typecode llvm.Value) *big.I
// (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 num *big.Int
var classNumber int64
switch class {
case "chan":
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
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:
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.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
num = state.getNonBasicTypeCode(class, typecode)
} else {
num = big.NewInt(int64(state.getNonBasicNamedTypeNum(name, num))<<1 | 1)
num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1))
// 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 := 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
}
}
Expand Down Expand Up @@ -272,29 +312,6 @@ func (state *typeCodeAssignmentState) getBasicNamedTypeNum(name string) int {
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
// reflect.structTypesSidetable or an unique number for every struct if this
// sidetable is not needed in the to-be-compiled program.
Expand Down
6 changes: 3 additions & 3 deletions src/reflect/sidetables.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import (
)

// 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
// simpler non-basic types: pointer, struct, and channel.
// 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 byte
var namedNonBasicTypesSidetable uintptr

//go:extern reflect.structTypesSidetable
var structTypesSidetable byte
Expand Down
2 changes: 1 addition & 1 deletion src/reflect/type.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ func (t Type) stripPrefix() Type {
if (t>>4)%2 != 0 {
// This is a named type. The data is stored in a sidetable.
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)
}
// Not a named type, so the value is stored directly in the type code.
Expand Down
10 changes: 10 additions & 0 deletions src/reflect/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,14 @@ func (v Value) Kind() Kind {
return v.Type().Kind()
}

// IsNil returns whether the value is the nil value. It panics if the value Kind
// is not a channel, map, pointer, function, slice, or interface.
func (v Value) IsNil() bool {
switch v.Kind() {
case Chan, Map, Ptr:
if v.isIndirect() {
return *(*uintptr)(v.value) == 0
}
return v.value == nil
case Func:
if v.value == nil {
Expand All @@ -96,9 +101,14 @@ func (v Value) IsNil() bool {
}
}

// Pointer returns the underlying pointer of the given value for the following
// types: chan, map, pointer, unsafe.Pointer, slice, func.
func (v Value) Pointer() uintptr {
switch v.Kind() {
case Chan, Map, Ptr, UnsafePointer:
if v.isIndirect() {
return *(*uintptr)(v.value)
}
return uintptr(v.value)
case Slice:
slice := (*SliceHeader)(v.value)
Expand Down
7 changes: 7 additions & 0 deletions testdata/reflect.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ type (
buf []byte
Buf []byte
}
linkedList struct {
next *linkedList `description:"chain"`
foo int
}
)

func main() {
Expand Down Expand Up @@ -103,6 +107,9 @@ func main() {
c int8
}{42, 321, 123},
mystruct{5, point{-5, 3}, struct{}{}, []byte{'G', 'o'}, []byte{'X'}},
&linkedList{
foo: 42,
},
} {
showValue(reflect.ValueOf(v), "")
}
Expand Down
16 changes: 16 additions & 0 deletions testdata/reflect.txt
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,22 @@ reflect type: struct
indexing: 0
reflect type: uint8 settable=true
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:
int8 1 8
Expand Down