Skip to content

Commit dfef168

Browse files
committed
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.
1 parent 101f2e5 commit dfef168

File tree

6 files changed

+376
-65
lines changed

6 files changed

+376
-65
lines changed

compiler/interface.go

Lines changed: 61 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ package compiler
88
import (
99
"go/token"
1010
"go/types"
11+
"strconv"
12+
"strings"
1113

1214
"github.com/tinygo-org/tinygo/ir"
1315
"golang.org/x/tools/go/ssa"
@@ -92,55 +94,91 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
9294
// interface lowering pass to assign type codes as expected by the reflect
9395
// package. See getTypeCodeNum.
9496
func getTypeCodeName(t types.Type) string {
97+
name := ""
98+
if named, ok := t.(*types.Named); ok {
99+
name = "~" + named.String() + ":"
100+
t = t.Underlying()
101+
}
95102
switch t := t.(type) {
103+
case *types.Array:
104+
return "array:" + name + strconv.FormatInt(t.Len(), 10) + ":" + getTypeCodeName(t.Elem())
96105
case *types.Basic:
97-
var name string
106+
var kind string
98107
switch t.Kind() {
99108
case types.Bool:
100-
name = "bool"
109+
kind = "bool"
101110
case types.Int:
102-
name = "int"
111+
kind = "int"
103112
case types.Int8:
104-
name = "int8"
113+
kind = "int8"
105114
case types.Int16:
106-
name = "int16"
115+
kind = "int16"
107116
case types.Int32:
108-
name = "int32"
117+
kind = "int32"
109118
case types.Int64:
110-
name = "int64"
119+
kind = "int64"
111120
case types.Uint:
112-
name = "uint"
121+
kind = "uint"
113122
case types.Uint8:
114-
name = "uint8"
123+
kind = "uint8"
115124
case types.Uint16:
116-
name = "uint16"
125+
kind = "uint16"
117126
case types.Uint32:
118-
name = "uint32"
127+
kind = "uint32"
119128
case types.Uint64:
120-
name = "uint64"
129+
kind = "uint64"
121130
case types.Uintptr:
122-
name = "uintptr"
131+
kind = "uintptr"
123132
case types.Float32:
124-
name = "float32"
133+
kind = "float32"
125134
case types.Float64:
126-
name = "float64"
135+
kind = "float64"
127136
case types.Complex64:
128-
name = "complex64"
137+
kind = "complex64"
129138
case types.Complex128:
130-
name = "complex128"
139+
kind = "complex128"
131140
case types.String:
132-
name = "string"
141+
kind = "string"
133142
case types.UnsafePointer:
134-
name = "unsafeptr"
143+
kind = "unsafeptr"
135144
default:
136145
panic("unknown basic type: " + t.Name())
137146
}
138-
return "basic:" + name
147+
return "basic:" + name + kind
148+
case *types.Chan:
149+
return "chan:" + name + getTypeCodeName(t.Elem())
150+
case *types.Interface:
151+
methods := make([]string, t.NumMethods())
152+
for i := 0; i < t.NumMethods(); i++ {
153+
methods[i] = getTypeCodeName(t.Method(i).Type())
154+
}
155+
return "interface:" + name + "{" + strings.Join(methods, ",") + "}"
156+
case *types.Map:
157+
keyType := getTypeCodeName(t.Key())
158+
elemType := getTypeCodeName(t.Elem())
159+
return "map:" + name + "{" + keyType + "," + elemType + "}"
160+
case *types.Pointer:
161+
return "pointer:" + name + getTypeCodeName(t.Elem())
162+
case *types.Signature:
163+
params := make([]string, t.Params().Len())
164+
for i := 0; i < t.Params().Len(); i++ {
165+
params[i] = getTypeCodeName(t.Params().At(i).Type())
166+
}
167+
results := make([]string, t.Results().Len())
168+
for i := 0; i < t.Results().Len(); i++ {
169+
results[i] = getTypeCodeName(t.Results().At(i).Type())
170+
}
171+
return "func:" + name + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}"
139172
case *types.Slice:
140-
return "slice:" + getTypeCodeName(t.Elem())
173+
return "slice:" + name + getTypeCodeName(t.Elem())
174+
case *types.Struct:
175+
elems := make([]string, t.NumFields())
176+
for i := 0; i < t.NumFields(); i++ {
177+
elems[i] = getTypeCodeName(t.Field(i).Type())
178+
}
179+
return "struct:" + name + "{" + strings.Join(elems, ",") + "}"
141180
default:
142-
// Unknown type, fall back to the .String() method for identification.
143-
return "other:" + t.String()
181+
panic("unknown type: " + t.String())
144182
}
145183
}
146184

compiler/reflect.go

Lines changed: 91 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,12 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
3939

