ruby-changes:69118
From: Alan <ko1@a...>
Date: Thu, 21 Oct 2021 08:20:53 +0900 (JST)
Subject: [ruby-changes:69118] bd876c243a (master): TracePoint support
https://git.ruby-lang.org/ruby.git/commit/?id=bd876c243a From bd876c243aeace00ea312d0a5bbff091ccc84ba2 Mon Sep 17 00:00:00 2001 From: Alan Wu <XrXr@u...> Date: Wed, 25 Aug 2021 17:00:45 -0400 Subject: TracePoint support This change fixes some cases where YJIT fails to fire tracing events. Most of the situations YJIT did not handle correctly involves enabling tracing while running inside generated code. A new operation to invalidate all generated code is added, which uses patching to make generated code exit at the next VM instruction boundary. A new routine called `jit_prepare_routine_call()` is introduced to facilitate this and should be used when generating code that could allocate, or could otherwise use `RB_VM_LOCK_ENTER()`. The `c_return` event is fired in the middle of an instruction as opposed to at an instruction boundary, so it requires special handling. C method call return points are patched to go to a fucntion which does everything the interpreter does, including firing the `c_return` event. The generated code for C method calls normally does not fire the event. Invalided code should not change after patching so the exits are not clobbered. A new variable is introduced to track the region of code that should not change. --- README.md | 1 - bootstraptest/test_yjit.rb | 214 ++++++++++++++++++++++++++++++ common.mk | 6 +- iseq.c | 16 --- vm_trace.c | 5 + yjit.h | 1 + yjit_codegen.c | 318 ++++++++++++++++++++++++++++++++++++++------- yjit_codegen.h | 5 + yjit_core.c | 53 ++++---- yjit_iface.c | 11 ++ yjit_iface.h | 4 + 11 files changed, 550 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index dff716d281..6760a56950 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,6 @@ To cite this repository in your publications, please use this bibtex snippet: https://github.com/ruby/ruby/blob/trunk/README.md#L32 YJIT is a work in progress and as such may not yet be mature enough for mission-critical software. Below is a list of known limitations, all of which we plan to eventually address: -- No support for the `TracePoint` API (see [#54](https://github.com/Shopify/yjit/issues/54)). - No garbage collection for generated code. Because there is no GC for generated code yet, your software could run out of executable memory if it is large enough. You can change how much executable memory is allocated using [YJIT's command-line options](https://github.com/Shopify/yjit#command-line-options). diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 8b2d5b044e..00abed9f3b 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -1612,3 +1612,217 @@ end https://github.com/ruby/ruby/blob/trunk/bootstraptest/test_yjit.rb#L1612 bar(123, 1.1) bar(123, 1.1) } + +# test enabling a line TracePoint in a C method call +assert_equal '[[:line, true]]', %q{ + events = [] + events.instance_variable_set( + :@tp, + TracePoint.new(:line) { |tp| events << [tp.event, tp.lineno] if tp.path == __FILE__ } + ) + def events.to_str + @tp.enable; '' + end + + # Stay in generated code while enabling tracing + def events.compiled(obj) + String(obj) + @tp.disable; __LINE__ + end + + line = events.compiled(events) + events[0][-1] = (events[0][-1] == line) + + events +} + +# test enabling a c_return TracePoint in a C method call +assert_equal '[[:c_return, :String, :string_alias, "events_to_str"]]', %q{ + events = [] + events.instance_variable_set(:@tp, TracePoint.new(:c_return) { |tp| events << [tp.event, tp.method_id, tp.callee_id, tp.return_value] }) + def events.to_str + @tp.enable; 'events_to_str' + end + + # Stay in generated code while enabling tracing + alias string_alias String + def events.compiled(obj) + string_alias(obj) + @tp.disable + end + + events.compiled(events) + + events +} + +# test enabling a TracePoint that targets a particular line in a C method call +assert_equal '[true]', %q{ + events = [] + events.instance_variable_set(:@tp, TracePoint.new(:line) { |tp| events << tp.lineno }) + def events.to_str + @tp.enable(target: method(:compiled)) + '' + end + + # Stay in generated code while enabling tracing + def events.compiled(obj) + String(obj) + __LINE__ + end + + line = events.compiled(events) + events[0] = (events[0] == line) + + events +} + +# test enabling tracing in the middle of splatarray +assert_equal '[true]', %q{ + events = [] + obj = Object.new + obj.instance_variable_set(:@tp, TracePoint.new(:line) { |tp| events << tp.lineno }) + def obj.to_a + @tp.enable(target: method(:compiled)) + [] + end + + # Enable tracing in the middle of the splatarray instruction + def obj.compiled(obj) + * = *obj + __LINE__ + end + + obj.compiled([]) + line = obj.compiled(obj) + events[0] = (events[0] == line) + + events +} + +# test enabling tracing in the middle of opt_aref. Different since the codegen +# for it ends in a jump. +assert_equal '[true]', %q{ + def lookup(hash, tp) + hash[42] + tp.disable; __LINE__ + end + + lines = [] + tp = TracePoint.new(:line) { lines << _1.lineno if _1.path == __FILE__ } + + lookup(:foo, tp) + lookup({}, tp) + + enable_tracing_on_missing = Hash.new { tp.enable } + + expected_line = lookup(enable_tracing_on_missing, tp) + + lines[0] = true if lines[0] == expected_line + + lines +} + +# test enabling c_call tracing before compiling +assert_equal '[[:c_call, :itself]]', %q{ + def shouldnt_compile + itself + end + + events = [] + tp = TracePoint.new(:c_call) { |tp| events << [tp.event, tp.method_id] } + + # assume first call compiles + tp.enable { shouldnt_compile } + + events +} + +# test enabling c_return tracing before compiling +assert_equal '[[:c_return, :itself, main]]', %q{ + def shouldnt_compile + itself + end + + events = [] + tp = TracePoint.new(:c_return) { |tp| events << [tp.event, tp.method_id, tp.return_value] } + + # assume first call compiles + tp.enable { shouldnt_compile } + + events +} + +# test enabling tracing for a suspended fiber +assert_equal '[[:return, 42]]', %q{ + def traced_method + Fiber.yield + 42 + end + + events = [] + tp = TracePoint.new(:return) { events << [_1.event, _1.return_value] } + # assume first call compiles + fiber = Fiber.new { traced_method } + fiber.resume + tp.enable(target: method(:traced_method)) + fiber.resume + + events +} + +# test compiling on non-tracing ractor then running on a tracing one +assert_equal '[:itself]', %q{ + def traced_method + itself + end + + + tracing_ractor = Ractor.new do + # 1: start tracing + events = [] + tp = TracePoint.new(:c_call) { events << _1.method_id } + tp.enable + Ractor.yield(nil) + + # 3: run comipled method on tracing ractor + Ractor.yield(nil) + traced_method + + events + ensure + tp&.disable + end + + tracing_ractor.take + + # 2: compile on non tracing ractor + traced_method + + tracing_ractor.take + tracing_ractor.take +} + +# Try to hit a lazy branch stub while another ractor enables tracing +assert_equal '42', %q{ + def compiled(arg) + if arg + arg + 1 + else + itself + itself + end + end + + ractor = Ractor.new do + compiled(false) + Ractor.yield(nil) + compiled(41) + end + + tp = TracePoint.new(:line) { itself } + ractor.take + tp.enable + + ractor.take +} diff --git a/common.mk b/common.mk index 5ccc9a389d..288df7aae4 100644 --- a/common.mk +++ b/common.mk @@ -7024,7 +7024,6 @@ iseq.$(OBJEXT): {$(VPATH)}vm_callinfo.h https://github.com/ruby/ruby/blob/trunk/common.mk#L7024 iseq.$(OBJEXT): {$(VPATH)}vm_core.h iseq.$(OBJEXT): {$(VPATH)}vm_opts.h iseq.$(OBJEXT): {$(VPATH)}yjit.h -iseq.$(OBJEXT): {$(VPATH)}yjit_asm.h load.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h load.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h load.$(OBJEXT): $(CCAN_DIR)/list/list.h @@ -16722,6 +16721,7 @@ yjit_codegen.$(OBJEXT): $(top_srcdir)/internal/gc.h https://github.com/ruby/ruby/blob/trunk/common.mk#L16721 yjit_codegen.$(OBJEXT): $(top_srcdir)/internal/imemo.h yjit_codegen.$(OBJEXT): $(top_srcdir)/internal/object.h yjit_codegen.$(OBJEXT): $(top_srcdir)/internal/re.h +yjit_codegen.$(OBJEXT): $(top_srcdir)/internal/sanitizers.h yjit_codegen.$(OBJEXT): $(top_srcdir)/internal/serial.h yjit_codegen.$(OBJEXT): $(top_srcdir)/internal/static_assert.h yjit_codegen.$(OBJEXT): $(top_srcdir)/internal/string.h @@ -16746,6 +16746,7 @@ yjit_codegen.$(OBJEXT): {$(VPATH)}darray.h https://github.com/ruby/ruby/blob/trunk/common.mk#L16746 yjit_codegen.$(OBJEXT): {$(VPATH)}debug_counter.h yjit_codegen.$(OBJEXT): {$(VPATH)}defines.h yjit_codegen.$(OBJEXT): {$(VPATH)}encoding.h +yjit_codegen.$(OBJEXT): {$(VPATH)}gc.h yjit_codegen.$(OBJEXT): {$(VPATH)}id.h yjit_codegen.$(OBJEXT): {$(VPATH)}id_table.h yjit_codegen.$(OBJEXT): {$(VPATH)}insns.def @@ -16898,6 +16899,9 @@ yjit_codegen.$(OBJEXT): {$(VPATH)}missing.h https://github.com/ruby/ruby/blob/trunk/common.mk#L16899 yjit_codegen.$(OBJEXT): {$(VPATH)}node.h yjit_codegen.$(OBJEXT): {$(VPATH)}onigmo.h yjit_codegen.$(OBJEXT): {$(VPATH)}oniguruma.h +yjit_codegen.$(OBJEXT): {$(VPATH)}probes.dmyh +yjit_codegen.$(OBJEXT): {$(VPATH)}probes.h +yjit_codegen.$(OBJEXT): {$(VPATH)}probes_helper.h yjit_codegen.$(OBJEXT): {$(VPATH)}ruby_assert.h yjit_codegen.$(OBJEXT): {$(VPATH)}ruby_atomic.h yjit_codegen.$(OBJEXT): {$(VPATH)}st.h diff --git a/iseq.c b/iseq.c index fe9ae59dad..cd8dfebdbb 100644 --- a/iseq.c +++ b/iseq.c @@ -3181,14 +3181,6 @@ typedef struct insn_data_struct { https://github.com/ruby/ruby/blob/trunk/iseq.c#L3181 } insn_data_t; static insn_data_t insn_data[VM_INSTRUCTION_SIZE/2]; - - - -#include "yjit_asm.h" - - - - void rb_vm_encoded_insn_data_table_init(void) { @@ -3305,10 +3297,6 @@ iseq_add_local_tracepoint(const rb_iseq_t *iseq, rb_ (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/