YJIT: don't compile attr_accessor methods when tracing (#4998)

2d98593bf5 made it so that
attr_accessor methods fire C method tracing events.
Previously, we weren't checking for whether we are tracing before
compiling, leading to missed events.

Since global invalidation invalidates all code, and that attr_accessor
methods can never enable tracing while running, events are only dropped
when YJIT tries to compile when tracing is already enabled.

Factor out the code for checking tracing and check it before generating
code for attr_accessor methods.

This change fixes TestSetTraceFunc#test_tracepoint_attr when it's
ran in isolation.
This commit is contained in:
Alan Wu 2021-10-21 15:07:32 -04:00 коммит произвёл GitHub
Родитель d0cad8ad83
Коммит bdfc23cba9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
2 изменённых файлов: 95 добавлений и 22 удалений

Просмотреть файл

@ -2214,3 +2214,53 @@ assert_equal 'ok', %q{
:ok
}
# regression test for tracing attr_accessor methods.
assert_equal "true", %q{
c = Class.new do
attr_accessor :x
alias y x
alias y= x=
end
obj = c.new
ar_meth = obj.method(:x)
aw_meth = obj.method(:x=)
aar_meth = obj.method(:y)
aaw_meth = obj.method(:y=)
events = []
trace = TracePoint.new(:c_call, :c_return){|tp|
next if tp.path != __FILE__
next if tp.method_id == :call
case tp.event
when :c_call
events << [tp.event, tp.method_id, tp.callee_id]
when :c_return
events << [tp.event, tp.method_id, tp.callee_id, tp.return_value]
end
}
test_proc = proc do
obj.x = 1
obj.x
obj.y = 2
obj.y
aw_meth.call(1)
ar_meth.call
aaw_meth.call(2)
aar_meth.call
end
test_proc.call # populate call caches
trace.enable(&test_proc)
expected = [
[:c_call, :x=, :x=],
[:c_return, :x=, :x=, 1],
[:c_call, :x, :x],
[:c_return, :x, :x, 1],
[:c_call, :x=, :y=],
[:c_return, :x=, :y=, 2],
[:c_call, :x, :y],
[:c_return, :x, :y, 2],
] * 2
expected == events
}

Просмотреть файл

@ -3076,6 +3076,24 @@ lookup_cfunc_codegen(const rb_method_definition_t *def)
return NULL;
}
// Is anyone listening for :c_call and :c_return event currently?
static bool
c_method_tracing_currently_enabled(const jitstate_t *jit)
{
rb_event_flag_t tracing_events;
if (rb_multi_ractor_p()) {
tracing_events = ruby_vm_event_enabled_global_flags;
}
else {
// At the time of writing, events are never removed from
// ruby_vm_event_enabled_global_flags so always checking using it would
// mean we don't compile even after tracing is disabled.
tracing_events = rb_ec_ractor_hooks(jit->ec)->events;
}
return tracing_events & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN);
}
static codegen_status_t
gen_send_cfunc(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const rb_callable_method_entry_t *cme, rb_iseq_t *block, const int32_t argc, VALUE *recv_known_klass)
{
@ -3099,23 +3117,10 @@ gen_send_cfunc(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const
return YJIT_CANT_COMPILE;
}
// Don't JIT if tracing c_call or c_return
{
rb_event_flag_t tracing_events;
if (rb_multi_ractor_p()) {
tracing_events = ruby_vm_event_enabled_global_flags;
}
else {
// We could always use ruby_vm_event_enabled_global_flags,
// but since events are never removed from it, doing so would mean
// we don't compile even after tracing is disabled.
tracing_events = rb_ec_ractor_hooks(jit->ec)->events;
}
if (tracing_events & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN)) {
GEN_COUNTER_INC(cb, send_cfunc_tracing);
return YJIT_CANT_COMPILE;
}
if (c_method_tracing_currently_enabled(jit)) {
// Don't JIT if tracing c_call or c_return
GEN_COUNTER_INC(cb, send_cfunc_tracing);
return YJIT_CANT_COMPILE;
}
// Delegate to codegen for C methods if we have it.
@ -3848,12 +3853,24 @@ gen_send_general(jitstate_t *jit, ctx_t *ctx, struct rb_call_data *cd, rb_iseq_t
GEN_COUNTER_INC(cb, send_getter_arity);
return YJIT_CANT_COMPILE;
}
else {
mov(cb, REG0, recv);
ID ivar_name = cme->def->body.attr.id;
return gen_get_ivar(jit, ctx, SEND_MAX_DEPTH, comptime_recv, ivar_name, recv_opnd, side_exit);
if (c_method_tracing_currently_enabled(jit)) {
// Can't generate code for firing c_call and c_return events
// :attr-tracing:
// Handling the C method tracing events for attr_accessor
// methods is easier than regular C methods as we know the
// "method" we are calling into never enables those tracing
// events. Once global invalidation runs, the code for the
// attr_accessor is invalidated and we exit at the closest
// instruction boundary which is always outside of the body of
// the attr_accessor code.
GEN_COUNTER_INC(cb, send_cfunc_tracing);
return YJIT_CANT_COMPILE;
}
mov(cb, REG0, recv);
ID ivar_name = cme->def->body.attr.id;
return gen_get_ivar(jit, ctx, SEND_MAX_DEPTH, comptime_recv, ivar_name, recv_opnd, side_exit);
case VM_METHOD_TYPE_ATTRSET:
if ((vm_ci_flag(ci) & VM_CALL_KWARG) != 0) {
GEN_COUNTER_INC(cb, send_attrset_kwargs);
@ -3863,6 +3880,12 @@ gen_send_general(jitstate_t *jit, ctx_t *ctx, struct rb_call_data *cd, rb_iseq_t
GEN_COUNTER_INC(cb, send_ivar_set_method);
return YJIT_CANT_COMPILE;
}
else if (c_method_tracing_currently_enabled(jit)) {
// Can't generate code for firing c_call and c_return events
// See :attr-tracing:
GEN_COUNTER_INC(cb, send_cfunc_tracing);
return YJIT_CANT_COMPILE;
}
else {
ID ivar_name = cme->def->body.attr.id;
return gen_set_ivar(jit, ctx, comptime_recv, comptime_recv_klass, ivar_name);