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  }