From 439dc7aca1f62426fa77ab9f9c7accaf631627aa Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Tue, 4 Mar 2025 11:31:22 +0100 Subject: [PATCH 1/2] internal/reflectlite: do not use the reflect package This avoids a dependency on the reflect package. I've tried to deduplicate as much as reasonably possible, while keeping the code clean. Especially some tricky methods for the reflect.Type type have a shared implementation (using //go:linkname). In other cases, I've simply used the same type using a type alias, in the other direction (the reflect package now imports the internal/reflectlite package instead of the other way around). --- .../reflectlite}/endian-big.go | 2 +- .../reflectlite}/endian-little.go | 2 +- src/internal/reflectlite/error.go | 28 + src/internal/reflectlite/reflect.go | 51 -- .../reflectlite}/strconv.go | 2 +- src/internal/reflectlite/swapper.go | 40 ++ src/internal/reflectlite/type.go | 653 ++++++++++++++++++ src/internal/reflectlite/value.go | 591 ++++++++++++++++ src/reflect/swapper.go | 37 +- src/reflect/type.go | 465 ++----------- src/reflect/value.go | 464 +------------ src/runtime/hashmap.go | 2 +- transform/rtcalls.go | 8 +- 13 files changed, 1427 insertions(+), 918 deletions(-) rename src/{reflect => internal/reflectlite}/endian-big.go (98%) rename src/{reflect => internal/reflectlite}/endian-little.go (98%) create mode 100644 src/internal/reflectlite/error.go delete mode 100644 src/internal/reflectlite/reflect.go rename src/{reflect => internal/reflectlite}/strconv.go (99%) create mode 100644 src/internal/reflectlite/swapper.go create mode 100644 src/internal/reflectlite/type.go create mode 100644 src/internal/reflectlite/value.go diff --git a/src/reflect/endian-big.go b/src/internal/reflectlite/endian-big.go similarity index 98% rename from src/reflect/endian-big.go rename to src/internal/reflectlite/endian-big.go index 94951e200d..5ad792dcc3 100644 --- a/src/reflect/endian-big.go +++ b/src/internal/reflectlite/endian-big.go @@ -1,6 +1,6 @@ //go:build mips -package reflect +package reflectlite import "unsafe" diff --git a/src/reflect/endian-little.go b/src/internal/reflectlite/endian-little.go similarity index 98% rename from src/reflect/endian-little.go rename to src/internal/reflectlite/endian-little.go index 7d7e30059d..035ec01d8b 100644 --- a/src/reflect/endian-little.go +++ b/src/internal/reflectlite/endian-little.go @@ -1,6 +1,6 @@ //go:build !mips -package reflect +package reflectlite import "unsafe" diff --git a/src/internal/reflectlite/error.go b/src/internal/reflectlite/error.go new file mode 100644 index 0000000000..02c78b191d --- /dev/null +++ b/src/internal/reflectlite/error.go @@ -0,0 +1,28 @@ +package reflectlite + +type TypeError struct { + Method string +} + +func (e *TypeError) Error() string { + return "reflect: call of reflect.Type." + e.Method + " on invalid type" +} + +var ( + errTypeKey = &TypeError{"Key"} + errTypeElem = &TypeError{"Elem"} + errTypeField = &TypeError{"Field"} + errTypeChanDir = &TypeError{"ChanDir"} +) + +type ValueError struct { + Method string + Kind Kind +} + +func (e *ValueError) Error() string { + if e.Kind == 0 { + return "reflect: call of " + e.Method + " on zero Value" + } + return "reflect: call of " + e.Method + " on " + e.Kind.String() + " Value" +} diff --git a/src/internal/reflectlite/reflect.go b/src/internal/reflectlite/reflect.go deleted file mode 100644 index 938e56a556..0000000000 --- a/src/internal/reflectlite/reflect.go +++ /dev/null @@ -1,51 +0,0 @@ -package reflectlite - -import "reflect" - -func Swapper(slice interface{}) func(i, j int) { - return reflect.Swapper(slice) -} - -type Kind = reflect.Kind -type Type = reflect.Type -type Value = reflect.Value - -const ( - Invalid Kind = reflect.Invalid - Bool Kind = reflect.Bool - Int Kind = reflect.Int - Int8 Kind = reflect.Int8 - Int16 Kind = reflect.Int16 - Int32 Kind = reflect.Int32 - Int64 Kind = reflect.Int64 - Uint Kind = reflect.Uint - Uint8 Kind = reflect.Uint8 - Uint16 Kind = reflect.Uint16 - Uint32 Kind = reflect.Uint32 - Uint64 Kind = reflect.Uint64 - Uintptr Kind = reflect.Uintptr - Float32 Kind = reflect.Float32 - Float64 Kind = reflect.Float64 - Complex64 Kind = reflect.Complex64 - Complex128 Kind = reflect.Complex128 - Array Kind = reflect.Array - Chan Kind = reflect.Chan - Func Kind = reflect.Func - Interface Kind = reflect.Interface - Map Kind = reflect.Map - Ptr Kind = reflect.Ptr - Slice Kind = reflect.Slice - String Kind = reflect.String - Struct Kind = reflect.Struct - UnsafePointer Kind = reflect.UnsafePointer -) - -func ValueOf(i interface{}) reflect.Value { - return reflect.ValueOf(i) -} - -func TypeOf(i interface{}) reflect.Type { - return reflect.TypeOf(i) -} - -type ValueError = reflect.ValueError diff --git a/src/reflect/strconv.go b/src/internal/reflectlite/strconv.go similarity index 99% rename from src/reflect/strconv.go rename to src/internal/reflectlite/strconv.go index 2d8131c9df..deabe4a5c7 100644 --- a/src/reflect/strconv.go +++ b/src/internal/reflectlite/strconv.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package reflect +package reflectlite import ( "unicode/utf8" diff --git a/src/internal/reflectlite/swapper.go b/src/internal/reflectlite/swapper.go new file mode 100644 index 0000000000..b8d85a9442 --- /dev/null +++ b/src/internal/reflectlite/swapper.go @@ -0,0 +1,40 @@ +package reflectlite + +import "unsafe" + +// Some of code here has been copied from the Go sources: +// https://github.com/golang/go/blob/go1.15.2/src/reflect/swapper.go +// It has the following copyright note: +// +// Copyright 2016 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +func Swapper(slice interface{}) func(i, j int) { + v := ValueOf(slice) + if v.Kind() != Slice { + panic(&ValueError{Method: "Swapper"}) + } + + // Just return Nop func if nothing to swap. + if v.Len() < 2 { + return func(i, j int) {} + } + + typ := v.typecode.Elem() + size := typ.Size() + + header := (*sliceHeader)(v.value) + tmp := unsafe.Pointer(&make([]byte, size)[0]) + + return func(i, j int) { + if uint(i) >= uint(header.len) || uint(j) >= uint(header.len) { + panic("reflect: slice index out of range") + } + val1 := unsafe.Add(header.data, uintptr(i)*size) + val2 := unsafe.Add(header.data, uintptr(j)*size) + memcpy(tmp, val1, size) + memcpy(val1, val2, size) + memcpy(val2, tmp, size) + } +} diff --git a/src/internal/reflectlite/type.go b/src/internal/reflectlite/type.go new file mode 100644 index 0000000000..bbb425743b --- /dev/null +++ b/src/internal/reflectlite/type.go @@ -0,0 +1,653 @@ +package reflectlite + +import ( + "internal/itoa" + "unsafe" +) + +type Kind uint8 + +// Copied from reflect/type.go +// https://golang.org/src/reflect/type.go?s=8302:8316#L217 +// These constants must match basicTypes and the typeKind* constants in +// compiler/interface.go +const ( + Invalid Kind = iota + Bool + Int + Int8 + Int16 + Int32 + Int64 + Uint + Uint8 + Uint16 + Uint32 + Uint64 + Uintptr + Float32 + Float64 + Complex64 + Complex128 + String + UnsafePointer + Chan + Interface + Pointer + Slice + Array + Func + Map + Struct +) + +// Ptr is the old name for the Pointer kind. +const Ptr = Pointer + +func (k Kind) String() string { + switch k { + case Invalid: + return "invalid" + case Bool: + return "bool" + case Int: + return "int" + case Int8: + return "int8" + case Int16: + return "int16" + case Int32: + return "int32" + case Int64: + return "int64" + case Uint: + return "uint" + case Uint8: + return "uint8" + case Uint16: + return "uint16" + case Uint32: + return "uint32" + case Uint64: + return "uint64" + case Uintptr: + return "uintptr" + case Float32: + return "float32" + case Float64: + return "float64" + case Complex64: + return "complex64" + case Complex128: + return "complex128" + case String: + return "string" + case UnsafePointer: + return "unsafe.Pointer" + case Chan: + return "chan" + case Interface: + return "interface" + case Pointer: + return "ptr" + case Slice: + return "slice" + case Array: + return "array" + case Func: + return "func" + case Map: + return "map" + case Struct: + return "struct" + default: + return "kind" + itoa.Itoa(int(int8(k))) + } +} + +// Copied from reflect/type.go +// https://go.dev/src/reflect/type.go?#L348 + +// ChanDir represents a channel type's direction. +type ChanDir int + +const ( + RecvDir ChanDir = 1 << iota // <-chan + SendDir // chan<- + BothDir = RecvDir | SendDir // chan +) + +type Type interface { + Name() string + PkgPath() string + Size() uintptr + Kind() Kind + Implements(u Type) bool + AssignableTo(u Type) bool + Comparable() bool + String() string + Elem() Type +} + +// Constants for the 'meta' byte. +// These constants are also defined in the reflect package. +const ( + kindMask = 31 // mask to apply to the meta byte to get the Kind value + flagNamed = 32 // flag that is set if this is a named type + flagComparable = 64 // flag that is set if this type is comparable + flagIsBinary = 128 // flag that is set if this type uses the hashmap binary algorithm +) + +// The below types (rawType, elemType, etc) are also defined in the reflect +// package and must match the compiler output. + +type rawType struct { + meta uint8 +} + +type elemType struct { + rawType + numMethod uint16 + ptrTo *rawType + elem *rawType +} + +type ptrType struct { + rawType + numMethod uint16 + elem *rawType +} + +type interfaceType struct { + rawType + ptrTo *rawType +} + +type arrayType struct { + rawType + numMethod uint16 + ptrTo *rawType + elem *rawType + arrayLen uintptr + slicePtr *rawType +} + +type mapType struct { + rawType + numMethod uint16 + ptrTo *rawType + elem *rawType + key *rawType +} + +type namedType struct { + rawType + numMethod uint16 + ptrTo *rawType + elem *rawType + pkg *byte + name [1]byte +} + +type structType struct { + rawType + numMethod uint16 + ptrTo *rawType + pkgpath *byte + size uint32 + numField uint16 + fields [1]structField // the remaining fields are all of type structField +} + +type structField struct { + fieldType *rawType + data unsafe.Pointer // various bits of information, packed in a byte array +} + +// rawStructField is the same as StructField but with the Type member replaced +// with rawType. For internal use only. Avoiding this conversion to the Type +// interface improves code size in many cases. +type rawStructField struct { + Name string + PkgPath string + Type *rawType + Tag StructTag + Offset uintptr + Anonymous bool +} + +// Flags stored in the first byte of the struct field byte array. Must be kept +// up to date with compiler/interface.go. +const ( + structFieldFlagAnonymous = 1 << iota + structFieldFlagHasTag + structFieldFlagIsExported + structFieldFlagIsEmbedded +) + +func TypeOf(i interface{}) Type { + if i == nil { + return nil + } + typecode, _ := decomposeInterface(i) + return (*rawType)(typecode) +} + +func (t *rawType) ptrtag() uintptr { + return uintptr(unsafe.Pointer(t)) & 0b11 +} + +func (t *rawType) isNamed() bool { + if tag := t.ptrtag(); tag != 0 { + return false + } + + return t.meta&flagNamed != 0 +} + +func (t *rawType) underlying() *rawType { + if t.isNamed() { + return (*elemType)(unsafe.Pointer(t)).elem + } + return t +} + +func (t *rawType) arrayLen() uintptr { + return (*arrayType)(unsafe.Pointer(t.underlying())).arrayLen +} + +func (t *rawType) numField() int { + return int((*structType)(unsafe.Pointer(t.underlying())).numField) +} + +func (t *rawType) name() string { + ntype := (*namedType)(unsafe.Pointer(t)) + return readStringZ(unsafe.Pointer(&ntype.name[0])) +} + +// Return the size (in bytes) of the given type. +func typeSize(t *rawType) uintptr { + switch t.Kind() { + case Bool, Int8, Uint8: + return 1 + case Int16, Uint16: + return 2 + case Int32, Uint32: + return 4 + case Int64, Uint64: + return 8 + case Int, Uint: + return unsafe.Sizeof(int(0)) + case Uintptr: + return unsafe.Sizeof(uintptr(0)) + case Float32: + return 4 + case Float64: + return 8 + case Complex64: + return 8 + case Complex128: + return 16 + case String: + return unsafe.Sizeof("") + case UnsafePointer, Chan, Map, Pointer: + return unsafe.Sizeof(uintptr(0)) + case Slice: + return unsafe.Sizeof([]int{}) + case Interface: + return unsafe.Sizeof(interface{}(nil)) + case Func: + var f func() + return unsafe.Sizeof(f) + case Array: + return typeElem(t).Size() * t.arrayLen() + case Struct: + u := t.underlying() + return uintptr((*structType)(unsafe.Pointer(u)).size) + default: + panic("unimplemented: size of type") + } +} + +// Return the type kind of the given type. +func typeKind(t *rawType) Kind { + if t == nil { + return Invalid + } + + if tag := t.ptrtag(); tag != 0 { + return Pointer + } + + return Kind(t.meta & kindMask) +} + +func typeString(t *rawType) string { + if t.isNamed() { + s := t.name() + if s[0] == '.' { + return s[1:] + } + return s + } + + switch t.Kind() { + case Chan: + elem := typeElem(t).String() + switch typeChanDir(t) { + case SendDir: + return "chan<- " + elem + case RecvDir: + return "<-chan " + elem + case BothDir: + if elem[0] == '<' { + // typ is recv chan, need parentheses as "<-" associates with leftmost + // chan possible, see: + // * https://golang.org/ref/spec#Channel_types + // * https://github.com/golang/go/issues/39897 + return "chan (" + elem + ")" + } + return "chan " + elem + } + + case Pointer: + return "*" + typeElem(t).String() + case Slice: + return "[]" + typeElem(t).String() + case Array: + return "[" + itoa.Itoa(int(t.arrayLen())) + "]" + typeElem(t).String() + case Map: + return "map[" + typeKey(t).String() + "]" + typeElem(t).String() + case Struct: + numField := t.numField() + if numField == 0 { + return "struct {}" + } + s := "struct {" + for i := 0; i < numField; i++ { + f := typeRawField(t, i) + s += " " + f.Name + " " + f.Type.String() + if f.Tag != "" { + s += " " + quote(string(f.Tag)) + } + // every field except the last needs a semicolon + if i < numField-1 { + s += ";" + } + } + s += " }" + return s + case Interface: + // TODO(dgryski): Needs actual method set info + return "interface {}" + default: + return t.Kind().String() + } + + return t.Kind().String() +} + +// Return the element type given a type. Panics if this type doesn't have an +// element type. +func typeElem(t *rawType) *rawType { + if tag := t.ptrtag(); tag != 0 { + return (*rawType)(unsafe.Add(unsafe.Pointer(t), -1)) + } + + underlying := t.underlying() + switch underlying.Kind() { + case Pointer: + return (*ptrType)(unsafe.Pointer(underlying)).elem + case Chan, Slice, Array, Map: + return (*elemType)(unsafe.Pointer(underlying)).elem + default: + panic(errTypeElem) + } +} + +func typeKey(t *rawType) *rawType { + underlying := t.underlying() + if underlying.Kind() != Map { + panic(errTypeKey) + } + return (*mapType)(unsafe.Pointer(underlying)).key +} + +func typeChanDir(t *rawType) ChanDir { + if t.Kind() != Chan { + panic(errTypeChanDir) + } + + dir := int((*elemType)(unsafe.Pointer(t)).numMethod) + + // nummethod is overloaded for channel to store channel direction + return ChanDir(dir) +} + +func typeNumMethod(t *rawType) int { + if t.isNamed() { + return int((*namedType)(unsafe.Pointer(t)).numMethod) + } + + switch t.Kind() { + case Pointer: + return int((*ptrType)(unsafe.Pointer(t)).numMethod) + case Struct: + return int((*structType)(unsafe.Pointer(t)).numMethod) + case Interface: + //FIXME: Use len(methods) + return typeNumMethod((*interfaceType)(unsafe.Pointer(t)).ptrTo) + } + + // Other types have no methods attached. Note we don't panic here. + return 0 +} + +func typeAssignableTo(t, u *rawType) bool { + if t == u { + return true + } + + if t.underlying() == u.underlying() && (!t.isNamed() || !u.isNamed()) { + return true + } + + if u.Kind() == Interface && typeNumMethod(u) == 0 { + return true + } + + if u.Kind() == Interface { + panic("reflect: unimplemented: AssignableTo with interface") + } + return false +} + +func rawStructFieldFromPointer(descriptor *structType, fieldType *rawType, data unsafe.Pointer, flagsByte uint8, name string, offset uint32) rawStructField { + // Read the field tag, if there is one. + var tag string + if flagsByte&structFieldFlagHasTag != 0 { + data = unsafe.Add(data, 1) // C: data+1 + tagLen := uintptr(*(*byte)(data)) + data = unsafe.Add(data, 1) // C: data+1 + tag = *(*string)(unsafe.Pointer(&stringHeader{ + data: data, + len: tagLen, + })) + } + + // Set the PkgPath to some (arbitrary) value if the package path is not + // exported. + pkgPath := "" + if flagsByte&structFieldFlagIsExported == 0 { + // This field is unexported. + pkgPath = readStringZ(unsafe.Pointer(descriptor.pkgpath)) + } + + return rawStructField{ + Name: name, + PkgPath: pkgPath, + Type: fieldType, + Tag: StructTag(tag), + Anonymous: flagsByte&structFieldFlagAnonymous != 0, + Offset: uintptr(offset), + } +} + +func typeRawField(t *rawType, n int) rawStructField { + if t.Kind() != Struct { + panic(errTypeField) + } + descriptor := (*structType)(unsafe.Pointer(t.underlying())) + if uint(n) >= uint(descriptor.numField) { + panic("reflect: field index out of range") + } + + // Iterate over all the fields to calculate the offset. + // This offset could have been stored directly in the array (to make the + // lookup faster), but by calculating it on-the-fly a bit of storage can be + // saved. + field := (*structField)(unsafe.Add(unsafe.Pointer(&descriptor.fields[0]), uintptr(n)*unsafe.Sizeof(structField{}))) + data := field.data + + // Read some flags of this field, like whether the field is an embedded + // field. See structFieldFlagAnonymous and similar flags. + flagsByte := *(*byte)(data) + data = unsafe.Add(data, 1) + offset, lenOffs := uvarint32(unsafe.Slice((*byte)(data), maxVarintLen32)) + data = unsafe.Add(data, lenOffs) + + name := readStringZ(data) + data = unsafe.Add(data, len(name)) + + return rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset) +} + +func (t *rawType) Name() string { panic("todo: internal/reflectlite.Type.Name") } +func (t *rawType) PkgPath() string { panic("todo: internal/reflectlite.Type.PkgPath") } + +func (t *rawType) Size() uintptr { + return typeSize(t) +} + +func (t *rawType) Kind() Kind { + return typeKind(t) +} + +func (t *rawType) Implements(u Type) bool { + uraw := u.(*rawType) + if uraw.Kind() != Interface { + panic("reflect: non-interface type passed to Type.Implements") + } + return typeAssignableTo(t, uraw) +} + +func (t *rawType) AssignableTo(u Type) bool { + return typeAssignableTo(t, u.(*rawType)) +} + +func (t *rawType) Comparable() bool { + return (t.meta & flagComparable) == flagComparable +} + +func (t *rawType) String() string { + return typeString(t) +} + +func (t *rawType) Elem() Type { + return typeElem(t) +} + +// A StructTag is the tag string in a struct field. +type StructTag string + +// TODO: it would be feasible to do the key/value splitting at compile time, +// avoiding the code size cost of doing it at runtime + +// Get returns the value associated with key in the tag string. +func (tag StructTag) Get(key string) string { + v, _ := tag.Lookup(key) + return v +} + +// Lookup returns the value associated with key in the tag string. +func (tag StructTag) Lookup(key string) (value string, ok bool) { + for tag != "" { + // Skip leading space. + i := 0 + for i < len(tag) && tag[i] == ' ' { + i++ + } + tag = tag[i:] + if tag == "" { + break + } + + // Scan to colon. A space, a quote or a control character is a syntax error. + // Strictly speaking, control chars include the range [0x7f, 0x9f], not just + // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters + // as it is simpler to inspect the tag's bytes than the tag's runes. + i = 0 + for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { + i++ + } + if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { + break + } + name := string(tag[:i]) + tag = tag[i+1:] + + // Scan quoted string to find value. + i = 1 + for i < len(tag) && tag[i] != '"' { + if tag[i] == '\\' { + i++ + } + i++ + } + if i >= len(tag) { + break + } + qvalue := string(tag[:i+1]) + tag = tag[i+1:] + + if key == name { + value, err := unquote(qvalue) + if err != nil { + break + } + return value, true + } + } + return "", false +} + +// Read and return a null terminated string starting from data. +func readStringZ(data unsafe.Pointer) string { + start := data + var len uintptr + for *(*byte)(data) != 0 { + len++ + data = unsafe.Add(data, 1) // C: data++ + } + + return *(*string)(unsafe.Pointer(&stringHeader{ + data: start, + len: len, + })) +} + +const maxVarintLen32 = 5 + +// encoding/binary.Uvarint, specialized for uint32 +func uvarint32(buf []byte) (uint32, int) { + var x uint32 + var s uint + for i, b := range buf { + if b < 0x80 { + return x | uint32(b)<= uint(slice.len) { + panic("reflect: slice index out of range") + } + flags := (v.flags & (valueFlagExported | valueFlagIndirect)) | valueFlagIndirect | v.flags.ro() + elem := Value{ + typecode: typeElem(v.typecode), + flags: flags, + } + elem.value = unsafe.Add(slice.data, elem.typecode.Size()*uintptr(i)) // pointer to new value + return elem + case String: + // Extract a character from a string. + // A string is never stored directly in the interface, but always as a + // pointer to the string value. + // Keeping valueFlagExported if set, but don't set valueFlagIndirect + // otherwise CanSet will return true for string elements (which is bad, + // strings are read-only). + s := *(*stringHeader)(v.value) + if uint(i) >= uint(s.len) { + panic("reflect: string index out of range") + } + return Value{ + typecode: uint8Type, + value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Add(s.data, i)))), + flags: v.flags & valueFlagExported, + } + case Array: + // Extract an element from the array. + elemType := typeElem(v.typecode) + elemSize := elemType.Size() + size := v.typecode.Size() + if size == 0 { + // The element size is 0 and/or the length of the array is 0. + return Value{ + typecode: typeElem(v.typecode), + 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 := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value + return Value{ + typecode: typeElem(v.typecode), + flags: v.flags, + value: addr, + } + } + + if size > unsafe.Sizeof(uintptr(0)) || v.isIndirect() { + // The element fits in a pointer, but the array is not stored in the pointer directly. + // Load the value from the pointer. + addr := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value + value := addr + if !v.isIndirect() { + // Use a pointer to the value (don't load the value) if the + // 'indirect' flag is set. + value = unsafe.Pointer(loadValue(addr, elemSize)) + } + return Value{ + typecode: typeElem(v.typecode), + flags: v.flags, + value: value, + } + } + + // 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: typeElem(v.typecode), + flags: v.flags, + value: unsafe.Pointer(value), + } + default: + panic(&ValueError{Method: "Index", Kind: v.Kind()}) + } +} + +func valueField(v Value, i int) Value { + if v.Kind() != Struct { + panic(&ValueError{Method: "Field", Kind: v.Kind()}) + } + structField := typeRawField(v.typecode, i) + + // Copy flags but clear EmbedRO; we're not an embedded field anymore + flags := v.flags & ^valueFlagEmbedRO + if structField.PkgPath != "" { + // No PkgPath => not exported. + // Clear exported flag even if the parent was exported. + flags &^= valueFlagExported + + // Update the RO flag + if structField.Anonymous { + // Embedded field + flags |= valueFlagEmbedRO + } else { + flags |= valueFlagStickyRO + } + } else { + // Parent field may not have been exported but we are + flags |= valueFlagExported + } + + size := v.typecode.Size() + fieldType := structField.Type + fieldSize := fieldType.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: fieldType, + value: unsafe.Add(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: fieldType, + value: unsafe.Pointer(nil), + } + } + + 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.Add(v.value, structField.Offset) + value := unsafe.Pointer(loadValue(ptr, fieldSize)) + return Value{ + flags: flags &^ valueFlagIndirect, + typecode: fieldType, + 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. + value := maskAndShift(uintptr(v.value), structField.Offset, fieldSize) + return Value{ + flags: flags, + typecode: fieldType, + value: unsafe.Pointer(value), + } +} + +// valueInterfaceUnsafe is used by the runtime to hash map keys. It should not +// be subject to the isExported check. +func valueInterfaceUnsafe(v Value) interface{} { + if v.typecode.Kind() == Interface { + // The value itself is an interface. This can happen when getting the + // value of a struct field of interface type, like this: + // type T struct { + // X interface{} + // } + return *(*interface{})(v.value) + } + if v.isIndirect() && v.typecode.Size() <= unsafe.Sizeof(uintptr(0)) { + // Value was indirect but must be put back directly in the interface + // value. + var value uintptr + for j := v.typecode.Size(); j != 0; j-- { + value = (value << 8) | uintptr(*(*uint8)(unsafe.Add(v.value, j-1))) + } + v.value = unsafe.Pointer(value) + } + return composeInterface(unsafe.Pointer(v.typecode), v.value) +} + +// Internal function only, do not use. +// +// RawType returns the raw, underlying type code. It is used in the runtime +// package and needs to be exported for the runtime package to access it. +func (v Value) RawType() *rawType { + return v.typecode +} + +func (v Value) Type() Type { + return v.typecode +} + +func (v Value) IsNil() bool { + switch v.Kind() { + case Chan, Map, Ptr, UnsafePointer: + return v.pointer() == 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 == nil + case Interface: + val := *(*interface{})(v.value) + return val == nil + default: + panic(&ValueError{Method: "IsNil", Kind: v.Kind()}) + } +} + +func (v Value) Elem() Value { + return valueElem(v) +} + +func (v Value) Set(x Value) { + v.checkAddressable() + v.checkRO() + if !x.typecode.AssignableTo(v.typecode) { + panic("reflect.Value.Set: value of type " + x.typecode.String() + " cannot be assigned to type " + v.typecode.String()) + } + + if v.typecode.Kind() == Interface && x.typecode.Kind() != Interface { + // move the value of x back into the interface, if possible + if x.isIndirect() && x.typecode.Size() <= unsafe.Sizeof(uintptr(0)) { + x.value = unsafe.Pointer(loadValue(x.value, x.typecode.Size())) + } + + intf := composeInterface(unsafe.Pointer(x.typecode), x.value) + x = Value{ + typecode: v.typecode, + value: unsafe.Pointer(&intf), + } + } + + size := v.typecode.Size() + if size <= unsafe.Sizeof(uintptr(0)) && !x.isIndirect() { + storeValue(v.value, size, uintptr(x.value)) + } else { + memcpy(v.value, x.value, size) + } +} + +func (v Value) Kind() Kind { + return v.typecode.Kind() +} + +func (v Value) Len() int { + switch v.typecode.Kind() { + case Array: + return int(v.typecode.arrayLen()) + case Chan: + return chanlen(v.pointer()) + case Map: + return maplen(v.pointer()) + case Slice: + return int((*sliceHeader)(v.value).len) + case String: + return int((*stringHeader)(v.value).len) + default: + panic(&ValueError{Method: "Len", Kind: v.Kind()}) + } +} + +func (v Value) Bool() bool { + switch v.Kind() { + case Bool: + if v.isIndirect() { + return *((*bool)(v.value)) + } else { + return uintptr(v.value) != 0 + } + default: + panic(&ValueError{Method: "Bool", Kind: v.Kind()}) + } +} + +func (v Value) Int() int64 { + switch v.Kind() { + case Int: + 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.isIndirect() { + return int64(*(*int8)(v.value)) + } else { + return int64(int8(uintptr(v.value))) + } + case Int16: + if v.isIndirect() { + return int64(*(*int16)(v.value)) + } else { + return int64(int16(uintptr(v.value))) + } + case Int32: + 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.isIndirect() || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) { + return int64(*(*int64)(v.value)) + } else { + return int64(int64(uintptr(v.value))) + } + default: + panic(&ValueError{Method: "Int", Kind: v.Kind()}) + } +} + +func (v Value) Uint() uint64 { + switch v.Kind() { + case Uintptr: + if v.isIndirect() { + return uint64(*(*uintptr)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint8: + if v.isIndirect() { + return uint64(*(*uint8)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint16: + if v.isIndirect() { + return uint64(*(*uint16)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint: + 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.isIndirect() || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) { + return uint64(*(*uint32)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + case Uint64: + if v.isIndirect() || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) { + return uint64(*(*uint64)(v.value)) + } else { + return uint64(uintptr(v.value)) + } + default: + panic(&ValueError{Method: "Uint", Kind: v.Kind()}) + } +} + +func (v Value) Float() float64 { + switch v.Kind() { + case Float32: + 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)) + } else { + // The float is directly stored in the interface value on systems + // with 32-bit and 64-bit pointers. + return float64(*(*float32)(unsafe.Pointer(&v.value))) + } + case Float64: + 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 { + // The float is directly stored in the interface value on systems + // with 64-bit pointers. + return *(*float64)(unsafe.Pointer(&v.value)) + } + default: + panic(&ValueError{Method: "Float", Kind: v.Kind()}) + } +} + +func (v Value) Complex() complex128 { + switch v.Kind() { + case Complex64: + 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)) + } else { + // The complex number is directly stored in the interface value on + // systems with 64-bit pointers. + return complex128(*(*complex64)(unsafe.Pointer(&v.value))) + } + case Complex128: + // This is a 128-bit value, which is always stored as an external value. + // It may be stored in the pointer directly on very uncommon + // architectures with 128-bit pointers, however. + return *(*complex128)(v.value) + default: + panic(&ValueError{Method: "Complex", Kind: v.Kind()}) + } +} + +func (v Value) String() string { + switch v.Kind() { + case String: + // A string value is always bigger than a pointer as it is made of a + // pointer and a length. + return *(*string)(v.value) + default: + // Special case because of the special treatment of .String() in Go. + return "<" + v.typecode.String() + " Value>" + } +} + +func (v Value) UnsafePointer() unsafe.Pointer { + switch v.Kind() { + case Chan, Map, Ptr, UnsafePointer: + return v.pointer() + case Slice: + slice := (*sliceHeader)(v.value) + return slice.data + case Func: + fn := (*funcHeader)(v.value) + if fn.Context != nil { + return fn.Context + } + return fn.Code + default: + panic(&ValueError{Method: "UnsafePointer", Kind: v.Kind()}) + } +} + +// NumField returns the number of fields of this struct. It panics for other +// value types. +func (v Value) NumField() int { + return v.typecode.numField() +} + +func (v Value) Index(i int) Value { + return valueIndex(v, i) +} + +func (v Value) Field(i int) Value { + return valueField(v, i) +} + +type funcHeader struct { + Context unsafe.Pointer + Code unsafe.Pointer +} + +type sliceHeader struct { + data unsafe.Pointer + len uintptr + cap uintptr +} + +type stringHeader struct { + data unsafe.Pointer + len uintptr +} + +//go:linkname memcpy runtime.memcpy +func memcpy(dst, src unsafe.Pointer, size uintptr) + +//go:linkname maplen runtime.hashmapLen +func maplen(p unsafe.Pointer) int + +//go:linkname chanlen runtime.chanLen +func chanlen(p unsafe.Pointer) int + +// This function is needed to convert reflect.Value to reflectlite.Value in the +// reflect package, so that reflectlite.Value methods can be used directly. +// +//go:linkname liteImpl reflect.lite +func liteImpl(v Value) Value { + return v +} diff --git a/src/reflect/swapper.go b/src/reflect/swapper.go index a2fa44cef0..d49e33e04c 100644 --- a/src/reflect/swapper.go +++ b/src/reflect/swapper.go @@ -1,40 +1,7 @@ package reflect -import "unsafe" - -// Some of code here has been copied from the Go sources: -// https://github.com/golang/go/blob/go1.15.2/src/reflect/swapper.go -// It has the following copyright note: -// -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. +import "internal/reflectlite" func Swapper(slice interface{}) func(i, j int) { - v := ValueOf(slice) - if v.Kind() != Slice { - panic(&ValueError{Method: "Swapper"}) - } - - // Just return Nop func if nothing to swap. - if v.Len() < 2 { - return func(i, j int) {} - } - - typ := v.typecode.Elem() - size := typ.Size() - - header := (*sliceHeader)(v.value) - tmp := unsafe.Pointer(&make([]byte, size)[0]) - - return func(i, j int) { - if uint(i) >= uint(header.len) || uint(j) >= uint(header.len) { - panic("reflect: slice index out of range") - } - val1 := unsafe.Add(header.data, uintptr(i)*size) - val2 := unsafe.Add(header.data, uintptr(j)*size) - memcpy(tmp, val1, size) - memcpy(val1, val2, size) - memcpy(val2, tmp, size) - } + return reflectlite.Swapper(slice) } diff --git a/src/reflect/type.go b/src/reflect/type.go index c81d6ba554..4386f35bdb 100644 --- a/src/reflect/type.go +++ b/src/reflect/type.go @@ -65,7 +65,7 @@ package reflect import ( "internal/gclayout" - "internal/itoa" + "internal/reflectlite" "unsafe" ) @@ -78,116 +78,47 @@ const ( structFieldFlagIsEmbedded ) -type Kind uint8 +type Kind = reflectlite.Kind -// Copied from reflect/type.go -// https://golang.org/src/reflect/type.go?s=8302:8316#L217 -// These constants must match basicTypes and the typeKind* constants in -// compiler/interface.go const ( - Invalid Kind = iota - Bool - Int - Int8 - Int16 - Int32 - Int64 - Uint - Uint8 - Uint16 - Uint32 - Uint64 - Uintptr - Float32 - Float64 - Complex64 - Complex128 - String - UnsafePointer - Chan - Interface - Pointer - Slice - Array - Func - Map - Struct + Invalid Kind = reflectlite.Invalid + Bool Kind = reflectlite.Bool + Int Kind = reflectlite.Int + Int8 Kind = reflectlite.Int8 + Int16 Kind = reflectlite.Int16 + Int32 Kind = reflectlite.Int32 + Int64 Kind = reflectlite.Int64 + Uint Kind = reflectlite.Uint + Uint8 Kind = reflectlite.Uint8 + Uint16 Kind = reflectlite.Uint16 + Uint32 Kind = reflectlite.Uint32 + Uint64 Kind = reflectlite.Uint64 + Uintptr Kind = reflectlite.Uintptr + Float32 Kind = reflectlite.Float32 + Float64 Kind = reflectlite.Float64 + Complex64 Kind = reflectlite.Complex64 + Complex128 Kind = reflectlite.Complex128 + String Kind = reflectlite.String + UnsafePointer Kind = reflectlite.UnsafePointer + Chan Kind = reflectlite.Chan + Interface Kind = reflectlite.Interface + Pointer Kind = reflectlite.Pointer + Slice Kind = reflectlite.Slice + Array Kind = reflectlite.Array + Func Kind = reflectlite.Func + Map Kind = reflectlite.Map + Struct Kind = reflectlite.Struct ) // Ptr is the old name for the Pointer kind. const Ptr = Pointer -func (k Kind) String() string { - switch k { - case Invalid: - return "invalid" - case Bool: - return "bool" - case Int: - return "int" - case Int8: - return "int8" - case Int16: - return "int16" - case Int32: - return "int32" - case Int64: - return "int64" - case Uint: - return "uint" - case Uint8: - return "uint8" - case Uint16: - return "uint16" - case Uint32: - return "uint32" - case Uint64: - return "uint64" - case Uintptr: - return "uintptr" - case Float32: - return "float32" - case Float64: - return "float64" - case Complex64: - return "complex64" - case Complex128: - return "complex128" - case String: - return "string" - case UnsafePointer: - return "unsafe.Pointer" - case Chan: - return "chan" - case Interface: - return "interface" - case Pointer: - return "ptr" - case Slice: - return "slice" - case Array: - return "array" - case Func: - return "func" - case Map: - return "map" - case Struct: - return "struct" - default: - return "kind" + itoa.Itoa(int(int8(k))) - } -} - -// Copied from reflect/type.go -// https://go.dev/src/reflect/type.go?#L348 - -// ChanDir represents a channel type's direction. -type ChanDir int +type ChanDir = reflectlite.ChanDir const ( - RecvDir ChanDir = 1 << iota // <-chan - SendDir // chan<- - BothDir = RecvDir | SendDir // chan + RecvDir = reflectlite.RecvDir + SendDir = reflectlite.SendDir + BothDir = reflectlite.BothDir ) // Method represents a single method. @@ -412,6 +343,7 @@ type Type interface { } // Constants for the 'meta' byte. +// These constants are also defined in the internal/reflectlite package. const ( kindMask = 31 // mask to apply to the meta byte to get the Kind value flagNamed = 32 // flag that is set if this is a named type @@ -549,91 +481,25 @@ func pointerTo(t *rawType) *rawType { } } -func (t *rawType) String() string { - if t.isNamed() { - s := t.name() - if s[0] == '.' { - return s[1:] - } - return s - } - - switch t.Kind() { - case Chan: - elem := t.elem().String() - switch t.ChanDir() { - case SendDir: - return "chan<- " + elem - case RecvDir: - return "<-chan " + elem - case BothDir: - if elem[0] == '<' { - // typ is recv chan, need parentheses as "<-" associates with leftmost - // chan possible, see: - // * https://golang.org/ref/spec#Channel_types - // * https://github.com/golang/go/issues/39897 - return "chan (" + elem + ")" - } - return "chan " + elem - } - - case Pointer: - return "*" + t.elem().String() - case Slice: - return "[]" + t.elem().String() - case Array: - return "[" + itoa.Itoa(t.Len()) + "]" + t.elem().String() - case Map: - return "map[" + t.key().String() + "]" + t.elem().String() - case Struct: - numField := t.NumField() - if numField == 0 { - return "struct {}" - } - s := "struct {" - for i := 0; i < numField; i++ { - f := t.rawField(i) - s += " " + f.Name + " " + f.Type.String() - if f.Tag != "" { - s += " " + quote(string(f.Tag)) - } - // every field except the last needs a semicolon - if i < numField-1 { - s += ";" - } - } - s += " }" - return s - case Interface: - // TODO(dgryski): Needs actual method set info - return "interface {}" - default: - return t.Kind().String() - } +//go:linkname typeString internal/reflectlite.typeString +func typeString(t *rawType) string - return t.Kind().String() +func (t *rawType) String() string { + return typeString(t) } -func (t *rawType) Kind() Kind { - if t == nil { - return Invalid - } - - if tag := t.ptrtag(); tag != 0 { - return Pointer - } +//go:linkname typeKind internal/reflectlite.typeKind +func typeKind(t *rawType) Kind - return Kind(t.meta & kindMask) +func (t *rawType) Kind() Kind { + return typeKind(t) } var ( - errTypeElem = &TypeError{"Elem"} - errTypeKey = &TypeError{"Key"} errTypeField = &TypeError{"Field"} errTypeBits = &TypeError{"Bits"} errTypeLen = &TypeError{"Len"} errTypeNumField = &TypeError{"NumField"} - errTypeChanDir = &TypeError{"ChanDir"} errTypeFieldByName = &TypeError{"FieldByName"} errTypeFieldByIndex = &TypeError{"FieldByIndex"} ) @@ -644,28 +510,18 @@ func (t *rawType) Elem() Type { return t.elem() } -func (t *rawType) elem() *rawType { - if tag := t.ptrtag(); tag != 0 { - return (*rawType)(unsafe.Add(unsafe.Pointer(t), -1)) - } +//go:linkname typeElem internal/reflectlite.typeElem +func typeElem(t *rawType) *rawType - underlying := t.underlying() - switch underlying.Kind() { - case Pointer: - return (*ptrType)(unsafe.Pointer(underlying)).elem - case Chan, Slice, Array, Map: - return (*elemType)(unsafe.Pointer(underlying)).elem - default: - panic(errTypeElem) - } +func (t *rawType) elem() *rawType { + return typeElem(t) } +//go:linkname typeKey internal/reflectlite.typeKey +func typeKey(t *rawType) *rawType + func (t *rawType) key() *rawType { - underlying := t.underlying() - if underlying.Kind() != Map { - panic(errTypeKey) - } - return (*mapType)(unsafe.Pointer(underlying)).key + return typeKey(t) } // Field returns the type of the i'th field of this struct type. It panics if t @@ -683,68 +539,18 @@ func (t *rawType) Field(i int) StructField { } } -func rawStructFieldFromPointer(descriptor *structType, fieldType *rawType, data unsafe.Pointer, flagsByte uint8, name string, offset uint32) rawStructField { - // Read the field tag, if there is one. - var tag string - if flagsByte&structFieldFlagHasTag != 0 { - data = unsafe.Add(data, 1) // C: data+1 - tagLen := uintptr(*(*byte)(data)) - data = unsafe.Add(data, 1) // C: data+1 - tag = *(*string)(unsafe.Pointer(&stringHeader{ - data: data, - len: tagLen, - })) - } - - // Set the PkgPath to some (arbitrary) value if the package path is not - // exported. - pkgPath := "" - if flagsByte&structFieldFlagIsExported == 0 { - // This field is unexported. - pkgPath = readStringZ(unsafe.Pointer(descriptor.pkgpath)) - } +//go:linkname rawStructFieldFromPointer internal/reflectlite.rawStructFieldFromPointer +func rawStructFieldFromPointer(descriptor *structType, fieldType *rawType, data unsafe.Pointer, flagsByte uint8, name string, offset uint32) rawStructField - return rawStructField{ - Name: name, - PkgPath: pkgPath, - Type: fieldType, - Tag: StructTag(tag), - Anonymous: flagsByte&structFieldFlagAnonymous != 0, - Offset: uintptr(offset), - } -} +//go:linkname typeRawField internal/reflectlite.typeRawField +func typeRawField(t *rawType, n int) rawStructField // rawField returns nearly the same value as Field but without converting the // Type member to an interface. // // For internal use only. func (t *rawType) rawField(n int) rawStructField { - if t.Kind() != Struct { - panic(errTypeField) - } - descriptor := (*structType)(unsafe.Pointer(t.underlying())) - if uint(n) >= uint(descriptor.numField) { - panic("reflect: field index out of range") - } - - // Iterate over all the fields to calculate the offset. - // This offset could have been stored directly in the array (to make the - // lookup faster), but by calculating it on-the-fly a bit of storage can be - // saved. - field := (*structField)(unsafe.Add(unsafe.Pointer(&descriptor.fields[0]), uintptr(n)*unsafe.Sizeof(structField{}))) - data := field.data - - // Read some flags of this field, like whether the field is an embedded - // field. See structFieldFlagAnonymous and similar flags. - flagsByte := *(*byte)(data) - data = unsafe.Add(data, 1) - offset, lenOffs := uvarint32(unsafe.Slice((*byte)(data), maxVarintLen32)) - data = unsafe.Add(data, lenOffs) - - name := readStringZ(data) - data = unsafe.Add(data, len(name)) - - return rawStructFieldFromPointer(descriptor, field.fieldType, data, flagsByte, name, offset) + return typeRawField(t, n) } // rawFieldByNameFunc returns nearly the same value as FieldByNameFunc but without converting the @@ -871,49 +677,13 @@ func (t *rawType) NumField() int { return int((*structType)(unsafe.Pointer(t.underlying())).numField) } +//go:linkname typeSize internal/reflectlite.typeSize +func typeSize(t *rawType) uintptr + // Size returns the size in bytes of a given type. It is similar to // unsafe.Sizeof. func (t *rawType) Size() uintptr { - switch t.Kind() { - case Bool, Int8, Uint8: - return 1 - case Int16, Uint16: - return 2 - case Int32, Uint32: - return 4 - case Int64, Uint64: - return 8 - case Int, Uint: - return unsafe.Sizeof(int(0)) - case Uintptr: - return unsafe.Sizeof(uintptr(0)) - case Float32: - return 4 - case Float64: - return 8 - case Complex64: - return 8 - case Complex128: - return 16 - case String: - return unsafe.Sizeof("") - case UnsafePointer, Chan, Map, Pointer: - return unsafe.Sizeof(uintptr(0)) - case Slice: - return unsafe.Sizeof([]int{}) - case Interface: - return unsafe.Sizeof(interface{}(nil)) - case Func: - var f func() - return unsafe.Sizeof(f) - case Array: - return t.elem().Size() * uintptr(t.Len()) - case Struct: - u := t.underlying() - return uintptr((*structType)(unsafe.Pointer(u)).size) - default: - panic("unimplemented: size of type") - } + return typeSize(t) } // Align returns the alignment of this type. It is similar to calling @@ -994,25 +764,13 @@ func (t *rawType) FieldAlign() int { return t.Align() } +//go:linkname typeAssignableTo internal/reflectlite.typeAssignableTo +func typeAssignableTo(t, u *rawType) bool + // AssignableTo returns whether a value of type t can be assigned to a variable // of type u. func (t *rawType) AssignableTo(u Type) bool { - if t == u.(*rawType) { - return true - } - - if t.underlying() == u.(*rawType).underlying() && (!t.isNamed() || !u.(*rawType).isNamed()) { - return true - } - - if u.Kind() == Interface && u.NumMethod() == 0 { - return true - } - - if u.Kind() == Interface { - panic("reflect: unimplemented: AssignableTo with interface") - } - return false + return typeAssignableTo(t, u.(*rawType)) } func (t *rawType) Implements(u Type) bool { @@ -1032,15 +790,11 @@ func (t *rawType) isBinary() bool { return (t.meta & flagIsBinary) == flagIsBinary } -func (t *rawType) ChanDir() ChanDir { - if t.Kind() != Chan { - panic(errTypeChanDir) - } +//go:linkname typeChanDir internal/reflectlite.typeChanDir +func typeChanDir(t *rawType) ChanDir - dir := int((*elemType)(unsafe.Pointer(t)).numMethod) - - // nummethod is overloaded for channel to store channel direction - return ChanDir(dir) +func (t *rawType) ChanDir() ChanDir { + return typeChanDir(t) } func (t *rawType) ConvertibleTo(u Type) bool { @@ -1059,24 +813,11 @@ func (t *rawType) NumOut() int { panic("unimplemented: (reflect.Type).NumOut()") } -func (t *rawType) NumMethod() int { - - if t.isNamed() { - return int((*namedType)(unsafe.Pointer(t)).numMethod) - } +//go:linkname typeNumMethod internal/reflectlite.typeNumMethod +func typeNumMethod(t *rawType) int - switch t.Kind() { - case Pointer: - return int((*ptrType)(unsafe.Pointer(t)).numMethod) - case Struct: - return int((*structType)(unsafe.Pointer(t)).numMethod) - case Interface: - //FIXME: Use len(methods) - return (*interfaceType)(unsafe.Pointer(t)).ptrTo.NumMethod() - } - - // Other types have no methods attached. Note we don't panic here. - return 0 +func (t *rawType) NumMethod() int { + return typeNumMethod(t) } // Read and return a null terminated string starting from data. @@ -1304,69 +1045,7 @@ type rawStructField struct { Anonymous bool } -// A StructTag is the tag string in a struct field. -type StructTag string - -// TODO: it would be feasible to do the key/value splitting at compile time, -// avoiding the code size cost of doing it at runtime - -// Get returns the value associated with key in the tag string. -func (tag StructTag) Get(key string) string { - v, _ := tag.Lookup(key) - return v -} - -// Lookup returns the value associated with key in the tag string. -func (tag StructTag) Lookup(key string) (value string, ok bool) { - for tag != "" { - // Skip leading space. - i := 0 - for i < len(tag) && tag[i] == ' ' { - i++ - } - tag = tag[i:] - if tag == "" { - break - } - - // Scan to colon. A space, a quote or a control character is a syntax error. - // Strictly speaking, control chars include the range [0x7f, 0x9f], not just - // [0x00, 0x1f], but in practice, we ignore the multi-byte control characters - // as it is simpler to inspect the tag's bytes than the tag's runes. - i = 0 - for i < len(tag) && tag[i] > ' ' && tag[i] != ':' && tag[i] != '"' && tag[i] != 0x7f { - i++ - } - if i == 0 || i+1 >= len(tag) || tag[i] != ':' || tag[i+1] != '"' { - break - } - name := string(tag[:i]) - tag = tag[i+1:] - - // Scan quoted string to find value. - i = 1 - for i < len(tag) && tag[i] != '"' { - if tag[i] == '\\' { - i++ - } - i++ - } - if i >= len(tag) { - break - } - qvalue := string(tag[:i+1]) - tag = tag[i+1:] - - if key == name { - value, err := unquote(qvalue) - if err != nil { - break - } - return value, true - } - } - return "", false -} +type StructTag = reflectlite.StructTag // TypeError is the error that is used in a panic when invoking a method on a // type that is not applicable to that type. diff --git a/src/reflect/value.go b/src/reflect/value.go index ac47e05f03..3bc65eafa0 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -1,6 +1,7 @@ package reflect import ( + "internal/reflectlite" "math" "unsafe" ) @@ -11,6 +12,8 @@ type valueFlags uint8 // 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). +// +// These flags are shared with the internal/reflectlite package. const ( valueFlagIndirect valueFlags = 1 << iota valueFlagExported @@ -20,13 +23,6 @@ const ( valueFlagRO = valueFlagEmbedRO | valueFlagStickyRO ) -func (v valueFlags) ro() valueFlags { - if v&valueFlagRO != 0 { - return valueFlagStickyRO - } - return 0 -} - type Value struct { typecode *rawType value unsafe.Pointer @@ -86,33 +82,17 @@ func (v Value) Interface() interface{} { return valueInterfaceUnsafe(v) } -// valueInterfaceUnsafe is used by the runtime to hash map keys. It should not -// be subject to the isExported check. -func valueInterfaceUnsafe(v Value) interface{} { - if v.typecode.Kind() == Interface { - // The value itself is an interface. This can happen when getting the - // value of a struct field of interface type, like this: - // type T struct { - // X interface{} - // } - return *(*interface{})(v.value) - } - if v.isIndirect() && v.typecode.Size() <= unsafe.Sizeof(uintptr(0)) { - // Value was indirect but must be put back directly in the interface - // value. - var value uintptr - for j := v.typecode.Size(); j != 0; j-- { - value = (value << 8) | uintptr(*(*uint8)(unsafe.Add(v.value, j-1))) - } - v.value = unsafe.Pointer(value) - } - return composeInterface(unsafe.Pointer(v.typecode), v.value) -} +//go:linkname valueInterfaceUnsafe internal/reflectlite.valueInterfaceUnsafe +func valueInterfaceUnsafe(v Value) interface{} func (v Value) Type() Type { return v.typecode } +// Implemented in the reflectlite package, to cast Value to reflectlite.Value as +// a no-op. +func lite(v Value) reflectlite.Value + // IsZero reports whether v is the zero value for its type. // It panics if the argument is invalid. func (v Value) IsZero() bool { @@ -167,27 +147,7 @@ func (v Value) Kind() Kind { // IsNil returns whether the value is the nil value. It panics if the value Kind // is not a channel, map, pointer, function, slice, or interface. func (v Value) IsNil() bool { - switch v.Kind() { - case Chan, Map, Ptr, UnsafePointer: - return v.pointer() == 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 == nil - case Interface: - val := *(*interface{})(v.value) - return val == nil - default: - panic(&ValueError{Method: "IsNil", Kind: v.Kind()}) - } + return lite(v).IsNil() } // Pointer returns the underlying pointer of the given value for the following @@ -199,21 +159,7 @@ func (v Value) Pointer() uintptr { // UnsafePointer returns the underlying pointer of the given value for the // following types: chan, map, pointer, unsafe.Pointer, slice, func. func (v Value) UnsafePointer() unsafe.Pointer { - switch v.Kind() { - case Chan, Map, Ptr, UnsafePointer: - return v.pointer() - case Slice: - slice := (*sliceHeader)(v.value) - return slice.data - case Func: - fn := (*funcHeader)(v.value) - if fn.Context != nil { - return fn.Context - } - return fn.Code - default: - panic(&ValueError{Method: "UnsafePointer", Kind: v.Kind()}) - } + return lite(v).UnsafePointer() } // pointer returns the underlying pointer represented by v. @@ -372,19 +318,10 @@ func (v Value) CanSet() bool { } func (v Value) Bool() bool { - switch v.Kind() { - case Bool: - if v.isIndirect() { - return *((*bool)(v.value)) - } else { - return uintptr(v.value) != 0 - } - default: - panic(&ValueError{Method: "Bool", Kind: v.Kind()}) - } + return lite(v).Bool() } -// CanInt reports whether Uint can be used without panicking. +// CanInt reports whether Int can be used without panicking. func (v Value) CanInt() bool { switch v.Kind() { case Int, Int8, Int16, Int32, Int64: @@ -395,40 +332,7 @@ func (v Value) CanInt() bool { } func (v Value) Int() int64 { - switch v.Kind() { - case Int: - 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.isIndirect() { - return int64(*(*int8)(v.value)) - } else { - return int64(int8(uintptr(v.value))) - } - case Int16: - if v.isIndirect() { - return int64(*(*int16)(v.value)) - } else { - return int64(int16(uintptr(v.value))) - } - case Int32: - 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.isIndirect() || unsafe.Sizeof(int64(0)) > unsafe.Sizeof(uintptr(0)) { - return int64(*(*int64)(v.value)) - } else { - return int64(int64(uintptr(v.value))) - } - default: - panic(&ValueError{Method: "Int", Kind: v.Kind()}) - } + return lite(v).Int() } // CanUint reports whether Uint can be used without panicking. @@ -442,46 +346,7 @@ func (v Value) CanUint() bool { } func (v Value) Uint() uint64 { - switch v.Kind() { - case Uintptr: - if v.isIndirect() { - return uint64(*(*uintptr)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint8: - if v.isIndirect() { - return uint64(*(*uint8)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint16: - if v.isIndirect() { - return uint64(*(*uint16)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint: - 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.isIndirect() || unsafe.Sizeof(uint32(0)) > unsafe.Sizeof(uintptr(0)) { - return uint64(*(*uint32)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - case Uint64: - if v.isIndirect() || unsafe.Sizeof(uint64(0)) > unsafe.Sizeof(uintptr(0)) { - return uint64(*(*uint64)(v.value)) - } else { - return uint64(uintptr(v.value)) - } - default: - panic(&ValueError{Method: "Uint", Kind: v.Kind()}) - } + return lite(v).Uint() } // CanFloat reports whether Float can be used without panicking. @@ -516,29 +381,7 @@ func (v Value) Float32() float32 { } func (v Value) Float() float64 { - switch v.Kind() { - case Float32: - 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)) - } else { - // The float is directly stored in the interface value on systems - // with 32-bit and 64-bit pointers. - return float64(*(*float32)(unsafe.Pointer(&v.value))) - } - case Float64: - 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 { - // The float is directly stored in the interface value on systems - // with 64-bit pointers. - return *(*float64)(unsafe.Pointer(&v.value)) - } - default: - panic(&ValueError{Method: "Float", Kind: v.Kind()}) - } + return lite(v).Float() } // CanComplex reports whether Complex can be used without panicking. @@ -552,37 +395,11 @@ func (v Value) CanComplex() bool { } func (v Value) Complex() complex128 { - switch v.Kind() { - case Complex64: - 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)) - } else { - // The complex number is directly stored in the interface value on - // systems with 64-bit pointers. - return complex128(*(*complex64)(unsafe.Pointer(&v.value))) - } - case Complex128: - // This is a 128-bit value, which is always stored as an external value. - // It may be stored in the pointer directly on very uncommon - // architectures with 128-bit pointers, however. - return *(*complex128)(v.value) - default: - panic(&ValueError{Method: "Complex", Kind: v.Kind()}) - } + return lite(v).Complex() } func (v Value) String() string { - switch v.Kind() { - case String: - // A string value is always bigger than a pointer as it is made of a - // pointer and a length. - return *(*string)(v.value) - default: - // Special case because of the special treatment of .String() in Go. - return "<" + v.typecode.String() + " Value>" - } + return lite(v).String() } func (v Value) Bytes() []byte { @@ -733,20 +550,7 @@ func chanlen(p unsafe.Pointer) int // Len returns the length of this value for slices, strings, arrays, channels, // and maps. For other types, it panics. func (v Value) Len() int { - switch v.typecode.Kind() { - case Array: - return v.typecode.Len() - case Chan: - return chanlen(v.pointer()) - case Map: - return maplen(v.pointer()) - case Slice: - return int((*sliceHeader)(v.value).len) - case String: - return int((*stringHeader)(v.value).len) - default: - panic(&ValueError{Method: "Len", Kind: v.Kind()}) - } + return lite(v).Len() } //go:linkname chancap runtime.chanCap @@ -792,196 +596,26 @@ func (v Value) NumField() int { return v.typecode.NumField() } +//go:linkname valueElem internal/reflectlite.valueElem +func valueElem(v Value) Value + func (v Value) Elem() Value { - switch v.Kind() { - case Ptr: - ptr := v.pointer() - if ptr == nil { - return Value{} - } - // Don't copy RO flags - flags := (v.flags & (valueFlagIndirect | valueFlagExported)) | valueFlagIndirect - return Value{ - typecode: v.typecode.elem(), - value: ptr, - flags: flags, - } - case Interface: - typecode, value := decomposeInterface(*(*interface{})(v.value)) - return Value{ - typecode: (*rawType)(typecode), - value: value, - flags: v.flags &^ valueFlagIndirect, - } - default: - panic(&ValueError{Method: "Elem", Kind: v.Kind()}) - } + return valueElem(v) } +//go:linkname valueField internal/reflectlite.valueField +func valueField(v Value, i int) Value + // Field returns the value of the i'th field of this struct. func (v Value) Field(i int) Value { - if v.Kind() != Struct { - panic(&ValueError{Method: "Field", Kind: v.Kind()}) - } - structField := v.typecode.rawField(i) - - // Copy flags but clear EmbedRO; we're not an embedded field anymore - flags := v.flags & ^valueFlagEmbedRO - if structField.PkgPath != "" { - // No PkgPath => not exported. - // Clear exported flag even if the parent was exported. - flags &^= valueFlagExported - - // Update the RO flag - if structField.Anonymous { - // Embedded field - flags |= valueFlagEmbedRO - } else { - flags |= valueFlagStickyRO - } - } else { - // Parent field may not have been exported but we are - flags |= valueFlagExported - } - - size := v.typecode.Size() - fieldType := structField.Type - fieldSize := fieldType.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: fieldType, - value: unsafe.Add(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: fieldType, - value: unsafe.Pointer(nil), - } - } - - 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.Add(v.value, structField.Offset) - value := unsafe.Pointer(loadValue(ptr, fieldSize)) - return Value{ - flags: flags &^ valueFlagIndirect, - typecode: fieldType, - 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. - value := maskAndShift(uintptr(v.value), structField.Offset, fieldSize) - return Value{ - flags: flags, - typecode: fieldType, - value: unsafe.Pointer(value), - } + return valueField(v, i) } -var uint8Type = TypeOf(uint8(0)).(*rawType) +//go:linkname valueIndex internal/reflectlite.valueIndex +func valueIndex(v Value, i int) Value func (v Value) Index(i int) Value { - switch v.Kind() { - case Slice: - // Extract an element from the slice. - slice := *(*sliceHeader)(v.value) - if uint(i) >= uint(slice.len) { - panic("reflect: slice index out of range") - } - flags := (v.flags & (valueFlagExported | valueFlagIndirect)) | valueFlagIndirect | v.flags.ro() - elem := Value{ - typecode: v.typecode.elem(), - flags: flags, - } - elem.value = unsafe.Add(slice.data, elem.typecode.Size()*uintptr(i)) // pointer to new value - return elem - case String: - // Extract a character from a string. - // A string is never stored directly in the interface, but always as a - // pointer to the string value. - // Keeping valueFlagExported if set, but don't set valueFlagIndirect - // otherwise CanSet will return true for string elements (which is bad, - // strings are read-only). - s := *(*stringHeader)(v.value) - if uint(i) >= uint(s.len) { - panic("reflect: string index out of range") - } - return Value{ - typecode: uint8Type, - value: unsafe.Pointer(uintptr(*(*uint8)(unsafe.Add(s.data, i)))), - flags: v.flags & valueFlagExported, - } - case Array: - // Extract an element from the array. - elemType := v.typecode.elem() - elemSize := elemType.Size() - size := v.typecode.Size() - if size == 0 { - // The element size is 0 and/or the length of the array is 0. - return Value{ - typecode: v.typecode.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 := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value - return Value{ - typecode: v.typecode.elem(), - flags: v.flags, - value: addr, - } - } - - if size > unsafe.Sizeof(uintptr(0)) || v.isIndirect() { - // The element fits in a pointer, but the array is not stored in the pointer directly. - // Load the value from the pointer. - addr := unsafe.Add(v.value, elemSize*uintptr(i)) // pointer to new value - value := addr - if !v.isIndirect() { - // Use a pointer to the value (don't load the value) if the - // 'indirect' flag is set. - value = unsafe.Pointer(loadValue(addr, elemSize)) - } - return Value{ - typecode: v.typecode.elem(), - flags: v.flags, - value: value, - } - } - - // 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.typecode.elem(), - flags: v.flags, - value: unsafe.Pointer(value), - } - default: - panic(&ValueError{Method: "Index", Kind: v.Kind()}) - } + return valueIndex(v, i) } func (v Value) NumMethod() int { @@ -1159,31 +793,7 @@ func (it *MapIter) Next() bool { } func (v Value) Set(x Value) { - v.checkAddressable() - v.checkRO() - if !x.typecode.AssignableTo(v.typecode) { - panic("reflect.Value.Set: value of type " + x.typecode.String() + " cannot be assigned to type " + v.typecode.String()) - } - - if v.typecode.Kind() == Interface && x.typecode.Kind() != Interface { - // move the value of x back into the interface, if possible - if x.isIndirect() && x.typecode.Size() <= unsafe.Sizeof(uintptr(0)) { - x.value = unsafe.Pointer(loadValue(x.value, x.typecode.Size())) - } - - intf := composeInterface(unsafe.Pointer(x.typecode), x.value) - x = Value{ - typecode: v.typecode, - value: unsafe.Pointer(&intf), - } - } - - size := v.typecode.Size() - if size <= unsafe.Sizeof(uintptr(0)) && !x.isIndirect() { - storeValue(v.value, size, uintptr(x.value)) - } else { - memcpy(v.value, x.value, size) - } + lite(v).Set(lite(x)) } func (v Value) SetZero() { @@ -1722,17 +1332,7 @@ var ( _ [unsafe.Sizeof("")]byte = [unsafe.Sizeof(stringHeader{})]byte{} ) -type ValueError struct { - Method string - Kind Kind -} - -func (e *ValueError) Error() string { - if e.Kind == 0 { - return "reflect: call of " + e.Method + " on zero Value" - } - return "reflect: call of " + e.Method + " on " + e.Kind.String() + " Value" -} +type ValueError = reflectlite.ValueError //go:linkname memcpy runtime.memcpy func memcpy(dst, src unsafe.Pointer, size uintptr) diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index ad42fda753..e4e5bb68b9 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -539,7 +539,7 @@ func hashmapStringDelete(m *hashmap, key string) { // a field is exported and thus allows circumventing the type system. // The hash function needs it as it also needs to hash unexported struct fields. // -//go:linkname valueInterfaceUnsafe reflect.valueInterfaceUnsafe +//go:linkname valueInterfaceUnsafe internal/reflectlite.valueInterfaceUnsafe func valueInterfaceUnsafe(v reflect.Value) interface{} func hashmapFloat32Hash(ptr unsafe.Pointer, seed uintptr) uint32 { diff --git a/transform/rtcalls.go b/transform/rtcalls.go index 8310fc9f19..3abc1d3952 100644 --- a/transform/rtcalls.go +++ b/transform/rtcalls.go @@ -117,8 +117,9 @@ func OptimizeStringEqual(mod llvm.Module) { // As of this writing, the (reflect.Type).Interface method has not yet been // implemented so this optimization is critical for the encoding/json package. func OptimizeReflectImplements(mod llvm.Module) { - implementsSignature := mod.NamedGlobal("reflect/methods.Implements(reflect.Type) bool") - if implementsSignature.IsNil() { + implementsSignature1 := mod.NamedGlobal("reflect/methods.Implements(reflect.Type) bool") + implementsSignature2 := mod.NamedGlobal("reflect/methods.Implements(internal/reflectlite.Type) bool") + if implementsSignature1.IsNil() && implementsSignature2.IsNil() { return } @@ -132,7 +133,8 @@ func OptimizeReflectImplements(mod llvm.Module) { if attr.IsNil() { continue } - if attr.GetStringValue() == "reflect/methods.Implements(reflect.Type) bool" { + val := attr.GetStringValue() + if val == "reflect/methods.Implements(reflect.Type) bool" || val == "reflect/methods.Implements(internal/reflectlite.Type) bool" { implementsFunc = fn break } From 8bff0653c4d6ddfa8b088d52be0474ac76dcfa5f Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Thu, 13 Mar 2025 10:12:48 +0100 Subject: [PATCH 2/2] runtime: use internal/reflectlite instead of reflect This avoids a circular dependency. --- src/reflect/value.go | 8 -------- src/runtime/hashmap.go | 32 ++++++++++++++++---------------- src/runtime/interface.go | 5 +++-- 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/reflect/value.go b/src/reflect/value.go index 3bc65eafa0..f95d7e96e7 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -132,14 +132,6 @@ func (v Value) IsZero() bool { } } -// Internal function only, do not use. -// -// RawType returns the raw, underlying type code. It is used in the runtime -// package and needs to be exported for the runtime package to access it. -func (v Value) RawType() *rawType { - return v.typecode -} - func (v Value) Kind() Kind { return v.typecode.Kind() } diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index e4e5bb68b9..ddb893109f 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -6,7 +6,7 @@ package runtime // https://golang.org/src/runtime/map.go import ( - "reflect" + "internal/reflectlite" "tinygo" "unsafe" ) @@ -534,13 +534,13 @@ func hashmapStringDelete(m *hashmap, key string) { // Hashmap with interface keys (for everything else). -// This is a method that is intentionally unexported in the reflect package. It -// is identical to the Interface() method call, except it doesn't check whether -// a field is exported and thus allows circumventing the type system. +// This is a method that is intentionally unexported in the reflectlite package. +// It is identical to the Interface() method call, except it doesn't check +// whether a field is exported and thus allows circumventing the type system. // The hash function needs it as it also needs to hash unexported struct fields. // //go:linkname valueInterfaceUnsafe internal/reflectlite.valueInterfaceUnsafe -func valueInterfaceUnsafe(v reflect.Value) interface{} +func valueInterfaceUnsafe(v reflectlite.Value) interface{} func hashmapFloat32Hash(ptr unsafe.Pointer, seed uintptr) uint32 { f := *(*uint32)(ptr) @@ -561,7 +561,7 @@ func hashmapFloat64Hash(ptr unsafe.Pointer, seed uintptr) uint32 { } func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 { - x := reflect.ValueOf(itf) + x := reflectlite.ValueOf(itf) if x.RawType() == nil { return 0 // nil interface } @@ -574,39 +574,39 @@ func hashmapInterfaceHash(itf interface{}, seed uintptr) uint32 { } switch x.RawType().Kind() { - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + case reflectlite.Int, reflectlite.Int8, reflectlite.Int16, reflectlite.Int32, reflectlite.Int64: return hash32(ptr, x.RawType().Size(), seed) - case reflect.Bool, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + case reflectlite.Bool, reflectlite.Uint, reflectlite.Uint8, reflectlite.Uint16, reflectlite.Uint32, reflectlite.Uint64, reflectlite.Uintptr: return hash32(ptr, x.RawType().Size(), seed) - case reflect.Float32: + case reflectlite.Float32: // It should be possible to just has the contents. However, NaN != NaN // so if you're using lots of NaNs as map keys (you shouldn't) then hash // time may become exponential. To fix that, it would be better to // return a random number instead: // https://research.swtch.com/randhash return hashmapFloat32Hash(ptr, seed) - case reflect.Float64: + case reflectlite.Float64: return hashmapFloat64Hash(ptr, seed) - case reflect.Complex64: + case reflectlite.Complex64: rptr, iptr := ptr, unsafe.Add(ptr, 4) return hashmapFloat32Hash(rptr, seed) ^ hashmapFloat32Hash(iptr, seed) - case reflect.Complex128: + case reflectlite.Complex128: rptr, iptr := ptr, unsafe.Add(ptr, 8) return hashmapFloat64Hash(rptr, seed) ^ hashmapFloat64Hash(iptr, seed) - case reflect.String: + case reflectlite.String: return hashmapStringHash(x.String(), seed) - case reflect.Chan, reflect.Ptr, reflect.UnsafePointer: + case reflectlite.Chan, reflectlite.Ptr, reflectlite.UnsafePointer: // It might seem better to just return the pointer, but that won't // result in an evenly distributed hashmap. Instead, hash the pointer // like most other types. return hash32(ptr, x.RawType().Size(), seed) - case reflect.Array: + case reflectlite.Array: var hash uint32 for i := 0; i < x.Len(); i++ { hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Index(i)), seed) } return hash - case reflect.Struct: + case reflectlite.Struct: var hash uint32 for i := 0; i < x.NumField(); i++ { hash ^= hashmapInterfaceHash(valueInterfaceUnsafe(x.Field(i)), seed) diff --git a/src/runtime/interface.go b/src/runtime/interface.go index b9813225f2..5c775df1c3 100644 --- a/src/runtime/interface.go +++ b/src/runtime/interface.go @@ -6,6 +6,7 @@ package runtime // anything (including non-pointers). import ( + "internal/reflectlite" "reflect" "unsafe" ) @@ -27,10 +28,10 @@ func decomposeInterface(i _interface) (unsafe.Pointer, unsafe.Pointer) { // Return true iff both interfaces are equal. func interfaceEqual(x, y interface{}) bool { - return reflectValueEqual(reflect.ValueOf(x), reflect.ValueOf(y)) + return reflectValueEqual(reflectlite.ValueOf(x), reflectlite.ValueOf(y)) } -func reflectValueEqual(x, y reflect.Value) bool { +func reflectValueEqual(x, y reflectlite.Value) bool { // Note: doing a x.Type() == y.Type() comparison would not work here as that // would introduce an infinite recursion: comparing two reflect.Type values // is done with this reflectValueEqual runtime call.