| /* |
| ** gc.c - garbage collector for mruby |
| ** |
| ** See Copyright Notice in mruby.h |
| */ |
| |
| #include <string.h> |
| #include <stdlib.h> |
| #include <mruby.h> |
| #include <mruby/array.h> |
| #include <mruby/class.h> |
| #include <mruby/data.h> |
| #include <mruby/hash.h> |
| #include <mruby/proc.h> |
| #include <mruby/range.h> |
| #include <mruby/string.h> |
| #include <mruby/variable.h> |
| #include <mruby/gc.h> |
| #include <mruby/error.h> |
| #include <mruby/throw.h> |
| |
| /* |
| = Tri-color Incremental Garbage Collection |
| |
| mruby's GC is Tri-color Incremental GC with Mark & Sweep. |
| Algorithm details are omitted. |
| Instead, the implementation part is described below. |
| |
| == Object's Color |
| |
| Each object can be painted in three colors: |
| |
| * White - Unmarked. |
| * Gray - Marked, But the child objects are unmarked. |
| * Black - Marked, the child objects are also marked. |
| |
| == Two White Types |
| |
| There're two white color types in a flip-flop fashion: White-A and White-B, |
| which respectively represent the Current White color (the newly allocated |
| objects in the current GC cycle) and the Sweep Target White color (the |
| dead objects to be swept). |
| |
| A and B will be switched just at the beginning of the next GC cycle. At |
| that time, all the dead objects have been swept, while the newly created |
| objects in the current GC cycle which finally remains White are now |
| regarded as dead objects. Instead of traversing all the White-A objects and |
| painting them as White-B, just switch the meaning of White-A and White-B as |
| this will be much cheaper. |
| |
| As a result, the objects we sweep in the current GC cycle are always |
| left from the previous GC cycle. This allows us to sweep objects |
| incrementally, without the disturbance of the newly created objects. |
| |
| == Execution Timing |
| |
| GC Execution Time and Each step interval are decided by live objects count. |
| List of Adjustment API: |
| |
| * gc_interval_ratio_set |
| * gc_step_ratio_set |
| |
| For details, see the comments for each function. |
| |
| == Write Barrier |
| |
| mruby implementer and C extension library writer must insert a write |
| barrier when updating a reference from a field of an object. |
| When updating a reference from a field of object A to object B, |
| two different types of write barrier are available: |
| |
| * mrb_field_write_barrier - target B object for a mark. |
| * mrb_write_barrier - target A object for a mark. |
| |
| == Generational Mode |
| |
| mruby's GC offers an Generational Mode while re-using the tri-color GC |
| infrastructure. It will treat the Black objects as Old objects after each |
| sweep phase, instead of painting them White. The key ideas are still the same |
| as traditional generational GC: |
| |
| * Minor GC - just traverse the Young objects (Gray objects) in the mark |
| phase, then only sweep the newly created objects, and leave |
| the Old objects live. |
| |
| * Major GC - same as a full regular GC cycle. |
| |
| The difference from "traditional" generational GC is, that the major GC |
| in mruby is triggered incrementally in a tri-color manner. |
| |
| |
| For details, see the comments for each function. |
| |
| */ |
| |
| struct free_obj { |
| MRB_OBJECT_HEADER; |
| struct RBasic *next; |
| }; |
| |
| typedef struct { |
| union { |
| struct free_obj free; |
| struct RBasic basic; |
| struct RObject object; |
| struct RClass klass; |
| struct RString string; |
| struct RArray array; |
| struct RHash hash; |
| struct RRange range; |
| struct RData data; |
| struct RProc proc; |
| struct REnv env; |
| struct RException exc; |
| struct RBreak brk; |
| #ifdef MRB_WORD_BOXING |
| struct RFloat floatv; |
| struct RCptr cptr; |
| #endif |
| } as; |
| } RVALUE; |
| |
| #ifdef GC_PROFILE |
| #include <stdio.h> |
| #include <sys/time.h> |
| |
| static double program_invoke_time = 0; |
| static double gc_time = 0; |
| static double gc_total_time = 0; |
| |
| static double |
| gettimeofday_time(void) |
| { |
| struct timeval tv; |
| gettimeofday(&tv, NULL); |
| return tv.tv_sec + tv.tv_usec * 1e-6; |
| } |
| |
| #define GC_INVOKE_TIME_REPORT(with) do {\ |
| fprintf(stderr, "%s\n", with);\ |
| fprintf(stderr, "gc_invoke: %19.3f\n", gettimeofday_time() - program_invoke_time);\ |
| fprintf(stderr, "is_generational: %d\n", is_generational(gc));\ |
| fprintf(stderr, "is_major_gc: %d\n", is_major_gc(gc));\ |
| } while(0) |
| |
| #define GC_TIME_START do {\ |
| gc_time = gettimeofday_time();\ |
| } while(0) |
| |
| #define GC_TIME_STOP_AND_REPORT do {\ |
| gc_time = gettimeofday_time() - gc_time;\ |
| gc_total_time += gc_time;\ |
| fprintf(stderr, "gc_state: %d\n", gc->state);\ |
| fprintf(stderr, "live: %zu\n", gc->live);\ |
| fprintf(stderr, "majorgc_old_threshold: %zu\n", gc->majorgc_old_threshold);\ |
| fprintf(stderr, "gc_threshold: %zu\n", gc->threshold);\ |
| fprintf(stderr, "gc_time: %30.20f\n", gc_time);\ |
| fprintf(stderr, "gc_total_time: %30.20f\n\n", gc_total_time);\ |
| } while(0) |
| #else |
| #define GC_INVOKE_TIME_REPORT(s) |
| #define GC_TIME_START |
| #define GC_TIME_STOP_AND_REPORT |
| #endif |
| |
| #ifdef GC_DEBUG |
| #define DEBUG(x) (x) |
| #else |
| #define DEBUG(x) |
| #endif |
| |
| #ifndef MRB_HEAP_PAGE_SIZE |
| #define MRB_HEAP_PAGE_SIZE 1024 |
| #endif |
| |
| #define GC_STEP_SIZE 1024 |
| |
| /* white: 011, black: 100, gray: 000 */ |
| #define GC_GRAY 0 |
| #define GC_WHITE_A 1 |
| #define GC_WHITE_B (1 << 1) |
| #define GC_BLACK (1 << 2) |
| #define GC_WHITES (GC_WHITE_A | GC_WHITE_B) |
| #define GC_COLOR_MASK 7 |
| |
| #define paint_gray(o) ((o)->color = GC_GRAY) |
| #define paint_black(o) ((o)->color = GC_BLACK) |
| #define paint_white(o) ((o)->color = GC_WHITES) |
| #define paint_partial_white(s, o) ((o)->color = (s)->current_white_part) |
| #define is_gray(o) ((o)->color == GC_GRAY) |
| #define is_white(o) ((o)->color & GC_WHITES) |
| #define is_black(o) ((o)->color & GC_BLACK) |
| #define flip_white_part(s) ((s)->current_white_part = other_white_part(s)) |
| #define other_white_part(s) ((s)->current_white_part ^ GC_WHITES) |
| #define is_dead(s, o) (((o)->color & other_white_part(s) & GC_WHITES) || (o)->tt == MRB_TT_FREE) |
| |
| #define objects(p) ((RVALUE *)p->objects) |
| |
| MRB_API void* |
| mrb_realloc_simple(mrb_state *mrb, void *p, size_t len) |
| { |
| void *p2; |
| |
| p2 = (mrb->allocf)(mrb, p, len, mrb->allocf_ud); |
| if (!p2 && len > 0 && mrb->gc.heaps) { |
| mrb_full_gc(mrb); |
| p2 = (mrb->allocf)(mrb, p, len, mrb->allocf_ud); |
| } |
| |
| return p2; |
| } |
| |
| MRB_API void* |
| mrb_realloc(mrb_state *mrb, void *p, size_t len) |
| { |
| void *p2; |
| |
| p2 = mrb_realloc_simple(mrb, p, len); |
| if (len == 0) return p2; |
| if (p2 == NULL) { |
| if (mrb->gc.out_of_memory) { |
| mrb_exc_raise(mrb, mrb_obj_value(mrb->nomem_err)); |
| /* mrb_panic(mrb); */ |
| } |
| else { |
| mrb->gc.out_of_memory = TRUE; |
| mrb_exc_raise(mrb, mrb_obj_value(mrb->nomem_err)); |
| } |
| } |
| else { |
| mrb->gc.out_of_memory = FALSE; |
| } |
| |
| return p2; |
| } |
| |
| MRB_API void* |
| mrb_malloc(mrb_state *mrb, size_t len) |
| { |
| return mrb_realloc(mrb, 0, len); |
| } |
| |
| MRB_API void* |
| mrb_malloc_simple(mrb_state *mrb, size_t len) |
| { |
| return mrb_realloc_simple(mrb, 0, len); |
| } |
| |
| MRB_API void* |
| mrb_calloc(mrb_state *mrb, size_t nelem, size_t len) |
| { |
| void *p; |
| |
| if (nelem > 0 && len > 0 && |
| nelem <= SIZE_MAX / len) { |
| size_t size; |
| size = nelem * len; |
| p = mrb_malloc(mrb, size); |
| |
| memset(p, 0, size); |
| } |
| else { |
| p = NULL; |
| } |
| |
| return p; |
| } |
| |
| MRB_API void |
| mrb_free(mrb_state *mrb, void *p) |
| { |
| (mrb->allocf)(mrb, p, 0, mrb->allocf_ud); |
| } |
| |
| MRB_API mrb_bool |
| mrb_object_dead_p(mrb_state *mrb, struct RBasic *object) { |
| return is_dead(&mrb->gc, object); |
| } |
| |
| static void |
| link_heap_page(mrb_gc *gc, mrb_heap_page *page) |
| { |
| page->next = gc->heaps; |
| if (gc->heaps) |
| gc->heaps->prev = page; |
| gc->heaps = page; |
| } |
| |
| static void |
| unlink_heap_page(mrb_gc *gc, mrb_heap_page *page) |
| { |
| if (page->prev) |
| page->prev->next = page->next; |
| if (page->next) |
| page->next->prev = page->prev; |
| if (gc->heaps == page) |
| gc->heaps = page->next; |
| page->prev = NULL; |
| page->next = NULL; |
| } |
| |
| static void |
| link_free_heap_page(mrb_gc *gc, mrb_heap_page *page) |
| { |
| page->free_next = gc->free_heaps; |
| if (gc->free_heaps) { |
| gc->free_heaps->free_prev = page; |
| } |
| gc->free_heaps = page; |
| } |
| |
| static void |
| unlink_free_heap_page(mrb_gc *gc, mrb_heap_page *page) |
| { |
| if (page->free_prev) |
| page->free_prev->free_next = page->free_next; |
| if (page->free_next) |
| page->free_next->free_prev = page->free_prev; |
| if (gc->free_heaps == page) |
| gc->free_heaps = page->free_next; |
| page->free_prev = NULL; |
| page->free_next = NULL; |
| } |
| |
| 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) + MRB_HEAP_PAGE_SIZE * sizeof(RVALUE)); |
| RVALUE *p, *e; |
| struct RBasic *prev = NULL; |
| |
| for (p = objects(page), e=p+MRB_HEAP_PAGE_SIZE; p<e; p++) { |
| p->as.free.tt = MRB_TT_FREE; |
| p->as.free.next = prev; |
| prev = &p->as.basic; |
| } |
| page->freelist = prev; |
| |
| link_heap_page(gc, page); |
| link_free_heap_page(gc, page); |
| } |
| |
| #define DEFAULT_GC_INTERVAL_RATIO 200 |
| #define DEFAULT_GC_STEP_RATIO 200 |
| #define DEFAULT_MAJOR_GC_INC_RATIO 200 |
| #define is_generational(gc) ((gc)->generational) |
| #define is_major_gc(gc) (is_generational(gc) && (gc)->full) |
| #define is_minor_gc(gc) (is_generational(gc) && !(gc)->full) |
| |
| void |
| mrb_gc_init(mrb_state *mrb, mrb_gc *gc) |
| { |
| #ifndef MRB_GC_FIXED_ARENA |
| gc->arena = (struct RBasic**)mrb_malloc(mrb, sizeof(struct RBasic*)*MRB_GC_ARENA_SIZE); |
| gc->arena_capa = MRB_GC_ARENA_SIZE; |
| #endif |
| |
| gc->current_white_part = GC_WHITE_A; |
| gc->heaps = NULL; |
| gc->free_heaps = NULL; |
| add_heap(mrb, gc); |
| gc->interval_ratio = DEFAULT_GC_INTERVAL_RATIO; |
| gc->step_ratio = DEFAULT_GC_STEP_RATIO; |
| #ifndef MRB_GC_TURN_OFF_GENERATIONAL |
| gc->generational = TRUE; |
| gc->full = TRUE; |
| #endif |
| |
| #ifdef GC_PROFILE |
| program_invoke_time = gettimeofday_time(); |
| #endif |
| } |
| |
| static void obj_free(mrb_state *mrb, struct RBasic *obj, int end); |
| |
| void |
| free_heap(mrb_state *mrb, mrb_gc *gc) |
| { |
| mrb_heap_page *page = gc->heaps; |
| mrb_heap_page *tmp; |
| RVALUE *p, *e; |
| |
| while (page) { |
| tmp = page; |
| page = page->next; |
| for (p = objects(tmp), e=p+MRB_HEAP_PAGE_SIZE; p<e; p++) { |
| if (p->as.free.tt != MRB_TT_FREE) |
| obj_free(mrb, &p->as.basic, TRUE); |
| } |
| mrb_free(mrb, tmp); |
| } |
| } |
| |
| void |
| mrb_gc_destroy(mrb_state *mrb, mrb_gc *gc) |
| { |
| free_heap(mrb, gc); |
| #ifndef MRB_GC_FIXED_ARENA |
| mrb_free(mrb, gc->arena); |
| #endif |
| } |
| |
| static void |
| gc_protect(mrb_state *mrb, mrb_gc *gc, struct RBasic *p) |
| { |
| #ifdef MRB_GC_FIXED_ARENA |
| if (gc->arena_idx >= MRB_GC_ARENA_SIZE) { |
| /* arena overflow error */ |
| gc->arena_idx = MRB_GC_ARENA_SIZE - 4; /* force room in arena */ |
| mrb_exc_raise(mrb, mrb_obj_value(mrb->arena_err)); |
| } |
| #else |
| if (gc->arena_idx >= gc->arena_capa) { |
| /* extend arena */ |
| gc->arena_capa = (int)(gc->arena_capa * 1.5); |
| gc->arena = (struct RBasic**)mrb_realloc(mrb, gc->arena, sizeof(struct RBasic*)*gc->arena_capa); |
| } |
| #endif |
| gc->arena[gc->arena_idx++] = p; |
| } |
| |
| /* mrb_gc_protect() leaves the object in the arena */ |
| MRB_API void |
| mrb_gc_protect(mrb_state *mrb, mrb_value obj) |
| { |
| if (mrb_immediate_p(obj)) return; |
| gc_protect(mrb, &mrb->gc, mrb_basic_ptr(obj)); |
| } |
| |
| #define GC_ROOT_NAME "_gc_root_" |
| |
| /* mrb_gc_register() keeps the object from GC. |
| |
| Register your object when it's exported to C world, |
| without reference from Ruby world, e.g. callback |
| arguments. Don't forget to remove the object using |
| mrb_gc_unregister, otherwise your object will leak. |
| */ |
| |
| MRB_API void |
| mrb_gc_register(mrb_state *mrb, mrb_value obj) |
| { |
| mrb_sym root = mrb_intern_lit(mrb, GC_ROOT_NAME); |
| mrb_value table = mrb_gv_get(mrb, root); |
| |
| if (mrb_nil_p(table) || mrb_type(table) != MRB_TT_ARRAY) { |
| table = mrb_ary_new(mrb); |
| mrb_gv_set(mrb, root, table); |
| } |
| mrb_ary_push(mrb, table, obj); |
| } |
| |
| /* mrb_gc_unregister() removes the object from GC root. */ |
| MRB_API void |
| mrb_gc_unregister(mrb_state *mrb, mrb_value obj) |
| { |
| mrb_sym root = mrb_intern_lit(mrb, GC_ROOT_NAME); |
| mrb_value table = mrb_gv_get(mrb, root); |
| struct RArray *a; |
| mrb_int i; |
| |
| if (mrb_nil_p(table)) return; |
| if (mrb_type(table) != MRB_TT_ARRAY) { |
| mrb_gv_set(mrb, root, mrb_nil_value()); |
| return; |
| } |
| a = mrb_ary_ptr(table); |
| mrb_ary_modify(mrb, a); |
| for (i = 0; i < ARY_LEN(a); i++) { |
| if (mrb_obj_eq(mrb, ARY_PTR(a)[i], obj)) { |
| mrb_int len = ARY_LEN(a)-1; |
| mrb_value *ptr = ARY_PTR(a); |
| |
| ARY_SET_LEN(a, len); |
| memmove(&ptr[i], &ptr[i + 1], (len - i) * sizeof(mrb_value)); |
| break; |
| } |
| } |
| } |
| |
| MRB_API struct RBasic* |
| mrb_obj_alloc(mrb_state *mrb, enum mrb_vtype ttype, struct RClass *cls) |
| { |
| struct RBasic *p; |
| static const RVALUE RVALUE_zero = { { { MRB_TT_FALSE } } }; |
| mrb_gc *gc = &mrb->gc; |
| |
| if (cls) { |
| enum mrb_vtype tt; |
| |
| switch (cls->tt) { |
| case MRB_TT_CLASS: |
| case MRB_TT_SCLASS: |
| case MRB_TT_MODULE: |
| case MRB_TT_ENV: |
| break; |
| default: |
| mrb_raise(mrb, E_TYPE_ERROR, "allocation failure"); |
| } |
| tt = MRB_INSTANCE_TT(cls); |
| if (tt != MRB_TT_FALSE && |
| ttype != MRB_TT_SCLASS && |
| ttype != MRB_TT_ICLASS && |
| ttype != MRB_TT_ENV && |
| ttype != tt) { |
| mrb_raisef(mrb, E_TYPE_ERROR, "allocation failure of %S", mrb_obj_value(cls)); |
| } |
| } |
| |
| #ifdef MRB_GC_STRESS |
| mrb_full_gc(mrb); |
| #endif |
| if (gc->threshold < gc->live) { |
| mrb_incremental_gc(mrb); |
| } |
| if (gc->free_heaps == NULL) { |
| add_heap(mrb, gc); |
| } |
| |
| p = gc->free_heaps->freelist; |
| gc->free_heaps->freelist = ((struct free_obj*)p)->next; |
| if (gc->free_heaps->freelist == NULL) { |
| unlink_free_heap_page(gc, gc->free_heaps); |
| } |
| |
| gc->live++; |
| gc_protect(mrb, gc, p); |
| *(RVALUE *)p = RVALUE_zero; |
| p->tt = ttype; |
| p->c = cls; |
| paint_partial_white(gc, p); |
| return p; |
| } |
| |
| static inline void |
| add_gray_list(mrb_state *mrb, mrb_gc *gc, struct RBasic *obj) |
| { |
| #ifdef MRB_GC_STRESS |
| if (obj->tt > MRB_TT_MAXDEFINE) { |
| abort(); |
| } |
| #endif |
| paint_gray(obj); |
| obj->gcnext = gc->gray_list; |
| gc->gray_list = obj; |
| } |
| |
| static void |
| mark_context_stack(mrb_state *mrb, struct mrb_context *c) |
| { |
| size_t i; |
| size_t e; |
| mrb_value nil; |
| int nregs; |
| |
| if (c->stack == NULL) return; |
| e = c->stack - c->stbase; |
| if (c->ci) { |
| nregs = c->ci->argc + 2; |
| if (c->ci->nregs > nregs) |
| nregs = c->ci->nregs; |
| e += nregs; |
| } |
| if (c->stbase + e > c->stend) e = c->stend - c->stbase; |
| for (i=0; i<e; i++) { |
| mrb_value v = c->stbase[i]; |
| |
| if (!mrb_immediate_p(v)) { |
| mrb_gc_mark(mrb, mrb_basic_ptr(v)); |
| } |
| } |
| e = c->stend - c->stbase; |
| nil = mrb_nil_value(); |
| for (; i<e; i++) { |
| c->stbase[i] = nil; |
| } |
| } |
| |
| static void |
| mark_context(mrb_state *mrb, struct mrb_context *c) |
| { |
| int i; |
| mrb_callinfo *ci; |
| |
| if (c->status == MRB_FIBER_TERMINATED) return; |
| |
| /* mark VM stack */ |
| mark_context_stack(mrb, c); |
| |
| /* mark call stack */ |
| if (c->cibase) { |
| for (ci = c->cibase; ci <= c->ci; ci++) { |
| mrb_gc_mark(mrb, (struct RBasic*)ci->env); |
| mrb_gc_mark(mrb, (struct RBasic*)ci->proc); |
| mrb_gc_mark(mrb, (struct RBasic*)ci->target_class); |
| } |
| } |
| /* mark ensure stack */ |
| for (i=0; i<c->esize; i++) { |
| if (c->ensure[i] == NULL) break; |
| mrb_gc_mark(mrb, (struct RBasic*)c->ensure[i]); |
| } |
| /* mark fibers */ |
| mrb_gc_mark(mrb, (struct RBasic*)c->fib); |
| if (c->prev) { |
| mark_context(mrb, c->prev); |
| } |
| } |
| |
| static void |
| gc_mark_children(mrb_state *mrb, mrb_gc *gc, struct RBasic *obj) |
| { |
| mrb_assert(is_gray(obj)); |
| paint_black(obj); |
| gc->gray_list = obj->gcnext; |
| mrb_gc_mark(mrb, (struct RBasic*)obj->c); |
| switch (obj->tt) { |
| case MRB_TT_ICLASS: |
| { |
| struct RClass *c = (struct RClass*)obj; |
| if (MRB_FLAG_TEST(c, MRB_FLAG_IS_ORIGIN)) |
| mrb_gc_mark_mt(mrb, c); |
| mrb_gc_mark(mrb, (struct RBasic*)((struct RClass*)obj)->super); |
| } |
| break; |
| |
| case MRB_TT_CLASS: |
| case MRB_TT_MODULE: |
| case MRB_TT_SCLASS: |
| { |
| struct RClass *c = (struct RClass*)obj; |
| |
| mrb_gc_mark_mt(mrb, c); |
| mrb_gc_mark(mrb, (struct RBasic*)c->super); |
| } |
| /* fall through */ |
| |
| case MRB_TT_OBJECT: |
| case MRB_TT_DATA: |
| case MRB_TT_EXCEPTION: |
| mrb_gc_mark_iv(mrb, (struct RObject*)obj); |
| break; |
| |
| case MRB_TT_PROC: |
| { |
| struct RProc *p = (struct RProc*)obj; |
| |
| mrb_gc_mark(mrb, (struct RBasic*)p->env); |
| mrb_gc_mark(mrb, (struct RBasic*)p->target_class); |
| } |
| break; |
| |
| case MRB_TT_ENV: |
| { |
| struct REnv *e = (struct REnv*)obj; |
| mrb_int i, len; |
| |
| if (MRB_ENV_STACK_SHARED_P(e)) { |
| if (e->cxt.c->fib) { |
| mrb_gc_mark(mrb, (struct RBasic*)e->cxt.c->fib); |
| } |
| break; |
| } |
| len = MRB_ENV_STACK_LEN(e); |
| for (i=0; i<len; i++) { |
| mrb_gc_mark_value(mrb, e->stack[i]); |
| } |
| } |
| break; |
| |
| case MRB_TT_FIBER: |
| { |
| struct mrb_context *c = ((struct RFiber*)obj)->cxt; |
| |
| if (c) mark_context(mrb, c); |
| } |
| break; |
| |
| case MRB_TT_ARRAY: |
| { |
| struct RArray *a = (struct RArray*)obj; |
| size_t i, e; |
| |
| for (i=0,e=ARY_LEN(a); i<e; i++) { |
| mrb_gc_mark_value(mrb, ARY_PTR(a)[i]); |
| } |
| } |
| break; |
| |
| case MRB_TT_HASH: |
| mrb_gc_mark_iv(mrb, (struct RObject*)obj); |
| mrb_gc_mark_hash(mrb, (struct RHash*)obj); |
| break; |
| |
| case MRB_TT_STRING: |
| break; |
| |
| case MRB_TT_RANGE: |
| { |
| struct RRange *r = (struct RRange*)obj; |
| |
| if (r->edges) { |
| mrb_gc_mark_value(mrb, r->edges->beg); |
| mrb_gc_mark_value(mrb, r->edges->end); |
| } |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } |
| |
| MRB_API void |
| mrb_gc_mark(mrb_state *mrb, struct RBasic *obj) |
| { |
| if (obj == 0) return; |
| if (!is_white(obj)) return; |
| mrb_assert((obj)->tt != MRB_TT_FREE); |
| add_gray_list(mrb, &mrb->gc, obj); |
| } |
| |
| static void |
| obj_free(mrb_state *mrb, struct RBasic *obj, int end) |
| { |
| DEBUG(fprintf(stderr, "obj_free(%p,tt=%d)\n",obj,obj->tt)); |
| switch (obj->tt) { |
| /* immediate - no mark */ |
| case MRB_TT_TRUE: |
| case MRB_TT_FIXNUM: |
| case MRB_TT_SYMBOL: |
| /* cannot happen */ |
| return; |
| |
| case MRB_TT_FLOAT: |
| #ifdef MRB_WORD_BOXING |
| break; |
| #else |
| return; |
| #endif |
| |
| case MRB_TT_OBJECT: |
| mrb_gc_free_iv(mrb, (struct RObject*)obj); |
| break; |
| |
| case MRB_TT_EXCEPTION: |
| mrb_gc_free_iv(mrb, (struct RObject*)obj); |
| break; |
| |
| case MRB_TT_CLASS: |
| case MRB_TT_MODULE: |
| case MRB_TT_SCLASS: |
| mrb_gc_free_mt(mrb, (struct RClass*)obj); |
| mrb_gc_free_iv(mrb, (struct RObject*)obj); |
| break; |
| case MRB_TT_ICLASS: |
| if (MRB_FLAG_TEST(obj, MRB_FLAG_IS_ORIGIN)) |
| mrb_gc_free_mt(mrb, (struct RClass*)obj); |
| break; |
| case MRB_TT_ENV: |
| { |
| struct REnv *e = (struct REnv*)obj; |
| |
| if (MRB_ENV_STACK_SHARED_P(e)) { |
| /* cannot be freed */ |
| return; |
| } |
| mrb_free(mrb, e->stack); |
| e->stack = NULL; |
| } |
| break; |
| |
| case MRB_TT_FIBER: |
| { |
| struct mrb_context *c = ((struct RFiber*)obj)->cxt; |
| |
| if (!end && c && c != mrb->root_c) { |
| mrb_callinfo *ci = c->ci; |
| mrb_callinfo *ce = c->cibase; |
| |
| while (ce <= ci) { |
| struct REnv *e = ci->env; |
| if (e && !is_dead(&mrb->gc, e) && |
| e->tt == MRB_TT_ENV && MRB_ENV_STACK_SHARED_P(e)) { |
| mrb_env_unshare(mrb, e); |
| } |
| ci--; |
| } |
| mrb_free_context(mrb, c); |
| } |
| } |
| break; |
| |
| case MRB_TT_ARRAY: |
| if (ARY_SHARED_P(obj)) |
| mrb_ary_decref(mrb, ((struct RArray*)obj)->as.heap.aux.shared); |
| else if (!ARY_EMBED_P(obj)) |
| mrb_free(mrb, ((struct RArray*)obj)->as.heap.ptr); |
| break; |
| |
| case MRB_TT_HASH: |
| mrb_gc_free_iv(mrb, (struct RObject*)obj); |
| mrb_gc_free_hash(mrb, (struct RHash*)obj); |
| break; |
| |
| case MRB_TT_STRING: |
| mrb_gc_free_str(mrb, (struct RString*)obj); |
| break; |
| |
| case MRB_TT_PROC: |
| { |
| struct RProc *p = (struct RProc*)obj; |
| |
| if (!MRB_PROC_CFUNC_P(p) && p->body.irep) { |
| mrb_irep_decref(mrb, p->body.irep); |
| } |
| } |
| break; |
| |
| case MRB_TT_RANGE: |
| mrb_free(mrb, ((struct RRange*)obj)->edges); |
| break; |
| |
| case MRB_TT_DATA: |
| { |
| struct RData *d = (struct RData*)obj; |
| if (d->type && d->type->dfree) { |
| d->type->dfree(mrb, d->data); |
| } |
| mrb_gc_free_iv(mrb, (struct RObject*)obj); |
| } |
| break; |
| |
| default: |
| break; |
| } |
| obj->tt = MRB_TT_FREE; |
| } |
| |
| static void |
| root_scan_phase(mrb_state *mrb, mrb_gc *gc) |
| { |
| int i, e; |
| |
| if (!is_minor_gc(gc)) { |
| gc->gray_list = NULL; |
| gc->atomic_gray_list = NULL; |
| } |
| |
| mrb_gc_mark_gv(mrb); |
| /* mark arena */ |
| for (i=0,e=gc->arena_idx; i<e; i++) { |
| mrb_gc_mark(mrb, gc->arena[i]); |
| } |
| /* mark class hierarchy */ |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->object_class); |
| |
| /* mark built-in classes */ |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->class_class); |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->module_class); |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->proc_class); |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->string_class); |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->array_class); |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->hash_class); |
| |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->float_class); |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->fixnum_class); |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->true_class); |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->false_class); |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->nil_class); |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->symbol_class); |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->kernel_module); |
| |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->eException_class); |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->eStandardError_class); |
| |
| /* mark top_self */ |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->top_self); |
| /* mark exception */ |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->exc); |
| /* mark pre-allocated exception */ |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->nomem_err); |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->stack_err); |
| #ifdef MRB_GC_FIXED_ARENA |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->arena_err); |
| #endif |
| |
| mark_context(mrb, mrb->c); |
| if (mrb->root_c != mrb->c) { |
| mark_context(mrb, mrb->root_c); |
| } |
| } |
| |
| static size_t |
| gc_gray_mark(mrb_state *mrb, mrb_gc *gc, struct RBasic *obj) |
| { |
| size_t children = 0; |
| |
| gc_mark_children(mrb, gc, obj); |
| |
| switch (obj->tt) { |
| case MRB_TT_ICLASS: |
| children++; |
| break; |
| |
| case MRB_TT_CLASS: |
| case MRB_TT_SCLASS: |
| case MRB_TT_MODULE: |
| { |
| struct RClass *c = (struct RClass*)obj; |
| |
| children += mrb_gc_mark_iv_size(mrb, (struct RObject*)obj); |
| children += mrb_gc_mark_mt_size(mrb, c); |
| children++; |
| } |
| break; |
| |
| case MRB_TT_OBJECT: |
| case MRB_TT_DATA: |
| case MRB_TT_EXCEPTION: |
| children += mrb_gc_mark_iv_size(mrb, (struct RObject*)obj); |
| break; |
| |
| case MRB_TT_ENV: |
| children += (int)obj->flags; |
| break; |
| |
| case MRB_TT_FIBER: |
| { |
| struct mrb_context *c = ((struct RFiber*)obj)->cxt; |
| size_t i; |
| mrb_callinfo *ci; |
| |
| if (!c) break; |
| /* mark stack */ |
| i = c->stack - c->stbase; |
| if (c->ci) i += c->ci->nregs; |
| if (c->stbase + i > c->stend) i = c->stend - c->stbase; |
| children += i; |
| |
| /* mark ensure stack */ |
| children += c->eidx; |
| |
| /* mark closure */ |
| if (c->cibase) { |
| for (i=0, ci = c->cibase; ci <= c->ci; i++, ci++) |
| ; |
| } |
| children += i; |
| } |
| break; |
| |
| case MRB_TT_ARRAY: |
| { |
| struct RArray *a = (struct RArray*)obj; |
| children += ARY_LEN(a); |
| } |
| break; |
| |
| case MRB_TT_HASH: |
| children += mrb_gc_mark_iv_size(mrb, (struct RObject*)obj); |
| children += mrb_gc_mark_hash_size(mrb, (struct RHash*)obj); |
| break; |
| |
| case MRB_TT_PROC: |
| case MRB_TT_RANGE: |
| children+=2; |
| break; |
| |
| default: |
| break; |
| } |
| return children; |
| } |
| |
| |
| static void |
| gc_mark_gray_list(mrb_state *mrb, mrb_gc *gc) { |
| while (gc->gray_list) { |
| if (is_gray(gc->gray_list)) |
| gc_mark_children(mrb, gc, gc->gray_list); |
| else |
| gc->gray_list = gc->gray_list->gcnext; |
| } |
| } |
| |
| |
| static size_t |
| incremental_marking_phase(mrb_state *mrb, mrb_gc *gc, size_t limit) |
| { |
| size_t tried_marks = 0; |
| |
| while (gc->gray_list && tried_marks < limit) { |
| tried_marks += gc_gray_mark(mrb, gc, gc->gray_list); |
| } |
| |
| return tried_marks; |
| } |
| |
| static void |
| final_marking_phase(mrb_state *mrb, mrb_gc *gc) |
| { |
| int i, e; |
| |
| /* mark arena */ |
| for (i=0,e=gc->arena_idx; i<e; i++) { |
| mrb_gc_mark(mrb, gc->arena[i]); |
| } |
| mrb_gc_mark_gv(mrb); |
| mark_context(mrb, mrb->c); |
| mark_context(mrb, mrb->root_c); |
| mrb_gc_mark(mrb, (struct RBasic*)mrb->exc); |
| gc_mark_gray_list(mrb, gc); |
| mrb_assert(gc->gray_list == NULL); |
| gc->gray_list = gc->atomic_gray_list; |
| gc->atomic_gray_list = NULL; |
| gc_mark_gray_list(mrb, gc); |
| mrb_assert(gc->gray_list == NULL); |
| } |
| |
| static void |
| prepare_incremental_sweep(mrb_state *mrb, mrb_gc *gc) |
| { |
| gc->state = MRB_GC_STATE_SWEEP; |
| gc->sweeps = gc->heaps; |
| gc->live_after_mark = gc->live; |
| } |
| |
| static size_t |
| incremental_sweep_phase(mrb_state *mrb, mrb_gc *gc, size_t limit) |
| { |
| mrb_heap_page *page = gc->sweeps; |
| size_t tried_sweep = 0; |
| |
| while (page && (tried_sweep < limit)) { |
| RVALUE *p = objects(page); |
| RVALUE *e = p + MRB_HEAP_PAGE_SIZE; |
| size_t freed = 0; |
| mrb_bool dead_slot = TRUE; |
| mrb_bool full = (page->freelist == NULL); |
| |
| if (is_minor_gc(gc) && page->old) { |
| /* skip a slot which doesn't contain any young object */ |
| p = e; |
| dead_slot = FALSE; |
| } |
| while (p<e) { |
| if (is_dead(gc, &p->as.basic)) { |
| if (p->as.basic.tt != MRB_TT_FREE) { |
| obj_free(mrb, &p->as.basic, FALSE); |
| if (p->as.basic.tt == MRB_TT_FREE) { |
| p->as.free.next = page->freelist; |
| page->freelist = (struct RBasic*)p; |
| freed++; |
| } |
| else { |
| dead_slot = FALSE; |
| } |
| } |
| } |
| else { |
| if (!is_generational(gc)) |
| paint_partial_white(gc, &p->as.basic); /* next gc target */ |
| dead_slot = FALSE; |
| } |
| p++; |
| } |
| |
| /* free dead slot */ |
| if (dead_slot && freed < MRB_HEAP_PAGE_SIZE) { |
| mrb_heap_page *next = page->next; |
| |
| unlink_heap_page(gc, page); |
| unlink_free_heap_page(gc, page); |
| mrb_free(mrb, page); |
| page = next; |
| } |
| else { |
| if (full && freed > 0) { |
| link_free_heap_page(gc, page); |
| } |
| if (page->freelist == NULL && is_minor_gc(gc)) |
| page->old = TRUE; |
| else |
| page->old = FALSE; |
| page = page->next; |
| } |
| tried_sweep += MRB_HEAP_PAGE_SIZE; |
| gc->live -= freed; |
| gc->live_after_mark -= freed; |
| } |
| gc->sweeps = page; |
| return tried_sweep; |
| } |
| |
| static size_t |
| incremental_gc(mrb_state *mrb, mrb_gc *gc, size_t limit) |
| { |
| switch (gc->state) { |
| case MRB_GC_STATE_ROOT: |
| root_scan_phase(mrb, gc); |
| gc->state = MRB_GC_STATE_MARK; |
| flip_white_part(gc); |
| return 0; |
| case MRB_GC_STATE_MARK: |
| if (gc->gray_list) { |
| return incremental_marking_phase(mrb, gc, limit); |
| } |
| else { |
| final_marking_phase(mrb, gc); |
| prepare_incremental_sweep(mrb, gc); |
| return 0; |
| } |
| case MRB_GC_STATE_SWEEP: { |
| size_t tried_sweep = 0; |
| tried_sweep = incremental_sweep_phase(mrb, gc, limit); |
| if (tried_sweep == 0) |
| gc->state = MRB_GC_STATE_ROOT; |
| return tried_sweep; |
| } |
| default: |
| /* unknown state */ |
| mrb_assert(0); |
| return 0; |
| } |
| } |
| |
| static void |
| incremental_gc_until(mrb_state *mrb, mrb_gc *gc, mrb_gc_state to_state) |
| { |
| do { |
| incremental_gc(mrb, gc, SIZE_MAX); |
| } while (gc->state != to_state); |
| } |
| |
| static void |
| incremental_gc_step(mrb_state *mrb, mrb_gc *gc) |
| { |
| size_t limit = 0, result = 0; |
| limit = (GC_STEP_SIZE/100) * gc->step_ratio; |
| while (result < limit) { |
| result += incremental_gc(mrb, gc, limit); |
| if (gc->state == MRB_GC_STATE_ROOT) |
| break; |
| } |
| |
| gc->threshold = gc->live + GC_STEP_SIZE; |
| } |
| |
| static void |
| clear_all_old(mrb_state *mrb, mrb_gc *gc) |
| { |
| mrb_bool origin_mode = gc->generational; |
| |
| mrb_assert(is_generational(gc)); |
| if (is_major_gc(gc)) { |
| /* finish the half baked GC */ |
| incremental_gc_until(mrb, gc, MRB_GC_STATE_ROOT); |
| } |
| |
| /* Sweep the dead objects, then reset all the live objects |
| * (including all the old objects, of course) to white. */ |
| gc->generational = FALSE; |
| prepare_incremental_sweep(mrb, gc); |
| incremental_gc_until(mrb, gc, MRB_GC_STATE_ROOT); |
| gc->generational = origin_mode; |
| |
| /* The gray objects have already been painted as white */ |
| gc->atomic_gray_list = gc->gray_list = NULL; |
| } |
| |
| MRB_API void |
| mrb_incremental_gc(mrb_state *mrb) |
| { |
| mrb_gc *gc = &mrb->gc; |
| |
| if (gc->disabled || gc->iterating) return; |
| |
| GC_INVOKE_TIME_REPORT("mrb_incremental_gc()"); |
| GC_TIME_START; |
| |
| if (is_minor_gc(gc)) { |
| incremental_gc_until(mrb, gc, MRB_GC_STATE_ROOT); |
| } |
| else { |
| incremental_gc_step(mrb, gc); |
| } |
| |
| if (gc->state == MRB_GC_STATE_ROOT) { |
| mrb_assert(gc->live >= gc->live_after_mark); |
| gc->threshold = (gc->live_after_mark/100) * gc->interval_ratio; |
| if (gc->threshold < GC_STEP_SIZE) { |
| gc->threshold = GC_STEP_SIZE; |
| } |
| |
| if (is_major_gc(gc)) { |
| gc->majorgc_old_threshold = gc->live_after_mark/100 * DEFAULT_MAJOR_GC_INC_RATIO; |
| gc->full = FALSE; |
| } |
| else if (is_minor_gc(gc)) { |
| if (gc->live > gc->majorgc_old_threshold) { |
| clear_all_old(mrb, gc); |
| gc->full = TRUE; |
| } |
| } |
| } |
| |
| GC_TIME_STOP_AND_REPORT; |
| } |
| |
| /* Perform a full gc cycle */ |
| MRB_API void |
| mrb_full_gc(mrb_state *mrb) |
| { |
| mrb_gc *gc = &mrb->gc; |
| |
| if (gc->disabled || gc->iterating) return; |
| |
| GC_INVOKE_TIME_REPORT("mrb_full_gc()"); |
| GC_TIME_START; |
| |
| if (is_generational(gc)) { |
| /* clear all the old objects back to young */ |
| clear_all_old(mrb, gc); |
| gc->full = TRUE; |
| } |
| else if (gc->state != MRB_GC_STATE_ROOT) { |
| /* finish half baked GC cycle */ |
| incremental_gc_until(mrb, gc, MRB_GC_STATE_ROOT); |
| } |
| |
| incremental_gc_until(mrb, gc, MRB_GC_STATE_ROOT); |
| gc->threshold = (gc->live_after_mark/100) * gc->interval_ratio; |
| |
| if (is_generational(gc)) { |
| gc->majorgc_old_threshold = gc->live_after_mark/100 * DEFAULT_MAJOR_GC_INC_RATIO; |
| gc->full = FALSE; |
| } |
| |
| GC_TIME_STOP_AND_REPORT; |
| } |
| |
| MRB_API void |
| mrb_garbage_collect(mrb_state *mrb) |
| { |
| mrb_full_gc(mrb); |
| } |
| |
| /* |
| * Field write barrier |
| * Paint obj(Black) -> value(White) to obj(Black) -> value(Gray). |
| */ |
| |
| MRB_API void |
| mrb_field_write_barrier(mrb_state *mrb, struct RBasic *obj, struct RBasic *value) |
| { |
| mrb_gc *gc = &mrb->gc; |
| |
| if (!is_black(obj)) return; |
| if (!is_white(value)) return; |
| |
| mrb_assert(gc->state == MRB_GC_STATE_MARK || (!is_dead(gc, value) && !is_dead(gc, obj))); |
| mrb_assert(is_generational(gc) || gc->state != MRB_GC_STATE_ROOT); |
| |
| if (is_generational(gc) || gc->state == MRB_GC_STATE_MARK) { |
| add_gray_list(mrb, gc, value); |
| } |
| else { |
| mrb_assert(gc->state == MRB_GC_STATE_SWEEP); |
| paint_partial_white(gc, obj); /* for never write barriers */ |
| } |
| } |
| |
| /* |
| * Write barrier |
| * Paint obj(Black) to obj(Gray). |
| * |
| * The object that is painted gray will be traversed atomically in final |
| * mark phase. So you use this write barrier if it's frequency written spot. |
| * e.g. Set element on Array. |
| */ |
| |
| MRB_API void |
| mrb_write_barrier(mrb_state *mrb, struct RBasic *obj) |
| { |
| mrb_gc *gc = &mrb->gc; |
| |
| if (!is_black(obj)) return; |
| |
| mrb_assert(!is_dead(gc, obj)); |
| mrb_assert(is_generational(gc) || gc->state != MRB_GC_STATE_ROOT); |
| paint_gray(obj); |
| obj->gcnext = gc->atomic_gray_list; |
| gc->atomic_gray_list = obj; |
| } |
| |
| /* |
| * call-seq: |
| * GC.start -> nil |
| * |
| * Initiates full garbage collection. |
| * |
| */ |
| |
| static mrb_value |
| gc_start(mrb_state *mrb, mrb_value obj) |
| { |
| mrb_full_gc(mrb); |
| return mrb_nil_value(); |
| } |
| |
| /* |
| * call-seq: |
| * GC.enable -> true or false |
| * |
| * Enables garbage collection, returning <code>true</code> if garbage |
| * collection was previously disabled. |
| * |
| * GC.disable #=> false |
| * GC.enable #=> true |
| * GC.enable #=> false |
| * |
| */ |
| |
| static mrb_value |
| gc_enable(mrb_state *mrb, mrb_value obj) |
| { |
| mrb_bool old = mrb->gc.disabled; |
| |
| mrb->gc.disabled = FALSE; |
| |
| return mrb_bool_value(old); |
| } |
| |
| /* |
| * call-seq: |
| * GC.disable -> true or false |
| * |
| * Disables garbage collection, returning <code>true</code> if garbage |
| * collection was already disabled. |
| * |
| * GC.disable #=> false |
| * GC.disable #=> true |
| * |
| */ |
| |
| static mrb_value |
| gc_disable(mrb_state *mrb, mrb_value obj) |
| { |
| mrb_bool old = mrb->gc.disabled; |
| |
| mrb->gc.disabled = TRUE; |
| |
| return mrb_bool_value(old); |
| } |
| |
| /* |
| * call-seq: |
| * GC.interval_ratio -> fixnum |
| * |
| * Returns ratio of GC interval. Default value is 200(%). |
| * |
| */ |
| |
| static mrb_value |
| gc_interval_ratio_get(mrb_state *mrb, mrb_value obj) |
| { |
| return mrb_fixnum_value(mrb->gc.interval_ratio); |
| } |
| |
| /* |
| * call-seq: |
| * GC.interval_ratio = fixnum -> nil |
| * |
| * Updates ratio of GC interval. Default value is 200(%). |
| * GC start as soon as after end all step of GC if you set 100(%). |
| * |
| */ |
| |
| static mrb_value |
| gc_interval_ratio_set(mrb_state *mrb, mrb_value obj) |
| { |
| mrb_int ratio; |
| |
| mrb_get_args(mrb, "i", &ratio); |
| mrb->gc.interval_ratio = ratio; |
| return mrb_nil_value(); |
| } |
| |
| /* |
| * call-seq: |
| * GC.step_ratio -> fixnum |
| * |
| * Returns step span ratio of Incremental GC. Default value is 200(%). |
| * |
| */ |
| |
| static mrb_value |
| gc_step_ratio_get(mrb_state *mrb, mrb_value obj) |
| { |
| return mrb_fixnum_value(mrb->gc.step_ratio); |
| } |
| |
| /* |
| * call-seq: |
| * GC.step_ratio = fixnum -> nil |
| * |
| * Updates step span ratio of Incremental GC. Default value is 200(%). |
| * 1 step of incrementalGC becomes long if a rate is big. |
| * |
| */ |
| |
| static mrb_value |
| gc_step_ratio_set(mrb_state *mrb, mrb_value obj) |
| { |
| mrb_int ratio; |
| |
| mrb_get_args(mrb, "i", &ratio); |
| mrb->gc.step_ratio = ratio; |
| return mrb_nil_value(); |
| } |
| |
| static void |
| change_gen_gc_mode(mrb_state *mrb, mrb_gc *gc, mrb_bool enable) |
| { |
| if (gc->disabled || gc->iterating) { |
| mrb_raise(mrb, E_RUNTIME_ERROR, "generational mode changed when GC disabled"); |
| return; |
| } |
| if (is_generational(gc) && !enable) { |
| clear_all_old(mrb, gc); |
| mrb_assert(gc->state == MRB_GC_STATE_ROOT); |
| gc->full = FALSE; |
| } |
| else if (!is_generational(gc) && enable) { |
| incremental_gc_until(mrb, gc, MRB_GC_STATE_ROOT); |
| gc->majorgc_old_threshold = gc->live_after_mark/100 * DEFAULT_MAJOR_GC_INC_RATIO; |
| gc->full = FALSE; |
| } |
| gc->generational = enable; |
| } |
| |
| /* |
| * call-seq: |
| * GC.generational_mode -> true or false |
| * |
| * Returns generational or normal gc mode. |
| * |
| */ |
| |
| static mrb_value |
| gc_generational_mode_get(mrb_state *mrb, mrb_value self) |
| { |
| return mrb_bool_value(mrb->gc.generational); |
| } |
| |
| /* |
| * call-seq: |
| * GC.generational_mode = true or false -> true or false |
| * |
| * Changes to generational or normal gc mode. |
| * |
| */ |
| |
| static mrb_value |
| gc_generational_mode_set(mrb_state *mrb, mrb_value self) |
| { |
| mrb_bool enable; |
| |
| mrb_get_args(mrb, "b", &enable); |
| if (mrb->gc.generational != enable) |
| change_gen_gc_mode(mrb, &mrb->gc, enable); |
| |
| return mrb_bool_value(enable); |
| } |
| |
| |
| static void |
| gc_each_objects(mrb_state *mrb, mrb_gc *gc, mrb_each_object_callback *callback, void *data) |
| { |
| mrb_heap_page* page; |
| |
| page = gc->heaps; |
| while (page != NULL) { |
| RVALUE *p; |
| int i; |
| |
| p = objects(page); |
| for (i=0; i < MRB_HEAP_PAGE_SIZE; i++) { |
| if ((*callback)(mrb, &p[i].as.basic, data) == MRB_EACH_OBJ_BREAK) |
| return; |
| } |
| page = page->next; |
| } |
| } |
| |
| void |
| mrb_objspace_each_objects(mrb_state *mrb, mrb_each_object_callback *callback, void *data) |
| { |
| mrb_bool iterating = mrb->gc.iterating; |
| |
| mrb->gc.iterating = TRUE; |
| if (iterating) { |
| gc_each_objects(mrb, &mrb->gc, callback, data); |
| } |
| else { |
| struct mrb_jmpbuf *prev_jmp = mrb->jmp; |
| struct mrb_jmpbuf c_jmp; |
| |
| MRB_TRY(&c_jmp) { |
| mrb->jmp = &c_jmp; |
| gc_each_objects(mrb, &mrb->gc, callback, data); |
| mrb->jmp = prev_jmp; |
| mrb->gc.iterating = iterating; |
| } MRB_CATCH(&c_jmp) { |
| mrb->gc.iterating = iterating; |
| mrb->jmp = prev_jmp; |
| MRB_THROW(prev_jmp); |
| } MRB_END_EXC(&c_jmp); |
| } |
| } |
| |
| #ifdef GC_TEST |
| #ifdef GC_DEBUG |
| static mrb_value gc_test(mrb_state *, mrb_value); |
| #endif |
| #endif |
| |
| void |
| mrb_init_gc(mrb_state *mrb) |
| { |
| struct RClass *gc; |
| |
| gc = mrb_define_module(mrb, "GC"); |
| |
| mrb_define_class_method(mrb, gc, "start", gc_start, MRB_ARGS_NONE()); |
| mrb_define_class_method(mrb, gc, "enable", gc_enable, MRB_ARGS_NONE()); |
| mrb_define_class_method(mrb, gc, "disable", gc_disable, MRB_ARGS_NONE()); |
| mrb_define_class_method(mrb, gc, "interval_ratio", gc_interval_ratio_get, MRB_ARGS_NONE()); |
| mrb_define_class_method(mrb, gc, "interval_ratio=", gc_interval_ratio_set, MRB_ARGS_REQ(1)); |
| mrb_define_class_method(mrb, gc, "step_ratio", gc_step_ratio_get, MRB_ARGS_NONE()); |
| mrb_define_class_method(mrb, gc, "step_ratio=", gc_step_ratio_set, MRB_ARGS_REQ(1)); |
| mrb_define_class_method(mrb, gc, "generational_mode=", gc_generational_mode_set, MRB_ARGS_REQ(1)); |
| mrb_define_class_method(mrb, gc, "generational_mode", gc_generational_mode_get, MRB_ARGS_NONE()); |
| #ifdef GC_TEST |
| #ifdef GC_DEBUG |
| mrb_define_class_method(mrb, gc, "test", gc_test, MRB_ARGS_NONE()); |
| #endif |
| #endif |
| } |
| |
| #ifdef GC_TEST |
| #ifdef GC_DEBUG |
| void |
| test_mrb_field_write_barrier(void) |
| { |
| mrb_state *mrb = mrb_open(); |
| struct RBasic *obj, *value; |
| mrb_gc *gc = &mrb->gc; |
| |
| puts("test_mrb_field_write_barrier"); |
| gc->generational = FALSE; |
| obj = mrb_basic_ptr(mrb_ary_new(mrb)); |
| value = mrb_basic_ptr(mrb_str_new_lit(mrb, "value")); |
| paint_black(obj); |
| paint_partial_white(gc, value); |
| |
| |
| puts(" in MRB_GC_STATE_MARK"); |
| gc->state = MRB_GC_STATE_MARK; |
| mrb_field_write_barrier(mrb, obj, value); |
| |
| mrb_assert(is_gray(value)); |
| |
| |
| puts(" in MRB_GC_STATE_SWEEP"); |
| paint_partial_white(gc, value); |
| gc->state = MRB_GC_STATE_SWEEP; |
| mrb_field_write_barrier(mrb, obj, value); |
| |
| mrb_assert(obj->color & gc->current_white_part); |
| mrb_assert(value->color & gc->current_white_part); |
| |
| |
| puts(" fail with black"); |
| gc->state = MRB_GC_STATE_MARK; |
| paint_white(obj); |
| paint_partial_white(gc, value); |
| mrb_field_write_barrier(mrb, obj, value); |
| |
| mrb_assert(obj->color & gc->current_white_part); |
| |
| |
| puts(" fail with gray"); |
| gc->state = MRB_GC_STATE_MARK; |
| paint_black(obj); |
| paint_gray(value); |
| mrb_field_write_barrier(mrb, obj, value); |
| |
| mrb_assert(is_gray(value)); |
| |
| |
| { |
| puts("test_mrb_field_write_barrier_value"); |
| obj = mrb_basic_ptr(mrb_ary_new(mrb)); |
| mrb_value value = mrb_str_new_lit(mrb, "value"); |
| paint_black(obj); |
| paint_partial_white(gc, mrb_basic_ptr(value)); |
| |
| gc->state = MRB_GC_STATE_MARK; |
| mrb_field_write_barrier_value(mrb, obj, value); |
| |
| mrb_assert(is_gray(mrb_basic_ptr(value))); |
| } |
| |
| mrb_close(mrb); |
| } |
| |
| void |
| test_mrb_write_barrier(void) |
| { |
| mrb_state *mrb = mrb_open(); |
| struct RBasic *obj; |
| mrb_gc *gc = &mrb->gc; |
| |
| puts("test_mrb_write_barrier"); |
| obj = mrb_basic_ptr(mrb_ary_new(mrb)); |
| paint_black(obj); |
| |
| puts(" in MRB_GC_STATE_MARK"); |
| gc->state = MRB_GC_STATE_MARK; |
| mrb_write_barrier(mrb, obj); |
| |
| mrb_assert(is_gray(obj)); |
| mrb_assert(gc->atomic_gray_list == obj); |
| |
| |
| puts(" fail with gray"); |
| paint_gray(obj); |
| mrb_write_barrier(mrb, obj); |
| |
| mrb_assert(is_gray(obj)); |
| |
| mrb_close(mrb); |
| } |
| |
| void |
| test_add_gray_list(void) |
| { |
| mrb_state *mrb = mrb_open(); |
| struct RBasic *obj1, *obj2; |
| mrb_gc *gc = &mrb->gc; |
| |
| puts("test_add_gray_list"); |
| change_gen_gc_mode(mrb, gc, FALSE); |
| mrb_assert(gc->gray_list == NULL); |
| obj1 = mrb_basic_ptr(mrb_str_new_lit(mrb, "test")); |
| add_gray_list(mrb, gc, obj1); |
| mrb_assert(gc->gray_list == obj1); |
| mrb_assert(is_gray(obj1)); |
| |
| obj2 = mrb_basic_ptr(mrb_str_new_lit(mrb, "test")); |
| add_gray_list(mrb, gc, obj2); |
| mrb_assert(gc->gray_list == obj2); |
| mrb_assert(gc->gray_list->gcnext == obj1); |
| mrb_assert(is_gray(obj2)); |
| |
| mrb_close(mrb); |
| } |
| |
| void |
| test_gc_gray_mark(void) |
| { |
| mrb_state *mrb = mrb_open(); |
| mrb_value obj_v, value_v; |
| struct RBasic *obj; |
| size_t gray_num = 0; |
| mrb_gc *gc = &mrb->gc; |
| |
| puts("test_gc_gray_mark"); |
| |
| puts(" in MRB_TT_CLASS"); |
| obj = (struct RBasic*)mrb->object_class; |
| paint_gray(obj); |
| gray_num = gc_gray_mark(mrb, gc, obj); |
| mrb_assert(is_black(obj)); |
| mrb_assert(gray_num > 1); |
| |
| puts(" in MRB_TT_ARRAY"); |
| obj_v = mrb_ary_new(mrb); |
| value_v = mrb_str_new_lit(mrb, "test"); |
| paint_gray(mrb_basic_ptr(obj_v)); |
| paint_partial_white(gc, mrb_basic_ptr(value_v)); |
| mrb_ary_push(mrb, obj_v, value_v); |
| gray_num = gc_gray_mark(mrb, gc, mrb_basic_ptr(obj_v)); |
| mrb_assert(is_black(mrb_basic_ptr(obj_v))); |
| mrb_assert(is_gray(mrb_basic_ptr(value_v))); |
| mrb_assert(gray_num == 1); |
| |
| mrb_close(mrb); |
| } |
| |
| void |
| test_incremental_gc(void) |
| { |
| mrb_state *mrb = mrb_open(); |
| size_t max = ~0, live = 0, total = 0, freed = 0; |
| RVALUE *free; |
| mrb_heap_page *page; |
| mrb_gc *gc = &mrb->gc; |
| |
| puts("test_incremental_gc"); |
| change_gen_gc_mode(mrb, gc, FALSE); |
| |
| puts(" in mrb_full_gc"); |
| mrb_full_gc(mrb); |
| |
| mrb_assert(gc->state == MRB_GC_STATE_ROOT); |
| puts(" in MRB_GC_STATE_ROOT"); |
| incremental_gc(mrb, gc, max); |
| mrb_assert(gc->state == MRB_GC_STATE_MARK); |
| puts(" in MRB_GC_STATE_MARK"); |
| incremental_gc_until(mrb, gc, MRB_GC_STATE_SWEEP); |
| mrb_assert(gc->state == MRB_GC_STATE_SWEEP); |
| |
| puts(" in MRB_GC_STATE_SWEEP"); |
| page = gc->heaps; |
| while (page) { |
| RVALUE *p = objects(page); |
| RVALUE *e = p + MRB_HEAP_PAGE_SIZE; |
| while (p<e) { |
| if (is_black(&p->as.basic)) { |
| live++; |
| } |
| if (is_gray(&p->as.basic) && !is_dead(gc, &p->as.basic)) { |
| printf("%p\n", &p->as.basic); |
| } |
| p++; |
| } |
| page = page->next; |
| total += MRB_HEAP_PAGE_SIZE; |
| } |
| |
| mrb_assert(gc->gray_list == NULL); |
| |
| incremental_gc(mrb, gc, max); |
| mrb_assert(gc->state == MRB_GC_STATE_SWEEP); |
| |
| incremental_gc(mrb, gc, max); |
| mrb_assert(gc->state == MRB_GC_STATE_ROOT); |
| |
| free = (RVALUE*)gc->heaps->freelist; |
| while (free) { |
| freed++; |
| free = (RVALUE*)free->as.free.next; |
| } |
| |
| mrb_assert(gc->live == live); |
| mrb_assert(gc->live == total-freed); |
| |
| puts("test_incremental_gc(gen)"); |
| incremental_gc_until(mrb, gc, MRB_GC_STATE_SWEEP); |
| change_gen_gc_mode(mrb, gc, TRUE); |
| |
| mrb_assert(gc->full == FALSE); |
| mrb_assert(gc->state == MRB_GC_STATE_ROOT); |
| |
| puts(" in minor"); |
| mrb_assert(is_minor_gc(gc)); |
| mrb_assert(gc->majorgc_old_threshold > 0); |
| gc->majorgc_old_threshold = 0; |
| mrb_incremental_gc(mrb); |
| mrb_assert(gc->full == TRUE); |
| mrb_assert(gc->state == MRB_GC_STATE_ROOT); |
| |
| puts(" in major"); |
| mrb_assert(is_major_gc(gc)); |
| do { |
| mrb_incremental_gc(mrb); |
| } while (gc->state != MRB_GC_STATE_ROOT); |
| mrb_assert(gc->full == FALSE); |
| |
| mrb_close(mrb); |
| } |
| |
| void |
| test_incremental_sweep_phase(void) |
| { |
| mrb_state *mrb = mrb_open(); |
| mrb_gc *gc = &mrb->gc; |
| |
| puts("test_incremental_sweep_phase"); |
| |
| add_heap(mrb, gc); |
| gc->sweeps = gc->heaps; |
| |
| mrb_assert(gc->heaps->next->next == NULL); |
| mrb_assert(gc->free_heaps->next->next == NULL); |
| incremental_sweep_phase(mrb, gc, MRB_HEAP_PAGE_SIZE * 3); |
| |
| mrb_assert(gc->heaps->next == NULL); |
| mrb_assert(gc->heaps == gc->free_heaps); |
| |
| mrb_close(mrb); |
| } |
| |
| static mrb_value |
| gc_test(mrb_state *mrb, mrb_value self) |
| { |
| test_mrb_field_write_barrier(); |
| test_mrb_write_barrier(); |
| test_add_gray_list(); |
| test_gc_gray_mark(); |
| test_incremental_gc(); |
| test_incremental_sweep_phase(); |
| return mrb_nil_value(); |
| } |
| #endif /* GC_DEBUG */ |
| #endif /* GC_TEST */ |