From 3aac74c3c2652ca291f2ed3555561bb27969af6b Mon Sep 17 00:00:00 2001 From: Matthew Gaudet Date: Fri, 24 Feb 2017 13:41:10 -0500 Subject: [PATCH 1/2] Add TracePoint for basic operation redefinition. This infrequent event can have an outsized performance impact by eliminating a lot of the optimization that exists in the Ruby interpreter. In order to know when these sorts of things happen, this patch adds a trace point that will fire when a basic operation is redefined. The event data can be accessed by calling #basic_operation_redefined, which returns a hash of the class being modified and the basic operation that's been redefined. This patch may also be useful for JIT compilers that want to speculate on basic operation definitions. --- include/ruby/ruby.h | 13 ++++---- test/ruby/test_settracefunc.rb | 18 ++++++++++++ vm.c | 1 + vm_trace.c | 67 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 6 deletions(-) diff --git a/include/ruby/ruby.h b/include/ruby/ruby.h index d770c30..50e24fc 100644 --- a/include/ruby/ruby.h +++ b/include/ruby/ruby.h @@ -2056,12 +2056,13 @@ int ruby_native_thread_p(void); #define RUBY_EVENT_ALL 0x00ff /* for TracePoint extended events */ -#define RUBY_EVENT_B_CALL 0x0100 -#define RUBY_EVENT_B_RETURN 0x0200 -#define RUBY_EVENT_THREAD_BEGIN 0x0400 -#define RUBY_EVENT_THREAD_END 0x0800 -#define RUBY_EVENT_FIBER_SWITCH 0x1000 -#define RUBY_EVENT_TRACEPOINT_ALL 0xffff +#define RUBY_EVENT_B_CALL 0x0100 +#define RUBY_EVENT_B_RETURN 0x0200 +#define RUBY_EVENT_THREAD_BEGIN 0x0400 +#define RUBY_EVENT_THREAD_END 0x0800 +#define RUBY_EVENT_FIBER_SWITCH 0x1000 +#define RUBY_EVENT_BASIC_OP_REDEFINED 0x2000 +#define RUBY_EVENT_TRACEPOINT_ALL 0xffff /* special events */ #define RUBY_EVENT_SPECIFIED_LINE 0x010000 diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb index 0930a22..50ece9f 100644 --- a/test/ruby/test_settracefunc.rb +++ b/test/ruby/test_settracefunc.rb @@ -1599,4 +1599,22 @@ def m assert_equal [[:c_call, :itself, :alias_itself], [:c_return, :itself, :alias_itself]], events events.clear end + + def test_basic_op_redefinition_tracepoint + events = [] + TracePoint.new(:basic_op_redefined) { |tp| + next if !target_thread? + events << [tp.event, tp.basic_operation_redefined ] + }.enable { + String.class_eval do + def +(s) + self.concat(s) + end + end + } + assert_equal events.length, 1, "Events #{events}" + assert_equal events[0][0], :basic_op_redefined + assert_equal events[0][1][:klass], String + assert_equal events[0][1][:bop], :BOP_PLUS + end end diff --git a/vm.c b/vm.c index c542546..d6ab195 100644 --- a/vm.c +++ b/vm.c @@ -1483,6 +1483,7 @@ rb_vm_check_redefinition_opt_method(const rb_method_entry_t *me, VALUE klass) int flag = vm_redefinition_check_flag(klass); ruby_vm_redefined_flag[bop] |= flag; + EXEC_EVENT_HOOK(GET_THREAD(), RUBY_EVENT_BASIC_OP_REDEFINED, GET_THREAD()->cfp->self, 0, 0, klass, bop); } } } diff --git a/vm_trace.c b/vm_trace.c index 02bf54e..2a3af79 100644 --- a/vm_trace.c +++ b/vm_trace.c @@ -598,6 +598,7 @@ get_event_id(rb_event_flag_t event) C(thread_end, THREAD_END); C(fiber_switch, FIBER_SWITCH); C(specified_line, SPECIFIED_LINE); + C(basic_op_redefined, BASIC_OP_REDEFINED); case RUBY_EVENT_LINE | RUBY_EVENT_SPECIFIED_LINE: CONST_ID(id, "line"); return id; #undef C default: @@ -706,6 +707,7 @@ symbol2event_flag(VALUE v) C(specified_line, SPECIFIED_LINE); C(a_call, A_CALL); C(a_return, A_RETURN); + C(basic_op_redefined, BASIC_OP_REDEFINED); #undef C rb_raise(rb_eArgError, "unknown event: %"PRIsVALUE, rb_sym2str(sym)); } @@ -854,6 +856,61 @@ rb_tracearg_return_value(rb_trace_arg_t *trace_arg) } VALUE +rb_tracearg_basic_op_redefined(rb_trace_arg_t *trace_arg) +{ + const char * basic_operator_names[] = { + "BOP_PLUS", + "BOP_MINUS", + "BOP_MULT", + "BOP_DIV", + "BOP_MOD", + "BOP_EQ", + "BOP_EQQ", + "BOP_LT", + "BOP_LE", + "BOP_LTLT", + "BOP_AREF", + "BOP_ASET", + "BOP_LENGTH", + "BOP_SIZE", + "BOP_EMPTY_P", + "BOP_SUCC", + "BOP_GT", + "BOP_GE", + "BOP_NOT", + "BOP_NEQ", + "BOP_MATCH", + "BOP_FREEZE", + "BOP_MAX", + "BOP_MIN", + NULL + }; + + VALUE bop, klass, hash; + if (trace_arg->event & (RUBY_EVENT_BASIC_OP_REDEFINED)) { + /* ok */ + } + else { + rb_raise(rb_eRuntimeError, "not supported by this event"); + } + if (trace_arg->data == Qundef) { + rb_bug("tp_basic_op_redefined_m: unreachable"); + } + bop = trace_arg->data; + klass = trace_arg->klass; + + if (bop > BOP_LAST_) { + rb_bug("rb_tracearg_basic_op_redefined: Invalid bop %d", bop); + } + + hash = rb_hash_new(); + rb_hash_aset(hash, ID2SYM(rb_intern("klass")),klass); + rb_hash_aset(hash, ID2SYM(rb_intern("bop")), ID2SYM(rb_intern(basic_operator_names[bop]))); + return hash; +} + + +VALUE rb_tracearg_raised_exception(rb_trace_arg_t *trace_arg) { if (trace_arg->event & (RUBY_EVENT_RAISE)) { @@ -1009,6 +1066,15 @@ tracepoint_attr_raised_exception(VALUE tpval) return rb_tracearg_raised_exception(get_trace_arg()); } +/* + * klass and basic operation redefined on :basic_op_redefinition event. + */ +static VALUE +tracepoint_basic_operation_redefined(VALUE tpval) +{ + return rb_tracearg_basic_op_redefined(get_trace_arg()); +} + static void tp_call_trace(VALUE tpval, rb_trace_arg_t *trace_arg) { @@ -1502,6 +1568,7 @@ Init_vm_trace(void) rb_define_method(rb_cTracePoint, "self", tracepoint_attr_self, 0); rb_define_method(rb_cTracePoint, "return_value", tracepoint_attr_return_value, 0); rb_define_method(rb_cTracePoint, "raised_exception", tracepoint_attr_raised_exception, 0); + rb_define_method(rb_cTracePoint, "basic_operation_redefined", tracepoint_basic_operation_redefined, 0); rb_define_singleton_method(rb_cTracePoint, "stat", tracepoint_stat_s, 0); -- 2.7.4