wingo pushed a commit to branch wip-whippet in repository guile. commit f97906421eef769113be67a6c728f21cb4347de2 Author: Andy Wingo <wi...@igalia.com> AuthorDate: Sun May 1 14:46:36 2022 +0200
Sweep by block, not by slab This lets mutators run in parallel. There is a bug currently however with a race between stopping mutators marking their roots and other mutators still sweeping. Will fix in a followup. --- mark-sweep.h | 262 ++++++++++++++++++++++++++++------------------------------- 1 file changed, 124 insertions(+), 138 deletions(-) diff --git a/mark-sweep.h b/mark-sweep.h index 883eb5c84..7079b5aeb 100644 --- a/mark-sweep.h +++ b/mark-sweep.h @@ -138,10 +138,6 @@ struct gcobj_free { struct gcobj_free *next; }; -struct gcobj_freelists { - struct gcobj_free *by_size[MEDIUM_OBJECT_GRANULE_THRESHOLD]; -}; - // Objects larger than MEDIUM_OBJECT_GRANULE_THRESHOLD. struct gcobj_free_medium { struct gcobj_free_medium *next; @@ -159,13 +155,10 @@ struct gcobj { }; struct mark_space { - struct gcobj_freelists small_objects; - // Unordered list of medium objects. - struct gcobj_free_medium *medium_objects; uintptr_t low_addr; size_t extent; size_t heap_size; - uintptr_t sweep; + uintptr_t next_block; struct slab *slabs; size_t nslabs; }; @@ -197,7 +190,10 @@ struct mutator_mark_buf { struct mutator { // Segregated freelists of small objects. - struct gcobj_freelists small_objects; + struct gcobj_free *small_objects[MEDIUM_OBJECT_GRANULE_THRESHOLD]; + // Unordered list of medium objects. + struct gcobj_free_medium *medium_objects; + uintptr_t sweep; struct heap *heap; struct handle *roots; struct mutator_mark_buf mark_buf; @@ -218,10 +214,9 @@ static inline struct heap* mutator_heap(struct mutator *mutator) { } static inline struct gcobj_free** -get_small_object_freelist(struct gcobj_freelists *freelists, - size_t granules) { +get_small_object_freelist(struct mutator *mut, size_t granules) { ASSERT(granules > 0 && granules <= MEDIUM_OBJECT_GRANULE_THRESHOLD); - return &freelists->by_size[granules - 1]; + return &mut->small_objects[granules - 1]; } #define GC_HEADER uintptr_t _gc_header @@ -278,16 +273,10 @@ static inline void trace_one(struct gcobj *obj, void *mark_data) { } } -static void clear_small_freelists(struct gcobj_freelists *small) { - for (int i = 0; i < MEDIUM_OBJECT_GRANULE_THRESHOLD; i++) - small->by_size[i] = NULL; -} static void clear_mutator_freelists(struct mutator *mut) { - clear_small_freelists(&mut->small_objects); -} -static void clear_global_freelists(struct mark_space *space) { - clear_small_freelists(&space->small_objects); - space->medium_objects = NULL; + for (int i = 0; i < MEDIUM_OBJECT_GRANULE_THRESHOLD; i++) + mut->small_objects[i] = NULL; + mut->medium_objects = NULL; } static int heap_has_multiple_mutators(struct heap *heap) { @@ -435,9 +424,13 @@ static void wait_for_mutators_to_stop(struct heap *heap) { pthread_cond_wait(&heap->collector_cond, &heap->lock); } +static void finish_sweeping(struct mutator *mut); + static void mark_inactive_mutators(struct heap *heap) { - for (struct mutator *mut = heap->deactivated_mutators; mut; mut = mut->next) + for (struct mutator *mut = heap->deactivated_mutators; mut; mut = mut->next) { + finish_sweeping(mut); mark_controlling_mutator_roots(mut); + } } static void mark_global_roots(struct heap *heap) { @@ -480,6 +473,7 @@ static void pause_mutator_for_collection_with_lock(struct mutator *mut) NEVER_IN static void pause_mutator_for_collection_with_lock(struct mutator *mut) { struct heap *heap = mutator_heap(mut); ASSERT(mutators_are_stopping(heap)); + finish_sweeping(mut); mark_controlling_mutator_roots(mut); pause_mutator_for_collection(heap); clear_mutator_freelists(mut); @@ -489,6 +483,7 @@ static void pause_mutator_for_collection_without_lock(struct mutator *mut) NEVER static void pause_mutator_for_collection_without_lock(struct mutator *mut) { struct heap *heap = mutator_heap(mut); ASSERT(mutators_are_stopping(heap)); + finish_sweeping(mut); mark_stopping_mutator_roots(mut); heap_lock(heap); pause_mutator_for_collection(heap); @@ -503,7 +498,7 @@ static inline void maybe_pause_mutator_for_collection(struct mutator *mut) { } static void reset_sweeper(struct mark_space *space) { - space->sweep = (uintptr_t) &space->slabs[0].blocks; + space->next_block = (uintptr_t) &space->slabs[0].blocks; } static void collect(struct mutator *mut) { @@ -520,7 +515,6 @@ static void collect(struct mutator *mut) { mark_global_roots(heap); tracer_trace(heap); tracer_release(heap); - clear_global_freelists(space); reset_sweeper(space); heap->count++; large_object_space_finish_gc(lospace); @@ -535,10 +529,10 @@ static void push_free(struct gcobj_free **loc, struct gcobj_free *obj) { *loc = obj; } -static void push_small(struct gcobj_freelists *small_objects, void *region, +static void push_small(struct mutator *mut, void *region, size_t granules, size_t region_granules) { uintptr_t addr = (uintptr_t) region; - struct gcobj_free **loc = get_small_object_freelist(small_objects, granules); + struct gcobj_free **loc = get_small_object_freelist(mut, granules); while (granules <= region_granules) { push_free(loc, (struct gcobj_free*) addr); region_granules -= granules; @@ -546,34 +540,32 @@ static void push_small(struct gcobj_freelists *small_objects, void *region, } // Fit any remaining granules into smaller freelist. if (region_granules) - push_free(get_small_object_freelist(small_objects, region_granules), + push_free(get_small_object_freelist(mut, region_granules), (struct gcobj_free*) addr); } -static void push_medium(struct mark_space *space, void *region, size_t granules) { +static void push_medium(struct mutator *mut, void *region, size_t granules) { struct gcobj_free_medium *medium = region; - medium->next = space->medium_objects; + medium->next = mut->medium_objects; medium->granules = granules; - space->medium_objects = medium; + mut->medium_objects = medium; } -static void reclaim(struct mark_space *space, - struct gcobj_freelists *small_objects, +static void reclaim(struct mutator *mut, size_t small_object_granules, void *region, size_t region_granules) { if (small_object_granules == 0) small_object_granules = region_granules; if (small_object_granules <= MEDIUM_OBJECT_GRANULE_THRESHOLD) - push_small(small_objects, region, small_object_granules, region_granules); + push_small(mut, region, small_object_granules, region_granules); else - push_medium(space, region, region_granules); + push_medium(mut, region, region_granules); } -static void split_medium_object(struct mark_space *space, - struct gcobj_freelists *small_objects, - struct gcobj_free_medium *medium, - size_t granules) { +static void split_medium_object(struct mutator *mut, + struct gcobj_free_medium *medium, + size_t granules) { size_t medium_granules = medium->granules; ASSERT(medium_granules >= granules); ASSERT(granules >= MEDIUM_OBJECT_GRANULE_THRESHOLD); @@ -587,11 +579,11 @@ static void split_medium_object(struct mark_space *space, return; char *tail = ((char*)medium) + granules * GRANULE_SIZE; - reclaim(space, small_objects, 0, tail, medium_granules - granules); + reclaim(mut, 0, tail, medium_granules - granules); } static void unlink_medium_object(struct gcobj_free_medium **prev, - struct gcobj_free_medium *medium) { + struct gcobj_free_medium *medium) { *prev = medium->next; } @@ -632,28 +624,51 @@ static size_t next_mark(const uint8_t *mark, size_t limit) { return limit; } +static uintptr_t mark_space_next_block(struct mark_space *space) { + uintptr_t block = atomic_load_explicit(&space->next_block, + memory_order_acquire); + uintptr_t next_block; + do { + if (block == 0) + return 0; + + next_block = block + BLOCK_SIZE; + if (next_block % SLAB_SIZE == 0) { + uintptr_t hi_addr = space->low_addr + space->extent; + if (next_block == hi_addr) + next_block = 0; + else + next_block += META_BLOCKS_PER_SLAB * BLOCK_SIZE; + } + } while (!atomic_compare_exchange_weak(&space->next_block, &block, + next_block)); + return block; +} + // Sweep some heap to reclaim free space. Return 1 if there is more // heap to sweep, or 0 if we reached the end. -static int sweep(struct mark_space *space, - struct gcobj_freelists *small_objects, +static int sweep(struct mutator *mut, size_t small_object_granules, size_t medium_object_granules) { - // Sweep until we have reclaimed 32 kB of free memory, or we reach the - // end of the heap. - ssize_t to_reclaim = 32 * 1024 / GRANULE_SIZE; - uintptr_t sweep = space->sweep; - uintptr_t limit = align_up(sweep, SLAB_SIZE); + // Sweep until we have reclaimed memory corresponding to twice the + // size of the smallest medium object, or we reach the end of the + // block. + ssize_t to_reclaim = 2 * MEDIUM_OBJECT_GRANULE_THRESHOLD; + uintptr_t sweep = mut->sweep; + uintptr_t limit = align_up(sweep, BLOCK_SIZE); if (sweep == limit) { - if (sweep == space->low_addr + space->extent) + sweep = mark_space_next_block(heap_mark_space(mutator_heap(mut))); + if (sweep == 0) { + mut->sweep = 0; return 0; - // Assumes contiguous slabs. To relax later. - sweep += META_BLOCKS_PER_SLAB * BLOCK_SIZE; - limit += SLAB_SIZE; + } + limit = sweep + BLOCK_SIZE; } while (to_reclaim > 0 && sweep < limit) { - uint8_t* mark = mark_byte(space, (struct gcobj*)sweep); + ASSERT((sweep & (GRANULE_SIZE - 1)) == 0); + uint8_t* mark = object_metadata_byte((struct gcobj*)sweep); size_t limit_granules = (limit - sweep) >> GRANULE_SIZE_LOG_2; if (limit_granules > to_reclaim) { if (small_object_granules == 0) { @@ -665,10 +680,10 @@ static int sweep(struct mark_space *space, } size_t free_granules = next_mark(mark, limit_granules); if (free_granules) { + ASSERT(free_granules <= limit_granules); size_t free_bytes = free_granules * GRANULE_SIZE; clear_memory(sweep + sizeof(uintptr_t), free_bytes - sizeof(uintptr_t)); - reclaim(space, small_objects, small_object_granules, (void*)sweep, - free_granules); + reclaim(mut, small_object_granules, (void*)sweep, free_granules); sweep += free_bytes; to_reclaim -= free_granules; @@ -682,10 +697,23 @@ static int sweep(struct mark_space *space, sweep += live_object_granules((struct gcobj *)sweep) * GRANULE_SIZE; } - space->sweep = sweep; + mut->sweep = sweep; return 1; } +// Another thread is triggering GC. Before we stop, finish clearing the +// mark bytes for the mutator's block, and release the block. +static void finish_sweeping(struct mutator *mut) { + uintptr_t sweep = mut->sweep; + if (sweep) { + uintptr_t limit = align_up(sweep, BLOCK_SIZE); + uint8_t* mark = object_metadata_byte((struct gcobj*)sweep); + size_t limit_granules = (limit - sweep) >> GRANULE_SIZE_LOG_2; + memset(mark, 0, limit_granules); + mut->sweep = 0; + } +} + static void* allocate_large(struct mutator *mut, enum alloc_kind kind, size_t granules) { struct heap *heap = mutator_heap(mut); @@ -693,6 +721,9 @@ static void* allocate_large(struct mutator *mut, enum alloc_kind kind, size_t size = granules * GRANULE_SIZE; size_t npages = large_object_space_npages(space, size); + + heap_lock(heap); + if (!heap_steal_pages(heap, npages)) { collect(mut); if (!heap_steal_pages(heap, npages)) { @@ -705,6 +736,8 @@ static void* allocate_large(struct mutator *mut, enum alloc_kind kind, if (!ret) ret = large_object_space_obtain_and_alloc(space, npages); + heap_unlock(heap); + if (!ret) { perror("weird: we have the space but mmap didn't work"); abort(); @@ -716,156 +749,112 @@ static void* allocate_large(struct mutator *mut, enum alloc_kind kind, static void* allocate_medium(struct mutator *mut, enum alloc_kind kind, size_t granules) { - struct heap *heap = mutator_heap(mut); - struct mark_space *space = heap_mark_space(heap); - struct gcobj_freelists *small_objects = heap_has_multiple_mutators(heap) ? - &space->small_objects : &mut->small_objects; - maybe_pause_mutator_for_collection(mut); - heap_lock(heap); - - while (mutators_are_stopping(heap)) - pause_mutator_for_collection_with_lock(mut); - int swept_from_beginning = 0; while (1) { struct gcobj_free_medium *already_scanned = NULL; do { - struct gcobj_free_medium **prev = &space->medium_objects; - for (struct gcobj_free_medium *medium = space->medium_objects; + struct gcobj_free_medium **prev = &mut->medium_objects; + for (struct gcobj_free_medium *medium = mut->medium_objects; medium != already_scanned; prev = &medium->next, medium = medium->next) { if (medium->granules >= granules) { unlink_medium_object(prev, medium); - split_medium_object(space, small_objects, medium, granules); - heap_unlock(heap); + split_medium_object(mut, medium, granules); struct gcobj *obj = (struct gcobj *)medium; obj->tag = tag_live(kind); return medium; } } - already_scanned = space->medium_objects; - } while (sweep(space, small_objects, 0, granules)); + already_scanned = mut->medium_objects; + } while (sweep(mut, 0, granules)); - // No medium object, and we swept across the whole heap. Collect. + struct heap *heap = mutator_heap(mut); if (swept_from_beginning) { fprintf(stderr, "ran out of space, heap size %zu\n", heap->size); abort(); } else { - collect(mut); + heap_lock(heap); + if (mutators_are_stopping(heap)) + pause_mutator_for_collection_with_lock(mut); + else + collect(mut); + heap_unlock(heap); swept_from_beginning = 1; } } } -static int fill_small_from_local(struct gcobj_freelists *small_objects, - size_t granules) { +static int fill_small_from_small(struct mutator *mut, size_t granules) { // Precondition: the freelist for KIND is already empty. - ASSERT(!*get_small_object_freelist(small_objects, granules)); + ASSERT(!*get_small_object_freelist(mut, granules)); // See if there are small objects already on the freelists // that can be split. for (size_t next_size = granules + 1; next_size <= MEDIUM_OBJECT_GRANULE_THRESHOLD; next_size++) { - struct gcobj_free **loc = get_small_object_freelist(small_objects, - next_size); + struct gcobj_free **loc = get_small_object_freelist(mut, next_size); if (*loc) { struct gcobj_free *ret = *loc; *loc = ret->next; - push_small(small_objects, ret, granules, next_size); + push_small(mut, ret, granules, next_size); return 1; } } return 0; } -// with heap lock -static int fill_small_from_medium(struct mark_space *space, - struct gcobj_freelists *small_objects, - size_t granules) { +static int fill_small_from_medium(struct mutator *mut, size_t granules) { // If there is a medium object, take and split it. - struct gcobj_free_medium *medium = space->medium_objects; + struct gcobj_free_medium *medium = mut->medium_objects; if (!medium) return 0; - unlink_medium_object(&space->medium_objects, medium); + unlink_medium_object(&mut->medium_objects, medium); ASSERT(medium->granules >= MEDIUM_OBJECT_GRANULE_THRESHOLD); - split_medium_object(space, small_objects, medium, - MEDIUM_OBJECT_GRANULE_THRESHOLD); - push_small(small_objects, medium, granules, MEDIUM_OBJECT_GRANULE_THRESHOLD); + split_medium_object(mut, medium, MEDIUM_OBJECT_GRANULE_THRESHOLD); + push_small(mut, medium, granules, MEDIUM_OBJECT_GRANULE_THRESHOLD); return 1; } -static int fill_small_from_global_small(struct mark_space *space, - struct gcobj_freelists *small_objects, - size_t granules) { - struct gcobj_free **src = - get_small_object_freelist(&space->small_objects, granules); - if (*src) { - struct gcobj_free **dst = get_small_object_freelist(small_objects, granules); - ASSERT(!*dst); - *dst = *src; - *src = NULL; - return 1; - } - return 0; -} - -static void fill_small_from_global(struct mutator *mut, - size_t granules) NEVER_INLINE; -static void fill_small_from_global(struct mutator *mut, - size_t granules) { - struct gcobj_freelists *small_objects = &mut->small_objects; - struct heap *heap = mutator_heap(mut); - struct mark_space *space = heap_mark_space(heap); - +static void fill_small(struct mutator *mut, size_t granules) NEVER_INLINE; +static void fill_small(struct mutator *mut, size_t granules) { maybe_pause_mutator_for_collection(mut); - heap_lock(heap); - - while (mutators_are_stopping(heap)) - pause_mutator_for_collection_with_lock(mut); - int swept_from_beginning = 0; while (1) { - if (fill_small_from_global_small(space, small_objects, granules)) + if (fill_small_from_small(mut, granules)) break; - if (fill_small_from_medium(space, small_objects, granules)) + if (fill_small_from_medium(mut, granules)) break; - // By default, pull in 16 kB of data at a time. - if (!sweep(space, small_objects, granules, 0)) { + if (!sweep(mut, granules, 0)) { + struct heap *heap = mutator_heap(mut); if (swept_from_beginning) { fprintf(stderr, "ran out of space, heap size %zu\n", heap->size); abort(); } else { - collect(mut); + heap_lock(heap); + if (mutators_are_stopping(heap)) + pause_mutator_for_collection_with_lock(mut); + else + collect(mut); + heap_unlock(heap); swept_from_beginning = 1; } } - if (*get_small_object_freelist(small_objects, granules)) + if (*get_small_object_freelist(mut, granules)) break; } - heap_unlock(heap); -} - -static void fill_small(struct mutator *mut, size_t granules) { - // See if there are small objects already on the local freelists that - // can be split. - if (fill_small_from_local(&mut->small_objects, granules)) - return; - - fill_small_from_global(mut, granules); } static inline void* allocate_small(struct mutator *mut, enum alloc_kind kind, size_t granules) { ASSERT(granules > 0); // allocating 0 granules would be silly - struct gcobj_free **loc = - get_small_object_freelist(&mut->small_objects, granules); + struct gcobj_free **loc = get_small_object_freelist(mut, granules); if (!*loc) fill_small(mut, granules); struct gcobj_free *ret = *loc; @@ -935,10 +924,7 @@ static int mark_space_init(struct mark_space *space, struct heap *heap) { space->nslabs = nslabs; space->low_addr = (uintptr_t) slabs; space->extent = size; - space->sweep = space->low_addr + space->extent; - for (size_t i = 0; i < nslabs; i++) - reclaim(space, NULL, 0, &slabs[i].blocks, - NONMETA_BLOCKS_PER_SLAB * GRANULES_PER_BLOCK); + reset_sweeper(space); return 1; }