From 41865bb6712a14ad1fc2e729316b4e2d8452babc Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 7 Nov 2025 21:57:24 +0900 Subject: [PATCH 1/6] Use `IO.popen` instead of `IO.foreach` with pipe --- win32/mkexports.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/win32/mkexports.rb b/win32/mkexports.rb index 1a9f474be28826..44bda94990d52f 100755 --- a/win32/mkexports.rb +++ b/win32/mkexports.rb @@ -138,7 +138,11 @@ def each_export(objs) class Exports::Cygwin < Exports def self.nm - @@nm ||= RbConfig::CONFIG["NM"] + @@nm ||= + begin + require 'shellwords' + RbConfig::CONFIG["NM"].shellsplit + end end def exports(*) @@ -146,7 +150,9 @@ def exports(*) end def each_line(objs, &block) - IO.foreach("|#{self.class.nm} --extern-only --defined-only #{objs.join(' ')}", &block) + IO.popen([*self.class.nm, *%w[--extern-only --defined-only], *objs]) do |f| + f.each(&block) + end end def each_export(objs) From a2201570bd12e096b0cecf9d82f4d45eb19c8676 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:55:17 +0100 Subject: [PATCH 2/6] Remove `rb_path_check` declaration Implementation was removed in https://github.com/ruby/ruby/commit/a4c051b870ac7f7b3c5482baf05600e1f6751b47 --- include/ruby/internal/intern/file.h | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/include/ruby/internal/intern/file.h b/include/ruby/internal/intern/file.h index b669758d21ac09..8508b7ab9e0b51 100644 --- a/include/ruby/internal/intern/file.h +++ b/include/ruby/internal/intern/file.h @@ -211,22 +211,6 @@ int rb_is_absolute_path(const char *path); */ rb_off_t rb_file_size(VALUE file); -#ifdef RBIMPL_ATTR_DEPRECATED_INTERNAL_ONLY -RBIMPL_ATTR_DEPRECATED_INTERNAL_ONLY() -#endif -/** - * If the PATH_SEPARATOR-separated list of directory names contains the name of - * a world-writable directory, issue a warning for it. This may do nothing on - * some platforms. - * - * @param[in] path A local path. - * @retval 0 The "check" succeeded. - * @retval otherwise The "check" failed. - * @note This feature may be disabled by setting `ENABLE_PATH_CHECK` - * macro to zero at compilation time. - */ -int rb_path_check(const char *path); - RBIMPL_SYMBOL_EXPORT_END() #endif /* RBIMPL_INTERN_FILE_H */ From 201ed7541b840310105c272625e5c0d01fe1d48b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 7 Nov 2025 23:09:24 +0900 Subject: [PATCH 3/6] Suppress sign-compare warnings --- vm_dump.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vm_dump.c b/vm_dump.c index c51c6cca81ea5a..1b0d69b30e9b5a 100644 --- a/vm_dump.c +++ b/vm_dump.c @@ -134,7 +134,7 @@ control_frame_dump(const rb_execution_context_t *ec, const rb_control_frame_t *c iseq = cfp->iseq; pc = cfp->pc - ISEQ_BODY(iseq)->iseq_encoded; iseq_name = RSTRING_PTR(ISEQ_BODY(iseq)->location.label); - if (pc >= 0 && pc <= ISEQ_BODY(iseq)->iseq_size) { + if (pc >= 0 && (size_t)pc <= ISEQ_BODY(iseq)->iseq_size) { line = rb_vm_get_sourceline(cfp); } if (line) { @@ -355,7 +355,7 @@ box_env_dump(const rb_execution_context_t *ec, const VALUE *env, const rb_contro iseq = cfp->iseq; pc = cfp->pc - ISEQ_BODY(iseq)->iseq_encoded; iseq_name = RSTRING_PTR(ISEQ_BODY(iseq)->location.label); - if (pc >= 0 && pc <= ISEQ_BODY(iseq)->iseq_size) { + if (pc >= 0 && (size_t)pc <= ISEQ_BODY(iseq)->iseq_size) { line = rb_vm_get_sourceline(cfp); } if (line) { From a1366f21e3beb936fedcd2dcce51b2d10a5434a0 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 7 Nov 2025 23:47:16 +0900 Subject: [PATCH 4/6] [Feature #19630] [DOC] News about removal of IO operation with `|` --- NEWS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS.md b/NEWS.md index d8dc21f795d291..3306e3e5f6e713 100644 --- a/NEWS.md +++ b/NEWS.md @@ -55,6 +55,9 @@ Note: We're only listing outstanding class updates. [[Feature #21219]] + * A deprecated behavior, process creation by `Kernel#open` with a + leading `|`, was removed. [[Feature #19630]] + * Binding * `Binding#local_variables` does no longer include numbered parameters. @@ -66,6 +69,9 @@ Note: We're only listing outstanding class updates. * `IO.select` accepts `Float::INFINITY` as a timeout argument. [[Feature #20610]] + * A deprecated behavior, process creation by `IO` class methods + with a leading `|`, was removed. [[Feature #19630]] + * Math * `Math.log1p` and `Math.expm1` are added. [[Feature #21527]] @@ -317,6 +323,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #15408]: https://bugs.ruby-lang.org/issues/15408 [Feature #17473]: https://bugs.ruby-lang.org/issues/17473 [Feature #18455]: https://bugs.ruby-lang.org/issues/18455 +[Feature #19630]: https://bugs.ruby-lang.org/issues/19630 [Feature #19908]: https://bugs.ruby-lang.org/issues/19908 [Feature #20610]: https://bugs.ruby-lang.org/issues/20610 [Feature #20724]: https://bugs.ruby-lang.org/issues/20724 From ae60b0bfd84a3d860f43fe8b6ea91048a25e41e6 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Sat, 8 Nov 2025 00:29:34 +0900 Subject: [PATCH 5/6] Use DOT_WAIT for old GNU make [ci skip] --- defs/gmake.mk | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/defs/gmake.mk b/defs/gmake.mk index 3dcfe9f639a244..da98f70d9a7cb7 100644 --- a/defs/gmake.mk +++ b/defs/gmake.mk @@ -548,13 +548,17 @@ matz: OLD := $(MAJOR).$(MINOR).0 ifdef NEW matz: MAJOR := $(word 1,$(subst ., ,$(NEW))) matz: MINOR := $(word 2,$(subst ., ,$(NEW))) -matz: .WAIT bump_news +matz: $(DOT_WAIT) bump_news +bump_news$(DOT_WAIT): up +bump_headers$(DOT_WAIT): bump_news else matz: MINOR := $(shell expr $(MINOR) + 1) -matz: .WAIT reset_news +matz: $(DOT_WAIT) reset_news +flush_news$(DOT_WAIT): up +bump_headers$(DOT_WAIT): reset_news endif -matz: .WAIT bump_headers +matz: $(DOT_WAIT) bump_headers matz: override NEW := $(MAJOR).$(MINOR).0 matz: files := include/ruby/version.h include/ruby/internal/abi.h matz: message := Development of $(NEW) started. From 9bbe4b600b2bc2866561d095c3409589a214a358 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Fri, 7 Nov 2025 08:54:21 -0800 Subject: [PATCH 6/6] ZJIT: Carve out IseqPayload into a separate module (#15098) --- zjit/src/codegen.rs | 3 +- zjit/src/gc.rs | 87 +----------------------------------------- zjit/src/hir.rs | 2 +- zjit/src/invariants.rs | 5 ++- zjit/src/lib.rs | 1 + zjit/src/payload.rs | 86 +++++++++++++++++++++++++++++++++++++++++ zjit/src/profile.rs | 2 +- 7 files changed, 96 insertions(+), 90 deletions(-) create mode 100644 zjit/src/payload.rs diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index f90c4605a26050..fd34b6e0dfba17 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -13,7 +13,8 @@ use crate::invariants::{ track_bop_assumption, track_cme_assumption, track_no_ep_escape_assumption, track_no_trace_point_assumption, track_single_ractor_assumption, track_stable_constant_names_assumption, track_no_singleton_class_assumption }; -use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqCodePtrs, IseqPayload, IseqStatus}; +use crate::gc::append_gc_offsets; +use crate::payload::{get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqCodePtrs, IseqPayload, IseqStatus}; use crate::state::ZJITState; use crate::stats::{send_fallback_counter, exit_counter_for_compile_error, incr_counter, incr_counter_by, send_fallback_counter_for_method_type, send_without_block_fallback_counter_for_method_type, send_without_block_fallback_counter_for_optimized_method_type, send_fallback_counter_ptr_for_opcode, CompileError}; use crate::stats::{counter_ptr, with_time_stat, Counter, Counter::{compile_time_ns, exit_compile_error}}; diff --git a/zjit/src/gc.rs b/zjit/src/gc.rs index baa0926c515189..93a9b10e562c4a 100644 --- a/zjit/src/gc.rs +++ b/zjit/src/gc.rs @@ -1,94 +1,11 @@ //! This module is responsible for marking/moving objects on GC. use std::{ffi::c_void, ops::Range}; -use crate::codegen::IseqCallRef; -use crate::stats::CompileError; -use crate::{cruby::*, profile::IseqProfile, state::ZJITState, stats::with_time_stat, virtualmem::CodePtr}; +use crate::{cruby::*, state::ZJITState, stats::with_time_stat, virtualmem::CodePtr}; +use crate::payload::{IseqPayload, get_or_create_iseq_payload, payload_ptr_as_mut}; use crate::stats::Counter::gc_time_ns; use crate::state::gc_mark_raw_samples; -/// This is all the data ZJIT stores on an ISEQ. We mark objects in this struct on GC. -#[derive(Debug)] -pub struct IseqPayload { - /// Compilation status of the ISEQ. It has the JIT code address of the first block if Compiled. - pub status: IseqStatus, - - /// Type information of YARV instruction operands - pub profile: IseqProfile, - - /// GC offsets of the JIT code. These are the addresses of objects that need to be marked. - pub gc_offsets: Vec, - - /// JIT-to-JIT calls in the ISEQ. The IseqPayload's ISEQ is the caller of it. - pub iseq_calls: Vec, -} - -impl IseqPayload { - fn new(iseq_size: u32) -> Self { - Self { - status: IseqStatus::NotCompiled, - profile: IseqProfile::new(iseq_size), - gc_offsets: vec![], - iseq_calls: vec![], - } - } -} - -/// Set of CodePtrs for an ISEQ -#[derive(Clone, Debug, PartialEq)] -pub struct IseqCodePtrs { - /// Entry for the interpreter - pub start_ptr: CodePtr, - /// Entries for JIT-to-JIT calls - pub jit_entry_ptrs: Vec, -} - -#[derive(Debug, PartialEq)] -pub enum IseqStatus { - Compiled(IseqCodePtrs), - CantCompile(CompileError), - NotCompiled, -} - -/// Get a pointer to the payload object associated with an ISEQ. Create one if none exists. -pub fn get_or_create_iseq_payload_ptr(iseq: IseqPtr) -> *mut IseqPayload { - type VoidPtr = *mut c_void; - - unsafe { - let payload = rb_iseq_get_zjit_payload(iseq); - if payload.is_null() { - // Allocate a new payload with Box and transfer ownership to the GC. - // We drop the payload with Box::from_raw when the GC frees the ISEQ and calls us. - // NOTE(alan): Sometimes we read from an ISEQ without ever writing to it. - // We allocate in those cases anyways. - let iseq_size = get_iseq_encoded_size(iseq); - let new_payload = IseqPayload::new(iseq_size); - let new_payload = Box::into_raw(Box::new(new_payload)); - rb_iseq_set_zjit_payload(iseq, new_payload as VoidPtr); - - new_payload - } else { - payload as *mut IseqPayload - } - } -} - -/// Get the payload object associated with an ISEQ. Create one if none exists. -pub fn get_or_create_iseq_payload(iseq: IseqPtr) -> &'static mut IseqPayload { - let payload_non_null = get_or_create_iseq_payload_ptr(iseq); - payload_ptr_as_mut(payload_non_null) -} - -/// Convert an IseqPayload pointer to a mutable reference. Only one reference -/// should be kept at a time. -fn payload_ptr_as_mut(payload_ptr: *mut IseqPayload) -> &'static mut IseqPayload { - // SAFETY: we should have the VM lock and all other Ruby threads should be asleep. So we have - // exclusive mutable access. - // Hmm, nothing seems to stop calling this on the same - // iseq twice, though, which violates aliasing rules. - unsafe { payload_ptr.as_mut() }.unwrap() -} - /// GC callback for marking GC objects in the per-ISEQ payload. #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_iseq_mark(payload: *mut c_void) { diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 07ffe4b00a7f5e..5a15aac06c43cb 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -6,7 +6,7 @@ #![allow(clippy::if_same_then_else)] #![allow(clippy::match_like_matches_macro)] use crate::{ - cast::IntoUsize, codegen::local_idx_to_ep_offset, cruby::*, gc::{get_or_create_iseq_payload, IseqPayload}, options::{debug, get_option, DumpHIR}, state::ZJITState + cast::IntoUsize, codegen::local_idx_to_ep_offset, cruby::*, payload::{get_or_create_iseq_payload, IseqPayload}, options::{debug, get_option, DumpHIR}, state::ZJITState }; use std::{ cell::RefCell, collections::{HashMap, HashSet, VecDeque}, ffi::{c_void, c_uint, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter diff --git a/zjit/src/invariants.rs b/zjit/src/invariants.rs index 119aa5ca52fb66..f1adc7b32cfdb9 100644 --- a/zjit/src/invariants.rs +++ b/zjit/src/invariants.rs @@ -2,7 +2,8 @@ use std::{collections::{HashMap, HashSet}, mem}; -use crate::{backend::lir::{asm_comment, Assembler}, cruby::{iseq_name, rb_callable_method_entry_t, rb_gc_location, ruby_basic_operators, src_loc, with_vm_lock, IseqPtr, RedefinitionFlag, ID, VALUE}, gc::IseqPayload, hir::Invariant, options::debug, state::{zjit_enabled_p, ZJITState}, virtualmem::CodePtr}; +use crate::{backend::lir::{asm_comment, Assembler}, cruby::{iseq_name, rb_callable_method_entry_t, rb_gc_location, ruby_basic_operators, src_loc, with_vm_lock, IseqPtr, RedefinitionFlag, ID, VALUE}, hir::Invariant, options::debug, state::{zjit_enabled_p, ZJITState}, virtualmem::CodePtr}; +use crate::payload::IseqPayload; use crate::stats::with_time_stat; use crate::stats::Counter::invalidation_time_ns; use crate::gc::remove_gc_offsets; @@ -367,7 +368,7 @@ pub fn track_no_trace_point_assumption(patch_point_ptr: CodePtr, side_exit_ptr: #[unsafe(no_mangle)] pub extern "C" fn rb_zjit_tracing_invalidate_all() { - use crate::gc::{get_or_create_iseq_payload, IseqStatus}; + use crate::payload::{get_or_create_iseq_payload, IseqStatus}; use crate::cruby::{for_each_iseq, rb_iseq_reset_jit_func}; if !zjit_enabled_p() { diff --git a/zjit/src/lib.rs b/zjit/src/lib.rs index e58cf2bec4787b..f8ff380148004e 100644 --- a/zjit/src/lib.rs +++ b/zjit/src/lib.rs @@ -28,3 +28,4 @@ mod profile; mod invariants; mod bitset; mod gc; +mod payload; diff --git a/zjit/src/payload.rs b/zjit/src/payload.rs new file mode 100644 index 00000000000000..1fb3f919946dbb --- /dev/null +++ b/zjit/src/payload.rs @@ -0,0 +1,86 @@ +use std::ffi::c_void; +use crate::codegen::IseqCallRef; +use crate::stats::CompileError; +use crate::{cruby::*, profile::IseqProfile, virtualmem::CodePtr}; + +/// This is all the data ZJIT stores on an ISEQ. We mark objects in this struct on GC. +#[derive(Debug)] +pub struct IseqPayload { + /// Compilation status of the ISEQ. It has the JIT code address of the first block if Compiled. + pub status: IseqStatus, + + /// Type information of YARV instruction operands + pub profile: IseqProfile, + + /// GC offsets of the JIT code. These are the addresses of objects that need to be marked. + pub gc_offsets: Vec, + + /// JIT-to-JIT calls in the ISEQ. The IseqPayload's ISEQ is the caller of it. + pub iseq_calls: Vec, +} + +impl IseqPayload { + fn new(iseq_size: u32) -> Self { + Self { + status: IseqStatus::NotCompiled, + profile: IseqProfile::new(iseq_size), + gc_offsets: vec![], + iseq_calls: vec![], + } + } +} + +/// Set of CodePtrs for an ISEQ +#[derive(Clone, Debug, PartialEq)] +pub struct IseqCodePtrs { + /// Entry for the interpreter + pub start_ptr: CodePtr, + /// Entries for JIT-to-JIT calls + pub jit_entry_ptrs: Vec, +} + +#[derive(Debug, PartialEq)] +pub enum IseqStatus { + Compiled(IseqCodePtrs), + CantCompile(CompileError), + NotCompiled, +} + +/// Get a pointer to the payload object associated with an ISEQ. Create one if none exists. +pub fn get_or_create_iseq_payload_ptr(iseq: IseqPtr) -> *mut IseqPayload { + type VoidPtr = *mut c_void; + + unsafe { + let payload = rb_iseq_get_zjit_payload(iseq); + if payload.is_null() { + // Allocate a new payload with Box and transfer ownership to the GC. + // We drop the payload with Box::from_raw when the GC frees the ISEQ and calls us. + // NOTE(alan): Sometimes we read from an ISEQ without ever writing to it. + // We allocate in those cases anyways. + let iseq_size = get_iseq_encoded_size(iseq); + let new_payload = IseqPayload::new(iseq_size); + let new_payload = Box::into_raw(Box::new(new_payload)); + rb_iseq_set_zjit_payload(iseq, new_payload as VoidPtr); + + new_payload + } else { + payload as *mut IseqPayload + } + } +} + +/// Get the payload object associated with an ISEQ. Create one if none exists. +pub fn get_or_create_iseq_payload(iseq: IseqPtr) -> &'static mut IseqPayload { + let payload_non_null = get_or_create_iseq_payload_ptr(iseq); + payload_ptr_as_mut(payload_non_null) +} + +/// Convert an IseqPayload pointer to a mutable reference. Only one reference +/// should be kept at a time. +pub fn payload_ptr_as_mut(payload_ptr: *mut IseqPayload) -> &'static mut IseqPayload { + // SAFETY: we should have the VM lock and all other Ruby threads should be asleep. So we have + // exclusive mutable access. + // Hmm, nothing seems to stop calling this on the same + // iseq twice, though, which violates aliasing rules. + unsafe { payload_ptr.as_mut() }.unwrap() +} diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index 3366fe8e58db17..47bae3ac633a86 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -3,7 +3,7 @@ // We use the YARV bytecode constants which have a CRuby-style name #![allow(non_upper_case_globals)] -use crate::{cruby::*, gc::get_or_create_iseq_payload, options::{get_option, NumProfiles}}; +use crate::{cruby::*, payload::get_or_create_iseq_payload, options::{get_option, NumProfiles}}; use crate::distribution::{Distribution, DistributionSummary}; use crate::stats::Counter::profile_time_ns; use crate::stats::with_time_stat;