diff --git a/Memory-Manager.md b/Memory-Manager.md index 310b98d..6b09c90 100644 --- a/Memory-Manager.md +++ b/Memory-Manager.md @@ -89,9 +89,6 @@ def gc_collect(): # implemented by ports # add root pointers (platform-dependent) gc_collect_root(roots) - # mark all reachable blocks - gc_mark() - # if there was a stack overflow, scan all memory for reachable blocks gc_deal_with_stack_overflow() @@ -100,41 +97,48 @@ def gc_collect(): # implemented by ports def gc_collect_root(roots): for root in roots: - # Add this root to the (empty) stack. len(gc.stack) will be 1. - may_add_to_stack(root) + # Test whether this could be a pointer. + if looks_like_pointer(root): + block = block_from_ptr(root) + if block.state == HEAD: + # Set the mark bit on this block. + block.state = MARK - # Shrink the stack to zero, hopefully without GC stack overflow. - gc_drain_stack() + # Mark all descendants of this root. + gc_mark_subtree(block) -def may_add_to_stack(pointer): # VERIFY_MARK_AND_PUSH - # If this is a pointer and points to an unmarked block, mark it and try to - # push it on the stack. - if is_heap_pointer(pointer): - # Get the block number this pointer points to. - block = BLOCK_FROM_PTR(pointer) - if block.state == HEAD: - block.state = MARK - # Is there space left on the GC stack? - if len(gc.stack) < MICROPY_ALLOC_GC_STACK_SIZE: - # Yes, add this block to the stack. - gc.stack.append(block) - else: - # Sadly, no. The mark phase can continue, but the whole memory will need - # to be scanned again (some marked blocks may have unmarked children). - gc.stack_overflow = True +def gc_mark_subtree(block): + # The passed in block must already been marked. -def gc_drain_stack(): # Try to reduce the stack, and don't return until it's empty. But with every # reduction, multiple blocks may be added to the stack so this function may # actually hit a stack overflow. - while len(gc.stack) > 0: - block = gc.stack.pop() + while True: + # Check this block's children. # Each block contains 4 memory words (on a 32-bit system with 16-byte # blocks). These words may be pointers to blocks on the heap, but may also # be other things like integers, parts of raw data (strings, bytecode, etc.) # or pointers to memory outside of the heap. - for pointer in block: - may_add_to_stack(pointer) + for pointer in reversed(block.all_pointers_in_chain()[1:]): + if looks_like_pointer(pointer): + childblock = block_from_ptr(pointer) + if childblock.state == HEAD: + childblock.state = MARK + # Is there space left on the GC stack? + if len(gc.stack) < MICROPY_ALLOC_GC_STACK_SIZE: + # Yes, add this block to the stack. + gc.stack.append(block) + else: + # Sadly, no. The mark phase can continue, but the whole memory will need + # to be scanned again (some marked blocks may have unmarked children). + gc.stack_overflow = True + + # Are there any blocks on the stack? + if len(gc.stack) == 0: + break # No, stack is empty, we're done. + + # pop the next block off the stack + block = gc.stack.pop() def gc_deal_with_stack_overflow(): # Keep scanning the whole memory until all marked blocks don't have unmarked @@ -143,16 +147,13 @@ def gc_deal_with_stack_overflow(): # The stack is there to make the mark phase more efficient, i.e. it avoids # having to scan the whole memory too often. But sometimes, the stack is too # small and requires a full scan (like at the beginning of the mark phase). - gc.stack_overflow = True # indicates the whole memory needs to be scanned while gc.stack_overflow: gc.stack_overflow = False - gc.stack = [] for block in gc.memory: # A block can have the states FREE, HEAD, TAIL or MARK, as set in the # allocation table. if block.state == MARK: - gc.stack.append(block) - gc_drain_stack() + gc_mark_subtree(block) def gc_sweep(): # Free unmarked blocks and unmark marked blocks.