summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorko1 <ko1@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2007-04-19 10:37:08 +0000
committerko1 <ko1@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2007-04-19 10:37:08 +0000
commita73894337a830cdd32a913964f3150bc35269975 (patch)
tree9867f12c88a52c9c4d7ca5fe30c9b57e5a9e411f
parent2dd91facca2337ff611489f1ccd84441d8fc9bf2 (diff)
* 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. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@12195 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--ChangeLog18
-rw-r--r--compile.c18
-rw-r--r--compile.h5
-rw-r--r--eval.c274
-rw-r--r--eval_intern.h26
-rw-r--r--insns.def10
-rw-r--r--iseq.c2
-rw-r--r--node.h17
-rw-r--r--test/ruby/test_settracefunc.rb2
-rw-r--r--thread.c289
-rw-r--r--vm.c41
-rw-r--r--vm_macro.def37
-rw-r--r--vm_opts.h1
-rw-r--r--yarvcore.c13
-rw-r--r--yarvcore.h32
15 files changed, 472 insertions, 313 deletions
diff --git a/ChangeLog b/ChangeLog
index 85231086f3..18daf8345f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,21 @@
+Thu Apr 19 18:37:49 2007 Koichi Sasada <ko1@atdot.net>
+
+ * 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 <ko1@atdot.net>
* lib/optparse.rb: fix to override conv proc.
diff --git a/compile.c b/compile.c
index 85160de2eb..b028ff853e 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 f0c7089218..9991232446 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 720b5f6afb..b72fbe44de 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: <code>c-call</code>
- * (call a C-language routine), <code>c-return</code> (return from a
- * C-language routine), <code>call</code> (call a Ruby method),
- * <code>class</code> (start a class or module definition),
- * <code>end</code> (finish a class or module definition),
- * <code>line</code> (execute code on a new line), <code>raise</code>
- * (raise an exception), and <code>return</code> (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 33386a0eab..8b2c972631 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 582e698d1d..d616508b8c 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 4a2bed7c5a..8071a57637 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 14ba0ad38e..bc9a5923d0 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 617df574e3..a7d9720d0d 100644
--- a/test/ruby/test_settracefunc.rb
+++ b/test/ruby/test_settracefunc.rb
@@ -35,6 +35,8 @@ class TestSetTraceFunc < Test::Unit::TestCase
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 8373318a9e..05f4840072 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: <code>c-call</code>
+ * (call a C-language routine), <code>c-return</code> (return from a
+ * C-language routine), <code>call</code> (call a Ruby method),
+ * <code>class</code> (start a class or module definition),
+ * <code>end</code> (finish a class or module definition),
+ * <code>line</code> (execute code on a new line), <code>raise</code>
+ * (raise an exception), and <code>return</code> (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 37f0d8cd6e..c3cba1ea21 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 9a2d6eb12d..2cb9fd7c6d 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 a34ee264b4..041cef7faa 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 d8d332f9e7..7d6869ad5b 100644
--- a/yarvcore.c
+++ b/yarvcore.c
@@ -168,6 +168,15 @@ vm_mark_each_thread_func(st_data_t key, st_data_t value, st_data_t dummy)
}
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)
{
MARK_REPORT_ENTER("vm");
@@ -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 d0ff4127c5..4961adfc75 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;