From dfef16813927fa41b40b50e0722bc666f98f71d6 Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sun, 20 Jan 2019 17:30:27 +0100 Subject: [PATCH] reflect: add limited support for all type kinds This commit makes sure all Go types can be encoded in the interface type code, so that Type.Kind() always returns a proper type kind for any non-nil interface. --- compiler/interface.go | 84 ++++++++++++++++++++++--------- compiler/reflect.go | 112 ++++++++++++++++++++++++++++++++++-------- src/reflect/type.go | 66 ++++++++++++++++++------- src/reflect/value.go | 35 +++++++++++-- testdata/reflect.go | 81 +++++++++++++++++++++++++++++- testdata/reflect.txt | 65 ++++++++++++++++++++++++ 6 files changed, 377 insertions(+), 66 deletions(-) diff --git a/compiler/interface.go b/compiler/interface.go index a849a00e..6aec6298 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -8,6 +8,8 @@ package compiler import ( "go/token" "go/types" + "strconv" + "strings" "github.com/tinygo-org/tinygo/ir" "golang.org/x/tools/go/ssa" @@ -92,55 +94,91 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value { // interface lowering pass to assign type codes as expected by the reflect // package. See getTypeCodeNum. func getTypeCodeName(t types.Type) string { + name := "" + if named, ok := t.(*types.Named); ok { + name = "~" + named.String() + ":" + t = t.Underlying() + } switch t := t.(type) { + case *types.Array: + return "array:" + name + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem()) case *types.Basic: - var name string + var kind string switch t.Kind() { case types.Bool: - name = "bool" + kind = "bool" case types.Int: - name = "int" + kind = "int" case types.Int8: - name = "int8" + kind = "int8" case types.Int16: - name = "int16" + kind = "int16" case types.Int32: - name = "int32" + kind = "int32" case types.Int64: - name = "int64" + kind = "int64" case types.Uint: - name = "uint" + kind = "uint" case types.Uint8: - name = "uint8" + kind = "uint8" case types.Uint16: - name = "uint16" + kind = "uint16" case types.Uint32: - name = "uint32" + kind = "uint32" case types.Uint64: - name = "uint64" + kind = "uint64" case types.Uintptr: - name = "uintptr" + kind = "uintptr" case types.Float32: - name = "float32" + kind = "float32" case types.Float64: - name = "float64" + kind = "float64" case types.Complex64: - name = "complex64" + kind = "complex64" case types.Complex128: - name = "complex128" + kind = "complex128" case types.String: - name = "string" + kind = "string" case types.UnsafePointer: - name = "unsafeptr" + kind = "unsafeptr" default: panic("unknown basic type: " + t.Name()) } - return "basic:" + name + return "basic:" + name + kind + case *types.Chan: + return "chan:" + name + getTypeCodeName(t.Elem()) + case *types.Interface: + methods := make([]string, t.NumMethods()) + for i := 0; i < t.NumMethods(); i++ { + methods[i] = getTypeCodeName(t.Method(i).Type()) + } + return "interface:" + name + "{" + strings.Join(methods, ",") + "}" + case *types.Map: + keyType := getTypeCodeName(t.Key()) + elemType := getTypeCodeName(t.Elem()) + return "map:" + name + "{" + keyType + "," + elemType + "}" + case *types.Pointer: + return "pointer:" + name + getTypeCodeName(t.Elem()) + case *types.Signature: + params := make([]string, t.Params().Len()) + for i := 0; i < t.Params().Len(); i++ { + params[i] = getTypeCodeName(t.Params().At(i).Type()) + } + results := make([]string, t.Results().Len()) + for i := 0; i < t.Results().Len(); i++ { + results[i] = getTypeCodeName(t.Results().At(i).Type()) + } + return "func:" + name + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}" case *types.Slice: - return "slice:" + getTypeCodeName(t.Elem()) + return "slice:" + name + getTypeCodeName(t.Elem()) + case *types.Struct: + elems := make([]string, t.NumFields()) + for i := 0; i < t.NumFields(); i++ { + elems[i] = getTypeCodeName(t.Field(i).Type()) + } + return "struct:" + name + "{" + strings.Join(elems, ",") + "}" default: - // Unknown type, fall back to the .String() method for identification. - return "other:" + t.String() + panic("unknown type: " + t.String()) } } diff --git a/compiler/reflect.go b/compiler/reflect.go index cd1ad9ce..f449a297 100644 --- a/compiler/reflect.go +++ b/compiler/reflect.go @@ -39,18 +39,12 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) { // Assign typecodes the way the reflect package expects. fallbackIndex := 1 + namedTypes := make(map[string]int) for _, t := range typeSlice { if t.name[:5] != "type:" { panic("expected type name to start with 'type:'") } - num := c.getTypeCodeNum(t.name[5:]) - if num == nil { - // Fallback/unsupported types have a typecode with the lowest bits - // set to 11. - t.num = uint64(fallbackIndex<<2 | 3) - fallbackIndex++ - continue - } + num := c.getTypeCodeNum(t.name[5:], &fallbackIndex, namedTypes) 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. @@ -65,20 +59,98 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) { // 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(name string) *big.Int { - if strings.HasPrefix(name, "basic:") { - // Basic types have a typecode with the lowest bits set to 00. - num, ok := basicTypes[name[len("basic:"):]] +func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[string]int) *big.Int { + // Note: see src/reflect/type.go for bit allocations. + // A type can be named or unnamed. Example of both: + // basic:~foo:uint64 + // basic:uint64 + // Extract the class (basic, slice, pointer, etc.), the name, and the + // contents of this type ID string. Allocate bits based on that, as + // src/runtime/types.go expects. + class := id[:strings.IndexByte(id, ':')] + value := id[len(class)+1:] + name := "" + if value[0] == '~' { + name = value[1:strings.IndexByte(value, ':')] + value = value[len(name)+2:] + } + if class == "basic" { + // Basic types follow the following bit pattern: + // ...xxxxx0 + // where xxxxx is allocated for the 18 possible basic types and all the + // upper bits are used to indicate the named type. + num, ok := basicTypes[value] if !ok { - panic("invalid basic type: " + name) + panic("invalid basic type: " + id) } - return big.NewInt(num<<2 | 0) - } else if strings.HasPrefix(name, "slice:") { - // Slices have a typecode with the lowest bits set to 01. - num := c.getTypeCodeNum(name[len("slice:"):]) - num.Lsh(num, 2).Or(num, big.NewInt(1)) - return num + if name != "" { + // This type is named, set the upper bits to the name ID. + num |= int64(getNamedTypeNum(namedTypes, name)) << 5 + } + return big.NewInt(num << 1) } else { - return nil + // Complex types use the following bit pattern: + // ...nxxx1 + // where xxx indicates the complex type (any non-basic type). The upper + // bits contain whatever the type contains. Types that wrap a single + // other type (channel, interface, pointer, slice) just contain the bits + // of the wrapped type. Other types (like struct) have a different + // method of encoding the contents of the type. + var num *big.Int + var classNumber int64 + switch class { + case "chan": + num = c.getTypeCodeNum(value, fallbackIndex, namedTypes) + classNumber = 0 + case "interface": + num = big.NewInt(int64(*fallbackIndex)) + *fallbackIndex++ + classNumber = 1 + case "pointer": + num = c.getTypeCodeNum(value, fallbackIndex, namedTypes) + classNumber = 2 + case "slice": + num = c.getTypeCodeNum(value, fallbackIndex, namedTypes) + classNumber = 3 + case "array": + num = big.NewInt(int64(*fallbackIndex)) + *fallbackIndex++ + classNumber = 4 + case "func": + num = big.NewInt(int64(*fallbackIndex)) + *fallbackIndex++ + classNumber = 5 + case "map": + num = big.NewInt(int64(*fallbackIndex)) + *fallbackIndex++ + classNumber = 6 + case "struct": + num = big.NewInt(int64(*fallbackIndex)) + *fallbackIndex++ + classNumber = 7 + default: + panic("unknown type kind: " + id) + } + if name == "" { + num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1)) + } else { + // TODO: store num in a sidetable + num = big.NewInt(int64(getNamedTypeNum(namedTypes, name))<<1 | 1) + num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1)) + } + return num + } +} + +// getNamedTypeNum returns an appropriate (unique) number for the given named +// type. If the name already has a number that number is returned, else a new +// number is returned. The number is always non-zero. +func getNamedTypeNum(namedTypes map[string]int, name string) int { + if num, ok := namedTypes[name]; ok { + return num + } else { + num = len(namedTypes) + 1 + namedTypes[name] = num + return num } } diff --git a/src/reflect/type.go b/src/reflect/type.go index fc27a446..d21151df 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -4,10 +4,26 @@ import ( "unsafe" ) -// A Kind is the number that the compiler uses for this type. +// The compiler uses a compact encoding to store type information. Unlike the +// main Go compiler, most of the types are stored directly in the type code. // -// Not used directly. These types are all replaced with the number the compiler -// uses internally for the type. +// Type code bit allocation: +// xxxxx0: basic types, where xxxxx is the basic type number (never 0). +// The higher bits indicate the named type, if any. +// nxxx1: complex types, where n indicates whether this is a named type (named +// if set) and xxx contains the type kind number: +// 0 (0001): Chan +// 1 (0011): Interface +// 2 (0101): Ptr +// 3 (0111): Slice +// 4 (1001): Array +// 5 (1011): Func +// 6 (1101): Map +// 7 (1111): Struct +// The higher bits are either the contents of the type depending on the +// type (if n is clear) or indicate the number of the named type (if n +// is set). + type Kind uintptr // Copied from reflect/type.go @@ -32,13 +48,13 @@ const ( Complex128 String UnsafePointer - Array Chan - Func Interface - Map Ptr Slice + Array + Func + Map Struct ) @@ -80,8 +96,22 @@ func (k Kind) String() string { return "string" case UnsafePointer: return "unsafe.Pointer" + case Chan: + return "chan" + case Interface: + return "interface" + case Ptr: + return "ptr" case Slice: return "slice" + case Array: + return "array" + case Func: + return "func" + case Map: + return "map" + case Struct: + return "struct" default: return "invalid" } @@ -89,7 +119,7 @@ func (k Kind) String() string { // basicType returns a new Type for this kind if Kind is a basic type. func (k Kind) basicType() Type { - return Type(k << 2 | 0) + return Type(k << 1) } // The typecode as used in an interface{}. @@ -104,22 +134,22 @@ func (t Type) String() string { } func (t Type) Kind() Kind { - if t % 4 == 0 { - // Basic type - return Kind(t >> 2) - } else if t % 4 == 1 { - // Slice - return Slice + if t % 2 == 0 { + // basic type + return Kind((t >> 1) % 32) } else { - return Invalid // TODO + return Kind(t >> 1) % 8 + 19 } } func (t Type) Elem() Type { switch t.Kind() { - case Slice: - return t >> 2 - default: // not implemented: Array, Chan, Map, Ptr + case Chan, Ptr, Slice: + if (t >> 4) % 2 != 0 { + panic("unimplemented: (reflect.Type).Elem() for named types") + } + return t >> 5 + default: // not implemented: Array, Map panic("unimplemented: (reflect.Type).Elem()") } } @@ -164,7 +194,7 @@ func (t Type) Size() uintptr { return 16 case String: return unsafe.Sizeof(StringHeader{}) - case UnsafePointer: + case UnsafePointer, Chan, Map, Ptr: return unsafe.Sizeof(uintptr(0)) case Slice: return unsafe.Sizeof(SliceHeader{}) diff --git a/src/reflect/value.go b/src/reflect/value.go index d45f0b5c..8d8d59c9 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -31,16 +31,38 @@ func (v Value) Kind() Kind { } func (v Value) IsNil() bool { - panic("unimplemented: (reflect.Value).IsNil()") + switch v.Kind() { + case Chan, Map, Ptr: + return v.value == nil + case Func: + if v.value == nil { + return true + } + fn := (*funcHeader)(v.value) + return fn.Code == nil + case Slice: + if v.value == nil { + return true + } + slice := (*SliceHeader)(v.value) + return slice.Data == 0 + case Interface: + panic("unimplemented: (reflect.Value).IsNil()") + default: + panic(&ValueError{"IsNil"}) + } } func (v Value) Pointer() uintptr { switch v.Kind() { - case UnsafePointer: + case Chan, Map, Ptr, UnsafePointer: return uintptr(v.value) - case Chan, Func, Map, Ptr, Slice: + case Slice: + slice := (*SliceHeader)(v.value) + return slice.Data + case Func: panic("unimplemented: (reflect.Value).Pointer()") - default: + default: // not implemented: Func panic(&ValueError{"Pointer"}) } } @@ -322,6 +344,11 @@ func MakeSlice(typ Type, len, cap int) Value { panic("unimplemented: reflect.MakeSlice()") } +type funcHeader struct { + Context unsafe.Pointer + Code unsafe.Pointer +} + type SliceHeader struct { Data uintptr Len uintptr diff --git a/testdata/reflect.go b/testdata/reflect.go index d6c18f2f..e8ca8ef3 100644 --- a/testdata/reflect.go +++ b/testdata/reflect.go @@ -5,16 +5,29 @@ import ( "unsafe" ) -type myint int +type ( + myint int + myslice []byte + myslice2 []myint +) func main() { println("matching types") println(reflect.TypeOf(int(3)) == reflect.TypeOf(int(5))) println(reflect.TypeOf(int(3)) == reflect.TypeOf(uint(5))) println(reflect.TypeOf(myint(3)) == reflect.TypeOf(int(5))) + println(reflect.TypeOf(myslice{}) == reflect.TypeOf([]byte{})) + println(reflect.TypeOf(myslice2{}) == reflect.TypeOf([]myint{})) + println(reflect.TypeOf(myslice2{}) == reflect.TypeOf([]int{})) println("\nvalues of interfaces") + var zeroSlice []byte + var zeroFunc func() + var zeroMap map[string]int + var zeroChan chan int + n := 42 for _, v := range []interface{}{ + // basic types true, false, int(2000), @@ -37,15 +50,57 @@ func main() { float64(3.14), complex64(1.2 + 0.3i), complex128(1.3 + 0.4i), + myint(32), "foo", unsafe.Pointer(new(int)), + // channels + zeroChan, + // pointers + new(int), + new(error), + &n, + // slices []byte{1, 2, 3}, make([]uint8, 2, 5), []rune{3, 5}, []string{"xyz", "Z"}, + zeroSlice, + []byte{}, + // array + [4]int{1, 2, 3, 4}, + // functions + zeroFunc, + emptyFunc, + // maps + zeroMap, + map[string]int{}, + // structs + struct{}{}, + struct{ error }{}, } { showValue(v, "") } + + // test sizes + println("\nsizes:") + println("int8", int(reflect.TypeOf(int8(0)).Size())) + println("int16", int(reflect.TypeOf(int16(0)).Size())) + println("int32", int(reflect.TypeOf(int32(0)).Size())) + println("int64", int(reflect.TypeOf(int64(0)).Size())) + println("uint8", int(reflect.TypeOf(uint8(0)).Size())) + println("uint16", int(reflect.TypeOf(uint16(0)).Size())) + println("uint32", int(reflect.TypeOf(uint32(0)).Size())) + println("uint64", int(reflect.TypeOf(uint64(0)).Size())) + println("float32", int(reflect.TypeOf(float32(0)).Size())) + println("float64", int(reflect.TypeOf(float64(0)).Size())) + println("complex64", int(reflect.TypeOf(complex64(0)).Size())) + println("complex128", int(reflect.TypeOf(complex128(0)).Size())) + assertSize(reflect.TypeOf(uintptr(0)).Size() == unsafe.Sizeof(uintptr(0)), "uintptr") + assertSize(reflect.TypeOf("").Size() == unsafe.Sizeof(""), "string") + assertSize(reflect.TypeOf(new(int)).Size() == unsafe.Sizeof(new(int)), "*int") +} + +func emptyFunc() { } func showValue(v interface{}, indent string) { @@ -79,13 +134,37 @@ func showValue(v interface{}, indent string) { } case reflect.UnsafePointer: println(indent+" pointer:", rv.Pointer() != 0) + case reflect.Array: + println(indent + " array") + case reflect.Chan: + println(indent+" chan:", rt.Elem().Kind().String()) + println(indent+" nil:", rv.IsNil()) + case reflect.Func: + println(indent + " func") + println(indent+" nil:", rv.IsNil()) + case reflect.Map: + println(indent + " map") + println(indent+" nil:", rv.IsNil()) + case reflect.Ptr: + println(indent+" pointer:", rv.Pointer() != 0, rt.Elem().Kind().String()) + println(indent+" nil:", rv.IsNil()) case reflect.Slice: println(indent+" slice:", rt.Elem().Kind().String(), rv.Len(), rv.Cap()) + println(indent+" pointer:", rv.Pointer() != 0) + println(indent+" nil:", rv.IsNil()) for i := 0; i < rv.Len(); i++ { println(indent+" indexing:", i) showValue(rv.Index(i).Interface(), indent+" ") } + case reflect.Struct: + println(indent + " struct") default: println(indent + " unknown type kind!") } } + +func assertSize(ok bool, typ string) { + if !ok { + panic("size mismatch for type " + typ) + } +} diff --git a/testdata/reflect.txt b/testdata/reflect.txt index 2badc139..fdb46034 100644 --- a/testdata/reflect.txt +++ b/testdata/reflect.txt @@ -2,6 +2,9 @@ matching types true false false +false +false +false values of interfaces reflect type: bool @@ -48,6 +51,8 @@ reflect type: complex64 complex: (+1.200000e+000+3.000000e-001i) reflect type: complex128 complex: (+1.300000e+000+4.000000e-001i) +reflect type: int + int: 32 reflect type: string string: foo 3 reflect type: uint8 @@ -58,8 +63,22 @@ reflect type: string uint: 111 reflect type: unsafe.Pointer pointer: true +reflect type: chan + chan: int + nil: true +reflect type: ptr + pointer: true int + nil: false +reflect type: ptr + pointer: true interface + nil: false +reflect type: ptr + pointer: true int + nil: false reflect type: slice slice: uint8 3 3 + pointer: true + nil: false indexing: 0 reflect type: uint8 uint: 1 @@ -71,6 +90,8 @@ reflect type: slice uint: 3 reflect type: slice slice: uint8 2 5 + pointer: true + nil: false indexing: 0 reflect type: uint8 uint: 0 @@ -79,6 +100,8 @@ reflect type: slice uint: 0 reflect type: slice slice: int32 2 2 + pointer: true + nil: false indexing: 0 reflect type: int32 int: 3 @@ -87,6 +110,8 @@ reflect type: slice int: 5 reflect type: slice slice: string 2 2 + pointer: true + nil: false indexing: 0 reflect type: string string: xyz 3 @@ -101,3 +126,43 @@ reflect type: slice string: Z 1 reflect type: uint8 uint: 90 +reflect type: slice + slice: uint8 0 0 + pointer: false + nil: true +reflect type: slice + slice: uint8 0 0 + pointer: true + nil: false +reflect type: array + array +reflect type: func + func + nil: true +reflect type: func + func + nil: false +reflect type: map + map + nil: true +reflect type: map + map + nil: false +reflect type: struct + struct +reflect type: struct + struct + +sizes: +int8 1 +int16 2 +int32 4 +int64 8 +uint8 1 +uint16 2 +uint32 4 +uint64 8 +float32 4 +float64 8 +complex64 8 +complex128 16