Interesting.
A patch for CVS trunk.
···
===================================================================
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