gc.c: add contiguous heap region support (mrb_gc_add_region)

Allow users to provide contiguous memory buffers for GC heap pages
via mrb_gc_add_region(). Region pages are carved from user-owned
buffers and never freed by the GC. This is the foundation for
bitmap GC on embedded targets with fragmented RAM.

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-02-26 08:54:01 +09:00
parent 74fb046544
commit 072855a242
2 changed files with 87 additions and 8 deletions
+2
View File
@@ -42,6 +42,7 @@ typedef struct mrb_gc {
struct mrb_heap_page *heaps; /* all heaps pages */
struct mrb_heap_page *free_heaps;/* heaps for allocation */
struct mrb_heap_page *sweeps; /* page where sweep starts */
struct mrb_heap_region *regions; /* contiguous heap regions */
struct RBasic *gray_stack[MRB_GRAY_STACK_SIZE]; /* stack of gray objects */
size_t gray_stack_top; /* top index of gray stack */
mrb_bool gray_overflow:1; /* gray stack overflowed; needs heap rescan */
@@ -69,6 +70,7 @@ typedef struct mrb_gc {
} mrb_gc;
MRB_API mrb_bool mrb_object_dead_p(struct mrb_state *mrb, struct RBasic *object);
MRB_API int mrb_gc_add_region(struct mrb_state *mrb, void *start, size_t size);
#define MRB_GC_RED 7
+85 -8
View File
@@ -166,9 +166,17 @@ typedef struct mrb_heap_page {
struct mrb_heap_page *next;
struct mrb_heap_page *free_next;
mrb_bool old:1;
mrb_bool region:1; /* from contiguous region, not malloc */
RVALUE objects[MRB_HEAP_PAGE_SIZE];
} mrb_heap_page;
typedef struct mrb_heap_region {
struct mrb_heap_region *next;
uint8_t *base; /* start of user buffer */
size_t size; /* buffer size in bytes */
uint16_t page_count; /* pages carved from region */
} mrb_heap_region;
#define GC_STEP_SIZE 1024
/* white: 001 or 010, black: 100, gray: 000, red:111 */
@@ -283,6 +291,17 @@ static mrb_bool
heap_p(mrb_gc *gc, const struct RBasic *object)
{
mrb_heap_page* page;
mrb_heap_region *region;
/* fast path: check contiguous regions via arithmetic */
for (region = gc->regions; region; region = region->next) {
uintptr_t addr = (uintptr_t)object;
uintptr_t base = (uintptr_t)region->base;
uintptr_t end = base + (size_t)region->page_count * sizeof(mrb_heap_page);
if (addr >= base && addr < end) {
return TRUE;
}
}
page = gc->heaps;
while (page) {
@@ -306,9 +325,17 @@ mrb_object_dead_p(mrb_state *mrb, struct RBasic *object)
}
static void
add_heap(mrb_state *mrb, mrb_gc *gc)
link_heap_page(mrb_gc *gc, mrb_heap_page *page)
{
page->next = gc->heaps;
gc->heaps = page;
page->free_next = gc->free_heaps;
gc->free_heaps = page;
}
static void
init_heap_page(mrb_heap_page *page)
{
mrb_heap_page *page = (mrb_heap_page*)mrb_calloc(mrb, 1, sizeof(mrb_heap_page));
RVALUE *p, *e;
RVALUE *prev = NULL;
@@ -318,12 +345,50 @@ add_heap(mrb_state *mrb, mrb_gc *gc)
prev = p;
}
page->freelist = prev;
}
page->next = gc->heaps;
gc->heaps = page;
static void
add_heap(mrb_state *mrb, mrb_gc *gc)
{
mrb_heap_page *page = (mrb_heap_page*)mrb_calloc(mrb, 1, sizeof(mrb_heap_page));
init_heap_page(page);
link_heap_page(gc, page);
}
page->free_next = gc->free_heaps;
gc->free_heaps = page;
MRB_API int
mrb_gc_add_region(mrb_state *mrb, void *start, size_t size)
{
mrb_gc *gc = &mrb->gc;
uint8_t *base = (uint8_t*)start;
mrb_heap_region *region;
uint16_t page_count;
uint16_t i;
/* align base to pointer size */
uintptr_t align = sizeof(void*);
uintptr_t offset = ((uintptr_t)base + align - 1) & ~(align - 1);
size -= (size_t)(offset - (uintptr_t)base);
base = (uint8_t*)offset;
page_count = (uint16_t)(size / sizeof(mrb_heap_page));
if (page_count == 0) return 0;
region = (mrb_heap_region*)mrb_malloc(mrb, sizeof(mrb_heap_region));
region->base = base;
region->size = size;
region->page_count = page_count;
region->next = gc->regions;
gc->regions = region;
/* carve pages from the contiguous buffer */
for (i = 0; i < page_count; i++) {
mrb_heap_page *page = (mrb_heap_page*)(base + (size_t)i * sizeof(mrb_heap_page));
memset(page, 0, sizeof(mrb_heap_page));
page->region = TRUE;
init_heap_page(page);
link_heap_page(gc, page);
}
return page_count;
}
#define DEFAULT_GC_INTERVAL_RATIO 200
@@ -345,6 +410,7 @@ mrb_gc_init(mrb_state *mrb, mrb_gc *gc)
gc->current_white_part = GC_WHITE_A;
gc->heaps = NULL;
gc->free_heaps = NULL;
gc->regions = NULL;
add_heap(mrb, gc);
gc->interval_ratio = DEFAULT_GC_INTERVAL_RATIO;
gc->step_ratio = DEFAULT_GC_STEP_RATIO;
@@ -370,7 +436,9 @@ free_heap(mrb_state *mrb, mrb_gc *gc)
if (p->as.free.tt != MRB_TT_FREE)
obj_free(mrb, &p->as.basic, TRUE);
}
mrb_free(mrb, tmp);
if (!tmp->region) {
mrb_free(mrb, tmp);
}
}
}
@@ -378,6 +446,15 @@ void
mrb_gc_destroy(mrb_state *mrb, mrb_gc *gc)
{
free_heap(mrb, gc);
/* free region descriptors (buffer memory belongs to the caller) */
{
mrb_heap_region *region = gc->regions;
while (region) {
mrb_heap_region *next = region->next;
mrb_free(mrb, region);
region = next;
}
}
#ifndef MRB_GC_FIXED_ARENA
mrb_free(mrb, gc->arena);
#endif
@@ -1120,7 +1197,7 @@ incremental_sweep_phase(mrb_state *mrb, mrb_gc *gc, size_t limit)
}
/* free dead slot */
if (dead_slot) {
if (dead_slot && !page->region) {
mrb_heap_page *next = page->next;
if (prev) prev->next = next;