]> Cypherpunks.ru repositories - gostls13.git/commitdiff
reflect: allow Value be stack allocated
authorCherry Mui <cherryyz@google.com>
Fri, 27 May 2022 19:44:40 +0000 (15:44 -0400)
committerCherry Mui <cherryyz@google.com>
Fri, 12 May 2023 21:11:33 +0000 (21:11 +0000)
Currently, reflect.ValueOf forces the referenced object to be heap
allocated. This CL makes it possible to be stack allocated. We
need to be careful to make sure the compiler's escape analysis can
do the right thing, e.g. channel send, map assignment, unsafe
pointer conversions.

Tests will be added in a later CL.

CL 408827 might help ensure the correctness.

Change-Id: I8663651370c7c8108584902235062dd2b3f65954
Reviewed-on: https://go-review.googlesource.com/c/go/+/408826
Run-TryBot: Cherry Mui <cherryyz@google.com>
Reviewed-by: David Chase <drchase@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Austin Clements <austin@google.com>
src/reflect/value.go
src/runtime/chan.go
src/runtime/map.go

index f079b8228bd68577a22ec79e3240d5e341af77f6..60556e6349932da996da8ba117141720099f0aba 100644 (file)
@@ -1521,6 +1521,8 @@ func valueInterface(v Value, safe bool) any {
 // compatible with InterfaceData.
 func (v Value) InterfaceData() [2]uintptr {
        v.mustBe(Interface)
+       // The compiler loses track as it converts to uintptr. Force escape.
+       escapes(v.ptr)
        // We treat this as a read operation, so we allow
        // it even for unexported data, because the caller
        // has to import "unsafe" to turn it into something
@@ -2121,6 +2123,9 @@ func (v Value) OverflowUint(x uint64) bool {
 //
 // It's preferred to use uintptr(Value.UnsafePointer()) to get the equivalent result.
 func (v Value) Pointer() uintptr {
+       // The compiler loses track as it converts to uintptr. Force escape.
+       escapes(v.ptr)
+
        k := v.kind()
        switch k {
        case Pointer:
@@ -2682,6 +2687,8 @@ func (v Value) UnsafeAddr() uintptr {
        if v.flag&flagAddr == 0 {
                panic("reflect.Value.UnsafeAddr of unaddressable value")
        }
+       // The compiler loses track as it converts to uintptr. Force escape.
+       escapes(v.ptr)
        return uintptr(v.ptr)
 }
 
@@ -2939,6 +2946,11 @@ type runtimeSelect struct {
 // The conventional OK bool indicates whether the receive corresponds
 // to a sent value.
 //
+// rselect generally doesn't escape the runtimeSelect slice, except
+// that for the send case the value to send needs to escape. We don't
+// have a way to represent that in the function signature. So we handle
+// that with a forced escape in function Select.
+//
 //go:noescape
 func rselect([]runtimeSelect) (chosen int, recvOK bool)
 
@@ -3044,6 +3056,9 @@ func Select(cases []SelectCase) (chosen int, recv Value, recvOK bool) {
                        } else {
                                rc.val = unsafe.Pointer(&v.ptr)
                        }
+                       // The value to send needs to escape. See the comment at rselect for
+                       // why we need forced escape.
+                       escapes(rc.val)
 
                case SelectRecv:
                        if c.Send.IsValid() {
@@ -3150,6 +3165,14 @@ func Indirect(v Value) Value {
        return v.Elem()
 }
 
+// Before Go 1.21, ValueOf always escapes and a Value's content
+// is always heap allocated.
+// Set go121noForceValueEscape to true to avoid the forced escape,
+// allowing Value content to be on the stack.
+// Set go121noForceValueEscape to false for the legacy behavior
+// (for debugging).
+const go121noForceValueEscape = true
+
 // ValueOf returns a new Value initialized to the concrete value
 // stored in the interface i. ValueOf(nil) returns the zero Value.
 func ValueOf(i any) Value {
@@ -3157,11 +3180,9 @@ func ValueOf(i any) Value {
                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)
+       if !go121noForceValueEscape {
+               escapes(i)
+       }
 
        return unpackEface(i)
 }
@@ -3736,23 +3757,33 @@ func cvtI2I(v Value, typ Type) Value {
 }
 
 // implemented in ../runtime
+//
+//go:noescape
 func chancap(ch unsafe.Pointer) int
+
+//go:noescape
 func chanclose(ch unsafe.Pointer)
+
+//go:noescape
 func chanlen(ch unsafe.Pointer) int
 
 // Note: some of the noescape annotations below are technically a lie,
-// but safe in the context of this package. Functions like chansend
-// and mapassign don't escape the referent, but may escape anything
+// but safe in the context of this package. Functions like chansend0
+// and mapassign0 don't escape the referent, but may escape anything
 // the referent points to (they do shallow copies of the referent).
-// It is safe in this package because the referent may only point
-// to something a Value may point to, and that is always in the heap
-// (due to the escapes() call in ValueOf).
+// We add a 0 to their names and wrap them in functions with the
+// proper escape behavior.
 
 //go:noescape
 func chanrecv(ch unsafe.Pointer, nb bool, val unsafe.Pointer) (selected, received bool)
 
 //go:noescape
-func chansend(ch unsafe.Pointer, val unsafe.Pointer, nb bool) bool
+func chansend0(ch unsafe.Pointer, val unsafe.Pointer, nb bool) bool
+
+func chansend(ch unsafe.Pointer, val unsafe.Pointer, nb bool) bool {
+       contentEscapes(val)
+       return chansend0(ch, val, nb)
+}
 
 func makechan(typ *abi.Type, size int) (ch unsafe.Pointer)
 func makemap(t *abi.Type, cap int) (m unsafe.Pointer)
@@ -3764,10 +3795,22 @@ func mapaccess(t *abi.Type, m unsafe.Pointer, key unsafe.Pointer) (val unsafe.Po
 func mapaccess_faststr(t *abi.Type, m unsafe.Pointer, key string) (val unsafe.Pointer)
 
 //go:noescape
-func mapassign(t *abi.Type, m unsafe.Pointer, key, val unsafe.Pointer)
+func mapassign0(t *abi.Type, m unsafe.Pointer, key, val unsafe.Pointer)
+
+func mapassign(t *abi.Type, m unsafe.Pointer, key, val unsafe.Pointer) {
+       contentEscapes(key)
+       contentEscapes(val)
+       mapassign0(t, m, key, val)
+}
 
 //go:noescape
-func mapassign_faststr(t *abi.Type, m unsafe.Pointer, key string, val unsafe.Pointer)
+func mapassign_faststr0(t *abi.Type, m unsafe.Pointer, key string, val unsafe.Pointer)
+
+func mapassign_faststr(t *abi.Type, m unsafe.Pointer, key string, val unsafe.Pointer) {
+       contentEscapes((*unsafeheader.String)(unsafe.Pointer(&key)).Data)
+       contentEscapes(val)
+       mapassign_faststr0(t, m, key, val)
+}
 
 //go:noescape
 func mapdelete(t *abi.Type, m unsafe.Pointer, key unsafe.Pointer)
@@ -3876,3 +3919,13 @@ var dummy struct {
        b bool
        x any
 }
+
+// Dummy annotation marking that the content of value x
+// escapes (i.e. modeling roughly heap=*x),
+// for use in cases where the reflect code is so clever that
+// the compiler cannot follow.
+func contentEscapes(x unsafe.Pointer) {
+       if dummy.b {
+               escapes(*(*any)(x)) // the dereference may not always be safe, but never executed
+       }
+}
index 98e08366701ce4de9e01d2b87714d26e00a763c5..aff4cf87b723d18df8cc2af81fae2897ff785f1a 100644 (file)
@@ -714,7 +714,7 @@ func selectnbrecv(elem unsafe.Pointer, c *hchan) (selected, received bool) {
        return chanrecv(c, elem, false)
 }
 
-//go:linkname reflect_chansend reflect.chansend
+//go:linkname reflect_chansend reflect.chansend0
 func reflect_chansend(c *hchan, elem unsafe.Pointer, nb bool) (selected bool) {
        return chansend(c, elem, !nb, getcallerpc())
 }
index 7488945926f4c5e76b06186c1cde963826ff1946..a1fe08f7581b3d0b277a64a09a8de9324da4fa0d 100644 (file)
@@ -1365,13 +1365,13 @@ func reflect_mapaccess_faststr(t *maptype, h *hmap, key string) unsafe.Pointer {
        return elem
 }
 
-//go:linkname reflect_mapassign reflect.mapassign
+//go:linkname reflect_mapassign reflect.mapassign0
 func reflect_mapassign(t *maptype, h *hmap, key unsafe.Pointer, elem unsafe.Pointer) {
        p := mapassign(t, h, key)
        typedmemmove(t.Elem, p, elem)
 }
 
-//go:linkname reflect_mapassign_faststr reflect.mapassign_faststr
+//go:linkname reflect_mapassign_faststr reflect.mapassign_faststr0
 func reflect_mapassign_faststr(t *maptype, h *hmap, key string, elem unsafe.Pointer) {
        p := mapassign_faststr(t, h, key)
        typedmemmove(t.Elem, p, elem)