Bug #15313 ยป 0001-Add-a-tail-call-predicate-for-TracePoint.patch
include/ruby/debug.h | ||
---|---|---|
VALUE rb_tracearg_return_value(rb_trace_arg_t *trace_arg);
|
||
VALUE rb_tracearg_raised_exception(rb_trace_arg_t *trace_arg);
|
||
VALUE rb_tracearg_object(rb_trace_arg_t *trace_arg);
|
||
VALUE rb_tracearg_is_tailcall(rb_trace_arg_t *trace_arg);
|
||
/*
|
||
* Postponed Job API
|
spec/ruby/core/tracepoint/tailcall_spec.rb | ||
---|---|---|
require_relative '../../spec_helper'
|
||
describe 'TracePoint#tailcall?' do
|
||
it 'returns true when a tailcall happens' do
|
||
iseq = RubyVM::InstructionSequence.compile(<<-RUBY, "#{__FILE__}:#{__LINE__}", tailcall_optimization: true)
|
||
Module.new do
|
||
def self.leaf(foo, bar)
|
||
foo * bar
|
||
end
|
||
def self.middle
|
||
leaf(2, 3)
|
||
end
|
||
def self.tco
|
||
leaf(1, 9)
|
||
middle
|
||
end
|
||
end
|
||
RUBY
|
||
mod = iseq.eval
|
||
events = []
|
||
TracePoint.new(:call) { |tp| events << [tp.method_id, tp.tailcall?] }.enable do
|
||
mod.tco
|
||
mod.leaf(2, 3)
|
||
end
|
||
events.should == [
|
||
[:tco, false],
|
||
[:leaf, false],
|
||
[:middle, true],
|
||
[:leaf, true],
|
||
[:leaf, false]
|
||
]
|
||
end
|
||
end
|
test/ruby/test_settracefunc.rb | ||
---|---|---|
begin
|
||
eval <<-EOF.gsub(/^.*?: /, ""), nil, 'xyzzy'
|
||
1: trace = TracePoint.trace(*trace_events){|tp| next if !target_thread?
|
||
2: events << [tp.event, tp.lineno, tp.path, _defined_class.(tp), tp.method_id, tp.self, tp.binding.eval("_local_var"), _get_data.(tp)] if tp.path == 'xyzzy'
|
||
2: events << [tp.event, tp.lineno, tp.path, _defined_class.(tp), tp.method_id, tp.self, tp.binding.eval("_local_var"), _get_data.(tp), tp.tailcall?] if tp.path == 'xyzzy'
|
||
3: }
|
||
4: 1.times{|;_local_var| _local_var = :inner
|
||
5: tap{}
|
||
... | ... | |
answer_events = [
|
||
#
|
||
[:c_return, 1, "xyzzy", TracePoint, :trace, TracePoint, :outer, trace],
|
||
[:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing],
|
||
[:c_call, 4, 'xyzzy', Integer, :times, 1, :outer, :nothing],
|
||
[:line, 4, 'xyzzy', self.class, method, self, nil, :nothing],
|
||
[:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing],
|
||
[:c_call, 5, 'xyzzy', Kernel, :tap, self, :inner, :nothing],
|
||
[:c_return, 5, "xyzzy", Kernel, :tap, self, :inner, self],
|
||
[:c_return, 4, "xyzzy", Integer, :times, 1, :outer, 1],
|
||
[:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing],
|
||
[:c_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing],
|
||
[:c_return, 7, "xyzzy", Class, :inherited, Object, :outer, nil],
|
||
[:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
|
||
[:line, 8, "xyzzy", nil, nil, xyzzy.class, nil, :nothing],
|
||
[:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
|
||
[:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing],
|
||
[:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil],
|
||
[:line, 13, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
|
||
[:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing],
|
||
[:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil],
|
||
[:end, 17, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing],
|
||
[:line, 18, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
|
||
[:c_call, 18, "xyzzy", Class, :new, xyzzy.class, :outer, :nothing],
|
||
[:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, :nothing],
|
||
[:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, nil],
|
||
[:c_return,18, "xyzzy", Class, :new, xyzzy.class, :outer, xyzzy],
|
||
[:line, 19, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
|
||
[:call, 9, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing],
|
||
[:line, 10, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing],
|
||
[:line, 11, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, :nothing],
|
||
[:call, 13, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing],
|
||
[:line, 14, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing],
|
||
[:line, 15, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, :nothing],
|
||
[:c_call, 15, "xyzzy", Kernel, :tap, xyzzy, :XYZZY_bar, :nothing],
|
||
[:c_return,15, "xyzzy", Kernel, :tap, xyzzy, :XYZZY_bar, xyzzy],
|
||
[:return, 16, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, xyzzy],
|
||
[:return, 12, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, xyzzy],
|
||
[:line, 20, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
|
||
[:c_call, 20, "xyzzy", Kernel, :raise, self, :outer, :nothing],
|
||
[:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, :outer, :nothing],
|
||
[:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, :outer, :nothing],
|
||
[:c_return,20, "xyzzy", Exception, :initialize, raised_exc, :outer, raised_exc],
|
||
[:c_return,20, "xyzzy", Exception, :exception, RuntimeError, :outer, raised_exc],
|
||
[:c_return,20, "xyzzy", Kernel, :raise, self, :outer, nil],
|
||
[:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, :outer, :nothing],
|
||
[:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, :outer, nil],
|
||
[:raise, 20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc],
|
||
[:c_call, 20, "xyzzy", Module, :===, RuntimeError,:outer, :nothing],
|
||
[:c_return,20, "xyzzy", Module, :===, RuntimeError,:outer, true],
|
||
[:line, 21, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing],
|
||
[:c_call, 21, "xyzzy", TracePoint, :disable, trace, :outer, :nothing],
|
||
[:c_return, 1, "xyzzy", TracePoint, :trace, TracePoint, :outer, trace, false],
|
||
[:line, 4, 'xyzzy', self.class, method, self, :outer, :nothing, false],
|
||
[:c_call, 4, 'xyzzy', Integer, :times, 1, :outer, :nothing, false],
|
||
[:line, 4, 'xyzzy', self.class, method, self, nil, :nothing, false],
|
||
[:line, 5, 'xyzzy', self.class, method, self, :inner, :nothing, false],
|
||
[:c_call, 5, 'xyzzy', Kernel, :tap, self, :inner, :nothing, false],
|
||
[:c_return, 5, "xyzzy", Kernel, :tap, self, :inner, self, false],
|
||
[:c_return, 4, "xyzzy", Integer, :times, 1, :outer, 1, false],
|
||
[:line, 7, 'xyzzy', self.class, method, self, :outer, :nothing, false],
|
||
[:c_call, 7, "xyzzy", Class, :inherited, Object, :outer, :nothing, false],
|
||
[:c_return, 7, "xyzzy", Class, :inherited, Object, :outer, nil, false],
|
||
[:class, 7, "xyzzy", nil, nil, xyzzy.class, nil, :nothing, false],
|
||
[:line, 8, "xyzzy", nil, nil, xyzzy.class, nil, :nothing, false],
|
||
[:line, 9, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing, false],
|
||
[:c_call, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing, false],
|
||
[:c_return, 9, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil, false],
|
||
[:line, 13, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing, false],
|
||
[:c_call, 13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, :nothing, false],
|
||
[:c_return,13, "xyzzy", Module, :method_added, xyzzy.class, :XYZZY_outer, nil, false],
|
||
[:end, 17, "xyzzy", nil, nil, xyzzy.class, :XYZZY_outer, :nothing, false],
|
||
[:line, 18, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing, false],
|
||
[:c_call, 18, "xyzzy", Class, :new, xyzzy.class, :outer, :nothing, false],
|
||
[:c_call, 18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, :nothing, false],
|
||
[:c_return,18, "xyzzy", BasicObject, :initialize, xyzzy, :outer, nil, false],
|
||
[:c_return,18, "xyzzy", Class, :new, xyzzy.class, :outer, xyzzy, false],
|
||
[:line, 19, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing, false],
|
||
[:call, 9, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing, false],
|
||
[:line, 10, "xyzzy", xyzzy.class, :foo, xyzzy, nil, :nothing, false],
|
||
[:line, 11, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, :nothing, false],
|
||
[:call, 13, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing, false],
|
||
[:line, 14, "xyzzy", xyzzy.class, :bar, xyzzy, nil, :nothing, false],
|
||
[:line, 15, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, :nothing, false],
|
||
[:c_call, 15, "xyzzy", Kernel, :tap, xyzzy, :XYZZY_bar, :nothing, false],
|
||
[:c_return,15, "xyzzy", Kernel, :tap, xyzzy, :XYZZY_bar, xyzzy, false],
|
||
[:return, 16, "xyzzy", xyzzy.class, :bar, xyzzy, :XYZZY_bar, xyzzy, false],
|
||
[:return, 12, "xyzzy", xyzzy.class, :foo, xyzzy, :XYZZY_foo, xyzzy, false],
|
||
[:line, 20, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing, false],
|
||
[:c_call, 20, "xyzzy", Kernel, :raise, self, :outer, :nothing, false],
|
||
[:c_call, 20, "xyzzy", Exception, :exception, RuntimeError, :outer, :nothing, false],
|
||
[:c_call, 20, "xyzzy", Exception, :initialize, raised_exc, :outer, :nothing, false],
|
||
[:c_return,20, "xyzzy", Exception, :initialize, raised_exc, :outer, raised_exc, false],
|
||
[:c_return,20, "xyzzy", Exception, :exception, RuntimeError, :outer, raised_exc, false],
|
||
[:c_return,20, "xyzzy", Kernel, :raise, self, :outer, nil, false],
|
||
[:c_call, 20, "xyzzy", Exception, :backtrace, raised_exc, :outer, :nothing, false],
|
||
[:c_return,20, "xyzzy", Exception, :backtrace, raised_exc, :outer, nil, false],
|
||
[:raise, 20, "xyzzy", TestSetTraceFunc, :trace_by_tracepoint, self, :outer, raised_exc, false],
|
||
[:c_call, 20, "xyzzy", Module, :===, RuntimeError,:outer, :nothing, false],
|
||
[:c_return,20, "xyzzy", Module, :===, RuntimeError,:outer, true, false],
|
||
[:line, 21, "xyzzy", TestSetTraceFunc, method, self, :outer, :nothing, false],
|
||
[:c_call, 21, "xyzzy", TracePoint, :disable, trace, :outer, :nothing, false],
|
||
]
|
||
return events, answer_events
|
vm.c | ||
---|---|---|
iseq->body->stack_max);
|
||
RUBY_DTRACE_METHOD_ENTRY_HOOK(ec, me->owner, me->def->original_id);
|
||
EXEC_EVENT_HOOK(ec, RUBY_EVENT_CALL, self, me->def->original_id, me->called_id, me->owner, Qnil);
|
||
EXEC_EVENT_HOOK(ec, RUBY_EVENT_CALL, self, me->def->original_id, me->called_id, me->owner, Qfalse);
|
||
VM_ENV_FLAGS_SET(ec->cfp->ep, VM_FRAME_FLAG_FINISH);
|
||
ret = vm_exec(ec, TRUE);
|
||
EXEC_EVENT_HOOK(ec, RUBY_EVENT_RETURN, self, me->def->original_id, me->called_id, me->owner, ret);
|
vm_core.h | ||
---|---|---|
enum {
|
||
/* Frame/Environment flag bits:
|
||
* MMMM MMMM MMMM MMMM ____ __FF FFFF EEEX (LSB)
|
||
* MMMM MMMM MMMM MMMM ____ _FFF FFFF EEEX (LSB)
|
||
*
|
||
* X : tag for GC marking (It seems as Fixnum)
|
||
* EEE : 3 bits Env flags
|
||
* FF..: 6 bits Frame flags
|
||
* FF..: 7 bits Frame flags
|
||
* MM..: 15 bits frame magic (to check frame corruption)
|
||
*/
|
||
... | ... | |
VM_FRAME_FLAG_CFRAME = 0x0080,
|
||
VM_FRAME_FLAG_LAMBDA = 0x0100,
|
||
VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM = 0x0200,
|
||
VM_FRAME_FLAG_TAILCALLED = 0x0400,
|
||
/* env flag */
|
||
VM_ENV_FLAG_LOCAL = 0x0002,
|
vm_insnhelper.c | ||
---|---|---|
*sp++ = src_argv[i];
|
||
}
|
||
vm_push_frame(ec, iseq, VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL | finish_flag,
|
||
vm_push_frame(ec, iseq, VM_FRAME_MAGIC_METHOD | VM_FRAME_FLAG_TAILCALLED | VM_ENV_FLAG_LOCAL | finish_flag,
|
||
calling->recv, calling->block_handler, (VALUE)me,
|
||
iseq->body->iseq_encoded + opt_pc, sp,
|
||
iseq->body->local_table_size - iseq->body->param.size,
|
||
... | ... | |
VM_ASSERT(vm_event_flags & events);
|
||
/* increment PC because source line is calculated with PC-1 */
|
||
if ((event = (events & (RUBY_EVENT_CLASS | RUBY_EVENT_CALL | RUBY_EVENT_B_CALL))) != 0) {
|
||
VM_ASSERT(event == RUBY_EVENT_CLASS ||
|
||
event == RUBY_EVENT_CALL ||
|
||
event == RUBY_EVENT_B_CALL);
|
||
if ((event = (events & (RUBY_EVENT_CLASS | RUBY_EVENT_B_CALL))) != 0) {
|
||
VM_ASSERT(event == RUBY_EVENT_CLASS || event == RUBY_EVENT_B_CALL);
|
||
reg_cfp->pc++;
|
||
vm_dtrace(event, ec);
|
||
EXEC_EVENT_HOOK(ec, event, GET_SELF(), 0, 0, 0, Qundef);
|
||
reg_cfp->pc--;
|
||
}
|
||
if (events & RUBY_EVENT_CALL) {
|
||
VALUE is_tailcall = (VM_ENV_FLAGS(reg_cfp->ep, VM_FRAME_FLAG_TAILCALLED)) ? Qtrue : Qfalse;
|
||
reg_cfp->pc++;
|
||
vm_dtrace(RUBY_EVENT_CALL, ec);
|
||
EXEC_EVENT_HOOK(ec, RUBY_EVENT_CALL, GET_SELF(), 0, 0, 0, is_tailcall);
|
||
reg_cfp->pc--;
|
||
}
|
||
if (events & RUBY_EVENT_LINE) {
|
||
reg_cfp->pc++;
|
||
vm_dtrace(RUBY_EVENT_LINE, ec);
|
vm_trace.c | ||
---|---|---|
return trace_arg->data;
|
||
}
|
||
VALUE
|
||
rb_tracearg_is_tailcall(rb_trace_arg_t *trace_arg)
|
||
{
|
||
if (trace_arg->event == RUBY_EVENT_CALL) {
|
||
return (trace_arg->data == Qtrue) ? Qtrue : Qfalse;
|
||
}
|
||
return Qfalse;
|
||
}
|
||
/*
|
||
* Type of event
|
||
*
|
||
... | ... | |
return rb_tracearg_raised_exception(get_trace_arg());
|
||
}
|
||
/*
|
||
* Whether the interpreter performed a tail call. Can only
|
||
* be true for +:call+ events.
|
||
*/
|
||
static VALUE
|
||
tracepoint_attr_is_tailcall(VALUE tpval)
|
||
{
|
||
return rb_tracearg_is_tailcall(get_trace_arg());
|
||
}
|
||
static void
|
||
tp_call_trace(VALUE tpval, rb_trace_arg_t *trace_arg)
|
||
{
|
||
... | ... | |
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, "tailcall?", tracepoint_attr_is_tailcall, 0);
|
||
rb_define_singleton_method(rb_cTracePoint, "stat", tracepoint_stat_s, 0);
|
||
}
|