mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-04 02:57:46 +00:00
153 lines
5.8 KiB
Go
153 lines
5.8 KiB
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 essence 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 sizeFieldBits = 4 + (unsafe.Sizeof(uintptr(0)) / 4)
|
|
|
|
// parseGCLayout stores the layout information passed to alloc into a gcLayout value.
|
|
func parseGCLayout(layout unsafe.Pointer) gcLayout {
|
|
return gcLayout(layout)
|
|
}
|
|
|
|
// gcLayout tracks pointer locations in a heap object.
|
|
type gcLayout uintptr
|
|
|
|
func (layout gcLayout) pointerFree() bool {
|
|
return layout&1 != 0 && layout>>(sizeFieldBits+1) == 0
|
|
}
|
|
|
|
// scan an object with this element layout.
|
|
// The starting address must be valid and pointer-aligned.
|
|
// The length is rounded down to a multiple of the element size.
|
|
func (layout gcLayout) scan(start, len uintptr) {
|
|
switch {
|
|
case layout == 0:
|
|
// This is an unknown layout.
|
|
// Scan conservatively.
|
|
// NOTE: This is *NOT* equivalent to a slice of pointers on AVR.
|
|
scanConservative(start, len)
|
|
|
|
case layout&1 != 0:
|
|
// The layout is stored directly in the integer value.
|
|
// Extract the bitfields.
|
|
size := uintptr(layout>>1) & (1<<sizeFieldBits - 1)
|
|
mask := uintptr(layout) >> (1 + sizeFieldBits)
|
|
|
|
// Scan with the extracted mask.
|
|
scanSimple(start, len, size*unsafe.Alignof(start), mask)
|
|
|
|
default:
|
|
// The layout is stored separately in a global object.
|
|
// Extract the size and bitmap.
|
|
layoutAddr := unsafe.Pointer(layout)
|
|
size := *(*uintptr)(layoutAddr)
|
|
bitmapPtr := unsafe.Add(layoutAddr, unsafe.Sizeof(uintptr(0)))
|
|
bitmapLen := (size + 7) / 8
|
|
bitmap := unsafe.Slice((*byte)(bitmapPtr), bitmapLen)
|
|
|
|
// Scan with the bitmap.
|
|
scanComplex(start, len, size*unsafe.Alignof(start), bitmap)
|
|
}
|
|
}
|
|
|
|
// scanSimple scans an object with an integer bitmask of pointer locations.
|
|
// The starting address must be valid and pointer-aligned.
|
|
func scanSimple(start, len, size, mask uintptr) {
|
|
for len >= size {
|
|
// Scan this element.
|
|
scanWithMask(start, mask)
|
|
|
|
// Move to the next element.
|
|
start += size
|
|
len -= size
|
|
}
|
|
}
|
|
|
|
// scanComplex scans an object with a bitmap of pointer locations.
|
|
// The starting address must be valid and pointer-aligned.
|
|
func scanComplex(start, len, size uintptr, bitmap []byte) {
|
|
for len >= size {
|
|
// Scan this element.
|
|
for i, mask := range bitmap {
|
|
addr := start + 8*unsafe.Alignof(start)*uintptr(i)
|
|
scanWithMask(addr, uintptr(mask))
|
|
}
|
|
|
|
// Move to the next element.
|
|
start += size
|
|
len -= size
|
|
}
|
|
}
|
|
|
|
// scanWithMask scans a portion of an object with a mask of pointer locations.
|
|
// The address must be valid and pointer-aligned.
|
|
func scanWithMask(addr, mask uintptr) {
|
|
// TODO: use ctz when available
|
|
for mask != 0 {
|
|
if mask&1 != 0 {
|
|
// Load and mark this pointer.
|
|
root := *(*uintptr)(unsafe.Pointer(addr))
|
|
markRoot(addr, root)
|
|
}
|
|
|
|
// Move to the next offset.
|
|
mask >>= 1
|
|
addr += unsafe.Alignof(addr)
|
|
}
|
|
}
|