Skip to content

Commit 4a70f94

Browse files
authored
ZJIT: Implement SingleRactorMode invalidation (ruby#14121)
* ZJIT: Implement SingleRactorMode invalidation * ZJIT: Add macro for compiling jumps * ZJIT: Fix typo in comment * YJIT: Fix typo in comment * ZJIT: Avoid using unexported types in zjit.h `enum ruby_vminsn_type` is declared in `insns.inc` and is not exported. Using it in `zjit.h` would cause build errors when the file including it doesn't include `insns.inc`.
1 parent e378a21 commit 4a70f94

File tree

8 files changed

+85
-35
lines changed

8 files changed

+85
-35
lines changed

depend

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12702,6 +12702,7 @@ ractor.$(OBJEXT): {$(VPATH)}vm_debug.h
1270212702
ractor.$(OBJEXT): {$(VPATH)}vm_opts.h
1270312703
ractor.$(OBJEXT): {$(VPATH)}vm_sync.h
1270412704
ractor.$(OBJEXT): {$(VPATH)}yjit.h
12705+
ractor.$(OBJEXT): {$(VPATH)}zjit.h
1270512706
random.$(OBJEXT): $(CCAN_DIR)/check_type/check_type.h
1270612707
random.$(OBJEXT): $(CCAN_DIR)/container_of/container_of.h
1270712708
random.$(OBJEXT): $(CCAN_DIR)/list/list.h

ractor.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "internal/thread.h"
2020
#include "variable.h"
2121
#include "yjit.h"
22+
#include "zjit.h"
2223

2324
VALUE rb_cRactor;
2425
static VALUE rb_cRactorSelector;
@@ -511,6 +512,7 @@ ractor_create(rb_execution_context_t *ec, VALUE self, VALUE loc, VALUE name, VAL
511512
r->debug = cr->debug;
512513

513514
rb_yjit_before_ractor_spawn();
515+
rb_zjit_before_ractor_spawn();
514516
rb_thread_create_ractor(r, args, block);
515517

516518
RB_GC_GUARD(rv);

test/ruby/test_zjit.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,26 @@ def test = A::B::C
950950
RUBY
951951
end
952952

953+
def test_single_ractor_mode_invalidation
954+
# Without invalidating the single-ractor mode, the test would crash
955+
assert_compiles '"errored but not crashed"', <<~RUBY, call_threshold: 2, insns: [:opt_getconstant_path]
956+
C = Object.new
957+
958+
def test
959+
C
960+
rescue Ractor::IsolationError
961+
"errored but not crashed"
962+
end
963+
964+
test
965+
test
966+
967+
Ractor.new {
968+
test
969+
}.value
970+
RUBY
971+
end
972+
953973
def test_dupn
954974
assert_compiles '[[1], [1, 1], :rhs, [nil, :rhs]]', <<~RUBY, insns: [:dupn]
955975
def test(array) = (array[1, 2] ||= :rhs)

yjit/src/invariants.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -303,7 +303,7 @@ pub extern "C" fn rb_yjit_cme_invalidate(callee_cme: *const rb_callable_method_e
303303
});
304304
}
305305

