]> Cypherpunks.ru repositories - gostls13.git/blobdiff - src/runtime/pinner.go
runtime: add available godoc link
[gostls13.git] / src / runtime / pinner.go
index 1c4209997ae5e61509435ce9e7172f99668bf9b3..ea5b909aea1a174144a6c9143cc5cd0ed0f18d4c 100644 (file)
@@ -6,49 +6,85 @@ package runtime
 
 import (
        "runtime/internal/atomic"
-       "runtime/internal/sys"
        "unsafe"
 )
 
-// Pinner represents a set of pinned Go objects. An object can be pinned with
-// the Pin method and all pinned objects of a Pinner can be unpinned with the
-// Unpin method.
+// A Pinner is a set of Go objects each pinned to a fixed location in memory. The
+// [Pin] method pins one object, while [Unpin] unpins all pinned objects. See their
+// comments for more information.
 type Pinner struct {
        *pinner
 }
 
-// Pin a Go object. The object will not be moved or freed by the garbage
-// collector until the Unpin method has been called. The pointer to a pinned
-// object can be directly stored in C memory or can be contained in Go memory
-// passed to C functions. If the pinned object iftself contains pointers to Go
-// objects, these objects must be pinned separately if they are going to be
-// accessed from C code. The argument must be a pointer of any type or an
-// unsafe.Pointer. It must be a pointer to an object allocated by calling new,
-// by taking the address of a composite literal, or by taking the address of a
-// local variable. If one of these conditions is not met, Pin will panic.
+// Pin pins a Go object, preventing it from being moved or freed by the garbage
+// collector until the [Pinner.Unpin] method has been called.
+//
+// A pointer to a pinned object can be directly stored in C memory or can be
+// contained in Go memory passed to C functions. If the pinned object itself
+// contains pointers to Go objects, these objects must be pinned separately if they
+// are going to be accessed from C code.
+//
+// The argument must be a pointer of any type or an [unsafe.Pointer].
+// It's safe to call Pin on non-Go pointers, in which case Pin will do nothing.
 func (p *Pinner) Pin(pointer any) {
        if p.pinner == nil {
-               p.pinner = new(pinner)
-               SetFinalizer(p.pinner, func(i *pinner) {
-                       if i.refs != nil {
-                               i.unpin() // only required to make the test idempotent
-                               pinnerLeakPanic()
-                       }
-               })
+               // Check the pinner cache first.
+               mp := acquirem()
+               if pp := mp.p.ptr(); pp != nil {
+                       p.pinner = pp.pinnerCache
+                       pp.pinnerCache = nil
+               }
+               releasem(mp)
+
+               if p.pinner == nil {
+                       // Didn't get anything from the pinner cache.
+                       p.pinner = new(pinner)
+                       p.refs = p.refStore[:0]
+
+                       // We set this finalizer once and never clear it. Thus, if the
+                       // pinner gets cached, we'll reuse it, along with its finalizer.
+                       // This lets us avoid the relatively expensive SetFinalizer call
+                       // when reusing from the cache. The finalizer however has to be
+                       // resilient to an empty pinner being finalized, which is done
+                       // by checking p.refs' length.
+                       SetFinalizer(p.pinner, func(i *pinner) {
+                               if len(i.refs) != 0 {
+                                       i.unpin() // only required to make the test idempotent
+                                       pinnerLeakPanic()
+                               }
+                       })
+               }
        }
        ptr := pinnerGetPtr(&pointer)
-
-       setPinned(ptr, true)
-       p.refs = append(p.refs, ptr)
+       if setPinned(ptr, true) {
+               p.refs = append(p.refs, ptr)
+       }
 }
 
-// Unpin all pinned objects of the Pinner.
+// Unpin unpins all pinned objects of the [Pinner].
 func (p *Pinner) Unpin() {
        p.pinner.unpin()
+
+       mp := acquirem()
+       if pp := mp.p.ptr(); pp != nil && pp.pinnerCache == nil {
+               // Put the pinner back in the cache, but only if the
+               // cache is empty. If application code is reusing Pinners
+               // on its own, we want to leave the backing store in place
+               // so reuse is more efficient.
+               pp.pinnerCache = p.pinner
+               p.pinner = nil
+       }
+       releasem(mp)
 }
 
