You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

407 lines
11 KiB

/* -*- coding: utf-8 -*-
* ----------------------------------------------------------------------
* Copyright © 2011, RedJack, LLC.
* All rights reserved.
*
* Please see the COPYING file in this distribution for license
* details.
* ----------------------------------------------------------------------
*/
#include <stdlib.h>
#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 <stdio.h>
#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))); \
} \
free((hdr)); \
} 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);
}