github.com/tinygo-org/tinygo@v0.31.3-0.20240404173401-90b0bf646c27/src/runtime/gc_precise.go (about)

     1  //go:build gc.precise
     2  
     3  // This implements the block-based GC as a partially precise GC. This means that
     4  // for most heap allocations it is known which words contain a pointer and which
     5  // don't. This should in theory make the GC faster (because it can skip
     6  // non-pointer object) and have fewer false positives in a GC cycle. It does
     7  // however use a bit more RAM to store the layout of each object.
     8  //
     9  // The pointer/non-pointer information for objects is stored in the first word
    10  // of the object. It is described below but in essense it contains a bitstring
    11  // of a particular size. This size does not indicate the size of the object:
    12  // instead the allocated object is a multiple of the bitstring size. This is so
    13  // that arrays and slices can store the size of the object efficiently. The
    14  // bitstring indicates where the pointers are in the object (the bit is set when
    15  // the value may be a pointer, and cleared when it certainly isn't a pointer).
    16  // Some examples (assuming a 32-bit system for the moment):
    17  //
    18  // | object type | size | bitstring | note
    19  // |-------------|------|-----------|------
    20  // | int         | 1    |   0       | no pointers in this object
    21  // | string      | 2    |  01       | {pointer, len} pair so there is one pointer
    22  // | []int       | 3    | 001       | {pointer, len, cap}
    23  // | [4]*int     | 1    |   1       | even though it contains 4 pointers, an array repeats so it can be stored with size=1
    24  // | [30]byte    | 1    |   0       | there are no pointers so the layout is very simple
    25  //
    26  // The garbage collector scans objects by starting at the first word value in
    27  // the object. If the least significant bit of the bitstring is clear, it is
    28  // skipped (it's not a pointer). If the bit is set, it is treated as if it could
    29  // be a pointer. The garbage collector continues by scanning further words in
    30  // the object and checking them against the corresponding bit in the bitstring.
    31  // Once it reaches the end of the bitstring, it wraps around (for arrays,
    32  // slices, strings, etc).
    33  //
    34  // The layout as passed to the runtime.alloc function and stored in the object
    35  // is a pointer-sized value. If the least significant bit of the value is set,
    36  // the bitstring is contained directly inside the value, of the form
    37  // pppp_pppp_ppps_sss1.
    38  //   * The 'p' bits indicate which parts of the object are a pointer.
    39  //   * The 's' bits indicate the size of the object. In this case, there are 11
    40  //     pointer bits so four bits are enough for the size (0-15).
    41  //   * The lowest bit is always set to distinguish this value from a pointer.
    42  // This example is for a 16-bit architecture. For example, 32-bit architectures
    43  // use a layout format of pppppppp_pppppppp_pppppppp_ppsssss1 (26 bits for
    44  // pointer/non-pointer information, 5 size bits, and one bit that's always set).
    45  //
    46  // For larger objects that don't fit in an uintptr, the layout value is a
    47  // pointer to a global with a format as follows:
    48  //     struct {
    49  //         size uintptr
    50  //         bits [...]uint8
    51  //     }
    52  // The 'size' field is the number of bits in the bitstring. The 'bits' field is
    53  // a byte array that contains the bitstring itself, in little endian form. The
    54  // length of the bits array is ceil(size/8).
    55  
    56  package runtime
    57  
    58  import "unsafe"
    59  
    60  const preciseHeap = true
    61  
    62  type gcObjectScanner struct {
    63  	index      uintptr
    64  	size       uintptr
    65  	bitmap     uintptr
    66  	bitmapAddr unsafe.Pointer
    67  }
    68  
    69  func newGCObjectScanner(block gcBlock) gcObjectScanner {
    70  	if gcAsserts && block != block.findHead() {
    71  		runtimePanic("gc: object scanner must start at head")
    72  	}
    73  	scanner := gcObjectScanner{}
    74  	layout := *(*uintptr)(unsafe.Pointer(block.address()))
    75  	if layout == 0 {
    76  		// Unknown layout. Assume all words in the object could be pointers.
    77  		// This layout value below corresponds to a slice of pointers like:
    78  		//     make(*byte, N)
    79  		scanner.size = 1
    80  		scanner.bitmap = 1
    81  	} else if layout&1 != 0 {
    82  		// Layout is stored directly in the integer value.
    83  		// Determine format of bitfields in the integer.
    84  		const layoutBits = uint64(unsafe.Sizeof(layout) * 8)
    85  		var sizeFieldBits uint64
    86  		switch layoutBits { // note: this switch should be resolved at compile time
    87  		case 16:
    88  			sizeFieldBits = 4
    89  		case 32:
    90  			sizeFieldBits = 5
    91  		case 64:
    92  			sizeFieldBits = 6
    93  		default:
    94  			runtimePanic("unknown pointer size")
    95  		}
    96  
    97  		// Extract values from the bitfields.
    98  		// See comment at the top of this file for more information.
    99  		scanner.size = (layout >> 1) & (1<<sizeFieldBits - 1)
   100  		scanner.bitmap = layout >> (1 + sizeFieldBits)
   101  	} else {
   102  		// Layout is stored separately in a global object.
   103  		layoutAddr := unsafe.Pointer(layout)
   104  		scanner.size = *(*uintptr)(layoutAddr)
   105  		scanner.bitmapAddr = unsafe.Add(layoutAddr, unsafe.Sizeof(uintptr(0)))
   106  	}
   107  	return scanner
   108  }
   109  
   110  func (scanner *gcObjectScanner) pointerFree() bool {
   111  	if scanner.bitmapAddr != nil {
   112  		// While the format allows for large objects without pointers, this is
   113  		// optimized by the compiler so if bitmapAddr is set, we know that there
   114  		// are at least some pointers in the object.
   115  		return false
   116  	}
   117  	// If the bitmap is zero, there are definitely no pointers in the object.
   118  	return scanner.bitmap == 0
   119  }
   120  
   121  func (scanner *gcObjectScanner) nextIsPointer(word, parent, addrOfWord uintptr) bool {
   122  	index := scanner.index
   123  	scanner.index++
   124  	if scanner.index == scanner.size {
   125  		scanner.index = 0
   126  	}
   127  
   128  	if !isOnHeap(word) {
   129  		// Definitely isn't a pointer.
   130  		return false
   131  	}
   132  
   133  	// Might be a pointer. Now look at the object layout to know for sure.
   134  	if scanner.bitmapAddr != nil {
   135  		if (*(*uint8)(unsafe.Add(scanner.bitmapAddr, index/8))>>(index%8))&1 == 0 {
   136  			return false
   137  		}
   138  		return true
   139  	}
   140  	if (scanner.bitmap>>index)&1 == 0 {
   141  		// not a pointer!
   142  		return false
   143  	}
   144  
   145  	// Probably a pointer.
   146  	return true
   147  }