+const (
+       pinnerSize         = 64
+       pinnerRefStoreSize = (pinnerSize - unsafe.Sizeof([]unsafe.Pointer{})) / unsafe.Sizeof(unsafe.Pointer(nil))
+)
+
 type pinner struct {
-       refs []unsafe.Pointer
+       refs     []unsafe.Pointer
+       refStore [pinnerRefStoreSize]unsafe.Pointer
 }
 
 func (p *pinner) unpin() {
@@ -57,9 +93,12 @@ func (p *pinner) unpin() {
        }
        for i := range p.refs {
                setPinned(p.refs[i], false)
-               p.refs[i] = nil
        }
-       p.refs = nil
+       // The following two lines make all pointers to references
+       // in p.refs unreachable, either by deleting them or dropping
+       // p.refs' backing store (if it was not backed by refStore).
+       p.refStore = [pinnerRefStoreSize]unsafe.Pointer{}
+       p.refs = p.refStore[:0]
 }
 
 func pinnerGetPtr(i *any) unsafe.Pointer {
@@ -90,26 +129,31 @@ func isPinned(ptr unsafe.Pointer) bool {
                return true
        }
        pinnerBits := span.getPinnerBits()
+       // these pinnerBits might get unlinked by a concurrently running sweep, but
+       // that's OK because gcBits don't get cleared until the following GC cycle
+       // (nextMarkBitArenaEpoch)
        if pinnerBits == nil {
                return false
        }
        objIndex := span.objIndex(uintptr(ptr))
-       bytep := &pinnerBits.x[objIndex/8]
-       mask := byte(1 << (objIndex % 8))
-       result := (bytep.Load() & mask) != 0
+       pinState := pinnerBits.ofObject(objIndex)
        KeepAlive(ptr) // make sure ptr is alive until we are done so the span can't be freed
-       return result
+       return pinState.isPinned()
 }
 
