Copyright 2009 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 (
	
	
	
)

const (
	_WorkbufSize = 2048 // in bytes; larger values result in less contention
workbufAlloc is the number of bytes to allocate at a time for new workbufs. This must be a multiple of pageSize and should be a multiple of _WorkbufSize. Larger values reduce workbuf allocation overhead. Smaller values reduce heap fragmentation.
	workbufAlloc = 32 << 10
)

func () {
	if workbufAlloc%pageSize != 0 || workbufAlloc%_WorkbufSize != 0 {
		throw("bad workbufAlloc")
	}
}
Garbage collector work pool abstraction. This implements a producer/consumer model for pointers to grey objects. A grey object is one that is marked and on a work queue. A black object is marked and not on a work queue. Write barriers, root discovery, stack scanning, and object scanning produce pointers to grey objects. Scanning consumes pointers to grey objects, thus blackening them, and then scans them, potentially producing new pointers to grey objects.
A gcWork provides the interface to produce and consume work for the garbage collector. A gcWork can be used on the stack as follows: (preemption must be disabled) gcw := &getg().m.p.ptr().gcw .. call gcw.put() to produce and gcw.tryGet() to consume .. It's important that any use of gcWork during the mark phase prevent the garbage collector from transitioning to mark termination since gcWork may locally hold GC work buffers. This can be done by disabling preemption (systemstack or acquirem).
wbuf1 and wbuf2 are the primary and secondary work buffers. This can be thought of as a stack of both work buffers' pointers concatenated. When we pop the last pointer, we shift the stack up by one work buffer by bringing in a new full buffer and discarding an empty one. When we fill both buffers, we shift the stack down by one work buffer by bringing in a new empty buffer and discarding a full one. This way we have one buffer's worth of hysteresis, which amortizes the cost of getting or putting a work buffer over at least one buffer of work and reduces contention on the global work lists. wbuf1 is always the buffer we're currently pushing to and popping from and wbuf2 is the buffer that will be discarded next. Invariant: Both wbuf1 and wbuf2 are nil or neither are.
Bytes marked (blackened) on this gcWork. This is aggregated into work.bytesMarked by dispose.
Scan work performed on this gcWork. This is aggregated into gcController by dispose and may also be flushed by callers.
flushedWork indicates that a non-empty work buffer was flushed to the global work list since the last gcMarkDone termination check. Specifically, this indicates that this gcWork may have communicated work to another gcWork.
Most of the methods of gcWork are go:nowritebarrierrec because the write barrier itself can invoke gcWork methods but the methods are not generally re-entrant. Hence, if a gcWork method invoked the write barrier while the gcWork was in an inconsistent state, and the write barrier in turn invoked a gcWork method, it could permanently corrupt the gcWork.

func ( *gcWork) () {
	.wbuf1 = getempty()
	 := trygetfull()
	if  == nil {
		 = getempty()
	}
	.wbuf2 = 
}
put enqueues a pointer for the garbage collector to trace. obj must point to the beginning of a heap object or an oblet.go:nowritebarrierrec
func ( *gcWork) ( uintptr) {
	 := false
Record that this may acquire the wbufSpans or heap lock to allocate a workbuf.
wbuf is empty at this point.
	} else if .nobj == len(.obj) {
		.wbuf1, .wbuf2 = .wbuf2, .wbuf1
		 = .wbuf1
		if .nobj == len(.obj) {
			putfull()
			.flushedWork = true
			 = getempty()
			.wbuf1 = 
			 = true
		}
	}

	.obj[.nobj] = 
	.nobj++
