Skip to content
This repository has been archived by the owner on Jul 25, 2022. It is now read-only.

Commit

Permalink
Add TracePoint for basic operation redefinition.
Browse files Browse the repository at this point in the history
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.

(cherry picked from commit 3aac74c)
  • Loading branch information
Matthew Gaudet committed Mar 3, 2017
1 parent 6593ce1 commit c5c64fb
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 6 deletions.
13 changes: 7 additions & 6 deletions include/ruby/ruby.h
Expand Up @@ -2059,12 +2059,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
Expand Down
18 changes: 18 additions & 0 deletions test/ruby/test_settracefunc.rb
Expand Up @@ -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
1 change: 1 addition & 0 deletions vm.c
Expand Up @@ -1491,6 +1491,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);
}
}
}
Expand Down
67 changes: 67 additions & 0 deletions vm_trace.c
Expand Up @@ -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:
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -853,6 +855,61 @@ rb_tracearg_return_value(rb_trace_arg_t *trace_arg)
return trace_arg->data;
}

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)
{
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);

Expand Down

0 comments on commit c5c64fb

Please sign in to comment.