diff --git a/vm.c b/vm.c index 0fa87a63ef..916e379d67 100644 --- a/vm.c +++ b/vm.c @@ -533,23 +533,12 @@ yjit_compile(rb_execution_context_t *ec) #endif #if USE_ZJIT -bool rb_zjit_iseq_tracing_currently_enabled(void); - static inline rb_jit_func_t zjit_compile(rb_execution_context_t *ec) { const rb_iseq_t *iseq = ec->cfp->iseq; struct rb_iseq_constant_body *body = ISEQ_BODY(iseq); - // Don't compile while tracing is active. ZJIT's send fallback uses - // rb_vm_opt_send_without_block which calls VM_EXEC, setting FLAG_FINISH - // on the callee frame. This changes exception handling semantics for - // throw TAG_RETURN (e.g. return from rescue), causing TracePoint to - // report nil instead of the actual return value. [Bug #21389] - if (rb_zjit_iseq_tracing_currently_enabled()) { - return NULL; - } - if (body->jit_entry == NULL) { body->jit_entry_calls++; diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index fa61f61481..3e82efd8f6 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -292,6 +292,7 @@ fn main() { .allowlist_function("rb_set_cfp_(pc|sp)") .allowlist_function("rb_c_method_tracing_currently_enabled") .allowlist_function("rb_zjit_method_tracing_currently_enabled") + .allowlist_function("rb_zjit_iseq_tracing_currently_enabled") .allowlist_function("rb_full_cfunc_return") .allowlist_function("rb_assert_(iseq|cme)_handle") .allowlist_function("rb_IMEMO_TYPE_P") diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 2b643d22dd..41ebdb0f55 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -2151,6 +2151,7 @@ unsafe extern "C" { pub fn rb_zjit_singleton_class_p(klass: VALUE) -> bool; pub fn rb_zjit_defined_ivar(obj: VALUE, id: ID, pushval: VALUE) -> VALUE; pub fn rb_zjit_method_tracing_currently_enabled() -> bool; + pub fn rb_zjit_iseq_tracing_currently_enabled() -> bool; pub fn rb_zjit_insn_leaf(insn: ::std::os::raw::c_int, opes: *const VALUE) -> bool; pub fn rb_zjit_local_id(iseq: *const rb_iseq_t, idx: ::std::os::raw::c_uint) -> ID; pub fn rb_zjit_cme_is_cfunc( diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 10c420336b..4471827986 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -532,6 +532,7 @@ pub enum SideExitReason { SplatKwPolymorphic, SplatKwNotProfiled, DirectiveInduced, + SendWhileTracing, } #[derive(Debug, Clone, Copy)] @@ -7509,6 +7510,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { } let argc = unsafe { vm_ci_argc((*cd).ci) }; + // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics + if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing }); + break; + } let args = state.stack_pop_n(argc as usize)?; let recv = state.stack_pop()?; let send = fun.push_insn(block, Insn::Send { recv, cd, blockiseq: None, args, state: exit_id, reason: Uncategorized(opcode) }); @@ -7638,6 +7644,12 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { } } + // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics + if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing }); + break; + } + { fn new_branch_block( fun: &mut Function, @@ -7724,6 +7736,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block } + // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics + if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing }); + break; + } let argc = unsafe { vm_ci_argc((*cd).ci) }; let block_arg = (flags & VM_CALL_ARGS_BLOCKARG) != 0; @@ -7758,6 +7775,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block } + // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics + if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing }); + break; + } let argc = unsafe { vm_ci_argc((*cd).ci) }; let args = state.stack_pop_n(argc as usize + usize::from(forwarding))?; @@ -7787,6 +7809,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block } + // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics + if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing }); + break; + } let argc = unsafe { vm_ci_argc((*cd).ci) }; let block_arg = (flags & VM_CALL_ARGS_BLOCKARG) != 0; let args = state.stack_pop_n(argc as usize + usize::from(block_arg))?; @@ -7821,6 +7848,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block } + // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics + if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing }); + break; + } let argc = unsafe { vm_ci_argc((*cd).ci) }; let args = state.stack_pop_n(argc as usize + usize::from(forwarding))?; let recv = state.stack_pop()?; @@ -7851,6 +7883,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) }); break; // End the block } + // Side-exit send fallbacks while tracing to avoid FLAG_FINISH breaking throw TAG_RETURN semantics + if unsafe { rb_zjit_iseq_tracing_currently_enabled() } { + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::SendWhileTracing }); + break; + } let argc = unsafe { vm_ci_argc((*cd).ci) }; let block_arg = (flags & VM_CALL_ARGS_BLOCKARG) != 0; let args = state.stack_pop_n(argc as usize + usize::from(block_arg))?; diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 47a434360d..66f418067c 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -232,6 +232,7 @@ make_counters! { exit_splatkw_polymorphic, exit_splatkw_not_profiled, exit_directive_induced, + exit_send_while_tracing, } // Send fallback counters that are summed as dynamic_send_count @@ -616,6 +617,7 @@ pub fn side_exit_counter(reason: crate::hir::SideExitReason) -> Counter { => exit_patchpoint_no_singleton_class, PatchPoint(Invariant::RootBoxOnly) => exit_patchpoint_root_box_only, + SendWhileTracing => exit_send_while_tracing, } }