/* -*- coding: utf-8 -*- * ---------------------------------------------------------------------- * Copyright © 2011-2014, RedJack, LLC. * All rights reserved. * * Please see the COPYING file in this distribution for license details. * ---------------------------------------------------------------------- */ #include #include "libcork/config/config.h" #include "libcork/core/allocator.h" #include "libcork/core/gc.h" #include "libcork/core/types.h" #include "libcork/ds/dllist.h" #include "libcork/threads/basics.h" #if !defined(CORK_DEBUG_GC) #define CORK_DEBUG_GC 0 #endif #if CORK_DEBUG_GC #include #define DEBUG(...) fprintf(stderr, __VA_ARGS__) #else #define DEBUG(...) /* no debug messages */ #endif /*----------------------------------------------------------------------- * GC context life cycle */ #define ROOTS_SIZE 1024 /* An internal structure allocated with every garbage-collected object. */ struct cork_gc_header; /* A garbage collector context. */ struct cork_gc { /* The number of used entries in roots. */ size_t root_count; /* The possible roots of garbage cycles */ struct cork_gc_header *roots[ROOTS_SIZE]; }; cork_tls(struct cork_gc, cork_gc); static void cork_gc_collect_cycles(struct cork_gc *gc); /*----------------------------------------------------------------------- * Garbage collection functions */ struct cork_gc_header { /* The current reference count for this object, along with its color * during the mark/sweep process. */ volatile int ref_count_color; /* The allocated size of this garbage-collected object (including * the header). */ size_t allocated_size; /* The garbage collection interface for this object. */ struct cork_gc_obj_iface *iface; }; /* * Structure of ref_count_color: * * +-----+---+---+---+---+---+ * | ... | 4 | 3 | 2 | 1 | 0 | * +-----+---+---+---+---+---+ * ref_count | color * | * buffered --/ */ #define cork_gc_ref_count_color(count, buffered, color) \ (((count) << 3) | ((buffered) << 2) | (color)) #define cork_gc_get_ref_count(hdr) \ ((hdr)->ref_count_color >> 3) #define cork_gc_inc_ref_count(hdr) \ do { \ (hdr)->ref_count_color += (1 << 3); \ } while (0) #define cork_gc_dec_ref_count(hdr) \ do { \ (hdr)->ref_count_color -= (1 << 3); \ } while (0) #define cork_gc_get_color(hdr) \ ((hdr)->ref_count_color & 0x3) #define cork_gc_set_color(hdr, color) \ do { \ (hdr)->ref_count_color = \ ((hdr)->ref_count_color & ~0x3) | (color & 0x3); \ } while (0) #define cork_gc_get_buffered(hdr) \ (((hdr)->ref_count_color & 0x4) != 0) #define cork_gc_set_buffered(hdr, buffered) \ do { \ (hdr)->ref_count_color = \ ((hdr)->ref_count_color & ~0x4) | (((buffered) & 1) << 2); \ } while (0) #define cork_gc_free(hdr) \ do { \ if ((hdr)->iface->free != NULL) { \ (hdr)->iface->free(cork_gc_get_object((hdr))); \ } \ cork_free((hdr), (hdr)->allocated_size); \ } while (0) #define cork_gc_recurse(gc, hdr, recurser) \ do { \ if ((hdr)->iface->recurse != NULL) { \ (hdr)->iface->recurse \ ((gc), cork_gc_get_object((hdr)), (recurser), NULL); \ } \ } while (0) enum cork_gc_color { /* In use or free */ GC_BLACK = 0, /* Possible member of garbage cycle */ GC_GRAY = 1, /* Member of garbage cycle */ GC_WHITE = 2, /* Possible root of garbage cycle */ GC_PURPLE = 3 }; #define cork_gc_get_header(obj) \ (((struct cork_gc_header *) (obj)) - 1) #define cork_gc_get_object(hdr) \ ((void *) (((struct cork_gc_header *) (hdr)) + 1)) void cork_gc_init(void) { cork_gc_get(); } void cork_gc_done(void) { cork_gc_collect_cycles(cork_gc_get()); } void * cork_gc_alloc(size_t instance_size, struct cork_gc_obj_iface *iface) { size_t full_size = instance_size + sizeof(struct cork_gc_header); DEBUG("Allocating %zu (%zu) bytes\n", instance_size, full_size); struct cork_gc_header *header = cork_malloc(full_size); DEBUG(" Result is %p[%p]\n", cork_gc_get_object(header), header); header->ref_count_color = cork_gc_ref_count_color(1, false, GC_BLACK); header->allocated_size = full_size; header->iface = iface; return cork_gc_get_object(header); } void * cork_gc_incref(void *obj) { if (obj != NULL) { struct cork_gc_header *header = cork_gc_get_header(obj); cork_gc_inc_ref_count(header); DEBUG("Incrementing %p -> %d\n", obj, cork_gc_get_ref_count(header)); cork_gc_set_color(header, GC_BLACK); } return obj; } static void cork_gc_decref_step(struct cork_gc *gc, void *obj, void *ud); static void cork_gc_release(struct cork_gc *gc, struct cork_gc_header *header) { cork_gc_recurse(gc, header, cork_gc_decref_step); cork_gc_set_color(header, GC_BLACK); if (!cork_gc_get_buffered(header)) { cork_gc_free(header); } } static void cork_gc_possible_root(struct cork_gc *gc, struct cork_gc_header *header) { if (cork_gc_get_color(header) != GC_PURPLE) { DEBUG(" Possible garbage cycle root\n"); cork_gc_set_color(header, GC_PURPLE); if (!cork_gc_get_buffered(header)) { cork_gc_set_buffered(header, true); if (gc->root_count >= ROOTS_SIZE) { cork_gc_collect_cycles(gc); } gc->roots[gc->root_count++] = header; } } else { DEBUG(" Already marked as possible garbage cycle root\n"); } } static void cork_gc_decref_step(struct cork_gc *gc, void *obj, void *ud) { if (obj != NULL) { struct cork_gc_header *header = cork_gc_get_header(obj); cork_gc_dec_ref_count(header); DEBUG("Decrementing %p -> %d\n", obj, cork_gc_get_ref_count(header)); if (cork_gc_get_ref_count(header) == 0) { DEBUG(" Releasing %p\n", header); cork_gc_release(gc, header); } else { cork_gc_possible_root(gc, header); } } } void cork_gc_decref(void *obj) { if (obj != NULL) { struct cork_gc *gc = cork_gc_get(); struct cork_gc_header *header = cork_gc_get_header(obj); cork_gc_dec_ref_count(header); DEBUG("Decrementing %p -> %d\n", obj, cork_gc_get_ref_count(header)); if (cork_gc_get_ref_count(header) == 0) { DEBUG(" Releasing %p\n", header); cork_gc_release(gc, header); } else { cork_gc_possible_root(gc, header); } } } static void cork_gc_mark_gray_step(struct cork_gc *gc, void *obj, void *ud); static void cork_gc_mark_gray(struct cork_gc *gc, struct cork_gc_header *header) { if (cork_gc_get_color(header) != GC_GRAY) { DEBUG(" Setting color to gray\n"); cork_gc_set_color(header, GC_GRAY); cork_gc_recurse(gc, header, cork_gc_mark_gray_step); } } static void cork_gc_mark_gray_step(struct cork_gc *gc, void *obj, void *ud) { if (obj != NULL) { DEBUG(" cork_gc_mark_gray(%p)\n", obj); struct cork_gc_header *header = cork_gc_get_header(obj); cork_gc_dec_ref_count(header); DEBUG(" Reference count now %d\n", cork_gc_get_ref_count(header)); cork_gc_mark_gray(gc, header); } } static void cork_gc_mark_roots(struct cork_gc *gc) { size_t i; for (i = 0; i < gc->root_count; i++) { struct cork_gc_header *header = gc->roots[i]; if (cork_gc_get_color(header) == GC_PURPLE) { DEBUG(" Checking possible garbage cycle root %p\n", cork_gc_get_object(header)); DEBUG(" cork_gc_mark_gray(%p)\n", cork_gc_get_object(header)); cork_gc_mark_gray(gc, header); } else { DEBUG(" Possible garbage cycle root %p already checked\n", cork_gc_get_object(header)); cork_gc_set_buffered(header, false); gc->roots[i] = NULL; if (cork_gc_get_color(header) == GC_BLACK && cork_gc_get_ref_count(header) == 0) { DEBUG(" Freeing %p\n", header); cork_gc_free(header); } } } } static void cork_gc_scan_black_step(struct cork_gc *gc, void *obj, void *ud); static void cork_gc_scan_black(struct cork_gc *gc, struct cork_gc_header *header) { DEBUG(" Setting color of %p to BLACK\n", cork_gc_get_object(header)); cork_gc_set_color(header, GC_BLACK); cork_gc_recurse(gc, header, cork_gc_scan_black_step); } static void cork_gc_scan_black_step(struct cork_gc *gc, void *obj, void *ud) { if (obj != NULL) { struct cork_gc_header *header = cork_gc_get_header(obj); cork_gc_inc_ref_count(header); DEBUG(" Increasing reference count %p -> %d\n", obj, cork_gc_get_ref_count(header)); if (cork_gc_get_color(header) != GC_BLACK) { cork_gc_scan_black(gc, header); } } } static void cork_gc_scan(struct cork_gc *gc, void *obj, void *ud) { if (obj != NULL) { DEBUG(" Scanning possible garbage cycle entry %p\n", obj); struct cork_gc_header *header = cork_gc_get_header(obj); if (cork_gc_get_color(header) == GC_GRAY) { if (cork_gc_get_ref_count(header) > 0) { DEBUG(" Remaining references; can't be a cycle\n"); cork_gc_scan_black(gc, header); } else { DEBUG(" Definitely a garbage cycle\n"); cork_gc_set_color(header, GC_WHITE); cork_gc_recurse(gc, header, cork_gc_scan); } } else { DEBUG(" Already checked\n"); } } } static void cork_gc_scan_roots(struct cork_gc *gc) { size_t i; for (i = 0; i < gc->root_count; i++) { if (gc->roots[i] != NULL) { void *obj = cork_gc_get_object(gc->roots[i]); cork_gc_scan(gc, obj, NULL); } } } static void cork_gc_collect_white(struct cork_gc *gc, void *obj, void *ud) { if (obj != NULL) { struct cork_gc_header *header = cork_gc_get_header(obj); if (cork_gc_get_color(header) == GC_WHITE && !cork_gc_get_buffered(header)) { DEBUG(" Releasing %p\n", obj); cork_gc_set_color(header, GC_BLACK); cork_gc_recurse(gc, header, cork_gc_collect_white); DEBUG(" Freeing %p\n", header); cork_gc_free(header); } } } static void cork_gc_collect_roots(struct cork_gc *gc) { size_t i; for (i = 0; i < gc->root_count; i++) { if (gc->roots[i] != NULL) { struct cork_gc_header *header = gc->roots[i]; void *obj = cork_gc_get_object(header); cork_gc_set_buffered(header, false); DEBUG("Collecting cycles from garbage root %p\n", obj); cork_gc_collect_white(gc, obj, NULL); gc->roots[i] = NULL; } } gc->root_count = 0; } static void cork_gc_collect_cycles(struct cork_gc *gc) { DEBUG("Collecting garbage cycles\n"); cork_gc_mark_roots(gc); cork_gc_scan_roots(gc); cork_gc_collect_roots(gc); }