Skip to content

Commit 4a292de

Browse files
committed
ZJIT: Clear ZJIT compiled code when TracePoint is enabled
1 parent 99bf47a commit 4a292de

13 files changed

Lines changed: 200 additions & 72 deletions

File tree

jit.c

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,3 +466,72 @@ rb_jit_shape_too_complex_p(shape_id_t shape_id)
466466
{
467467
return rb_shape_too_complex_p(shape_id);
468468
}
469+
470+
// Is anyone listening for :c_call and :c_return event currently?
471+
bool
472+
rb_c_method_tracing_currently_enabled(const rb_execution_context_t *ec)
473+
{
474+
rb_event_flag_t tracing_events;
475+
if (rb_multi_ractor_p()) {
476+
tracing_events = ruby_vm_event_enabled_global_flags;
477+
}
478+
else {
479+
// At the time of writing, events are never removed from
480+
// ruby_vm_event_enabled_global_flags so always checking using it would
481+
// mean we don't compile even after tracing is disabled.
482+
tracing_events = rb_ec_ractor_hooks(ec)->events;
483+
}
484+
485+
return tracing_events & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN);
486+
}
487+
488+
void
489+
rb_iseq_reset_jit_func(const rb_iseq_t *iseq)
490+
{
491+
RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(iseq, imemo_iseq));
492+
iseq->body->jit_entry = NULL;
493+
iseq->body->jit_exception = NULL;
494+
// Enable re-compiling this ISEQ. Event when it's invalidated for TracePoint,
495+
// we'd like to re-compile ISEQs that haven't been converted to trace_* insns.
496+
iseq->body->jit_entry_calls = 0;
497+
iseq->body->jit_exception_calls = 0;
498+
}
499+
500+
// Callback type for rb_jit_for_each_iseq
501+
typedef void (*rb_iseq_callback)(const rb_iseq_t *iseq, void *data);
502+
503+
// Callback data for rb_jit_for_each_iseq
504+
struct iseq_callback_data {
505+
rb_iseq_callback callback;
506+
void *data;
507+
};
508+
509+
// Heap-walking callback for rb_jit_for_each_iseq
510+
static int
511+
for_each_iseq_i(void *vstart, void *vend, size_t stride, void *data)
512+
{
513+
const struct iseq_callback_data *callback_data = (struct iseq_callback_data *)data;
514+
VALUE v = (VALUE)vstart;
515+
for (; v != (VALUE)vend; v += stride) {
516+
void *ptr = rb_asan_poisoned_object_p(v);
517+
rb_asan_unpoison_object(v, false);
518+
519+
if (rb_obj_is_iseq(v)) {
520+
rb_iseq_t *iseq = (rb_iseq_t *)v;
521+
callback_data->callback(iseq, callback_data->data);
522+
}
523+
524+
if (ptr) {
525+
rb_asan_poison_object(v);
526+
}
527+
}
528+
return 0;
529+
}
530+
531+
// Walk all ISEQs in the heap and invoke the callback - shared between YJIT and ZJIT
532+
void
533+
rb_jit_for_each_iseq(rb_iseq_callback callback, void *data)
534+
{
535+
struct iseq_callback_data callback_data = { .callback = callback, .data = data };
536+
rb_objspace_each_objects(for_each_iseq_i, (void *)&callback_data);
537+
}

test/ruby/test_zjit.rb

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2148,6 +2148,44 @@ def test
21482148
}
21492149
end
21502150

2151+
def test_global_tracepoint
2152+
assert_compiles 'true', %q{
2153+
def foo = 1
2154+
2155+
foo
2156+
foo
2157+
2158+
called = false
2159+
2160+
tp = TracePoint.new(:return) { |event|
2161+
if event.method_id == :foo
2162+
called = true
2163+
end
2164+
}
2165+
tp.enable do
2166+
foo
2167+
end
2168+
called
2169+
}
2170+
end
2171+
2172+
def test_local_tracepoint
2173+
assert_compiles 'true', %q{
2174+
def foo = 1
2175+
2176+
foo
2177+
foo
2178+
2179+
called = false
2180+
2181+
tp = TracePoint.new(:return) { |_| called = true }
2182+
tp.enable(target: method(:foo)) do
2183+
foo
2184+
end
2185+
called
2186+
}
2187+
end
2188+
21512189
private
21522190

21532191
# Assert that every method call in `test_script` can be compiled by ZJIT

vm_trace.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "vm_core.h"
3636
#include "ruby/ractor.h"
3737
#include "yjit.h"
38+
#include "zjit.h"
3839

3940
#include "builtin.h"
4041

