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