runtime: mark and identify tiny blocks in checkfinalizers mode

This change adds support for identifying cleanups and finalizers
attached to tiny blocks to checkfinalizers mode. It also notes a subtle
pitfall, which is that the cleanup arg, if tiny-allocated, could end up
co-located with the object with the cleanup attached! Oops...

For #72949.

Change-Id: Icbe0112f7dcfc63f35c66cf713216796a70121ce
Reviewed-on: https://go-review.googlesource.com/c/go/+/662037
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Auto-Submit: Michael Knyszek <mknyszek@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
This commit is contained in:
Michael Anthony Knyszek 2025-05-09 18:53:06 +00:00 committed by Gopher Robot
parent 913c069819
commit c58f58b9f8
6 changed files with 151 additions and 38 deletions

View file

@ -218,6 +218,7 @@ type mheap struct {
specialfinalizeralloc fixalloc // allocator for specialfinalizer*
specialCleanupAlloc fixalloc // allocator for specialCleanup*
specialCheckFinalizerAlloc fixalloc // allocator for specialCheckFinalizer*
specialTinyBlockAlloc fixalloc // allocator for specialTinyBlock*
specialprofilealloc fixalloc // allocator for specialprofile*
specialReachableAlloc fixalloc // allocator for specialReachable
specialPinCounterAlloc fixalloc // allocator for specialPinCounter
@ -793,6 +794,7 @@ func (h *mheap) init() {
h.specialfinalizeralloc.init(unsafe.Sizeof(specialfinalizer{}), nil, nil, &memstats.other_sys)
h.specialCleanupAlloc.init(unsafe.Sizeof(specialCleanup{}), nil, nil, &memstats.other_sys)
h.specialCheckFinalizerAlloc.init(unsafe.Sizeof(specialCheckFinalizer{}), nil, nil, &memstats.other_sys)
h.specialTinyBlockAlloc.init(unsafe.Sizeof(specialTinyBlock{}), nil, nil, &memstats.other_sys)
h.specialprofilealloc.init(unsafe.Sizeof(specialprofile{}), nil, nil, &memstats.other_sys)
h.specialReachableAlloc.init(unsafe.Sizeof(specialReachable{}), nil, nil, &memstats.other_sys)
h.specialPinCounterAlloc.init(unsafe.Sizeof(specialPinCounter{}), nil, nil, &memstats.other_sys)
@ -1967,23 +1969,28 @@ func (q *mSpanQueue) popN(n int) mSpanQueue {
}
const (
// _KindSpecialTinyBlock indicates that a given allocation is a tiny block.
// Ordered before KindSpecialFinalizer and KindSpecialCleanup so that it
// always appears first in the specials list.
// Used only if debug.checkfinalizers != 0.
_KindSpecialTinyBlock = 1
// _KindSpecialFinalizer is for tracking finalizers.
_KindSpecialFinalizer = 1
_KindSpecialFinalizer = 2
// _KindSpecialWeakHandle is used for creating weak pointers.
_KindSpecialWeakHandle = 2
_KindSpecialWeakHandle = 3
// _KindSpecialProfile is for memory profiling.
_KindSpecialProfile = 3
_KindSpecialProfile = 4
// _KindSpecialReachable is a special used for tracking
// reachability during testing.
_KindSpecialReachable = 4
_KindSpecialReachable = 5
// _KindSpecialPinCounter is a special used for objects that are pinned
// multiple times
_KindSpecialPinCounter = 5
_KindSpecialPinCounter = 6
// _KindSpecialCleanup is for tracking cleanups.
_KindSpecialCleanup = 6
_KindSpecialCleanup = 7
// _KindSpecialCheckFinalizer adds additional context to a finalizer or cleanup.
// Used only if debug.checkfinalizers != 0.
_KindSpecialCheckFinalizer = 7
_KindSpecialCheckFinalizer = 8
)
type special struct {
@ -2347,6 +2354,45 @@ func clearCleanupContext(ptr uintptr, cleanupID uint64) {
unlock(&mheap_.speciallock)
}
// Indicates that an allocation is a tiny block.
// Used only if debug.checkfinalizers != 0.
type specialTinyBlock struct {
_ sys.NotInHeap
special special
}
// setTinyBlockContext marks an allocation as a tiny block to diagnostics like
// checkfinalizer.
//
// A tiny block is only marked if it actually contains more than one distinct
// value, since we're using this for debugging.
func setTinyBlockContext(ptr unsafe.Pointer) {
lock(&mheap_.speciallock)
s := (*specialTinyBlock)(mheap_.specialTinyBlockAlloc.alloc())
unlock(&mheap_.speciallock)
s.special.kind = _KindSpecialTinyBlock
mp := acquirem()
addspecial(ptr, &s.special, false)
releasem(mp)
KeepAlive(ptr)
}
// inTinyBlock returns whether ptr is in a tiny alloc block, at one point grouped
// with other distinct values.
func inTinyBlock(ptr uintptr) bool {
assertWorldStopped()
ptr = alignDown(ptr, maxTinySize)
span := spanOfHeap(ptr)
if span == nil {
return false
}
offset := ptr - span.base()
_, exists := span.specialFindSplicePoint(offset, _KindSpecialTinyBlock)
return exists
}
// The described object has a weak pointer.
//
// Weak pointers in the GC have the following invariants:
@ -2766,6 +2812,11 @@ func freeSpecial(s *special, p unsafe.Pointer, size uintptr) {
lock(&mheap_.speciallock)
mheap_.specialCheckFinalizerAlloc.free(unsafe.Pointer(sc))
unlock(&mheap_.speciallock)
case _KindSpecialTinyBlock:
st := (*specialTinyBlock)(unsafe.Pointer(s))
lock(&mheap_.speciallock)
mheap_.specialTinyBlockAlloc.free(unsafe.Pointer(st))
unlock(&mheap_.speciallock)
default:
throw("bad special kind")
panic("not reached")