|  | // Copyright 2016 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package runtime | 
|  |  | 
|  | import ( | 
|  | "runtime/internal/atomic" | 
|  | "runtime/internal/sys" | 
|  | "unsafe" | 
|  | ) | 
|  |  | 
|  | // A gcSweepBuf is a set of *mspans. | 
|  | // | 
|  | // gcSweepBuf is safe for concurrent push operations *or* concurrent | 
|  | // pop operations, but not both simultaneously. | 
|  | type gcSweepBuf struct { | 
|  | // A gcSweepBuf is a two-level data structure consisting of a | 
|  | // growable spine that points to fixed-sized blocks. The spine | 
|  | // can be accessed without locks, but adding a block or | 
|  | // growing it requires taking the spine lock. | 
|  | // | 
|  | // Because each mspan covers at least 8K of heap and takes at | 
|  | // most 8 bytes in the gcSweepBuf, the growth of the spine is | 
|  | // quite limited. | 
|  | // | 
|  | // The spine and all blocks are allocated off-heap, which | 
|  | // allows this to be used in the memory manager and avoids the | 
|  | // need for write barriers on all of these. We never release | 
|  | // this memory because there could be concurrent lock-free | 
|  | // access and we're likely to reuse it anyway. (In principle, | 
|  | // we could do this during STW.) | 
|  |  | 
|  | spineLock mutex | 
|  | spine     unsafe.Pointer // *[N]*gcSweepBlock, accessed atomically | 
|  | spineLen  uintptr        // Spine array length, accessed atomically | 
|  | spineCap  uintptr        // Spine array cap, accessed under lock | 
|  |  | 
|  | // index is the first unused slot in the logical concatenation | 
|  | // of all blocks. It is accessed atomically. | 
|  | index uint32 | 
|  | } | 
|  |  | 
|  | const ( | 
|  | gcSweepBlockEntries    = 512 // 4KB on 64-bit | 
|  | gcSweepBufInitSpineCap = 256 // Enough for 1GB heap on 64-bit | 
|  | ) | 
|  |  | 
|  | type gcSweepBlock struct { | 
|  | spans [gcSweepBlockEntries]*mspan | 
|  | } | 
|  |  | 
|  | // push adds span s to buffer b. push is safe to call concurrently | 
|  | // with other push operations, but NOT to call concurrently with pop. | 
|  | func (b *gcSweepBuf) push(s *mspan) { | 
|  | // Obtain our slot. | 
|  | cursor := uintptr(atomic.Xadd(&b.index, +1) - 1) | 
|  | top, bottom := cursor/gcSweepBlockEntries, cursor%gcSweepBlockEntries | 
|  |  | 
|  | // Do we need to add a block? | 
|  | spineLen := atomic.Loaduintptr(&b.spineLen) | 
|  | var block *gcSweepBlock | 
|  | retry: | 
|  | if top < spineLen { | 
|  | spine := atomic.Loadp(unsafe.Pointer(&b.spine)) | 
|  | blockp := add(spine, sys.PtrSize*top) | 
|  | block = (*gcSweepBlock)(atomic.Loadp(blockp)) | 
|  | } else { | 
|  | // Add a new block to the spine, potentially growing | 
|  | // the spine. | 
|  | lock(&b.spineLock) | 
|  | // spineLen cannot change until we release the lock, | 
|  | // but may have changed while we were waiting. | 
|  | spineLen = atomic.Loaduintptr(&b.spineLen) | 
|  | if top < spineLen { | 
|  | unlock(&b.spineLock) | 
|  | goto retry | 
|  | } | 
|  |  | 
|  | if spineLen == b.spineCap { | 
|  | // Grow the spine. | 
|  | newCap := b.spineCap * 2 | 
|  | if newCap == 0 { | 
|  | newCap = gcSweepBufInitSpineCap | 
|  | } | 
|  | newSpine := persistentalloc(newCap*sys.PtrSize, sys.CacheLineSize, &memstats.gc_sys) | 
|  | if b.spineCap != 0 { | 
|  | // Blocks are allocated off-heap, so | 
|  | // no write barriers. | 
|  | memmove(newSpine, b.spine, b.spineCap*sys.PtrSize) | 
|  | } | 
|  | // Spine is allocated off-heap, so no write barrier. | 
|  | atomic.StorepNoWB(unsafe.Pointer(&b.spine), newSpine) | 
|  | b.spineCap = newCap | 
|  | // We can't immediately free the old spine | 
|  | // since a concurrent push with a lower index | 
|  | // could still be reading from it. We let it | 
|  | // leak because even a 1TB heap would waste | 
|  | // less than 2MB of memory on old spines. If | 
|  | // this is a problem, we could free old spines | 
|  | // during STW. | 
|  | } | 
|  |  | 
|  | // Allocate a new block and add it to the spine. | 
|  | block = (*gcSweepBlock)(persistentalloc(unsafe.Sizeof(gcSweepBlock{}), sys.CacheLineSize, &memstats.gc_sys)) | 
|  | blockp := add(b.spine, sys.PtrSize*top) | 
|  | // Blocks are allocated off-heap, so no write barrier. | 
|  | atomic.StorepNoWB(blockp, unsafe.Pointer(block)) | 
|  | atomic.Storeuintptr(&b.spineLen, spineLen+1) | 
|  | unlock(&b.spineLock) | 
|  | } | 
|  |  | 
|  | // We have a block. Insert the span. | 
|  | block.spans[bottom] = s | 
|  | } | 
|  |  | 
|  | // pop removes and returns a span from buffer b, or nil if b is empty. | 
|  | // pop is safe to call concurrently with other pop operations, but NOT | 
|  | // to call concurrently with push. | 
|  | func (b *gcSweepBuf) pop() *mspan { | 
|  | cursor := atomic.Xadd(&b.index, -1) | 
|  | if int32(cursor) < 0 { | 
|  | atomic.Xadd(&b.index, +1) | 
|  | return nil | 
|  | } | 
|  |  | 
|  | // There are no concurrent spine or block modifications during | 
|  | // pop, so we can omit the atomics. | 
|  | top, bottom := cursor/gcSweepBlockEntries, cursor%gcSweepBlockEntries | 
|  | blockp := (**gcSweepBlock)(add(b.spine, sys.PtrSize*uintptr(top))) | 
|  | block := *blockp | 
|  | s := block.spans[bottom] | 
|  | // Clear the pointer for block(i). | 
|  | block.spans[bottom] = nil | 
|  | return s | 
|  | } | 
|  |  | 
|  | // numBlocks returns the number of blocks in buffer b. numBlocks is | 
|  | // safe to call concurrently with any other operation. Spans that have | 
|  | // been pushed prior to the call to numBlocks are guaranteed to appear | 
|  | // in some block in the range [0, numBlocks()), assuming there are no | 
|  | // intervening pops. Spans that are pushed after the call may also | 
|  | // appear in these blocks. | 
|  | func (b *gcSweepBuf) numBlocks() int { | 
|  | return int((atomic.Load(&b.index) + gcSweepBlockEntries - 1) / gcSweepBlockEntries) | 
|  | } | 
|  |  | 
|  | // block returns the spans in the i'th block of buffer b. block is | 
|  | // safe to call concurrently with push. | 
|  | func (b *gcSweepBuf) block(i int) []*mspan { | 
|  | // Perform bounds check before loading spine address since | 
|  | // push ensures the allocated length is at least spineLen. | 
|  | if i < 0 || uintptr(i) >= atomic.Loaduintptr(&b.spineLen) { | 
|  | throw("block index out of range") | 
|  | } | 
|  |  | 
|  | // Get block i. | 
|  | spine := atomic.Loadp(unsafe.Pointer(&b.spine)) | 
|  | blockp := add(spine, sys.PtrSize*uintptr(i)) | 
|  | block := (*gcSweepBlock)(atomic.Loadp(blockp)) | 
|  |  | 
|  | // Slice the block if necessary. | 
|  | cursor := uintptr(atomic.Load(&b.index)) | 
|  | top, bottom := cursor/gcSweepBlockEntries, cursor%gcSweepBlockEntries | 
|  | var spans []*mspan | 
|  | if uintptr(i) < top { | 
|  | spans = block.spans[:] | 
|  | } else { | 
|  | spans = block.spans[:bottom] | 
|  | } | 
|  |  | 
|  | // push may have reserved a slot but not filled it yet, so | 
|  | // trim away unused entries. | 
|  | for len(spans) > 0 && spans[len(spans)-1] == nil { | 
|  | spans = spans[:len(spans)-1] | 
|  | } | 
|  | return spans | 
|  | } |