4040
// Assign typecodes the way the reflect package expects.
4141
fallbackIndex := 1
42+
namedTypes := make(map[string]int)
4243
for _, t := range typeSlice {
4344
if t.name[:5] != "type:" {
4445
panic("expected type name to start with 'type:'")
4546
}
46-
num := c.getTypeCodeNum(t.name[5:])
47-
if num == nil {
48-
// Fallback/unsupported types have a typecode with the lowest bits
49-
// set to 11.
50-
t.num = uint64(fallbackIndex<<2 | 3)
51-
fallbackIndex++
52-
continue
53-
}
47+
num := c.getTypeCodeNum(t.name[5:], &fallbackIndex, namedTypes)
5448
if num.BitLen() > c.uintptrType.IntTypeWidth() || !num.IsUint64() {
5549
// TODO: support this in some way, using a side table for example.
5650
// That's less efficient but better than not working at all.
@@ -65,20 +59,98 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) {
6559
// getTypeCodeNum returns the typecode for a given type as expected by the
6660
// reflect package. Also see getTypeCodeName, which serializes types to a string
6761
// based on a types.Type value for this function.
68-
func (c *Compiler) getTypeCodeNum(name string) *big.Int {
69-
if strings.HasPrefix(name, "basic:") {
70-
// Basic types have a typecode with the lowest bits set to 00.
71-
num, ok := basicTypes[name[len("basic:"):]]
62+
func (c *Compiler) getTypeCodeNum(id string, fallbackIndex *int, namedTypes map[string]int) *big.Int {
63+
// Note: see src/reflect/type.go for bit allocations.
64+
// A type can be named or unnamed. Example of both:
65+
// basic:~foo:uint64
66+
// basic:uint64
67+
// Extract the class (basic, slice, pointer, etc.), the name, and the
68+
// contents of this type ID string. Allocate bits based on that, as
69+
// src/runtime/types.go expects.
70+
class := id[:strings.IndexByte(id, ':')]
71+
value := id[len(class)+1:]
72+
name := ""
73+
if value[0] == '~' {
74+
name = value[1:strings.IndexByte(value, ':')]
75+
value = value[len(name)+2:]
76+
}
77+
if class == "basic" {
78+
// Basic types follow the following bit pattern:
79+
// ...xxxxx0
80+
// where xxxxx is allocated for the 18 possible basic types and all the
81+
// upper bits are used to indicate the named type.
82+
num, ok := basicTypes[value]
7283
if !ok {
73-
panic("invalid basic type: " + name)
84+
panic("invalid basic type: " + id)
85+
}
86+
if name != "" {
87+
// This type is named, set the upper bits to the name ID.
88+
num |= int64(getNamedTypeNum(namedTypes, name)) << 5
89+
}
90+
return big.NewInt(num << 1)
91+
} else {
92+
// Complex types use the following bit pattern:
93+
// ...nxxx1
94+
// where xxx indicates the complex type (any non-basic type). The upper
95+
// bits contain whatever the type contains. Types that wrap a single
96+
// other type (channel, interface, pointer, slice) just contain the bits
97+
// of the wrapped type. Other types (like struct) have a different
98+
// method of encoding the contents of the type.
99+
var num *big.Int
100+
var classNumber int64
101+
switch class {
102+
case "chan":
103+
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
104+
classNumber = 0
105+
case "interface":
106+
num = big.NewInt(int64(*fallbackIndex))
107+
*fallbackIndex++
108+
classNumber = 1
109+
case "pointer":
110+
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
111+
classNumber = 2
112+
case "slice":
113+
num = c.getTypeCodeNum(value, fallbackIndex, namedTypes)
114+
classNumber = 3
115+
case "array":
116+
num = big.NewInt(int64(*fallbackIndex))
117+
*fallbackIndex++
118+
classNumber = 4
119+
case "func":
120+
num = big.NewInt(int64(*fallbackIndex))
121+
*fallbackIndex++
122+
classNumber = 5
123+
case "map":
124+
num = big.NewInt(int64(*fallbackIndex))
125+
*fallbackIndex++
126+
classNumber = 6
127+
case "struct":
128+
num = big.NewInt(int64(*fallbackIndex))
129+
*fallbackIndex++
130+
classNumber = 7
131+
default:
132+
panic("unknown type kind: " + id)
133+
}
134+
if name == "" {
135+
num.Lsh(num, 5).Or(num, big.NewInt((classNumber<<1)+1))
136+
} else {
137+
// TODO: store num in a sidetable
138+
num = big.NewInt(int64(getNamedTypeNum(namedTypes, name))<<1 | 1)
139+
num.Lsh(num, 4).Or(num, big.NewInt((classNumber<<1)+1))
74140
}
75-
return big.NewInt(num<<2 | 0)
76-
} else if strings.HasPrefix(name, "slice:") {
77-
// Slices have a typecode with the lowest bits set to 01.
78-
num := c.getTypeCodeNum(name[len("slice:"):])
79-
num.Lsh(num, 2).Or(num, big.NewInt(1))
141+
return num
142+
}
143+
}
144+
145+
// getNamedTypeNum returns an appropriate (unique) number for the given named
146+
// type. If the name already has a number that number is returned, else a new
147+
// number is returned. The number is always non-zero.
148+
func getNamedTypeNum(namedTypes map[string]int, name string) int {
149+
if num, ok := namedTypes[name]; ok {
80150
return num
81151
} else {
82-
return nil
152+
num = len(namedTypes) + 1
153+
namedTypes[name] = num
154+
return num
83155
}
84156
}

src/reflect/type.go

Lines changed: 48 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,26 @@ import (
44
"unsafe"
55
)
66

7-
// A Kind is the number that the compiler uses for this type.
7+
// The compiler uses a compact encoding to store type information. Unlike the
8+
// main Go compiler, most of the types are stored directly in the type code.
89
//
9-
// Not used directly. These types are all replaced with the number the compiler
10-
// uses internally for the type.
10+
// Type code bit allocation:
11+
// xxxxx0: basic types, where xxxxx is the basic type number (never 0).
12+
// The higher bits indicate the named type, if any.
13+
// nxxx1: complex types, where n indicates whether this is a named type (named
14+
// if set) and xxx contains the type kind number:
15+
// 0 (0001): Chan
16+
// 1 (0011): Interface
17+
// 2 (0101): Ptr
18+
// 3 (0111): Slice
19+
// 4 (1001): Array
20+
// 5 (1011): Func
21+
// 6 (1101): Map
22+
// 7 (1111): Struct
23+
// The higher bits are either the contents of the type depending on the
24+
// type (if n is clear) or indicate the number of the named type (if n
25+
// is set).
26+
1127
type Kind uintptr
1228

1329
// Copied from reflect/type.go
@@ -32,13 +48,13 @@ const (
3248
Complex128
3349
String
3450
UnsafePointer
35-
Array
3651
Chan
37-
Func
3852
Interface
39-
Map
4053
Ptr
4154
Slice
55+
Array
56+
Func
57+
Map
4258
Struct
4359
)
4460

@@ -80,16 +96,30 @@ func (k Kind) String() string {
8096
return "string"
8197
case UnsafePointer:
8298
return "unsafe.Pointer"
99+
case Chan:
100+
return "chan"
101+
case Interface:
102+
return "interface"
103+
case Ptr:
104+
return "ptr"
83105
case Slice:
84106
return "slice"
107+
case Array:
108+
return "array"
109+
case Func:
110+
return "func"
111+
case Map:
112+
return "map"
113+
case Struct:
114+
return "struct"
85115
default:
86116
return "invalid"
87117
}
88118
}
89119

90120
// basicType returns a new Type for this kind if Kind is a basic type.
91121
func (k Kind) basicType() Type {
92-
return Type(k << 2 | 0)
122+
return Type(k << 1)
93123
}
94124

95125
// The typecode as used in an interface{}.
@@ -104,22 +134,22 @@ func (t Type) String() string {
104134
}
105135

106136
func (t Type) Kind() Kind {
107-
if t % 4 == 0 {
108-
// Basic type
109-
return Kind(t >> 2)
110-
} else if t % 4 == 1 {
111-
// Slice
112-
return Slice
137+
if t % 2 == 0 {
138+
// basic type
139+
return Kind((t >> 1) % 32)
113140
} else {
114-
return Invalid // TODO
141+
return Kind(t >> 1) % 8 + 19
115142
}
116143
}
117144

118145
func (t Type) Elem() Type {
119146
switch t.Kind() {
120-
case Slice:
121-
return t >> 2
122-
default: // not implemented: Array, Chan, Map, Ptr
147+
case Chan, Ptr, Slice:
148+
if (t >> 4) % 2 != 0 {
149+
panic("unimplemented: (reflect.Type).Elem() for named types")
150+
}
151+
return t >> 5
152+
default: // not implemented: Array, Map
123153
panic("unimplemented: (reflect.Type).Elem()")
124154
}
125155
}
@@ -164,7 +194,7 @@ func (t Type) Size() uintptr {
164194
return 16
165195
case String:
166196
return unsafe.Sizeof(StringHeader{})
167-
case UnsafePointer:
197+
case UnsafePointer, Chan, Map, Ptr:
168198
return unsafe.Sizeof(uintptr(0))
169199
case Slice:
170200
return unsafe.Sizeof(SliceHeader{})

0 commit comments

Comments
 (0)