diff --git a/compiler/interface.go b/compiler/interface.go index 2e609c87..4d2995ba 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -54,6 +54,7 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value { // element types. Store it directly in the typecode global to make // reflect lowering simpler. var references llvm.Value + var length int64 switch typ := typ.(type) { case *types.Named: references = c.getTypeCode(typ.Underlying()) @@ -63,6 +64,9 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value { references = c.getTypeCode(typ.Elem()) case *types.Slice: references = c.getTypeCode(typ.Elem()) + case *types.Array: + references = c.getTypeCode(typ.Elem()) + length = typ.Len() case *types.Struct: // Take a pointer to the typecodeID of the first field (if it exists). structGlobal := c.makeStructTypeFields(typ) @@ -72,6 +76,10 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value { // Set the 'references' field of the runtime.typecodeID struct. globalValue := c.getZeroValue(global.Type().ElementType()) globalValue = llvm.ConstInsertValue(globalValue, references, []uint32{0}) + if length != 0 { + lengthValue := llvm.ConstInt(c.uintptrType, uint64(length), false) + globalValue = llvm.ConstInsertValue(globalValue, lengthValue, []uint32{1}) + } global.SetInitializer(globalValue) global.SetLinkage(llvm.PrivateLinkage) } diff --git a/compiler/reflect.go b/compiler/reflect.go index 83aec8da..da155e16 100644 --- a/compiler/reflect.go +++ b/compiler/reflect.go @@ -89,6 +89,11 @@ type typeCodeAssignmentState struct { namedBasicTypes map[string]int namedNonBasicTypes map[string]int + // Map of array types to their type code. + arrayTypes map[string]int + arrayTypesSidetable []byte + needsArrayTypesSidetable bool + // Map of struct types to their type code. structTypes map[string]int structTypesSidetable []byte @@ -137,11 +142,13 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) { uintptrLen: c.uintptrType.IntTypeWidth(), namedBasicTypes: make(map[string]int), namedNonBasicTypes: make(map[string]int), + arrayTypes: make(map[string]int), 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, + needsArrayTypesSidetable: len(getUses(c.mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0, } for _, t := range typeSlice { num := state.getTypeCodeNum(t.typecode) @@ -161,6 +168,11 @@ func (c *Compiler) assignTypeCodes(typeSlice typeInfoSlice) { global.SetLinkage(llvm.InternalLinkage) global.SetUnnamedAddr(true) } + if state.needsArrayTypesSidetable { + global := c.replaceGlobalIntWithArray("reflect.arrayTypesSidetable", state.arrayTypesSidetable) + global.SetLinkage(llvm.InternalLinkage) + global.SetUnnamedAddr(true) + } if state.needsStructTypesSidetable { global := c.replaceGlobalIntWithArray("reflect.structTypesSidetable", state.structTypesSidetable) global.SetLinkage(llvm.InternalLinkage) @@ -268,6 +280,10 @@ func (state *typeCodeAssignmentState) getNonBasicTypeCode(class string, typecode // Prefix-style type kinds. The upper bits contain the element type. sub := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0}) return state.getTypeCodeNum(sub) + case "array": + // An array is basically a pair of (typecode, length) stored in a + // sidetable. + return big.NewInt(int64(state.getArrayTypeNum(typecode))) case "struct": // More complicated type kind. The upper bits contain the index to the // struct type in the struct types sidetable. @@ -312,6 +328,43 @@ func (state *typeCodeAssignmentState) getBasicNamedTypeNum(name string) int { return num } +// getArrayTypeNum returns the array type number, which is an index into the +// reflect.arrayTypesSidetable or a unique number for this type if this table is +// not used. +func (state *typeCodeAssignmentState) getArrayTypeNum(typecode llvm.Value) int { + name := typecode.Name() + if num, ok := state.arrayTypes[name]; ok { + // This array type already has an entry in the sidetable. Don't store + // it twice. + return num + } + + if !state.needsArrayTypesSidetable { + // We don't need array sidetables, so we can just assign monotonically + // increasing numbers to each array type. + num := len(state.arrayTypes) + state.arrayTypes[name] = num + return num + } + + elemTypeCode := llvm.ConstExtractValue(typecode.Initializer(), []uint32{0}) + elemTypeNum := state.getTypeCodeNum(elemTypeCode) + if elemTypeNum.BitLen() > state.uintptrLen || !elemTypeNum.IsUint64() { + // TODO: make this a regular error + panic("array element type has a type code that is too big") + } + + // The array side table is a sequence of {element type, array length}. + arrayLength := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1}).ZExtValue() + buf := makeVarint(elemTypeNum.Uint64()) + buf = append(buf, makeVarint(arrayLength)...) + + index := len(state.arrayTypesSidetable) + state.arrayTypes[name] = index + state.arrayTypesSidetable = append(state.arrayTypesSidetable, buf...) + return index +} + // 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. diff --git a/src/reflect/sidetables.go b/src/reflect/sidetables.go index 59172229..f53b53bd 100644 --- a/src/reflect/sidetables.go +++ b/src/reflect/sidetables.go @@ -16,6 +16,9 @@ var structTypesSidetable byte //go:extern reflect.structNamesSidetable var structNamesSidetable byte +//go:extern reflect.arrayTypesSidetable +var arrayTypesSidetable 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 diff --git a/src/reflect/type.go b/src/reflect/type.go index b59aa76e..d537f40e 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -142,11 +142,17 @@ func (t Type) Kind() Kind { } } +// Elem returns the element type for channel, slice and array types, the +// pointed-to value for pointer types, and the key type for map types. func (t Type) Elem() Type { switch t.Kind() { case Chan, Ptr, Slice: return t.stripPrefix() - default: // not implemented: Array, Map + case Array: + index := t.stripPrefix() + elem, _ := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&arrayTypesSidetable)) + uintptr(index))) + return Type(elem) + default: // not implemented: Map panic("unimplemented: (reflect.Type).Elem()") } } @@ -254,8 +260,20 @@ func (t Type) Bits() int { panic(TypeError{"Bits"}) } +// Len returns the number of elements in this array. It panics of the type kind +// is not Array. func (t Type) Len() int { - panic("unimplemented: (reflect.Type).Len()") + if t.Kind() != Array { + panic(TypeError{"Len"}) + } + + // skip past the element type + arrayIdentifier := t.stripPrefix() + _, p := readVarint(unsafe.Pointer(uintptr(unsafe.Pointer(&arrayTypesSidetable)) + uintptr(arrayIdentifier))) + + // Read the array length. + arrayLen, _ := readVarint(p) + return int(arrayLen) } // NumField returns the number of fields of a struct type. It panics for other @@ -301,6 +319,8 @@ func (t Type) Size() uintptr { return unsafe.Sizeof(SliceHeader{}) case Interface: return unsafe.Sizeof(interfaceHeader{}) + case Array: + return t.Elem().Size() * uintptr(t.Len()) case Struct: numField := t.NumField() if numField == 0 { diff --git a/src/reflect/value.go b/src/reflect/value.go index ccd06948..a2ed58aa 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -302,6 +302,8 @@ func (v Value) Slice(i, j int) Value { panic("unimplemented: (reflect.Value).Slice()") } +// Len returns the length of this value for slices, strings, arrays, channels, +// and maps. For oter types, it panics. func (v Value) Len() int { t := v.Type() switch t.Kind() { @@ -309,7 +311,9 @@ func (v Value) Len() int { return int((*SliceHeader)(v.value).Len) case String: return int((*StringHeader)(v.value).Len) - default: // Array, Chan, Map + case Array: + return v.Type().Len() + default: // Chan, Map panic("unimplemented: (reflect.Value).Len()") } } @@ -392,27 +396,21 @@ func (v Value) Field(i int) Value { // 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) - } + value := unsafe.Pointer(loadValue(ptr, fieldSize)) return Value{ flags: 0, typecode: structField.Type, - value: unsafe.Pointer(loadedValue), + value: value, } } // 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) + value := maskAndShift(uintptr(v.value), structField.Offset, fieldSize) return Value{ flags: flags, typecode: structField.Type, - value: unsafe.Pointer((uintptr(v.value) >> (structField.Offset * 8)) & mask), + value: unsafe.Pointer(value), } } @@ -444,12 +442,75 @@ func (v Value) Index(i int) Value { value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Pointer(s.Data + uintptr(i))))), } case Array: - panic("unimplemented: (reflect.Value).Index()") + // Extract an element from the array. + elemType := v.Type().Elem() + elemSize := elemType.Size() + size := v.Type().Size() + if size == 0 { + // The element size is 0 and/or the length of the array is 0. + return Value{ + typecode: v.Type().Elem(), + flags: v.flags, + } + } + if elemSize > unsafe.Sizeof(uintptr(0)) { + // The resulting value doesn't fit in a pointer so must be + // indirect. Also, because size != 0 this implies that the array + // length must be != 0, and thus that the total size is at least + // elemSize. + addr := uintptr(v.value) + elemSize*uintptr(i) // pointer to new value + return Value{ + typecode: v.Type().Elem(), + flags: v.flags, + value: unsafe.Pointer(addr), + } + } + + if size > unsafe.Sizeof(uintptr(0)) { + // The element fits in a pointer, but the array does not. + // Load the value from the pointer. + addr := uintptr(v.value) + elemSize*uintptr(i) // pointer to new value + return Value{ + typecode: v.Type().Elem(), + flags: v.flags, + value: unsafe.Pointer(loadValue(unsafe.Pointer(addr), elemSize)), + } + } + + // The value fits in a pointer, so extract it with some shifting and + // masking. + offset := elemSize * uintptr(i) + value := maskAndShift(uintptr(v.value), offset, elemSize) + return Value{ + typecode: v.Type().Elem(), + flags: v.flags, + value: unsafe.Pointer(value), + } default: panic(&ValueError{"Index"}) } } +// loadValue loads a value that may or may not be word-aligned. The number of +// bytes given in size are loaded. The biggest possible size it can load is that +// of an uintptr. +func loadValue(ptr unsafe.Pointer, size uintptr) uintptr { + loadedValue := uintptr(0) + shift := uintptr(0) + for i := uintptr(0); i < size; i++ { + loadedValue |= uintptr(*(*byte)(ptr)) << shift + shift += 8 + ptr = unsafe.Pointer(uintptr(ptr) + 1) + } + return loadedValue +} + +// maskAndShift cuts out a part of a uintptr. Note that the offset may not be 0. +func maskAndShift(value, offset, size uintptr) uintptr { + mask := ^uintptr(0) >> ((unsafe.Sizeof(uintptr(0)) - size) * 8) + return (uintptr(value) >> (offset * 8)) & mask +} + func (v Value) MapKeys() []Value { panic("unimplemented: (reflect.Value).MapKeys()") } diff --git a/src/runtime/interface.go b/src/runtime/interface.go index c4032db5..78cfba2c 100644 --- a/src/runtime/interface.go +++ b/src/runtime/interface.go @@ -49,10 +49,13 @@ type typecodeID struct { // * basic types: null // * named type: the underlying type // * interface: null - // * chan/pointer/slice: the element type - // * struct: GEP of structField array (to typecode field) - // * array/func/map: TODO + // * chan/pointer/slice/array: the element type + // * struct: bitcast of global with structField array + // * func/map: TODO references *typecodeID + + // The array length, for array types. + length uintptr } // structField is used by the compiler to pass information to the interface diff --git a/testdata/reflect.go b/testdata/reflect.go index 314f112c..8377f95c 100644 --- a/testdata/reflect.go +++ b/testdata/reflect.go @@ -91,7 +91,8 @@ func main() { []complex128{1, 1.128 + 0.4i}, myslice{5, 3, 11}, // array - [4]int{1, 2, 3, 4}, + [3]int64{5, 8, 2}, + [2]uint8{3, 5}, // functions zeroFunc, emptyFunc, @@ -287,7 +288,10 @@ func showValue(rv reflect.Value, indent string) { case reflect.UnsafePointer: println(indent+" pointer:", rv.Pointer() != 0) case reflect.Array: - println(indent + " array") + println(indent+" array:", rt.Len(), rt.Elem().Kind().String(), int(rt.Size())) + for i := 0; i < rv.Len(); i++ { + showValue(rv.Index(i), indent+" ") + } case reflect.Chan: println(indent+" chan:", rt.Elem().Kind().String()) println(indent+" nil:", rv.IsNil()) diff --git a/testdata/reflect.txt b/testdata/reflect.txt index 4b17a90f..ab3232be 100644 --- a/testdata/reflect.txt +++ b/testdata/reflect.txt @@ -203,7 +203,19 @@ reflect type: slice reflect type: uint8 settable=true uint: 11 reflect type: array - array + array: 3 int64 24 + reflect type: int64 + int: 5 + reflect type: int64 + int: 8 + reflect type: int64 + int: 2 +reflect type: array + array: 2 uint8 2 + reflect type: uint8 + uint: 3 + reflect type: uint8 + uint: 5 reflect type: func func nil: true