// 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 }