From: Cherry Mui Date: Fri, 27 May 2022 19:44:40 +0000 (-0400) Subject: reflect: allow Value be stack allocated X-Git-Tag: go1.21rc1~535 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=0ac72f8b96166c8aa3953d27f4fd3d33fb9e51cf;p=gostls13.git reflect: allow Value be stack allocated 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 Reviewed-by: David Chase TryBot-Result: Gopher Robot Reviewed-by: Austin Clements --- diff --git a/src/reflect/value.go b/src/reflect/value.go index f079b8228b..60556e6349 100644 --- a/src/reflect/value.go +++ b/src/reflect/value.go @@ -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 + } +} diff --git a/src/runtime/chan.go b/src/runtime/chan.go index 98e0836670..aff4cf87b7 100644 --- a/src/runtime/chan.go +++ b/src/runtime/chan.go @@ -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()) } diff --git a/src/runtime/map.go b/src/runtime/map.go index 7488945926..a1fe08f758 100644 --- a/src/runtime/map.go +++ b/src/runtime/map.go @@ -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)