Skip to content

Commit

Permalink
internal/reflectlite: allow Value be stack allocated
Browse files Browse the repository at this point in the history
Port CL 408826 and CL 413474 from reflect to internal/reflectlite.
It is a bit simpler as reflectlite has fewer methods.

Change-Id: I479199c8984afd35f42c3d8e764340184c17948f
Reviewed-on: https://go-review.googlesource.com/c/go/+/542976
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
  • Loading branch information
cherrymui committed Nov 16, 2023
1 parent 0434ca9 commit d573a8a
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 33 deletions.
18 changes: 9 additions & 9 deletions src/internal/reflectlite/all_test.go
Expand Up @@ -809,15 +809,15 @@ func TestAllocations(t *testing.T) {
var i any
var v Value

// We can uncomment this when compiler escape analysis
// is good enough to see that the integer assigned to i
// does not escape and therefore need not be allocated.
//
// i = 42 + j
// v = ValueOf(i)
// if int(v.Int()) != 42+j {
// panic("wrong int")
// }
i = []int{j, j, j}
v = ValueOf(i)
if v.Len() != 3 {
panic("wrong length")
}
})
noAlloc(t, 100, func(j int) {
var i any
var v Value

i = func(j int) int { return j }
v = ValueOf(i)
Expand Down
2 changes: 1 addition & 1 deletion src/internal/reflectlite/export_test.go
Expand Up @@ -14,7 +14,7 @@ func Field(v Value, i int) Value {
if v.kind() != Struct {
panic(&ValueError{"reflect.Value.Field", v.kind()})
}
tt := (*structType)(unsafe.Pointer(v.typ))
tt := (*structType)(unsafe.Pointer(v.typ()))
if uint(i) >= uint(len(tt.Fields)) {
panic("reflect: Field index out of range")
}
Expand Down
8 changes: 7 additions & 1 deletion src/internal/reflectlite/type.go
Expand Up @@ -233,11 +233,15 @@ func pkgPath(n abi.Name) string {
// resolveNameOff resolves a name offset from a base pointer.
// The (*rtype).nameOff method is a convenience wrapper for this function.
// Implemented in the runtime package.
//
//go:noescape
func resolveNameOff(ptrInModule unsafe.Pointer, off int32) unsafe.Pointer

// resolveTypeOff resolves an *rtype offset from a base type.
// The (*rtype).typeOff method is a convenience wrapper for this function.
// Implemented in the runtime package.
//
//go:noescape
func resolveTypeOff(rtype unsafe.Pointer, off int32) unsafe.Pointer

func (t rtype) nameOff(off nameOff) abi.Name {
Expand Down Expand Up @@ -395,7 +399,9 @@ func add(p unsafe.Pointer, x uintptr, whySafe string) unsafe.Pointer {
// If i is a nil interface value, TypeOf returns nil.
func TypeOf(i any) Type {
eface := *(*emptyInterface)(unsafe.Pointer(&i))
return toType(eface.typ)
// Noescape so this doesn't make i to escape. See the comment
// at Value.typ for why this is safe.
return toType((*abi.Type)(noescape(unsafe.Pointer(eface.typ))))
}

func (t rtype) Implements(u Type) bool {
Expand Down
59 changes: 37 additions & 22 deletions src/internal/reflectlite/value.go
Expand Up @@ -34,8 +34,9 @@ import (
// Using == on two Values does not compare the underlying values
// they represent.
type Value struct {
// typ holds the type of the value represented by a Value.
typ *abi.Type
// typ_ holds the type of the value represented by a Value.
// Access using the typ method to avoid escape of v.
typ_ *abi.Type

// Pointer-valued data or, if flagIndir is set, pointer to data.
// Valid when either flagIndir is set or typ.pointers() is true.
Expand Down Expand Up @@ -87,10 +88,19 @@ func (f flag) ro() flag {
return 0
}

func (v Value) typ() *abi.Type {
// Types are either static (for compiler-created types) or
// heap-allocated but always reachable (for reflection-created
// types, held in the central map). So there is no need to
// escape types. noescape here help avoid unnecessary escape
// of v.
return (*abi.Type)(noescape(unsafe.Pointer(v.typ_)))
}

// pointer returns the underlying pointer represented by v.
// v.Kind() must be Pointer, Map, Chan, Func, or UnsafePointer
func (v Value) pointer() unsafe.Pointer {
if v.typ.Size() != goarch.PtrSize || !v.typ.Pointers() {
if v.typ().Size() != goarch.PtrSize || !v.typ().Pointers() {
panic("can't call pointer on a non-pointer Value")
}
if v.flag&flagIndir != 0 {
Expand All @@ -101,7 +111,7 @@ func (v Value) pointer() unsafe.Pointer {

// packEface converts v to the empty interface.
func packEface(v Value) any {
t := v.typ
t := v.typ()
var i any
e := (*emptyInterface)(unsafe.Pointer(&i))
// First, fill in the data portion of the interface.
Expand Down Expand Up @@ -228,7 +238,7 @@ func (v Value) Elem() Value {
switch k {
case abi.Interface:
var eface any
if v.typ.NumMethod() == 0 {
if v.typ().NumMethod() == 0 {
eface = *(*any)(v.ptr)
} else {
eface = (any)(*(*interface {
Expand All @@ -249,7 +259,7 @@ func (v Value) Elem() Value {
if ptr == nil {
return Value{}
}
tt := (*ptrType)(unsafe.Pointer(v.typ))
tt := (*ptrType)(unsafe.Pointer(v.typ()))
typ := tt.Elem
fl := v.flag&flagRO | flagIndir | flagAddr
fl |= flag(typ.Kind())
Expand Down Expand Up @@ -322,7 +332,11 @@ func (v Value) Kind() Kind {
}

// implemented in runtime:

//go:noescape
func chanlen(unsafe.Pointer) int

//go:noescape
func maplen(unsafe.Pointer) int

// Len returns v's length.
Expand All @@ -331,7 +345,7 @@ func (v Value) Len() int {
k := v.kind()
switch k {
case abi.Array:
tt := (*arrayType)(unsafe.Pointer(v.typ))
tt := (*arrayType)(unsafe.Pointer(v.typ()))
return int(tt.Len)
case abi.Chan:
return chanlen(v.pointer())
Expand All @@ -349,10 +363,10 @@ func (v Value) Len() int {

// NumMethod returns the number of exported methods in the value's method set.
func (v Value) numMethod() int {
if v.typ == nil {
if v.typ() == nil {
panic(&ValueError{"reflectlite.Value.NumMethod", abi.Invalid})
}
return v.typ.NumMethod()
return v.typ().NumMethod()
}

// Set assigns x to the value v.
Expand All @@ -365,9 +379,9 @@ func (v Value) Set(x Value) {
if v.kind() == abi.Interface {
target = v.ptr
}
x = x.assignTo("reflectlite.Set", v.typ, target)
x = x.assignTo("reflectlite.Set", v.typ(), target)
if x.flag&flagIndir != 0 {
typedmemmove(v.typ, v.ptr, x.ptr)
typedmemmove(v.typ(), v.ptr, x.ptr)
} else {
*(*unsafe.Pointer)(v.ptr) = x.ptr
}
Expand All @@ -380,14 +394,16 @@ func (v Value) Type() Type {
panic(&ValueError{"reflectlite.Value.Type", abi.Invalid})
}
// Method values not supported.
return toRType(v.typ)
return toRType(v.typ())
}

/*
* constructors
*/

// implemented in package runtime

//go:noescape
func unsafe_New(*abi.Type) unsafe.Pointer

// ValueOf returns a new Value initialized to the concrete value
Expand All @@ -396,13 +412,6 @@ func ValueOf(i any) Value {
if i == nil {
return Value{}
}

// TODO: Maybe allow contents of a Value to live on the stack.
// For now we make the contents always escape to the heap. It
// makes life easier in a few places (see chanrecv/mapassign
// comment below).
escapes(i)

return unpackEface(i)
}

Expand All @@ -415,14 +424,14 @@ func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Va
// }

switch {
case directlyAssignable(dst, v.typ):
case directlyAssignable(dst, v.typ()):
// Overwrite type so that they match.
// Same memory layout, so no harm done.
fl := v.flag&(flagAddr|flagIndir) | v.flag.ro()
fl |= flag(dst.Kind())
return Value{dst, v.ptr, fl}

case implements(dst, v.typ):
case implements(dst, v.typ()):
if target == nil {
target = unsafe_New(dst)
}
Expand All @@ -442,7 +451,7 @@ func (v Value) assignTo(context string, dst *abi.Type, target unsafe.Pointer) Va
}

// Failed.
panic(context + ": value of type " + toRType(v.typ).String() + " is not assignable to type " + toRType(dst).String())
panic(context + ": value of type " + toRType(v.typ()).String() + " is not assignable to type " + toRType(dst).String())
}

// arrayAt returns the i-th element of p,
Expand Down Expand Up @@ -476,3 +485,9 @@ var dummy struct {
b bool
x any
}

//go:nosplit
func noescape(p unsafe.Pointer) unsafe.Pointer {
x := uintptr(p)
return unsafe.Pointer(x ^ 0)
}

0 comments on commit d573a8a

Please sign in to comment.