github.com/aykevl/tinygo@v0.5.0/src/runtime/gc_marksweep.go (about) 1 // +build gc.marksweep 2 3 package runtime 4 5 // This memory manager is a textbook mark/sweep implementation, heavily inspired 6 // by the MicroPython garbage collector. 7 // 8 // The memory manager internally uses blocks of 4 pointers big (see 9 // bytesPerBlock). Every allocation first rounds up to this size to align every 10 // block. It will first try to find a chain of blocks that is big enough to 11 // satisfy the allocation. If it finds one, it marks the first one as the "head" 12 // and the following ones (if any) as the "tail" (see below). If it cannot find 13 // any free space, it will perform a garbage collection cycle and try again. If 14 // it still cannot find any free space, it gives up. 15 // 16 // Every block has some metadata, which is stored at the beginning of the heap. 17 // The four states are "free", "head", "tail", and "mark". During normal 18 // operation, there are no marked blocks. Every allocated object starts with a 19 // "head" and is followed by "tail" blocks. The reason for this distinction is 20 // that this way, the start and end of every object can be found easily. 21 // 22 // Metadata is stored in a special area at the beginning of the heap, in the 23 // area heapStart..poolStart. The actual blocks are stored in 24 // poolStart..heapEnd. 25 // 26 // More information: 27 // https://github.com/micropython/micropython/wiki/Memory-Manager 28 // "The Garbage Collection Handbook" by Richard Jones, Antony Hosking, Eliot 29 // Moss. 30 31 import ( 32 "unsafe" 33 ) 34 35 // Set gcDebug to true to print debug information. 36 const ( 37 gcDebug = false // print debug info 38 gcAsserts = gcDebug // perform sanity checks 39 ) 40 41 // Some globals + constants for the entire GC. 42 43 const ( 44 wordsPerBlock = 4 // number of pointers in an allocated block 45 bytesPerBlock = wordsPerBlock * unsafe.Sizeof(heapStart) 46 stateBits = 2 // how many bits a block state takes (see blockState type) 47 blocksPerStateByte = 8 / stateBits 48 ) 49 50 var ( 51 poolStart uintptr // the first heap pointer 52 nextAlloc gcBlock // the next block that should be tried by the allocator 53 endBlock gcBlock // the block just past the end of the available space 54 ) 55 56 // zeroSizedAlloc is just a sentinel that gets returned when allocating 0 bytes. 57 var zeroSizedAlloc uint8 58 59 // Provide some abstraction over heap blocks. 60 61 // blockState stores the four states in which a block can be. It is two bits in 62 // size. 63 type blockState uint8 64 65 const ( 66 blockStateFree blockState = 0 // 00 67 blockStateHead blockState = 1 // 01 68 blockStateTail blockState = 2 // 10 69 blockStateMark blockState = 3 // 11 70 blockStateMask blockState = 3 // 11 71 ) 72 73 // String returns a human-readable version of the block state, for debugging. 74 func (s blockState) String() string { 75 switch s { 76 case blockStateFree: 77 return "free" 78 case blockStateHead: 79 return "head" 80 case blockStateTail: 81 return "tail" 82 case blockStateMark: 83 return "mark" 84 default: 85 // must never happen 86 return "!err" 87 } 88 } 89 90 // The block number in the pool. 91 type gcBlock uintptr 92 93 // blockFromAddr returns a block given an address somewhere in the heap (which 94 // might not be heap-aligned). 95 func blockFromAddr(addr uintptr) gcBlock { 96 return gcBlock((addr - poolStart) / bytesPerBlock) 97 } 98 99 // Return a pointer to the start of the allocated object. 100 func (b gcBlock) pointer() unsafe.Pointer { 101 return unsafe.Pointer(b.address()) 102 } 103 104 // Return the address of the start of the allocated object. 105 func (b gcBlock) address() uintptr { 106 return poolStart + uintptr(b)*bytesPerBlock 107 } 108 109 // findHead returns the head (first block) of an object, assuming the block 110 // points to an allocated object. It returns the same block if this block 111 // already points to the head. 112 func (b gcBlock) findHead() gcBlock { 113 for b.state() == blockStateTail { 114 b-- 115 } 116 return b 117 } 118 119 // findNext returns the first block just past the end of the tail. This may or 120 // may not be the head of an object. 121 func (b gcBlock) findNext() gcBlock { 122 if b.state() == blockStateHead { 123 b++ 124 } 125 for b.state() == blockStateTail { 126 b++ 127 } 128 return b 129 } 130 131 // State returns the current block state. 132 func (b gcBlock) state() blockState { 133 stateBytePtr := (*uint8)(unsafe.Pointer(heapStart + uintptr(b/blocksPerStateByte))) 134 return blockState(*stateBytePtr>>((b%blocksPerStateByte)*2)) % 4 135 } 136 137 // setState sets the current block to the given state, which must contain more 138 // bits than the current state. Allowed transitions: from free to any state and 139 // from head to mark. 140 func (b gcBlock) setState(newState blockState) { 141 stateBytePtr := (*uint8)(unsafe.Pointer(heapStart + uintptr(b/blocksPerStateByte))) 142 *stateBytePtr |= uint8(newState << ((b % blocksPerStateByte) * 2)) 143 if gcAsserts && b.state() != newState { 144 runtimePanic("gc: setState() was not successful") 145 } 146 } 147 148 // markFree sets the block state to free, no matter what state it was in before. 149 func (b gcBlock) markFree() { 150 stateBytePtr := (*uint8)(unsafe.Pointer(heapStart + uintptr(b/blocksPerStateByte))) 151 *stateBytePtr &^= uint8(blockStateMask << ((b % blocksPerStateByte) * 2)) 152 if gcAsserts && b.state() != blockStateFree { 153 runtimePanic("gc: markFree() was not successful") 154 } 155 } 156 157 // unmark changes the state of the block from mark to head. It must be marked 158 // before calling this function. 159 func (b gcBlock) unmark() { 160 if gcAsserts && b.state() != blockStateMark { 161 runtimePanic("gc: unmark() on a block that is not marked") 162 } 163 clearMask := blockStateMask ^ blockStateHead // the bits to clear from the state 164 stateBytePtr := (*uint8)(unsafe.Pointer(heapStart + uintptr(b/blocksPerStateByte))) 165 *stateBytePtr &^= uint8(clearMask << ((b % blocksPerStateByte) * 2)) 166 if gcAsserts && b.state() != blockStateHead { 167 runtimePanic("gc: unmark() was not successful") 168 } 169 } 170 171 // Initialize the memory allocator. 172 // No memory may be allocated before this is called. That means the runtime and 173 // any packages the runtime depends upon may not allocate memory during package 174 // initialization. 175 func init() { 176 totalSize := heapEnd - heapStart 177 178 // Allocate some memory to keep 2 bits of information about every block. 179 metadataSize := totalSize / (blocksPerStateByte * bytesPerBlock) 180 181 // Align the pool. 182 poolStart = (heapStart + metadataSize + (bytesPerBlock - 1)) &^ (bytesPerBlock - 1) 183 poolEnd := heapEnd &^ (bytesPerBlock - 1) 184 numBlocks := (poolEnd - poolStart) / bytesPerBlock 185 endBlock = gcBlock(numBlocks) 186 if gcDebug { 187 println("heapStart: ", heapStart) 188 println("heapEnd: ", heapEnd) 189 println("total size: ", totalSize) 190 println("metadata size: ", metadataSize) 191 println("poolStart: ", poolStart) 192 println("# of blocks: ", numBlocks) 193 println("# of block states:", metadataSize*blocksPerStateByte) 194 } 195 if gcAsserts && metadataSize*blocksPerStateByte < numBlocks { 196 // sanity check 197 runtimePanic("gc: metadata array is too small") 198 } 199 200 // Set all block states to 'free'. 201 memzero(unsafe.Pointer(heapStart), metadataSize) 202 } 203 204 // alloc tries to find some free space on the heap, possibly doing a garbage 205 // collection cycle if needed. If no space is free, it panics. 206 func alloc(size uintptr) unsafe.Pointer { 207 if size == 0 { 208 return unsafe.Pointer(&zeroSizedAlloc) 209 } 210 211 neededBlocks := (size + (bytesPerBlock - 1)) / bytesPerBlock 212 213 // Continue looping until a run of free blocks has been found that fits the 214 // requested size. 215 index := nextAlloc 216 numFreeBlocks := uintptr(0) 217 heapScanCount := uint8(0) 218 for { 219 if index == nextAlloc { 220 if heapScanCount == 0 { 221 heapScanCount = 1 222 } else if heapScanCount == 1 { 223 // The entire heap has been searched for free memory, but none 224 // could be found. Run a garbage collection cycle to reclaim 225 // free memory and try again. 226 heapScanCount = 2 227 GC() 228 } else { 229 // Even after garbage collection, no free memory could be found. 230 runtimePanic("out of memory") 231 } 232 } 233 234 // Wrap around the end of the heap. 235 if index == endBlock { 236 index = 0 237 // Reset numFreeBlocks as allocations cannot wrap. 238 numFreeBlocks = 0 239 } 240 241 // Is the block we're looking at free? 242 if index.state() != blockStateFree { 243 // This block is in use. Try again from this point. 244 numFreeBlocks = 0 245 index++ 246 continue 247 } 248 numFreeBlocks++ 249 index++ 250 251 // Are we finished? 252 if numFreeBlocks == neededBlocks { 253 // Found a big enough range of free blocks! 254 nextAlloc = index 255 thisAlloc := index - gcBlock(neededBlocks) 256 if gcDebug { 257 println("found memory:", thisAlloc.pointer(), int(size)) 258 } 259 260 // Set the following blocks as being allocated. 261 thisAlloc.setState(blockStateHead) 262 for i := thisAlloc + 1; i != nextAlloc; i++ { 263 i.setState(blockStateTail) 264 } 265 266 // Return a pointer to this allocation. 267 pointer := thisAlloc.pointer() 268 memzero(pointer, size) 269 return pointer 270 } 271 } 272 } 273 274 func free(ptr unsafe.Pointer) { 275 // TODO: free blocks on request, when the compiler knows they're unused. 276 } 277 278 // GC performs a garbage collection cycle. 279 func GC() { 280 if gcDebug { 281 println("running collection cycle...") 282 } 283 284 // Mark phase: mark all reachable objects, recursively. 285 markRoots(globalsStart, globalsEnd) 286 markRoots(getCurrentStackPointer(), stackTop) // assume a descending stack 287 288 // Sweep phase: free all non-marked objects and unmark marked objects for 289 // the next collection cycle. 290 sweep() 291 292 // Show how much has been sweeped, for debugging. 293 if gcDebug { 294 dumpHeap() 295 } 296 } 297 298 // markRoots reads all pointers from start to end (exclusive) and if they look 299 // like a heap pointer and are unmarked, marks them and scans that object as 300 // well (recursively). The start and end parameters must be valid pointers and 301 // must be aligned. 302 func markRoots(start, end uintptr) { 303 if gcDebug { 304 println("mark from", start, "to", end, int(end-start)) 305 } 306 307 for addr := start; addr != end; addr += unsafe.Sizeof(addr) { 308 root := *(*uintptr)(unsafe.Pointer(addr)) 309 if looksLikePointer(root) { 310 block := blockFromAddr(root) 311 head := block.findHead() 312 if head.state() != blockStateMark { 313 if gcDebug { 314 println("found unmarked pointer", root, "at address", addr) 315 } 316 head.setState(blockStateMark) 317 next := block.findNext() 318 // TODO: avoid recursion as much as possible 319 markRoots(head.address(), next.address()) 320 } 321 } 322 } 323 } 324 325 // Sweep goes through all memory and frees unmarked memory. 326 func sweep() { 327 freeCurrentObject := false 328 for block := gcBlock(0); block < endBlock; block++ { 329 switch block.state() { 330 case blockStateHead: 331 // Unmarked head. Free it, including all tail blocks following it. 332 block.markFree() 333 freeCurrentObject = true 334 case blockStateTail: 335 if freeCurrentObject { 336 // This is a tail object following an unmarked head. 337 // Free it now. 338 block.markFree() 339 } 340 case blockStateMark: 341 // This is a marked object. The next tail blocks must not be freed, 342 // but the mark bit must be removed so the next GC cycle will 343 // collect this object if it is unreferenced then. 344 block.unmark() 345 freeCurrentObject = false 346 } 347 } 348 } 349 350 // looksLikePointer returns whether this could be a pointer. Currently, it 351 // simply returns whether it lies anywhere in the heap. Go allows interior 352 // pointers so we can't check alignment or anything like that. 353 func looksLikePointer(ptr uintptr) bool { 354 return ptr >= poolStart && ptr < heapEnd 355 } 356 357 // dumpHeap can be used for debugging purposes. It dumps the state of each heap 358 // block to standard output. 359 func dumpHeap() { 360 println("heap:") 361 for block := gcBlock(0); block < endBlock; block++ { 362 switch block.state() { 363 case blockStateHead: 364 print("*") 365 case blockStateTail: 366 print("-") 367 case blockStateMark: 368 print("#") 369 default: // free 370 print("ยท") 371 } 372 if block%64 == 63 || block+1 == endBlock { 373 println() 374 } 375 } 376 } 377 378 func KeepAlive(x interface{}) { 379 // Unimplemented. Only required with SetFinalizer(). 380 } 381 382 func SetFinalizer(obj interface{}, finalizer interface{}) { 383 // Unimplemented. 384 }