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 }