reflect: add support for struct types

This commit is contained in:
Ayke van Laethem
2019-08-08 14:41:52 +02:00
committed by Ron Evans
parent 5012be337f
commit e2c8654237
9 changed files with 654 additions and 134 deletions
+56 -7
View File
@@ -53,21 +53,28 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
// Some type classes contain more information for underlying types or
// element types. Store it directly in the typecode global to make
// reflect lowering simpler.
var elementType types.Type
var references llvm.Value
switch typ := typ.(type) {
case *types.Named:
elementType = typ.Underlying()
references = c.getTypeCode(typ.Underlying())
case *types.Chan:
elementType = typ.Elem()
references = c.getTypeCode(typ.Elem())
case *types.Pointer:
elementType = typ.Elem()
references = c.getTypeCode(typ.Elem())
case *types.Slice:
elementType = typ.Elem()
references = c.getTypeCode(typ.Elem())
case *types.Struct:
// Take a pointer to the typecodeID of the first field (if it exists).
structGlobal := c.makeStructTypeFields(typ)
references = llvm.ConstGEP(structGlobal, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
})
}
if elementType != nil {
if !references.IsNil() {
// Set the 'references' field of the runtime.typecodeID struct.
globalValue := c.getZeroValue(global.Type().ElementType())
globalValue = llvm.ConstInsertValue(globalValue, c.getTypeCode(elementType), []uint32{0})
globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0})
global.SetInitializer(globalValue)
global.SetLinkage(llvm.PrivateLinkage)
}
@@ -76,6 +83,48 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
return global
}
// makeStructTypeFields creates a new global that stores all type information
// related to this struct type, and returns the resulting global. This global is
// actually an array of all the fields in the structs.
func (c *Compiler) makeStructTypeFields(typ *types.Struct) llvm.Value {
// The global is an array of runtime.structField structs.
runtimeStructField := c.getLLVMRuntimeType("structField")
structGlobalType := llvm.ArrayType(runtimeStructField, typ.NumFields())
structGlobal := llvm.AddGlobal(c.mod, structGlobalType, "reflect/types.structFields")
structGlobalValue := c.getZeroValue(structGlobalType)
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 = llvm.ConstGEP(fieldName, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
})
fieldName.SetLinkage(llvm.PrivateLinkage)
fieldName.SetUnnamedAddr(true)
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldName, []uint32{1})
if typ.Tag(i) != "" {
fieldTag := c.makeGlobalBytes([]byte(typ.Tag(i)), "reflect/types.structFieldTag")
fieldTag = llvm.ConstGEP(fieldTag, []llvm.Value{
llvm.ConstInt(llvm.Int32Type(), 0, false),
llvm.ConstInt(llvm.Int32Type(), 0, false),
})
fieldTag.SetLinkage(llvm.PrivateLinkage)
fieldTag.SetUnnamedAddr(true)
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldTag, []uint32{2})
}
if typ.Field(i).Embedded() {
fieldEmbedded := llvm.ConstInt(c.ctx.Int1Type(), 1, false)
fieldGlobalValue = llvm.ConstInsertValue(fieldGlobalValue, fieldEmbedded, []uint32{3})
}
structGlobalValue = llvm.ConstInsertValue(structGlobalValue, fieldGlobalValue, []uint32{uint32(i)})
}
structGlobal.SetInitializer(structGlobalValue)
structGlobal.SetUnnamedAddr(true)
structGlobal.SetLinkage(llvm.PrivateLinkage)
return structGlobal
}
// getTypeCodeName returns a name for this type that can be used in the
// interface lowering pass to assign type codes as expected by the reflect
// package. See getTypeCodeNum.
+43
View File
@@ -152,3 +152,46 @@ 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
// 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))
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)})
}
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.
func getGlobalBytes(global llvm.Value) []byte {
value := global.Initializer()
buf := make([]byte, value.Type().ArrayLength())
for i := range buf {
buf[i] = byte(llvm.ConstExtractValue(value, []uint32{uint32(i)}).ZExtValue())
}
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")
oldGlobal := c.mod.NamedGlobal(name)
gep := llvm.ConstGEP(global, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
})
oldGlobal.ReplaceAllUsesWith(gep)
oldGlobal.EraseFromParentAsGlobal()
global.SetName(name)
return global
}
+155 -54
View File
@@ -28,6 +28,8 @@ package compiler
// non-basic types have their underlying type stored in a sidetable.
import (
"encoding/binary"
"go/ast"
"math/big"
"strings"
@@ -65,11 +67,25 @@ type typeCodeAssignmentState struct {
// 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 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
@@ -82,10 +98,6 @@ type typeCodeAssignmentState struct {
// needsNamedTypesSidetable.
namedNonBasicTypesSidetable []byte
// This is the length of an uintptr. Only used occasionally to know whether
// a given number can be encoded as a varint.
uintptrLen int
// This indicates whether namedNonBasicTypesSidetable needs to be created at
// all. If it is false, namedNonBasicTypesSidetable will contain simple
// monotonically increasing numbers.
@@ -109,13 +121,17 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
// Assign typecodes the way the reflect package expects.
state := typeCodeAssignmentState{
fallbackIndex: 1,
uintptrLen: c.uintptrType.IntTypeWidth(),
namedBasicTypes: make(map[string]int),
namedNonBasicTypes: make(map[string]int),
uintptrLen: c.uintptrType.IntTypeWidth(),
structTypes: make(map[string]int),
structNames: make(map[string]int),
needsNamedNonBasicTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.namedNonBasicTypesSidetable"))) != 0,
needsStructTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structTypesSidetable"))) != 0,
needsStructNamesSidetable: len(getUses(c.mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
}
for _, t := range typeSlice {
num := c.getTypeCodeNum(t.typecode, &state)
num := state.getTypeCodeNum(t.typecode)
if num.BitLen() > c.uintptrType.IntTypeWidth() || !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.
@@ -128,29 +144,26 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
// Only create this sidetable when it is necessary.
if state.needsNamedNonBasicTypesSidetable {
// Create the sidetable and replace the old dummy global with this value.
globalType := llvm.ArrayType(c.ctx.Int8Type(), len(state.namedNonBasicTypesSidetable))
global := llvm.AddGlobal(c.mod, globalType, "reflect.namedNonBasicTypesSidetable.tmp")
value := llvm.Undef(globalType)
for i, ch := range state.namedNonBasicTypesSidetable {
value = llvm.ConstInsertValue(value, llvm.ConstInt(c.ctx.Int8Type(), uint64(ch), false), []uint32{uint32(i)})
}
global.SetInitializer(value)
oldGlobal := c.mod.NamedGlobal("reflect.namedNonBasicTypesSidetable")
gep := llvm.ConstGEP(global, []llvm.Value{
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
llvm.ConstInt(c.ctx.Int32Type(), 0, false),
})
oldGlobal.ReplaceAllUsesWith(gep)
oldGlobal.EraseFromParentAsGlobal()
global.SetName("reflect.namedNonBasicTypesSidetable")
global := c.replaceGlobalByteWithArray("reflect.namedNonBasicTypesSidetable", state.namedNonBasicTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
if state.needsStructTypesSidetable {
global := c.replaceGlobalByteWithArray("reflect.structTypesSidetable", state.structTypesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
if state.needsStructNamesSidetable {
global := c.replaceGlobalByteWithArray("reflect.structNamesSidetable", state.structNamesSidetable)
global.SetLinkage(llvm.InternalLinkage)
global.SetUnnamedAddr(true)
}
}
// 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 (c *Compiler) getTypeCodeNum(typecode llvm.Value, state *typeCodeAssignmentState) *big.Int {
func (state *typeCodeAssignmentState) getTypeCodeNum(typecode llvm.Value) *big.Int {
// Note: see src/reflect/type.go for bit allocations.
class, value := getClassAndValueFromTypeCode(typecode)
name := ""
@@ -186,7 +199,7 @@ func (c *Compiler) getTypeCodeNum(typecode llvm.Value, state *typeCodeAssignment
switch class {
case "chan":
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
num = c.getTypeCodeNum(sub, state)
num = state.getTypeCodeNum(sub)
classNumber = 0
case "interface":
num = big.NewInt(int64(state.fallbackIndex))
@@ -194,11 +207,11 @@ func (c *Compiler) getTypeCodeNum(typecode llvm.Value, state *typeCodeAssignment
classNumber = 1
case "pointer":
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
num = c.getTypeCodeNum(sub, state)
num = state.getTypeCodeNum(sub)
classNumber = 2
case "slice":
sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0})
num = c.getTypeCodeNum(sub, state)
num = state.getTypeCodeNum(sub)
classNumber = 3
case "array":
num = big.NewInt(int64(state.fallbackIndex))
@@ -213,8 +226,7 @@ func (c *Compiler) getTypeCodeNum(typecode llvm.Value, state *typeCodeAssignment
state.fallbackIndex++
classNumber = 6
case "struct":
num = big.NewInt(int64(state.fallbackIndex))
state.fallbackIndex++
num = big.NewInt(int64(state.getStructTypeNum(typecode)))
classNumber = 7
default:
panic("unknown type kind: " + class)
@@ -283,36 +295,125 @@ func (state *typeCodeAssignmentState) getNonBasicNamedTypeNum(name string, value
return num
}
// makeVarint encodes a varint in a way that should be easy to decode.
// It may need to be decoded very quickly at runtime at low-powered processors
// so should be efficient to decode.
// The current algorithm is probably not even close to efficient, but it is easy
// to change as the format is only used inside the same program.
func makeVarint(n uint64) []byte {
// This is the reverse of what src/runtime/sidetables.go does.
buf := make([]byte, 0, 8)
for {
c := byte(n & 0x7f << 1)
n >>= 7
if n != 0 {
c |= 1
// 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 := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0}).Operand(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 := llvm.ConstExtractValue(structTypeGlobal, []uint32{uint32(i)})
nameGlobal := llvm.ConstExtractValue(field, []uint32{1})
if nameGlobal == llvm.ConstPointerNull(nameGlobal.Type()) {
panic("compiler: no name for this struct field")
}
buf = append(buf, c)
if n == 0 {
break
fieldNameBytes := getGlobalBytes(nameGlobal.Operand(0))
fieldNameNumber := state.getStructNameNumber(fieldNameBytes)
// See whether this struct field has an associated tag, and if so,
// store that tag in the tags sidetable.
tagGlobal := llvm.ConstExtractValue(field, []uint32{2})
hasTag := false
tagNumber := 0
if tagGlobal != llvm.ConstPointerNull(tagGlobal.Type()) {
hasTag = true
tagBytes := getGlobalBytes(tagGlobal.Operand(0))
tagNumber = state.getStructNameNumber(tagBytes)
}
// The 'embedded' or 'anonymous' flag for this field.
embedded := llvm.ConstExtractValue(field, []uint32{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(llvm.ConstExtractValue(field, []uint32{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))...)
}
}
reverseBytes(buf)
return buf
num := len(state.structTypesSidetable)
state.structTypes[name] = num
state.structTypesSidetable = append(state.structTypesSidetable, buf...)
return num
}
func reverseBytes(s []byte) {
// Actually copied from https://blog.golang.org/why-generics
first := 0
last := len(s) - 1
for first < last {
s[first], s[last] = s[last], s[first]
first++
last--
// 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)]
}