If we put a buffer on full, let the GC controller know so it can encourage more workers to run. We delay this until the end of put so that w is in a consistent state, since enlistWorker may itself manipulate w.
	if  && gcphase == _GCmark {
		gcController.enlistWorker()
	}
}
putFast does a put and reports whether it can be done quickly otherwise it returns false and the caller needs to call put.go:nowritebarrierrec
func ( *gcWork) ( uintptr) bool {
	 := .wbuf1
	if  == nil {
		return false
	} else if .nobj == len(.obj) {
		return false
	}

	.obj[.nobj] = 
	.nobj++
	return true
}
putBatch performs a put on every pointer in obj. See put for constraints on these pointers.go:nowritebarrierrec
func ( *gcWork) ( []uintptr) {
	if len() == 0 {
		return
	}

	 := false
	 := .wbuf1
	if  == nil {
		.init()
		 = .wbuf1
	}

	for len() > 0 {
		for .nobj == len(.obj) {
			putfull()
			.flushedWork = true
			.wbuf1, .wbuf2 = .wbuf2, getempty()
			 = .wbuf1
			 = true
		}
		 := copy(.obj[.nobj:], )
		.nobj += 
		 = [:]
	}

	if  && gcphase == _GCmark {
		gcController.enlistWorker()
	}
}
tryGet dequeues a pointer for the garbage collector to trace. If there are no pointers remaining in this gcWork or in the global queue, tryGet returns 0. Note that there may still be pointers in other gcWork instances or other caches.go:nowritebarrierrec
func ( *gcWork) () uintptr {
	 := .wbuf1
	if  == nil {
		.init()
wbuf is empty at this point.
	}
	if .nobj == 0 {
		.wbuf1, .wbuf2 = .wbuf2, .wbuf1
		 = .wbuf1
		if .nobj == 0 {
			 := 
			 = trygetfull()
			if  == nil {
				return 0
			}
			putempty()
			.wbuf1 = 
		}
	}

	.nobj--
	return .obj[.nobj]
}
tryGetFast dequeues a pointer for the garbage collector to trace if one is readily available. Otherwise it returns 0 and the caller is expected to call tryGet().go:nowritebarrierrec
func ( *gcWork) () uintptr {
	 := .wbuf1
	if  == nil {
		return 0
	}
	if .nobj == 0 {
		return 0
	}

	.nobj--
	return .obj[.nobj]
}
dispose returns any cached pointers to the global queue. The buffers are being put on the full queue so that the write barriers will not simply reacquire them before the GC can inspect them. This helps reduce the mutator's ability to hide pointers during the concurrent mark phase.go:nowritebarrierrec
func ( *gcWork) () {
	if  := .wbuf1;  != nil {
		if .nobj == 0 {
			putempty()
		} else {
			putfull()
			.flushedWork = true
		}
		.wbuf1 = nil

		 = .wbuf2
		if .nobj == 0 {
			putempty()
		} else {
			putfull()
			.flushedWork = true
		}
		.wbuf2 = nil
	}
dispose happens relatively infrequently. If this atomic becomes a problem, we should first try to dispose less and if necessary aggregate in a per-P counter.
balance moves some work that's cached in this gcWork back on the global queue.go:nowritebarrierrec
func ( *gcWork) () {
	if .wbuf1 == nil {
		return
	}
	if  := .wbuf2; .nobj != 0 {
		putfull()
		.flushedWork = true
		.wbuf2 = getempty()
	} else if  := .wbuf1; .nobj > 4 {
		.wbuf1 = handoff()
		.flushedWork = true // handoff did putfull
	} else {
		return
We flushed a buffer to the full list, so wake a worker.
empty reports whether w has no mark work available.go:nowritebarrierrec
func ( *gcWork) () bool {
	return .wbuf1 == nil || (.wbuf1.nobj == 0 && .wbuf2.nobj == 0)
}
Internally, the GC work pool is kept in arrays in work buffers. The gcWork interface caches a work buffer until full (or empty) to avoid contending on the global work buffer lists.

type workbufhdr struct {
	node lfnode // must be first
	nobj int
}
go:notinheap
type workbuf struct {
account for the above fields
workbuf factory routines. These funcs are used to manage the workbufs. If the GC asks for some work these are the only routines that make wbufs available to the GC.

func ( *workbuf) () {
	if .nobj == 0 {
		throw("workbuf is empty")
	}
}

func ( *workbuf) () {
	if .nobj != 0 {
		throw("workbuf is not empty")
	}
}
getempty pops an empty work buffer off the work.empty list, allocating new buffers if none are available.go:nowritebarrier
func () *workbuf {
	var  *workbuf
	if work.empty != 0 {
		 = (*workbuf)(work.empty.pop())
		if  != nil {
			.checkempty()
		}
Record that this may acquire the wbufSpans or heap lock to allocate a workbuf.
Allocate more workbufs.
Record the new span in the busy list.
Slice up the span into new workbufs. Return one and put the rest on the empty list.
		for  := uintptr(0); +_WorkbufSize <= workbufAlloc;  += _WorkbufSize {
			 := (*workbuf)(unsafe.Pointer(.base() + ))
			.nobj = 0
			lfnodeValidate(&.node)
			if  == 0 {
				 = 
			} else {
				putempty()
			}
		}
	}
	return 
}
putempty puts a workbuf onto the work.empty list. Upon entry this go routine owns b. The lfstack.push relinquishes ownership.go:nowritebarrier
func ( *workbuf) {
	.checkempty()
	work.empty.push(&.node)
}
putfull puts the workbuf on the work.full list for the GC. putfull accepts partially full buffers so the GC can avoid competing with the mutators for ownership of partially full buffers.go:nowritebarrier
trygetfull tries to get a full or partially empty workbuffer. If one is not immediately available return nilgo:nowritebarrier
func () *workbuf {
	 := (*workbuf)(work.full.pop())
	if  != nil {
		.checknonempty()
		return 
	}
	return 
}
go:nowritebarrier
Make new buffer with half of b's pointers.
	 := getempty()
	 := .nobj / 2
	.nobj -= 
	.nobj = 
	memmove(unsafe.Pointer(&.obj[0]), unsafe.Pointer(&.obj[.nobj]), uintptr()*unsafe.Sizeof(.obj[0]))
Put b on full list - let first half of b get stolen.
	putfull()
	return 
}
prepareFreeWorkbufs moves busy workbuf spans to free list so they can be freed to the heap. This must only be called when all workbufs are on the empty list.
func () {
	lock(&work.wbufSpans.lock)
	if work.full != 0 {
		throw("cannot free workbufs when work.full != 0")
Since all workbufs are on the empty list, we don't care which ones are in which spans. We can wipe the entire empty list and move all workbuf spans to the free list.
freeSomeWbufs frees some workbufs back to the heap and returns true if it should be called again to free more.
func ( bool) bool {
	const  = 64 // ~1–2 µs per span.
	lock(&work.wbufSpans.lock)
	if gcphase != _GCoff || work.wbufSpans.free.isEmpty() {
		unlock(&work.wbufSpans.lock)
		return false
	}
	systemstack(func() {
		 := getg().m.curg
		for  := 0;  <  && !( && .preempt); ++ {
			 := work.wbufSpans.free.first
			if  == nil {
				break
			}
			work.wbufSpans.free.remove()
			mheap_.freeManual(, spanAllocWorkBuf)
		}
	})
	 := !work.wbufSpans.free.isEmpty()
	unlock(&work.wbufSpans.lock)
	return