diff --git a/ChangeLog b/ChangeLog index 85231086f37085..18daf8345f8d97 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,21 @@ +Thu Apr 19 18:37:49 2007 Koichi Sasada + + * eval.c, node.h, thread.c, yarvcore.[ch], eval_intern.h: + support set_trace_func (incomplete. id and klass + don't be passed). And support Thread#set_trace_func + which hook only specified thread and Thread#add_trace_func + which add new trace func instead of replace old one. + C level API was modified. See thread.c (logic) and + yarvcore.h (data structures). + + * vm.c, vm_macro.def: add hook points. + + * compile.c, insns.def: fix "trace" instruction. + + * iseq.c, vm_macro.h: add compile option "trace_instruction". + + * test/ruby/test_settracefunc.rb: hook "c-return" of set_trace_func. + Thu Apr 19 17:46:36 2007 Koichi Sasada * lib/optparse.rb: fix to override conv proc. diff --git a/compile.c b/compile.c index 85160de2eb0b85..b028ff853e49b7 100644 --- a/compile.c +++ b/compile.c @@ -162,7 +162,19 @@ rb_iseq_compile(VALUE self, NODE *node) ADD_CATCH_ENTRY(CATCH_TYPE_NEXT, start, end, 0, end); } else { - COMPILE(ret, "scoped node", node->nd_body); + if (iseq->type == ISEQ_TYPE_CLASS) { + ADD_TRACE(ret, nd_line(node), RUBY_EVENT_CLASS); + COMPILE(ret, "scoped node", node->nd_body); + ADD_TRACE(ret, nd_line(node), RUBY_EVENT_END); + } + else if (iseq->type == ISEQ_TYPE_METHOD) { + ADD_TRACE(ret, nd_line(node), RUBY_EVENT_CALL); + COMPILE(ret, "scoped node", node->nd_body); + ADD_TRACE(ret, nd_line(node), RUBY_EVENT_RETURN); + } + else { + COMPILE(ret, "scoped node", node->nd_body); + } } } else { @@ -2433,6 +2445,10 @@ iseq_compile_each(rb_iseq_t *iseq, LINK_ANCHOR *ret, NODE * node, int poped) type = nd_type(node); + if (node->flags & NODE_NEWLINE) { + ADD_TRACE(ret, nd_line(node), RUBY_EVENT_LINE); + } + switch (type) { case NODE_METHOD:{ diff --git a/compile.h b/compile.h index f0c7089218d754..9991232446a661 100644 --- a/compile.h +++ b/compile.h @@ -157,6 +157,11 @@ r_value(VALUE value) new_insn_send(iseq, line, \ (VALUE)id, (VALUE)argc, (VALUE)block, (VALUE)flag)) +#define ADD_TRACE(seq, line, event) \ + if (iseq->compile_data->option->trace_instruction) { \ + ADD_INSN1(seq, line, trace, INT2FIX(event)); \ + } + /* add label */ #define ADD_LABEL(seq, label) \ ADD_ELEM(seq, (LINK_ELEMENT *)label) diff --git a/eval.c b/eval.c index 720b5f6afbebed..b72fbe44de0f2f 100644 --- a/eval.c +++ b/eval.c @@ -40,29 +40,6 @@ static VALUE eval _((VALUE, VALUE, VALUE, char *, int)); static VALUE rb_yield_0 _((VALUE, VALUE, VALUE, int, int)); static VALUE rb_call(VALUE, VALUE, ID, int, const VALUE *, int); -static void rb_clear_trace_func(void); - -typedef struct event_hook { - rb_event_hook_func_t func; - rb_event_t events; - struct event_hook *next; -} rb_event_hook_t; - -static rb_event_hook_t *event_hooks; - -#define EXEC_EVENT_HOOK(event, node, self, id, klass) \ - do { \ - rb_event_hook_t *hook; \ - \ - for (hook = event_hooks; hook; hook = hook->next) { \ - if (hook->events & event) \ - (*hook->func)(event, node, self, id, klass); \ - } \ - } while (0) - -static void call_trace_func _((rb_event_t, NODE *, VALUE, ID, VALUE)); - - #include "eval_error.h" #include "eval_method.h" #include "eval_safe.h" @@ -162,8 +139,8 @@ ruby_finalize_1(void) { signal(SIGINT, SIG_DFL); GET_THREAD()->errinfo = 0; - rb_gc_call_finalizer_at_exit(); rb_clear_trace_func(); + rb_gc_call_finalizer_at_exit(); } void @@ -446,219 +423,6 @@ rb_frozen_class_p(VALUE klass) } } -#ifdef C_ALLOCA -# define TMP_PROTECT NODE * volatile tmp__protect_tmp=0 -# define TMP_ALLOC(n) \ - (tmp__protect_tmp = NEW_NODE(NODE_ALLOCA, \ - ALLOC_N(VALUE,n),tmp__protect_tmp,n), \ - (void*)tmp__protect_tmp->nd_head) -#else -# define TMP_PROTECT typedef int foobazzz -# define TMP_ALLOC(n) ALLOCA_N(VALUE,n) -#endif - -#define MATCH_DATA *rb_svar(node->nd_cnt) - -void -rb_add_event_hook(func, events) - rb_event_hook_func_t func; - rb_event_t events; -{ - rb_event_hook_t *hook; - - hook = ALLOC(rb_event_hook_t); - hook->func = func; - hook->events = events; - hook->next = event_hooks; - event_hooks = hook; -} - -int -rb_remove_event_hook(rb_event_hook_func_t func) -{ - rb_event_hook_t *prev, *hook; - - prev = NULL; - hook = event_hooks; - while (hook) { - if (hook->func == func) { - if (prev) { - prev->next = hook->next; - } - else { - event_hooks = hook->next; - } - xfree(hook); - return 0; - } - prev = hook; - hook = hook->next; - } - return -1; -} - -static void -rb_clear_trace_func(void) -{ - /* TODO: fix me */ -} - -/* - * call-seq: - * set_trace_func(proc) => proc - * set_trace_func(nil) => nil - * - * Establishes _proc_ as the handler for tracing, or disables - * tracing if the parameter is +nil+. _proc_ takes up - * to six parameters: an event name, a filename, a line number, an - * object id, a binding, and the name of a class. _proc_ is - * invoked whenever an event occurs. Events are: c-call - * (call a C-language routine), c-return (return from a - * C-language routine), call (call a Ruby method), - * class (start a class or module definition), - * end (finish a class or module definition), - * line (execute code on a new line), raise - * (raise an exception), and return (return from a Ruby - * method). Tracing is disabled within the context of _proc_. - * - * class Test - * def test - * a = 1 - * b = 2 - * end - * end - * - * set_trace_func proc { |event, file, line, id, binding, classname| - * printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname - * } - * t = Test.new - * t.test - * - * line prog.rb:11 false - * c-call prog.rb:11 new Class - * c-call prog.rb:11 initialize Object - * c-return prog.rb:11 initialize Object - * c-return prog.rb:11 new Class - * line prog.rb:12 false - * call prog.rb:2 test Test - * line prog.rb:3 test Test - * line prog.rb:4 test Test - * return prog.rb:4 test Test - */ - - -static VALUE -set_trace_func(VALUE obj, VALUE trace) -{ - rb_event_hook_t *hook; - - if (NIL_P(trace)) { - rb_clear_trace_func(); - rb_remove_event_hook(call_trace_func); - return Qnil; - } - if (!rb_obj_is_proc(trace)) { - rb_raise(rb_eTypeError, "trace_func needs to be Proc"); - } - - /* register trace func */ - /* trace_func = trace; */ - - for (hook = event_hooks; hook; hook = hook->next) { - if (hook->func == call_trace_func) - return trace; - } - rb_add_event_hook(call_trace_func, RUBY_EVENT_ALL); - return trace; -} - -static char * -get_event_name(rb_event_t event) -{ - switch (event) { - case RUBY_EVENT_LINE: - return "line"; - case RUBY_EVENT_CLASS: - return "class"; - case RUBY_EVENT_END: - return "end"; - case RUBY_EVENT_CALL: - return "call"; - case RUBY_EVENT_RETURN: - return "return"; - case RUBY_EVENT_C_CALL: - return "c-call"; - case RUBY_EVENT_C_RETURN: - return "c-return"; - case RUBY_EVENT_RAISE: - return "raise"; - default: - return "unknown"; - } -} - -static void -call_trace_func(rb_event_t event, NODE *node, VALUE self, ID id, VALUE klass) -{ - /* TODO: fix me */ -#if 0 - int state, raised; - NODE *node_save; - VALUE srcfile; - char *event_name; - - if (!trace_func) - return; - if (tracing) - return; - if (id == ID_ALLOCATOR) - return; - if (!node && ruby_sourceline == 0) - return; - - if (!(node_save = ruby_current_node)) { - node_save = NEW_BEGIN(0); - } - tracing = 1; - - if (node) { - ruby_current_node = node; - ruby_sourcefile = node->nd_file; - ruby_sourceline = nd_line(node); - } - if (klass) { - if (TYPE(klass) == T_ICLASS) { - klass = RBASIC(klass)->klass; - } - else if (FL_TEST(klass, FL_SINGLETON)) { - klass = self; - } - } - PUSH_TAG(PROT_NONE); - raised = thread_reset_raised(th); - if ((state = EXEC_TAG()) == 0) { - srcfile = rb_str_new2(ruby_sourcefile ? ruby_sourcefile : "(ruby)"); - event_name = get_event_name(event); - proc_invoke(trace_func, rb_ary_new3(6, rb_str_new2(event_name), - srcfile, - INT2FIX(ruby_sourceline), - id ? ID2SYM(id) : Qnil, - self ? rb_binding_new() : Qnil, - klass ? klass : Qnil), Qundef, 0); - } - if (raised) - thread_set_raised(th); - POP_TAG(); - - tracing = 0; - ruby_current_node = node_save; - SET_CURRENT_SOURCE(); - if (state) - JUMP_TAG(state); -#endif -} - - /* * call-seq: * obj.respond_to?(symbol, include_private=false) => true or false @@ -884,19 +648,11 @@ NORETURN(static void rb_longjmp _((int, VALUE))); static VALUE make_backtrace _((void)); static void -rb_longjmp(tag, mesg) - int tag; - VALUE mesg; +rb_longjmp(int tag, VALUE mesg) { VALUE at; rb_thread_t *th = GET_THREAD(); - /* - //while (th->cfp->pc == 0 || th->cfp->iseq == 0) { - //th->cfp++; - //} - */ - if (thread_set_raised(th)) { th->errinfo = exception_error; JUMP_TAG(TAG_FATAL); @@ -943,7 +699,8 @@ rb_longjmp(tag, mesg) rb_trap_restore_mask(); if (tag != TAG_FATAL) { - /* EXEC_EVENT_HOOK(RUBY_EVENT_RAISE ...) */ + EXEC_EVENT_HOOK(th, RUBY_EVENT_RAISE, th->cfp->self, + 0 /* TODO: id */, 0 /* TODO: klass */); } thread_reset_raised(th); JUMP_TAG(tag); @@ -1889,18 +1646,29 @@ rb_frame_self(void) const char * rb_sourcefile(void) { - rb_iseq_t *iseq = GET_THREAD()->cfp->iseq; - if (RUBY_VM_NORMAL_ISEQ_P(iseq)) { - return RSTRING_PTR(iseq->filename); + rb_thread_t *th = GET_THREAD(); + rb_control_frame_t *cfp = th_get_ruby_level_cfp(th, th->cfp); + + if (cfp) { + return RSTRING_PTR(cfp->iseq->filename); + } + else { + return ""; } - return 0; } int rb_sourceline(void) { rb_thread_t *th = GET_THREAD(); - return th_get_sourceline(th->cfp); + rb_control_frame_t *cfp = th_get_ruby_level_cfp(th, th->cfp); + + if (cfp) { + return th_get_sourceline(cfp); + } + else { + return 0; + } } static VALUE @@ -2991,8 +2759,6 @@ Init_eval(void) rb_define_global_function("trace_var", rb_f_trace_var, -1); /* in variable.c */ rb_define_global_function("untrace_var", rb_f_untrace_var, -1); /* in variable.c */ - rb_define_global_function("set_trace_func", set_trace_func, 1); - rb_define_virtual_variable("$SAFE", safe_getter, safe_setter); } diff --git a/eval_intern.h b/eval_intern.h index 33386a0eab5931..8b2c9726313ac6 100644 --- a/eval_intern.h +++ b/eval_intern.h @@ -239,4 +239,30 @@ void rb_thread_terminate_all(void); #define ruby_cbase() th_get_cbase(GET_THREAD()) + +/* tracer */ +static void inline +exec_event_hooks(rb_event_hook_t *hook, rb_event_flag_t flag, VALUE self, ID id, VALUE klass) +{ + while (hook) { + (*hook->func)(flag, hook->data, self, id, klass); + hook = hook->next; + } +} + +#define EXEC_EVENT_HOOK(th, flag, self, id, klass) do { \ + rb_event_flag_t wait_event__ = th->event_flags; \ + if (UNLIKELY(wait_event__)) { \ + VALUE self__ = (self), klass__ = (klass); \ + ID id__ = (id); \ + if (wait_event__ & flag) { \ + exec_event_hooks(th->event_hooks, flag, self__, id__, klass__); \ + } \ + if (wait_event__ & RUBY_EVENT_VM) { \ + exec_event_hooks(th->vm->event_hooks, flag, self__, id__, klass__); \ + } \ + } \ +} while (0) + + #endif /* EVAL_INTERN_H_INCLUDED */ diff --git a/insns.def b/insns.def index 582e698d1d86f2..d616508b8cdf7b 100644 --- a/insns.def +++ b/insns.def @@ -843,7 +843,6 @@ definemethod get_cref(GET_ISEQ(), GET_LFP())); } - /** @c setting @e make alias (if v_p is Qtrue, make valias) @@ -1020,15 +1019,12 @@ postexe */ DEFINE_INSN trace -(num_t flag, VALUE args) +(num_t nf) () () { - /* TODO: trace instruction design */ - if (th->vm->trace_flag & flag) { - /* */ - args = Qnil; - } + rb_event_flag_t flag = nf; + EXEC_EVENT_HOOK(th, flag, GET_SELF(), 0, 0 /* TODO: id, klass */); } /**********************************************************/ diff --git a/iseq.c b/iseq.c index 4a2bed7c5a5248..8071a5763791dd 100644 --- a/iseq.c +++ b/iseq.c @@ -200,6 +200,7 @@ static rb_compile_option_t COMPILE_OPTION_DEFAULT = { OPT_OPERANDS_UNIFICATION, /* int operands_unification; */ OPT_INSTRUCTIONS_UNIFICATION, /* int instructions_unification; */ OPT_STACK_CACHING, /* int stack_caching; */ + OPT_TRACE_INSTRUCTION, }; static const rb_compile_option_t COMPILE_OPTION_FALSE; @@ -227,6 +228,7 @@ make_compile_option(rb_compile_option_t *option, VALUE opt) SET_COMPILE_OPTION(option, opt, operands_unification); SET_COMPILE_OPTION(option, opt, instructions_unification); SET_COMPILE_OPTION(option, opt, stack_caching); + SET_COMPILE_OPTION(option, opt, trace_instruction); #undef SET_COMPILE_OPTION } else { diff --git a/node.h b/node.h index 14ba0ad38e17c4..bc9a5923d0ca14 100644 --- a/node.h +++ b/node.h @@ -387,23 +387,6 @@ VALUE rb_gvar_get(struct global_entry *); VALUE rb_gvar_set(struct global_entry *, VALUE); VALUE rb_gvar_defined(struct global_entry *); -typedef unsigned int rb_event_t; - -#define RUBY_EVENT_NONE 0x00 -#define RUBY_EVENT_LINE 0x01 -#define RUBY_EVENT_CLASS 0x02 -#define RUBY_EVENT_END 0x04 -#define RUBY_EVENT_CALL 0x08 -#define RUBY_EVENT_RETURN 0x10 -#define RUBY_EVENT_C_CALL 0x20 -#define RUBY_EVENT_C_RETURN 0x40 -#define RUBY_EVENT_RAISE 0x80 -#define RUBY_EVENT_ALL 0xff - -typedef void (*rb_event_hook_func_t)(rb_event_t,NODE*,VALUE,ID,VALUE); -void rb_add_event_hook(rb_event_hook_func_t,rb_event_t); -int rb_remove_event_hook(rb_event_hook_func_t); - #if defined(__cplusplus) } /* extern "C" { */ #endif diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb index 617df574e3b48e..a7d9720d0d340c 100644 --- a/test/ruby/test_settracefunc.rb +++ b/test/ruby/test_settracefunc.rb @@ -35,6 +35,8 @@ def test_event eval("class Foo; end") set_trace_func nil + assert_equal(["c-return", 18, :set_trace_func, TestSetTraceFunc], + events.shift) # TODO assert_equal(["line", 19, :test_event, TestSetTraceFunc], events.shift) # a = 1 assert_equal(["line", 20, :test_event, TestSetTraceFunc], diff --git a/thread.c b/thread.c index 8373318a9ed2c5..05f4840072b563 100644 --- a/thread.c +++ b/thread.c @@ -2393,6 +2393,289 @@ rb_exec_recursive(VALUE (*func) (VALUE, VALUE, int), VALUE obj, VALUE arg) } } +/* tracer */ + +static rb_event_hook_t * +alloc_event_fook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data) +{ + rb_event_hook_t *hook = ALLOC(rb_event_hook_t); + hook->func = func; + hook->flag = events; + hook->data = data; +} + +static void +thread_reset_event_flags(rb_thread_t *th) +{ + rb_event_hook_t *hook = th->event_hooks; + rb_event_flag_t flag = th->event_flags & RUBY_EVENT_VM; + + while (hook) { + flag |= hook->flag; + hook = hook->next; + } +} + +void +rb_thread_add_event_hook(rb_thread_t *th, + rb_event_hook_func_t func, rb_event_flag_t events, VALUE data) +{ + rb_event_hook_t *hook = alloc_event_fook(func, events, data); + hook->next = th->event_hooks; + th->event_hooks = hook; + thread_reset_event_flags(th); +} + +static int +set_threads_event_flags_i(st_data_t key, st_data_t val, st_data_t flag) +{ + VALUE thval = key; + rb_thread_t *th; + GetThreadPtr(thval, th); + + if (flag) { + th->event_flags |= RUBY_EVENT_VM; + } + else { + th->event_flags &= (~RUBY_EVENT_VM); + } + return ST_CONTINUE; +} + +static void +set_threads_event_flags(int flag) +{ + st_foreach(GET_VM()->living_threads, set_threads_event_flags_i, (st_data_t) flag); +} + +void +rb_add_event_hook(rb_event_hook_func_t func, rb_event_flag_t events, VALUE data) +{ + rb_event_hook_t *hook = alloc_event_fook(func, events, data); + rb_vm_t *vm = GET_VM(); + + hook->next = vm->event_hooks; + vm->event_hooks = hook; + + set_threads_event_flags(1); +} + +static int +remove_event_hook(rb_event_hook_t **root, rb_event_hook_func_t func) +{ + rb_event_hook_t *prev = NULL, *hook = *root; + + while (hook) { + if (func == 0 || hook->func == func) { + if (prev) { + prev->next = hook->next; + } + else { + *root = hook->next; + } + xfree(hook); + } + prev = hook; + hook = hook->next; + } + return -1; +} + +int +rb_thread_remove_event_hook(rb_thread_t *th, rb_event_hook_func_t func) +{ + remove_event_hook(&th->event_hooks, func); + thread_reset_event_flags(th); +} + +int +rb_remove_event_hook(rb_event_hook_func_t func) +{ + rb_vm_t *vm = GET_VM(); + rb_event_hook_t *hook = vm->event_hooks; + int ret = remove_event_hook(&vm->event_hooks, func); + + if (hook != NULL && vm->event_hooks == NULL) { + set_threads_event_flags(0); + } + + return ret; +} + +static int +clear_trace_func_i(st_data_t key, st_data_t val, st_data_t flag) +{ + rb_thread_t *th; + GetThreadPtr((VALUE)key, th); + rb_thread_remove_event_hook(th, 0); + return ST_CONTINUE; +} + +void +rb_clear_trace_func(void) +{ + st_foreach(GET_VM()->living_threads, clear_trace_func_i, (st_data_t) 0); + rb_remove_event_hook(0); +} + +/* + * call-seq: + * set_trace_func(proc) => proc + * set_trace_func(nil) => nil + * + * Establishes _proc_ as the handler for tracing, or disables + * tracing if the parameter is +nil+. _proc_ takes up + * to six parameters: an event name, a filename, a line number, an + * object id, a binding, and the name of a class. _proc_ is + * invoked whenever an event occurs. Events are: c-call + * (call a C-language routine), c-return (return from a + * C-language routine), call (call a Ruby method), + * class (start a class or module definition), + * end (finish a class or module definition), + * line (execute code on a new line), raise + * (raise an exception), and return (return from a Ruby + * method). Tracing is disabled within the context of _proc_. + * + * class Test + * def test + * a = 1 + * b = 2 + * end + * end + * + * set_trace_func proc { |event, file, line, id, binding, classname| + * printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname + * } + * t = Test.new + * t.test + * + * line prog.rb:11 false + * c-call prog.rb:11 new Class + * c-call prog.rb:11 initialize Object + * c-return prog.rb:11 initialize Object + * c-return prog.rb:11 new Class + * line prog.rb:12 false + * call prog.rb:2 test Test + * line prog.rb:3 test Test + * line prog.rb:4 test Test + * return prog.rb:4 test Test + */ + +static void call_trace_func(rb_event_flag_t, VALUE data, VALUE self, ID id, VALUE klass); + +static VALUE +set_trace_func(VALUE obj, VALUE trace) +{ + rb_vm_t *vm = GET_VM(); + rb_remove_event_hook(call_trace_func); + + if (NIL_P(trace)) { + return Qnil; + } + + if (!rb_obj_is_proc(trace)) { + rb_raise(rb_eTypeError, "trace_func needs to be Proc"); + } + + rb_add_event_hook(call_trace_func, RUBY_EVENT_ALL, trace); + return trace; +} + +static void +thread_add_trace_func(rb_thread_t *th, VALUE trace) +{ + if (!rb_obj_is_proc(trace)) { + rb_raise(rb_eTypeError, "trace_func needs to be Proc"); + } + + rb_thread_add_event_hook(th, call_trace_func, RUBY_EVENT_ALL, trace); +} + +static VALUE +thread_add_trace_func_m(VALUE obj, VALUE trace) +{ + rb_thread_t *th; + GetThreadPtr(obj, th); + thread_add_trace_func(th, trace); + return trace; +} + +static VALUE +thread_set_trace_func_m(VALUE obj, VALUE trace) +{ + rb_thread_t *th; + GetThreadPtr(obj, th); + rb_thread_remove_event_hook(th, call_trace_func); + + if (!NIL_P(trace)) { + return Qnil; + } + thread_add_trace_func(th, trace); + return trace; +} + +static char * +get_event_name(rb_event_flag_t event) +{ + switch (event) { + case RUBY_EVENT_LINE: + return "line"; + case RUBY_EVENT_CLASS: + return "class"; + case RUBY_EVENT_END: + return "end"; + case RUBY_EVENT_CALL: + return "call"; + case RUBY_EVENT_RETURN: + return "return"; + case RUBY_EVENT_C_CALL: + return "c-call"; + case RUBY_EVENT_C_RETURN: + return "c-return"; + case RUBY_EVENT_RAISE: + return "raise"; + default: + return "unknown"; + } +} + +static void +call_trace_func(rb_event_flag_t event, VALUE proc, VALUE self, ID id, VALUE klass) +{ + rb_thread_t *th = GET_THREAD(); + int state, raised; + VALUE eventname = rb_str_new2(get_event_name(event)); + VALUE filename = rb_str_new2(rb_sourcefile()); + int line = rb_sourceline(); + + if (th->tracing) { + return; + } + else { + th->tracing = 1; + } + + raised = thread_reset_raised(th); + + PUSH_TAG(); + if ((state = EXEC_TAG()) == 0) { + proc_invoke(proc, rb_ary_new3(6, + eventname, filename, INT2FIX(line), + id ? ID2SYM(id) : Qnil, + self ? rb_binding_new() : Qnil, + klass ? klass : Qnil), Qundef, 0); + } + + if (raised) { + thread_set_raised(th); + } + POP_TAG(); + + th->tracing = 0; + if (state) { + JUMP_TAG(state); + } +} /* * +Thread+ encapsulates the behavior of a thread of @@ -2488,6 +2771,12 @@ Init_Thread(void) rb_define_method(rb_cCont, "[]", rb_cont_call, -1); rb_define_global_function("callcc", rb_callcc, 0); + /* trace */ + rb_define_global_function("set_trace_func", set_trace_func, 1); + rb_define_method(rb_cThread, "set_trace_func", thread_set_trace_func_m, 1); + rb_define_method(rb_cThread, "add_trace_func", thread_add_trace_func_m, 1); + + /* init thread core */ Init_native_thread(); { /* main thread setting */ diff --git a/vm.c b/vm.c index 37f0d8cd6e605a..c3cba1ea2123cb 100644 --- a/vm.c +++ b/vm.c @@ -534,25 +534,28 @@ th_call0(rb_thread_t *th, VALUE klass, VALUE recv, break; } case NODE_CFUNC: { - rb_control_frame_t *reg_cfp = th->cfp; - rb_control_frame_t *cfp = - push_frame(th, 0, FRAME_MAGIC_CFUNC, - recv, (VALUE)blockptr, 0, reg_cfp->sp, 0, 1); - - cfp->callee_id = oid; - cfp->method_id = id; - cfp->method_klass = klass; - - val = call_cfunc(body->nd_cfnc, recv, body->nd_argc, argc, argv); - - if (reg_cfp != th->cfp + 1) { - SDR2(reg_cfp); - SDR2(th->cfp-5); - rb_bug("cfp consistency error - call0"); - th->cfp = reg_cfp; + EXEC_EVENT_HOOK(th, RUBY_EVENT_C_CALL, recv, id, klass); + { + rb_control_frame_t *reg_cfp = th->cfp; + rb_control_frame_t *cfp = + push_frame(th, 0, FRAME_MAGIC_CFUNC, + recv, (VALUE)blockptr, 0, reg_cfp->sp, 0, 1); + + cfp->callee_id = oid; + cfp->method_id = id; + cfp->method_klass = klass; + + val = call_cfunc(body->nd_cfnc, recv, body->nd_argc, argc, argv); + + if (reg_cfp != th->cfp + 1) { + SDR2(reg_cfp); + SDR2(th->cfp-5); + rb_bug("cfp consistency error - call0"); + th->cfp = reg_cfp; + } + pop_frame(th); } - pop_frame(th); - + EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, recv, id, klass); break; } case NODE_ATTRSET:{ @@ -1472,6 +1475,8 @@ yarv_init_redefined_flag(void) } } + + #include "vm_evalbody.ci" /* finish diff --git a/vm_macro.def b/vm_macro.def index 9a2d6eb12d9225..2cb9fd7c6d1f66 100644 --- a/vm_macro.def +++ b/vm_macro.def @@ -56,23 +56,28 @@ MACRO macro_eval_setup_send_arguments(num, blockptr, flag, blockiseq) MACRO macro_eval_invoke_cfunc(num, id, recv, klass, mn, blockptr) { - rb_control_frame_t *cfp = - push_frame(th, 0, FRAME_MAGIC_CFUNC, - recv, (VALUE) blockptr, 0, GET_SP(), 0, 1); - cfp->callee_id = id; /* TODO */ - cfp->method_id = id; - cfp->method_klass = klass; - - reg_cfp->sp -= num + 1; - - val = call_cfunc(mn->nd_cfnc, recv, mn->nd_argc, num, reg_cfp->sp + 1); - if (reg_cfp != th->cfp + 1) { - SDR2(reg_cfp); - SDR2(th->cfp-5); - rb_bug("cfp consistency error - send"); - th->cfp = reg_cfp; + EXEC_EVENT_HOOK(th, RUBY_EVENT_C_CALL, recv, id, klass); + { + rb_control_frame_t *cfp = + push_frame(th, 0, FRAME_MAGIC_CFUNC, + recv, (VALUE) blockptr, 0, GET_SP(), 0, 1); + cfp->callee_id = id; /* TODO */ + cfp->method_id = id; + cfp->method_klass = klass; + + reg_cfp->sp -= num + 1; + + val = call_cfunc(mn->nd_cfnc, recv, mn->nd_argc, num, reg_cfp->sp + 1); + + if (reg_cfp != th->cfp + 1) { + SDR2(reg_cfp); + SDR2(th->cfp-5); + rb_bug("cfp consistency error - send"); + th->cfp = reg_cfp; + } + pop_frame(th); } - pop_frame(th); + EXEC_EVENT_HOOK(th, RUBY_EVENT_C_RETURN, recv, id, klass); } MACRO macro_eval_invoke_func(niseqval, recv, klass, blockptr, num) diff --git a/vm_opts.h b/vm_opts.h index a34ee264b45ade..041cef7faa6139 100644 --- a/vm_opts.h +++ b/vm_opts.h @@ -22,6 +22,7 @@ /* VM running option */ #define OPT_CHECKED_RUN 1 +#define OPT_TRACE_INSTRUCTION 1 /* at compile */ #define OPT_INLINE_CONST_CACHE 1 diff --git a/yarvcore.c b/yarvcore.c index d8d332f9e7dc47..7d6869ad5bf043 100644 --- a/yarvcore.c +++ b/yarvcore.c @@ -167,6 +167,15 @@ vm_mark_each_thread_func(st_data_t key, st_data_t value, st_data_t dummy) return ST_CONTINUE; } +static void +mark_event_hooks(rb_event_hook_t *hook) +{ + while (hook) { + rb_gc_mark(hook->data); + hook = hook->next; + } +} + static void vm_mark(void *ptr) { @@ -181,6 +190,8 @@ vm_mark(void *ptr) MARK_UNLESS_NULL(vm->mark_object_ary); MARK_UNLESS_NULL(vm->last_status); MARK_UNLESS_NULL(vm->loaded_features); + + mark_event_hooks(vm->event_hooks); } MARK_REPORT_LEAVE("vm"); @@ -289,6 +300,8 @@ thread_mark(void *ptr) (VALUE *)(&th->machine_regs) + sizeof(th->machine_regs) / sizeof(VALUE)); } + + mark_event_hooks(th->event_hooks); } MARK_UNLESS_NULL(th->stat_insn_usage); diff --git a/yarvcore.h b/yarvcore.h index d0ff4127c511f4..4961adfc754c35 100644 --- a/yarvcore.h +++ b/yarvcore.h @@ -186,6 +186,7 @@ typedef struct rb_compile_option_struct { int operands_unification; int instructions_unification; int stack_caching; + int trace_instruction; } rb_compile_option_t; struct iseq_compile_data { @@ -304,6 +305,29 @@ struct rb_iseq_struct { typedef struct rb_iseq_struct rb_iseq_t; +#define RUBY_EVENT_NONE 0x00 +#define RUBY_EVENT_LINE 0x01 +#define RUBY_EVENT_CLASS 0x02 +#define RUBY_EVENT_END 0x04 +#define RUBY_EVENT_CALL 0x08 +#define RUBY_EVENT_RETURN 0x10 +#define RUBY_EVENT_C_CALL 0x20 +#define RUBY_EVENT_C_RETURN 0x40 +#define RUBY_EVENT_RAISE 0x80 +#define RUBY_EVENT_ALL 0xff +#define RUBY_EVENT_VM 0x100 + +typedef unsigned int rb_event_flag_t; +typedef void (*rb_event_hook_func_t)(rb_event_flag_t, VALUE data, VALUE, ID, VALUE klass); + +typedef struct rb_event_hook_struct { + rb_event_flag_t flag; + rb_event_hook_func_t func; + VALUE data; + struct rb_event_hook_struct *next; +} rb_event_hook_t; + + #define GetVMPtr(obj, ptr) \ Data_Get_Struct(obj, rb_vm_t, ptr) @@ -332,6 +356,9 @@ typedef struct rb_vm_struct { /* signal */ rb_atomic_t signal_buff[RUBY_NSIG]; rb_atomic_t bufferd_signal_size; + + /* hook */ + rb_event_hook_t *event_hooks; } rb_vm_t; typedef struct { @@ -456,6 +483,11 @@ struct rb_thread_struct /* statistics data for profiler */ VALUE stat_insn_usage; + /* tracer */ + rb_event_hook_t *event_hooks; + rb_event_flag_t event_flags; + int tracing; + /* misc */ int method_missing_reason; int abort_on_exception;