306-
/// Callback for then Ruby is about to spawn a ractor. In that case we need to
306+
/// Callback for when Ruby is about to spawn a ractor. In that case we need to
307307
/// invalidate every block that is assuming single ractor mode.
308308
#[no_mangle]
309309
pub extern "C" fn rb_yjit_before_ractor_spawn() {

zjit.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,25 @@ extern bool rb_zjit_enabled_p;
1414
extern uint64_t rb_zjit_call_threshold;
1515
extern uint64_t rb_zjit_profile_threshold;
1616
void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception);
17-
void rb_zjit_profile_insn(enum ruby_vminsn_type insn, rb_execution_context_t *ec);
17+
void rb_zjit_profile_insn(uint32_t insn, rb_execution_context_t *ec);
1818
void rb_zjit_profile_enable(const rb_iseq_t *iseq);
1919
void rb_zjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop);
2020
void rb_zjit_cme_invalidate(const rb_callable_method_entry_t *cme);
2121
void rb_zjit_invalidate_ep_is_bp(const rb_iseq_t *iseq);
2222
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);
25+
void rb_zjit_before_ractor_spawn(void);
2526
#else
2627
#define rb_zjit_enabled_p false
2728
static inline void rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception) {}
28-
static inline void rb_zjit_profile_insn(enum ruby_vminsn_type insn, rb_execution_context_t *ec) {}
29+
static inline void rb_zjit_profile_insn(uint32_t insn, rb_execution_context_t *ec) {}
2930
static inline void rb_zjit_profile_enable(const rb_iseq_t *iseq) {}
3031
static inline void rb_zjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop) {}
3132
static inline void rb_zjit_cme_invalidate(const rb_callable_method_entry_t *cme) {}
3233
static inline void rb_zjit_invalidate_ep_is_bp(const rb_iseq_t *iseq) {}
3334
static inline void rb_zjit_constant_state_changed(ID id) {}
34-
#endif // #if USE_YJIT
35+
static inline void rb_zjit_before_ractor_spawn(void) {}
36+
#endif // #if USE_ZJIT
3537

3638
#endif // #ifndef ZJIT_H

zjit/src/codegen.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::ffi::{c_int, c_void};
44

55
use crate::asm::Label;
66
use crate::backend::current::{Reg, ALLOC_REGS};
7-
use crate::invariants::{track_bop_assumption, track_cme_assumption, track_stable_constant_names_assumption};
7+
use crate::invariants::{track_bop_assumption, track_cme_assumption, track_single_ractor_assumption, track_stable_constant_names_assumption};
88
use crate::gc::{get_or_create_iseq_payload, append_gc_offsets};
99
use crate::state::ZJITState;
1010
use crate::stats::{counter_ptr, Counter};
@@ -542,9 +542,9 @@ fn gen_patch_point(jit: &mut JITState, asm: &mut Assembler, invariant: &Invarian
542542
let side_exit_ptr = cb.resolve_label(label);
543543
track_stable_constant_names_assumption(idlist, code_ptr, side_exit_ptr);
544544
}
545-
_ => {
546-
debug!("ZJIT: gen_patch_point: unimplemented invariant {invariant:?}");
547-
return;
545+
Invariant::SingleRactorMode => {
546+
let side_exit_ptr = cb.resolve_label(label);
547+
track_single_ractor_assumption(code_ptr, side_exit_ptr);
548548
}
549549
}
550550
});

zjit/src/invariants.rs

Lines changed: 50 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,20 @@
1-
use std::{collections::{HashMap, HashSet}};
1+
use std::{collections::{HashMap, HashSet}, mem};
22

33
use crate::{backend::lir::{asm_comment, Assembler}, cruby::{rb_callable_method_entry_t, ruby_basic_operators, src_loc, with_vm_lock, IseqPtr, RedefinitionFlag, ID}, hir::Invariant, options::debug, state::{zjit_enabled_p, ZJITState}, virtualmem::CodePtr};
44

