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
+37 -11
View File
@@ -10,22 +10,48 @@ import (
//go:extern reflect.namedNonBasicTypesSidetable
var namedNonBasicTypesSidetable byte
func readVarint(buf unsafe.Pointer) Type {
var t Type
//go:extern reflect.structTypesSidetable
var structTypesSidetable byte
//go:extern reflect.structNamesSidetable
var structNamesSidetable 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: uintptr(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.
// Read the next byte in the buffer.
c := *(*byte)(buf)
// Add this byte to the type code. The upper 7 bits are the value.
t = t<<7 | Type(c>>1)
// Check whether this is the last byte of this varint. The lower bit
// indicates whether any bytes follow.
if c%1 == 0 {
return t
}
// 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
}
}
}
+132 -13
View File
@@ -145,23 +145,102 @@ func (t Type) Kind() Kind {
func (t Type) Elem() Type {
switch t.Kind() {
case Chan, Ptr, Slice:
// Look at the 'n' bit in the type code (see the top of this file) to
// see whether this is a named type.
if (t>>4)%2 != 0 {
// This is a named type. The element type is stored in a sidetable.
namedTypeNum := t >> 5
return readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&namedNonBasicTypesSidetable)) + uintptr(namedTypeNum)))
}
// Not a named type, so the element type is stored directly in the type
// code.
return t >> 5
return t.stripPrefix()
default: // not implemented: Array, Map
panic("unimplemented: (reflect.Type).Elem()")
}
}
// stripPrefix removes the "prefix" (the first 5 bytes of the type code) from
// the type code. If this is a named type, it will resolve the underlying type
// (which is the data for this named type). If it is not, the lower bits are
// simply shifted off.
//
// The behavior is only defined for non-basic types.
func (t Type) stripPrefix() Type {
// Look at the 'n' bit in the type code (see the top of this file) to see
// whether this is a named 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)))
return Type(n)
}
// Not a named type, so the value is stored directly in the type code.
return t >> 5
}
// Field returns the type of the i'th field of this struct type. It panics if t
// is not a struct type.
func (t Type) Field(i int) StructField {
panic("unimplemented: (reflect.Type).Field()")
if t.Kind() != Struct {
panic(&TypeError{"Field"})
}
structIdentifier := t.stripPrefix()
numField, p := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&structTypesSidetable)) + uintptr(structIdentifier)))
if uint(i) >= uint(numField) {
panic("reflect: field index out of range")
}
// Iterate over every field in the struct and update the StructField each
// time, until the target field has been reached. This is very much not
// efficient, but it is easy to implement.
// Adding a jump table at the start to jump to the field directly would
// make this much faster, but that would also impact code size.
field := StructField{}
offset := uintptr(0)
for fieldNum := 0; fieldNum <= i; fieldNum++ {
// Read some flags of this field, like whether the field is an
// embedded field.
flagsByte := *(*uint8)(p)
p = unsafe.Pointer(uintptr(p) + 1)
// Read the type of this struct field.
var fieldType uintptr
fieldType, p = readVarint(p)
field.Type = Type(fieldType)
// Move Offset forward to align it to this field's alignment.
// Assume alignment is a power of two.
offset = align(offset, uintptr(field.Type.Align()))
field.Offset = offset
offset += field.Type.Size() // starting (unaligned) offset for next field
// Read the field name.
var nameNum uintptr
nameNum, p = readVarint(p)
field.Name = readStringSidetable(unsafe.Pointer(&structNamesSidetable), nameNum)
// The first bit in the flagsByte indicates whether this is an embedded
// field.
field.Anonymous = flagsByte&1 != 0
// The second bit indicates whether there is a tag.
if flagsByte&2 != 0 {
// There is a tag.
var tagNum uintptr
tagNum, p = readVarint(p)
field.Tag = readStringSidetable(unsafe.Pointer(&structNamesSidetable), tagNum)
} else {
// There is no tag.
field.Tag = ""
}
// The third bit indicates whether this field is exported.
if flagsByte&4 != 0 {
// This field is exported.
field.PkgPath = ""
} else {
// This field is unexported.
// TODO: list the real package path here. Storing it should not
// significantly impact binary size as there is only a limited
// number of packages in any program.
field.PkgPath = "<unimplemented>"
}
}
return field
}
// Bits returns the number of bits that this type uses. It is only valid for
@@ -179,10 +258,19 @@ func (t Type) Len() int {
panic("unimplemented: (reflect.Type).Len()")
}
// NumField returns the number of fields of a struct type. It panics for other
// type kinds.
func (t Type) NumField() int {
panic("unimplemented: (reflect.Type).NumField()")
if t.Kind() != Struct {
panic(&TypeError{"NumField"})
}
structIdentifier := t.stripPrefix()
n, _ := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&structTypesSidetable)) + uintptr(structIdentifier)))
return int(n)
}
// Size returns the size in bytes of a given type. It is similar to
// unsafe.Sizeof.
func (t Type) Size() uintptr {
switch t.Kind() {
case Bool, Int8, Uint8:
@@ -211,6 +299,15 @@ func (t Type) Size() uintptr {
return unsafe.Sizeof(uintptr(0))
case Slice:
return unsafe.Sizeof(SliceHeader{})
case Interface:
return unsafe.Sizeof(interfaceHeader{})
case Struct:
numField := t.NumField()
if numField == 0 {
return 0
}
lastField := t.Field(numField - 1)
return lastField.Offset + lastField.Type.Size()
default:
panic("unimplemented: size of type")
}
@@ -246,6 +343,18 @@ func (t Type) Align() int {
return int(unsafe.Alignof(uintptr(0)))
case Slice:
return int(unsafe.Alignof(SliceHeader{}))
case Interface:
return int(unsafe.Alignof(interfaceHeader{}))
case Struct:
numField := t.NumField()
alignment := 1
for i := 0; i < numField; i++ {
fieldAlignment := t.Field(i).Type.Align()
if fieldAlignment > alignment {
alignment = fieldAlignment
}
}
return alignment
default:
panic("unimplemented: alignment of type")
}
@@ -269,9 +378,19 @@ func (t Type) AssignableTo(u Type) bool {
return false
}
// A StructField describes a single field in a struct.
type StructField struct {
// Name indicates the field name.
Name string
Type Type
// PkgPath is the package path where the struct containing this field is
// declared for unexported fields, or the empty string for exported fields.
PkgPath string
Type Type
Tag string
Anonymous bool
Offset uintptr
}
// TypeError is the error that is used in a panic when invoking a method on a
+120 -45
View File
@@ -4,10 +4,28 @@ import (
"unsafe"
)
type valueFlags uint8
// Flags list some useful flags that contain some extra information not
// contained in an interface{} directly, like whether this value was exported at
// all (it is possible to read unexported fields using reflection, but it is not
// possible to modify them).
const (
valueFlagIndirect valueFlags = 1 << iota
valueFlagExported
)
type Value struct {
typecode Type
value unsafe.Pointer
indirect bool
flags valueFlags
}
// isIndirect returns whether the value pointer in this Value is always a
// pointer to the value. If it is false, it is only a pointer to the value if
// the value is bigger than a pointer.
func (v Value) isIndirect() bool {
return v.flags&valueFlagIndirect != 0
}
func Indirect(v Value) Value {
@@ -22,6 +40,7 @@ func ValueOf(i interface{}) Value {
return Value{
typecode: v.typecode,
value: v.value,
flags: valueFlagExported,
}
}
@@ -30,7 +49,7 @@ func (v Value) Interface() interface{} {
typecode: v.typecode,
value: v.value,
}
if v.indirect && v.Type().Size() <= unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() && v.Type().Size() <= unsafe.Sizeof(uintptr(0)) {
// Value was indirect but must be put back directly in the interface
// value.
var value uintptr
@@ -109,13 +128,13 @@ func (v Value) Addr() Value {
}
func (v Value) CanSet() bool {
return v.indirect
return v.flags&(valueFlagExported|valueFlagIndirect) == valueFlagExported|valueFlagIndirect
}
func (v Value) Bool() bool {
switch v.Kind() {
case Bool:
if v.indirect {
if v.isIndirect() {
return *((*bool)(v.value))
} else {
return uintptr(v.value) != 0
@@ -128,31 +147,31 @@ func (v Value) Bool() bool {
func (v Value) Int() int64 {
switch v.Kind() {
case Int:
if v.indirect || unsafe.Sizeof(int(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(int(0)) > unsafe.Sizeof(uintptr(0)) {
return int64(*(*int)(v.value))
} else {
return int64(int(uintptr(v.value)))
}
case Int8:
if v.indirect {
if v.isIndirect() {
return int64(*(*int8)(v.value))
} else {
return int64(int8(uintptr(v.value)))
}
case Int16:
if v.indirect {
if v.isIndirect() {
return int64(*(*int16)(v.value))
} else {
return int64(int16(uintptr(v.value)))
}
case Int32:
if v.indirect || unsafe.Sizeof(int32(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(int32(0)) > unsafe.Sizeof(uintptr(0)) {
return int64(*(*int32)(v.value))
} else {
return int64(int32(uintptr(v.value)))
}
case Int64:
if v.indirect || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) {
return int64(*(*int64)(v.value))
} else {
return int64(int64(uintptr(v.value)))
@@ -165,37 +184,37 @@ func (v Value) Int() int64 {
func (v Value) Uint() uint64 {
switch v.Kind() {
case Uintptr:
if v.indirect {
if v.isIndirect() {
return uint64(*(*uintptr)(v.value))
} else {
return uint64(uintptr(v.value))
}
case Uint8:
if v.indirect {
if v.isIndirect() {
return uint64(*(*uint8)(v.value))
} else {
return uint64(uintptr(v.value))
}
case Uint16:
if v.indirect {
if v.isIndirect() {
return uint64(*(*uint16)(v.value))
} else {
return uint64(uintptr(v.value))
}
case Uint:
if v.indirect || unsafe.Sizeof(uint(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(uint(0)) > unsafe.Sizeof(uintptr(0)) {
return uint64(*(*uint)(v.value))
} else {
return uint64(uintptr(v.value))
}
case Uint32:
if v.indirect || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) {
return uint64(*(*uint32)(v.value))
} else {
return uint64(uintptr(v.value))
}
case Uint64:
if v.indirect || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) {
return uint64(*(*uint64)(v.value))
} else {
return uint64(uintptr(v.value))
@@ -208,7 +227,7 @@ func (v Value) Uint() uint64 {
func (v Value) Float() float64 {
switch v.Kind() {
case Float32:
if v.indirect || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(float32(0)) > unsafe.Sizeof(uintptr(0)) {
// The float is stored as an external value on systems with 16-bit
// pointers.
return float64(*(*float32)(v.value))
@@ -218,7 +237,7 @@ func (v Value) Float() float64 {
return float64(*(*float32)(unsafe.Pointer(&v.value)))
}
case Float64:
if v.indirect || unsafe.Sizeof(float64(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(float64(0)) > unsafe.Sizeof(uintptr(0)) {
// For systems with 16-bit and 32-bit pointers.
return *(*float64)(v.value)
} else {
@@ -234,7 +253,7 @@ func (v Value) Float() float64 {
func (v Value) Complex() complex128 {
switch v.Kind() {
case Complex64:
if v.indirect || unsafe.Sizeof(complex64(0)) > unsafe.Sizeof(uintptr(0)) {
if v.isIndirect() || unsafe.Sizeof(complex64(0)) > unsafe.Sizeof(uintptr(0)) {
// The complex number is stored as an external value on systems with
// 16-bit and 32-bit pointers.
return complex128(*(*complex64)(v.value))
@@ -295,15 +314,17 @@ func (v Value) Cap() int {
}
}
// NumField returns the number of fields of this struct. It panics for other
// value types.
func (v Value) NumField() int {
panic("unimplemented: (reflect.Value).NumField()")
return v.Type().NumField()
}
func (v Value) Elem() Value {
switch v.Kind() {
case Ptr:
ptr := v.value
if v.indirect {
if v.isIndirect() {
ptr = *(*unsafe.Pointer)(ptr)
}
if ptr == nil {
@@ -312,15 +333,77 @@ func (v Value) Elem() Value {
return Value{
typecode: v.Type().Elem(),
value: ptr,
indirect: true,
flags: v.flags | valueFlagIndirect,
}
default: // not implemented: Interface
panic(&ValueError{"Elem"})
}
}
// Field returns the value of the i'th field of this struct.
func (v Value) Field(i int) Value {
panic("unimplemented: (reflect.Value).Field()")
structField := v.Type().Field(i)
flags := v.flags
if structField.PkgPath != "" {
// The fact that PkgPath is present means that this field is not
// exported.
flags &^= valueFlagExported
}
size := v.Type().Size()
fieldSize := structField.Type.Size()
if v.isIndirect() || fieldSize > unsafe.Sizeof(uintptr(0)) {
// v.value was already a pointer to the value and it should stay that
// way.
return Value{
flags: flags,
typecode: structField.Type,
value: unsafe.Pointer(uintptr(v.value) + structField.Offset),
}
}
// The fieldSize is smaller than uintptr, which means that the value will
// have to be stored directly in the interface value.
if fieldSize == 0 {
// The struct field is zero sized.
// This is a rare situation, but because it's undefined behavior
// to shift the size of the value (zeroing the value), handle this
// situation explicitly.
return Value{
flags: flags,
typecode: structField.Type,
value: unsafe.Pointer(uintptr(0)),
}
}
if size > unsafe.Sizeof(uintptr(0)) {
// The value was not stored in the interface before but will be
// afterwards, so load the value (from the correct offset) and return
// it.
ptr := unsafe.Pointer(uintptr(v.value) + structField.Offset)
loadedValue := uintptr(0)
shift := uintptr(0)
for i := uintptr(0); i < fieldSize; i++ {
loadedValue |= uintptr(*(*byte)(ptr)) << shift
shift += 8
ptr = unsafe.Pointer(uintptr(ptr) + 1)
}
return Value{
flags: 0,
typecode: structField.Type,
value: unsafe.Pointer(loadedValue),
}
}
// The value was already stored directly in the interface and it still
// is. Cut out the part of the value that we need.
mask := ^uintptr(0) >> ((unsafe.Sizeof(uintptr(0)) - fieldSize) * 8)
return Value{
flags: flags,
typecode: structField.Type,
value: unsafe.Pointer((uintptr(v.value) >> (structField.Offset * 8)) & mask),
}
}
func (v Value) Index(i int) Value {
@@ -333,7 +416,7 @@ func (v Value) Index(i int) Value {
}
elem := Value{
typecode: v.Type().Elem(),
indirect: true,
flags: v.flags | valueFlagIndirect,
}
addr := uintptr(slice.Data) + elem.Type().Size()*uintptr(i) // pointer to new value
elem.value = unsafe.Pointer(addr)
@@ -385,15 +468,13 @@ func (it *MapIter) Next() bool {
}
func (v Value) Set(x Value) {
if !v.indirect {
panic("reflect: value is not addressable")
}
v.checkAddressable()
if !v.Type().AssignableTo(x.Type()) {
panic("reflect: cannot set")
}
size := v.Type().Size()
xptr := x.value
if size <= unsafe.Sizeof(uintptr(0)) && !x.indirect {
if size <= unsafe.Sizeof(uintptr(0)) && !x.isIndirect() {
value := x.value
xptr = unsafe.Pointer(&value)
}
@@ -401,9 +482,7 @@ func (v Value) Set(x Value) {
}
func (v Value) SetBool(x bool) {
if !v.indirect {
panic("reflect: value is not addressable")
}
v.checkAddressable()
switch v.Kind() {
case Bool:
*(*bool)(v.value) = x
@@ -413,9 +492,7 @@ func (v Value) SetBool(x bool) {
}
func (v Value) SetInt(x int64) {
if !v.indirect {
panic("reflect: value is not addressable")
}
v.checkAddressable()
switch v.Kind() {
case Int:
*(*int)(v.value) = int(x)
@@ -433,9 +510,7 @@ func (v Value) SetInt(x int64) {
}
func (v Value) SetUint(x uint64) {
if !v.indirect {
panic("reflect: value is not addressable")
}
v.checkAddressable()
switch v.Kind() {
case Uint:
*(*uint)(v.value) = uint(x)
@@ -455,9 +530,7 @@ func (v Value) SetUint(x uint64) {
}
func (v Value) SetFloat(x float64) {
if !v.indirect {
panic("reflect: value is not addressable")
}
v.checkAddressable()
switch v.Kind() {
case Float32:
*(*float32)(v.value) = float32(x)
@@ -469,9 +542,7 @@ func (v Value) SetFloat(x float64) {
}
func (v Value) SetComplex(x complex128) {
if !v.indirect {
panic("reflect: value is not addressable")
}
v.checkAddressable()
switch v.Kind() {
case Complex64:
*(*complex64)(v.value) = complex64(x)
@@ -483,9 +554,7 @@ func (v Value) SetComplex(x complex128) {
}
func (v Value) SetString(x string) {
if !v.indirect {
panic("reflect: value is not addressable")
}
v.checkAddressable()
switch v.Kind() {
case String:
*(*string)(v.value) = x
@@ -494,6 +563,12 @@ func (v Value) SetString(x string) {
}
}
func (v Value) checkAddressable() {
if !v.isIndirect() {
panic("reflect: value is not addressable")
}
}
func MakeSlice(typ Type, len, cap int) Value {
panic("unimplemented: reflect.MakeSlice()")
}
+11 -1
View File
@@ -50,10 +50,20 @@ type typecodeID struct {
// * named type: the underlying type
// * interface: null
// * chan/pointer/slice: the element type
// * array/func/map/struct: TODO
// * struct: GEP of structField array (to typecode field)
// * array/func/map: TODO
references *typecodeID
}
// structField is used by the compiler to pass information to the interface
// lowering pass. It is not used in the final binary.
type structField struct {
typecode *typecodeID // type of this struct field
name *uint8 // pointer to char array
tag *uint8 // pointer to char array, or nil
embedded bool
}
// Pseudo type used before interface lowering. By using a struct instead of a
// function call, this is simpler to reason about during init interpretation
// than a function call. Also, by keeping the method set around it is easier to