From 31a1a39ace8971f7ac8e1d192c4e61ae89108422 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 15 Oct 2025 13:27:30 -0400 Subject: ZJIT: Never yield to the GC while compiling This fixes a reliable "ZJIT saw a dead object" repro on my machine, and should fix the flaky ones on CI. The code for disabling the GC is the same as the code in newobj_of(). See: https://github.com/ruby/ruby/actions/runs/18511676257/job/52753782036 --- zjit/bindgen/src/main.rs | 2 ++ zjit/src/cruby.rs | 15 ++++++++++++++- zjit/src/cruby_bindings.inc.rs | 2 ++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 975a2d9157..77d482db4e 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -130,6 +130,8 @@ fn main() { .allowlist_function("rb_singleton_class") .allowlist_function("rb_define_class") .allowlist_function("rb_class_get_superclass") + .allowlist_function("rb_gc_disable_no_rest") + .allowlist_function("rb_gc_enable") .allowlist_function("rb_gc_mark") .allowlist_function("rb_gc_mark_movable") .allowlist_function("rb_gc_location") diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 1a2dce03ed..4eb1d3a17c 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -890,6 +890,14 @@ where let mut recursive_lock_level: c_uint = 0; unsafe { rb_jit_vm_lock_then_barrier(&mut recursive_lock_level, file, line) }; + // Ensure GC is off while we have the VM lock because: + // 1. We create many transient Rust collections that hold VALUEs during compilation. + // It's extremely tricky to properly marked and reference update these, not to + // mention the overhead and ergonomics issues. + // 2. If we yield to the GC while compiling, it re-enters our mark and update functions. + // This breaks `&mut` exclusivity since mark functions derive fresh `&mut` from statics + // while there is a stack frame below it that has an overlapping `&mut`. That's UB. + let gc_disabled_pre_call = unsafe { rb_gc_disable_no_rest() }.test(); let ret = match catch_unwind(func) { Ok(result) => result, @@ -909,7 +917,12 @@ where } }; - unsafe { rb_jit_vm_unlock(&mut recursive_lock_level, file, line) }; + unsafe { + if !gc_disabled_pre_call { + rb_gc_enable(); + } + rb_jit_vm_unlock(&mut recursive_lock_level, file, line); + }; ret } diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index f18c0035ee..6a6263ab15 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -745,6 +745,7 @@ unsafe extern "C" { pub fn rb_gc_mark(obj: VALUE); pub fn rb_gc_mark_movable(obj: VALUE); pub fn rb_gc_location(obj: VALUE) -> VALUE; + pub fn rb_gc_enable() -> VALUE; pub fn rb_gc_writebarrier(old: VALUE, young: VALUE); pub fn rb_class_get_superclass(klass: VALUE) -> VALUE; pub static mut rb_cObject: VALUE; @@ -876,6 +877,7 @@ unsafe extern "C" { buff_size: usize, obj: VALUE, ) -> *const ::std::os::raw::c_char; + pub fn rb_gc_disable_no_rest() -> VALUE; pub fn rb_ec_stack_check(ec: *mut rb_execution_context_struct) -> ::std::os::raw::c_int; pub fn rb_gc_writebarrier_remember(obj: VALUE); pub fn rb_shape_id_offset() -> i32; -- cgit v1.2.3