]> Cypherpunks repositories - gostls13.git/commitdiff
internal/reflectlite: allow Value be stack allocated
authorCherry Mui <cherryyz@google.com>
Thu, 16 Nov 2023 06:00:22 +0000 (01:00 -0500)
committerCherry Mui <cherryyz@google.com>
Thu, 16 Nov 2023 19:26:08 +0000 (19:26 +0000)
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>
src/internal/reflectlite/all_test.go
src/internal/reflectlite/export_test.go
src/internal/reflectlite/type.go
src/internal/reflectlite/value.go

index 820b4aeaf80d296877c54a52a8999a226f96c94a..a78f9ae70f9e3eacefd6d247c870dd30144ea982 100644 (file)
@@ -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)
index 88be6e27230ddf1c184fb998afdad424f487bdc0..ea937b8db758d9ce9d4e0d3ebd945113e2e954a0 100644 (file)
@@ -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")
        }
index f13ce8fc6222c6849570fb645db54baec944772a..e585d24f538c11877a49406a58e42c8f1a235bef 100644 (file)
@@ -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 {
@@ -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 {
index eb798948420321b85f65bdae4d2cf5ec1091c1f0..c47e5ea12b3ced5df11264b27c5f4dd1b98ba9fd 100644 (file)
@@ -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.
@@ -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 {
@@ -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.
@@ -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 {
@@ -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())
@@ -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.
@@ -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())
@@ -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.
@@ -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
        }
@@ -380,7 +394,7 @@ func (v Value) Type() Type {
                panic(&ValueError{"reflectlite.Value.Type", abi.Invalid})
        }
        // Method values not supported.
-       return toRType(v.typ)
+       return toRType(v.typ())
 }
 
 /*
@@ -388,6 +402,8 @@ func (v Value) Type() Type {
  */
 
 // implemented in package runtime
+
+//go:noescape
 func unsafe_New(*abi.Type) unsafe.Pointer
 
 // ValueOf returns a new Value initialized to the concrete value
@@ -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)
 }
 
@@ -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)
                }
@@ -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,
@@ -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)
+}