Skip to content

Commit 05fb09d

Browse files
committed
Fix concurrent escape bug. Comment about over spilling
1 parent 97f7b4e commit 05fb09d

2 files changed

Lines changed: 46 additions & 4 deletions

File tree

vm.c

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,10 +1146,14 @@ vm_make_env_each(const rb_execution_context_t * const ec, rb_control_frame_t *co
11461146
if (VM_FRAME_RUBYFRAME_P(cfp) &&
11471147
!rbimpl_atomic_load(&ISEQ_BODY(iseq)->jit_ep_escape_recorded, RBIMPL_ATOMIC_RELAXED)) {
11481148
if (rb_yjit_enabled_p) rb_yjit_invalidate_ep_is_bp(iseq);
1149-
if (rb_zjit_enabled_p) {
1150-
rb_zjit_invalidate_no_ep_escape(iseq);
1151-
rb_zjit_spill_frame(cfp);
1152-
}
1149+
if (rb_zjit_enabled_p) rb_zjit_invalidate_no_ep_escape(iseq);
1150+
}
1151+
1152+
// Spill ZJIT frame if there is one. This is a separate concern from
1153+
// invalidation and needs to happen in all execution threads that reach here.
1154+
if (VM_FRAME_RUBYFRAME_P(cfp)) {
1155+
// We only need the locals here but we reconstruct the stack as well for simplicity.
1156+
rb_zjit_spill_frame(cfp);
11531157
}
11541158

11551159
/*

zjit/src/codegen_tests.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8239,6 +8239,44 @@ fn test_no_ep_escape_invalidation_at_max_versions() {
82398239
assert_snapshot!(result, @r#""expected""#);
82408240
}
82418241

8242+
#[test]
8243+
fn test_concurrent_ep_escape() {
8244+
// Test to see if concurrent environment escapes
8245+
// sharing one piece of JIT code produce one consistent result.
8246+
rb_zjit_prepare_options();
8247+
set_call_threshold(2);
8248+
set_inline_threshold(0);
8249+
let result = inspect(r#"
8250+
def spill_escaper(gate)
8251+
a = 1
8252+
b = 2
8253+
spill_captor(gate) { } # empty block, no eager spill
8254+
end
8255+
8256+
# Returning `blk` materializes the Proc, which escapes spill_escaper's EP.
8257+
def spill_captor(gate, &blk)
8258+
gate.receive ? blk : nil
8259+
end
8260+
8261+
ready = Ractor::Port.new
8262+
results = Ractor::Port.new
8263+
ractors = 4.times.map do
8264+
Ractor.new(ready, results) do |ready, results|
8265+
port = Ractor.current.default_port
8266+
2.times { port << false; spill_escaper(port) } # compile spill_escaper, no escape
8267+
ready << :parked # parks until the main Ractor releases
8268+
bnd = spill_escaper(port).binding
8269+
results << [bnd.local_variable_get(:a), bnd.local_variable_get(:b)]
8270+
end
8271+
end
8272+
8273+
ractors.size.times { ready.receive }
8274+
ractors.each { |r| r << true } # have all ractor do EP escapes
8275+
ractors.size.times.map { results.receive }.uniq
8276+
"#);
8277+
assert_snapshot!(result, @r#"[[1, 2]]"#);
8278+
}
8279+
82428280
#[test]
82438281
fn test_float_arithmetic() {
82448282
set_call_threshold(1);

0 commit comments

Comments
 (0)