@@ -135,6 +136,7 @@ update_global_event_hook(rb_event_flag_t prev_events, rb_event_flag_t new_events
135136
// Do this after event flags updates so other ractors see updated vm events
136137
// when they wake up.
137138
rb_yjit_tracing_invalidate_all();
139+
rb_zjit_tracing_invalidate_all();
138140
}
139141
}
140142

@@ -1285,6 +1287,7 @@ rb_tracepoint_enable_for_target(VALUE tpval, VALUE target, VALUE target_line)
12851287
}
12861288

12871289
rb_yjit_tracing_invalidate_all();
1290+
rb_zjit_tracing_invalidate_all();
12881291

12891292
ruby_vm_event_local_num++;
12901293

yjit.c

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -343,24 +343,6 @@ rb_yjit_reserve_addr_space(uint32_t mem_size)
343343
#endif
344344
}
345345

346-
// Is anyone listening for :c_call and :c_return event currently?
347-
bool
348-
rb_c_method_tracing_currently_enabled(const rb_execution_context_t *ec)
349-
{
350-
rb_event_flag_t tracing_events;
351-
if (rb_multi_ractor_p()) {
352-
tracing_events = ruby_vm_event_enabled_global_flags;
353-
}
354-
else {
355-
// At the time of writing, events are never removed from
356-
// ruby_vm_event_enabled_global_flags so always checking using it would
357-
// mean we don't compile even after tracing is disabled.
358-
tracing_events = rb_ec_ractor_hooks(ec)->events;
359-
}
360-
361-
return tracing_events & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN);
362-
}
363-
364346
// The code we generate in gen_send_cfunc() doesn't fire the c_return TracePoint event
365347
// like the interpreter. When tracing for c_return is enabled, we patch the code after
366348
// the C method return to call into this to fire the event.
@@ -414,18 +396,6 @@ rb_iseq_set_yjit_payload(const rb_iseq_t *iseq, void *payload)
414396
iseq->body->yjit_payload = payload;
415397
}
416398

417-
void
418-
rb_iseq_reset_jit_func(const rb_iseq_t *iseq)
419-
{
420-
RUBY_ASSERT_ALWAYS(IMEMO_TYPE_P(iseq, imemo_iseq));
421-
iseq->body->jit_entry = NULL;
422-
iseq->body->jit_exception = NULL;
423-
// Enable re-compiling this ISEQ. Event when it's invalidated for TracePoint,
424-
// we'd like to re-compile ISEQs that haven't been converted to trace_* insns.
425-
iseq->body->jit_entry_calls = 0;
426-
iseq->body->jit_exception_calls = 0;
427-
}
428-
429399
rb_proc_t *
430400
rb_yjit_get_proc_ptr(VALUE procv)
431401
{
@@ -650,41 +620,6 @@ rb_yjit_constcache_shareable(const struct iseq_inline_constant_cache_entry *ice)
650620
return (ice->flags & IMEMO_CONST_CACHE_SHAREABLE) != 0;
651621
}
652622

653-
// Used for passing a callback and other data over rb_objspace_each_objects
654-
struct iseq_callback_data {
655-
rb_iseq_callback callback;
656-
void *data;
657-
};
658-
659-
// Heap-walking callback for rb_yjit_for_each_iseq().
660-
static int
661-
for_each_iseq_i(void *vstart, void *vend, size_t stride, void *data)
662-
{
663-
const struct iseq_callback_data *callback_data = (struct iseq_callback_data *)data;
664-
VALUE v = (VALUE)vstart;
665-
for (; v != (VALUE)vend; v += stride) {
666-
void *ptr = rb_asan_poisoned_object_p(v);
667-
rb_asan_unpoison_object(v, false);
668-
669-
if (rb_obj_is_iseq(v)) {
670-
rb_iseq_t *iseq = (rb_iseq_t *)v;
671-
callback_data->callback(iseq, callback_data->data);
672-
}
673-
674-
asan_poison_object_if(ptr, v);
675-
}
676-
return 0;
677-
}
678-
679-
// Iterate through the whole GC heap and invoke a callback for each iseq.
680-
// Used for global code invalidation.
681-
void
682-
rb_yjit_for_each_iseq(rb_iseq_callback callback, void *data)
683-
{
684-
struct iseq_callback_data callback_data = { .callback = callback, .data = data };
685-
rb_objspace_each_objects(for_each_iseq_i, (void *)&callback_data);
686-
}
687-
688623
// For running write barriers from Rust. Required when we add a new edge in the
689624
// object graph from `old` to `young`.
690625
void

