147 строки
5,6 КиБ
Go
147 строки
5,6 КиБ
Go
//go:build gc.precise
|
|
|
|
// This implements the block-based GC as a partially precise GC. This means that
|
|
// for most heap allocations it is known which words contain a pointer and which
|
|
// don't. This should in theory make the GC faster (because it can skip
|
|
// non-pointer object) and have fewer false positives in a GC cycle. It does
|
|
// however use a bit more RAM to store the layout of each object.
|
|
//
|
|
// The pointer/non-pointer information for objects is stored in the first word
|
|
// of the object. It is described below but in essense it contains a bitstring
|
|
// of a particular size. This size does not indicate the size of the object:
|
|
// instead the allocated object is a multiple of the bitstring size. This is so
|
|
// that arrays and slices can store the size of the object efficiently. The
|
|
// bitstring indicates where the pointers are in the object (the bit is set when
|
|
// the value may be a pointer, and cleared when it certainly isn't a pointer).
|
|
// Some examples (assuming a 32-bit system for the moment):
|
|
//
|
|
// | object type | size | bitstring | note
|
|
// |-------------|------|-----------|------
|
|
// | int | 1 | 0 | no pointers in this object
|
|
// | string | 2 | 01 | {pointer, len} pair so there is one pointer
|
|
// | []int | 3 | 001 | {pointer, len, cap}
|
|
// | [4]*int | 1 | 1 | even though it contains 4 pointers, an array repeats so it can be stored with size=1
|
|
// | [30]byte | 1 | 0 | there are no pointers so the layout is very simple
|
|
//
|
|
// The garbage collector scans objects by starting at the first word value in
|
|
// the object. If the least significant bit of the bitstring is clear, it is
|
|
// skipped (it's not a pointer). If the bit is set, it is treated as if it could
|
|
// be a pointer. The garbage collector continues by scanning further words in
|
|
// the object and checking them against the corresponding bit in the bitstring.
|
|
// Once it reaches the end of the bitstring, it wraps around (for arrays,
|
|
// slices, strings, etc).
|
|
//
|
|
// The layout as passed to the runtime.alloc function and stored in the object
|
|
// is a pointer-sized value. If the least significant bit of the value is set,
|
|
// the bitstring is contained directly inside the value, of the form
|
|
// pppp_pppp_ppps_sss1.
|
|
// * The 'p' bits indicate which parts of the object are a pointer.
|
|
// * The 's' bits indicate the size of the object. In this case, there are 11
|
|
// pointer bits so four bits are enough for the size (0-15).
|
|
// * The lowest bit is always set to distinguish this value from a pointer.
|
|
// This example is for a 16-bit architecture. For example, 32-bit architectures
|
|
// use a layout format of pppppppp_pppppppp_pppppppp_ppsssss1 (26 bits for
|
|
// pointer/non-pointer information, 5 size bits, and one bit that's always set).
|
|
//
|
|
// For larger objects that don't fit in an uintptr, the layout value is a
|
|
// pointer to a global with a format as follows:
|
|
// struct {
|
|
// size uintptr
|
|
// bits [...]uint8
|
|
// }
|
|
// The 'size' field is the number of bits in the bitstring. The 'bits' field is
|
|
// a byte array that contains the bitstring itself, in little endian form. The
|
|
// length of the bits array is ceil(size/8).
|
|
|
|
package runtime
|
|
|
|
import "unsafe"
|
|
|
|
const preciseHeap = true
|
|
|
|
type gcObjectScanner struct {
|
|
index uintptr
|
|
size uintptr
|
|
bitmap uintptr
|
|
bitmapAddr unsafe.Pointer
|
|
}
|
|
|
|
func newGCObjectScanner(block gcBlock) gcObjectScanner {
|
|
if gcAsserts && block != block.findHead() {
|
|
runtimePanic("gc: object scanner must start at head")
|
|
}
|
|
scanner := gcObjectScanner{}
|
|
layout := *(*uintptr)(unsafe.Pointer(block.address()))
|
|
if layout == 0 {
|
|
// Unknown layout. Assume all words in the object could be pointers.
|
|
// This layout value below corresponds to a slice of pointers like:
|
|
// make(*byte, N)
|
|
scanner.size = 1
|
|
scanner.bitmap = 1
|
|
} else if layout&1 != 0 {
|
|
// Layout is stored directly in the integer value.
|
|
// Determine format of bitfields in the integer.
|
|
const layoutBits = uint64(unsafe.Sizeof(layout) * 8)
|
|
var sizeFieldBits uint64
|
|
switch layoutBits { // note: this switch should be resolved at compile time
|
|
case 16:
|
|
sizeFieldBits = 4
|
|
case 32:
|
|
sizeFieldBits = 5
|
|
case 64:
|
|
sizeFieldBits = 6
|
|
default:
|
|
runtimePanic("unknown pointer size")
|
|
}
|
|
|
|
// Extract values from the bitfields.
|
|
// See comment at the top of this file for more information.
|
|
scanner.size = (layout >> 1) & (1<<sizeFieldBits - 1)
|
|
scanner.bitmap = layout >> (1 + sizeFieldBits)
|
|
} else {
|
|
// Layout is stored separately in a global object.
|
|
layoutAddr := unsafe.Pointer(layout)
|
|
scanner.size = *(*uintptr)(layoutAddr)
|
|
scanner.bitmapAddr = unsafe.Add(layoutAddr, unsafe.Sizeof(uintptr(0)))
|
|
}
|
|
return scanner
|
|
}
|
|
|
|
func (scanner *gcObjectScanner) pointerFree() bool {
|
|
if scanner.bitmapAddr != nil {
|
|
// While the format allows for large objects without pointers, this is
|
|
// optimized by the compiler so if bitmapAddr is set, we know that there
|
|
// are at least some pointers in the object.
|
|
return false
|
|
}
|
|
// If the bitmap is zero, there are definitely no pointers in the object.
|
|
return scanner.bitmap == 0
|
|
}
|
|
|
|
func (scanner *gcObjectScanner) nextIsPointer(word, parent, addrOfWord uintptr) bool {
|
|
index := scanner.index
|
|
scanner.index++
|
|
if scanner.index == scanner.size {
|
|
scanner.index = 0
|
|
}
|
|
|
|
if !isOnHeap(word) {
|
|
// Definitely isn't a pointer.
|
|
return false
|
|
}
|
|
|
|
// Might be a pointer. Now look at the object layout to know for sure.
|
|
if scanner.bitmapAddr != nil {
|
|
if (*(*uint8)(unsafe.Add(scanner.bitmapAddr, index/8))>>(index%8))&1 == 0 {
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
if (scanner.bitmap>>index)&1 == 0 {
|
|
// not a pointer!
|
|
return false
|
|
}
|
|
|
|
// Probably a pointer.
|
|
return true
|
|
}
|