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,
}
}