diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 2db82efd2fc265..d43de41125df47 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -92,7 +92,7 @@ jobs: rustup install ${{ matrix.rust_version }} --profile minimal rustup default ${{ matrix.rust_version }} - - uses: taiki-e/install-action@3a911424851a96b72dc168c8dd71fd98ed215d66 # v2.68.36 + - uses: taiki-e/install-action@42721ded7ddc3cd90f687527e8602066e4e1ff3a # v2.69.2 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index e4a9842c5ad3c6..4f226da9fb8d6f 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -119,7 +119,7 @@ jobs: ruby-version: '3.1' bundler: none - - uses: taiki-e/install-action@3a911424851a96b72dc168c8dd71fd98ed215d66 # v2.68.36 + - uses: taiki-e/install-action@42721ded7ddc3cd90f687527e8602066e4e1ff3a # v2.69.2 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} diff --git a/ext/date/date_core.c b/ext/date/date_core.c index 9755fb819eaaa9..f37c1a54e5f53e 100644 --- a/ext/date/date_core.c +++ b/ext/date/date_core.c @@ -7478,7 +7478,7 @@ strftimev(const char *fmt, VALUE self, * * Date.new(2001, 2, 3).asctime # => "Sat Feb 3 00:00:00 2001" * - * See {asctime}[https://linux.die.net/man/3/asctime]. + * See {asctime}[https://man7.org/linux/man-pages/man3/asctime.3p.html]. * */ static VALUE diff --git a/file.c b/file.c index e503de9d932c3d..e40f67ec73817a 100644 --- a/file.c +++ b/file.c @@ -3425,7 +3425,7 @@ unlink_internal(const char *path, void *arg) * Since the underlying implementation relies on the * unlink(2) system call, the type of * exception raised depends on its error type (see - * https://linux.die.net/man/2/unlink) and has the form of + * https://man7.org/linux/man-pages/man2/unlink.2.html) and has the form of * e.g. Errno::ENOENT. * * See also Dir::rmdir. diff --git a/io.c b/io.c index 0ac78e96cca28d..ab04d8df22864c 100644 --- a/io.c +++ b/io.c @@ -10948,7 +10948,7 @@ advice_arg_check(VALUE advice) * advise(advice, offset = 0, len = 0) -> nil * * Invokes Posix system call - * {posix_fadvise(2)}[https://linux.die.net/man/2/posix_fadvise], + * {posix_fadvise(2)}[https://man7.org/linux/man-pages/man2/posix_fadvise.2.html], * which announces an intention to access data from the current file * in a particular manner. * @@ -11014,7 +11014,7 @@ is_pos_inf(VALUE x) * call-seq: * IO.select(read_ios, write_ios = [], error_ios = [], timeout = nil) -> array or nil * - * Invokes system call {select(2)}[https://linux.die.net/man/2/select], + * Invokes system call {select(2)}[https://man7.org/linux/man-pages/man2/select.2.html], * which monitors multiple file descriptors, * waiting until one or more of the file descriptors * becomes ready for some class of I/O operation. @@ -11101,7 +11101,7 @@ is_pos_inf(VALUE x) * Finally, Linux kernel developers don't guarantee that * readability of select(2) means readability of following read(2) even * for a single process; - * see {select(2)}[https://linux.die.net/man/2/select] + * see {select(2)}[https://man7.org/linux/man-pages/man2/select.2.html] * * Invoking \IO.select before IO#readpartial works well as usual. * However it is not the best way to use \IO.select. @@ -11495,7 +11495,7 @@ rb_ioctl(VALUE io, VALUE req, VALUE arg) * call-seq: * ioctl(integer_cmd, argument) -> integer * - * Invokes Posix system call {ioctl(2)}[https://linux.die.net/man/2/ioctl], + * Invokes Posix system call {ioctl(2)}[https://man7.org/linux/man-pages/man2/ioctl.2.html], * which issues a low-level command to an I/O device. * * Issues a low-level command to an I/O device. @@ -11584,7 +11584,7 @@ rb_fcntl(VALUE io, VALUE req, VALUE arg) * call-seq: * fcntl(integer_cmd, argument) -> integer * - * Invokes Posix system call {fcntl(2)}[https://linux.die.net/man/2/fcntl], + * Invokes Posix system call {fcntl(2)}[https://man7.org/linux/man-pages/man2/fcntl.2.html], * which provides a mechanism for issuing low-level commands to control or query * a file-oriented I/O stream. Arguments and results are platform * dependent. @@ -11614,7 +11614,7 @@ rb_io_fcntl(int argc, VALUE *argv, VALUE io) * call-seq: * syscall(integer_callno, *arguments) -> integer * - * Invokes Posix system call {syscall(2)}[https://linux.die.net/man/2/syscall], + * Invokes Posix system call {syscall(2)}[https://man7.org/linux/man-pages/man2/syscall.2.html], * which calls a specified function. * * Calls the operating system function identified by +integer_callno+; diff --git a/lib/erb/erb.gemspec b/lib/erb/erb.gemspec index 3793e5d70fac87..70113a2a04115e 100644 --- a/lib/erb/erb.gemspec +++ b/lib/erb/erb.gemspec @@ -20,8 +20,8 @@ Gem::Specification.new do |spec| spec.metadata['source_code_uri'] = spec.homepage spec.metadata['changelog_uri'] = "https://github.com/ruby/erb/blob/v#{spec.version}/NEWS.md" - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } + spec.files = Dir.chdir(__dir__) do + `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|\.git|\.github)/}) } end spec.bindir = 'libexec' spec.executables = ['erb'] diff --git a/vcpkg.json b/vcpkg.json index 0bb6deaa118bcb..2652e626e55a83 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -7,5 +7,5 @@ "openssl", "zlib" ], - "builtin-baseline": "66c0373dc7fca549e5803087b9487edfe3aca0a1" + "builtin-baseline": "c3867e714dd3a51c272826eea77267876517ed99" } \ No newline at end of file diff --git a/zjit.c b/zjit.c index 1fdccbff64fab0..7704f4975e0cbb 100644 --- a/zjit.c +++ b/zjit.c @@ -264,6 +264,25 @@ rb_zjit_method_tracing_currently_enabled(void) return tracing_events & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN); } +// Check if any ISEQ trace events are currently enabled. +// Used to prevent ZJIT from compiling while tracing is active, since ZJIT's +// send fallback (rb_vm_opt_send_without_block) uses VM_EXEC which sets +// VM_FRAME_FLAG_FINISH on the callee frame, changing exception handling +// semantics for throw TAG_RETURN (e.g. return from rescue). +bool +rb_zjit_iseq_tracing_currently_enabled(void) +{ + rb_event_flag_t tracing_events; + if (rb_multi_ractor_p()) { + tracing_events = ruby_vm_event_enabled_global_flags; + } + else { + tracing_events = rb_ec_ractor_hooks(GET_EC())->events; + } + + return tracing_events & ISEQ_TRACE_EVENTS; +} + bool rb_zjit_insn_leaf(int insn, const VALUE *opes) { diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index fa61f61481fd95..3e82efd8f623bc 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/codegen.rs b/zjit/src/codegen.rs index 0dd35bdf7eaaac..e51ddafe33798e 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -630,7 +630,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::CCallVariadic { cfunc, recv, name, args, cme, state, blockiseq, return_type: _, elidable: _ } => { gen_ccall_variadic(jit, asm, *cfunc, *name, opnd!(recv), opnds!(args), *cme, *blockiseq, &function.frame_state(*state)) } - Insn::GetIvar { self_val, id, ic, state: _ } => gen_getivar(jit, asm, opnd!(self_val), *id, *ic), + Insn::GetIvar { self_val, id, ic, state } => gen_getivar(jit, asm, opnd!(self_val), *id, *ic, &function.frame_state(*state)), Insn::SetGlobal { id, val, state } => no_output!(gen_setglobal(jit, asm, *id, opnd!(val), &function.frame_state(*state))), Insn::GetGlobal { id, state } => gen_getglobal(jit, asm, *id, &function.frame_state(*state)), &Insn::IsBlockParamModified { ep } => gen_is_block_param_modified(asm, opnd!(ep)), @@ -1107,7 +1107,9 @@ fn gen_ccall_variadic( } /// Emit an uncached instance variable lookup -fn gen_getivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic: *const iseq_inline_iv_cache_entry) -> Opnd { +fn gen_getivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic: *const iseq_inline_iv_cache_entry, state: &FrameState) -> Opnd { + // rb_ivar_get can raise Ractor::IsolationError for class/module ivars from non-main Ractors + gen_prepare_non_leaf_call(jit, asm, state); if ic.is_null() { asm_ccall!(asm, rb_ivar_get, recv, id.0.into()) } else { diff --git a/zjit/src/codegen_tests.rs b/zjit/src/codegen_tests.rs index c3b67b9ff89865..143200c2bc72cf 100644 --- a/zjit/src/codegen_tests.rs +++ b/zjit/src/codegen_tests.rs @@ -5156,3 +5156,27 @@ fn test_local_tracepoint() { called "), @"true"); } + +// Regression test: TracePoint return value for methods with rescue that use `return`. +// ZJIT's send fallback uses rb_vm_opt_send_without_block which calls VM_EXEC, +// setting FLAG_FINISH on the callee frame. This changes how throw TAG_RETURN is +// handled, causing the return value to be nil instead of the actual value. +#[test] +fn test_tracepoint_return_value_with_rescue() { + assert_snapshot!(inspect(" + def f_raise + raise + rescue + return :f_raise_return + end + + ary = [] + TracePoint.new(:return, :b_return){|tp| + ary << [tp.event, tp.method_id, tp.return_value] + }.enable{ + send :f_raise + } + ary.pop # last b_return event is not required + ary + "), @"[[:return, :f_raise, :f_raise_return]]"); +} diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 2b643d22ddd471..41ebdb0f55b512 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 72086cdf964ed6..4471827986ce65 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)] @@ -4949,8 +4950,8 @@ impl Function { } fn optimize_load_store(&mut self) { - let mut compile_time_heap: HashMap<(InsnId, i32), InsnId> = HashMap::new(); for block in self.rpo() { + let mut compile_time_heap: HashMap<(InsnId, i32), InsnId> = HashMap::new(); let old_insns = std::mem::take(&mut self.blocks[block.0].insns); let mut new_insns = vec![]; for insn_id in old_insns { @@ -5620,33 +5621,25 @@ impl Function { let mut passes: Vec = Vec::new(); let should_dump = get_option!(dump_hir_iongraph); - macro_rules! ident_equal { - ($a:ident, $b:ident) => { stringify!($a) == stringify!($b) }; + macro_rules! counter_for { + // Bucket all strength reduction together + (type_specialize) => { Counter::compile_hir_strength_reduce_time_ns }; + (inline) => { Counter::compile_hir_strength_reduce_time_ns }; + (optimize_getivar) => { Counter::compile_hir_strength_reduce_time_ns }; + (optimize_c_calls) => { Counter::compile_hir_strength_reduce_time_ns }; + // End strength reduction bucket + (optimize_load_store) => { Counter::compile_hir_optimize_load_store_time_ns }; + (fold_constants) => { Counter::compile_hir_fold_constants_time_ns }; + (clean_cfg) => { Counter::compile_hir_clean_cfg_time_ns }; + (remove_redundant_patch_points) => { Counter::compile_hir_remove_redundant_patch_points_time_ns }; + (remove_duplicate_check_interrupts) => { Counter::compile_hir_remove_duplicate_check_interrupts_time_ns }; + (eliminate_dead_code) => { Counter::compile_hir_eliminate_dead_code_time_ns }; + ($name:ident) => { unimplemented!("Counter for pass {}", stringify!($name)) }; } macro_rules! run_pass { ($name:ident) => { - // Bucket all strength reduction together - let counter = if ident_equal!($name, type_specialize) - || ident_equal!($name, inline) - || ident_equal!($name, optimize_getivar) - || ident_equal!($name, optimize_c_calls) { - Counter::compile_hir_strength_reduce_time_ns - } else if ident_equal!($name, optimize_load_store) { - Counter::compile_hir_optimize_load_store_time_ns - } else if ident_equal!($name, fold_constants) { - Counter::compile_hir_fold_constants_time_ns - } else if ident_equal!($name, clean_cfg) { - Counter::compile_hir_clean_cfg_time_ns - } else if ident_equal!($name, remove_redundant_patch_points) { - Counter::compile_hir_remove_redundant_patch_points_time_ns - } else if ident_equal!($name, remove_duplicate_check_interrupts) { - Counter::compile_hir_remove_duplicate_check_interrupts_time_ns - } else if ident_equal!($name, eliminate_dead_code) { - Counter::compile_hir_eliminate_dead_code_time_ns - } else { - unimplemented!("Counter for pass {}", stringify!($name)); - }; + let counter = counter_for!($name); crate::stats::with_time_stat(counter, || self.$name()); #[cfg(debug_assertions)] self.assert_validates(); if should_dump { @@ -7517,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) }); @@ -7646,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, @@ -7732,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; @@ -7766,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))?; @@ -7795,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))?; @@ -7829,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()?; @@ -7859,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 47a434360da5ff..66f418067cfcc9 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, } }