diff --git a/ChangeLog b/ChangeLog index a06b5be2f4..655bac0555 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,38 @@ +Thu May 24 14:30:13 2012 Koichi Sasada + + * vm.c: add RubyVM::Backtrace object (btobj). + Backtrace information contains an array consists of location + information for each frames by string. + RubyVM::Backtrace object is lightweight backtrace information, + which contains complete information to generate traditional style + backtrace (an array of strings) with faster generation. + If someone accesses to backtrace information via + Exception#backtrace, then convert a RubyVM::Backtrace object to + traditonal style backtrace. + This change causes incompatibility on marshal dumpped binary + of Exception. If you have any trouble on it, please tell us + before Ruby 2.0 release. + Note that RubyVM::Backtrace object should not expose Ruby level. + + * error.c, eval.c, vm_eval.c: ditto. + + * internal.h: ditto. + + * eval_error.c: fix to skip "set_backtrace" method invocation in + creating an exception object if it call a normal set_backtrace + method (defined by core). + + * test/ruby/test_settracefunc.rb: fix for above change. + + * vm_method.c (rb_method_defined_by): added. This function + checks that the given object responds with the given method + by the given cfunc. + + * benchmark/bm_vm2_raise1.rb, benchmark/bm_vm2_raise2.rb: + add to measure exception creation speed. raise1 create + exception objects from shallow stack frame. raise2 create + exception objects from deep stack frame. + Thu May 24 12:07:46 2012 Nobuyoshi Nakada * io.c (io_strip_bom): check EOF. [Bug #6487][ruby-core:45203] diff --git a/benchmark/bm_vm2_raise1.rb b/benchmark/bm_vm2_raise1.rb new file mode 100644 index 0000000000..b3d9c3a8a4 --- /dev/null +++ b/benchmark/bm_vm2_raise1.rb @@ -0,0 +1,18 @@ +def rec n + if n > 0 + rec n-1 + else + raise + end +end + +i=0 +while i<6_000_000 # benchmark loop 2 + i+=1 + + begin + rec 1 + rescue + # ignore + end +end diff --git a/benchmark/bm_vm2_raise2.rb b/benchmark/bm_vm2_raise2.rb new file mode 100644 index 0000000000..3f1e8dc1c7 --- /dev/null +++ b/benchmark/bm_vm2_raise2.rb @@ -0,0 +1,18 @@ +def rec n + if n > 0 + rec n-1 + else + raise + end +end + +i=0 +while i<6_000_000 # benchmark loop 2 + i+=1 + + begin + rec 10 + rescue + # ignore + end +end diff --git a/error.c b/error.c index 90f04829f9..1be043057a 100644 --- a/error.c +++ b/error.c @@ -673,9 +673,17 @@ static VALUE exc_backtrace(VALUE exc) { ID bt; + VALUE obj; CONST_ID(bt, "bt"); - return rb_attr_get(exc, bt); + obj = rb_attr_get(exc, bt); + + if (rb_backtrace_p(obj)) { + obj = rb_backtrace_to_str_ary(obj); + /* rb_iv_set(exc, "bt", obj); */ + } + + return obj; } VALUE @@ -686,6 +694,7 @@ rb_check_backtrace(VALUE bt) if (!NIL_P(bt)) { if (RB_TYPE_P(bt, T_STRING)) return rb_ary_new3(1, bt); + if (rb_backtrace_p(bt)) return bt; if (!RB_TYPE_P(bt, T_ARRAY)) { rb_raise(rb_eTypeError, err); } @@ -708,8 +717,8 @@ rb_check_backtrace(VALUE bt) * */ -static VALUE -exc_set_backtrace(VALUE exc, VALUE bt) +VALUE +rb_exc_set_backtrace(VALUE exc, VALUE bt) { return rb_iv_set(exc, "bt", rb_check_backtrace(bt)); } @@ -1669,7 +1678,7 @@ Init_Exception(void) rb_define_method(rb_eException, "message", exc_message, 0); rb_define_method(rb_eException, "inspect", exc_inspect, 0); rb_define_method(rb_eException, "backtrace", exc_backtrace, 0); - rb_define_method(rb_eException, "set_backtrace", exc_set_backtrace, 1); + rb_define_method(rb_eException, "set_backtrace", rb_exc_set_backtrace, 1); rb_eSystemExit = rb_define_class("SystemExit", rb_eException); rb_define_method(rb_eSystemExit, "initialize", exit_initialize, -1); diff --git a/eval.c b/eval.c index 75dca74be3..298326a556 100644 --- a/eval.c +++ b/eval.c @@ -387,7 +387,7 @@ setup_exception(rb_thread_t *th, int tag, volatile VALUE mesg) else { at = get_backtrace(mesg); if (NIL_P(at)) { - at = rb_make_backtrace(); + at = rb_vm_backtrace_object(); if (OBJ_FROZEN(mesg)) { mesg = rb_obj_dup(mesg); } diff --git a/eval_error.c b/eval_error.c index e7eb9cf0fc..a2e95a759f 100644 --- a/eval_error.c +++ b/eval_error.c @@ -58,6 +58,17 @@ rb_get_backtrace(VALUE info) static void set_backtrace(VALUE info, VALUE bt) { + ID set_backtrace = rb_intern("set_backtrace"); + + if (rb_backtrace_p(bt)) { + if (rb_method_defined_by(info, set_backtrace, rb_exc_set_backtrace)) { + rb_exc_set_backtrace(info, bt); + return; + } + else { + bt = rb_backtrace_to_str_ary(bt); + } + } rb_funcall(info, rb_intern("set_backtrace"), 1, bt); } diff --git a/internal.h b/internal.h index e5818429ea..6ba8e1f3b0 100644 --- a/internal.h +++ b/internal.h @@ -83,6 +83,7 @@ void rb_gc_mark_encodings(void); NORETURN(PRINTF_ARGS(void rb_compile_bug(const char*, int, const char*, ...), 3, 4)); VALUE rb_check_backtrace(VALUE); NORETURN(void rb_async_bug_errno(const char *,int)); +VALUE rb_exc_set_backtrace(VALUE exc, VALUE bt); /* eval_error.c */ void ruby_error_print(void); @@ -207,6 +208,9 @@ void rb_vm_inc_const_missing_count(void); void rb_thread_mark(void *th); const void **rb_vm_get_insns_address_table(void); VALUE rb_sourcefilename(void); +int rb_backtrace_p(VALUE obj); +VALUE rb_backtrace_to_str_ary(VALUE obj); +VALUE rb_vm_backtrace_object(); /* vm_dump.c */ void rb_vm_bugreport(void); @@ -217,6 +221,7 @@ VALUE rb_current_realfilepath(void); /* vm_method.c */ void Init_eval_method(void); +int rb_method_defined_by(VALUE obj, ID mid, VALUE (*cfunc)(ANYARGS)); /* miniprelude.c, prelude.c */ void Init_prelude(void); diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb index 32be12814b..463923aa26 100644 --- a/test/ruby/test_settracefunc.rb +++ b/test/ruby/test_settracefunc.rb @@ -250,10 +250,6 @@ class TestSetTraceFunc < Test::Unit::TestCase events.shift) assert_equal(["c-return", 5, :backtrace, Exception], events.shift) - assert_equal(["c-call", 5, :set_backtrace, Exception], - events.shift) - assert_equal(["c-return", 5, :set_backtrace, Exception], - events.shift) assert_equal(["raise", 5, :test_raise, TestSetTraceFunc], events.shift) assert_equal(["c-return", 5, :raise, Kernel], diff --git a/vm.c b/vm.c index dea51a2911..0898deac92 100644 --- a/vm.c +++ b/vm.c @@ -36,14 +36,16 @@ VALUE rb_cRubyVM; VALUE rb_cThread; VALUE rb_cEnv; VALUE rb_mRubyVMFrozenCore; +VALUE rb_cBacktrace; +VALUE rb_cFrameInfo; VALUE ruby_vm_const_missing_count = 0; - char ruby_vm_redefined_flag[BOP_LAST_]; - rb_thread_t *ruby_current_thread = 0; rb_vm_t *ruby_current_vm = 0; +extern VALUE ruby_engine_name; + static void thread_free(void *ptr); void vm_analysis_operand(int insn, int n, VALUE op); @@ -731,6 +733,12 @@ rb_lastline_set(VALUE val) /* backtrace */ +inline static int +calc_line_no(const rb_iseq_t *iseq, VALUE *pc) +{ + return rb_iseq_line_no(iseq, pc - iseq->iseq_encoded); +} + int rb_vm_get_sourceline(const rb_control_frame_t *cfp) { @@ -738,22 +746,289 @@ rb_vm_get_sourceline(const rb_control_frame_t *cfp) const rb_iseq_t *iseq = cfp->iseq; if (RUBY_VM_NORMAL_ISEQ_P(iseq)) { - size_t pos = cfp->pc - cfp->iseq->iseq_encoded; - line_no = rb_iseq_line_no(iseq, pos); + line_no = calc_line_no(cfp->iseq, cfp->pc); } return line_no; } +typedef struct rb_frame_info_struct { + enum FRAME_INFO_TYPE { + FRAME_INFO_TYPE_ISEQ = 1, + FRAME_INFO_TYPE_CFUNC, + FRAME_INFO_TYPE_IFUNC, + } type; + + union { + struct { + rb_iseq_t *iseq; + VALUE *pc; + } iseq; + struct { + ID mid; + } cfunc; + } body; +} rb_frame_info_t; + +static void +frame_info_mark(void *ptr) +{ + if (ptr) { + rb_frame_info_t *fi = (rb_frame_info_t *)ptr; + + switch (fi->type) { + case FRAME_INFO_TYPE_ISEQ: + rb_gc_mark(fi->body.iseq.iseq->self); + break; + case FRAME_INFO_TYPE_CFUNC: + case FRAME_INFO_TYPE_IFUNC: + default: + break; + } + } +} + +static VALUE +frame_info_format(VALUE file, VALUE line_no, VALUE name) +{ + if (line_no != INT2FIX(0)) { + return rb_enc_sprintf(rb_enc_compatible(file, name), "%s:%d:in `%s'", + RSTRING_PTR(file), FIX2INT(line_no), RSTRING_PTR(name)); + } + else { + return rb_enc_sprintf(rb_enc_compatible(file, name), "%s:in `%s'", + RSTRING_PTR(file), RSTRING_PTR(name)); + } +} + +static VALUE +frame_info_to_str_override(rb_frame_info_t *fi, VALUE *args) +{ + switch (fi->type) { + case FRAME_INFO_TYPE_ISEQ: + args[0] = fi->body.iseq.iseq->location.filename; + args[1] = INT2FIX(calc_line_no(fi->body.iseq.iseq, fi->body.iseq.pc)); + args[2] = fi->body.iseq.iseq->location.name; + break; + case FRAME_INFO_TYPE_CFUNC: + args[2] = rb_id2str(fi->body.cfunc.mid); + break; + case FRAME_INFO_TYPE_IFUNC: + default: + rb_bug("frame_info_to_str_overwrite: unreachable"); + } + + return frame_info_format(args[0], args[1], args[2]); +} + +typedef struct rb_backtrace_struct { + rb_frame_info_t *backtrace; + int backtrace_size; + VALUE str; +} rb_backtrace_t; + +static void +backtrace_mark(void *ptr) +{ + if (ptr) { + rb_backtrace_t *bt = (rb_backtrace_t *)ptr; + int i, s = bt->backtrace_size; + + for (i=0; ibacktrace[i]); + rb_gc_mark(bt->str); + } + } +} + +static void +backtrace_free(void *ptr) +{ + if (ptr) { + rb_backtrace_t *bt = (rb_backtrace_t *)ptr; + if (bt->backtrace) ruby_xfree(bt->backtrace); + ruby_xfree(bt); + } +} + +static size_t +backtrace_memsize(const void *ptr) +{ + rb_backtrace_t *bt = (rb_backtrace_t *)ptr; + return sizeof(rb_backtrace_t) + sizeof(rb_frame_info_t) * bt->backtrace_size; +} + +static const rb_data_type_t backtrace_data_type = { + "backtrace", + {backtrace_mark, backtrace_free, backtrace_memsize,}, +}; + +int +rb_backtrace_p(VALUE obj) +{ + if (TYPE(obj) == T_DATA && RTYPEDDATA_P(obj) && RTYPEDDATA_TYPE(obj) == &backtrace_data_type) { + return 1; + } + else { + return 0; + } +} + +static VALUE +backtrace_alloc(VALUE klass) +{ + rb_backtrace_t *bt; + VALUE obj = TypedData_Make_Struct(klass, rb_backtrace_t, &backtrace_data_type, bt); + bt->backtrace = 0; + bt->backtrace_size = 0; + bt->str = 0; + return obj; +} + +static VALUE +backtrace_object(rb_thread_t *th, int lev, int n) +{ + VALUE btobj = backtrace_alloc(rb_cBacktrace); + rb_backtrace_t *bt; + rb_control_frame_t *last_cfp = th->cfp; + rb_control_frame_t *start_cfp = RUBY_VM_END_CONTROL_FRAME(th); + rb_control_frame_t *cfp; + int size, i, j; + + start_cfp = RUBY_VM_NEXT_CONTROL_FRAME( + RUBY_VM_NEXT_CONTROL_FRAME( + RUBY_VM_NEXT_CONTROL_FRAME(start_cfp))); /* skip top frames */ + size = (start_cfp - last_cfp) + 1; + + if (n <= 0) { + n = size + n; + if (n < 0) { + n = 0; + } + } + if (lev < 0) { + lev = 0; + } + + GetCoreDataFromValue(btobj, rb_backtrace_t, bt); + bt->backtrace = ruby_xmalloc(sizeof(rb_frame_info_t) * n); + bt->backtrace_size = n; + + for (i=lev, j=0, cfp = start_cfp; ibacktrace[j]; + + if (cfp->iseq) { + if (cfp->pc) { + fi->type = FRAME_INFO_TYPE_ISEQ; + fi->body.iseq.iseq = cfp->iseq; + fi->body.iseq.pc = cfp->pc; + j++; + } + } + else if (RUBYVM_CFUNC_FRAME_P(cfp)) { + ID mid = cfp->me->def ? cfp->me->def->original_id : cfp->me->called_id; + + if (mid != ID_ALLOCATOR) { + fi->type = FRAME_INFO_TYPE_CFUNC; + fi->body.cfunc.mid = mid; + j++; + } + } + } + + if (j > 0) { + bt->backtrace_size = j; /* TODO: realloc? */ + return btobj; + } + else { + /* gc will free object */ + return Qnil; + } +} + +static VALUE +backtreace_collect(rb_backtrace_t *bt, VALUE (*func)(rb_frame_info_t *, VALUE *)) +{ + VALUE btary; + int i; + VALUE args[3]; + rb_thread_t *th = GET_THREAD(); + + btary = rb_ary_new2(bt->backtrace_size); + rb_ary_store(btary, bt->backtrace_size-1, Qnil); /* create places */ + + args[0] = th->vm->progname ? th->vm->progname : ruby_engine_name;; + args[1] = INT2FIX(0); + args[2] = Qnil; + + for (i=0; ibacktrace_size; i++) { + rb_frame_info_t *fi = &bt->backtrace[i]; + RARRAY_PTR(btary)[bt->backtrace_size - i - 1] = func(fi, args); + } + + return btary; +} + +VALUE +rb_backtrace_to_str_ary(VALUE self) +{ + rb_backtrace_t *bt; + GetCoreDataFromValue(self, rb_backtrace_t, bt); + + if (bt->str) { + return bt->str; + } + else { + bt->str = backtreace_collect(bt, frame_info_to_str_override); + return bt->str; + } +} + +#if 0 +static VALUE +rb_backtrace_to_frame_ary(VALUE self) +{ + return backtreace_collect(self, frame_info_create); +} + +static VALUE +vm_backtrace_frame_ary(rb_thread_t *th, int lev, int n) +{ + return rb_backtrace_to_frame_ary(vm_backtrace_create(th, lev, n)); +} +#endif + +static VALUE +backtrace_dump_data(VALUE self) +{ + VALUE str = rb_backtrace_to_str_ary(self); + return str; +} + +static VALUE +backtrace_load_data(VALUE self, VALUE str) +{ + rb_backtrace_t *bt; + GetCoreDataFromValue(self, rb_backtrace_t, bt); + bt->str = str; + return self; +} + +/* old style backtrace for compatibility */ + static int -vm_backtrace_each(rb_thread_t *th, int lev, void (*init)(void *), rb_backtrace_iter_func *iter, void *arg) +vm_backtrace_each(rb_thread_t *th, int lev, int n, void (*init)(void *), rb_backtrace_iter_func *iter, void *arg) { const rb_control_frame_t *limit_cfp = th->cfp; const rb_control_frame_t *cfp = (void *)(th->stack + th->stack_size); VALUE file = Qnil; int line_no = 0; + if (n <= 0) { + n = cfp - limit_cfp; + } + cfp -= 2; - while (lev-- >= 0) { + while (lev-- >= 0 && n > 0) { if (++limit_cfp > cfp) { return FALSE; } @@ -761,20 +1036,21 @@ vm_backtrace_each(rb_thread_t *th, int lev, void (*init)(void *), rb_backtrace_i if (init) (*init)(arg); limit_cfp = RUBY_VM_NEXT_CONTROL_FRAME(limit_cfp); if (th->vm->progname) file = th->vm->progname; - while (cfp > limit_cfp) { + while (cfp > limit_cfp && n>0) { if (cfp->iseq != 0) { if (cfp->pc != 0) { rb_iseq_t *iseq = cfp->iseq; line_no = rb_vm_get_sourceline(cfp); file = iseq->location.filename; + n--; if ((*iter)(arg, file, line_no, iseq->location.name)) break; } } else if (RUBYVM_CFUNC_FRAME_P(cfp)) { ID id; - extern VALUE ruby_engine_name; + n--; if (NIL_P(file)) file = ruby_engine_name; if (cfp->me->def) id = cfp->me->def->original_id; @@ -813,15 +1089,15 @@ vm_backtrace_push(void *arg, VALUE file, int line_no, VALUE name) return 0; } -static inline VALUE -vm_backtrace(rb_thread_t *th, int lev) +static VALUE +vm_backtrace_str_ary(rb_thread_t *th, int lev, int n) { VALUE ary = 0; if (lev < 0) { ary = rb_ary_new(); } - vm_backtrace_each(th, lev, vm_backtrace_alloc, vm_backtrace_push, &ary); + vm_backtrace_each(th, lev, 0, vm_backtrace_alloc, vm_backtrace_push, &ary); if (!ary) return Qnil; return rb_ary_reverse(ary); } @@ -2172,6 +2448,13 @@ Init_VM(void) rb_cThread = rb_define_class("Thread", rb_cObject); rb_undef_alloc_func(rb_cThread); + /* ::RubyVM::Backtrace */ + rb_cBacktrace = rb_define_class_under(rb_cRubyVM, "Backtrace", rb_cObject); + rb_define_alloc_func(rb_cBacktrace, backtrace_alloc); + rb_undef_method(CLASS_OF(rb_cFrameInfo), "new"); + rb_define_method(rb_cBacktrace, "_dump_data", backtrace_dump_data, 0); + rb_define_method(rb_cBacktrace, "_load_data", backtrace_load_data, 1); + /* ::RubyVM::USAGE_ANALYSIS_* */ rb_define_const(rb_cRubyVM, "USAGE_ANALYSIS_INSN", rb_hash_new()); rb_define_const(rb_cRubyVM, "USAGE_ANALYSIS_REGS", rb_hash_new()); diff --git a/vm_eval.c b/vm_eval.c index 8d6c91a158..340abfc610 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -15,13 +15,15 @@ static inline VALUE method_missing(VALUE obj, ID id, int argc, const VALUE *argv static inline VALUE rb_vm_set_finish_env(rb_thread_t * th); static inline VALUE vm_yield_with_cref(rb_thread_t *th, int argc, const VALUE *argv, const NODE *cref); static inline VALUE vm_yield(rb_thread_t *th, int argc, const VALUE *argv); -static inline VALUE vm_backtrace(rb_thread_t *th, int lev); -static int vm_backtrace_each(rb_thread_t *th, int lev, void (*init)(void *), rb_backtrace_iter_func *iter, void *arg); static NODE *vm_cref_push(rb_thread_t *th, VALUE klass, int noex, rb_block_t *blockptr); static VALUE vm_exec(rb_thread_t *th); static void vm_set_eval_stack(rb_thread_t * th, VALUE iseqval, const NODE *cref); static int vm_collect_local_variables_in_heap(rb_thread_t *th, VALUE *dfp, VALUE ary); +static int vm_backtrace_each(rb_thread_t *th, int lev, int n, void (*init)(void *), rb_backtrace_iter_func *iter, void *arg); +static VALUE backtrace_object(rb_thread_t *th, int lev, int n); +static VALUE vm_backtrace_str_ary(rb_thread_t *th, int lev, int n); + typedef enum call_type { CALL_PUBLIC, CALL_FCALL, @@ -1085,7 +1087,7 @@ eval_string_with_cref(VALUE self, VALUE src, VALUE scope, NODE *cref, const char errat = rb_get_backtrace(errinfo); mesg = rb_attr_get(errinfo, id_mesg); if (!NIL_P(errat) && RB_TYPE_P(errat, T_ARRAY) && - (bt2 = vm_backtrace(th, -2), RARRAY_LEN(bt2) > 0)) { + (bt2 = vm_backtrace_str_ary(th, -2, 0), RARRAY_LEN(bt2) > 0)) { if (!NIL_P(mesg) && RB_TYPE_P(mesg, T_STRING) && !RSTRING_LEN(mesg)) { if (OBJ_FROZEN(mesg)) { VALUE m = rb_str_cat(rb_str_dup(RARRAY_PTR(errat)[0]), ": ", 2); @@ -1620,7 +1622,7 @@ rb_f_caller(int argc, VALUE *argv) if (lev < 0) rb_raise(rb_eArgError, "negative level (%d)", lev); - return vm_backtrace(GET_THREAD(), lev); + return vm_backtrace_str_ary(GET_THREAD(), lev, 0); } static int @@ -1642,13 +1644,19 @@ print_backtrace(void *arg, VALUE file, int line, VALUE method) void rb_backtrace(void) { - vm_backtrace_each(GET_THREAD(), -1, NULL, print_backtrace, stderr); + vm_backtrace_each(GET_THREAD(), -1, 0, NULL, print_backtrace, stderr); } VALUE rb_make_backtrace(void) { - return vm_backtrace(GET_THREAD(), -1); + return vm_backtrace_str_ary(GET_THREAD(), -1, 0); +} + +VALUE +rb_vm_backtrace_object() +{ + return backtrace_object(GET_THREAD(), -1, 0); } VALUE @@ -1667,13 +1675,13 @@ rb_thread_backtrace(VALUE thval) return Qnil; } - return vm_backtrace(th, 0); + return vm_backtrace_str_ary(th, 0, 0); } int rb_backtrace_each(rb_backtrace_iter_func *iter, void *arg) { - return vm_backtrace_each(GET_THREAD(), -1, NULL, iter, arg); + return vm_backtrace_each(GET_THREAD(), -1, 0, NULL, iter, arg); } /* diff --git a/vm_method.c b/vm_method.c index f3da623347..8c606d410c 100644 --- a/vm_method.c +++ b/vm_method.c @@ -1402,3 +1402,18 @@ Init_eval_method(void) } } +int +rb_method_defined_by(VALUE obj, ID mid, VALUE (*cfunc)(ANYARGS)) +{ + rb_method_entry_t *me = search_method(CLASS_OF(obj), mid); + + if (me && me->def && + me->def->type == VM_METHOD_TYPE_CFUNC && + me->def->body.cfunc.func == cfunc) { + return 1; + } + else { + return 0; + } +} +