Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/zjit-macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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' }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/zjit-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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' }}
Expand Down
2 changes: 1 addition & 1 deletion ext/date/date_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion file.c
Original file line number Diff line number Diff line change
Expand Up @@ -3425,7 +3425,7 @@ unlink_internal(const char *path, void *arg)
* Since the underlying implementation relies on the
* <code>unlink(2)</code> 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.
Expand Down
12 changes: 6 additions & 6 deletions io.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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+;
Expand Down
4 changes: 2 additions & 2 deletions lib/erb/erb.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -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']
Expand Down
2 changes: 1 addition & 1 deletion vcpkg.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
"openssl",
"zlib"
],
"builtin-baseline": "66c0373dc7fca549e5803087b9487edfe3aca0a1"
"builtin-baseline": "c3867e714dd3a51c272826eea77267876517ed99"
}
19 changes: 19 additions & 0 deletions zjit.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
1 change: 1 addition & 0 deletions zjit/bindgen/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
6 changes: 4 additions & 2 deletions zjit/src/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand Down Expand Up @@ -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 {
Expand Down
24 changes: 24 additions & 0 deletions zjit/src/codegen_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]]");
}
1 change: 1 addition & 0 deletions zjit/src/cruby_bindings.inc.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 53 additions & 24 deletions zjit/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ pub enum SideExitReason {
SplatKwPolymorphic,
SplatKwNotProfiled,
DirectiveInduced,
SendWhileTracing,
}

#[derive(Debug, Clone, Copy)]
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -5620,33 +5621,25 @@ impl Function {
let mut passes: Vec<Json> = 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 {
Expand Down Expand Up @@ -7517,6 +7510,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
}
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) });
Expand Down Expand Up @@ -7646,6 +7644,12 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
}
}

// 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,
Expand Down Expand Up @@ -7732,6 +7736,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
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;

Expand Down Expand Up @@ -7766,6 +7775,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
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))?;
Expand Down Expand Up @@ -7795,6 +7809,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
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))?;
Expand Down Expand Up @@ -7829,6 +7848,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
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()?;
Expand Down Expand Up @@ -7859,6 +7883,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
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))?;
Expand Down
2 changes: 2 additions & 0 deletions zjit/src/stats.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
}
}

Expand Down