-// setPinned marks or unmarks a Go pointer as pinned.
-func setPinned(ptr unsafe.Pointer, pin bool) {
+// setPinned marks or unmarks a Go pointer as pinned, when the ptr is a Go pointer.
+// It will be ignored while try to pin a non-Go pointer,
+// and it will be panic while try to unpin a non-Go pointer,
+// which should not happen in normal usage.
+func setPinned(ptr unsafe.Pointer, pin bool) bool {
        span := spanOfHeap(uintptr(ptr))
        if span == nil {
-               if isGoPointerWithoutSpan(ptr) {
-                       // this is a linker-allocated or zero size object, nothing to do.
-                       return
+               if !pin {
+                       panic(errorString("tried to unpin non-Go pointer"))
                }
-               panic(errorString("runtime.Pinner.Pin: argument is not a Go pointer"))
+               // This is a linker-allocated, zero size object or other object,
+               // nothing to do, silently ignore it.
+               return false
        }
 
        // ensure that the span is swept, b/c sweeping accesses the specials list
@@ -119,88 +163,167 @@ func setPinned(ptr unsafe.Pointer, pin bool) {
        KeepAlive(ptr) // make sure ptr is still alive after span is swept
 
        objIndex := span.objIndex(uintptr(ptr))
-       mask := byte(1 << (objIndex % 8))
 
        lock(&span.speciallock) // guard against concurrent calls of setPinned on same span
 
        pinnerBits := span.getPinnerBits()
        if pinnerBits == nil {
-               pinnerBits = mheap_.newPinnerBits()
+               pinnerBits = span.newPinnerBits()
                span.setPinnerBits(pinnerBits)
        }
-       bytep := &pinnerBits.x[objIndex/8]
-       alreadySet := pin == ((bytep.Load() & mask) != 0)
+       pinState := pinnerBits.ofObject(objIndex)
        if pin {
-               if alreadySet {
-                       // multiple pin on same object, record it in counter
-                       offset := objIndex * span.elemsize
+               if pinState.isPinned() {
+                       // multiple pins on same object, set multipin bit
+                       pinState.setMultiPinned(true)
+                       // and increase the pin counter
                        // TODO(mknyszek): investigate if systemstack is necessary here
                        systemstack(func() {
+                               offset := objIndex * span.elemsize
                                span.incPinCounter(offset)
                        })
                } else {
-                       bytep.Or(mask)
+                       // set pin bit
+                       pinState.setPinned(true)
                }
        } else {
-               if alreadySet {
-                       // unpinning unpinned object, bail out
-                       throw("runtime.Pinner: object already unpinned")
-               } else {
-                       multipin := false
-                       if pinnerBits.specialCnt.Load() != 0 {
+               // unpin
+               if pinState.isPinned() {
+                       if pinState.isMultiPinned() {
+                               var exists bool
                                // TODO(mknyszek): investigate if systemstack is necessary here
                                systemstack(func() {
                                        offset := objIndex * span.elemsize
-                                       multipin = span.decPinCounter(offset)
+                                       exists = span.decPinCounter(offset)
                                })
+                               if !exists {
+                                       // counter is 0, clear multipin bit
+                                       pinState.setMultiPinned(false)
+                               }
+                       } else {
+                               // no multipins recorded. unpin object.
+                               pinState.setPinned(false)
                        }
-                       if !multipin {
-                               // no multiple pins recoded. unpin object.
-                               bytep.And(^mask)
-                       }
+               } else {
+                       // unpinning unpinned object, bail out
+                       throw("runtime.Pinner: object already unpinned")
                }
        }
        unlock(&span.speciallock)
        releasem(mp)
-       return
+       return true
+}
+
+type pinState struct {
+       bytep   *uint8
+       byteVal uint8
+       mask    uint8
+}
+
+// nosplit, because it's called by isPinned, which is nosplit
+//
+//go:nosplit
+func (v *pinState) isPinned() bool {
+       return (v.byteVal & v.mask) != 0
 }
 
-// pinBits is a bitmap for pinned objects. This is always used as pinBits.x.
-type pinBits struct {
-       _          sys.NotInHeap
-       x          [(maxObjsPerSpan + 7) / 8]atomic.Uint8
-       specialCnt atomic.Int32
+func (v *pinState) isMultiPinned() bool {
+       return (v.byteVal & (v.mask << 1)) != 0
 }
 
-func (h *mheap) newPinnerBits() *pinBits {
-       lock(&h.speciallock)
-       pinnerBits := (*pinBits)(h.pinnerBitsAlloc.alloc())
-       unlock(&h.speciallock)
-       return pinnerBits
+func (v *pinState) setPinned(val bool) {
+       v.set(val, false)
 }
 
-func (h *mheap) freePinnerBits(p *pinBits) {
-       lock(&h.speciallock)
-       h.pinnerBitsAlloc.free(unsafe.Pointer(p))
-       unlock(&h.speciallock)
+func (v *pinState) setMultiPinned(val bool) {
+       v.set(val, true)
+}
+
+// set sets the pin bit of the pinState to val. If multipin is true, it
+// sets/unsets the multipin bit instead.
+func (v *pinState) set(val bool, multipin bool) {
+       mask := v.mask
+       if multipin {
+               mask <<= 1
+       }
+       if val {
+               atomic.Or8(v.bytep, mask)
+       } else {
+               atomic.And8(v.bytep, ^mask)
+       }
+}
+
+// pinnerBits is the same type as gcBits but has different methods.
+type pinnerBits gcBits
+
+// ofObject returns the pinState of the n'th object.
+// nosplit, because it's called by isPinned, which is nosplit
+//
+//go:nosplit
+func (p *pinnerBits) ofObject(n uintptr) pinState {
+       bytep, mask := (*gcBits)(p).bitp(n * 2)
+       byteVal := atomic.Load8(bytep)
+       return pinState{bytep, byteVal, mask}
+}
+
+func (s *mspan) pinnerBitSize() uintptr {
+       return divRoundUp(uintptr(s.nelems)*2, 8)
+}
+
+// newPinnerBits returns a pointer to 8 byte aligned bytes to be used for this
+// span's pinner bits. newPinneBits is used to mark objects that are pinned.
+// They are copied when the span is swept.
+func (s *mspan) newPinnerBits() *pinnerBits {
+       return (*pinnerBits)(newMarkBits(uintptr(s.nelems) * 2))
 }
 
 // nosplit, because it's called by isPinned, which is nosplit
 //
 //go:nosplit
-func (s *mspan) getPinnerBits() *pinBits {
-       return (*pinBits)(atomic.Loadp(unsafe.Pointer(&s.pinnerBits)))
+func (s *mspan) getPinnerBits() *pinnerBits {
+       return (*pinnerBits)(atomic.Loadp(unsafe.Pointer(&s.pinnerBits)))
 }
 
-func (s *mspan) setPinnerBits(p *pinBits) {
+func (s *mspan) setPinnerBits(p *pinnerBits) {
        atomicstorep(unsafe.Pointer(&s.pinnerBits), unsafe.Pointer(p))
 }
 
+// refreshPinnerBits replaces pinnerBits with a fresh copy in the arenas for the
+// next GC cycle. If it does not contain any pinned objects, pinnerBits of the
+// span is set to nil.
+func (s *mspan) refreshPinnerBits() {
+       p := s.getPinnerBits()
+       if p == nil {
+               return
+       }
+
+       hasPins := false
+       bytes := alignUp(s.pinnerBitSize(), 8)
+
+       // Iterate over each 8-byte chunk and check for pins. Note that
+       // newPinnerBits guarantees that pinnerBits will be 8-byte aligned, so we
+       // don't have to worry about edge cases, irrelevant bits will simply be
+       // zero.
+       for _, x := range unsafe.Slice((*uint64)(unsafe.Pointer(&p.x)), bytes/8) {
+               if x != 0 {
+                       hasPins = true
+                       break
+               }
+       }
+
+       if hasPins {
+               newPinnerBits := s.newPinnerBits()
+               memmove(unsafe.Pointer(&newPinnerBits.x), unsafe.Pointer(&p.x), bytes)
+               s.setPinnerBits(newPinnerBits)
+       } else {
+               s.setPinnerBits(nil)
+       }
+}
+
 // incPinCounter is only called for multiple pins of the same object and records
 // the _additional_ pins.
 func (span *mspan) incPinCounter(offset uintptr) {
        var rec *specialPinCounter
-
        ref, exists := span.specialFindSplicePoint(offset, _KindSpecialPinCounter)
        if !exists {
                lock(&mheap_.speciallock)
@@ -212,34 +335,32 @@ func (span *mspan) incPinCounter(offset uintptr) {
                rec.special.next = *ref
                *ref = (*special)(unsafe.Pointer(rec))
                spanHasSpecials(span)
-               span.pinnerBits.specialCnt.Add(1)
        } else {
                rec = (*specialPinCounter)(unsafe.Pointer(*ref))
        }
        rec.counter++
 }
 
-// decPinCounter is always called for unpins and returns false if no multiple
-// pins are recorded. If multiple pins are recorded, it decreases the counter
-// and returns true.
+// decPinCounter decreases the counter. If the counter reaches 0, the counter
+// special is deleted and false is returned. Otherwise true is returned.
 func (span *mspan) decPinCounter(offset uintptr) bool {
        ref, exists := span.specialFindSplicePoint(offset, _KindSpecialPinCounter)
-       if exists {
-               counter := (*specialPinCounter)(unsafe.Pointer(*ref))
-               if counter.counter > 1 {
-                       counter.counter--
-               } else {
-                       span.pinnerBits.specialCnt.Add(-1)
-                       *ref = counter.special.next
-                       if span.specials == nil {
-                               spanHasNoSpecials(span)
-                       }
-                       lock(&mheap_.speciallock)
-                       mheap_.specialPinCounterAlloc.free(unsafe.Pointer(counter))
-                       unlock(&mheap_.speciallock)
+       if !exists {
+               throw("runtime.Pinner: decreased non-existing pin counter")
+       }
+       counter := (*specialPinCounter)(unsafe.Pointer(*ref))
+       counter.counter--
+       if counter.counter == 0 {
+               *ref = counter.special.next
+               if span.specials == nil {
+                       spanHasNoSpecials(span)
                }
+               lock(&mheap_.speciallock)
+               mheap_.specialPinCounterAlloc.free(unsafe.Pointer(counter))
+               unlock(&mheap_.speciallock)
+               return false
        }
-       return exists
+       return true
 }
 
 // only for tests