yjit/bindgen/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,6 @@ fn main() {
336336
.allowlist_function("rb_yjit_constcache_shareable")
337337
.allowlist_function("rb_iseq_reset_jit_func")
338338
.allowlist_function("rb_yjit_dump_iseq_loc")
339-
.allowlist_function("rb_yjit_for_each_iseq")
340339
.allowlist_function("rb_yjit_obj_written")
341340
.allowlist_function("rb_yjit_str_simple_append")
342341
.allowlist_function("rb_RSTRING_PTR")
@@ -355,6 +354,7 @@ fn main() {
355354
// From jit.c
356355
.allowlist_function("rb_assert_holding_vm_lock")
357356
.allowlist_function("rb_jit_shape_too_complex_p")
357+
.allowlist_function("rb_jit_for_each_iseq")
358358
.allowlist_type("robject_offsets")
359359

360360
// from vm_sync.h

yjit/src/core.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1818,7 +1818,7 @@ pub fn for_each_iseq<F: FnMut(IseqPtr)>(mut callback: F) {
18181818
callback(iseq);
18191819
}
18201820
let mut data: &mut dyn FnMut(IseqPtr) = &mut callback;
1821-
unsafe { rb_yjit_for_each_iseq(Some(callback_wrapper), (&mut data) as *mut _ as *mut c_void) };
1821+
unsafe { rb_jit_for_each_iseq(Some(callback_wrapper), (&mut data) as *mut _ as *mut c_void) };
18221822
}
18231823

18241824
/// Iterate over all on-stack ISEQs

yjit/src/cruby_bindings.inc.rs

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

zjit.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ void rb_zjit_constant_state_changed(ID id);
2323
void rb_zjit_iseq_mark(void *payload);
2424
void rb_zjit_iseq_update_references(void *payload);
2525
void rb_zjit_before_ractor_spawn(void);
26+
void rb_zjit_tracing_invalidate_all(void);
2627
#else
2728
#define rb_zjit_enabled_p false
2829
static inline void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception) {}
@@ -33,6 +34,7 @@ static inline void rb_zjit_cme_invalidate(const rb_callable_method_entry_t *cme)
3334
static inline void rb_zjit_invalidate_ep_is_bp(const rb_iseq_t *iseq) {}
3435
static inline void rb_zjit_constant_state_changed(ID id) {}
3536
static inline void rb_zjit_before_ractor_spawn(void) {}
37+
static inline void rb_zjit_tracing_invalidate_all(void) {}
3638
#endif // #if USE_ZJIT
3739

3840
#endif // #ifndef ZJIT_H

zjit/bindgen/src/main.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,6 @@ fn main() {
351351
.allowlist_function("rb_zjit_vm_unlock")
352352
.allowlist_function("rb_assert_(iseq|cme)_handle")
353353
.allowlist_function("rb_IMEMO_TYPE_P")
354-
.allowlist_function("rb_iseq_reset_jit_func")
355354
.allowlist_function("rb_RSTRING_PTR")
356355
.allowlist_function("rb_RSTRING_LEN")
357356
.allowlist_function("rb_ENCODING_GET")
@@ -367,6 +366,8 @@ fn main() {
367366
// From jit.c
368367
.allowlist_function("rb_assert_holding_vm_lock")
369368
.allowlist_function("rb_jit_shape_too_complex_p")
369+
.allowlist_function("rb_jit_for_each_iseq")
370+
.allowlist_function("rb_iseq_reset_jit_func")
370371
.allowlist_type("robject_offsets")
371372

372373
// from vm_sync.h

zjit/src/codegen.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,17 @@ impl JITState {
6767

6868
/// CRuby API to compile a given ISEQ
6969
#[unsafe(no_mangle)]
70-
pub extern "C" fn rb_zjit_iseq_gen_entry_point(iseq: IseqPtr, _ec: EcPtr) -> *const u8 {
70+
pub extern "C" fn rb_zjit_iseq_gen_entry_point(iseq: IseqPtr, ec: EcPtr) -> *const u8 {
7171
// Do not test the JIT code in HIR tests
7272
if cfg!(test) {
7373
return std::ptr::null();
7474
}
7575

76+
// Check if c_call or c_return tracing is enabled - if so, don't compile
77+
if unsafe { rb_c_method_tracing_currently_enabled(ec) } {
78+
return std::ptr::null();
79+
}
80+
7681
// Take a lock to avoid writing to ISEQ in parallel with Ractors.
7782
// with_vm_lock() does nothing if the program doesn't use Ractors.
7883
with_vm_lock(src_loc!(), || {

0 commit comments

Comments
 (0)