Hi,

At Fri, 12 Aug 2005 04:25:35 +0900,
Joel VanderWerf wrote in [ruby-talk:151751]:
> Briefly, the patch adds a method
> 
> GC.reachability_paths obj
> 
> which returns a list of all the ways you can reference that object,
> starting from the basic references such as C and ruby globals, stack
> variables, and others.

Interesting.

A patch for CVS trunk.


Index: gc.c =================================================================== RCS file: /cvs/ruby/src/ruby/gc.c,v retrieving revision 1.206 diff -U2 -p -r1.206 gc.c --- gc.c 12 Aug 2005 08:13:28 -0000 1.206 +++ gc.c 12 Aug 2005 10:17:15 -0000 @@ -92,5 +92,10 @@ static unsigned long malloc_limit = GC_M static void run_final(); static VALUE nomem_error; +#ifdef DEBUG_REACHABILITY +static VALUE garbage_collect0 _((VALUE)); +#define garbage_collect() garbage_collect0(0) +#else static void garbage_collect(); +#endif void @@ -698,4 +703,25 @@ rb_gc_mark_maybe(obj) #define GC_LEVEL_MAX 250 +#ifdef DEBUG_REACHABILITY +VALUE rb_reach_test_obj = Qnil; +VALUE rb_reach_test_result = Qnil; +VALUE rb_reach_test_path = Qnil; + +static void +rb_gc_unmark() +{ + RVALUE *p, *pend; + int i, used = heaps_used; + + for (i = 0; i < used; i++) { + p = heaps[i].slot; pend = p + heaps[i].limit; + while (p < pend) { + RBASIC(p)->flags &= ~FL_MARK; + p++; + } + } +} +#endif + static void gc_mark(ptr, lev) @@ -704,8 +730,25 @@ gc_mark(ptr, lev) { register RVALUE *obj; +#ifdef DEBUG_REACHABILITY + long saved_len = 0; +#endif obj = RANY(ptr); if (rb_special_const_p(ptr)) return; /* special const not marked */ if (obj->as.basic.flags == 0) return; /* free cell */ +#ifdef DEBUG_REACHABILITY + if (!NIL_P(rb_reach_test_obj) && + (obj->as.basic.flags & T_MASK) != T_NODE) { + saved_len = RARRAY(rb_reach_test_path)->len; + if ((VALUE)obj == rb_reach_test_obj) { + rb_warn(" ...found, after %ld steps!", saved_len); + rb_ary_push(rb_reach_test_result, + rb_ary_dup(rb_reach_test_path)); + } + else if (!(obj->as.basic.flags & FL_MARK)) { + rb_ary_push(rb_reach_test_path, (VALUE)obj); + } + } +#endif if (obj->as.basic.flags & FL_MARK) return; /* already marked */ obj->as.basic.flags |= FL_MARK; @@ -724,4 +767,9 @@ gc_mark(ptr, lev) } gc_mark_children(ptr, lev+1); +#ifdef DEBUG_REACHABILITY + if (!NIL_P(rb_reach_test_path)) { + RARRAY(rb_reach_test_path)->len = saved_len; + } +#endif } @@ -1287,7 +1335,17 @@ int rb_setjmp (rb_jmp_buf); #endif /* __GNUC__ */ +#ifdef DEBUG_REACHABILITY +static VALUE +garbage_collect0(obj) + VALUE obj; +#else static void garbage_collect() +#endif { +#ifdef DEBUG_REACHABILITY + int i = 0; + VALUE result; +#endif struct gc_list *list; struct FRAME * volatile frame; /* gcc 2.7.2.3 -O2 bug?? */ @@ -1300,29 +1358,73 @@ garbage_collect() } #endif - if (dont_gc || during_gc) { - if (!freelist) { - add_heap(); +#ifdef DEBUG_REACHABILITY +#define IF_DEBUG_REACHABILITY(does) if (obj) {does;} + if (obj) { + if (!NIL_P(rb_reach_test_obj) || + !NIL_P(rb_reach_test_result) || + !NIL_P(rb_reach_test_path)) { + rb_raise(rb_eRuntimeError, "reachability_paths called recursively"); } - return; + + rb_reach_test_obj = obj; + rb_reach_test_result = rb_ary_new(); + rb_reach_test_path = rb_ary_new(); + } + else +#else +#define IF_DEBUG_REACHABILITY(does) +#endif + { + if (dont_gc || during_gc) { + if (!freelist) { + add_heap(); + } +#ifdef DEBUG_REACHABILITY + return 0; +#else + return; +#endif + } + during_gc++; } - if (during_gc) return; - during_gc++; init_mark_stack(); /* mark frame stack */ + IF_DEBUG_REACHABILITY(rb_warn("Checking frame stack...")); for (frame = ruby_frame; frame; frame = frame->prev) { + IF_DEBUG_REACHABILITY( + NODE *node = frame->node; + if (node) { + rb_ary_push(rb_reach_test_path, + rb_sprintf("frame %d: %s line %d", i, node->nd_file, nd_line(node))); + }); rb_gc_mark_frame(frame); + IF_DEBUG_REACHABILITY((rb_ary_pop(rb_reach_test_path), i++)); if (frame->tmp) { struct FRAME *tmp = frame->tmp; +#ifdef DEBUG_REACHABILITY + int ti = 0; +#endif while (tmp) { + IF_DEBUG_REACHABILITY( + NODE *node = tmp->node; + if (node) { + rb_ary_push(rb_reach_test_path, + rb_sprintf("tmp frame %d: %s line %d", + ti, node->nd_file, nd_line(node))); + }); rb_gc_mark_frame(tmp); + IF_DEBUG_REACHABILITY((rb_ary_pop(rb_reach_test_path), ti++)); tmp = tmp->prev; } } } + IF_DEBUG_REACHABILITY(rb_warn("Checking ruby_class...")); gc_mark((VALUE)ruby_scope, 0); + IF_DEBUG_REACHABILITY(rb_warn("Checking ruby_scope...")); gc_mark((VALUE)ruby_dyna_vars, 0); if (finalizer_table) { + IF_DEBUG_REACHABILITY(rb_warn("Checking finalizer_table...")); mark_tbl(finalizer_table, 0); } @@ -1331,5 +1433,7 @@ garbage_collect() /* This assumes that all registers are saved into the jmp_buf (and stack) */ setjmp(save_regs_gc_mark); + IF_DEBUG_REACHABILITY(rb_warn("Checking save_regs_gc_mark...")); mark_locations_array((VALUE*)save_regs_gc_mark, sizeof(save_regs_gc_mark) / sizeof(VALUE *)); + IF_DEBUG_REACHABILITY(rb_warn("Checking stack_start...")); #if STACK_GROW_DIRECTION < 0 rb_gc_mark_locations((VALUE*)STACK_END, rb_gc_stack_start); @@ -1371,23 +1475,34 @@ garbage_collect() (VALUE*)((char*)rb_gc_stack_start + 2)); #endif + IF_DEBUG_REACHABILITY(rb_warn("Checking threads...")); rb_gc_mark_threads(); /* mark protected global variables */ + IF_DEBUG_REACHABILITY(rb_warn("Checking C globals...")); for (list = global_List; list; list = list->next) { + IF_DEBUG_REACHABILITY(rb_ary_push(rb_reach_test_path, rb_sprintf("C global %d", i))); rb_gc_mark_maybe(*list->varptr); + IF_DEBUG_REACHABILITY((rb_ary_pop(rb_reach_test_path), i++)); } + IF_DEBUG_REACHABILITY(rb_warn("Checking end_proc...")); rb_mark_end_proc(); + IF_DEBUG_REACHABILITY(rb_warn("Checking global_tbl...")); rb_gc_mark_global_tbl(); + IF_DEBUG_REACHABILITY(rb_warn("Checking class_tbl...")); rb_mark_tbl(rb_class_tbl); + IF_DEBUG_REACHABILITY(rb_warn("Checking trap_list...")); rb_gc_mark_trap_list(); /* mark generic instance variables for special constants */ + IF_DEBUG_REACHABILITY(rb_warn("Checking generic_ivar_tbl...")); rb_mark_generic_ivar_tbl(); + IF_DEBUG_REACHABILITY(rb_warn("Checking mark parser...")); rb_gc_mark_parser(); /* gc_mark objects whose marking are not completed*/ - while (!MARK_STACK_EMPTY){ + IF_DEBUG_REACHABILITY(rb_warn("Checking mark stack...")); + while (!MARK_STACK_EMPTY) { if (mark_stack_overflow){ gc_mark_all(); @@ -1397,4 +1512,19 @@ garbage_collect() } } + + IF_DEBUG_REACHABILITY( + rb_warn("Unmarking..."); + rb_gc_unmark(); + + rb_warn("Done."); + + result = rb_reach_test_result; + + rb_reach_test_obj = Qnil; + rb_reach_test_result = Qnil; + rb_reach_test_path = Qnil; + + return result); + gc_sweep(); } @@ -1917,4 +2047,7 @@ Init_GC() rb_define_singleton_method(rb_mGC, "enable", rb_gc_enable, 0); rb_define_singleton_method(rb_mGC, "disable", rb_gc_disable, 0); +#ifdef DEBUG_REACHABILITY + rb_define_singleton_method(rb_mGC, "reachability_paths", garbage_collect0, 1); +#endif rb_define_method(rb_mGC, "garbage_collect", rb_gc_start, 0); @@ -1941,3 +2074,8 @@ Init_GC() nomem_error = rb_exc_new2(rb_eNoMemError, "failed to allocate memory"); rb_global_variable(&nomem_error); + +#ifdef DEBUG_REACHABILITY + rb_global_variable(&rb_reach_test_result); + rb_global_variable(&rb_reach_test_path); +#endif } Index: variable.c =================================================================== RCS file: /cvs/ruby/src/ruby/variable.c,v retrieving revision 1.125 diff -U2 -p -r1.125 variable.c --- variable.c 27 Jul 2005 07:27:17 -0000 1.125 +++ variable.c 12 Aug 2005 09:12:29 -0000 @@ -446,4 +446,11 @@ readonly_setter(val, id, var) } +#ifdef DEBUG_REACHABILITY +extern VALUE rb_reach_test_path; +#define IF_DEBUG_REACHABILITY(does) do {if (!NIL_P(rb_reach_test_path)) {does;}} while (0) +#else +#define IF_DEBUG_REACHABILITY(does) +#endif + static int mark_global_entry(key, entry) @@ -453,9 +460,23 @@ mark_global_entry(key, entry) struct trace_var *trace; struct global_variable *var = entry->var; - +#ifdef DEBUG_REACHABILITY + int i = 0; +#endif + + IF_DEBUG_REACHABILITY( + rb_ary_push(rb_reach_test_path, + rb_sprintf("Ruby global %s", rb_id2name(key)))); (*var->marker)(var->data); + IF_DEBUG_REACHABILITY(rb_ary_pop(rb_reach_test_path)); + trace = var->trace; while (trace) { - if (trace->data) rb_gc_mark_maybe(trace->data); + if (trace->data) { + IF_DEBUG_REACHABILITY( + rb_ary_push(rb_reach_test_path, + rb_sprintf("Ruby global %s trace %d", rb_id2name(key), i++))); + rb_gc_mark_maybe(trace->data); + IF_DEBUG_REACHABILITY(rb_ary_pop(rb_reach_test_path)); + } trace = trace->next; }
-- Nobu Nakada