5+
macro_rules! compile_jumps {
6+
($cb:expr, $jumps:expr, $($comment_args:tt)*) => {
7+
for jump in $jumps {
8+
$cb.with_write_ptr(jump.from, |cb| {
9+
let mut asm = Assembler::new();
10+
asm_comment!(asm, $($comment_args)*);
11+
asm.jmp(jump.to.into());
12+
asm.compile(cb).expect("can write existing code");
13+
});
14+
}
15+
};
16+
}
17+
518
#[derive(Debug, Eq, Hash, PartialEq)]
619
struct Jump {
720
from: CodePtr,
@@ -26,6 +39,9 @@ pub struct Invariants {
2639

2740
/// Map from constant ID to patch points that assume the constant hasn't been redefined
2841
constant_state_patch_points: HashMap<ID, HashSet<Jump>>,
42+
43+
/// Set of patch points that assume that the interpreter is running with only one ractor
44+
single_ractor_patch_points: HashSet<Jump>,
2945
}
3046

3147
/// Called when a basic operator is redefined. Note that all the blocks assuming
@@ -46,14 +62,7 @@ pub extern "C" fn rb_zjit_bop_redefined(klass: RedefinitionFlag, bop: ruby_basic
4662
debug!("BOP is redefined: {}", bop);
4763

4864
// Invalidate all patch points for this BOP
49-
for jump in jumps {
50-
cb.with_write_ptr(jump.from, |cb| {
51-
let mut asm = Assembler::new();
52-
asm_comment!(asm, "BOP is redefined: {}", bop);
53-
asm.jmp(jump.to.into());
54-
asm.compile(cb).expect("can write existing code");
55-
});
56-
}
65+
compile_jumps!(cb, jumps, "BOP is redefined: {}", bop);
5766

5867
cb.mark_all_executable();
5968
}
@@ -159,14 +168,8 @@ pub extern "C" fn rb_zjit_cme_invalidate(cme: *const rb_callable_method_entry_t)
159168
debug!("CME is invalidated: {:?}", cme);
160169

161170
// Invalidate all patch points for this CME
162-
for jump in jumps {
163-
cb.with_write_ptr(jump.from, |cb| {
164-
let mut asm = Assembler::new();
165-
asm_comment!(asm, "CME is invalidated: {:?}", cme);
166-
asm.jmp(jump.to.into());
167-
asm.compile(cb).expect("can write existing code");
168-
});
169-
}
171+
compile_jumps!(cb, jumps, "CME is invalidated: {:?}", cme);
172+
170173
cb.mark_all_executable();
171174
}
172175
});
@@ -187,16 +190,38 @@ pub extern "C" fn rb_zjit_constant_state_changed(id: ID) {
187190
debug!("Constant state changed: {:?}", id);
188191

189192
// Invalidate all patch points for this constant ID
190-
for jump in jumps {
191-
cb.with_write_ptr(jump.from, |cb| {
192-
let mut asm = Assembler::new();
193-
asm_comment!(asm, "Constant state changed: {:?}", id);
194-
asm.jmp(jump.to.into());
195-
asm.compile(cb).expect("can write existing code");
196-
});
197-
}
193+
compile_jumps!(cb, jumps, "Constant state changed: {:?}", id);
198194

199195
cb.mark_all_executable();
200196
}
201197
});
202198
}
199+
200+
/// Track the JIT code that assumes that the interpreter is running with only one ractor
201+
pub fn track_single_ractor_assumption(patch_point_ptr: CodePtr, side_exit_ptr: CodePtr) {
202+
let invariants = ZJITState::get_invariants();
203+
invariants.single_ractor_patch_points.insert(Jump {
204+
from: patch_point_ptr,
205+
to: side_exit_ptr,
206+
});
207+
}
208+
209+
/// Callback for when Ruby is about to spawn a ractor. In that case we need to
210+
/// invalidate every block that is assuming single ractor mode.
211+
#[unsafe(no_mangle)]
212+
pub extern "C" fn rb_zjit_before_ractor_spawn() {
213+
// If ZJIT isn't enabled, do nothing
214+
if !zjit_enabled_p() {
215+
return;
216+
}
217+
218+
with_vm_lock(src_loc!(), || {
219+
let cb = ZJITState::get_code_block();
220+
let jumps = mem::take(&mut ZJITState::get_invariants().single_ractor_patch_points);
221+
222+
// Invalidate all patch points for single ractor mode
223+
compile_jumps!(cb, jumps, "Another ractor spawned, invalidating single ractor mode assumption");
224+
225+
cb.mark_all_executable();
226+
});
227+
}

zjit/src/profile.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ impl Profiler {
3939

4040
/// API called from zjit_* instruction. opcode is the bare (non-zjit_*) instruction.
4141
#[unsafe(no_mangle)]
42-
pub extern "C" fn rb_zjit_profile_insn(bare_opcode: ruby_vminsn_type, ec: EcPtr) {
42+
pub extern "C" fn rb_zjit_profile_insn(bare_opcode: u32, ec: EcPtr) {
4343
with_vm_lock(src_loc!(), || {
4444
let mut profiler = Profiler::new(ec);
45-
profile_insn(&mut profiler, bare_opcode);
45+
profile_insn(&mut profiler, bare_opcode as ruby_vminsn_type);
4646
});
4747
}
4848

0 commit comments

Comments
 (0)