From ead3739c34b50b081140f1206cd36f13fc7db8ee Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 27 Jun 2025 08:51:16 -0400 Subject: [PATCH 001/130] Inline ASAN poison functions when ASAN is not enabled The ASAN poison functions was always defined in gc.c, even if ASAN was not enabled. This made function calls to happen all the time even if ASAN is not enabled. This commit defines these functions as empty macros when ASAN is not enabled. --- gc.c | 2 ++ internal/sanitizers.h | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/gc.c b/gc.c index 047fcdb3c06772..63e86d5ca201ef 100644 --- a/gc.c +++ b/gc.c @@ -4940,6 +4940,7 @@ rb_raw_obj_info_buitin_type(char *const buff, const size_t buff_size, const VALU #undef C +#ifdef RUBY_ASAN_ENABLED void rb_asan_poison_object(VALUE obj) { @@ -4960,6 +4961,7 @@ rb_asan_poisoned_object_p(VALUE obj) MAYBE_UNUSED(struct RVALUE *) ptr = (void *)obj; return __asan_region_is_poisoned(ptr, rb_gc_obj_slot_size(obj)); } +#endif static void raw_obj_info(char *const buff, const size_t buff_size, VALUE obj) diff --git a/internal/sanitizers.h b/internal/sanitizers.h index 279cbbe06941e7..8e6e87ddc8d749 100644 --- a/internal/sanitizers.h +++ b/internal/sanitizers.h @@ -127,6 +127,7 @@ asan_poison_memory_region(const volatile void *ptr, size_t size) #define asan_poison_object_if(ptr, obj) ((void)(ptr), (void)(obj)) #endif +#ifdef RUBY_ASAN_ENABLED RUBY_SYMBOL_EXPORT_BEGIN /** * This is a variant of asan_poison_memory_region that takes a VALUE. @@ -153,6 +154,11 @@ void *rb_asan_poisoned_object_p(VALUE obj); void rb_asan_unpoison_object(VALUE obj, bool newobj_p); RUBY_SYMBOL_EXPORT_END +#else +# define rb_asan_poison_object(obj) ((void)obj) +# define rb_asan_poisoned_object_p(obj) ((void)obj, NULL) +# define rb_asan_unpoison_object(obj, newobj_p) ((void)obj, (void)newobj_p) +#endif /** * This function asserts that a (formally poisoned) memory region from ptr to From 400793426ad19eb92306dee4092b1e25fbeb8a14 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 30 Jun 2025 21:17:33 +0900 Subject: [PATCH 002/130] [ruby/json] Remove trailing spaces [ci skip] https://github.com/ruby/json/commit/68ee9cf188 --- ext/json/parser/parser.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/parser/parser.c b/ext/json/parser/parser.c index d7796948274281..9bf247039e3389 100644 --- a/ext/json/parser/parser.c +++ b/ext/json/parser/parser.c @@ -908,7 +908,7 @@ static inline bool FORCE_INLINE string_scan(JSON_ParserState *state) { #ifdef HAVE_SIMD #if defined(HAVE_SIMD_NEON) - + uint64_t mask = 0; if (string_scan_simd_neon(&state->cursor, state->end, &mask)) { state->cursor += trailing_zeros64(mask) >> 2; From 81a2fdff1b311efb75dac463764f3658aede0010 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 30 Jun 2025 08:48:07 -0700 Subject: [PATCH 003/130] Force blank issues on fork repos We don't use issues on ruby/ruby, but we do in some fork repositories. When filing issues, GitHub checks if you want to report a security issue when .github/SECURITY.md exists, but we don't do that on GitHub. So this commit attempts to always create a blank issue. --- .github/ISSUE_TEMPLATE/config.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000000000..64eb98dc30a6f8 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,2 @@ +blank_issues_enabled: true +contact_links: [] From 44e4b02754681677976fec488112df5c984da013 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 30 Jun 2025 09:27:31 -0700 Subject: [PATCH 004/130] ZJIT: setglobal should not return output (#13744) * ZJIT: setglobal should not return output * Let the caller wrap Some --- .github/workflows/zjit-macos.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 2 +- zjit/src/codegen.rs | 11 ++++------- zjit/src/hir.rs | 2 +- 4 files changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index eadb23ce864d97..1d90252173b19b 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -108,6 +108,7 @@ jobs: RUST_BACKTRACE=1 ruby --disable=gems ../src/bootstraptest/runner.rb --ruby="./miniruby -I../src/lib -I. -I.ext/common --zjit-call-threshold=1" \ ../src/bootstraptest/test_attr.rb \ ../src/bootstraptest/test_autoload.rb \ + ../src/bootstraptest/test_class.rb \ ../src/bootstraptest/test_constant_cache.rb \ ../src/bootstraptest/test_env.rb \ ../src/bootstraptest/test_fiber.rb \ @@ -128,7 +129,6 @@ jobs: ../src/bootstraptest/test_yjit_30k_methods.rb \ ../src/bootstraptest/test_yjit_rust_port.rb # ../src/bootstraptest/test_block.rb \ - # ../src/bootstraptest/test_class.rb \ # ../src/bootstraptest/test_eval.rb \ # ../src/bootstraptest/test_exception.rb \ # ../src/bootstraptest/test_gc.rb \ diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index afcb8230ac2326..05583b45453a02 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -130,6 +130,7 @@ jobs: RUST_BACKTRACE=1 ruby --disable=gems ../src/bootstraptest/runner.rb --ruby="./miniruby -I../src/lib -I. -I.ext/common --zjit-call-threshold=1" \ ../src/bootstraptest/test_attr.rb \ ../src/bootstraptest/test_autoload.rb \ + ../src/bootstraptest/test_class.rb \ ../src/bootstraptest/test_constant_cache.rb \ ../src/bootstraptest/test_env.rb \ ../src/bootstraptest/test_fiber.rb \ @@ -151,7 +152,6 @@ jobs: ../src/bootstraptest/test_yjit_30k_methods.rb \ ../src/bootstraptest/test_yjit_rust_port.rb # ../src/bootstraptest/test_block.rb \ - # ../src/bootstraptest/test_class.rb \ # ../src/bootstraptest/test_eval.rb \ # ../src/bootstraptest/test_exception.rb \ # ../src/bootstraptest/test_gc.rb \ diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index f805b8b8d748a2..4c1d9698acd951 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -278,7 +278,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::PatchPoint(_) => return Some(()), // For now, rb_zjit_bop_redefined() panics. TODO: leave a patch point and fix rb_zjit_bop_redefined() Insn::CCall { cfun, args, name: _, return_type: _, elidable: _ } => gen_ccall(jit, asm, *cfun, args)?, Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id), - Insn::SetGlobal { id, val, state: _ } => gen_setglobal(asm, *id, opnd!(val)), + Insn::SetGlobal { id, val, state: _ } => return Some(gen_setglobal(asm, *id, opnd!(val))), Insn::GetGlobal { id, state: _ } => gen_getglobal(asm, *id), &Insn::GetLocal { ep_offset, level } => gen_nested_getlocal(asm, ep_offset, level)?, Insn::SetLocal { val, ep_offset, level } => return gen_nested_setlocal(asm, opnd!(val), *ep_offset, *level), @@ -294,7 +294,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio } }; - assert!(insn.has_output(), "Cannot write LIR output of HIR instruction with no output"); + assert!(insn.has_output(), "Cannot write LIR output of HIR instruction with no output: {insn}"); // If the instruction has an output, remember it in jit.opnds jit.opnds[insn_id.0] = Some(out_opnd); @@ -451,12 +451,9 @@ fn gen_getglobal(asm: &mut Assembler, id: ID) -> Opnd { } /// Set global variables -fn gen_setglobal(asm: &mut Assembler, id: ID, val: Opnd) -> Opnd { +fn gen_setglobal(asm: &mut Assembler, id: ID, val: Opnd) { asm_comment!(asm, "call rb_gvar_set"); - asm.ccall( - rb_gvar_set as *const u8, - vec![Opnd::UImm(id.0), val], - ) + asm.ccall(rb_gvar_set as *const u8, vec![Opnd::UImm(id.0), val]); } /// Side-exit into the interpreter diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index ad32d06f3e955c..3cc38e9c8326a3 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1120,7 +1120,7 @@ impl Function { | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } => - panic!("Cannot infer type of instruction with no output"), + panic!("Cannot infer type of instruction with no output: {}", self.insns[insn.0]), Insn::Const { val: Const::Value(val) } => Type::from_value(*val), Insn::Const { val: Const::CBool(val) } => Type::from_cbool(*val), Insn::Const { val: Const::CInt8(val) } => Type::from_cint(types::CInt8, *val as i64), From dc1b55a387108463b526dde4f9452ebbe8737363 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Mon, 30 Jun 2025 17:58:32 +0100 Subject: [PATCH 005/130] ZJIT: Add new ZJIT types for Set (#13743) --- zjit.c | 2 ++ zjit/bindgen/src/main.rs | 1 + zjit/src/cruby_bindings.inc.rs | 1 + zjit/src/hir.rs | 20 +++++++++++++++++++ zjit/src/hir_type/gen_hir_type.rb | 1 + zjit/src/hir_type/hir_type.inc.rs | 33 ++++++++++++++++++++----------- zjit/src/hir_type/mod.rs | 21 +++++++++++++++++++- 7 files changed, 66 insertions(+), 13 deletions(-) diff --git a/zjit.c b/zjit.c index 9218395582416c..560e115f3c4851 100644 --- a/zjit.c +++ b/zjit.c @@ -32,6 +32,8 @@ #include +RUBY_EXTERN VALUE rb_cSet; // defined in set.c and it's not exposed yet + uint32_t rb_zjit_get_page_size(void) { diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index cf328fc68cdfc8..eb66da561716b1 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -186,6 +186,7 @@ fn main() { .allowlist_var("rb_cThread") .allowlist_var("rb_cArray") .allowlist_var("rb_cHash") + .allowlist_var("rb_cSet") .allowlist_var("rb_cClass") .allowlist_var("rb_cISeq") diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 1367c9381b7c1e..dd0eb82bda1bf7 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -900,6 +900,7 @@ unsafe extern "C" { lines: *mut ::std::os::raw::c_int, ) -> ::std::os::raw::c_int; pub fn rb_jit_cont_each_iseq(callback: rb_iseq_callback, data: *mut ::std::os::raw::c_void); + pub static mut rb_cSet: VALUE; pub fn rb_zjit_get_page_size() -> u32; pub fn rb_zjit_reserve_addr_space(mem_size: u32) -> *mut u8; pub fn rb_zjit_profile_disable(iseq: *const rb_iseq_t); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 3cc38e9c8326a3..b5ad8b48b187d9 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -6235,4 +6235,24 @@ mod opt_tests { Return v11 "#]]); } + + #[test] + fn test_set_type_from_constant() { + eval(" + MY_SET = Set.new + + def test = MY_SET + + test + test + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, MY_SET) + v7:SetExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + Return v7 + "#]]); + } } diff --git a/zjit/src/hir_type/gen_hir_type.rb b/zjit/src/hir_type/gen_hir_type.rb index 92351aafa2b74f..1166d8ebb8e9a5 100644 --- a/zjit/src/hir_type/gen_hir_type.rb +++ b/zjit/src/hir_type/gen_hir_type.rb @@ -72,6 +72,7 @@ def base_type name base_type "Array" base_type "Hash" base_type "Range" +base_type "Set" (integer, integer_exact) = base_type "Integer" # CRuby partitions Integer into immediate and non-immediate variants. diff --git a/zjit/src/hir_type/hir_type.inc.rs b/zjit/src/hir_type/hir_type.inc.rs index 7d6f92a1808b18..7557610463a6af 100644 --- a/zjit/src/hir_type/hir_type.inc.rs +++ b/zjit/src/hir_type/hir_type.inc.rs @@ -9,7 +9,7 @@ mod bits { pub const BasicObjectSubclass: u64 = 1u64 << 3; pub const Bignum: u64 = 1u64 << 4; pub const BoolExact: u64 = FalseClassExact | TrueClassExact; - pub const BuiltinExact: u64 = ArrayExact | BasicObjectExact | FalseClassExact | FloatExact | HashExact | IntegerExact | NilClassExact | ObjectExact | RangeExact | StringExact | SymbolExact | TrueClassExact; + pub const BuiltinExact: u64 = ArrayExact | BasicObjectExact | FalseClassExact | FloatExact | HashExact | IntegerExact | NilClassExact | ObjectExact | RangeExact | SetExact | StringExact | SymbolExact | TrueClassExact; pub const CBool: u64 = 1u64 << 5; pub const CDouble: u64 = 1u64 << 6; pub const CInt: u64 = CSigned | CUnsigned; @@ -48,26 +48,29 @@ mod bits { pub const NilClass: u64 = NilClassExact | NilClassSubclass; pub const NilClassExact: u64 = 1u64 << 28; pub const NilClassSubclass: u64 = 1u64 << 29; - pub const Object: u64 = Array | FalseClass | Float | Hash | Integer | NilClass | ObjectExact | ObjectSubclass | Range | String | Symbol | TrueClass; + pub const Object: u64 = Array | FalseClass | Float | Hash | Integer | NilClass | ObjectExact | ObjectSubclass | Range | Set | String | Symbol | TrueClass; pub const ObjectExact: u64 = 1u64 << 30; pub const ObjectSubclass: u64 = 1u64 << 31; pub const Range: u64 = RangeExact | RangeSubclass; pub const RangeExact: u64 = 1u64 << 32; pub const RangeSubclass: u64 = 1u64 << 33; pub const RubyValue: u64 = BasicObject | CallableMethodEntry | Undef; - pub const StaticSymbol: u64 = 1u64 << 34; + pub const Set: u64 = SetExact | SetSubclass; + pub const SetExact: u64 = 1u64 << 34; + pub const SetSubclass: u64 = 1u64 << 35; + pub const StaticSymbol: u64 = 1u64 << 36; pub const String: u64 = StringExact | StringSubclass; - pub const StringExact: u64 = 1u64 << 35; - pub const StringSubclass: u64 = 1u64 << 36; - pub const Subclass: u64 = ArraySubclass | BasicObjectSubclass | FalseClassSubclass | FloatSubclass | HashSubclass | IntegerSubclass | NilClassSubclass | ObjectSubclass | RangeSubclass | StringSubclass | SymbolSubclass | TrueClassSubclass; + pub const StringExact: u64 = 1u64 << 37; + pub const StringSubclass: u64 = 1u64 << 38; + pub const Subclass: u64 = ArraySubclass | BasicObjectSubclass | FalseClassSubclass | FloatSubclass | HashSubclass | IntegerSubclass | NilClassSubclass | ObjectSubclass | RangeSubclass | SetSubclass | StringSubclass | SymbolSubclass | TrueClassSubclass; pub const Symbol: u64 = SymbolExact | SymbolSubclass; pub const SymbolExact: u64 = DynamicSymbol | StaticSymbol; - pub const SymbolSubclass: u64 = 1u64 << 37; + pub const SymbolSubclass: u64 = 1u64 << 39; pub const TrueClass: u64 = TrueClassExact | TrueClassSubclass; - pub const TrueClassExact: u64 = 1u64 << 38; - pub const TrueClassSubclass: u64 = 1u64 << 39; - pub const Undef: u64 = 1u64 << 40; - pub const AllBitPatterns: [(&'static str, u64); 67] = [ + pub const TrueClassExact: u64 = 1u64 << 40; + pub const TrueClassSubclass: u64 = 1u64 << 41; + pub const Undef: u64 = 1u64 << 42; + pub const AllBitPatterns: [(&'static str, u64); 70] = [ ("Any", Any), ("RubyValue", RubyValue), ("Immediate", Immediate), @@ -87,6 +90,9 @@ mod bits { ("StringExact", StringExact), ("SymbolExact", SymbolExact), ("StaticSymbol", StaticSymbol), + ("Set", Set), + ("SetSubclass", SetSubclass), + ("SetExact", SetExact), ("Range", Range), ("RangeSubclass", RangeSubclass), ("RangeExact", RangeExact), @@ -136,7 +142,7 @@ mod bits { ("ArrayExact", ArrayExact), ("Empty", Empty), ]; - pub const NumTypeBits: u64 = 41; + pub const NumTypeBits: u64 = 43; } pub mod types { use super::*; @@ -195,6 +201,9 @@ pub mod types { pub const RangeExact: Type = Type::from_bits(bits::RangeExact); pub const RangeSubclass: Type = Type::from_bits(bits::RangeSubclass); pub const RubyValue: Type = Type::from_bits(bits::RubyValue); + pub const Set: Type = Type::from_bits(bits::Set); + pub const SetExact: Type = Type::from_bits(bits::SetExact); + pub const SetSubclass: Type = Type::from_bits(bits::SetSubclass); pub const StaticSymbol: Type = Type::from_bits(bits::StaticSymbol); pub const String: Type = Type::from_bits(bits::String); pub const StringExact: Type = Type::from_bits(bits::StringExact); diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index 784c2f324ebbaf..0ad26bdc336def 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -1,6 +1,6 @@ #![allow(non_upper_case_globals)] use crate::cruby::{Qfalse, Qnil, Qtrue, VALUE, RUBY_T_ARRAY, RUBY_T_STRING, RUBY_T_HASH}; -use crate::cruby::{rb_cInteger, rb_cFloat, rb_cArray, rb_cHash, rb_cString, rb_cSymbol, rb_cObject, rb_cTrueClass, rb_cFalseClass, rb_cNilClass, rb_cRange}; +use crate::cruby::{rb_cInteger, rb_cFloat, rb_cArray, rb_cHash, rb_cString, rb_cSymbol, rb_cObject, rb_cTrueClass, rb_cFalseClass, rb_cNilClass, rb_cRange, rb_cSet}; use crate::cruby::ClassRelationship; use crate::cruby::get_class_name; use crate::cruby::rb_mRubyVMFrozenCore; @@ -195,6 +195,9 @@ impl Type { else if is_string_exact(val) { Type { bits: bits::StringExact, spec: Specialization::Object(val) } } + else if val.class_of() == unsafe { rb_cSet } { + Type { bits: bits::SetExact, spec: Specialization::Object(val) } + } else if val.class_of() == unsafe { rb_cObject } { Type { bits: bits::ObjectExact, spec: Specialization::Object(val) } } @@ -394,6 +397,7 @@ impl Type { if self.is_subtype(types::NilClassExact) { return Some(unsafe { rb_cNilClass }); } if self.is_subtype(types::ObjectExact) { return Some(unsafe { rb_cObject }); } if self.is_subtype(types::RangeExact) { return Some(unsafe { rb_cRange }); } + if self.is_subtype(types::SetExact) { return Some(unsafe { rb_cSet }); } if self.is_subtype(types::StringExact) { return Some(unsafe { rb_cString }); } if self.is_subtype(types::SymbolExact) { return Some(unsafe { rb_cSymbol }); } if self.is_subtype(types::TrueClassExact) { return Some(unsafe { rb_cTrueClass }); } @@ -585,6 +589,21 @@ mod tests { assert_eq!(types::Integer.inexact_ruby_class(), None); } + #[test] + fn set() { + assert_subtype(types::SetExact, types::Set); + assert_subtype(types::SetSubclass, types::Set); + } + + #[test] + fn set_has_ruby_class() { + crate::cruby::with_rubyvm(|| { + assert_eq!(types::SetExact.runtime_exact_ruby_class(), Some(unsafe { rb_cSet })); + assert_eq!(types::Set.runtime_exact_ruby_class(), None); + assert_eq!(types::SetSubclass.runtime_exact_ruby_class(), None); + }); + } + #[test] fn display_exact_bits_match() { assert_eq!(format!("{}", Type::fixnum(4)), "Fixnum[4]"); From 7743aa37995a4557045ebd38c94c5357e58637f5 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 30 Jun 2025 10:36:44 -0700 Subject: [PATCH 006/130] Revert "Force blank issues on fork repos" This reverts commit 81a2fdff1b311efb75dac463764f3658aede0010. Never mind, it didn't work. It seems like you'll see one as long as you're admin on the (fork) repository. Removing this unnecessary file. --- .github/ISSUE_TEMPLATE/config.yml | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index 64eb98dc30a6f8..00000000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,2 +0,0 @@ -blank_issues_enabled: true -contact_links: [] From 456f6f3f83ad422fa58f350bd2db45d0d0f4a59d Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sun, 29 Jun 2025 15:41:15 -0500 Subject: [PATCH 007/130] [DOC] Tweaks for Strings#byteslice --- doc/string/byteslice.rdoc | 54 +++++++++++++++++++++++++++++++++++++++ string.c | 41 +++-------------------------- 2 files changed, 57 insertions(+), 38 deletions(-) create mode 100644 doc/string/byteslice.rdoc diff --git a/doc/string/byteslice.rdoc b/doc/string/byteslice.rdoc new file mode 100644 index 00000000000000..d70441fb2b6d6d --- /dev/null +++ b/doc/string/byteslice.rdoc @@ -0,0 +1,54 @@ +Returns a substring of +self+, or +nil+ if the substring cannot be constructed. + +With integer arguments +offset+ and +length+ given, +returns the substring beginning at the given +offset+ +and of the given +length+ (as available): + + s = '0123456789' # => "0123456789" + s.byteslice(2) # => "2" + s.byteslice(200) # => nil + s.byteslice(4, 3) # => "456" + s.byteslice(4, 30) # => "456789" + +Returns +nil+ if +length+ is negative or +offset+ falls outside of +self+: + + s.byteslice(4, -1) # => nil + s.byteslice(40, 2) # => nil + +Counts backwards from the end of +self+ +if +offset+ is negative: + + s = '0123456789' # => "0123456789" + s.byteslice(-4) # => "6" + s.byteslice(-4, 3) # => "678" + +With Range argument +range+ given, returns +byteslice(range.begin, range.size): + + s = '0123456789' # => "0123456789" + s.byteslice(4..6) # => "456" + s.byteslice(-6..-4) # => "456" + s.byteslice(5..2) # => "" # range.size is zero. + s.byteslice(40..42) # => nil + +The starting and ending offsets need not be on character boundaries: + + s = 'こんにちは' + s.byteslice(0, 3) # => "こ" + s.byteslice(1, 3) # => "\x81\x93\xE3" + +The encodings of +self+ and the returned substring +are always the same: + + s.encoding # => # + s.byteslice(0, 3).encoding # => # + s.byteslice(1, 3).encoding # => # + +But, depending on the character boundaries, +the encoding of the returned substring may not be valid: + + s.valid_encoding? # => true + s.byteslice(0, 3).valid_encoding? # => true + s.byteslice(1, 3).valid_encoding? # => false + +Related: see {Converting to New String}[rdoc-ref:String@Converting+to+New+String]. diff --git a/string.c b/string.c index 0425388f375afe..183d1336d7020f 100644 --- a/string.c +++ b/string.c @@ -6870,45 +6870,10 @@ str_byte_aref(VALUE str, VALUE indx) /* * call-seq: - * byteslice(index, length = 1) -> string or nil - * byteslice(range) -> string or nil - * - * Returns a substring of +self+, or +nil+ if the substring cannot be constructed. - * - * With integer arguments +index+ and +length+ given, - * returns the substring beginning at the given +index+ - * of the given +length+ (if possible), - * or +nil+ if +length+ is negative or +index+ falls outside of +self+: - * - * s = '0123456789' # => "0123456789" - * s.byteslice(2) # => "2" - * s.byteslice(200) # => nil - * s.byteslice(4, 3) # => "456" - * s.byteslice(4, 30) # => "456789" - * s.byteslice(4, -1) # => nil - * s.byteslice(40, 2) # => nil - * - * In either case above, counts backwards from the end of +self+ - * if +index+ is negative: - * - * s = '0123456789' # => "0123456789" - * s.byteslice(-4) # => "6" - * s.byteslice(-4, 3) # => "678" - * - * With Range argument +range+ given, returns - * byteslice(range.begin, range.size): - * - * s = '0123456789' # => "0123456789" - * s.byteslice(4..6) # => "456" - * s.byteslice(-6..-4) # => "456" - * s.byteslice(5..2) # => "" # range.size is zero. - * s.byteslice(40..42) # => nil - * - * In all cases, a returned string has the same encoding as +self+: - * - * s.encoding # => # - * s.byteslice(4).encoding # => # + * byteslice(offset, length = 1) -> string or nil + * byteslice(range) -> string or nil * + * :include: doc/string/byteslice.rdoc */ static VALUE From 35feaee91767ca2d9224887a356c3a82d07dac26 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 30 Jun 2025 13:13:09 -0500 Subject: [PATCH 008/130] [DOC] Tweaks for String#bytesplice --- doc/string/bytesplice.rdoc | 66 ++++++++++++++++++++++++++++++++++++++ string.c | 23 ++++--------- string.rb | 1 + 3 files changed, 73 insertions(+), 17 deletions(-) create mode 100644 doc/string/bytesplice.rdoc diff --git a/doc/string/bytesplice.rdoc b/doc/string/bytesplice.rdoc new file mode 100644 index 00000000000000..5689ef4a2ba79b --- /dev/null +++ b/doc/string/bytesplice.rdoc @@ -0,0 +1,66 @@ +Replaces target bytes in +self+ with source bytes from the given string +str+; +returns +self+. + +In the first form, arguments +offset+ and +length+ determine the target bytes, +and the source bytes are all of the given +str+: + + '0123456789'.bytesplice(0, 3, 'abc') # => "abc3456789" + '0123456789'.bytesplice(3, 3, 'abc') # => "012abc6789" + '0123456789'.bytesplice(0, 50, 'abc') # => "abc" + '0123456789'.bytesplice(50, 3, 'abc') # Raises IndexError. + +The counts of the target bytes and source source bytes may be different: + + '0123456789'.bytesplice(0, 6, 'abc') # => "abc6789" # Shorter source. + '0123456789'.bytesplice(0, 1, 'abc') # => "abc123456789" # Shorter target. + +And either count may be zero (i.e., specifying an empty string): + + '0123456789'.bytesplice(0, 3, '') # => "3456789" # Empty source. + '0123456789'.bytesplice(0, 0, 'abc') # => "abc0123456789" # Empty target. + +In the second form, just as in the first, +arugments +offset+ and +length+ determine the target bytes; +argument +str+ _contains_ the source bytes, +and the additional arguments +str_offset+ and +str_length+ +determine the actual source bytes: + + '0123456789'.bytesplice(0, 3, 'abc', 0, 3) # => "abc3456789" + '0123456789'.bytesplice(0, 3, 'abc', 1, 1) # => "b3456789" # Shorter source. + '0123456789'.bytesplice(0, 1, 'abc', 0, 3) # => "abc123456789" # Shorter target. + '0123456789'.bytesplice(0, 3, 'abc', 1, 0) # => "3456789" # Empty source. + '0123456789'.bytesplice(0, 0, 'abc', 0, 3) # => "abc0123456789" # Empty target. + +In the third form, argument +range+ determines the target bytes +and the source bytes are all of the given +str+: + + '0123456789'.bytesplice(0..2, 'abc') # => "abc3456789" + '0123456789'.bytesplice(3..5, 'abc') # => "012abc6789" + '0123456789'.bytesplice(0..5, 'abc') # => "abc6789" # Shorter source. + '0123456789'.bytesplice(0..0, 'abc') # => "abc123456789" # Shorter target. + '0123456789'.bytesplice(0..2, '') # => "3456789" # Empty source. + '0123456789'.bytesplice(0...0, 'abc') # => "abc0123456789" # Empty target. + +In the fourth form, just as in the third, +arugment +range+ determines the target bytes; +argument +str+ _contains_ the source bytes, +and the additional argument +str_range+ +determines the actual source bytes: + + '0123456789'.bytesplice(0..2, 'abc', 0..2) # => "abc3456789" + '0123456789'.bytesplice(3..5, 'abc', 0..2) # => "012abc6789" + '0123456789'.bytesplice(0..2, 'abc', 0..1) # => "ab3456789" # Shorter source. + '0123456789'.bytesplice(0..1, 'abc', 0..2) # => "abc23456789" # Shorter target. + '0123456789'.bytesplice(0..2, 'abc', 0...0) # => "3456789" # Empty source. + '0123456789'.bytesplice(0...0, 'abc', 0..2) # => "abc0123456789" # Empty target. + +In any of the forms, the beginnings and endings of both source and target +must be on character boundaries. + +In these examples, +self+ has five 3-byte characters, +and so has character boundaries at offsets 0, 3, 6, 9, 12, and 15. + + 'こんにちは'.bytesplice(0, 3, 'abc') # => "abcんにちは" + 'こんにちは'.bytesplice(1, 3, 'abc') # Raises IndexError. + 'こんにちは'.bytesplice(0, 2, 'abc') # Raises IndexError. + diff --git a/string.c b/string.c index 183d1336d7020f..6069a8751b1fe1 100644 --- a/string.c +++ b/string.c @@ -6913,23 +6913,12 @@ str_check_beg_len(VALUE str, long *beg, long *len) /* * call-seq: - * bytesplice(index, length, str) -> string - * bytesplice(index, length, str, str_index, str_length) -> string - * bytesplice(range, str) -> string - * bytesplice(range, str, str_range) -> string - * - * Replaces some or all of the content of +self+ with +str+, and returns +self+. - * The portion of the string affected is determined using - * the same criteria as String#byteslice, except that +length+ cannot be omitted. - * If the replacement string is not the same length as the text it is replacing, - * the string will be adjusted accordingly. - * - * If +str_index+ and +str_length+, or +str_range+ are given, the content of +self+ is replaced by str.byteslice(str_index, str_length) or str.byteslice(str_range); however the substring of +str+ is not allocated as a new string. - * - * The form that take an Integer will raise an IndexError if the value is out - * of range; the Range form will raise a RangeError. - * If the beginning or ending offset does not land on character (codepoint) - * boundary, an IndexError will be raised. + * bytesplice(offset, length, str) -> self + * bytesplice(offset, length, str, str_offset, str_length) -> self + * bytesplice(range, str) -> self + * bytesplice(range, str, str_range) -> self + * + * :include: doc/string/bytesplice.rdoc */ static VALUE diff --git a/string.rb b/string.rb index a5ff79a62c04dc..a14c81ba2d82b1 100644 --- a/string.rb +++ b/string.rb @@ -391,6 +391,7 @@ # # _Substitution_ # +# - #bytesplice: Replaces bytes of +self+ with bytes from a given string; returns +self+. # - #sub!: Replaces the first substring that matches a given pattern with a given replacement string; # returns +self+ if any changes, +nil+ otherwise. # - #gsub!: Replaces each substring that matches a given pattern with a given replacement string; From 99360e500ddec455612a7b3e776352971268ac77 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 30 Jun 2025 11:15:00 -0700 Subject: [PATCH 009/130] ZJIT: Enable a couple more btests (#13748) --- .github/workflows/zjit-macos.yml | 4 ++-- .github/workflows/zjit-ubuntu.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 1d90252173b19b..e8fd7120e8b382 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -111,6 +111,7 @@ jobs: ../src/bootstraptest/test_class.rb \ ../src/bootstraptest/test_constant_cache.rb \ ../src/bootstraptest/test_env.rb \ + ../src/bootstraptest/test_exception.rb \ ../src/bootstraptest/test_fiber.rb \ ../src/bootstraptest/test_finalizer.rb \ ../src/bootstraptest/test_flip.rb \ @@ -120,6 +121,7 @@ jobs: ../src/bootstraptest/test_jump.rb \ ../src/bootstraptest/test_literal.rb \ ../src/bootstraptest/test_literal_suffix.rb \ + ../src/bootstraptest/test_load.rb \ ../src/bootstraptest/test_marshal.rb \ ../src/bootstraptest/test_objectspace.rb \ ../src/bootstraptest/test_string.rb \ @@ -130,10 +132,8 @@ jobs: ../src/bootstraptest/test_yjit_rust_port.rb # ../src/bootstraptest/test_block.rb \ # ../src/bootstraptest/test_eval.rb \ - # ../src/bootstraptest/test_exception.rb \ # ../src/bootstraptest/test_gc.rb \ # ../src/bootstraptest/test_insns.rb \ - # ../src/bootstraptest/test_load.rb \ # ../src/bootstraptest/test_massign.rb \ # ../src/bootstraptest/test_method.rb \ # ../src/bootstraptest/test_proc.rb \ diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 05583b45453a02..05b334a111495e 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -133,6 +133,7 @@ jobs: ../src/bootstraptest/test_class.rb \ ../src/bootstraptest/test_constant_cache.rb \ ../src/bootstraptest/test_env.rb \ + ../src/bootstraptest/test_exception.rb \ ../src/bootstraptest/test_fiber.rb \ ../src/bootstraptest/test_finalizer.rb \ ../src/bootstraptest/test_flip.rb \ @@ -142,6 +143,7 @@ jobs: ../src/bootstraptest/test_jump.rb \ ../src/bootstraptest/test_literal.rb \ ../src/bootstraptest/test_literal_suffix.rb \ + ../src/bootstraptest/test_load.rb \ ../src/bootstraptest/test_marshal.rb \ ../src/bootstraptest/test_massign.rb \ ../src/bootstraptest/test_objectspace.rb \ @@ -153,10 +155,8 @@ jobs: ../src/bootstraptest/test_yjit_rust_port.rb # ../src/bootstraptest/test_block.rb \ # ../src/bootstraptest/test_eval.rb \ - # ../src/bootstraptest/test_exception.rb \ # ../src/bootstraptest/test_gc.rb \ # ../src/bootstraptest/test_insns.rb \ - # ../src/bootstraptest/test_load.rb \ # ../src/bootstraptest/test_method.rb \ # ../src/bootstraptest/test_proc.rb \ # ../src/bootstraptest/test_ractor.rb \ From 90247fb77d4b99a55ba321d89eccaa5de1eda3b7 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 30 Jun 2025 17:43:50 -0400 Subject: [PATCH 010/130] ZJIT: Don't compile functions with unhandled parameter types (#13749) --- test/ruby/test_zjit.rb | 1 + zjit/src/hir.rs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index d9130c3116c813..823fb8e043d254 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -118,6 +118,7 @@ def test3 = baz(4, 1) end def test_invokebuiltin + omit 'Test fails at the moment due to not handling optional parameters' assert_compiles '["."]', %q{ def test = Dir.glob(".") test diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index b5ad8b48b187d9..bba3b183a0df25 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -2186,9 +2186,15 @@ pub enum CallType { Forwarding, } +#[derive(Debug, PartialEq)] +pub enum ParameterType { + Optional, +} + #[derive(Debug, PartialEq)] pub enum ParseError { StackUnderflow(FrameState), + UnknownParameterType(ParameterType), MalformedIseq(u32), // insn_idx into iseq_encoded } @@ -2248,8 +2254,14 @@ impl ProfileOracle { /// The index of the self parameter in the HIR function pub const SELF_PARAM_IDX: usize = 0; +fn filter_unknown_parameter_type(iseq: *const rb_iseq_t) -> Result<(), ParseError> { + if unsafe { rb_get_iseq_body_param_opt_num(iseq) } != 0 { return Err(ParseError::UnknownParameterType(ParameterType::Optional)); } + Ok(()) +} + /// Compile ISEQ into High-level IR pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { + filter_unknown_parameter_type(iseq)?; let payload = get_or_create_iseq_payload(iseq); let mut profiles = ProfileOracle::new(payload); let mut fun = Function::new(iseq); @@ -3227,6 +3239,11 @@ mod tests { assert_eq!(result.unwrap_err(), reason); } + #[test] + fn test_cant_compile_optional() { + eval("def test(x=1) = 123"); + assert_compile_fails("test", ParseError::UnknownParameterType(ParameterType::Optional)); + } #[test] fn test_putobject() { From e54a242bdf35d4bd90dd8628af411a848946d4f1 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 30 Jun 2025 18:15:43 -0400 Subject: [PATCH 011/130] ZJIT: Mark GetLocal as having no effects (#13750) This removes the GetLocal of l3 from: def test l3 = 3 1.times do |l2| _ = l3 1 end end --- zjit/src/hir.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index bba3b183a0df25..9324c333e05295 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -570,6 +570,7 @@ impl Insn { Insn::FixnumLe { .. } => false, Insn::FixnumGt { .. } => false, Insn::FixnumGe { .. } => false, + Insn::GetLocal { .. } => false, Insn::CCall { elidable, .. } => !elidable, _ => true, } From 2287dd4af2959dc080f4aed0f2edd30b60d62b2d Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 30 Jun 2025 15:22:56 -0700 Subject: [PATCH 012/130] ZJIT: Enable bootstraptest/test_block.rb (#13751) --- .github/workflows/zjit-macos.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index e8fd7120e8b382..5454f05b090f98 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -108,6 +108,7 @@ jobs: RUST_BACKTRACE=1 ruby --disable=gems ../src/bootstraptest/runner.rb --ruby="./miniruby -I../src/lib -I. -I.ext/common --zjit-call-threshold=1" \ ../src/bootstraptest/test_attr.rb \ ../src/bootstraptest/test_autoload.rb \ + ../src/bootstraptest/test_block.rb \ ../src/bootstraptest/test_class.rb \ ../src/bootstraptest/test_constant_cache.rb \ ../src/bootstraptest/test_env.rb \ @@ -130,7 +131,6 @@ jobs: ../src/bootstraptest/test_yjit_30k_ifelse.rb \ ../src/bootstraptest/test_yjit_30k_methods.rb \ ../src/bootstraptest/test_yjit_rust_port.rb - # ../src/bootstraptest/test_block.rb \ # ../src/bootstraptest/test_eval.rb \ # ../src/bootstraptest/test_gc.rb \ # ../src/bootstraptest/test_insns.rb \ diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 05b334a111495e..aa9402546a3b65 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -130,6 +130,7 @@ jobs: RUST_BACKTRACE=1 ruby --disable=gems ../src/bootstraptest/runner.rb --ruby="./miniruby -I../src/lib -I. -I.ext/common --zjit-call-threshold=1" \ ../src/bootstraptest/test_attr.rb \ ../src/bootstraptest/test_autoload.rb \ + ../src/bootstraptest/test_block.rb \ ../src/bootstraptest/test_class.rb \ ../src/bootstraptest/test_constant_cache.rb \ ../src/bootstraptest/test_env.rb \ @@ -153,7 +154,6 @@ jobs: ../src/bootstraptest/test_yjit_30k_ifelse.rb \ ../src/bootstraptest/test_yjit_30k_methods.rb \ ../src/bootstraptest/test_yjit_rust_port.rb - # ../src/bootstraptest/test_block.rb \ # ../src/bootstraptest/test_eval.rb \ # ../src/bootstraptest/test_gc.rb \ # ../src/bootstraptest/test_insns.rb \ From 665da05141afb032d8de43975a97863b5d443f92 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 30 Jun 2025 12:23:56 -0400 Subject: [PATCH 013/130] ZJIT: Pretty-print symbols in HIR dump This lets us better see what is going on, for example in pattern matching code, which has a bunch of dynamic method lookups and `respond_to?` sends. --- zjit/bindgen/src/main.rs | 1 + zjit/src/cruby.rs | 5 +++++ zjit/src/cruby_bindings.inc.rs | 1 + zjit/src/hir.rs | 12 ++++++------ zjit/src/hir_type/mod.rs | 2 ++ 5 files changed, 15 insertions(+), 6 deletions(-) diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index eb66da561716b1..0df900b45eadaa 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -228,6 +228,7 @@ fn main() { .allowlist_function("rb_sym2id") .allowlist_function("rb_str_intern") .allowlist_function("rb_id2str") + .allowlist_function("rb_sym2str") // From internal/numeric.h .allowlist_function("rb_fix_aref") diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 3a1c45ffd3b537..c4bf9262d77deb 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -738,6 +738,11 @@ fn ruby_str_to_rust(v: VALUE) -> String { } } +pub fn ruby_sym_to_rust(v: VALUE) -> String { + let ruby_str = unsafe { rb_sym2str(v) }; + ruby_str_to_rust(ruby_str) +} + /// A location in Rust code for integrating with debugging facilities defined in C. /// Use the [src_loc!] macro to crate an instance. pub struct SourceLocation { diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index dd0eb82bda1bf7..cb5910b5369b81 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -790,6 +790,7 @@ unsafe extern "C" { pub fn rb_intern(name: *const ::std::os::raw::c_char) -> ID; pub fn rb_intern2(name: *const ::std::os::raw::c_char, len: ::std::os::raw::c_long) -> ID; pub fn rb_id2str(id: ID) -> VALUE; + pub fn rb_sym2str(symbol: VALUE) -> VALUE; pub fn rb_class2name(klass: VALUE) -> *const ::std::os::raw::c_char; pub fn rb_obj_is_kind_of(obj: VALUE, klass: VALUE) -> VALUE; pub fn rb_obj_frozen_p(obj: VALUE) -> VALUE; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 9324c333e05295..b15403406e4b17 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -3377,8 +3377,8 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_newhash, expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): - v4:StaticSymbol[VALUE(0x1000)] = Const Value(VALUE(0x1000)) - v5:StaticSymbol[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v4:StaticSymbol[:a] = Const Value(VALUE(0x1000)) + v5:StaticSymbol[:b] = Const Value(VALUE(0x1008)) v7:HashExact = NewHash v4: v1, v5: v2 Return v7 "#]]); @@ -3435,7 +3435,7 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_putobject, expect![[r#" fn test: bb0(v0:BasicObject): - v2:StaticSymbol[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v2:StaticSymbol[:foo] = Const Value(VALUE(0x1000)) Return v2 "#]]); } @@ -4029,7 +4029,7 @@ mod tests { v5:HashExact = NewHash v7:BasicObject = SendWithoutBlock v3, :core#hash_merge_kwd, v5, v1 v8:BasicObject[VMFrozenCore] = Const Value(VALUE(0x1000)) - v9:StaticSymbol[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v9:StaticSymbol[:b] = Const Value(VALUE(0x1008)) v10:Fixnum[1] = Const Value(1) v12:BasicObject = SendWithoutBlock v8, :core#hash_merge_ptr, v7, v9, v10 SideExit @@ -4479,8 +4479,8 @@ mod tests { bb0(v0:BasicObject): v2:BasicObject[VMFrozenCore] = Const Value(VALUE(0x1000)) v3:BasicObject = PutSpecialObject CBase - v4:StaticSymbol[VALUE(0x1008)] = Const Value(VALUE(0x1008)) - v5:StaticSymbol[VALUE(0x1010)] = Const Value(VALUE(0x1010)) + v4:StaticSymbol[:aliased] = Const Value(VALUE(0x1008)) + v5:StaticSymbol[:__callee__] = Const Value(VALUE(0x1010)) v7:BasicObject = SendWithoutBlock v2, :core#set_method_alias, v3, v4, v5 Return v7 "#]]); diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index 0ad26bdc336def..41a3706d19b9b0 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -3,6 +3,7 @@ use crate::cruby::{Qfalse, Qnil, Qtrue, VALUE, RUBY_T_ARRAY, RUBY_T_STRING, RUBY use crate::cruby::{rb_cInteger, rb_cFloat, rb_cArray, rb_cHash, rb_cString, rb_cSymbol, rb_cObject, rb_cTrueClass, rb_cFalseClass, rb_cNilClass, rb_cRange, rb_cSet}; use crate::cruby::ClassRelationship; use crate::cruby::get_class_name; +use crate::cruby::ruby_sym_to_rust; use crate::cruby::rb_mRubyVMFrozenCore; use crate::hir::PtrPrintMap; @@ -70,6 +71,7 @@ fn write_spec(f: &mut std::fmt::Formatter, printer: &TypePrinter) -> std::fmt::R match ty.spec { Specialization::Any | Specialization::Empty => { Ok(()) }, Specialization::Object(val) if val == unsafe { rb_mRubyVMFrozenCore } => write!(f, "[VMFrozenCore]"), + Specialization::Object(val) if ty.is_subtype(types::SymbolExact) => write!(f, "[:{}]", ruby_sym_to_rust(val)), Specialization::Object(val) => write!(f, "[{}]", val.print(printer.ptr_map)), Specialization::Type(val) => write!(f, "[class:{}]", get_class_name(val)), Specialization::TypeExact(val) => write!(f, "[class_exact:{}]", get_class_name(val)), From 8f758de4c8838509136fc9ba1887d7b6db49b2a5 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 30 Jun 2025 18:29:33 -0400 Subject: [PATCH 014/130] ZJIT: Rename Ruby<->Rust functions for clarity No need to be so terse. --- zjit/src/cruby.rs | 10 +++++----- zjit/src/hir_type/mod.rs | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index c4bf9262d77deb..e5b66be8508fdf 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -701,7 +701,7 @@ pub fn iseq_name(iseq: IseqPtr) -> String { if iseq_label == Qnil { "None".to_string() } else { - ruby_str_to_rust(iseq_label) + ruby_str_to_rust_string(iseq_label) } } @@ -717,7 +717,7 @@ pub fn iseq_get_location(iseq: IseqPtr, pos: u16) -> String { if iseq_path == Qnil { s.push_str("None"); } else { - s.push_str(&ruby_str_to_rust(iseq_path)); + s.push_str(&ruby_str_to_rust_string(iseq_path)); } s.push_str(":"); s.push_str(&iseq_lineno.to_string()); @@ -728,7 +728,7 @@ pub fn iseq_get_location(iseq: IseqPtr, pos: u16) -> String { // Convert a CRuby UTF-8-encoded RSTRING into a Rust string. // This should work fine on ASCII strings and anything else // that is considered legal UTF-8, including embedded nulls. -fn ruby_str_to_rust(v: VALUE) -> String { +fn ruby_str_to_rust_string(v: VALUE) -> String { let str_ptr = unsafe { rb_RSTRING_PTR(v) } as *mut u8; let str_len: usize = unsafe { rb_RSTRING_LEN(v) }.try_into().unwrap(); let str_slice: &[u8] = unsafe { std::slice::from_raw_parts(str_ptr, str_len) }; @@ -738,9 +738,9 @@ fn ruby_str_to_rust(v: VALUE) -> String { } } -pub fn ruby_sym_to_rust(v: VALUE) -> String { +pub fn ruby_sym_to_rust_string(v: VALUE) -> String { let ruby_str = unsafe { rb_sym2str(v) }; - ruby_str_to_rust(ruby_str) + ruby_str_to_rust_string(ruby_str) } /// A location in Rust code for integrating with debugging facilities defined in C. diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index 41a3706d19b9b0..19dbeffdaa414c 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -3,7 +3,7 @@ use crate::cruby::{Qfalse, Qnil, Qtrue, VALUE, RUBY_T_ARRAY, RUBY_T_STRING, RUBY use crate::cruby::{rb_cInteger, rb_cFloat, rb_cArray, rb_cHash, rb_cString, rb_cSymbol, rb_cObject, rb_cTrueClass, rb_cFalseClass, rb_cNilClass, rb_cRange, rb_cSet}; use crate::cruby::ClassRelationship; use crate::cruby::get_class_name; -use crate::cruby::ruby_sym_to_rust; +use crate::cruby::ruby_sym_to_rust_string; use crate::cruby::rb_mRubyVMFrozenCore; use crate::hir::PtrPrintMap; @@ -71,7 +71,7 @@ fn write_spec(f: &mut std::fmt::Formatter, printer: &TypePrinter) -> std::fmt::R match ty.spec { Specialization::Any | Specialization::Empty => { Ok(()) }, Specialization::Object(val) if val == unsafe { rb_mRubyVMFrozenCore } => write!(f, "[VMFrozenCore]"), - Specialization::Object(val) if ty.is_subtype(types::SymbolExact) => write!(f, "[:{}]", ruby_sym_to_rust(val)), + Specialization::Object(val) if ty.is_subtype(types::SymbolExact) => write!(f, "[:{}]", ruby_sym_to_rust_string(val)), Specialization::Object(val) => write!(f, "[{}]", val.print(printer.ptr_map)), Specialization::Type(val) => write!(f, "[class:{}]", get_class_name(val)), Specialization::TypeExact(val) => write!(f, "[class_exact:{}]", get_class_name(val)), From 4a7d1a7062e7802eb6758bab121eecde59b19125 Mon Sep 17 00:00:00 2001 From: ywenc Date: Mon, 30 Jun 2025 11:18:58 -0400 Subject: [PATCH 015/130] ZJIT: Add IsNil optimization and tests for optimized hir --- zjit/src/hir.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index b15403406e4b17..ae279f82663daa 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -571,6 +571,7 @@ impl Insn { Insn::FixnumGt { .. } => false, Insn::FixnumGe { .. } => false, Insn::GetLocal { .. } => false, + Insn::IsNil { .. } => false, Insn::CCall { elidable, .. } => !elidable, _ => true, } @@ -6194,6 +6195,42 @@ mod opt_tests { "#]]); } + #[test] + fn test_branchnil_nil() { + eval(" + def test + x = nil + x&.itself + end + "); + + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:NilClassExact = Const Value(nil) + Return v3 + "#]]); + } + + #[test] + fn test_branchnil_truthy() { + eval(" + def test + x = 1 + x&.itself + end + "); + + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v3:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1000, itself@0x1008) + v15:BasicObject = CCall itself@0x1010, v3 + Return v15 + "#]]); + } + #[test] fn test_eliminate_load_from_frozen_array_in_bounds() { eval(r##" From 03e08a946d498e75e1bb31ddb28fc012dc4694f5 Mon Sep 17 00:00:00 2001 From: ywenc Date: Mon, 30 Jun 2025 15:26:23 -0400 Subject: [PATCH 016/130] ZJIT: Add codegen for IsNil --- test/ruby/test_zjit.rb | 9 +++++++++ zjit/src/codegen.rs | 8 ++++++++ 2 files changed, 17 insertions(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 823fb8e043d254..e7ce1e1837acf4 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -831,6 +831,15 @@ def test = Foo }, call_threshold: 2 end + def test_branchnil + assert_compiles '[2, nil]', %q{ + def test(x) + x&.succ + end + [test(1), test(nil)] + }, call_threshold: 1, insns: [:branchnil] + end + # tool/ruby_vm/views/*.erb relies on the zjit instructions a) being contiguous and # b) being reliably ordered after all the other instructions. def test_instruction_order diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 4c1d9698acd951..d7d3cb8acab2ee 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -272,6 +272,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::FixnumLe { left, right } => gen_fixnum_le(asm, opnd!(left), opnd!(right))?, Insn::FixnumGt { left, right } => gen_fixnum_gt(asm, opnd!(left), opnd!(right))?, Insn::FixnumGe { left, right } => gen_fixnum_ge(asm, opnd!(left), opnd!(right))?, + Insn::IsNil { val } => gen_isnil(asm, opnd!(val))?, Insn::Test { val } => gen_test(asm, opnd!(val))?, Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state))?, Insn::GuardBitEquals { val, expected, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state))?, @@ -890,6 +891,13 @@ fn gen_fixnum_ge(asm: &mut Assembler, left: lir::Opnd, right: lir::Opnd) -> Opti Some(asm.csel_ge(Qtrue.into(), Qfalse.into())) } +// Compile val == nil +fn gen_isnil(asm: &mut Assembler, val: lir::Opnd) -> Option { + asm.cmp(val, Qnil.into()); + // TODO: Implement and use setcc + Some(asm.csel_e(Opnd::Imm(1), Opnd::Imm(0))) +} + fn gen_anytostring(asm: &mut Assembler, val: lir::Opnd, str: lir::Opnd, state: &FrameState) -> Option { // Save PC gen_save_pc(asm, state); From 05443bb7e92498ded149ad0324f8d4fc7321e9ee Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 1 Jul 2025 09:47:20 +0900 Subject: [PATCH 017/130] [ruby/optparse] Use Dir.glob and base keyword arg for the installer of Ruby package https://github.com/ruby/optparse/commit/24374b42d3 --- lib/optparse/optparse.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/optparse/optparse.gemspec b/lib/optparse/optparse.gemspec index 8589f1857cf030..cd292674a9160c 100644 --- a/lib/optparse/optparse.gemspec +++ b/lib/optparse/optparse.gemspec @@ -25,7 +25,7 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir["{doc,lib,misc}/**/{*,.document}"] + + spec.files = Dir.glob("{doc,lib,misc}/**/{*,.document}", base: File.expand_path("..", __FILE__)) + %w[README.md ChangeLog COPYING .document .rdoc_options] spec.bindir = "exe" spec.executables = [] From 5ee63157040e5d5e3960c15e5fc685c08ee543ad Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 1 Jul 2025 10:00:31 +0900 Subject: [PATCH 018/130] Use Dir.glob and base keyword arg for the installer of Ruby package --- ext/json/json.gemspec | 3 +-- ext/openssl/openssl.gemspec | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/json/json.gemspec b/ext/json/json.gemspec index 07426363acf78b..55757310250dae 100644 --- a/ext/json/json.gemspec +++ b/ext/json/json.gemspec @@ -44,8 +44,7 @@ spec = Gem::Specification.new do |s| "LEGAL", "README.md", "json.gemspec", - *Dir["lib/**/*.rb"], - ] + ] + Dir.glob("lib/**/*.rb", base: File.expand_path("..", __FILE__)) if java_ext s.platform = 'java' diff --git a/ext/openssl/openssl.gemspec b/ext/openssl/openssl.gemspec index 9f7c718592045e..2ec1551885b201 100644 --- a/ext/openssl/openssl.gemspec +++ b/ext/openssl/openssl.gemspec @@ -13,7 +13,8 @@ Gem::Specification.new do |spec| spec.files = [] spec.add_runtime_dependency('jruby-openssl', '~> 0.14') else - spec.files = Dir["lib/**/*.rb", "ext/**/*.{c,h,rb}", "*.md", "BSDL", "COPYING"] + spec.files = Dir.glob(["lib/**/*.rb", "ext/**/*.{c,h,rb}", "*.md"], base: File.expand_path("..", __FILE__)) + + ["BSDL", "COPYING"] spec.require_paths = ["lib"] spec.extensions = ["ext/openssl/extconf.rb"] end From c3bdf7043cca0131e7ca66c1bc76ae6e24dc8965 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 1 Jul 2025 10:44:07 +0900 Subject: [PATCH 019/130] Use git ls-files instead of Dir.glob because optparse has optionparser.rb that is outside of lib/optparse directory Co-authored-by: Nobuyoshi Nakada --- lib/optparse/optparse.gemspec | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/optparse/optparse.gemspec b/lib/optparse/optparse.gemspec index cd292674a9160c..6ea6b883956dc0 100644 --- a/lib/optparse/optparse.gemspec +++ b/lib/optparse/optparse.gemspec @@ -25,8 +25,9 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir.glob("{doc,lib,misc}/**/{*,.document}", base: File.expand_path("..", __FILE__)) + - %w[README.md ChangeLog COPYING .document .rdoc_options] + dir, gemspec = File.split(__FILE__) + excludes = %W[#{gemspec} rakelib test/ Gemfile Rakefile .git* .editor*].map {|n| ":^"+n} + spec.files = IO.popen(%w[git ls-files -z --] + excludes, chdir: dir, &:read).split("\x0") spec.bindir = "exe" spec.executables = [] spec.require_paths = ["lib"] From 9dc60de4fcda927f4b7e4d8f160cc788c2ff93fa Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 1 Jul 2025 13:33:20 +0900 Subject: [PATCH 020/130] Fixed inconsistency gemspec location foo.gemspec should be located under the `lib/foo` directory. --- lib/{ => erb}/erb.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename lib/{ => erb}/erb.gemspec (97%) diff --git a/lib/erb.gemspec b/lib/erb/erb.gemspec similarity index 97% rename from lib/erb.gemspec rename to lib/erb/erb.gemspec index 0a59abad5391f7..94edc686825419 100644 --- a/lib/erb.gemspec +++ b/lib/erb/erb.gemspec @@ -2,7 +2,7 @@ begin require_relative 'lib/erb/version' rescue LoadError # for Ruby core repository - require_relative 'erb/version' + require_relative 'version' end Gem::Specification.new do |spec| From 91d5db55054c3d9dcdf7535c93303ba4c4ffc6b1 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 30 Jun 2025 21:17:57 +0900 Subject: [PATCH 021/130] [ruby/json] Use `load` simd/conf.rb When both extconf.rb of generator and parser are run in one process, the second `require_relative` does nothing. https://github.com/ruby/json/commit/8e775320b7 --- ext/json/generator/extconf.rb | 2 +- ext/json/parser/extconf.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/json/generator/extconf.rb b/ext/json/generator/extconf.rb index aaf02c77d607ba..fb9afd07f7bfee 100644 --- a/ext/json/generator/extconf.rb +++ b/ext/json/generator/extconf.rb @@ -9,7 +9,7 @@ $defs << "-DJSON_DEBUG" if ENV["JSON_DEBUG"] if enable_config('generator-use-simd', default=!ENV["JSON_DISABLE_SIMD"]) - require_relative "../simd/conf.rb" + load __dir__ + "/../simd/conf.rb" end create_makefile 'json/ext/generator' diff --git a/ext/json/parser/extconf.rb b/ext/json/parser/extconf.rb index 0b62fd6135c852..84049a7fe4d668 100644 --- a/ext/json/parser/extconf.rb +++ b/ext/json/parser/extconf.rb @@ -9,7 +9,7 @@ append_cflags("-std=c99") if enable_config('parser-use-simd', default=!ENV["JSON_DISABLE_SIMD"]) - require_relative "../simd/conf.rb" + load __dir__ + "/../simd/conf.rb" end create_makefile 'json/ext/parser' From 60eb1d5d233a753aaa2d60b305caf1909192b55a Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 30 Jun 2025 21:37:20 +0900 Subject: [PATCH 022/130] [ruby/json] Refactor simd/conf.rb - compiler warnings Suppress warnings for old style function definition and unused variable. https://github.com/ruby/json/commit/58dc0aa938 --- ext/json/simd/conf.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ext/json/simd/conf.rb b/ext/json/simd/conf.rb index 6393cf7891ea50..ee56718be54de7 100644 --- a/ext/json/simd/conf.rb +++ b/ext/json/simd/conf.rb @@ -3,8 +3,9 @@ if have_header('arm_neon.h') have_type('uint8x16_t', headers=['arm_neon.h']) && try_compile(<<~'SRC') #include - int main() { + int main(int argc, char **argv) { uint8x16_t test = vdupq_n_u8(32); + if (argc > 100000) printf("%p", &test); return 0; } SRC @@ -14,8 +15,9 @@ if have_header('x86intrin.h') && have_type('__m128i', headers=['x86intrin.h']) && try_compile(<<~'SRC') #include - int main() { + int main(int argc, char **argv) { __m128i test = _mm_set1_epi8(32); + if (argc > 100000) printf("%p", &test); return 0; } SRC From a9e2a818bd8dd8787d3e211c0384725125b9111c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 30 Jun 2025 21:38:56 +0900 Subject: [PATCH 023/130] [ruby/json] Refactor simd/conf.rb - balance Align code for arm and x86_64 in parallel. https://github.com/ruby/json/commit/2211e30a59 --- ext/json/simd/conf.rb | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/ext/json/simd/conf.rb b/ext/json/simd/conf.rb index ee56718be54de7..deaac412dc9fb2 100644 --- a/ext/json/simd/conf.rb +++ b/ext/json/simd/conf.rb @@ -1,4 +1,5 @@ -if RbConfig::CONFIG['host_cpu'] =~ /^(arm.*|aarch64.*)/ +case RbConfig::CONFIG['host_cpu'] +when /^(arm|aarch64)/ # Try to compile a small program using NEON instructions if have_header('arm_neon.h') have_type('uint8x16_t', headers=['arm_neon.h']) && try_compile(<<~'SRC') @@ -8,20 +9,20 @@ if (argc > 100000) printf("%p", &test); return 0; } - SRC - $defs.push("-DJSON_ENABLE_SIMD") + SRC + $defs.push("-DJSON_ENABLE_SIMD") end -end - -if have_header('x86intrin.h') && have_type('__m128i', headers=['x86intrin.h']) && try_compile(<<~'SRC') - #include - int main(int argc, char **argv) { - __m128i test = _mm_set1_epi8(32); - if (argc > 100000) printf("%p", &test); - return 0; - } - SRC +when /^(x86_64|x64)/ + if have_header('x86intrin.h') && have_type('__m128i', headers=['x86intrin.h']) && try_compile(<<~'SRC') + #include + int main(int argc, char **argv) { + __m128i test = _mm_set1_epi8(32); + if (argc > 100000) printf("%p", &test); + return 0; + } + SRC $defs.push("-DJSON_ENABLE_SIMD") + end end have_header('cpuid.h') From 7d9c3004cfe4ccba6afb17a1b90ff3c983bd007f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 30 Jun 2025 21:43:13 +0900 Subject: [PATCH 024/130] [ruby/json] Refactor simd/conf.rb - conditions to enable See the results of `have_type` and `try_compile` in addition to `have_header` for NEON as well as x86_64. The former results were just ignored, and `HAVE_TYPE_` macros are unused too. https://github.com/ruby/json/commit/fdbb6062c2 --- ext/json/simd/conf.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ext/json/simd/conf.rb b/ext/json/simd/conf.rb index deaac412dc9fb2..3b3cf68af329d4 100644 --- a/ext/json/simd/conf.rb +++ b/ext/json/simd/conf.rb @@ -1,8 +1,8 @@ case RbConfig::CONFIG['host_cpu'] when /^(arm|aarch64)/ # Try to compile a small program using NEON instructions - if have_header('arm_neon.h') - have_type('uint8x16_t', headers=['arm_neon.h']) && try_compile(<<~'SRC') + if have_header('arm_neon.h') && + have_type('uint8x16_t', headers=['arm_neon.h']) && try_compile(<<~'SRC') #include int main(int argc, char **argv) { uint8x16_t test = vdupq_n_u8(32); @@ -13,7 +13,8 @@ $defs.push("-DJSON_ENABLE_SIMD") end when /^(x86_64|x64)/ - if have_header('x86intrin.h') && have_type('__m128i', headers=['x86intrin.h']) && try_compile(<<~'SRC') + if have_header('x86intrin.h') && + have_type('__m128i', headers=['x86intrin.h']) && try_compile(<<~'SRC') #include int main(int argc, char **argv) { __m128i test = _mm_set1_epi8(32); From f909c907bbe7c2d9d433ff61e46bcb62e8316c25 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 30 Jun 2025 21:53:27 +0900 Subject: [PATCH 025/130] [ruby/json] Refactor simd/conf.rb - unnecessary `have_type` Remove `have_type` calls because the next `try_compile` calls check those types. https://github.com/ruby/json/commit/b08e1ca2c1 --- ext/json/simd/conf.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/json/simd/conf.rb b/ext/json/simd/conf.rb index 3b3cf68af329d4..fa5b97801f87f6 100644 --- a/ext/json/simd/conf.rb +++ b/ext/json/simd/conf.rb @@ -2,7 +2,7 @@ when /^(arm|aarch64)/ # Try to compile a small program using NEON instructions if have_header('arm_neon.h') && - have_type('uint8x16_t', headers=['arm_neon.h']) && try_compile(<<~'SRC') + try_compile(<<~'SRC') #include int main(int argc, char **argv) { uint8x16_t test = vdupq_n_u8(32); @@ -14,7 +14,7 @@ end when /^(x86_64|x64)/ if have_header('x86intrin.h') && - have_type('__m128i', headers=['x86intrin.h']) && try_compile(<<~'SRC') + try_compile(<<~'SRC') #include int main(int argc, char **argv) { __m128i test = _mm_set1_epi8(32); From 8a2210b351dedde847488734e646e112d9bd3dbe Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 30 Jun 2025 22:34:14 +0900 Subject: [PATCH 026/130] [ruby/json] Refactor simd/conf.rb - duplicate code Integrate duplicate code by extracting headers, types and initialization code. https://github.com/ruby/json/commit/1a768d9179 --- ext/json/simd/conf.rb | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/ext/json/simd/conf.rb b/ext/json/simd/conf.rb index fa5b97801f87f6..8e7d8ee26133d1 100644 --- a/ext/json/simd/conf.rb +++ b/ext/json/simd/conf.rb @@ -1,29 +1,20 @@ case RbConfig::CONFIG['host_cpu'] when /^(arm|aarch64)/ # Try to compile a small program using NEON instructions - if have_header('arm_neon.h') && - try_compile(<<~'SRC') - #include - int main(int argc, char **argv) { - uint8x16_t test = vdupq_n_u8(32); - if (argc > 100000) printf("%p", &test); - return 0; - } - SRC - $defs.push("-DJSON_ENABLE_SIMD") - end + header, type, init = 'arm_neon.h', 'uint8x16_t', 'vdupq_n_u8(32)' when /^(x86_64|x64)/ - if have_header('x86intrin.h') && - try_compile(<<~'SRC') - #include - int main(int argc, char **argv) { - __m128i test = _mm_set1_epi8(32); - if (argc > 100000) printf("%p", &test); - return 0; - } - SRC - $defs.push("-DJSON_ENABLE_SIMD") - end + header, type, init = 'x86intrin.h', '__m128i', '_mm_set1_epi8(32)' +end +if header + have_header(header) && try_compile(<<~SRC) + #{cpp_include(header)} + int main(int argc, char **argv) { + #{type} test = #{init}; + if (argc > 100000) printf("%p", &test); + return 0; + } + SRC + $defs.push("-DJSON_ENABLE_SIMD") end have_header('cpuid.h') From 9f148574184ccd38b8c4003433e8e1f0f27fce57 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 30 Jun 2025 08:07:17 -0700 Subject: [PATCH 027/130] [ruby/json] Suppress -Wunused-function https://github.com/ruby/json/commit/94ed471814 --- ext/json/simd/simd.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/json/simd/simd.h b/ext/json/simd/simd.h index ed2a6d467b6ab5..d11e4df3ff5c4c 100644 --- a/ext/json/simd/simd.h +++ b/ext/json/simd/simd.h @@ -59,7 +59,7 @@ static inline int trailing_zeros(int input) { #include #define FIND_SIMD_IMPLEMENTATION_DEFINED 1 -static SIMD_Implementation find_simd_implementation(void) { +static inline SIMD_Implementation find_simd_implementation(void) { return SIMD_NEON; } @@ -161,7 +161,7 @@ static inline TARGET_SSE2 FORCE_INLINE int string_scan_simd_sse2(const char **pt #include #endif /* HAVE_CPUID_H */ -static SIMD_Implementation find_simd_implementation(void) { +static inline SIMD_Implementation find_simd_implementation(void) { #if defined(__GNUC__ ) || defined(__clang__) #ifdef __GNUC__ @@ -183,7 +183,7 @@ static SIMD_Implementation find_simd_implementation(void) { #endif /* JSON_ENABLE_SIMD */ #ifndef FIND_SIMD_IMPLEMENTATION_DEFINED -static SIMD_Implementation find_simd_implementation(void) { +static inline SIMD_Implementation find_simd_implementation(void) { return SIMD_NONE; } #endif From ce6e61209510b648dace64468e1b602ddc8696fc Mon Sep 17 00:00:00 2001 From: git Date: Tue, 1 Jul 2025 07:06:23 +0000 Subject: [PATCH 028/130] Update bundled gems list as of 2025-07-01 --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index c6dc961360862d..2af947b4cd258b 100644 --- a/NEWS.md +++ b/NEWS.md @@ -158,7 +158,7 @@ The following bundled gems are updated. * minitest 5.25.5 * rake 13.3.0 -* test-unit 3.6.8 +* test-unit 3.6.9 * rexml 3.4.1 * net-imap 0.5.9 * net-smtp 0.5.1 diff --git a/gems/bundled_gems b/gems/bundled_gems index d1832335fd7d8f..25f5fcbda0c681 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -9,7 +9,7 @@ minitest 5.25.5 https://github.com/minitest/minitest power_assert 2.0.5 https://github.com/ruby/power_assert f88e406e7c9e0810cc149869582afbae1fb84c4a rake 13.3.0 https://github.com/ruby/rake -test-unit 3.6.8 https://github.com/test-unit/test-unit +test-unit 3.6.9 https://github.com/test-unit/test-unit rexml 3.4.1 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.8 https://github.com/ruby/net-ftp From 06f9fc20ec1d41bce7cbac14c5f8b977dfc479d3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Jul 2025 17:23:22 +0900 Subject: [PATCH 029/130] [ruby/io-console] Use `host_os` in RbConfig instead of `RUBY_PLATFORM` for JRuby https://github.com/ruby/io-console/commit/f8b33f38ae --- test/io/console/test_io_console.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index d43095bc4ca580..59fe01879ba64a 100644 --- a/test/io/console/test_io_console.rb +++ b/test/io/console/test_io_console.rb @@ -7,6 +7,11 @@ end class TestIO_Console < Test::Unit::TestCase + HOST_OS = RbConfig::CONFIG['host_os'] + private def host_os?(os) + HOST_OS =~ os + end + begin PATHS = $LOADED_FEATURES.grep(%r"/io/console(?:\.#{RbConfig::CONFIG['DLEXT']}|\.rb|/\w+\.rb)\z") {$`} rescue Encoding::CompatibilityError @@ -26,7 +31,7 @@ class TestIO_Console < Test::Unit::TestCase # But it does not occur in `make test-all > /dev/null`, so # there should be an additional factor, I guess. def set_winsize_setup - @old_ttou = trap(:TTOU, 'IGNORE') if RUBY_PLATFORM =~ /freebsd/i + @old_ttou = trap(:TTOU, 'IGNORE') if host_os?(/freebsd/) end def set_winsize_teardown @@ -387,7 +392,7 @@ def test_intr # TestIO_Console#test_intr [/usr/home/chkbuild/chkbuild/tmp/build/20220304T163001Z/ruby/test/io/console/test_io_console.rb:387]: # <"25"> expected but was # <"-e:12:in `p': \e[1mexecution expired (\e[1;4mTimeout::Error\e[m\e[1m)\e[m">. - omit if /freebsd/ =~ RUBY_PLATFORM + omit if host_os?(/freebsd/) run_pty("#{<<~"begin;"}\n#{<<~'end;'}") do |r, w, _| begin; @@ -413,7 +418,7 @@ def test_intr if cc = ctrl["intr"] assert_ctrl("#{cc.ord}", cc, r, w) assert_ctrl("#{cc.ord}", cc, r, w) - assert_ctrl("Interrupt", cc, r, w) unless /linux/ =~ RUBY_PLATFORM + assert_ctrl("Interrupt", cc, r, w) unless host_os?(/linux/) end if cc = ctrl["dsusp"] assert_ctrl("#{cc.ord}", cc, r, w) From d3d249b9048b338535ae033acb3606abf174b2da Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Jul 2025 17:24:02 +0900 Subject: [PATCH 030/130] [ruby/io-console] Fix removing unexpected control chars `cc` is created as `"\C-x"`, it is a String since ruby 1.9. https://github.com/ruby/io-console/commit/65c9266feb --- test/io/console/test_io_console.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/io/console/test_io_console.rb b/test/io/console/test_io_console.rb index 59fe01879ba64a..c3f9c91c7dedad 100644 --- a/test/io/console/test_io_console.rb +++ b/test/io/console/test_io_console.rb @@ -373,10 +373,10 @@ def assert_ctrl(expect, cc, r, w) w.flush result = EnvUtil.timeout(3) {r.gets} if result - case cc - when 0..31 + case cc.chr + when "\C-A".."\C-_" cc = "^" + (cc.ord | 0x40).chr - when 127 + when "\C-?" cc = "^?" end result.sub!(cc, "") From ad65d53aa4d241359df183afd3756653cc1571f9 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 1 Jul 2025 10:28:44 +0200 Subject: [PATCH 031/130] class.c: Stop deleting __classpath__ / __tmp_classpath__ These used to be private variables to store the class name but aren't a thing since several versions. --- class.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/class.c b/class.c index 96e9aaed21b0eb..bef54eae2f38c1 100644 --- a/class.c +++ b/class.c @@ -980,20 +980,15 @@ copy_tables(VALUE clone, VALUE orig) rb_id_table_free(RCLASS_M_TBL(clone)); RCLASS_WRITE_M_TBL_EVEN_WHEN_PROMOTED(clone, 0); if (!RB_TYPE_P(clone, T_ICLASS)) { - st_data_t id; - rb_fields_tbl_copy(clone, orig); - CONST_ID(id, "__tmp_classpath__"); - rb_attr_delete(clone, id); - CONST_ID(id, "__classpath__"); - rb_attr_delete(clone, id); } if (RCLASS_CONST_TBL(orig)) { struct clone_const_arg arg; struct rb_id_table *const_tbl; - arg.tbl = const_tbl = rb_id_table_create(0); + struct rb_id_table *orig_tbl = RCLASS_CONST_TBL(orig); + arg.tbl = const_tbl = rb_id_table_create(rb_id_table_size(orig_tbl)); arg.klass = clone; - rb_id_table_foreach(RCLASS_CONST_TBL(orig), clone_const_i, &arg); + rb_id_table_foreach(orig_tbl, clone_const_i, &arg); RCLASS_WRITE_CONST_TBL(clone, const_tbl, false); } } From 9ab3e47d35057cfe7ad0649e2e956d9897ebb3fc Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Mon, 30 Jun 2025 10:21:26 +0200 Subject: [PATCH 032/130] Simplify `rb_fields_tbl_copy` Now that ivars are stored in a imemo/fields, we can just clone the fields object. --- variable.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/variable.c b/variable.c index 3e17efd72e7fbe..b450a51b496df9 100644 --- a/variable.c +++ b/variable.c @@ -4747,14 +4747,6 @@ rb_class_ivar_set(VALUE obj, ID id, VALUE val) return !existing; } -static int -tbl_copy_i(ID key, VALUE val, st_data_t dest) -{ - rb_class_ivar_set((VALUE)dest, key, val); - - return ST_CONTINUE; -} - void rb_fields_tbl_copy(VALUE dst, VALUE src) { @@ -4762,7 +4754,11 @@ rb_fields_tbl_copy(VALUE dst, VALUE src) RUBY_ASSERT(RB_TYPE_P(dst, T_CLASS) || RB_TYPE_P(dst, T_MODULE)); RUBY_ASSERT(RSHAPE_TYPE_P(RBASIC_SHAPE_ID(dst), SHAPE_ROOT)); - rb_ivar_foreach(src, tbl_copy_i, dst); + VALUE fields_obj = RCLASS_WRITABLE_FIELDS_OBJ(src); + if (fields_obj) { + RCLASS_WRITABLE_SET_FIELDS_OBJ(dst, rb_imemo_fields_clone(fields_obj)); + RBASIC_SET_SHAPE_ID(dst, RBASIC_SHAPE_ID(src)); + } } static rb_const_entry_t * From 11fe8b26c1402461297cab902283c4601e699b35 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Jul 2025 17:38:25 +0900 Subject: [PATCH 033/130] [ruby/etc] Run `have_func` with the header providing the declarations https://github.com/ruby/etc/commit/6668bfd42a --- ext/etc/extconf.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/etc/extconf.rb b/ext/etc/extconf.rb index 3d7cceae40344d..497303a4fa51f7 100644 --- a/ext/etc/extconf.rb +++ b/ext/etc/extconf.rb @@ -60,7 +60,7 @@ # TODO: remove when dropping 2.7 support, as exported since 3.0 have_func('rb_deprecate_constant(Qnil, "None")') -have_func("rb_io_descriptor") +have_func("rb_io_descriptor", "ruby/io.h") $distcleanfiles << "constdefs.h" From 134bdf2d34a2630787cd5cf967097556b11d1c6e Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Jul 2025 17:38:25 +0900 Subject: [PATCH 034/130] [ruby/io-console] Run `have_func` with the header providing the declarations https://github.com/ruby/io-console/commit/dd013030dd --- ext/io/console/extconf.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/io/console/extconf.rb b/ext/io/console/extconf.rb index 4ad7ed6996df5b..dd3d221ae51df3 100644 --- a/ext/io/console/extconf.rb +++ b/ext/io/console/extconf.rb @@ -10,11 +10,11 @@ abort have_func("rb_interned_str_cstr") -have_func("rb_io_path") -have_func("rb_io_descriptor") -have_func("rb_io_get_write_io") -have_func("rb_io_closed_p") -have_func("rb_io_open_descriptor") +have_func("rb_io_path", "ruby/io.h") +have_func("rb_io_descriptor", "ruby/io.h") +have_func("rb_io_get_write_io", "ruby/io.h") +have_func("rb_io_closed_p", "ruby/io.h") +have_func("rb_io_open_descriptor", "ruby/io.h") have_func("rb_ractor_local_storage_value_newkey") is_wasi = /wasi/ =~ MakeMakefile::RbConfig::CONFIG["platform"] From ac72a25a57896724c6e4542829f5abde8731ed02 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Jul 2025 17:38:25 +0900 Subject: [PATCH 035/130] [ruby/io-nonblock] Run `have_func` with the header providing the declarations https://github.com/ruby/io-nonblock/commit/70909f5362 --- ext/io/nonblock/extconf.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/io/nonblock/extconf.rb b/ext/io/nonblock/extconf.rb index a1e6075c9b5052..505c9e6252fb99 100644 --- a/ext/io/nonblock/extconf.rb +++ b/ext/io/nonblock/extconf.rb @@ -7,7 +7,7 @@ return end -have_func("rb_io_descriptor") +have_func("rb_io_descriptor", "ruby/io.h") hdr = %w"fcntl.h" if have_macro("O_NONBLOCK", hdr) and From 8903166648776b17cb574557f25e5a77114f8b94 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Jul 2025 17:38:25 +0900 Subject: [PATCH 036/130] [ruby/io-wait] Run `have_func` with the header providing the declarations https://github.com/ruby/io-wait/commit/48309d7877 --- ext/io/wait/extconf.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/io/wait/extconf.rb b/ext/io/wait/extconf.rb index e63c0461873187..ba223f0ac24ad6 100644 --- a/ext/io/wait/extconf.rb +++ b/ext/io/wait/extconf.rb @@ -5,8 +5,8 @@ File.write("Makefile", dummy_makefile($srcdir).join("")) else target = "io/wait" - have_func("rb_io_wait") - have_func("rb_io_descriptor") + have_func("rb_io_wait", "ruby/io.h") + have_func("rb_io_descriptor", "ruby/io.h") unless macro_defined?("DOSISH", "#include ") have_header(ioctl_h = "sys/ioctl.h") or ioctl_h = nil fionread = %w[sys/ioctl.h sys/filio.h sys/socket.h].find do |h| From 5277ca1431af870d7cf28470d4a6b8ee443e50ee Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Jul 2025 17:38:25 +0900 Subject: [PATCH 037/130] [ruby/openssl] Run `have_func` with the header providing the declarations https://github.com/ruby/openssl/commit/b6f56c4540 --- ext/openssl/extconf.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 6eb401cf5597de..afbed10b54bd06 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -38,7 +38,7 @@ $defs.push("-D""OPENSSL_SUPPRESS_DEPRECATED") -have_func("rb_io_descriptor") +have_func("rb_io_descriptor", "ruby/io.h") have_func("rb_io_maybe_wait(0, Qnil, Qnil, Qnil)", "ruby/io.h") # Ruby 3.1 have_func("rb_io_timeout", "ruby/io.h") From 94803fe9e7b7048a031c4d39e27bd90373dde25c Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Jul 2025 17:38:25 +0900 Subject: [PATCH 038/130] [ruby/strscan] Run `have_func` with the header providing the declarations https://github.com/ruby/strscan/commit/18c0a59b65 --- ext/strscan/extconf.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/strscan/extconf.rb b/ext/strscan/extconf.rb index abcbdb3ad216cd..3c311d2364b7d7 100644 --- a/ext/strscan/extconf.rb +++ b/ext/strscan/extconf.rb @@ -2,7 +2,7 @@ require 'mkmf' if RUBY_ENGINE == 'ruby' $INCFLAGS << " -I$(top_srcdir)" if $extmk - have_func("onig_region_memsize") + have_func("onig_region_memsize(NULL)") have_func("rb_reg_onig_match", "ruby/re.h") create_makefile 'strscan' else From ae605b652da0933ae10aa7d40107b0234afd11ac Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 1 Jul 2025 08:48:00 +0200 Subject: [PATCH 039/130] [ruby/json] Stop calling `__builtin_cpu_init` It's only needed if using GCC `ifunc` mecanism, which we don't. https://github.com/ruby/json/commit/d3317b9f82 --- ext/json/simd/simd.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ext/json/simd/simd.h b/ext/json/simd/simd.h index d11e4df3ff5c4c..e0cf4754a2abda 100644 --- a/ext/json/simd/simd.h +++ b/ext/json/simd/simd.h @@ -162,17 +162,10 @@ static inline TARGET_SSE2 FORCE_INLINE int string_scan_simd_sse2(const char **pt #endif /* HAVE_CPUID_H */ static inline SIMD_Implementation find_simd_implementation(void) { - -#if defined(__GNUC__ ) || defined(__clang__) -#ifdef __GNUC__ - __builtin_cpu_init(); -#endif /* __GNUC__ */ - // TODO Revisit. I think the SSE version now only uses SSE2 instructions. if (__builtin_cpu_supports("sse2")) { return SIMD_SSE2; } -#endif /* __GNUC__ || __clang__*/ return SIMD_NONE; } From 9d080765cc3c6266521863ffe5882ba8d8322271 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 1 Jul 2025 17:38:25 +0900 Subject: [PATCH 040/130] [ruby/json] Run `have_func` with the header providing the declarations https://github.com/ruby/json/commit/95fb084027 --- ext/json/parser/extconf.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/parser/extconf.rb b/ext/json/parser/extconf.rb index 84049a7fe4d668..de5d5758b46c42 100644 --- a/ext/json/parser/extconf.rb +++ b/ext/json/parser/extconf.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true require 'mkmf' -have_func("rb_enc_interned_str", "ruby.h") # RUBY_VERSION >= 3.0 +have_func("rb_enc_interned_str", "ruby/encoding.h") # RUBY_VERSION >= 3.0 have_func("rb_hash_new_capa", "ruby.h") # RUBY_VERSION >= 3.2 have_func("rb_hash_bulk_insert", "ruby.h") # Missing on TruffleRuby have_func("strnlen", "string.h") # Missing on Solaris 10 From f4ea42a8ca719ebc3a42ea12e07ca8f3189afef4 Mon Sep 17 00:00:00 2001 From: Kevin Saison Date: Mon, 30 Jun 2025 14:11:19 +0200 Subject: [PATCH 041/130] [DOC] Fix ARGF example --- io.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/io.c b/io.c index 327740aa2ec614..ba43bd80659a01 100644 --- a/io.c +++ b/io.c @@ -14918,7 +14918,7 @@ set_LAST_READ_LINE(VALUE val, ID _x, VALUE *_y) * - \File +t.rb+: * * p "ARGV: #{ARGV}" - * p "Line: #{ARGF.read}" # Read everything from all specified streams. + * p "Read: #{ARGF.read}" # Read everything from all specified streams. * * - Command and output: * From 2fda84347964a9dc8ab70ccc2906d35bcaf15333 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 1 Jul 2025 11:59:33 -0700 Subject: [PATCH 042/130] ZJIT: Stop tracking EP == BP assumption on JIT entry (#13752) * ZJIT: Stop tracking EP == BP assumption on JIT entry * Enable test_method.rb as well --- .github/workflows/zjit-macos.yml | 6 ++--- .github/workflows/zjit-ubuntu.yml | 6 ++--- zjit/src/codegen.rs | 38 ++++++++++++++++++------------- zjit/src/invariants.rs | 2 ++ 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 5454f05b090f98..8e58605fe15f91 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -118,27 +118,27 @@ jobs: ../src/bootstraptest/test_flip.rb \ ../src/bootstraptest/test_flow.rb \ ../src/bootstraptest/test_fork.rb \ + ../src/bootstraptest/test_gc.rb \ ../src/bootstraptest/test_io.rb \ ../src/bootstraptest/test_jump.rb \ ../src/bootstraptest/test_literal.rb \ ../src/bootstraptest/test_literal_suffix.rb \ ../src/bootstraptest/test_load.rb \ ../src/bootstraptest/test_marshal.rb \ + ../src/bootstraptest/test_method.rb \ ../src/bootstraptest/test_objectspace.rb \ ../src/bootstraptest/test_string.rb \ ../src/bootstraptest/test_struct.rb \ ../src/bootstraptest/test_syntax.rb \ + ../src/bootstraptest/test_thread.rb \ ../src/bootstraptest/test_yjit_30k_ifelse.rb \ ../src/bootstraptest/test_yjit_30k_methods.rb \ ../src/bootstraptest/test_yjit_rust_port.rb # ../src/bootstraptest/test_eval.rb \ - # ../src/bootstraptest/test_gc.rb \ # ../src/bootstraptest/test_insns.rb \ # ../src/bootstraptest/test_massign.rb \ - # ../src/bootstraptest/test_method.rb \ # ../src/bootstraptest/test_proc.rb \ # ../src/bootstraptest/test_ractor.rb \ - # ../src/bootstraptest/test_thread.rb \ # ../src/bootstraptest/test_yjit.rb \ if: ${{ matrix.test_task == 'btest' }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index aa9402546a3b65..443c9c71df5980 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -140,6 +140,7 @@ jobs: ../src/bootstraptest/test_flip.rb \ ../src/bootstraptest/test_flow.rb \ ../src/bootstraptest/test_fork.rb \ + ../src/bootstraptest/test_gc.rb \ ../src/bootstraptest/test_io.rb \ ../src/bootstraptest/test_jump.rb \ ../src/bootstraptest/test_literal.rb \ @@ -147,20 +148,19 @@ jobs: ../src/bootstraptest/test_load.rb \ ../src/bootstraptest/test_marshal.rb \ ../src/bootstraptest/test_massign.rb \ + ../src/bootstraptest/test_method.rb \ ../src/bootstraptest/test_objectspace.rb \ ../src/bootstraptest/test_string.rb \ ../src/bootstraptest/test_struct.rb \ ../src/bootstraptest/test_syntax.rb \ + ../src/bootstraptest/test_thread.rb \ ../src/bootstraptest/test_yjit_30k_ifelse.rb \ ../src/bootstraptest/test_yjit_30k_methods.rb \ ../src/bootstraptest/test_yjit_rust_port.rb # ../src/bootstraptest/test_eval.rb \ - # ../src/bootstraptest/test_gc.rb \ # ../src/bootstraptest/test_insns.rb \ - # ../src/bootstraptest/test_method.rb \ # ../src/bootstraptest/test_proc.rb \ # ../src/bootstraptest/test_ractor.rb \ - # ../src/bootstraptest/test_thread.rb \ # ../src/bootstraptest/test_yjit.rb \ if: ${{ matrix.test_task == 'btest' }} diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index d7d3cb8acab2ee..33a8af68683b80 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -6,7 +6,6 @@ use crate::backend::current::{Reg, ALLOC_REGS}; use crate::profile::get_or_create_iseq_payload; use crate::state::ZJITState; use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; -use crate::invariants::{iseq_escapes_ep, track_no_ep_escape_assumption}; use crate::backend::lir::{self, asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, SP}; use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, CallInfo, RangeType, SELF_PARAM_IDX, SpecialObjectType}; use crate::hir::{Const, FrameState, Function, Insn, InsnId}; @@ -59,15 +58,6 @@ impl JITState { } } } - - /// Assume that this ISEQ doesn't escape EP. Return false if it's known to escape EP. - fn assume_no_ep_escape(iseq: IseqPtr) -> bool { - if iseq_escapes_ep(iseq) { - return false; - } - track_no_ep_escape_assumption(iseq); - true - } } /// CRuby API to compile a given ISEQ @@ -145,7 +135,7 @@ fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function, function_pt // Set up registers for CFP, EC, SP, and basic block arguments let mut asm = Assembler::new(); gen_entry_prologue(&mut asm, iseq); - gen_method_params(&mut asm, iseq, function.block(BlockId(0))); + gen_entry_params(&mut asm, iseq, function.block(BlockId(0))); // Jump to the first block using a call instruction asm.ccall(function_ptr.raw_ptr(cb) as *const u8, vec![]); @@ -501,7 +491,7 @@ fn gen_entry_prologue(asm: &mut Assembler, iseq: IseqPtr) { } /// Assign method arguments to basic block arguments at JIT entry -fn gen_method_params(asm: &mut Assembler, iseq: IseqPtr, entry_block: &Block) { +fn gen_entry_params(asm: &mut Assembler, iseq: IseqPtr, entry_block: &Block) { let self_param = gen_param(asm, SELF_PARAM_IDX); asm.mov(self_param, Opnd::mem(VALUE_BITS, CFP, RUBY_OFFSET_CFP_SELF)); @@ -516,7 +506,7 @@ fn gen_method_params(asm: &mut Assembler, iseq: IseqPtr, entry_block: &Block) { // Assign local variables to the basic block arguments for (idx, ¶m) in params.iter().enumerate() { - let local = gen_getlocal(asm, iseq, idx); + let local = gen_entry_param(asm, iseq, idx); asm.load_into(param, local); } } @@ -535,11 +525,13 @@ fn gen_branch_params(jit: &mut JITState, asm: &mut Assembler, branch: &BranchEdg Some(()) } -/// Get the local variable at the given index -fn gen_getlocal(asm: &mut Assembler, iseq: IseqPtr, local_idx: usize) -> lir::Opnd { +/// Get a method parameter on JIT entry. As of entry, whether EP is escaped or not solely +/// depends on the ISEQ type. +fn gen_entry_param(asm: &mut Assembler, iseq: IseqPtr, local_idx: usize) -> lir::Opnd { let ep_offset = local_idx_to_ep_offset(iseq, local_idx); - if JITState::assume_no_ep_escape(iseq) { + // If the ISEQ does not escape EP, we can optimize the local variable access using the SP register. + if !iseq_entry_escapes_ep(iseq) { // Create a reference to the local variable using the SP register. We assume EP == BP. // TODO: Implement the invalidation in rb_zjit_invalidate_ep_is_bp() let offs = -(SIZEOF_VALUE_I32 * (ep_offset + 1)); @@ -1057,6 +1049,20 @@ fn side_exit(jit: &mut JITState, state: &FrameState) -> Option { Some(target) } +/// Return true if a given ISEQ is known to escape EP to the heap on entry. +/// +/// As of vm_push_frame(), EP is always equal to BP. However, after pushing +/// a frame, some ISEQ setups call vm_bind_update_env(), which redirects EP. +fn iseq_entry_escapes_ep(iseq: IseqPtr) -> bool { + match unsafe { get_iseq_body_type(iseq) } { + //
frame is always associated to TOPLEVEL_BINDING. + ISEQ_TYPE_MAIN | + // Kernel#eval uses a heap EP when a Binding argument is not nil. + ISEQ_TYPE_EVAL => true, + _ => false, + } +} + impl Assembler { /// Make a C call while marking the start and end positions of it fn ccall_with_branch(&mut self, fptr: *const u8, opnds: Vec, branch: &Rc) -> Opnd { diff --git a/zjit/src/invariants.rs b/zjit/src/invariants.rs index 77fd78d95e9ed9..77ccc7d04c43d2 100644 --- a/zjit/src/invariants.rs +++ b/zjit/src/invariants.rs @@ -40,6 +40,8 @@ pub extern "C" fn rb_zjit_invalidate_ep_is_bp(iseq: IseqPtr) { invariants.ep_escape_iseqs.insert(iseq); // If the ISEQ has been compiled assuming it doesn't escape EP, invalidate the JIT code. + // Note: Nobody calls track_no_ep_escape_assumption() for now, so this is always false. + // TODO: Add a PatchPoint that assumes EP == BP in HIR and invalidate it here. if invariants.no_ep_escape_iseqs.contains(&iseq) { unimplemented!("Invalidation on EP escape is not implemented yet"); } From 53baafe4960f588d972262171540be0d88f730bf Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Tue, 1 Jul 2025 15:11:58 -0700 Subject: [PATCH 043/130] ZJIT: Shorten Debug print for 64-bit VReg (#13763) --- zjit/src/backend/lir.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index f46b35ded55fb1..5fe4b85b629764 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -77,6 +77,7 @@ impl fmt::Debug for Opnd { match self { Self::None => write!(fmt, "None"), Value(val) => write!(fmt, "Value({val:?})"), + VReg { idx, num_bits } if *num_bits == 64 => write!(fmt, "VReg({idx})"), VReg { idx, num_bits } => write!(fmt, "VReg{num_bits}({idx})"), Imm(signed) => write!(fmt, "{signed:x}_i64"), UImm(unsigned) => write!(fmt, "{unsigned:x}_u64"), From 29657a1ecb2a546a8b1a2a46c6fc0fe163b83a6c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 2 Jul 2025 07:46:57 +0900 Subject: [PATCH 044/130] Fixup 9dc60de4fcd Sync erb.gemspec to under the `lib/erb/ directory. --- tool/sync_default_gems.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index ca0b15dd194134..f3985f6f81c4d6 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -276,7 +276,7 @@ def sync_default_gems(gem) rm_rf(%w[lib/erb* test/erb libexec/erb]) cp_r("#{upstream}/lib/erb.rb", "lib") cp_r("#{upstream}/test/erb", "test") - cp_r("#{upstream}/erb.gemspec", "lib") + cp_r("#{upstream}/erb.gemspec", "lib/erb") cp_r("#{upstream}/libexec/erb", "libexec") when "pathname" rm_rf(%w[ext/pathname test/pathname]) From 2cb065d0a267fbd89f6b42447159146cb8694ade Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 2 Jul 2025 09:23:08 +0900 Subject: [PATCH 045/130] Update gcc for LTO to 15 --- .github/workflows/compilers.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/compilers.yml b/.github/workflows/compilers.yml index 4dd1fdd2e78d37..d0be762ceeb7c2 100644 --- a/.github/workflows/compilers.yml +++ b/.github/workflows/compilers.yml @@ -78,11 +78,11 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: { sparse-checkout-cone-mode: false, sparse-checkout: /.github } - { uses: './.github/actions/setup/directories', with: { srcdir: 'src', builddir: 'build', makeup: true, fetch-depth: 10 } } - - name: 'GCC 13 LTO' + - name: 'GCC 15 LTO' uses: './.github/actions/compilers' with: - tag: gcc-13 - with_gcc: 'gcc-13 -flto=auto -ffat-lto-objects -Werror=lto-type-mismatch' + tag: gcc-15 + with_gcc: 'gcc-15 -flto=auto -ffat-lto-objects -Werror=lto-type-mismatch' optflags: '-O2' enable_shared: false - { uses: './.github/actions/compilers', name: 'ext/Setup', with: { static_exts: 'etc json/* */escape' } } From d77e02bd85ab7f841df8d473bac214b9a92a3506 Mon Sep 17 00:00:00 2001 From: "Z. Liu" Date: Wed, 2 Jul 2025 09:09:52 +0800 Subject: [PATCH 046/130] [Bug #21497] [ruby/socket]: add full prototype MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit otherwise, gcc 15 will complain: > init.c:573:19: error: too many arguments to function ‘Rconnect’; expected 0, have 3 > 573 | return (VALUE)Rconnect(arg->fd, arg->sockaddr, arg->len); > | ^~~~~~~~ ~~~~~~~ > In file included from init.c:11: > rubysocket.h:294:5: note: declared here > 294 | int Rconnect(); > | ^~~~~~~~ > sockssocket.c:33:9: error: too many arguments to function ‘SOCKSinit’; expected 0, have 1 > 33 | SOCKSinit("ruby"); > | ^~~~~~~~~ ~~~~~~ > In file included from sockssocket.c:11: > rubysocket.h:293:6: note: declared here > 293 | void SOCKSinit(); > | ^~~~~~~~~ Signed-off-by: Z. Liu --- ext/socket/rubysocket.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index 54a5381da416e1..dcafbe24e3111e 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -292,8 +292,8 @@ extern VALUE rb_eResolution; #ifdef SOCKS extern VALUE rb_cSOCKSSocket; # ifndef SOCKS5 -void SOCKSinit(); -int Rconnect(); +void SOCKSinit(char *); +int Rconnect(int, const struct sockaddr *, socklen_t); # endif #endif From f11daeb6250e161306c91786a0a02bc6bf490e35 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 2 Jul 2025 10:15:24 +0900 Subject: [PATCH 047/130] Declaring with a prototype In C99, a function declaration with an empty argument list means "no information about the arguments", not "it takes no arguments" - the so-called old K&R style. --- yjit.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yjit.h b/yjit.h index 9360e7fe3c8c9e..468965500218e0 100644 --- a/yjit.h +++ b/yjit.h @@ -37,7 +37,7 @@ void rb_yjit_collect_binding_alloc(void); void rb_yjit_collect_binding_set(void); void rb_yjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit_exception); void rb_yjit_init(bool yjit_enabled); -void rb_yjit_free_at_exit(); +void rb_yjit_free_at_exit(void); void rb_yjit_bop_redefined(int redefined_flag, enum ruby_basic_operators bop); void rb_yjit_constant_state_changed(ID id); void rb_yjit_iseq_mark(void *payload); From 99178c3ebb05da31c8756e18bc46d7b230f9e6ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 3 Apr 2025 15:51:26 +0200 Subject: [PATCH 048/130] [rubygems/rubygems] Migrate `bundle add` specs to run offline https://github.com/rubygems/rubygems/commit/02de767e14 --- spec/bundler/commands/add_spec.rb | 61 +++++++++++++++---------------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/spec/bundler/commands/add_spec.rb b/spec/bundler/commands/add_spec.rb index 2676b06c781677..00aa6415e11c97 100644 --- a/spec/bundler/commands/add_spec.rb +++ b/spec/bundler/commands/add_spec.rb @@ -161,26 +161,47 @@ end describe "with --github" do + before do + build_git "rake", "13.0" + git("config --global url.file://#{lib_path("rake-13.0")}.insteadOf https://github.com/ruby/rake.git") + end + it "adds dependency with specified github source" do bundle "add rake --github=ruby/rake" expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake"}) end - end - describe "with --github and --branch" do it "adds dependency with specified github source and branch" do - bundle "add rake --github=ruby/rake --branch=master" + bundle "add rake --github=ruby/rake --branch=main" - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", branch: "master"}) + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", branch: "main"}) end - end - describe "with --github and --ref" do it "adds dependency with specified github source and ref" do - bundle "add rake --github=ruby/rake --ref=5c60da8" + ref = revision_for(lib_path("rake-13.0")) + bundle "add rake --github=ruby/rake --ref=#{ref}" - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", ref: "5c60da8"}) + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", ref: "#{ref}"}) + end + + it "adds dependency with specified github source and glob" do + bundle "add rake --github=ruby/rake --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", glob: "\.\/\*\.gemspec"}) + end + + it "adds dependency with specified github source, branch and glob" do + bundle "add rake --github=ruby/rake --branch=main --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", branch: "main", glob: "\.\/\*\.gemspec"}) + end + + it "adds dependency with specified github source, ref and glob" do + ref = revision_for(lib_path("rake-13.0")) + bundle "add rake --github=ruby/rake --ref=#{ref} --glob='./*.gemspec'" + + expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", ref: "#{ref}", glob: "\.\/\*\.gemspec"}) end end @@ -215,30 +236,6 @@ end end - describe "with --github and --glob" do - it "adds dependency with specified github source" do - bundle "add rake --github=ruby/rake --glob='./*.gemspec'" - - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", glob: "\.\/\*\.gemspec"}) - end - end - - describe "with --github and --branch --and glob" do - it "adds dependency with specified github source and branch" do - bundle "add rake --github=ruby/rake --branch=master --glob='./*.gemspec'" - - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", branch: "master", glob: "\.\/\*\.gemspec"}) - end - end - - describe "with --github and --ref and --glob" do - it "adds dependency with specified github source and ref" do - bundle "add rake --github=ruby/rake --ref=5c60da8 --glob='./*.gemspec'" - - expect(bundled_app_gemfile.read).to match(%r{gem "rake", "~> 13\.\d+", github: "ruby\/rake", ref: "5c60da8", glob: "\.\/\*\.gemspec"}) - end - end - describe "with --skip-install" do it "adds gem to Gemfile but is not installed" do bundle "add foo --skip-install --version=2.0" From e1896c1910b9a6537019898d9dfde6889363112b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 24 Jun 2025 12:37:44 +0200 Subject: [PATCH 049/130] [rubygems/rubygems] Lock specs don't need the real rake gem https://github.com/rubygems/rubygems/commit/d795b8fcb4 --- spec/bundler/commands/lock_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb index da21e44c9cf501..c47cc9727191ea 100644 --- a/spec/bundler/commands/lock_spec.rb +++ b/spec/bundler/commands/lock_spec.rb @@ -101,9 +101,8 @@ let(:gemfile_with_rails_weakling_and_foo_from_repo4) do build_repo4 do - FileUtils.cp rake_path, "#{gem_repo4}/gems/" - build_gem "rake", "10.0.1" + build_gem "rake", rake_version %w[2.3.1 2.3.2].each do |version| build_gem "rails", version do |s| From 83c403c4d8654e8eda68eee8190f5cc2dcb96d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 24 Jun 2025 13:30:49 +0200 Subject: [PATCH 050/130] [rubygems/rubygems] Rename some helpers to avoid name classes I plan to introduce a `base_system_gems` helper to actually install gems from `base_system_gem_path` into system gems but that would collapse with an existing helper. https://github.com/rubygems/rubygems/commit/714c209e62 --- spec/bundler/commands/newgem_spec.rb | 4 ++-- spec/bundler/runtime/gem_tasks_spec.rb | 6 +++--- .../support/artifice/helpers/compact_index.rb | 2 +- spec/bundler/support/builders.rb | 6 +++--- spec/bundler/support/helpers.rb | 2 +- spec/bundler/support/path.rb | 14 +++++++------- spec/bundler/support/rubygems_ext.rb | 6 +++--- 7 files changed, 20 insertions(+), 20 deletions(-) diff --git a/spec/bundler/commands/newgem_spec.rb b/spec/bundler/commands/newgem_spec.rb index 5fc3c7109b6218..ba579ffe5762b2 100644 --- a/spec/bundler/commands/newgem_spec.rb +++ b/spec/bundler/commands/newgem_spec.rb @@ -15,13 +15,13 @@ def gem_skeleton_assertions def bundle_exec_rubocop prepare_gemspec(bundled_app(gem_name, "#{gem_name}.gemspec")) - bundle "config set path #{rubocop_gems}", dir: bundled_app(gem_name) + bundle "config set path #{rubocop_gem_path}", dir: bundled_app(gem_name) bundle "exec rubocop --debug --config .rubocop.yml", dir: bundled_app(gem_name) end def bundle_exec_standardrb prepare_gemspec(bundled_app(gem_name, "#{gem_name}.gemspec")) - bundle "config set path #{standard_gems}", dir: bundled_app(gem_name) + bundle "config set path #{standard_gem_path}", dir: bundled_app(gem_name) bundle "exec standardrb --debug", dir: bundled_app(gem_name) end diff --git a/spec/bundler/runtime/gem_tasks_spec.rb b/spec/bundler/runtime/gem_tasks_spec.rb index 046300391b26cc..6a8de2b949253a 100644 --- a/spec/bundler/runtime/gem_tasks_spec.rb +++ b/spec/bundler/runtime/gem_tasks_spec.rb @@ -66,7 +66,7 @@ it "includes the relevant tasks" do define_local_gem_using_gem_tasks - with_gem_path_as(base_system_gem_path.to_s) do + with_gem_path_as(scoped_base_system_gem_path.to_s) do sys_exec "#{rake} -T", env: { "GEM_HOME" => system_gem_path.to_s } end @@ -85,7 +85,7 @@ it "defines a working `rake install` task", :ruby_repo do define_local_gem_using_gem_tasks - with_gem_path_as(base_system_gem_path.to_s) do + with_gem_path_as(scoped_base_system_gem_path.to_s) do sys_exec "#{rake} install", env: { "GEM_HOME" => system_gem_path.to_s } end @@ -155,7 +155,7 @@ it "adds 'pkg' to rake/clean's CLOBBER" do define_local_gem_using_gem_tasks - with_gem_path_as(base_system_gem_path.to_s) do + with_gem_path_as(scoped_base_system_gem_path.to_s) do sys_exec %(#{rake} -e 'load "Rakefile"; puts CLOBBER.inspect'), env: { "GEM_HOME" => system_gem_path.to_s } end expect(out).to eq '["pkg"]' diff --git a/spec/bundler/support/artifice/helpers/compact_index.rb b/spec/bundler/support/artifice/helpers/compact_index.rb index ba331e483f1d81..e61fe921ec3b05 100644 --- a/spec/bundler/support/artifice/helpers/compact_index.rb +++ b/spec/bundler/support/artifice/helpers/compact_index.rb @@ -2,7 +2,7 @@ require_relative "endpoint" -$LOAD_PATH.unshift Dir[Spec::Path.base_system_gem_path.join("gems/compact_index*/lib")].first.to_s +$LOAD_PATH.unshift Dir[Spec::Path.scoped_base_system_gem_path.join("gems/compact_index*/lib")].first.to_s require "compact_index" require "digest" diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb index e94ca5bfc5c6ca..a0c91b71d2abad 100644 --- a/spec/bundler/support/builders.rb +++ b/spec/bundler/support/builders.rb @@ -277,9 +277,9 @@ def update_repo(path, build_compact_index: true) @_build_path = "#{path}/gems" @_build_repo = File.basename(path) yield - with_gem_path_as base_system_gem_path do - Dir[base_system_gem_path.join("gems/rubygems-generate_index*/lib")].first || - raise("Could not find rubygems-generate_index lib directory in #{base_system_gem_path}") + with_gem_path_as scoped_base_system_gem_path do + Dir[scoped_base_system_gem_path.join("gems/rubygems-generate_index*/lib")].first || + raise("Could not find rubygems-generate_index lib directory in #{scoped_base_system_gem_path}") command = "generate_index" command += " --no-compact" if !build_compact_index && gem_command(command + " --help").include?("--[no-]compact") diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb index 83f24214f325ef..2eb2ca1d2226a6 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -509,7 +509,7 @@ def with_env_vars(env_hash, &block) def require_rack_test # need to hack, so we can require rack for testing old_gem_home = ENV["GEM_HOME"] - ENV["GEM_HOME"] = Spec::Path.base_system_gem_path.to_s + ENV["GEM_HOME"] = Spec::Path.scoped_base_system_gem_path.to_s require "rack/test" ENV["GEM_HOME"] = old_gem_home end diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index d0542669d0b1f9..798b92ba07ded9 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -175,19 +175,19 @@ def bundled_app_lock bundled_app("Gemfile.lock") end - def base_system_gem_path - scoped_gem_path(base_system_gems) + def scoped_base_system_gem_path + scoped_gem_path(base_system_gem_path) end - def base_system_gems + def base_system_gem_path tmp_root.join("gems/base") end - def rubocop_gems + def rubocop_gem_path tmp_root.join("gems/rubocop") end - def standard_gems + def standard_gem_path tmp_root.join("gems/standard") end @@ -285,7 +285,7 @@ def git_root end def rake_path - Dir["#{base_system_gems}/*/*/**/rake*.gem"].first + Dir["#{base_system_gem_path}/*/*/**/rake*.gem"].first end def rake_version @@ -303,7 +303,7 @@ def sinatra_dependency_paths logger cgi ] - Dir[base_system_gem_path.join("gems/{#{deps.join(",")}}-*/lib")].map(&:to_s) + Dir[scoped_base_system_gem_path.join("gems/{#{deps.join(",")}}-*/lib")].map(&:to_s) end private diff --git a/spec/bundler/support/rubygems_ext.rb b/spec/bundler/support/rubygems_ext.rb index e10400e040925c..43d7ef5456e8eb 100644 --- a/spec/bundler/support/rubygems_ext.rb +++ b/spec/bundler/support/rubygems_ext.rb @@ -56,9 +56,9 @@ def setup_test_paths end def install_test_deps - dev_bundle("install", gemfile: test_gemfile, path: Path.base_system_gems.to_s) - dev_bundle("install", gemfile: rubocop_gemfile, path: Path.rubocop_gems.to_s) - dev_bundle("install", gemfile: standard_gemfile, path: Path.standard_gems.to_s) + dev_bundle("install", gemfile: test_gemfile, path: Path.base_system_gem_path.to_s) + dev_bundle("install", gemfile: rubocop_gemfile, path: Path.rubocop_gem_path.to_s) + dev_bundle("install", gemfile: standard_gemfile, path: Path.standard_gem_path.to_s) require_relative "helpers" Helpers.install_dev_bundler From cd742b6be6585113032e473f32af666e971947fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 24 Jun 2025 13:35:29 +0200 Subject: [PATCH 051/130] [rubygems/rubygems] Simpler glob to find path to rake https://github.com/rubygems/rubygems/commit/852db00169 --- spec/bundler/support/path.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index 798b92ba07ded9..f02e2468f74175 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -285,7 +285,7 @@ def git_root end def rake_path - Dir["#{base_system_gem_path}/*/*/**/rake*.gem"].first + Dir["#{scoped_base_system_gem_path}/**/rake*.gem"].first end def rake_version From d8cf0013efe7c56d590a055b8149adce120f0fdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 24 Jun 2025 17:17:09 +0200 Subject: [PATCH 052/130] [rubygems/rubygems] These specs need stringio only for old versions https://github.com/rubygems/rubygems/commit/a44cdf4c21 --- spec/bundler/install/gems/standalone_spec.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb index a8cb8cb3f99d16..d6128b41a91a5f 100644 --- a/spec/bundler/install/gems/standalone_spec.rb +++ b/spec/bundler/install/gems/standalone_spec.rb @@ -146,7 +146,8 @@ end it "works and points to the vendored copies, not to the default copies" do - necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.4.3", "stringio --version 3.1.0"] + necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.4.3"] + necessary_gems_in_bundle_path += ["stringio --version 3.1.0"] if Gem.ruby_version < Gem::Version.new("3.4.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) build_gem "foo", "1.0.0", to_system: true, default: true do |s| @@ -184,7 +185,8 @@ it "works for gems with extensions and points to the vendored copies, not to the default copies" do simulate_platform "arm64-darwin-23" do - necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.4.3", "stringio --version 3.1.0", "shellwords --version 0.2.0", "open3 --version 0.2.1"] + necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.4.3", "shellwords --version 0.2.0", "open3 --version 0.2.1"] + necessary_gems_in_bundle_path += ["stringio --version 3.1.0"] if Gem.ruby_version < Gem::Version.new("3.4.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) build_gem "baz", "1.0.0", to_system: true, default: true, &:add_c_extension From 669813a48dff0fbe44e96690d8e3ab35fc507e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 24 Jun 2025 18:12:29 +0200 Subject: [PATCH 053/130] [rubygems/rubygems] Realworld tsort gem should be no longer necessary https://github.com/rubygems/rubygems/commit/93d9b97182 --- spec/bundler/commands/clean_spec.rb | 2 +- spec/bundler/install/gems/standalone_spec.rb | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb index 51a89842620b11..9072531b882e07 100644 --- a/spec/bundler/commands/clean_spec.rb +++ b/spec/bundler/commands/clean_spec.rb @@ -634,7 +634,7 @@ def should_not_have_gems(*gems) s.executables = "irb" end - realworld_system_gems "tsort --version 0.1.0", "pathname --version 0.1.0", "set --version 1.0.1" + realworld_system_gems "pathname --version 0.1.0", "set --version 1.0.1" install_gemfile <<-G source "https://gem.repo2" diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb index d6128b41a91a5f..85d1ff17b4d397 100644 --- a/spec/bundler/install/gems/standalone_spec.rb +++ b/spec/bundler/install/gems/standalone_spec.rb @@ -140,11 +140,6 @@ end describe "with default gems and a lockfile", :ruby_repo do - before do - necessary_system_gems = ["tsort --version 0.1.0"] - realworld_system_gems(*necessary_system_gems) - end - it "works and points to the vendored copies, not to the default copies" do necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.4.3"] necessary_gems_in_bundle_path += ["stringio --version 3.1.0"] if Gem.ruby_version < Gem::Version.new("3.4.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") From 2fd1f4e6d9126b71eeab8d09c0e8725e6fbd302d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 24 Jun 2025 18:15:08 +0200 Subject: [PATCH 054/130] [rubygems/rubygems] Logger realworld gem is no longer necessary https://github.com/rubygems/rubygems/commit/6e6288a496 --- spec/bundler/install/gems/standalone_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb index 85d1ff17b4d397..ec502072171c9f 100644 --- a/spec/bundler/install/gems/standalone_spec.rb +++ b/spec/bundler/install/gems/standalone_spec.rb @@ -141,7 +141,7 @@ describe "with default gems and a lockfile", :ruby_repo do it "works and points to the vendored copies, not to the default copies" do - necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.4.3"] + necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "etc --version 1.4.3"] necessary_gems_in_bundle_path += ["stringio --version 3.1.0"] if Gem.ruby_version < Gem::Version.new("3.4.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) @@ -180,7 +180,7 @@ it "works for gems with extensions and points to the vendored copies, not to the default copies" do simulate_platform "arm64-darwin-23" do - necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "logger --version 1.4.3", "etc --version 1.4.3", "shellwords --version 0.2.0", "open3 --version 0.2.1"] + necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "etc --version 1.4.3", "shellwords --version 0.2.0", "open3 --version 0.2.1"] necessary_gems_in_bundle_path += ["stringio --version 3.1.0"] if Gem.ruby_version < Gem::Version.new("3.4.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) From b953d1134acd0f0685405d5163a8d3d8d630f22d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 24 Jun 2025 12:38:02 +0200 Subject: [PATCH 055/130] [rubygems/rubygems] Migrate `bundle viz` specs to run offline https://github.com/rubygems/rubygems/commit/672722cd4d --- spec/bundler/commands/viz_spec.rb | 4 ++-- spec/bundler/support/helpers.rb | 4 ++++ spec/bundler/support/path.rb | 6 +++++- tool/bundler/test_gems.rb | 1 + tool/bundler/test_gems.rb.lock | 6 ++++++ 5 files changed, 18 insertions(+), 3 deletions(-) diff --git a/spec/bundler/commands/viz_spec.rb b/spec/bundler/commands/viz_spec.rb index 446b416c102a28..0ad285104c4e38 100644 --- a/spec/bundler/commands/viz_spec.rb +++ b/spec/bundler/commands/viz_spec.rb @@ -2,7 +2,7 @@ RSpec.describe "bundle viz", if: Bundler.which("dot") do before do - realworld_system_gems "ruby-graphviz --version 1.2.5" + base_system_gems "rexml", "ruby-graphviz" end it "graphs gems from the Gemfile" do @@ -71,7 +71,7 @@ context "with another gem that has a graphviz file" do before do - build_repo4 do + update_repo4 do build_gem "graphviz", "999" do |s| s.write("lib/graphviz.rb", "abort 'wrong graphviz gem loaded'") end diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb index 2eb2ca1d2226a6..78360b7a5a83b4 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -303,6 +303,10 @@ def lock_gemfile(*args) bundle :lock, opts end + def base_system_gems(*names, **options) + system_gems names.map {|name| find_base_path(name) }, **options + end + def system_gems(*gems) gems = gems.flatten options = gems.last.is_a?(Hash) ? gems.pop : {} diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index f02e2468f74175..8fbac5cc5aafe2 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -285,7 +285,7 @@ def git_root end def rake_path - Dir["#{scoped_base_system_gem_path}/**/rake*.gem"].first + find_base_path("rake") end def rake_version @@ -308,6 +308,10 @@ def sinatra_dependency_paths private + def find_base_path(name) + Dir["#{scoped_base_system_gem_path}/**/#{name}-*.gem"].first + end + def git_ls_files(glob) skip "Not running on a git context, since running tests from a tarball" if ruby_core_tarball? diff --git a/tool/bundler/test_gems.rb b/tool/bundler/test_gems.rb index a8fd9dc6f8daf5..8f08f905b11cf6 100644 --- a/tool/bundler/test_gems.rb +++ b/tool/bundler/test_gems.rb @@ -11,3 +11,4 @@ gem "rb_sys" gem "fiddle" gem "rubygems-generate_index", "~> 1.1" +gem "ruby-graphviz" diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index 028de749f47f7b..44c39572b8e5c5 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -22,6 +22,9 @@ GEM rake-compiler-dock (1.9.1) rb_sys (0.9.111) rake-compiler-dock (= 1.9.1) + rexml (3.4.1) + ruby-graphviz (1.2.5) + rexml ruby2_keywords (0.0.5) rubygems-generate_index (1.1.3) compact_index (~> 0.15.0) @@ -50,6 +53,7 @@ DEPENDENCIES rack-test (~> 2.1) rake (~> 13.1) rb_sys + ruby-graphviz rubygems-generate_index (~> 1.1) sinatra (~> 4.1) @@ -67,6 +71,8 @@ CHECKSUMS rake (13.3.0) sha256=96f5092d786ff412c62fde76f793cc0541bd84d2eb579caa529aa8a059934493 rake-compiler-dock (1.9.1) sha256=e73720a29aba9c114728ce39cc0d8eef69ba61d88e7978c57bac171724cd4d53 rb_sys (0.9.111) sha256=65822fd8d57c248cd893db0efe01bc6edc15fcbea3ba6666091e35430c1cbaf0 + rexml (3.4.1) sha256=c74527a9a0a04b4ec31dbe0dc4ed6004b960af943d8db42e539edde3a871abca + ruby-graphviz (1.2.5) sha256=1c2bb44e3f6da9e2b829f5e7fd8d75a521485fb6b4d1fc66ff0f93f906121504 ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef rubygems-generate_index (1.1.3) sha256=3571424322666598e9586a906485e1543b617f87644913eaf137d986a3393f5c sinatra (4.1.1) sha256=4e997b859aa1b5d2e624f85d5b0fd0f0b3abc0da44daa6cbdf10f7c0da9f4d00 From eb979d998a373c5a7e87be5c7a63e3e0377f0af1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 10 Apr 2025 16:44:57 +0200 Subject: [PATCH 056/130] [rubygems/rubygems] Don't use currently configured value for default branch https://github.com/rubygems/rubygems/commit/1ae8533690 --- spec/bundler/install/git_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/bundler/install/git_spec.rb b/spec/bundler/install/git_spec.rb index 670bd1fb7275a3..f9d96e488f9dcd 100644 --- a/spec/bundler/install/git_spec.rb +++ b/spec/bundler/install/git_spec.rb @@ -28,14 +28,14 @@ end it "displays the correct default branch", git: ">= 2.28.0" do - build_git "foo", "1.0", path: lib_path("foo"), default_branch: "main" + build_git "foo", "1.0", path: lib_path("foo"), default_branch: "non-standard" install_gemfile <<-G, verbose: true source "https://gem.repo1" gem "foo", :git => "#{lib_path("foo")}" G - expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at main@#{revision_for(lib_path("foo"))[0..6]})") + expect(out).to include("Using foo 1.0 from #{lib_path("foo")} (at non-standard@#{revision_for(lib_path("foo"))[0..6]})") expect(the_bundle).to include_gems "foo 1.0", source: "git@#{lib_path("foo")}" end From e5d1720e06e6ee35bdc84cf78b60cbf171fa0207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 24 Jun 2025 17:26:46 +0200 Subject: [PATCH 057/130] [rubygems/rubygems] Spec about cleaning default gems executables no longer needs realworld gems https://github.com/rubygems/rubygems/commit/9cf284997b --- spec/bundler/commands/clean_spec.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/spec/bundler/commands/clean_spec.rb b/spec/bundler/commands/clean_spec.rb index 9072531b882e07..99a8f9c1416e5f 100644 --- a/spec/bundler/commands/clean_spec.rb +++ b/spec/bundler/commands/clean_spec.rb @@ -625,7 +625,7 @@ def should_not_have_gems(*gems) expect(out).to eq("1.0") end - it "when using --force, it doesn't remove default gem binaries", :realworld do + it "when using --force, it doesn't remove default gem binaries" do default_irb_version = ruby "gem 'irb', '< 999999'; require 'irb'; puts IRB::VERSION", raise_on_error: false skip "irb isn't a default gem" if default_irb_version.empty? @@ -634,8 +634,6 @@ def should_not_have_gems(*gems) s.executables = "irb" end - realworld_system_gems "pathname --version 0.1.0", "set --version 1.0.1" - install_gemfile <<-G source "https://gem.repo2" G From 66c67098259ef32b003d6b479fe7adc3e40d089f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 24 Jun 2025 18:55:22 +0200 Subject: [PATCH 058/130] [rubygems/rubygems] `bundle viz` deprecation specs don't actually need the gem installed The deprecation message is printed regardless. https://github.com/rubygems/rubygems/commit/397999a390 --- spec/bundler/other/major_deprecation_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index a0ecba21327515..a0a19582449473 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -574,9 +574,8 @@ end end - context "bundle viz", :realworld do + context "bundle viz" do before do - realworld_system_gems "ruby-graphviz --version 1.2.5" create_file "gems.rb", "source 'https://gem.repo1'" bundle "viz" end From fe44e9580874d6088c1d70c4266a8d2a70b9ef1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 25 Jun 2025 18:52:19 +0200 Subject: [PATCH 059/130] [rubygems/rubygems] Remove possibly incorrect spec I spent quite a while on this and I have not been able to reproduce or even understand how the original issue would happen. I also suspect it never actually reproduced the original problem properly. Since I'm in the middle of migrating all specs away from touching the network, I decided to remove it since I can't write an equivalent spec without being able to understand the original problem. https://github.com/rubygems/rubygems/commit/c9dfa20877 --- spec/bundler/runtime/inline_spec.rb | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/spec/bundler/runtime/inline_spec.rb b/spec/bundler/runtime/inline_spec.rb index 48385b452d8537..0467d8b14abdcb 100644 --- a/spec/bundler/runtime/inline_spec.rb +++ b/spec/bundler/runtime/inline_spec.rb @@ -590,29 +590,6 @@ def confirm(msg, newline = nil) expect(err).to be_empty end - it "when requiring fileutils after does not show redefinition warnings" do - Dir.mkdir tmp("path_without_gemfile") - - default_fileutils_version = ruby "gem 'fileutils', '< 999999'; require 'fileutils'; puts FileUtils::VERSION", raise_on_error: false - skip "fileutils isn't a default gem" if default_fileutils_version.empty? - - realworld_system_gems "fileutils --version 1.4.1" - - realworld_system_gems "pathname --version 0.2.0" - - script <<-RUBY, dir: tmp("path_without_gemfile"), env: { "BUNDLER_GEM_DEFAULT_DIR" => system_gem_path.to_s, "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s } - require "bundler/inline" - - gemfile(true) do - source "https://gem.repo2" - end - - require "fileutils" - RUBY - - expect(err).to eq("The Gemfile specifies no dependencies") - end - it "does not load default timeout" do default_timeout_version = ruby "gem 'timeout', '< 999999'; require 'timeout'; puts Timeout::VERSION", raise_on_error: false skip "timeout isn't a default gem" if default_timeout_version.empty? From 41c44ff8e60291a420e64a7ef6c697ca264e669b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 25 Jun 2025 18:59:13 +0200 Subject: [PATCH 060/130] [rubygems/rubygems] Realworld optparse gem should be no longer necessary Optparse was vendored a while ago. https://github.com/rubygems/rubygems/commit/d7afd43756 --- spec/bundler/install/gems/standalone_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb index ec502072171c9f..5c5d9ecf08040d 100644 --- a/spec/bundler/install/gems/standalone_spec.rb +++ b/spec/bundler/install/gems/standalone_spec.rb @@ -141,7 +141,7 @@ describe "with default gems and a lockfile", :ruby_repo do it "works and points to the vendored copies, not to the default copies" do - necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "etc --version 1.4.3"] + necessary_gems_in_bundle_path = ["psych --version 3.3.2", "etc --version 1.4.3"] necessary_gems_in_bundle_path += ["stringio --version 3.1.0"] if Gem.ruby_version < Gem::Version.new("3.4.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) @@ -180,7 +180,7 @@ it "works for gems with extensions and points to the vendored copies, not to the default copies" do simulate_platform "arm64-darwin-23" do - necessary_gems_in_bundle_path = ["optparse --version 0.1.1", "psych --version 3.3.2", "etc --version 1.4.3", "shellwords --version 0.2.0", "open3 --version 0.2.1"] + necessary_gems_in_bundle_path = ["psych --version 3.3.2", "etc --version 1.4.3", "shellwords --version 0.2.0", "open3 --version 0.2.1"] necessary_gems_in_bundle_path += ["stringio --version 3.1.0"] if Gem.ruby_version < Gem::Version.new("3.4.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) From 5386b6568f34915605843852e144f3cac6a89ba9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 26 Jun 2025 18:23:16 +0200 Subject: [PATCH 061/130] [rubygems/rubygems] Verify specs still using realworld gems still pass with latest versions https://github.com/rubygems/rubygems/commit/9da44ade24 --- spec/bundler/install/gems/standalone_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb index 5c5d9ecf08040d..c1a74f2fdb901e 100644 --- a/spec/bundler/install/gems/standalone_spec.rb +++ b/spec/bundler/install/gems/standalone_spec.rb @@ -141,8 +141,8 @@ describe "with default gems and a lockfile", :ruby_repo do it "works and points to the vendored copies, not to the default copies" do - necessary_gems_in_bundle_path = ["psych --version 3.3.2", "etc --version 1.4.3"] - necessary_gems_in_bundle_path += ["stringio --version 3.1.0"] if Gem.ruby_version < Gem::Version.new("3.4.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") + necessary_gems_in_bundle_path = ["psych --version 5.2.3", "etc --version 1.4.5"] + necessary_gems_in_bundle_path += ["stringio --version 3.1.6"] if Gem.ruby_version < Gem::Version.new("3.4.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) build_gem "foo", "1.0.0", to_system: true, default: true do |s| @@ -180,8 +180,8 @@ it "works for gems with extensions and points to the vendored copies, not to the default copies" do simulate_platform "arm64-darwin-23" do - necessary_gems_in_bundle_path = ["psych --version 3.3.2", "etc --version 1.4.3", "shellwords --version 0.2.0", "open3 --version 0.2.1"] - necessary_gems_in_bundle_path += ["stringio --version 3.1.0"] if Gem.ruby_version < Gem::Version.new("3.4.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") + necessary_gems_in_bundle_path = ["psych --version 5.2.3", "etc --version 1.4.5", "shellwords --version 0.2.2", "open3 --version 0.2.1"] + necessary_gems_in_bundle_path += ["stringio --version 3.1.6"] if Gem.ruby_version < Gem::Version.new("3.4.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) build_gem "baz", "1.0.0", to_system: true, default: true, &:add_c_extension From b5ef0114cd8f7fb5564024e56b56036e6e1a8c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 24 Jun 2025 18:56:18 +0200 Subject: [PATCH 062/130] [rubygems/rubygems] Migrate all remaining specs to run offline Also removed the helper to install real gems during specs to avoid the temptation of introducing network stuff again. https://github.com/rubygems/rubygems/commit/a1ab5e319a --- spec/bundler/install/gems/standalone_spec.rb | 10 +++----- spec/bundler/support/helpers.rb | 10 -------- tool/bundler/test_gems.rb | 4 +++ tool/bundler/test_gems.rb.lock | 26 ++++++++++++++++++++ 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb index c1a74f2fdb901e..c286a332baeaa2 100644 --- a/spec/bundler/install/gems/standalone_spec.rb +++ b/spec/bundler/install/gems/standalone_spec.rb @@ -141,9 +141,8 @@ describe "with default gems and a lockfile", :ruby_repo do it "works and points to the vendored copies, not to the default copies" do - necessary_gems_in_bundle_path = ["psych --version 5.2.3", "etc --version 1.4.5"] - necessary_gems_in_bundle_path += ["stringio --version 3.1.6"] if Gem.ruby_version < Gem::Version.new("3.4.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") - realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) + base_system_gems "psych", "etc", path: scoped_gem_path(bundled_app("bundle")) + base_system_gems "stringio", path: scoped_gem_path(bundled_app("bundle")) if Gem.ruby_version < Gem::Version.new("3.3.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") build_gem "foo", "1.0.0", to_system: true, default: true do |s| s.add_dependency "bar" @@ -180,9 +179,8 @@ it "works for gems with extensions and points to the vendored copies, not to the default copies" do simulate_platform "arm64-darwin-23" do - necessary_gems_in_bundle_path = ["psych --version 5.2.3", "etc --version 1.4.5", "shellwords --version 0.2.2", "open3 --version 0.2.1"] - necessary_gems_in_bundle_path += ["stringio --version 3.1.6"] if Gem.ruby_version < Gem::Version.new("3.4.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") - realworld_system_gems(*necessary_gems_in_bundle_path, path: scoped_gem_path(bundled_app("bundle"))) + base_system_gems "psych", "etc", "shellwords", "open3", path: scoped_gem_path(bundled_app("bundle")) + base_system_gems "stringio", path: scoped_gem_path(bundled_app("bundle")) if Gem.ruby_version < Gem::Version.new("3.3.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") build_gem "baz", "1.0.0", to_system: true, default: true, &:add_c_extension diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb index 78360b7a5a83b4..9127cf7838985f 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -402,16 +402,6 @@ def pristine_system_gems(*gems) system_gems(*gems) end - def realworld_system_gems(*gems) - gems = gems.flatten - opts = gems.last.is_a?(Hash) ? gems.pop : {} - path = opts.fetch(:path, system_gem_path) - - gems.each do |gem| - gem_command "install --no-document --verbose --install-dir #{path} #{gem}" - end - end - def cache_gems(*gems, gem_repo: gem_repo1) gems = gems.flatten diff --git a/tool/bundler/test_gems.rb b/tool/bundler/test_gems.rb index 8f08f905b11cf6..28113cd299d4f0 100644 --- a/tool/bundler/test_gems.rb +++ b/tool/bundler/test_gems.rb @@ -12,3 +12,7 @@ gem "fiddle" gem "rubygems-generate_index", "~> 1.1" gem "ruby-graphviz" +gem "psych" +gem "etc", platforms: [:ruby, :windows] +gem "open3" +gem "shellwords" diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index 44c39572b8e5c5..cbc682cd5c45e0 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -4,10 +4,21 @@ GEM base64 (0.2.0) builder (3.3.0) compact_index (0.15.0) + date (3.4.1) + date (3.4.1-java) + etc (1.4.5) fiddle (1.1.6) + jar-dependencies (0.5.5) logger (1.7.0) mustermann (3.0.3) ruby2_keywords (~> 0.0.1) + open3 (0.2.1) + psych (5.2.3) + date + stringio + psych (5.2.3-java) + date + jar-dependencies (>= 0.1.7) rack (3.1.15) rack-protection (4.1.1) base64 (>= 0.1.0) @@ -28,6 +39,7 @@ GEM ruby2_keywords (0.0.5) rubygems-generate_index (1.1.3) compact_index (~> 0.15.0) + shellwords (0.2.2) sinatra (4.1.1) logger (>= 1.6.0) mustermann (~> 3.0) @@ -35,6 +47,7 @@ GEM rack-protection (= 4.1.1) rack-session (>= 2.0.0, < 3) tilt (~> 2.0) + stringio (3.1.6) tilt (2.6.0) PLATFORMS @@ -48,22 +61,33 @@ PLATFORMS DEPENDENCIES builder (~> 3.2) compact_index (~> 0.15.0) + etc fiddle + open3 + psych rack (~> 3.1) rack-test (~> 2.1) rake (~> 13.1) rb_sys ruby-graphviz rubygems-generate_index (~> 1.1) + shellwords sinatra (~> 4.1) CHECKSUMS base64 (0.2.0) sha256=0f25e9b21a02a0cc0cea8ef92b2041035d39350946e8789c562b2d1a3da01507 builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f compact_index (0.15.0) sha256=5c6c404afca8928a7d9f4dde9524f6e1610db17e675330803055db282da84a8b + date (3.4.1) sha256=bf268e14ef7158009bfeaec40b5fa3c7271906e88b196d958a89d4b408abe64f + date (3.4.1-java) sha256=74740d914c65a922a15657c25ff0e203c16f1d0f7aa910a9ebed712afe9819c4 + etc (1.4.5) sha256=0d854e7b97a40390b048ba51230c30886931931b9dba955e85985d7d3bccf26c fiddle (1.1.6) sha256=79e8d909e602d979434cf9fccfa6e729cb16432bb00e39c7596abe6bee1249ab + jar-dependencies (0.5.5) sha256=2972b9fcba4b014e6446a84b5c09674a3e8648b95b71768e729f0e8e40568059 logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203 mustermann (3.0.3) sha256=d1f8e9ba2ddaed47150ddf81f6a7ea046826b64c672fbc92d83bce6b70657e88 + open3 (0.2.1) sha256=8e2d7d2113526351201438c1aa35c8139f0141c9e8913baa007c898973bf3952 + psych (5.2.3) sha256=84a54bb952d14604fea22d99938348814678782f58b12648fcdfa4d2fce859ee + psych (5.2.3-java) sha256=3e5425b9e8a2f41cc2707d5ef14fdc1ae908abbafb12fe45727bd63900056585 rack (3.1.15) sha256=d12b3e9960d18a26ded961250f2c0e3b375b49ff40dbe6786e9c3b160cbffca4 rack-protection (4.1.1) sha256=51a254a5d574a7f0ca4f0672025ce2a5ef7c8c3bd09c431349d683e825d7d16a rack-session (2.1.0) sha256=437c3916535b58ef71c816ce4a2dee0a01c8a52ae6077dc2b6cd19085760a290 @@ -75,7 +99,9 @@ CHECKSUMS ruby-graphviz (1.2.5) sha256=1c2bb44e3f6da9e2b829f5e7fd8d75a521485fb6b4d1fc66ff0f93f906121504 ruby2_keywords (0.0.5) sha256=ffd13740c573b7301cf7a2e61fc857b2a8e3d3aff32545d6f8300d8bae10e3ef rubygems-generate_index (1.1.3) sha256=3571424322666598e9586a906485e1543b617f87644913eaf137d986a3393f5c + shellwords (0.2.2) sha256=b8695a791de2f71472de5abdc3f4332f6535a4177f55d8f99e7e44266cd32f94 sinatra (4.1.1) sha256=4e997b859aa1b5d2e624f85d5b0fd0f0b3abc0da44daa6cbdf10f7c0da9f4d00 + stringio (3.1.6) sha256=292c495d1657adfcdf0a32eecf12a60e6691317a500c3112ad3b2e31068274f5 tilt (2.6.0) sha256=263d748466e0d83e510aa1a2e2281eff547937f0ef06be33d3632721e255f76b BUNDLED WITH From daedebd64ade4b7535432cb5b7df6061e796b085 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Fri, 27 Jun 2025 12:16:31 +0200 Subject: [PATCH 063/130] [rubygems/rubygems] Remove `print_only_version_number` setting I don't think it makes sense to make this tiny behavior change configurable. If someone wants to parse version output, and we have a public setting, they are going to need to accommodate their regexps to both values of the setting. In addition to this, I plan to enhance version output with a note about "simulated version", and in that case, "print_only_version_number" would no longer hold, since what we print will be more than that anyways. So, I'd like to remove the setting and change the output in Bundler 4 with no way to opt out. https://github.com/rubygems/rubygems/commit/d84e9dcf09 --- lib/bundler/cli.rb | 2 +- lib/bundler/feature_flag.rb | 1 - lib/bundler/man/bundle-config.1 | 3 --- lib/bundler/man/bundle-config.1.ronn | 2 -- lib/bundler/settings.rb | 1 - 5 files changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index f16c0a0386f2f5..ae7bf271e8b862 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -489,7 +489,7 @@ def version build_info = " (#{BuildMetadata.built_at} commit #{BuildMetadata.git_commit_sha})" end - if !cli_help && Bundler.feature_flag.print_only_version_number? + if !cli_help && Bundler.feature_flag.bundler_4_mode? Bundler.ui.info "#{Bundler::VERSION}#{build_info}" else Bundler.ui.info "Bundler version #{Bundler::VERSION}#{build_info}" diff --git a/lib/bundler/feature_flag.rb b/lib/bundler/feature_flag.rb index 2267dc3ee04601..b36d7e73e94d95 100644 --- a/lib/bundler/feature_flag.rb +++ b/lib/bundler/feature_flag.rb @@ -36,7 +36,6 @@ def self.settings_method(name, key, &default) settings_flag(:lockfile_checksums) { bundler_4_mode? } settings_flag(:path_relative_to_cwd) { bundler_4_mode? } settings_flag(:plugins) { @bundler_version >= Gem::Version.new("1.14") } - settings_flag(:print_only_version_number) { bundler_4_mode? } settings_flag(:setup_makes_kernel_gem_public) { !bundler_4_mode? } settings_flag(:update_requires_all_flag) { bundler_5_mode? } diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 80b1be904eb3cc..58b107151766c0 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -161,9 +161,6 @@ Enable Bundler's experimental plugin system\. \fBprefer_patch\fR (BUNDLE_PREFER_PATCH) Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\. .TP -\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR) -Print only version number from \fBbundler \-\-version\fR\. -.TP \fBredirect\fR (\fBBUNDLE_REDIRECT\fR) The number of redirects allowed for network requests\. Defaults to \fB5\fR\. .TP diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index b36288c23bf407..deeae2683bc7e5 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -177,8 +177,6 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). Enable Bundler's experimental plugin system. * `prefer_patch` (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates. Makes `bundle update` calls equivalent to `bundler update --patch`. -* `print_only_version_number` (`BUNDLE_PRINT_ONLY_VERSION_NUMBER`): - Print only version number from `bundler --version`. * `redirect` (`BUNDLE_REDIRECT`): The number of redirects allowed for network requests. Defaults to `5`. * `retry` (`BUNDLE_RETRY`): diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index 8e92ca88d3fd6f..0737823a1305b2 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -40,7 +40,6 @@ class Settings path.system plugins prefer_patch - print_only_version_number setup_makes_kernel_gem_public silence_deprecations silence_root_warning From cac7644bdb28ca96df269ddd508a2ae056d9898f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Fri, 27 Jun 2025 12:23:17 +0200 Subject: [PATCH 064/130] [rubygems/rubygems] Document the `simulate_version` setting https://github.com/rubygems/rubygems/commit/1ffd83f6c2 --- lib/bundler/man/bundle-config.1 | 3 +++ lib/bundler/man/bundle-config.1.ronn | 4 ++++ lib/bundler/settings.rb | 1 + 3 files changed, 8 insertions(+) diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 58b107151766c0..67b77d8ec59307 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -179,6 +179,9 @@ Whether Bundler should silence deprecation warnings for behavior that will be ch \fBsilence_root_warning\fR (\fBBUNDLE_SILENCE_ROOT_WARNING\fR) Silence the warning Bundler prints when installing gems as root\. .TP +\fBsimulate_version\fR (\fBBUNDLE_SIMULATE_VERSION\fR) +The virtual version Bundler should use for activating feature flags\. Can be used to simulate all the new functionality that will be enabled in a future major version\. +.TP \fBssl_ca_cert\fR (\fBBUNDLE_SSL_CA_CERT\fR) Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format\. .TP diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index deeae2683bc7e5..08f61a6351df2c 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -192,6 +192,10 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). be changed in the next major version. * `silence_root_warning` (`BUNDLE_SILENCE_ROOT_WARNING`): Silence the warning Bundler prints when installing gems as root. +* `simulate_version` (`BUNDLE_SIMULATE_VERSION`): + The virtual version Bundler should use for activating feature flags. Can be + used to simulate all the new functionality that will be enabled in a future + major version. * `ssl_ca_cert` (`BUNDLE_SSL_CA_CERT`): Path to a designated CA certificate file or folder containing multiple certificates for trusted CAs in PEM format. diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index 0737823a1305b2..104a6f72ba595e 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -86,6 +86,7 @@ class Settings gemfile path shebang + simulate_version system_bindir trust-policy version From efd8b6d2015fdd83fa3e831b9ec7fc3e5b37b80b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Fri, 27 Jun 2025 12:24:15 +0200 Subject: [PATCH 065/130] [rubygems/rubygems] Use explicit receiver when accessing settings We have a quality spec that parses all code for explicit usages of `Bundler.settings[`, to detect undocumented settings. So using `Bundler.settings` consistently will help catching these things. https://github.com/rubygems/rubygems/commit/ce01bb7cc5 --- lib/bundler.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/bundler.rb b/lib/bundler.rb index 904dcb84671ea0..d3219fe46e52ee 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -113,13 +113,13 @@ def create_bundle_path end def configured_bundle_path - @configured_bundle_path ||= settings.path.tap(&:validate!) + @configured_bundle_path ||= Bundler.settings.path.tap(&:validate!) end # Returns absolute location of where binstubs are installed to. def bin_path @bin_path ||= begin - path = settings[:bin] || "bin" + path = Bundler.settings[:bin] || "bin" path = Pathname.new(path).expand_path(root).expand_path mkdir_p(path) path @@ -180,7 +180,7 @@ def auto_switch # should be called first, before you instantiate anything like an # `Installer` that'll keep a reference to the old one instead. def auto_install - return unless settings[:auto_install] + return unless Bundler.settings[:auto_install] begin definition.specs @@ -238,10 +238,10 @@ def definition(unlock = nil, lockfile = default_lockfile) end def frozen_bundle? - frozen = settings[:frozen] + frozen = Bundler.settings[:frozen] return frozen unless frozen.nil? - settings[:deployment] + Bundler.settings[:deployment] end def locked_gems @@ -342,7 +342,7 @@ def app_config_path def app_cache(custom_path = nil) path = custom_path || root - Pathname.new(path).join(settings.app_cache_path) + Pathname.new(path).join(Bundler.settings.app_cache_path) end def tmp(name = Process.pid.to_s) @@ -454,7 +454,7 @@ def unbundled_exec(*args) end def local_platform - return Gem::Platform::RUBY if settings[:force_ruby_platform] + return Gem::Platform::RUBY if Bundler.settings[:force_ruby_platform] Gem::Platform.local end @@ -480,11 +480,11 @@ def system_bindir # install binstubs there instead. Unfortunately, RubyGems doesn't expose # that directory at all, so rather than parse .gemrc ourselves, we allow # the directory to be set as well, via `bundle config set --local bindir foo`. - settings[:system_bindir] || Bundler.rubygems.gem_bindir + Bundler.settings[:system_bindir] || Bundler.rubygems.gem_bindir end def preferred_gemfile_name - settings[:init_gems_rb] ? "gems.rb" : "Gemfile" + Bundler.settings[:init_gems_rb] ? "gems.rb" : "Gemfile" end def use_system_gems? @@ -567,7 +567,7 @@ def git_present? end def feature_flag - @feature_flag ||= FeatureFlag.new(settings[:simulate_version] || VERSION) + @feature_flag ||= FeatureFlag.new(Bundler.settings[:simulate_version] || VERSION) end def reset! From c6b6b4f52c9b3417042298fa37bd5f1ca25508ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Fri, 27 Jun 2025 12:31:52 +0200 Subject: [PATCH 066/130] [rubygems/rubygems] Remove duplicated spec https://github.com/rubygems/rubygems/commit/66aabcc9f3 --- spec/bundler/bundler/cli_spec.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index 03edc1c81ede34..2e7e02ce792db7 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -122,11 +122,6 @@ def out_with_macos_man_workaround install_gemfile "source 'https://gem.repo1'", verbose: true expect(out).to start_with("Running `bundle install --verbose` with bundler #{Bundler::VERSION}") end - - it "doesn't print defaults" do - install_gemfile "source 'https://gem.repo1'", verbose: true - expect(out).to start_with("Running `bundle install --verbose` with bundler #{Bundler::VERSION}") - end end describe "bundle outdated" do From 0e1ca4962f6346fb3c41ce0988d855ac68d49625 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Fri, 27 Jun 2025 12:34:20 +0200 Subject: [PATCH 067/130] [rubygems/rubygems] None of the global options have default so this seems unnecessary https://github.com/rubygems/rubygems/commit/bea87eab0b --- lib/bundler/cli.rb | 7 +------ spec/bundler/bundler/cli_spec.rb | 4 +--- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index ae7bf271e8b862..c2c6b091676a0b 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -714,12 +714,7 @@ def print_command command_name = cmd.name return if PARSEABLE_COMMANDS.include?(command_name) command = ["bundle", command_name] + args - options_to_print = options.dup - options_to_print.delete_if do |k, v| - next unless o = cmd.options[k] - o.default == v - end - command << Thor::Options.to_switches(options_to_print.sort_by(&:first)).strip + command << Thor::Options.to_switches(options.sort_by(&:first)).strip command.reject!(&:empty?) Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler::VERSION}" end diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index 2e7e02ce792db7..c69229cc693018 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -116,10 +116,8 @@ def out_with_macos_man_workaround gemfile "source 'https://gem.repo1'" bundle "info bundler", verbose: true expect(out).to start_with("Running `bundle info bundler --verbose` with bundler #{Bundler::VERSION}") - end - it "doesn't print defaults" do - install_gemfile "source 'https://gem.repo1'", verbose: true + bundle "install", verbose: true expect(out).to start_with("Running `bundle install --verbose` with bundler #{Bundler::VERSION}") end end From 82692b32c1d67185e80ba40379e413fc377097f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Fri, 27 Jun 2025 12:38:25 +0200 Subject: [PATCH 068/130] [rubygems/rubygems] Log when `simulate_version` is enabled Tweak version output and verbose mode to be transparent about Bundler simulating a different version than the real one. https://github.com/rubygems/rubygems/commit/179354d153 --- lib/bundler/cli.rb | 6 +++--- lib/bundler/version.rb | 8 ++++++++ spec/bundler/bundler/cli_spec.rb | 16 ++++++++++++---- spec/bundler/commands/version_spec.rb | 21 +++++++++------------ 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index c2c6b091676a0b..67d1dc26c4d02e 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -490,9 +490,9 @@ def version end if !cli_help && Bundler.feature_flag.bundler_4_mode? - Bundler.ui.info "#{Bundler::VERSION}#{build_info}" + Bundler.ui.info "#{Bundler.verbose_version}#{build_info}" else - Bundler.ui.info "Bundler version #{Bundler::VERSION}#{build_info}" + Bundler.ui.info "Bundler version #{Bundler.verbose_version}#{build_info}" end end @@ -716,7 +716,7 @@ def print_command command = ["bundle", command_name] + args command << Thor::Options.to_switches(options.sort_by(&:first)).strip command.reject!(&:empty?) - Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler::VERSION}" + Bundler.ui.info "Running `#{command * " "}` with bundler #{Bundler.verbose_version}" end def warn_on_outdated_bundler diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index 7a8fb214c75410..f2e654b08a5b7c 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -10,4 +10,12 @@ def self.bundler_major_version def self.gem_version @gem_version ||= Gem::Version.create(VERSION) end + + def self.verbose_version + @verbose_version ||= "#{VERSION}#{simulated_version ? " (simulating Bundler #{simulated_version})" : ""}" + end + + def self.simulated_version + @simulated_version ||= Bundler.settings[:simulate_version] + end end diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index c69229cc693018..67674173a5f55b 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -112,14 +112,23 @@ def out_with_macos_man_workaround end context "with --verbose" do - it "prints the running command" do + before do gemfile "source 'https://gem.repo1'" + end + + it "prints the running command" do bundle "info bundler", verbose: true expect(out).to start_with("Running `bundle info bundler --verbose` with bundler #{Bundler::VERSION}") bundle "install", verbose: true expect(out).to start_with("Running `bundle install --verbose` with bundler #{Bundler::VERSION}") end + + it "prints the simulated version too when setting is enabled" do + bundle "config simulate_version 4", verbose: true + bundle "info bundler", verbose: true + expect(out).to start_with("Running `bundle info bundler --verbose` with bundler #{Bundler::VERSION} (simulating Bundler 4)") + end end describe "bundle outdated" do @@ -246,10 +255,9 @@ def out_with_macos_man_workaround it "shows the bundler version just as the `bundle` executable does" do bundler "--version" expect(out).to eq("Bundler version #{Bundler::VERSION}") - end - it "shows the bundler version just as the `bundle` executable does", bundler: "4" do + bundle "config simulate_version 4" bundler "--version" - expect(out).to eq(Bundler::VERSION) + expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 4)") end end diff --git a/spec/bundler/commands/version_spec.rb b/spec/bundler/commands/version_spec.rb index 556e77e01f7cf6..d655e760b5e3f8 100644 --- a/spec/bundler/commands/version_spec.rb +++ b/spec/bundler/commands/version_spec.rb @@ -10,38 +10,35 @@ end context "with -v" do - it "outputs the version" do + it "outputs the version and virtual version if set" do bundle "-v" expect(out).to eq("Bundler version #{Bundler::VERSION}") - end - it "outputs the version", bundler: "4" do + bundle "config simulate_version 4" bundle "-v" - expect(out).to eq(Bundler::VERSION) + expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 4)") end end context "with --version" do - it "outputs the version" do + it "outputs the version and virtual version if set" do bundle "--version" expect(out).to eq("Bundler version #{Bundler::VERSION}") - end - it "outputs the version", bundler: "4" do + bundle "config simulate_version 4" bundle "--version" - expect(out).to eq(Bundler::VERSION) + expect(out).to eq("#{Bundler::VERSION} (simulating Bundler 4)") end end context "with version" do - it "outputs the version with build metadata" do + it "outputs the version, virtual version if set, and build metadata" do bundle "version" expect(out).to match(/\ABundler version #{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) - end - it "outputs the version with build metadata", bundler: "4" do + bundle "config simulate_version 4" bundle "version" - expect(out).to match(/\A#{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + expect(out).to match(/\A#{Regexp.escape(Bundler::VERSION)} \(simulating Bundler 4\) \(\d{4}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) end end end From 098b0cd7be860fb975ea348f27a8ab0932563e10 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Sat, 28 Jun 2025 00:04:46 +0200 Subject: [PATCH 069/130] [rubygems/rubygems] Update man pages for the `bundle doctor ssl` subcommand: - ### Problem The man pages for `bundle doctor` which shows up when running `bundle doctor --help` are no longer in sync with the CLI. ### Context In #8624, we introduced a change that modifies the structure of the `bundle doctor` command. The change added a new subcommand as well a new flag option `bundle doctor --ssl` Bundler uses man pages to display help of Thor commands, those man pages are indepedent from Thor options and need to be kept in sync. ### Solution Updated the man page for `bundle doctor`. Now that this command is a subcommand composed of `bundle doctor diagnose` (the default) , and `bundle doctor ssl`, I modified the man page to follow the same markdown structure as other subcommands such as [bundle plugin](https://github.com/rubygems/rubygems/blob/a902381660f8d17b5c4a93226678c23e046f464f/bundler/lib/bundler/man/bundle-plugin.1.ronn) https://github.com/rubygems/rubygems/commit/de047f1458 --- lib/bundler/man/bundle-doctor.1 | 45 ++++++++++++++++++++++-- lib/bundler/man/bundle-doctor.1.ronn | 52 +++++++++++++++++++++++++--- 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/lib/bundler/man/bundle-doctor.1 b/lib/bundler/man/bundle-doctor.1 index 0cf01e02e93947..433f43b1009a30 100644 --- a/lib/bundler/man/bundle-doctor.1 +++ b/lib/bundler/man/bundle-doctor.1 @@ -4,11 +4,18 @@ .SH "NAME" \fBbundle\-doctor\fR \- Checks the bundle for common problems .SH "SYNOPSIS" -\fBbundle doctor\fR [\-\-quiet] [\-\-gemfile=GEMFILE] +\fBbundle doctor [diagnose]\fR [\-\-quiet] [\-\-gemfile=GEMFILE] [\-\-ssl] +.br +\fBbundle doctor ssl\fR [\-\-host=HOST] [\-\-tls\-version=VERSION] [\-\-verify\-mode=MODE] +.br +\fBbundle doctor\fR help [COMMAND] .SH "DESCRIPTION" +You can diagnose common Bundler problems with this command such as checking gem environment or SSL/TLS issue\. +.SH "SUB\-COMMANDS" +.SS "diagnose (default command)" Checks your Gemfile and gem environment for common problems\. If issues are detected, Bundler prints them and exits status 1\. Otherwise, Bundler prints a success message and exits status 0\. .P -Examples of common problems caught by bundle\-doctor include: +Examples of common problems caught include: .IP "\(bu" 4 Invalid Bundler settings .IP "\(bu" 4 @@ -20,11 +27,43 @@ Uninstalled gems .IP "\(bu" 4 Missing dependencies .IP "" 0 -.SH "OPTIONS" +.P +\fBOPTIONS\fR .TP \fB\-\-quiet\fR Only output warnings and errors\. .TP \fB\-\-gemfile=GEMFILE\fR The location of the Gemfile(5) which Bundler should use\. This defaults to a Gemfile(5) in the current working directory\. In general, Bundler will assume that the location of the Gemfile(5) is also the project's root and will try to find \fBGemfile\.lock\fR and \fBvendor/cache\fR relative to this location\. +.TP +\fB\-\-ssl\fR +Diagnose common SSL problems when connecting to https://rubygems\.org\. +.IP +This flag runs the \fBbundle doctor ssl\fR subcommand with default values underneath\. +.SS "ssl" +If you've experienced issues related to SSL certificates and/or TLS versions while connecting to https://rubygems\.org, this command can help troubleshoot common problems\. The diagnostic will perform a few checks such as: +.IP "\(bu" 4 +Verify the Ruby OpenSSL version installed on your system\. +.IP "\(bu" 4 +Check the OpenSSL library version used for compilation\. +.IP "\(bu" 4 +Ensure CA certificates are correctly setup on your machine\. +.IP "\(bu" 4 +Open a TLS connection and verify the outcome\. +.IP "" 0 +.P +\fBOPTIONS\fR +.TP +\fB\-\-host=HOST\fR +Perform the diagnostic on HOST\. Defaults to \fBrubygems\.org\fR\. +.TP +\fB\-\-tls\-version=VERSION\fR +Specify the TLS version when opening the connection to HOST\. +.IP +Accepted values are: \fB1\.1\fR or \fB1\.2\fR\. +.TP +\fB\-\-verify\-mode=MODE\fR +Specify the TLS verify mode when opening the connection to HOST\. Defaults to \fBSSL_VERIFY_PEER\fR\. +.IP +Accepted values are: \fBCLIENT_ONCE\fR, \fBFAIL_IF_NO_PEER_CERT\fR, \fBNONE\fR, \fBPEER\fR\. diff --git a/lib/bundler/man/bundle-doctor.1.ronn b/lib/bundler/man/bundle-doctor.1.ronn index 5970f6188b1221..7e8a21b1c58968 100644 --- a/lib/bundler/man/bundle-doctor.1.ronn +++ b/lib/bundler/man/bundle-doctor.1.ronn @@ -3,16 +3,27 @@ bundle-doctor(1) -- Checks the bundle for common problems ## SYNOPSIS -`bundle doctor` [--quiet] - [--gemfile=GEMFILE] +`bundle doctor [diagnose]` [--quiet] + [--gemfile=GEMFILE] + [--ssl]
+`bundle doctor ssl` [--host=HOST] + [--tls-version=VERSION] + [--verify-mode=MODE]
+`bundle doctor` help [COMMAND] ## DESCRIPTION +You can diagnose common Bundler problems with this command such as checking gem environment or SSL/TLS issue. + +## SUB-COMMANDS + +### diagnose (default command) + Checks your Gemfile and gem environment for common problems. If issues are detected, Bundler prints them and exits status 1. Otherwise, Bundler prints a success message and exits status 0. -Examples of common problems caught by bundle-doctor include: +Examples of common problems caught include: * Invalid Bundler settings * Mismatched Ruby versions @@ -20,7 +31,7 @@ Examples of common problems caught by bundle-doctor include: * Uninstalled gems * Missing dependencies -## OPTIONS +**OPTIONS** * `--quiet`: Only output warnings and errors. @@ -31,3 +42,36 @@ Examples of common problems caught by bundle-doctor include: will assume that the location of the Gemfile(5) is also the project's root and will try to find `Gemfile.lock` and `vendor/cache` relative to this location. + +* `--ssl`: + Diagnose common SSL problems when connecting to https://rubygems.org. + + This flag runs the `bundle doctor ssl` subcommand with default values + underneath. + +### ssl + +If you've experienced issues related to SSL certificates and/or TLS versions while connecting +to https://rubygems.org, this command can help troubleshoot common problems. +The diagnostic will perform a few checks such as: + +* Verify the Ruby OpenSSL version installed on your system. +* Check the OpenSSL library version used for compilation. +* Ensure CA certificates are correctly setup on your machine. +* Open a TLS connection and verify the outcome. + +**OPTIONS** + +* `--host=HOST`: + Perform the diagnostic on HOST. Defaults to `rubygems.org`. + +* `--tls-version=VERSION`: + Specify the TLS version when opening the connection to HOST. + + Accepted values are: `1.1` or `1.2`. + +* `--verify-mode=MODE`: + Specify the TLS verify mode when opening the connection to HOST. + Defaults to `SSL_VERIFY_PEER`. + + Accepted values are: `CLIENT_ONCE`, `FAIL_IF_NO_PEER_CERT`, `NONE`, `PEER`. From 560edfc8f752a124960fcc346d5d2bfe82b91b7f Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Mon, 30 Jun 2025 13:50:09 +0200 Subject: [PATCH 070/130] [rubygems/rubygems] Fix `bundle console` printing bug report template on `NameError` during require Followup to https://github.com/rubygems/rubygems/pull/8436 It fixed showing the template when requiring a non-existant file but user code can do much more than just trying to require other code. I encountered this particular case because of load order issues, where a library wasn't able to properly require itself when loaded before some other library. https://github.com/rubygems/rubygems/commit/1c910e5afe --- lib/bundler/runtime.rb | 2 +- spec/bundler/commands/console_spec.rb | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/bundler/runtime.rb b/lib/bundler/runtime.rb index 444b085bed214b..9b2416402bb74c 100644 --- a/lib/bundler/runtime.rb +++ b/lib/bundler/runtime.rb @@ -71,7 +71,7 @@ def require(*groups) raise Bundler::GemRequireError.new e, "There was an error while trying to load the gem '#{file}'." end - rescue RuntimeError => e + rescue StandardError => e raise Bundler::GemRequireError.new e, "There was an error while trying to load the gem '#{file}'." end diff --git a/spec/bundler/commands/console_spec.rb b/spec/bundler/commands/console_spec.rb index dbfbec874f408d..ec44fe59f3a3f2 100644 --- a/spec/bundler/commands/console_spec.rb +++ b/spec/bundler/commands/console_spec.rb @@ -40,7 +40,7 @@ def __pry__ end end - context "when the library has an unrelated error" do + context "when the library requires a non-existent file" do before do build_lib "loadfuuu", "1.0.0" do |s| s.write "lib/loadfuuu.rb", "require_relative 'loadfuuu/bar'" @@ -65,6 +65,30 @@ def __pry__ end end + context "when the library references a non-existent constant" do + before do + build_lib "loadfuuu", "1.0.0" do |s| + s.write "lib/loadfuuu.rb", "Some::NonExistent::Constant" + end + + install_gemfile <<-G + source "https://gem.repo2" + gem "irb" + path "#{lib_path}" do + gem "loadfuuu", require: true + end + G + end + + it "does not show the bug report template" do + bundle("console", raise_on_error: false) do |input, _, _| + input.puts("exit") + end + + expect(err).not_to include("ERROR REPORT TEMPLATE") + end + end + context "when the library does not have any errors" do before do install_gemfile <<-G From 70da38510fbc33d2c1844a70a633eec336516690 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 15:47:47 +0000 Subject: [PATCH 071/130] [rubygems/rubygems] Bump the rb-sys group across 2 directories with 1 update Bumps the rb-sys group with 1 update in the /test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib directory: [rb-sys](https://github.com/oxidize-rb/rb-sys). Bumps the rb-sys group with 1 update in the /test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example directory: [rb-sys](https://github.com/oxidize-rb/rb-sys). Updates `rb-sys` from 0.9.115 to 0.9.116 - [Release notes](https://github.com/oxidize-rb/rb-sys/releases) - [Commits](https://github.com/oxidize-rb/rb-sys/compare/v0.9.115...v0.9.116) Updates `rb-sys` from 0.9.115 to 0.9.116 - [Release notes](https://github.com/oxidize-rb/rb-sys/releases) - [Commits](https://github.com/oxidize-rb/rb-sys/compare/v0.9.115...v0.9.116) --- updated-dependencies: - dependency-name: rb-sys dependency-version: 0.9.116 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: rb-sys - dependency-name: rb-sys dependency-version: 0.9.116 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: rb-sys ... Signed-off-by: dependabot[bot] https://github.com/rubygems/rubygems/commit/dbb7447901 --- .../custom_name/ext/custom_name_lib/Cargo.lock | 8 ++++---- .../custom_name/ext/custom_name_lib/Cargo.toml | 2 +- .../rust_ruby_example/Cargo.lock | 8 ++++---- .../rust_ruby_example/Cargo.toml | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock index 84c35ff07485bb..4851de09d075b2 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.lock @@ -152,18 +152,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.115" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ca6726be0eca74687047fed7dcbc2d509571f3962e190c343ac1eb40e482b3" +checksum = "7059846f68396df83155779c75336ca24567741cb95256e6308c9fcc370e8dad" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.115" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2390cfc87b7513656656faad6567291e581542d3ec41dd0a2bf381896e0880" +checksum = "ac217510df41b9ffc041573e68d7a02aaff770c49943c7494441c4b224b0ecd0" dependencies = [ "bindgen", "lazy_static", diff --git a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml index 876dbfb23d8df7..7cb12fa8a6c16c 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/custom_name/ext/custom_name_lib/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.115" +rb-sys = "0.9.116" diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock index 767c24a1bf300a..9740b435e7bb8f 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock +++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.lock @@ -145,18 +145,18 @@ dependencies = [ [[package]] name = "rb-sys" -version = "0.9.115" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ca6726be0eca74687047fed7dcbc2d509571f3962e190c343ac1eb40e482b3" +checksum = "7059846f68396df83155779c75336ca24567741cb95256e6308c9fcc370e8dad" dependencies = [ "rb-sys-build", ] [[package]] name = "rb-sys-build" -version = "0.9.115" +version = "0.9.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f2390cfc87b7513656656faad6567291e581542d3ec41dd0a2bf381896e0880" +checksum = "ac217510df41b9ffc041573e68d7a02aaff770c49943c7494441c4b224b0ecd0" dependencies = [ "bindgen", "lazy_static", diff --git a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml index 4ed446c4efacd7..b389cff5425e5e 100644 --- a/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml +++ b/test/rubygems/test_gem_ext_cargo_builder/rust_ruby_example/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" crate-type = ["cdylib"] [dependencies] -rb-sys = "0.9.115" +rb-sys = "0.9.116" From 29ceefe5955a6a012cf24679c5f8326739c41c9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 26 Jun 2025 21:50:40 +0200 Subject: [PATCH 072/130] [rubygems/rubygems] Consistently access CLI flags with symbols https://github.com/rubygems/rubygems/commit/1497d3f146 --- lib/bundler/cli.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 67d1dc26c4d02e..3f71fa2c3a8dae 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -77,7 +77,7 @@ def initialize(*args) self.options ||= {} unprinted_warnings = Bundler.ui.unprinted_warnings Bundler.ui = UI::Shell.new(options) - Bundler.ui.level = "debug" if options["verbose"] + Bundler.ui.level = "debug" if options[:verbose] unprinted_warnings.each {|w| Bundler.ui.warn(w) } end From 7f057e13e0f5721b9bb3c9b1561aabfc1a9582dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 26 Jun 2025 21:51:11 +0200 Subject: [PATCH 073/130] [rubygems/rubygems] Add a `verbose` setting to enable verbose output for all commands https://github.com/rubygems/rubygems/commit/0aa1be946f --- lib/bundler/cli.rb | 2 +- lib/bundler/man/bundle-config.1 | 3 +++ lib/bundler/man/bundle-config.1.ronn | 3 +++ lib/bundler/settings.rb | 1 + spec/bundler/bundler/cli_spec.rb | 12 ++++++++++++ 5 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 3f71fa2c3a8dae..25e442c04f6f3c 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -77,7 +77,7 @@ def initialize(*args) self.options ||= {} unprinted_warnings = Bundler.ui.unprinted_warnings Bundler.ui = UI::Shell.new(options) - Bundler.ui.level = "debug" if options[:verbose] + Bundler.ui.level = "debug" if options[:verbose] || Bundler.settings[:verbose] unprinted_warnings.each {|w| Bundler.ui.warn(w) } end diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 67b77d8ec59307..a77e4ac2644139 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -203,6 +203,9 @@ Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be u \fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR) The custom user agent fragment Bundler includes in API requests\. .TP +\fBverbose\fR (\fBBUNDLE_VERBOSE\fR) +Whether Bundler should print verbose output\. Defaults to \fBfalse\fR, unless the \fB\-\-verbose\fR CLI flag is used\. +.TP \fBversion\fR (\fBBUNDLE_VERSION\fR) The version of Bundler to use when running under Bundler environment\. Defaults to \fBlockfile\fR\. You can also specify \fBsystem\fR or \fBx\.y\.z\fR\. \fBlockfile\fR will use the Bundler version specified in the \fBGemfile\.lock\fR, \fBsystem\fR will use the system version of Bundler, and \fBx\.y\.z\fR will use the specified version of Bundler\. .TP diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index 08f61a6351df2c..2386fe9dcdfb75 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -214,6 +214,9 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html). and disallow passing no options to `bundle update`. * `user_agent` (`BUNDLE_USER_AGENT`): The custom user agent fragment Bundler includes in API requests. +* `verbose` (`BUNDLE_VERBOSE`): + Whether Bundler should print verbose output. Defaults to `false`, unless the + `--verbose` CLI flag is used. * `version` (`BUNDLE_VERSION`): The version of Bundler to use when running under Bundler environment. Defaults to `lockfile`. You can also specify `system` or `x.y.z`. diff --git a/lib/bundler/settings.rb b/lib/bundler/settings.rb index 104a6f72ba595e..6f6bb59747cfb0 100644 --- a/lib/bundler/settings.rb +++ b/lib/bundler/settings.rb @@ -44,6 +44,7 @@ class Settings silence_deprecations silence_root_warning update_requires_all_flag + verbose ].freeze REMEMBERED_KEYS = %w[ diff --git a/spec/bundler/bundler/cli_spec.rb b/spec/bundler/bundler/cli_spec.rb index 67674173a5f55b..41cd8c636d4041 100644 --- a/spec/bundler/bundler/cli_spec.rb +++ b/spec/bundler/bundler/cli_spec.rb @@ -131,6 +131,18 @@ def out_with_macos_man_workaround end end + context "with verbose configuration" do + before do + bundle "config verbose true" + end + + it "prints the running command" do + gemfile "source 'https://gem.repo1'" + bundle "info bundler" + expect(out).to start_with("Running `bundle info bundler` with bundler #{Bundler::VERSION}") + end + end + describe "bundle outdated" do let(:run_command) do bundle "install" From 75cdf696e76a786c523550886c283f378acff357 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 17 Jun 2025 19:58:50 +0200 Subject: [PATCH 074/130] [rubygems/rubygems] Fix typos https://github.com/rubygems/rubygems/commit/2a2317249f --- spec/bundler/bundler/fetcher/downloader_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/bundler/bundler/fetcher/downloader_spec.rb b/spec/bundler/bundler/fetcher/downloader_spec.rb index 6164025ac61c16..40ae7109abe9a9 100644 --- a/spec/bundler/bundler/fetcher/downloader_spec.rb +++ b/spec/bundler/bundler/fetcher/downloader_spec.rb @@ -88,7 +88,7 @@ /`bundle config set --global www\.uri-to-fetch\.com username:password`.*`BUNDLE_WWW__URI___TO___FETCH__COM`/m) end - context "when the there are credentials provided in the request" do + context "when there are credentials provided in the request" do let(:uri) { Gem::URI("http://user:password@www.uri-to-fetch.com") } it "should raise a Bundler::Fetcher::BadAuthenticationError that doesn't contain the password" do @@ -116,7 +116,7 @@ to raise_error(Bundler::Fetcher::FallbackError, "Gem::Net::HTTPNotFound: http://www.uri-to-fetch.com/api/v2/endpoint") end - context "when the there are credentials provided in the request" do + context "when there are credentials provided in the request" do let(:uri) { Gem::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } it "should raise a Bundler::Fetcher::FallbackError that doesn't contain the password" do @@ -233,7 +233,7 @@ "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (other error about network)") end - context "when the there are credentials provided in the request" do + context "when there are credentials provided in the request" do let(:uri) { Gem::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } before do allow(net_http_get).to receive(:basic_auth).with("username", "password") From d2204044f48f9f87565b4c994158a5b9b44afe3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Fri, 20 Jun 2025 13:40:42 +0200 Subject: [PATCH 075/130] [rubygems/rubygems] Add back and deprecate Bundler::Fetcher::NET_ERRORS https://github.com/rubygems/rubygems/commit/4a4e5828db --- lib/bundler/fetcher.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index c07e8ab350d51b..60a94fe0c4d5bf 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -91,6 +91,27 @@ def initialize(remote_uri) Errno::EHOSTUNREACH, ].freeze + NET_ERRORS = [ + :HTTPBadGateway, + :HTTPBadRequest, + :HTTPFailedDependency, + :HTTPForbidden, + :HTTPInsufficientStorage, + :HTTPMethodNotAllowed, + :HTTPMovedPermanently, + :HTTPNoContent, + :HTTPNotFound, + :HTTPNotImplemented, + :HTTPPreconditionFailed, + :HTTPRequestEntityTooLarge, + :HTTPRequestURITooLong, + :HTTPUnauthorized, + :HTTPUnprocessableEntity, + :HTTPUnsupportedMediaType, + :HTTPVersionNotSupported, + ].freeze + deprecate_constant :NET_ERRORS + # Exceptions classes that should bypass retry attempts. If your password didn't work the # first time, it's not going to the third time. FAIL_ERRORS = [ From b671133c0649286aaaa317f0cfbbc3f03210a75f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Fri, 20 Jun 2025 13:41:03 +0200 Subject: [PATCH 076/130] [rubygems/rubygems] Move Bundler::Fetcher::HTTP_ERRORS to Bundler::Fetcher::DOWNLOADER And deprecate the old constant. It's only used in this class, and in Bundler::Fetcher there's already FAIL_ERRORS, very similar to it. So this makes things less confusing. https://github.com/rubygems/rubygems/commit/d32ed63d6f --- lib/bundler/fetcher.rb | 20 ++----------------- lib/bundler/fetcher/downloader.rb | 19 ++++++++++++++++++ .../bundler/fetcher/downloader_spec.rb | 2 +- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index 60a94fe0c4d5bf..e18501ce4ecc0f 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -72,24 +72,8 @@ def initialize(remote_uri) end end - HTTP_ERRORS = [ - Gem::Timeout::Error, - EOFError, - SocketError, - Errno::EADDRNOTAVAIL, - Errno::ENETDOWN, - Errno::ENETUNREACH, - Errno::EINVAL, - Errno::ECONNRESET, - Errno::ETIMEDOUT, - Errno::EAGAIN, - Gem::Net::HTTPBadResponse, - Gem::Net::HTTPHeaderSyntaxError, - Gem::Net::ProtocolError, - Gem::Net::HTTP::Persistent::Error, - Zlib::BufError, - Errno::EHOSTUNREACH, - ].freeze + HTTP_ERRORS = Downloader::HTTP_ERRORS + deprecate_constant :HTTP_ERRORS NET_ERRORS = [ :HTTPBadGateway, diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb index 868b39b959c79c..a90b93f1046d77 100644 --- a/lib/bundler/fetcher/downloader.rb +++ b/lib/bundler/fetcher/downloader.rb @@ -3,6 +3,25 @@ module Bundler class Fetcher class Downloader + HTTP_ERRORS = [ + Gem::Timeout::Error, + EOFError, + SocketError, + Errno::EADDRNOTAVAIL, + Errno::ENETDOWN, + Errno::ENETUNREACH, + Errno::EINVAL, + Errno::ECONNRESET, + Errno::ETIMEDOUT, + Errno::EAGAIN, + Gem::Net::HTTPBadResponse, + Gem::Net::HTTPHeaderSyntaxError, + Gem::Net::ProtocolError, + Gem::Net::HTTP::Persistent::Error, + Zlib::BufError, + Errno::EHOSTUNREACH, + ].freeze + attr_reader :connection attr_reader :redirect_limit diff --git a/spec/bundler/bundler/fetcher/downloader_spec.rb b/spec/bundler/bundler/fetcher/downloader_spec.rb index 40ae7109abe9a9..7184f22c4f80d8 100644 --- a/spec/bundler/bundler/fetcher/downloader_spec.rb +++ b/spec/bundler/bundler/fetcher/downloader_spec.rb @@ -206,7 +206,7 @@ let(:error) { RuntimeError.new(message) } before do - stub_const("Bundler::Fetcher::HTTP_ERRORS", [RuntimeError]) + stub_const("#{described_class}::HTTP_ERRORS", [RuntimeError]) allow(connection).to receive(:request).with(uri, net_http_get) { raise error } end From 46a90f9998ec16eb6c97560e25fb597d4e3afdbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Fri, 20 Jun 2025 13:46:41 +0200 Subject: [PATCH 077/130] [rubygems/rubygems] Refactor downloader specs to better test real behavior In particular, no route to host errors are actually fatal since they are usually raised as `SocketError`'s, while the specs were incorrectly testing that they are retryable. https://github.com/rubygems/rubygems/commit/9410ceb36b --- .../bundler/fetcher/downloader_spec.rb | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/spec/bundler/bundler/fetcher/downloader_spec.rb b/spec/bundler/bundler/fetcher/downloader_spec.rb index 7184f22c4f80d8..0811cc49f91436 100644 --- a/spec/bundler/bundler/fetcher/downloader_spec.rb +++ b/spec/bundler/bundler/fetcher/downloader_spec.rb @@ -202,11 +202,12 @@ end context "when the request response causes an error included in HTTP_ERRORS" do - let(:message) { nil } - let(:error) { RuntimeError.new(message) } + let(:message) { "error about network" } + let(:error_class) { RuntimeError } + let(:error) { error_class.new(message) } before do - stub_const("#{described_class}::HTTP_ERRORS", [RuntimeError]) + stub_const("#{described_class}::HTTP_ERRORS", [error_class]) allow(connection).to receive(:request).with(uri, net_http_get) { raise error } end @@ -216,37 +217,35 @@ expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError) end - context "when error message is about the host being down" do - let(:message) { "host down: http://www.uri-to-fetch.com" } - - it "should raise a Bundler::Fetcher::NetworkDownError" do - expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError, - /Could not reach host www.uri-to-fetch.com/) - end + it "should raise a Bundler::HTTPError" do + expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, + "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (error about network)") end - context "when error message is not about host down" do - let(:message) { "other error about network" } + context "when there are credentials provided in the request" do + let(:uri) { Gem::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } + before do + allow(net_http_get).to receive(:basic_auth).with("username", "password") + end - it "should raise a Bundler::HTTPError" do + it "should raise a Bundler::HTTPError that doesn't contain the password" do expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, - "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (other error about network)") + "Network error while fetching http://username@www.uri-to-fetch.com/api/v2/endpoint (error about network)") end + end - context "when there are credentials provided in the request" do - let(:uri) { Gem::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } - before do - allow(net_http_get).to receive(:basic_auth).with("username", "password") - end + context "when error is about the host being down" do + let(:error_class) { Gem::Net::HTTP::Persistent::Error } + let(:message) { "host down: http://www.uri-to-fetch.com" } - it "should raise a Bundler::HTTPError that doesn't contain the password" do - expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, - "Network error while fetching http://username@www.uri-to-fetch.com/api/v2/endpoint (other error about network)") - end + it "should raise a Bundler::Fetcher::NetworkDownError" do + expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError, + /Could not reach host www.uri-to-fetch.com/) end end context "when error message is about no route to host" do + let(:error_class) { Gem::Net::HTTP::Persistent::Error } let(:message) { "Failed to open TCP connection to www.uri-to-fetch.com:443 " } it "should raise a Bundler::Fetcher::HTTPError" do From c4c646d1bb96a5b7263c6e4cc962fc61b4d361b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Fri, 20 Jun 2025 13:32:41 +0200 Subject: [PATCH 078/130] [rubygems/rubygems] Handle connection refused and Errno::EADDRNOTAVAIL as non-retryable https://github.com/rubygems/rubygems/commit/cd529776d5 --- lib/bundler/fetcher/downloader.rb | 7 +++++-- .../bundler/bundler/fetcher/downloader_spec.rb | 18 ++++++++++++++---- spec/bundler/commands/install_spec.rb | 2 +- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb index a90b93f1046d77..079d431d1ccfad 100644 --- a/lib/bundler/fetcher/downloader.rb +++ b/lib/bundler/fetcher/downloader.rb @@ -88,8 +88,11 @@ def request(uri, headers) raise CertificateFailureError.new(uri) rescue *HTTP_ERRORS => e Bundler.ui.trace e - if e.is_a?(SocketError) || e.message.to_s.include?("host down:") - raise NetworkDownError, "Could not reach host #{uri.host}. Check your network " \ + if e.is_a?(SocketError) || e.is_a?(Errno::EADDRNOTAVAIL) || e.is_a?(Errno::ENETUNREACH) || e.is_a?(Gem::Net::HTTP::Persistent::Error) + host = uri.host + host_port = "#{host}:#{uri.port}" + host = host_port if filtered_uri.to_s.include?(host_port) + raise NetworkDownError, "Could not reach host #{host}. Check your network " \ "connection and try again." else raise HTTPError, "Network error while fetching #{filtered_uri}" \ diff --git a/spec/bundler/bundler/fetcher/downloader_spec.rb b/spec/bundler/bundler/fetcher/downloader_spec.rb index 0811cc49f91436..4ccfb7d5725833 100644 --- a/spec/bundler/bundler/fetcher/downloader_spec.rb +++ b/spec/bundler/bundler/fetcher/downloader_spec.rb @@ -244,13 +244,23 @@ end end - context "when error message is about no route to host" do + context "when error is about connection refused" do let(:error_class) { Gem::Net::HTTP::Persistent::Error } + let(:message) { "connection refused down: http://www.uri-to-fetch.com" } + + it "should raise a Bundler::Fetcher::NetworkDownError" do + expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError, + /Could not reach host www.uri-to-fetch.com/) + end + end + + context "when error is about no route to host" do + let(:error_class) { SocketError } let(:message) { "Failed to open TCP connection to www.uri-to-fetch.com:443 " } - it "should raise a Bundler::Fetcher::HTTPError" do - expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, - "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (#{message})") + it "should raise a Bundler::Fetcher::NetworkDownError" do + expect { subject.request(uri, options) }.to raise_error(Bundler::Fetcher::NetworkDownError, + /Could not reach host www.uri-to-fetch.com/) end end end diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 6b3f2b4c7ec893..4a581b3058e76e 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -697,7 +697,7 @@ end G - expect(err).to include("Could not fetch specs from http://0.0.0.0:9384/") + expect(err).to eq("Could not reach host 0.0.0.0:9384. Check your network connection and try again.") expect(err).not_to include("file://") end From 35dd2b2994570ac40fd0c5ebb683552b667e07f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 17 Jun 2025 21:58:04 +0200 Subject: [PATCH 079/130] [rubygems/rubygems] Split HTTP_ERRORS into retryable and non retryable https://github.com/rubygems/rubygems/commit/c241a640fc --- lib/bundler/fetcher.rb | 2 +- lib/bundler/fetcher/downloader.rb | 34 +++++++++------- .../bundler/fetcher/downloader_spec.rb | 40 ++++++++++--------- 3 files changed, 41 insertions(+), 35 deletions(-) diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb index e18501ce4ecc0f..0b6ced6f395cda 100644 --- a/lib/bundler/fetcher.rb +++ b/lib/bundler/fetcher.rb @@ -72,7 +72,7 @@ def initialize(remote_uri) end end - HTTP_ERRORS = Downloader::HTTP_ERRORS + HTTP_ERRORS = (Downloader::HTTP_RETRYABLE_ERRORS + Downloader::HTTP_NON_RETRYABLE_ERRORS).freeze deprecate_constant :HTTP_ERRORS NET_ERRORS = [ diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb index 079d431d1ccfad..2a4d13394e81da 100644 --- a/lib/bundler/fetcher/downloader.rb +++ b/lib/bundler/fetcher/downloader.rb @@ -3,13 +3,17 @@ module Bundler class Fetcher class Downloader - HTTP_ERRORS = [ - Gem::Timeout::Error, - EOFError, + HTTP_NON_RETRYABLE_ERRORS = [ SocketError, Errno::EADDRNOTAVAIL, - Errno::ENETDOWN, Errno::ENETUNREACH, + Gem::Net::HTTP::Persistent::Error, + ].freeze + + HTTP_RETRYABLE_ERRORS = [ + Gem::Timeout::Error, + EOFError, + Errno::ENETDOWN, Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, @@ -17,7 +21,6 @@ class Downloader Gem::Net::HTTPBadResponse, Gem::Net::HTTPHeaderSyntaxError, Gem::Net::ProtocolError, - Gem::Net::HTTP::Persistent::Error, Zlib::BufError, Errno::EHOSTUNREACH, ].freeze @@ -86,18 +89,19 @@ def request(uri, headers) connection.request(uri, req) rescue OpenSSL::SSL::SSLError raise CertificateFailureError.new(uri) - rescue *HTTP_ERRORS => e + rescue *HTTP_NON_RETRYABLE_ERRORS => e Bundler.ui.trace e - if e.is_a?(SocketError) || e.is_a?(Errno::EADDRNOTAVAIL) || e.is_a?(Errno::ENETUNREACH) || e.is_a?(Gem::Net::HTTP::Persistent::Error) - host = uri.host - host_port = "#{host}:#{uri.port}" - host = host_port if filtered_uri.to_s.include?(host_port) - raise NetworkDownError, "Could not reach host #{host}. Check your network " \ - "connection and try again." - else - raise HTTPError, "Network error while fetching #{filtered_uri}" \ + + host = uri.host + host_port = "#{host}:#{uri.port}" + host = host_port if filtered_uri.to_s.include?(host_port) + raise NetworkDownError, "Could not reach host #{host}. Check your network " \ + "connection and try again." + rescue *HTTP_RETRYABLE_ERRORS => e + Bundler.ui.trace e + + raise HTTPError, "Network error while fetching #{filtered_uri}" \ " (#{e})" - end end private diff --git a/spec/bundler/bundler/fetcher/downloader_spec.rb b/spec/bundler/bundler/fetcher/downloader_spec.rb index 4ccfb7d5725833..36b9b949908caf 100644 --- a/spec/bundler/bundler/fetcher/downloader_spec.rb +++ b/spec/bundler/bundler/fetcher/downloader_spec.rb @@ -201,36 +201,38 @@ end end - context "when the request response causes an error included in HTTP_ERRORS" do + context "when the request response causes an HTTP error" do let(:message) { "error about network" } - let(:error_class) { RuntimeError } let(:error) { error_class.new(message) } before do - stub_const("#{described_class}::HTTP_ERRORS", [error_class]) allow(connection).to receive(:request).with(uri, net_http_get) { raise error } end - it "should trace log the error" do - allow(Bundler).to receive_message_chain(:ui, :debug) - expect(Bundler).to receive_message_chain(:ui, :trace).with(error) - expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError) - end - - it "should raise a Bundler::HTTPError" do - expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, - "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (error about network)") - end + context "that it's retryable" do + let(:error_class) { Gem::Timeout::Error } - context "when there are credentials provided in the request" do - let(:uri) { Gem::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } - before do - allow(net_http_get).to receive(:basic_auth).with("username", "password") + it "should trace log the error" do + allow(Bundler).to receive_message_chain(:ui, :debug) + expect(Bundler).to receive_message_chain(:ui, :trace).with(error) + expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError) end - it "should raise a Bundler::HTTPError that doesn't contain the password" do + it "should raise a Bundler::HTTPError" do expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, - "Network error while fetching http://username@www.uri-to-fetch.com/api/v2/endpoint (error about network)") + "Network error while fetching http://www.uri-to-fetch.com/api/v2/endpoint (error about network)") + end + + context "when there are credentials provided in the request" do + let(:uri) { Gem::URI("http://username:password@www.uri-to-fetch.com/api/v2/endpoint") } + before do + allow(net_http_get).to receive(:basic_auth).with("username", "password") + end + + it "should raise a Bundler::HTTPError that doesn't contain the password" do + expect { subject.request(uri, options) }.to raise_error(Bundler::HTTPError, + "Network error while fetching http://username@www.uri-to-fetch.com/api/v2/endpoint (error about network)") + end end end From d5b4b59500f00c26e19aec838adaef1baf14120c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Fri, 20 Jun 2025 16:52:58 +0200 Subject: [PATCH 080/130] [rubygems/rubygems] Add Errno::ENETDOWN and Errno::EHOSTUNREACH to non retryable errors Connection errors as well, so useless to retry. https://github.com/rubygems/rubygems/commit/d2d211651a --- lib/bundler/fetcher/downloader.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/bundler/fetcher/downloader.rb b/lib/bundler/fetcher/downloader.rb index 2a4d13394e81da..2eac6e79754c5e 100644 --- a/lib/bundler/fetcher/downloader.rb +++ b/lib/bundler/fetcher/downloader.rb @@ -6,14 +6,15 @@ class Downloader HTTP_NON_RETRYABLE_ERRORS = [ SocketError, Errno::EADDRNOTAVAIL, + Errno::ENETDOWN, Errno::ENETUNREACH, Gem::Net::HTTP::Persistent::Error, + Errno::EHOSTUNREACH, ].freeze HTTP_RETRYABLE_ERRORS = [ Gem::Timeout::Error, EOFError, - Errno::ENETDOWN, Errno::EINVAL, Errno::ECONNRESET, Errno::ETIMEDOUT, @@ -22,7 +23,6 @@ class Downloader Gem::Net::HTTPHeaderSyntaxError, Gem::Net::ProtocolError, Zlib::BufError, - Errno::EHOSTUNREACH, ].freeze attr_reader :connection From 8bf13f26055d1c4eb7989b5ed846727d89da8220 Mon Sep 17 00:00:00 2001 From: Joshua Young Date: Fri, 27 Jun 2025 23:35:35 +1000 Subject: [PATCH 081/130] Reduce allocations in `Gem::BUNDLED_GEMS.warning?` --- lib/bundled_gems.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/bundled_gems.rb b/lib/bundled_gems.rb index e49d6fbdcf86ac..50fc31937c9440 100644 --- a/lib/bundled_gems.rb +++ b/lib/bundled_gems.rb @@ -99,11 +99,14 @@ def self.warning?(name, specs: nil) # and `require "syslog"` to `require "#{ARCHDIR}/syslog.so"`. feature.delete_prefix!(ARCHDIR) feature.delete_prefix!(LIBDIR) - segments = feature.split("/") + # 1. A segment for the EXACT mapping and SINCE check + # 2. A segment for the SINCE check for dashed names + # 3. A segment to check if there's a subfeature + segments = feature.split("/", 3) name = segments.shift name = EXACT[name] || name if !SINCE[name] - name = [name, segments.shift].join("-") + name = "#{name}-#{segments.shift}" return unless SINCE[name] end segments.any? From d31467311573b39bd07e182f863a26069f21d481 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 2 Jul 2025 12:37:08 +0900 Subject: [PATCH 082/130] CI: Fix appending to an array Parentheses are required to add a new element to an array, not to the first element of the array. --- .github/actions/setup/macos/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup/macos/action.yml b/.github/actions/setup/macos/action.yml index 5da7c6d44cfb2c..d0072ff82890c4 100644 --- a/.github/actions/setup/macos/action.yml +++ b/.github/actions/setup/macos/action.yml @@ -21,7 +21,7 @@ runs: dir_config() { local args=() lib var="$1"; shift for lib in "$@"; do - args+="--with-${lib%@*}-dir=$(brew --prefix $lib)" + args+=("--with-${lib%@*}-dir=$(brew --prefix $lib)") done echo "$var=${args[*]}" >> $GITHUB_ENV } From 6af5398359712fe2d54365778e2aae4acf344c57 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 2 Jul 2025 13:06:59 +0100 Subject: [PATCH 083/130] ZJIT: Support `Regexp` type (#13760) --- zjit/bindgen/src/main.rs | 1 + zjit/src/cruby_bindings.inc.rs | 1 + zjit/src/hir.rs | 13 +++++++++++ zjit/src/hir_type/gen_hir_type.rb | 1 + zjit/src/hir_type/hir_type.inc.rs | 37 +++++++++++++++++++------------ zjit/src/hir_type/mod.rs | 6 ++++- 6 files changed, 44 insertions(+), 15 deletions(-) diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 0df900b45eadaa..1e4c711e05fcd8 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -188,6 +188,7 @@ fn main() { .allowlist_var("rb_cHash") .allowlist_var("rb_cSet") .allowlist_var("rb_cClass") + .allowlist_var("rb_cRegexp") .allowlist_var("rb_cISeq") // From include/ruby/internal/fl_type.h diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index cb5910b5369b81..10c5c7c903df47 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -752,6 +752,7 @@ unsafe extern "C" { pub static mut rb_cNilClass: VALUE; pub static mut rb_cNumeric: VALUE; pub static mut rb_cRange: VALUE; + pub static mut rb_cRegexp: VALUE; pub static mut rb_cString: VALUE; pub static mut rb_cSymbol: VALUE; pub static mut rb_cThread: VALUE; diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index ae279f82663daa..da12f574a8c245 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -6310,4 +6310,17 @@ mod opt_tests { Return v7 "#]]); } + + #[test] + fn test_regexp_type() { + eval(" + def test = /a/ + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v2:RegexpExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + Return v2 + "#]]); + } } diff --git a/zjit/src/hir_type/gen_hir_type.rb b/zjit/src/hir_type/gen_hir_type.rb index 1166d8ebb8e9a5..69252ee9427857 100644 --- a/zjit/src/hir_type/gen_hir_type.rb +++ b/zjit/src/hir_type/gen_hir_type.rb @@ -73,6 +73,7 @@ def base_type name base_type "Hash" base_type "Range" base_type "Set" +base_type "Regexp" (integer, integer_exact) = base_type "Integer" # CRuby partitions Integer into immediate and non-immediate variants. diff --git a/zjit/src/hir_type/hir_type.inc.rs b/zjit/src/hir_type/hir_type.inc.rs index 7557610463a6af..ff3eccfcb43ca6 100644 --- a/zjit/src/hir_type/hir_type.inc.rs +++ b/zjit/src/hir_type/hir_type.inc.rs @@ -9,7 +9,7 @@ mod bits { pub const BasicObjectSubclass: u64 = 1u64 << 3; pub const Bignum: u64 = 1u64 << 4; pub const BoolExact: u64 = FalseClassExact | TrueClassExact; - pub const BuiltinExact: u64 = ArrayExact | BasicObjectExact | FalseClassExact | FloatExact | HashExact | IntegerExact | NilClassExact | ObjectExact | RangeExact | SetExact | StringExact | SymbolExact | TrueClassExact; + pub const BuiltinExact: u64 = ArrayExact | BasicObjectExact | FalseClassExact | FloatExact | HashExact | IntegerExact | NilClassExact | ObjectExact | RangeExact | RegexpExact | SetExact | StringExact | SymbolExact | TrueClassExact; pub const CBool: u64 = 1u64 << 5; pub const CDouble: u64 = 1u64 << 6; pub const CInt: u64 = CSigned | CUnsigned; @@ -48,29 +48,32 @@ mod bits { pub const NilClass: u64 = NilClassExact | NilClassSubclass; pub const NilClassExact: u64 = 1u64 << 28; pub const NilClassSubclass: u64 = 1u64 << 29; - pub const Object: u64 = Array | FalseClass | Float | Hash | Integer | NilClass | ObjectExact | ObjectSubclass | Range | Set | String | Symbol | TrueClass; + pub const Object: u64 = Array | FalseClass | Float | Hash | Integer | NilClass | ObjectExact | ObjectSubclass | Range | Regexp | Set | String | Symbol | TrueClass; pub const ObjectExact: u64 = 1u64 << 30; pub const ObjectSubclass: u64 = 1u64 << 31; pub const Range: u64 = RangeExact | RangeSubclass; pub const RangeExact: u64 = 1u64 << 32; pub const RangeSubclass: u64 = 1u64 << 33; + pub const Regexp: u64 = RegexpExact | RegexpSubclass; + pub const RegexpExact: u64 = 1u64 << 34; + pub const RegexpSubclass: u64 = 1u64 << 35; pub const RubyValue: u64 = BasicObject | CallableMethodEntry | Undef; pub const Set: u64 = SetExact | SetSubclass; - pub const SetExact: u64 = 1u64 << 34; - pub const SetSubclass: u64 = 1u64 << 35; - pub const StaticSymbol: u64 = 1u64 << 36; + pub const SetExact: u64 = 1u64 << 36; + pub const SetSubclass: u64 = 1u64 << 37; + pub const StaticSymbol: u64 = 1u64 << 38; pub const String: u64 = StringExact | StringSubclass; - pub const StringExact: u64 = 1u64 << 37; - pub const StringSubclass: u64 = 1u64 << 38; - pub const Subclass: u64 = ArraySubclass | BasicObjectSubclass | FalseClassSubclass | FloatSubclass | HashSubclass | IntegerSubclass | NilClassSubclass | ObjectSubclass | RangeSubclass | SetSubclass | StringSubclass | SymbolSubclass | TrueClassSubclass; + pub const StringExact: u64 = 1u64 << 39; + pub const StringSubclass: u64 = 1u64 << 40; + pub const Subclass: u64 = ArraySubclass | BasicObjectSubclass | FalseClassSubclass | FloatSubclass | HashSubclass | IntegerSubclass | NilClassSubclass | ObjectSubclass | RangeSubclass | RegexpSubclass | SetSubclass | StringSubclass | SymbolSubclass | TrueClassSubclass; pub const Symbol: u64 = SymbolExact | SymbolSubclass; pub const SymbolExact: u64 = DynamicSymbol | StaticSymbol; - pub const SymbolSubclass: u64 = 1u64 << 39; + pub const SymbolSubclass: u64 = 1u64 << 41; pub const TrueClass: u64 = TrueClassExact | TrueClassSubclass; - pub const TrueClassExact: u64 = 1u64 << 40; - pub const TrueClassSubclass: u64 = 1u64 << 41; - pub const Undef: u64 = 1u64 << 42; - pub const AllBitPatterns: [(&'static str, u64); 70] = [ + pub const TrueClassExact: u64 = 1u64 << 42; + pub const TrueClassSubclass: u64 = 1u64 << 43; + pub const Undef: u64 = 1u64 << 44; + pub const AllBitPatterns: [(&'static str, u64); 73] = [ ("Any", Any), ("RubyValue", RubyValue), ("Immediate", Immediate), @@ -93,6 +96,9 @@ mod bits { ("Set", Set), ("SetSubclass", SetSubclass), ("SetExact", SetExact), + ("Regexp", Regexp), + ("RegexpSubclass", RegexpSubclass), + ("RegexpExact", RegexpExact), ("Range", Range), ("RangeSubclass", RangeSubclass), ("RangeExact", RangeExact), @@ -142,7 +148,7 @@ mod bits { ("ArrayExact", ArrayExact), ("Empty", Empty), ]; - pub const NumTypeBits: u64 = 43; + pub const NumTypeBits: u64 = 45; } pub mod types { use super::*; @@ -200,6 +206,9 @@ pub mod types { pub const Range: Type = Type::from_bits(bits::Range); pub const RangeExact: Type = Type::from_bits(bits::RangeExact); pub const RangeSubclass: Type = Type::from_bits(bits::RangeSubclass); + pub const Regexp: Type = Type::from_bits(bits::Regexp); + pub const RegexpExact: Type = Type::from_bits(bits::RegexpExact); + pub const RegexpSubclass: Type = Type::from_bits(bits::RegexpSubclass); pub const RubyValue: Type = Type::from_bits(bits::RubyValue); pub const Set: Type = Type::from_bits(bits::Set); pub const SetExact: Type = Type::from_bits(bits::SetExact); diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index 19dbeffdaa414c..36e9a552d7688a 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -1,6 +1,6 @@ #![allow(non_upper_case_globals)] use crate::cruby::{Qfalse, Qnil, Qtrue, VALUE, RUBY_T_ARRAY, RUBY_T_STRING, RUBY_T_HASH}; -use crate::cruby::{rb_cInteger, rb_cFloat, rb_cArray, rb_cHash, rb_cString, rb_cSymbol, rb_cObject, rb_cTrueClass, rb_cFalseClass, rb_cNilClass, rb_cRange, rb_cSet}; +use crate::cruby::{rb_cInteger, rb_cFloat, rb_cArray, rb_cHash, rb_cString, rb_cSymbol, rb_cObject, rb_cTrueClass, rb_cFalseClass, rb_cNilClass, rb_cRange, rb_cSet, rb_cRegexp}; use crate::cruby::ClassRelationship; use crate::cruby::get_class_name; use crate::cruby::ruby_sym_to_rust_string; @@ -197,6 +197,9 @@ impl Type { else if is_string_exact(val) { Type { bits: bits::StringExact, spec: Specialization::Object(val) } } + else if val.class_of() == unsafe { rb_cRegexp } { + Type { bits: bits::RegexpExact, spec: Specialization::Object(val) } + } else if val.class_of() == unsafe { rb_cSet } { Type { bits: bits::SetExact, spec: Specialization::Object(val) } } @@ -292,6 +295,7 @@ impl Type { if class == unsafe { rb_cNilClass } { return true; } if class == unsafe { rb_cObject } { return true; } if class == unsafe { rb_cRange } { return true; } + if class == unsafe { rb_cRegexp } { return true; } if class == unsafe { rb_cString } { return true; } if class == unsafe { rb_cSymbol } { return true; } if class == unsafe { rb_cTrueClass } { return true; } From efc686697ec4e9497b916a4265c88ed648f85800 Mon Sep 17 00:00:00 2001 From: Naoto Ono Date: Wed, 2 Jul 2025 21:12:48 +0900 Subject: [PATCH 084/130] =?UTF-8?q?Launchable:=20Temporarily=20remove=20La?= =?UTF-8?q?unchable=20integration=20from=20Compilatio=E2=80=A6=20(#13759)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Launchable: Temporarily remove Launchable integration from Compilations workflow Currently, Launchable is unstable, which occationally causes workflow issues. Until this problem is fixed, we'll temporary disable Launchable in the Compilations workflow. --- .github/actions/compilers/entrypoint.sh | 58 ------------------------- 1 file changed, 58 deletions(-) diff --git a/.github/actions/compilers/entrypoint.sh b/.github/actions/compilers/entrypoint.sh index 1de7fce1d34dd3..17f749d69ebf60 100755 --- a/.github/actions/compilers/entrypoint.sh +++ b/.github/actions/compilers/entrypoint.sh @@ -74,64 +74,6 @@ btests='' tests='' spec_opts='' -# Launchable -launchable_record_session() { - launchable record session \ - --build "${build_name}" \ - --flavor test_task=$1 \ - --flavor workflow=Compilations \ - --flavor with-gcc="${INPUT_WITH_GCC}" \ - --flavor CFLAGS="${INPUT_CFLAGS}" \ - --flavor CXXFLAGS="${INPUT_CXXFLAGS}" \ - --flavor optflags="${INPUT_OPTFLAGS}" \ - --flavor cppflags="${INPUT_CPPFLAGS}" \ - --test-suite ${2-$1} -} -setup_launchable() { - pushd ${srcdir} - # To prevent a slowdown in CI, disable request retries when the Launchable server is unstable. - export LAUNCHABLE_SKIP_TIMEOUT_RETRY=1 - export LAUNCHABLE_COMMIT_TIMEOUT=1 - # Launchable creates .launchable file in the current directory, but cannot a file to ${srcdir} directory. - # As a workaround, we set LAUNCHABLE_SESSION_DIR to ${builddir}. - export LAUNCHABLE_SESSION_DIR=${builddir} - local github_ref="${GITHUB_REF//\//_}" - local build_name="${github_ref}"_"${GITHUB_PR_HEAD_SHA}" - launchable record build --name "${build_name}" || true - btest_session=$(launchable_record_session test btest) \ - && btests+=--launchable-test-reports="${btest_report_path}" || : - if [ "$INPUT_CHECK" = "true" ]; then - test_all_session=$(launchable_record_session test-all) \ - && tests+=--launchable-test-reports="${test_report_path}" || : - mkdir "${builddir}"/"${test_spec_report_path}" - test_spec_session=$(launchable_record_session test-spec) \ - && spec_opts+=--launchable-test-reports="${test_spec_report_path}" || : - fi -} -launchable_record_test() { - pushd "${builddir}" - grouped launchable record tests --session "${btest_session}" raw "${btest_report_path}" || true - if [ "$INPUT_CHECK" = "true" ]; then - grouped launchable record tests --session "${test_all_session}" raw "${test_report_path}" || true - grouped launchable record tests --session "${test_spec_session}" raw "${test_spec_report_path}"/* || true - fi -} -if [ "$LAUNCHABLE_ENABLED" = "true" ]; then - echo "::group::Setup Launchable" - btest_report_path='launchable_bootstraptest.json' - test_report_path='launchable_test_all.json' - test_spec_report_path='launchable_test_spec_report' - setup_pid=$$ - (sleep 180; echo "setup_launchable timed out; killing"; kill -INT "-$setup_pid" 2> /dev/null) & sleep_pid=$! - launchable_failed=false - trap "launchable_failed=true" INT - setup_launchable - kill "$sleep_pid" 2> /dev/null - trap - INT - echo "::endgroup::" - $launchable_failed || trap launchable_record_test EXIT -fi - pushd ${builddir} grouped make showflags From 565ab3ef57e6281199f32a932f7d176c989d89cc Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Fri, 27 Jun 2025 23:32:52 +0900 Subject: [PATCH 085/130] ZJIT: Use initialization shorthand --- zjit/src/hir.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index da12f574a8c245..a2ed23fa3eadc8 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1074,7 +1074,7 @@ impl Function { ArraySet { array, idx, val } => ArraySet { array: find!(*array), idx: *idx, val: find!(*val) }, ArrayDup { val , state } => ArrayDup { val: find!(*val), state: *state }, &HashDup { val , state } => HashDup { val: find!(val), state }, - &CCall { cfun, ref args, name, return_type, elidable } => CCall { cfun: cfun, args: find_vec!(args), name: name, return_type: return_type, elidable }, + &CCall { cfun, ref args, name, return_type, elidable } => CCall { cfun, args: find_vec!(args), name, return_type, elidable }, &Defined { op_type, obj, pushval, v } => Defined { op_type, obj, pushval, v: find!(v) }, &DefinedIvar { self_val, pushval, id, state } => DefinedIvar { self_val: find!(self_val), pushval, id, state }, NewArray { elements, state } => NewArray { elements: find_vec!(*elements), state: find!(*state) }, From ddb8de1f5f16e289a1c25bf771d62d0b8844ec7c Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Fri, 27 Jun 2025 23:51:26 +0900 Subject: [PATCH 086/130] ZJIT: `throw` to HIR --- zjit/src/hir.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index a2ed23fa3eadc8..10a5b1b67a6a6f 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -488,6 +488,8 @@ pub enum Insn { /// Control flow instructions Return { val: InsnId }, + /// Non-local control flow. See the throw YARV instruction + Throw { throw_state: u32, val: InsnId }, /// Fixnum +, -, *, /, %, ==, !=, <, <=, >, >= FixnumAdd { left: InsnId, right: InsnId, state: InsnId }, @@ -527,7 +529,7 @@ impl Insn { | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. } - | Insn::SetLocal { .. } => false, + | Insn::SetLocal { .. } | Insn::Throw { .. } => false, _ => true, } } @@ -535,7 +537,7 @@ impl Insn { /// Return true if the instruction ends a basic block and false otherwise. pub fn is_terminator(&self) -> bool { match self { - Insn::Jump(_) | Insn::Return { .. } | Insn::SideExit { .. } => true, + Insn::Jump(_) | Insn::Return { .. } | Insn::SideExit { .. } | Insn::Throw { .. } => true, _ => false, } } @@ -713,8 +715,25 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::ObjToString { val, .. } => { write!(f, "ObjToString {val}") }, Insn::AnyToString { val, str, .. } => { write!(f, "AnyToString {val}, str: {str}") }, Insn::SideExit { .. } => write!(f, "SideExit"), - Insn::PutSpecialObject { value_type } => { - write!(f, "PutSpecialObject {}", value_type) + Insn::PutSpecialObject { value_type } => write!(f, "PutSpecialObject {value_type}"), + Insn::Throw { throw_state, val } => { + let mut state_string = match throw_state & VM_THROW_STATE_MASK { + RUBY_TAG_NONE => "TAG_NONE".to_string(), + RUBY_TAG_RETURN => "TAG_RETURN".to_string(), + RUBY_TAG_BREAK => "TAG_BREAK".to_string(), + RUBY_TAG_NEXT => "TAG_NEXT".to_string(), + RUBY_TAG_RETRY => "TAG_RETRY".to_string(), + RUBY_TAG_REDO => "TAG_REDO".to_string(), + RUBY_TAG_RAISE => "TAG_RAISE".to_string(), + RUBY_TAG_THROW => "TAG_THROW".to_string(), + RUBY_TAG_FATAL => "TAG_FATAL".to_string(), + tag => format!("{tag}") + }; + if throw_state & VM_THROW_NO_ESCAPE_FLAG != 0 { + use std::fmt::Write; + write!(state_string, "|NO_ESCAPE")?; + } + write!(f, "Throw {state_string}, {val}") } insn => { write!(f, "{insn:?}") } } @@ -1015,6 +1034,7 @@ impl Function { } }, Return { val } => Return { val: find!(*val) }, + &Throw { throw_state, val } => Throw { throw_state, val: find!(val) }, StringCopy { val, chilled } => StringCopy { val: find!(*val), chilled: *chilled }, StringIntern { val } => StringIntern { val: find!(*val) }, Test { val } => Test { val: find!(*val) }, @@ -1119,7 +1139,7 @@ impl Function { match &self.insns[insn.0] { Insn::Param { .. } => unimplemented!("params should not be present in block.insns"), Insn::SetGlobal { .. } | Insn::ArraySet { .. } | Insn::Snapshot { .. } | Insn::Jump(_) - | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } + | Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::Throw { .. } | Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. } | Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } => panic!("Cannot infer type of instruction with no output: {}", self.insns[insn.0]), @@ -1755,6 +1775,7 @@ impl Function { Insn::StringCopy { val, .. } | Insn::StringIntern { val } | Insn::Return { val } + | Insn::Throw { val, .. } | Insn::Defined { v: val, .. } | Insn::Test { val } | Insn::SetLocal { val, .. } @@ -2710,6 +2731,10 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { fun.push_insn(block, Insn::Return { val: state.stack_pop()? }); break; // Don't enqueue the next block as a successor } + YARVINSN_throw => { + fun.push_insn(block, Insn::Throw { throw_state: get_arg(pc, 0).as_u32(), val: state.stack_pop()? }); + break; // Don't enqueue the next block as a successor + } // These are opt_send_without_block and all the opt_* instructions // specialized to a certain method that could also be serviced @@ -4620,6 +4645,26 @@ mod tests { SideExit "#]]); } + + #[test] + fn throw() { + eval(" + define_method(:throw_return) { return 1 } + define_method(:throw_break) { break 2 } + "); + assert_method_hir_with_opcode("throw_return", YARVINSN_throw, expect![[r#" + fn block in : + bb0(v0:BasicObject): + v2:Fixnum[1] = Const Value(1) + Throw TAG_RETURN, v2 + "#]]); + assert_method_hir_with_opcode("throw_break", YARVINSN_throw, expect![[r#" + fn block in : + bb0(v0:BasicObject): + v2:Fixnum[2] = Const Value(2) + Throw TAG_BREAK, v2 + "#]]); + } } #[cfg(test)] From a0bf36a9f42f8d627dacb2f7f3a697d53f712a5c Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 2 Jul 2025 18:15:52 +0100 Subject: [PATCH 087/130] ZJIT: Annotate NilClass#nil? and Kernel#nil? These methods return fixed `true` or `false` so we can be certain about their return types. --- test/ruby/test_zjit.rb | 14 +++++++++ zjit/src/cruby_methods.rs | 2 ++ zjit/src/hir.rs | 64 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index e7ce1e1837acf4..920ec461a55834 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -840,6 +840,20 @@ def test(x) }, call_threshold: 1, insns: [:branchnil] end + def test_nil_nil + assert_compiles 'true', %q{ + def test = nil.nil? + test + }, insns: [:opt_nil_p] + end + + def test_non_nil_nil + assert_compiles 'false', %q{ + def test = 1.nil? + test + }, insns: [:opt_nil_p] + end + # tool/ruby_vm/views/*.erb relies on the zjit instructions a) being contiguous and # b) being reliably ordered after all the other instructions. def test_instruction_order diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index edaaba1516f2a3..51ecb1c787f9ec 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -80,6 +80,8 @@ pub fn init() -> Annotations { annotate!(rb_cModule, "===", types::BoolExact, no_gc, leaf); annotate!(rb_cArray, "length", types::Fixnum, no_gc, leaf, elidable); annotate!(rb_cArray, "size", types::Fixnum, no_gc, leaf, elidable); + annotate!(rb_cNilClass, "nil?", types::TrueClassExact, no_gc, leaf, elidable); + annotate!(rb_mKernel, "nil?", types::FalseClassExact, no_gc, leaf, elidable); Annotations { cfuncs: std::mem::take(cfuncs) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 10a5b1b67a6a6f..30a6da201ed82b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -6368,4 +6368,68 @@ mod opt_tests { Return v2 "#]]); } + + #[test] + fn test_nil_nil_specialized_to_ccall() { + eval(" + def test = nil.nil? + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v2:NilClassExact = Const Value(nil) + PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008) + v7:TrueClassExact = CCall nil?@0x1010, v2 + Return v7 + "#]]); + } + + #[test] + fn test_eliminate_nil_nil_specialized_to_ccall() { + eval(" + def test + nil.nil? + 1 + end + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + PatchPoint MethodRedefined(NilClass@0x1000, nil?@0x1008) + v5:Fixnum[1] = Const Value(1) + Return v5 + "#]]); + } + + #[test] + fn test_non_nil_nil_specialized_to_ccall() { + eval(" + def test = 1.nil? + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + v2:Fixnum[1] = Const Value(1) + PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008) + v7:FalseClassExact = CCall nil?@0x1010, v2 + Return v7 + "#]]); + } + + #[test] + fn test_eliminate_non_nil_nil_specialized_to_ccall() { + eval(" + def test + 1.nil? + 2 + end + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + PatchPoint MethodRedefined(Integer@0x1000, nil?@0x1008) + v5:Fixnum[2] = Const Value(2) + Return v5 + "#]]); + } } From 6e28574ed08b076783035dc67ed0067550ff6bbe Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 2 Jul 2025 10:37:30 -0700 Subject: [PATCH 088/130] ZJIT: Support spilling basic block arguments (#13761) Co-authored-by: Max Bernstein --- test/ruby/test_zjit.rb | 34 +++++++++++++ zjit/src/backend/arm64/mod.rs | 1 + zjit/src/backend/lir.rs | 15 +++++- zjit/src/backend/x86_64/mod.rs | 1 + zjit/src/codegen.rs | 87 +++++++++++++++++++++++++++------- 5 files changed, 120 insertions(+), 18 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 920ec461a55834..0c73e6b456392d 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -693,6 +693,40 @@ def fib(n) }, call_threshold: 5, num_profiles: 3 end + def test_spilled_basic_block_args + assert_compiles '55', %q{ + def test(n1, n2) + n3 = 3 + n4 = 4 + n5 = 5 + n6 = 6 + n7 = 7 + n8 = 8 + n9 = 9 + n10 = 10 + if n1 < n2 + n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10 + end + end + test(1, 2) + } + end + + def test_spilled_method_args + omit 'CCall with spilled arguments is not implemented yet' + assert_compiles '55', %q{ + def foo(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10) + n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10 + end + + def test + foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + end + + test + } + end + def test_opt_aref_with assert_compiles ':ok', %q{ def aref_with(hash) = hash["key"] diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index dd1eb52d34778e..3c18a57dd05381 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -28,6 +28,7 @@ pub const _C_ARG_OPNDS: [Opnd; 6] = [ // C return value register on this platform pub const C_RET_REG: Reg = X0_REG; pub const _C_RET_OPND: Opnd = Opnd::Reg(X0_REG); +pub const _NATIVE_STACK_PTR: Opnd = Opnd::Reg(XZR_REG); // These constants define the way we work with Arm64's stack pointer. The stack // pointer always needs to be aligned to a 16-byte boundary. diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 5fe4b85b629764..c168170b2fdd56 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -16,6 +16,7 @@ pub const SP: Opnd = _SP; pub const C_ARG_OPNDS: [Opnd; 6] = _C_ARG_OPNDS; pub const C_RET_OPND: Opnd = _C_RET_OPND; +pub const NATIVE_STACK_PTR: Opnd = _NATIVE_STACK_PTR; pub use crate::backend::current::{Reg, C_RET_REG}; // Memory operand base @@ -277,7 +278,7 @@ pub enum Target /// Pointer to a piece of ZJIT-generated code CodePtr(CodePtr), // Side exit with a counter - SideExit { pc: *const VALUE, stack: Vec, locals: Vec }, + SideExit { pc: *const VALUE, stack: Vec, locals: Vec, c_stack_bytes: usize }, /// A label within the generated code Label(Label), } @@ -1774,7 +1775,7 @@ impl Assembler for (idx, target) in targets { // Compile a side exit. Note that this is past the split pass and alloc_regs(), // so you can't use a VReg or an instruction that needs to be split. - if let Target::SideExit { pc, stack, locals } = target { + if let Target::SideExit { pc, stack, locals, c_stack_bytes } = target { let side_exit_label = self.new_label("side_exit".into()); self.write_label(side_exit_label.clone()); @@ -1810,6 +1811,11 @@ impl Assembler let cfp_sp = Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SP); self.store(cfp_sp, Opnd::Reg(Assembler::SCRATCH_REG)); + if c_stack_bytes > 0 { + asm_comment!(self, "restore C stack pointer"); + self.add_into(NATIVE_STACK_PTR, c_stack_bytes.into()); + } + asm_comment!(self, "exit to the interpreter"); self.frame_teardown(); self.mov(C_RET_OPND, Opnd::UImm(Qundef.as_u64())); @@ -1842,6 +1848,11 @@ impl Assembler { out } + pub fn add_into(&mut self, left: Opnd, right: Opnd) -> Opnd { + self.push_insn(Insn::Add { left, right, out: left }); + left + } + #[must_use] pub fn and(&mut self, left: Opnd, right: Opnd) -> Opnd { let out = self.new_vreg(Opnd::match_num_bits(&[left, right])); diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index d83fc184f91a6a..793a096365df27 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -28,6 +28,7 @@ pub const _C_ARG_OPNDS: [Opnd; 6] = [ // C return value register on this platform pub const C_RET_REG: Reg = RAX_REG; pub const _C_RET_OPND: Opnd = Opnd::Reg(RAX_REG); +pub const _NATIVE_STACK_PTR: Opnd = Opnd::Reg(RSP_REG); impl CodeBlock { // The number of bytes that are generated by jmp_ptr diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 33a8af68683b80..6d73a3a32df1a4 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -6,7 +6,7 @@ use crate::backend::current::{Reg, ALLOC_REGS}; use crate::profile::get_or_create_iseq_payload; use crate::state::ZJITState; use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; -use crate::backend::lir::{self, asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, SP}; +use crate::backend::lir::{self, asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, SP}; use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, CallInfo, RangeType, SELF_PARAM_IDX, SpecialObjectType}; use crate::hir::{Const, FrameState, Function, Insn, InsnId}; use crate::hir_type::{types::Fixnum, Type}; @@ -25,16 +25,20 @@ struct JITState { /// Branches to an ISEQ that need to be compiled later branch_iseqs: Vec<(Rc, IseqPtr)>, + + /// The number of bytes allocated for basic block arguments spilled onto the C stack + c_stack_bytes: usize, } impl JITState { /// Create a new JITState instance - fn new(iseq: IseqPtr, num_insns: usize, num_blocks: usize) -> Self { + fn new(iseq: IseqPtr, num_insns: usize, num_blocks: usize, c_stack_bytes: usize) -> Self { JITState { iseq, opnds: vec![None; num_insns], labels: vec![None; num_blocks], branch_iseqs: Vec::default(), + c_stack_bytes, } } @@ -179,7 +183,8 @@ fn gen_iseq(cb: &mut CodeBlock, iseq: IseqPtr) -> Option<(CodePtr, Vec<(Rc Option<(CodePtr, Vec<(Rc, IseqPtr)>)> { - let mut jit = JITState::new(iseq, function.num_insns(), function.num_blocks()); + let c_stack_bytes = aligned_stack_bytes(max_num_params(function).saturating_sub(ALLOC_REGS.len())); + let mut jit = JITState::new(iseq, function.num_insns(), function.num_blocks(), c_stack_bytes); let mut asm = Assembler::new(); // Compile each basic block @@ -195,6 +200,13 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Optio // Set up the frame at the first block if block_id == BlockId(0) { asm.frame_setup(); + + // Bump the C stack pointer for basic block arguments + if jit.c_stack_bytes > 0 { + asm_comment!(asm, "bump C stack pointer"); + let new_sp = asm.sub(NATIVE_STACK_PTR, jit.c_stack_bytes.into()); + asm.mov(NATIVE_STACK_PTR, new_sp); + } } // Compile all parameters @@ -252,7 +264,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::SendWithoutBlock { call_info, cd, state, self_val, args, .. } => gen_send_without_block(jit, asm, call_info, *cd, &function.frame_state(*state), self_val, args)?, Insn::SendWithoutBlockDirect { cme, iseq, self_val, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(self_val), args, &function.frame_state(*state))?, Insn::InvokeBuiltin { bf, args, state } => gen_invokebuiltin(jit, asm, &function.frame_state(*state), bf, args)?, - Insn::Return { val } => return Some(gen_return(asm, opnd!(val))?), + Insn::Return { val } => return Some(gen_return(jit, asm, opnd!(val))?), Insn::FixnumAdd { left, right, state } => gen_fixnum_add(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?, Insn::FixnumSub { left, right, state } => gen_fixnum_sub(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?, Insn::FixnumMult { left, right, state } => gen_fixnum_mult(jit, asm, opnd!(left), opnd!(right), &function.frame_state(*state))?, @@ -518,7 +530,16 @@ fn gen_branch_params(jit: &mut JITState, asm: &mut Assembler, branch: &BranchEdg asm_comment!(asm, "set branch params: {}", branch.args.len()); let mut moves: Vec<(Reg, Opnd)> = vec![]; for (idx, &arg) in branch.args.iter().enumerate() { - moves.push((param_reg(idx), jit.get_opnd(arg)?)); + match param_opnd(idx) { + Opnd::Reg(reg) => { + // If a parameter is a register, we need to parallel-move it + moves.push((reg, jit.get_opnd(arg)?)); + }, + param => { + // If a parameter is memory, we set it beforehand + asm.mov(param, jit.get_opnd(arg)?); + } + } } asm.parallel_mov(moves); } @@ -555,7 +576,13 @@ fn gen_const(val: VALUE) -> lir::Opnd { /// Compile a basic block argument fn gen_param(asm: &mut Assembler, idx: usize) -> lir::Opnd { - asm.live_reg_opnd(Opnd::Reg(param_reg(idx))) + // Allocate a register or a stack slot + match param_opnd(idx) { + // If it's a register, insert LiveReg instruction to reserve the register + // in the register pool for register allocation. + param @ Opnd::Reg(_) => asm.live_reg_opnd(param), + param => param, + } } /// Compile a jump to a basic block @@ -797,7 +824,7 @@ fn gen_new_range( } /// Compile code that exits from JIT code with a return value -fn gen_return(asm: &mut Assembler, val: lir::Opnd) -> Option<()> { +fn gen_return(jit: &JITState, asm: &mut Assembler, val: lir::Opnd) -> Option<()> { // Pop the current frame (ec->cfp++) // Note: the return PC is already in the previous CFP asm_comment!(asm, "pop stack frame"); @@ -805,6 +832,13 @@ fn gen_return(asm: &mut Assembler, val: lir::Opnd) -> Option<()> { asm.mov(CFP, incr_cfp); asm.mov(Opnd::mem(64, EC, RUBY_OFFSET_EC_CFP), CFP); + // Restore the C stack pointer bumped for basic block arguments + if jit.c_stack_bytes > 0 { + asm_comment!(asm, "restore C stack pointer"); + let new_sp = asm.add(NATIVE_STACK_PTR, jit.c_stack_bytes.into()); + asm.mov(NATIVE_STACK_PTR, new_sp); + } + asm.frame_teardown(); // Return from the function @@ -992,17 +1026,15 @@ fn gen_push_frame(asm: &mut Assembler, argc: usize, state: &FrameState, frame: C asm.mov(cfp_opnd(RUBY_OFFSET_CFP_BLOCK_CODE), 0.into()); } -/// Return a register we use for the basic block argument at a given index -fn param_reg(idx: usize) -> Reg { - // To simplify the implementation, allocate a fixed register for each basic block argument for now. +/// Return an operand we use for the basic block argument at a given index +fn param_opnd(idx: usize) -> Opnd { + // To simplify the implementation, allocate a fixed register or a stack slot for each basic block argument for now. // TODO: Allow allocating arbitrary registers for basic block arguments - if idx >= ALLOC_REGS.len() { - unimplemented!( - "register spilling not yet implemented, too many basic block arguments ({}/{})", - idx + 1, ALLOC_REGS.len() - ); + if idx < ALLOC_REGS.len() { + Opnd::Reg(ALLOC_REGS[idx]) + } else { + Opnd::mem(64, NATIVE_STACK_PTR, -((idx - ALLOC_REGS.len() + 1) as i32) * SIZEOF_VALUE_I32) } - ALLOC_REGS[idx] } /// Inverse of ep_offset_to_local_idx(). See ep_offset_to_local_idx() for details. @@ -1045,6 +1077,7 @@ fn side_exit(jit: &mut JITState, state: &FrameState) -> Option { pc: state.pc, stack, locals, + c_stack_bytes: jit.c_stack_bytes, }; Some(target) } @@ -1063,6 +1096,28 @@ fn iseq_entry_escapes_ep(iseq: IseqPtr) -> bool { } } +/// Returne the maximum number of arguments for a block in a given function +fn max_num_params(function: &Function) -> usize { + let reverse_post_order = function.rpo(); + reverse_post_order.iter().map(|&block_id| { + let block = function.block(block_id); + block.params().len() + }).max().unwrap_or(0) +} + +/// Given the number of spill slots needed for a function, return the number of bytes +/// the function needs to allocate on the stack for the stack frame. +fn aligned_stack_bytes(num_slots: usize) -> usize { + // Both x86_64 and arm64 require the stack to be aligned to 16 bytes. + // Since SIZEOF_VALUE is 8 bytes, we need to round up the size to the nearest even number. + let num_slots = if num_slots % 2 == 0 { + num_slots + } else { + num_slots + 1 + }; + num_slots * SIZEOF_VALUE +} + impl Assembler { /// Make a C call while marking the start and end positions of it fn ccall_with_branch(&mut self, fptr: *const u8, opnds: Vec, branch: &Rc) -> Opnd { From 1d31c98e04098d406ae97fd51698533ab4933a0b Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 2 Jul 2025 10:46:08 -0700 Subject: [PATCH 089/130] ZJIT: Avoid panicing with "Option::unwrap() on None" (#13762) --- zjit/src/backend/lir.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index c168170b2fdd56..4cc093ed5e55e1 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -1525,7 +1525,12 @@ impl Assembler // Convert live_ranges to live_regs: the number of live registers at each index let mut live_regs: Vec = vec![]; for insn_idx in 0..insns.len() { - let live_count = live_ranges.iter().filter(|range| range.start() <= insn_idx && insn_idx <= range.end()).count(); + let live_count = live_ranges.iter().filter(|range| + match (range.start, range.end) { + (Some(start), Some(end)) => start <= insn_idx && insn_idx <= end, + _ => false, + } + ).count(); live_regs.push(live_count); } @@ -1561,7 +1566,8 @@ impl Assembler // If C_RET_REG is in use, move it to another register. // This must happen before last-use registers are deallocated. if let Some(vreg_idx) = pool.vreg_for(&C_RET_REG) { - let new_reg = pool.alloc_reg(vreg_idx).unwrap(); // TODO: support spill + let new_reg = pool.alloc_reg(vreg_idx) + .expect("spilling VReg is not implemented yet, can't evacuate C_RET_REG on CCall"); // TODO: support spilling VReg asm.mov(Opnd::Reg(new_reg), C_RET_OPND); pool.dealloc_reg(&C_RET_REG); reg_mapping[vreg_idx] = Some(new_reg); From e240b415a5286f7ec0b1edfc5a1a540c62118fd4 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 2 Jul 2025 14:57:09 -0400 Subject: [PATCH 090/130] ZJIT: Add reason for SideExit (#13768) This makes it clearer what is unimplemented when looking at HIR dumps. --- zjit/src/codegen.rs | 2 +- zjit/src/cruby.rs | 3 ++ zjit/src/hir.rs | 76 +++++++++++++++++++++++++++++---------------- 3 files changed, 53 insertions(+), 28 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 6d73a3a32df1a4..9fa088c0d1c7dc 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -287,7 +287,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::SetLocal { val, ep_offset, level } => return gen_nested_setlocal(asm, opnd!(val), *ep_offset, *level), Insn::GetConstantPath { ic, state } => gen_get_constant_path(asm, *ic, &function.frame_state(*state)), Insn::SetIvar { self_val, id, val, state: _ } => return gen_setivar(asm, opnd!(self_val), *id, opnd!(val)), - Insn::SideExit { state } => return gen_side_exit(jit, asm, &function.frame_state(*state)), + Insn::SideExit { state, reason: _ } => return gen_side_exit(jit, asm, &function.frame_state(*state)), Insn::PutSpecialObject { value_type } => gen_putspecialobject(asm, *value_type), Insn::AnyToString { val, str, state } => gen_anytostring(asm, opnd!(val), opnd!(str), &function.frame_state(*state))?, Insn::Defined { op_type, obj, pushval, v } => gen_defined(jit, asm, *op_type, *obj, *pushval, opnd!(v))?, diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index e5b66be8508fdf..82f0e3980452fb 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -218,6 +218,9 @@ pub use rb_vm_get_special_object as vm_get_special_object; /// Helper so we can get a Rust string for insn_name() pub fn insn_name(opcode: usize) -> String { + if opcode >= VM_INSTRUCTION_SIZE.try_into().unwrap() { + return "".into(); + } unsafe { // Look up Ruby's NULL-terminated insn name string let op_name = raw_insn_name(VALUE(opcode)); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 30a6da201ed82b..be320f6d741a5c 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -393,6 +393,28 @@ impl PtrPrintMap { } } +#[derive(Debug, Clone)] +pub enum SideExitReason { + UnknownNewarraySend(vm_opt_newarray_send_type), + UnknownCallType, + UnknownOpcode(u32), +} + +impl std::fmt::Display for SideExitReason { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + SideExitReason::UnknownOpcode(opcode) => write!(f, "UnknownOpcode({})", insn_name(*opcode as usize)), + SideExitReason::UnknownNewarraySend(VM_OPT_NEWARRAY_SEND_MAX) => write!(f, "UnknownNewarraySend(MAX)"), + SideExitReason::UnknownNewarraySend(VM_OPT_NEWARRAY_SEND_MIN) => write!(f, "UnknownNewarraySend(MIN)"), + SideExitReason::UnknownNewarraySend(VM_OPT_NEWARRAY_SEND_HASH) => write!(f, "UnknownNewarraySend(HASH)"), + SideExitReason::UnknownNewarraySend(VM_OPT_NEWARRAY_SEND_PACK) => write!(f, "UnknownNewarraySend(PACK)"), + SideExitReason::UnknownNewarraySend(VM_OPT_NEWARRAY_SEND_PACK_BUFFER) => write!(f, "UnknownNewarraySend(PACK_BUFFER)"), + SideExitReason::UnknownNewarraySend(VM_OPT_NEWARRAY_SEND_INCLUDE_P) => write!(f, "UnknownNewarraySend(INCLUDE_P)"), + _ => write!(f, "{self:?}"), + } + } +} + /// An instruction in the SSA IR. The output of an instruction is referred to by the index of /// the instruction ([`InsnId`]). SSA form enables this, and [`UnionFind`] ([`Function::find`]) /// helps with editing. @@ -518,7 +540,7 @@ pub enum Insn { PatchPoint(Invariant), /// Side-exit into the interpreter. - SideExit { state: InsnId }, + SideExit { state: InsnId, reason: SideExitReason }, } impl Insn { @@ -714,7 +736,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::ArrayPush { array, val, .. } => write!(f, "ArrayPush {array}, {val}"), Insn::ObjToString { val, .. } => { write!(f, "ObjToString {val}") }, Insn::AnyToString { val, str, .. } => { write!(f, "AnyToString {val}, str: {str}") }, - Insn::SideExit { .. } => write!(f, "SideExit"), + Insn::SideExit { reason, .. } => write!(f, "SideExit {reason}"), Insn::PutSpecialObject { value_type } => write!(f, "PutSpecialObject {value_type}"), Insn::Throw { throw_state, val } => { let mut state_string = match throw_state & VM_THROW_STATE_MASK { @@ -1863,7 +1885,7 @@ impl Function { worklist.push_back(state); } Insn::GetGlobal { state, .. } | - Insn::SideExit { state } => worklist.push_back(state), + Insn::SideExit { state, .. } => worklist.push_back(state), } } // Now remove all unnecessary instructions @@ -2425,7 +2447,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { VM_OPT_NEWARRAY_SEND_MAX => (BOP_MAX, Insn::ArrayMax { elements, state: exit_id }), _ => { // Unknown opcode; side-exit into the interpreter - fun.push_insn(block, Insn::SideExit { state: exit_id }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownNewarraySend(method) }); break; // End the block }, }; @@ -2651,7 +2673,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { // Unknown call type; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownCallType }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -2677,7 +2699,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { // Unknown call type; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownCallType }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -2708,7 +2730,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { // Unknown call type; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownCallType }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -2768,7 +2790,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { // Unknown call type; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownCallType }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -2796,7 +2818,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { if unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) { // Unknown call type; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownCallType }); break; // End the block } let argc = unsafe { vm_ci_argc((*cd).ci) }; @@ -2920,7 +2942,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { _ => { // Unknown opcode; side-exit into the interpreter let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state }); - fun.push_insn(block, Insn::SideExit { state: exit_id }); + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnknownOpcode(opcode) }); break; // End the block } } @@ -3962,7 +3984,7 @@ mod tests { fn test: bb0(v0:BasicObject, v1:BasicObject): v4:ArrayExact = ToArray v1 - SideExit + SideExit UnknownCallType "#]]); } @@ -3974,7 +3996,7 @@ mod tests { assert_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject): - SideExit + SideExit UnknownCallType "#]]); } @@ -3987,7 +4009,7 @@ mod tests { fn test: bb0(v0:BasicObject, v1:BasicObject): v3:Fixnum[1] = Const Value(1) - SideExit + SideExit UnknownCallType "#]]); } @@ -3999,7 +4021,7 @@ mod tests { assert_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject): - SideExit + SideExit UnknownCallType "#]]); } @@ -4013,7 +4035,7 @@ mod tests { assert_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): - SideExit + SideExit UnknownOpcode(invokesuper) "#]]); } @@ -4025,7 +4047,7 @@ mod tests { assert_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject): - SideExit + SideExit UnknownOpcode(invokesuper) "#]]); } @@ -4037,7 +4059,7 @@ mod tests { assert_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject): - SideExit + SideExit UnknownOpcode(invokesuperforward) "#]]); } @@ -4058,7 +4080,7 @@ mod tests { v9:StaticSymbol[:b] = Const Value(VALUE(0x1008)) v10:Fixnum[1] = Const Value(1) v12:BasicObject = SendWithoutBlock v8, :core#hash_merge_ptr, v7, v9, v10 - SideExit + SideExit UnknownCallType "#]]); } @@ -4073,7 +4095,7 @@ mod tests { v4:ArrayExact = ToNewArray v1 v5:Fixnum[1] = Const Value(1) ArrayPush v4, v5 - SideExit + SideExit UnknownCallType "#]]); } @@ -4085,7 +4107,7 @@ mod tests { assert_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject): - SideExit + SideExit UnknownOpcode(sendforward) "#]]); } @@ -4154,7 +4176,7 @@ mod tests { v3:NilClassExact = Const Value(nil) v4:NilClassExact = Const Value(nil) v7:BasicObject = SendWithoutBlock v1, :+, v2 - SideExit + SideExit UnknownNewarraySend(MIN) "#]]); } @@ -4174,7 +4196,7 @@ mod tests { v3:NilClassExact = Const Value(nil) v4:NilClassExact = Const Value(nil) v7:BasicObject = SendWithoutBlock v1, :+, v2 - SideExit + SideExit UnknownNewarraySend(HASH) "#]]); } @@ -4196,7 +4218,7 @@ mod tests { v7:BasicObject = SendWithoutBlock v1, :+, v2 v8:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v9:StringExact = StringCopy v8 - SideExit + SideExit UnknownNewarraySend(PACK) "#]]); } @@ -4218,7 +4240,7 @@ mod tests { v3:NilClassExact = Const Value(nil) v4:NilClassExact = Const Value(nil) v7:BasicObject = SendWithoutBlock v1, :+, v2 - SideExit + SideExit UnknownNewarraySend(INCLUDE_P) "#]]); } @@ -4642,7 +4664,7 @@ mod tests { v3:Fixnum[1] = Const Value(1) v5:BasicObject = ObjToString v3 v7:String = AnyToString v3, str: v5 - SideExit + SideExit UnknownOpcode(concatstrings) "#]]); } @@ -6220,7 +6242,7 @@ mod opt_tests { v2:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) v3:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v4:StringExact = StringCopy v3 - SideExit + SideExit UnknownOpcode(concatstrings) "#]]); } @@ -6236,7 +6258,7 @@ mod opt_tests { v3:Fixnum[1] = Const Value(1) v10:BasicObject = SendWithoutBlock v3, :to_s v7:String = AnyToString v3, str: v10 - SideExit + SideExit UnknownOpcode(concatstrings) "#]]); } From d5f5a56bf291d2456366bfb824d4413d02465f87 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 2 Jul 2025 13:01:24 -0700 Subject: [PATCH 091/130] ZJIT: Reject ISEQs with too-large stack_max (#13770) --- .github/workflows/zjit-macos.yml | 2 +- zjit/src/asm/arm64/mod.rs | 2 +- zjit/src/codegen.rs | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 8e58605fe15f91..7060d6a2529931 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -125,6 +125,7 @@ jobs: ../src/bootstraptest/test_literal_suffix.rb \ ../src/bootstraptest/test_load.rb \ ../src/bootstraptest/test_marshal.rb \ + ../src/bootstraptest/test_massign.rb \ ../src/bootstraptest/test_method.rb \ ../src/bootstraptest/test_objectspace.rb \ ../src/bootstraptest/test_string.rb \ @@ -136,7 +137,6 @@ jobs: ../src/bootstraptest/test_yjit_rust_port.rb # ../src/bootstraptest/test_eval.rb \ # ../src/bootstraptest/test_insns.rb \ - # ../src/bootstraptest/test_massign.rb \ # ../src/bootstraptest/test_proc.rb \ # ../src/bootstraptest/test_ractor.rb \ # ../src/bootstraptest/test_yjit.rb \ diff --git a/zjit/src/asm/arm64/mod.rs b/zjit/src/asm/arm64/mod.rs index 1e1b125eaaaf37..ef477821aa38a5 100644 --- a/zjit/src/asm/arm64/mod.rs +++ b/zjit/src/asm/arm64/mod.rs @@ -936,7 +936,7 @@ pub fn stur(cb: &mut CodeBlock, rt: A64Opnd, rn: A64Opnd) { let bytes: [u8; 4] = match (rt, rn) { (A64Opnd::Reg(rt), A64Opnd::Mem(rn)) => { assert!(rn.num_bits == 32 || rn.num_bits == 64); - assert!(mem_disp_fits_bits(rn.disp), "Expected displacement to be 9 bits or less"); + assert!(mem_disp_fits_bits(rn.disp), "Expected displacement {} to be 9 bits or less", rn.disp); LoadStore::stur(rt.reg_no, rn.base_reg_no, rn.disp as i16, rn.num_bits).into() }, diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 9fa088c0d1c7dc..419fc509832bdb 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -72,6 +72,14 @@ pub extern "C" fn rb_zjit_iseq_gen_entry_point(iseq: IseqPtr, _ec: EcPtr) -> *co return std::ptr::null(); } + // Reject ISEQs with very large temp stacks. + // We cannot encode too large offsets to access locals in arm64. + let stack_max = unsafe { rb_get_iseq_body_stack_max(iseq) }; + if stack_max >= i8::MAX as u32 { + debug!("ISEQ stack too large: {stack_max}"); + return std::ptr::null(); + } + // Take a lock to avoid writing to ISEQ in parallel with Ractors. // with_vm_lock() does nothing if the program doesn't use Ractors. let code_ptr = with_vm_lock(src_loc!(), || { From 4126c1c52219eec79f76f51687c5830234c5c6c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 24 Jun 2025 17:50:54 +0200 Subject: [PATCH 092/130] Adapt to upstream change in Bundler specs --- tool/sync_default_gems.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index f3985f6f81c4d6..7a231772b5cbb4 100755 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -136,7 +136,7 @@ def sync_default_gems(gem) cp_r("#{upstream}/bundler/spec", "spec/bundler") rm_rf("spec/bundler/bin") - ["parallel_rspec", "rspec"].each do |binstub| + ["bundle", "parallel_rspec", "rspec"].each do |binstub| content = File.read("#{upstream}/bundler/bin/#{binstub}").gsub("../spec", "../bundler") File.write("spec/bin/#{binstub}", content) chmod("+x", "spec/bin/#{binstub}") From f679202a0fbfe6dac1d6912742edf522c266e709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Fri, 27 Jun 2025 10:16:19 +0200 Subject: [PATCH 093/130] Remove old `bundle.rb` script usage --- common.mk | 8 ++------ tool/lib/bundle_env.rb | 4 ++++ 2 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 tool/lib/bundle_env.rb diff --git a/common.mk b/common.mk index 2a1e43604092be..bb193320b8dd27 100644 --- a/common.mk +++ b/common.mk @@ -1671,12 +1671,8 @@ test-bundler-prepare: $(TEST_RUNNABLE)-test-bundler-prepare no-test-bundler-prepare: no-test-bundler-precheck yes-test-bundler-prepare: yes-test-bundler-precheck $(ACTIONS_GROUP) - $(XRUBY) -C $(srcdir) -Ilib \ - -e 'ENV["GEM_HOME"] = File.expand_path(".bundle")' \ - -e 'ENV["BUNDLE_APP_CONFIG"] = File.expand_path(".bundle")' \ - -e 'ENV["BUNDLE_PATH__SYSTEM"] = "true"' \ - -e 'ENV["BUNDLE_WITHOUT"] = "lint doc"' \ - -e 'load "spec/bundler/support/bundle.rb"' -- install --quiet --gemfile=tool/bundler/dev_gems.rb + $(XRUBY) -C $(srcdir) -Ilib -r./tool/lib/bundle_env.rb \ + spec/bin/bundle install --quiet --gemfile=tool/bundler/dev_gems.rb $(ACTIONS_ENDGROUP) RSPECOPTS = -r formatter_overrides diff --git a/tool/lib/bundle_env.rb b/tool/lib/bundle_env.rb new file mode 100644 index 00000000000000..9ad5ea220b466b --- /dev/null +++ b/tool/lib/bundle_env.rb @@ -0,0 +1,4 @@ +ENV["GEM_HOME"] = File.expand_path("../../.bundle", __dir__) +ENV["BUNDLE_APP_CONFIG"] = File.expand_path("../../.bundle", __dir__) +ENV["BUNDLE_PATH__SYSTEM"] = "true" +ENV["BUNDLE_WITHOUT"] = "lint doc" From 81da38b3080a0d971d7b1720015117fef2d19c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 2 Jul 2025 21:25:57 +0200 Subject: [PATCH 094/130] Sync RubyGems --- lib/bundler/build_metadata.rb | 18 +++++------ lib/bundler/cli.rb | 2 +- spec/bin/bundle | 6 ++++ spec/bundler/bundler/build_metadata_spec.rb | 23 +++++++------- spec/bundler/bundler/shared_helpers_spec.rb | 2 +- spec/bundler/commands/version_spec.rb | 33 ++++++++++++++++---- spec/bundler/install/gems/standalone_spec.rb | 6 ++-- spec/bundler/support/build_metadata.rb | 12 ++++--- spec/bundler/support/builders.rb | 3 +- spec/bundler/support/bundle | 6 ++++ spec/bundler/support/bundle.rb | 6 ++-- spec/bundler/support/helpers.rb | 6 ++-- spec/bundler/support/path.rb | 19 +++++++++-- spec/bundler/support/rubygems_ext.rb | 4 +-- 14 files changed, 100 insertions(+), 46 deletions(-) create mode 100755 spec/bin/bundle create mode 100755 spec/bundler/support/bundle diff --git a/lib/bundler/build_metadata.rb b/lib/bundler/build_metadata.rb index 5d2a8b53bb58a6..49d2518078b206 100644 --- a/lib/bundler/build_metadata.rb +++ b/lib/bundler/build_metadata.rb @@ -4,21 +4,26 @@ module Bundler # Represents metadata from when the Bundler gem was built. module BuildMetadata # begin ivars - @release = false + @built_at = nil # end ivars # A hash representation of the build metadata. def self.to_h { - "Built At" => built_at, + "Timestamp" => timestamp, "Git SHA" => git_commit_sha, - "Released Version" => release?, } end + # A timestamp representing the date the bundler gem was built, or the + # current time if never built + def self.timestamp + @timestamp ||= @built_at || Time.now.utc.strftime("%Y-%m-%d").freeze + end + # A string representing the date the bundler gem was built. def self.built_at - @built_at ||= Time.now.utc.strftime("%Y-%m-%d").freeze + @built_at end # The SHA for the git commit the bundler gem was built from. @@ -34,10 +39,5 @@ def self.git_commit_sha @git_commit_sha ||= "unknown" end - - # Whether this is an official release build of Bundler. - def self.release? - @release - end end end diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 25e442c04f6f3c..bba60ddab4d3dc 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -486,7 +486,7 @@ def console(group = nil) def version cli_help = current_command.name == "cli_help" if cli_help || ARGV.include?("version") - build_info = " (#{BuildMetadata.built_at} commit #{BuildMetadata.git_commit_sha})" + build_info = " (#{BuildMetadata.timestamp} commit #{BuildMetadata.git_commit_sha})" end if !cli_help && Bundler.feature_flag.bundler_4_mode? diff --git a/spec/bin/bundle b/spec/bin/bundle new file mode 100755 index 00000000000000..8f8b5352951537 --- /dev/null +++ b/spec/bin/bundle @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative "../bundler/support/activate" + +load File.expand_path("bundle", Spec::Path.exedir) diff --git a/spec/bundler/bundler/build_metadata_spec.rb b/spec/bundler/bundler/build_metadata_spec.rb index afa2d1716fb390..2e69821f68fc7a 100644 --- a/spec/bundler/bundler/build_metadata_spec.rb +++ b/spec/bundler/bundler/build_metadata_spec.rb @@ -6,18 +6,20 @@ RSpec.describe Bundler::BuildMetadata do before do allow(Time).to receive(:now).and_return(Time.at(0)) - Bundler::BuildMetadata.instance_variable_set(:@built_at, nil) + Bundler::BuildMetadata.instance_variable_set(:@timestamp, nil) end - describe "#built_at" do - it "returns %Y-%m-%d formatted time" do - expect(Bundler::BuildMetadata.built_at).to eq "1970-01-01" + describe "#timestamp" do + it "returns %Y-%m-%d formatted current time if built_at not set" do + Bundler::BuildMetadata.instance_variable_set(:@built_at, nil) + expect(Bundler::BuildMetadata.timestamp).to eq "1970-01-01" end - end - describe "#release?" do - it "returns false as default" do - expect(Bundler::BuildMetadata.release?).to be_falsey + it "returns %Y-%m-%d formatted current time if built_at not set" do + Bundler::BuildMetadata.instance_variable_set(:@built_at, "2025-01-01") + expect(Bundler::BuildMetadata.timestamp).to eq "2025-01-01" + ensure + Bundler::BuildMetadata.instance_variable_set(:@built_at, nil) end end @@ -40,10 +42,9 @@ describe "#to_h" do subject { Bundler::BuildMetadata.to_h } - it "returns a hash includes Built At, Git SHA and Released Version" do - expect(subject["Built At"]).to eq "1970-01-01" + it "returns a hash includes Timestamp, and Git SHA" do + expect(subject["Timestamp"]).to eq "1970-01-01" expect(subject["Git SHA"]).to be_instance_of(String) - expect(subject["Released Version"]).to be_falsey end end end diff --git a/spec/bundler/bundler/shared_helpers_spec.rb b/spec/bundler/bundler/shared_helpers_spec.rb index 42271167d66b19..356858070105d1 100644 --- a/spec/bundler/bundler/shared_helpers_spec.rb +++ b/spec/bundler/bundler/shared_helpers_spec.rb @@ -423,7 +423,7 @@ it "sets BUNDLE_BIN_PATH to the bundle executable file" do subject.set_bundle_environment bin_path = ENV["BUNDLE_BIN_PATH"] - expect(bin_path).to eq(bindir.join("bundle").to_s) + expect(bin_path).to eq(exedir.join("bundle").to_s) expect(File.exist?(bin_path)).to be true end end diff --git a/spec/bundler/commands/version_spec.rb b/spec/bundler/commands/version_spec.rb index d655e760b5e3f8..1019803c8705d4 100644 --- a/spec/bundler/commands/version_spec.rb +++ b/spec/bundler/commands/version_spec.rb @@ -32,13 +32,34 @@ end context "with version" do - it "outputs the version, virtual version if set, and build metadata" do - bundle "version" - expect(out).to match(/\ABundler version #{Regexp.escape(Bundler::VERSION)} \(\d{4}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + context "when released", :ruby_repo do + before do + system_gems "bundler-2.9.9", released: true + end - bundle "config simulate_version 4" - bundle "version" - expect(out).to match(/\A#{Regexp.escape(Bundler::VERSION)} \(simulating Bundler 4\) \(\d{4}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + it "outputs the version, virtual version if set, and build metadata" do + bundle "version" + expect(out).to match(/\ABundler version 2\.9\.9 \(2100-01-01 commit #{COMMIT_HASH}\)\z/) + + bundle "config simulate_version 4" + bundle "version" + expect(out).to match(/\A2\.9\.9 \(simulating Bundler 4\) \(2100-01-01 commit #{COMMIT_HASH}\)\z/) + end + end + + context "when not released" do + before do + system_gems "bundler-2.9.9", released: false + end + + it "outputs the version, virtual version if set, and build metadata" do + bundle "version" + expect(out).to match(/\ABundler version 2\.9\.9 \(20\d{2}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + + bundle "config simulate_version 4" + bundle "version" + expect(out).to match(/\A2\.9\.9 \(simulating Bundler 4\) \(20\d{2}-\d{2}-\d{2} commit #{COMMIT_HASH}\)\z/) + end end end end diff --git a/spec/bundler/install/gems/standalone_spec.rb b/spec/bundler/install/gems/standalone_spec.rb index c286a332baeaa2..e0f87572dade48 100644 --- a/spec/bundler/install/gems/standalone_spec.rb +++ b/spec/bundler/install/gems/standalone_spec.rb @@ -141,8 +141,7 @@ describe "with default gems and a lockfile", :ruby_repo do it "works and points to the vendored copies, not to the default copies" do - base_system_gems "psych", "etc", path: scoped_gem_path(bundled_app("bundle")) - base_system_gems "stringio", path: scoped_gem_path(bundled_app("bundle")) if Gem.ruby_version < Gem::Version.new("3.3.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") + base_system_gems "stringio", "psych", "etc", path: scoped_gem_path(bundled_app("bundle")) build_gem "foo", "1.0.0", to_system: true, default: true do |s| s.add_dependency "bar" @@ -179,8 +178,7 @@ it "works for gems with extensions and points to the vendored copies, not to the default copies" do simulate_platform "arm64-darwin-23" do - base_system_gems "psych", "etc", "shellwords", "open3", path: scoped_gem_path(bundled_app("bundle")) - base_system_gems "stringio", path: scoped_gem_path(bundled_app("bundle")) if Gem.ruby_version < Gem::Version.new("3.3.0.a") || Gem.rubygems_version < Gem::Version.new("3.6.0.a") + base_system_gems "stringio", "psych", "etc", "shellwords", "open3", path: scoped_gem_path(bundled_app("bundle")) build_gem "baz", "1.0.0", to_system: true, default: true, &:add_c_extension diff --git a/spec/bundler/support/build_metadata.rb b/spec/bundler/support/build_metadata.rb index 189100edb7748c..2eade4137bd68f 100644 --- a/spec/bundler/support/build_metadata.rb +++ b/spec/bundler/support/build_metadata.rb @@ -8,11 +8,10 @@ module BuildMetadata include Spec::Path include Spec::Helpers - def write_build_metadata(dir: source_root) + def write_build_metadata(dir: source_root, version: Bundler::VERSION) build_metadata = { git_commit_sha: git_commit_sha, - built_at: loaded_gemspec.date.utc.strftime("%Y-%m-%d"), - release: true, + built_at: release_date_for(version, dir: dir), } replace_build_metadata(build_metadata, dir: dir) @@ -20,7 +19,7 @@ def write_build_metadata(dir: source_root) def reset_build_metadata(dir: source_root) build_metadata = { - release: false, + built_at: nil, } replace_build_metadata(build_metadata, dir: dir) @@ -44,6 +43,11 @@ def git_commit_sha ruby_core_tarball? ? "unknown" : git("rev-parse --short HEAD", source_root).strip end + def release_date_for(version, dir:) + changelog = File.expand_path("CHANGELOG.md", dir) + File.readlines(changelog)[2].scan(/^## #{Regexp.escape(version)} \((.*)\)/).first&.first if File.exist?(changelog) + end + extend self end end diff --git a/spec/bundler/support/builders.rb b/spec/bundler/support/builders.rb index a0c91b71d2abad..5cfbed3864c39c 100644 --- a/spec/bundler/support/builders.rb +++ b/spec/bundler/support/builders.rb @@ -450,9 +450,10 @@ def _build(options = {}) end @context.replace_version_file(@version, dir: build_path) + @context.replace_changelog(@version, dir: build_path) if options[:released] @context.replace_required_ruby_version(@required_ruby_version, dir: build_path) if @required_ruby_version - Spec::BuildMetadata.write_build_metadata(dir: build_path) + Spec::BuildMetadata.write_build_metadata(dir: build_path, version: @version) @context.gem_command "build #{@context.relative_gemspec}", dir: build_path diff --git a/spec/bundler/support/bundle b/spec/bundler/support/bundle new file mode 100755 index 00000000000000..8f8b5352951537 --- /dev/null +++ b/spec/bundler/support/bundle @@ -0,0 +1,6 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require_relative "../bundler/support/activate" + +load File.expand_path("bundle", Spec::Path.exedir) diff --git a/spec/bundler/support/bundle.rb b/spec/bundler/support/bundle.rb index 5d6d6580405c0f..aa7b12170694e3 100644 --- a/spec/bundler/support/bundle.rb +++ b/spec/bundler/support/bundle.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true -require_relative "activate" +require_relative "path" -load File.expand_path("bundle", Spec::Path.bindir) +warn "#{__FILE__} is deprecated. Please use #{Spec::Path.dev_binstub} instead" + +load Spec::Path.dev_binstub diff --git a/spec/bundler/support/helpers.rb b/spec/bundler/support/helpers.rb index 9127cf7838985f..26571126637b49 100644 --- a/spec/bundler/support/helpers.rb +++ b/spec/bundler/support/helpers.rb @@ -316,7 +316,7 @@ def system_gems(*gems) gem_name = g.to_s if gem_name.start_with?("bundler") version = gem_name.match(/\Abundler-(?.*)\z/)[:version] if gem_name != "bundler" - with_built_bundler(version) {|gem_path| install_gem(gem_path, install_dir, default) } + with_built_bundler(version, released: options.fetch(:released, false)) {|gem_path| install_gem(gem_path, install_dir, default) } elsif %r{\A(?:[a-zA-Z]:)?/.*\.gem\z}.match?(gem_name) install_gem(gem_name, install_dir, default) else @@ -341,10 +341,10 @@ def install_gem(path, install_dir, default = false) gem_command "install #{args} '#{path}'" end - def with_built_bundler(version = nil, &block) + def with_built_bundler(version = nil, opts = {}, &block) require_relative "builders" - Builders::BundlerBuilder.new(self, "bundler", version)._build(&block) + Builders::BundlerBuilder.new(self, "bundler", version)._build(opts, &block) end def with_gem_path_as(path) diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb index 8fbac5cc5aafe2..c4d2f06cbfe29d 100644 --- a/spec/bundler/support/path.rb +++ b/spec/bundler/support/path.rb @@ -45,8 +45,16 @@ def dev_gemfile @dev_gemfile ||= tool_dir.join("dev_gems.rb") end + def dev_binstub + @dev_binstub ||= bindir.join("bundle") + end + def bindir - @bindir ||= source_root.join(ruby_core? ? "libexec" : "exe") + @bindir ||= source_root.join(ruby_core? ? "spec/bin" : "bin") + end + + def exedir + @exedir ||= source_root.join(ruby_core? ? "libexec" : "exe") end def installed_bindir @@ -63,7 +71,7 @@ def gem_bin def path env_path = ENV["PATH"] - env_path = env_path.split(File::PATH_SEPARATOR).reject {|path| path == bindir.to_s }.join(File::PATH_SEPARATOR) if ruby_core? + env_path = env_path.split(File::PATH_SEPARATOR).reject {|path| path == exedir.to_s }.join(File::PATH_SEPARATOR) if ruby_core? env_path end @@ -280,6 +288,13 @@ def replace_required_ruby_version(version, dir:) File.open(gemspec_file, "w") {|f| f << contents } end + def replace_changelog(version, dir:) + changelog = File.expand_path("CHANGELOG.md", dir) + contents = File.readlines(changelog) + contents = [contents[0], contents[1], "## #{version} (2100-01-01)\n", *contents[3..-1]].join + File.open(changelog, "w") {|f| f << contents } + end + def git_root ruby_core? ? source_root : source_root.parent end diff --git a/spec/bundler/support/rubygems_ext.rb b/spec/bundler/support/rubygems_ext.rb index 43d7ef5456e8eb..2d681529aac2ef 100644 --- a/spec/bundler/support/rubygems_ext.rb +++ b/spec/bundler/support/rubygems_ext.rb @@ -52,7 +52,7 @@ def test_setup def setup_test_paths ENV["BUNDLE_PATH"] = nil ENV["PATH"] = [Path.system_gem_path("bin"), ENV["PATH"]].join(File::PATH_SEPARATOR) - ENV["PATH"] = [Path.bindir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core? + ENV["PATH"] = [Path.exedir, ENV["PATH"]].join(File::PATH_SEPARATOR) if Path.ruby_core? end def install_test_deps @@ -100,7 +100,7 @@ def dev_bundle(*args, gemfile: dev_gemfile, path: nil) require "shellwords" # We don't use `Open3` here because it does not work on JRuby + Windows - output = `ruby #{File.expand_path("support/bundle.rb", Path.spec_dir)} #{args.shelljoin}` + output = `ruby #{Path.dev_binstub} #{args.shelljoin}` raise output unless $?.success? output ensure From 9782bd52353526a50b77ddba3687d7e921d06eaa Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 3 Jul 2025 13:19:41 +0900 Subject: [PATCH 095/130] Drop HTTP support in downloader.rb The only use case is access to `repo.or.cz`, and it redirects HTTP requests to HTTPS now. --- tool/downloader.rb | 68 ++++++---------------------------------------- 1 file changed, 8 insertions(+), 60 deletions(-) diff --git a/tool/downloader.rb b/tool/downloader.rb index a1520eb6a95c56..e266f3d173ba23 100644 --- a/tool/downloader.rb +++ b/tool/downloader.rb @@ -1,41 +1,12 @@ # Used by configure and make to download or update mirrored Ruby and GCC -# files. This will use HTTPS if possible, falling back to HTTP. +# files. # -*- frozen-string-literal: true -*- require 'fileutils' require 'open-uri' require 'pathname' -begin - require 'net/https' -rescue LoadError - https = 'http' -else - https = 'https' - - # open-uri of ruby 2.2.0 accepts an array of PEMs as ssl_ca_cert, but old - # versions do not. so, patching OpenSSL::X509::Store#add_file instead. - class OpenSSL::X509::Store - alias orig_add_file add_file - def add_file(pems) - Array(pems).each do |pem| - if File.directory?(pem) - add_path pem - else - orig_add_file pem - end - end - end - end - # since open-uri internally checks ssl_ca_cert using File.directory?, - # allow to accept an array. - class < e - m1, m2 = e.message.split("\n", 2) - STDERR.puts "Download failed (#{m1}), try another URL\n#{m2}" - super("https://raw.githubusercontent.com/gcc-mirror/gcc/master/#{name}", name, *rest, **options) - end - else - super("https://repo.or.cz/official-gcc.git/blob_plain/HEAD:/#{name}", name, *rest, **options) + begin + super("https://cdn.jsdelivr.net/gh/gcc-mirror/gcc@master/#{name}", name, *rest, **options) + rescue => e + m1, m2 = e.message.split("\n", 2) + STDERR.puts "Download failed (#{m1}), try another URL\n#{m2}" + super("https://raw.githubusercontent.com/gcc-mirror/gcc/master/#{name}", name, *rest, **options) end end end @@ -222,11 +177,6 @@ def self.download(url, name, dir = nil, since = true, if link_cache(cache, file, name, verbose: verbose) return file.to_path end - if !https? and URI::HTTPS === url - warn "*** using http instead of https ***" - url.scheme = 'http' - url = URI(url.to_s) - end if verbose $stdout.print "downloading #{name} ... " $stdout.flush @@ -386,8 +336,6 @@ def self.with_retry(max_times, &block) private_class_method :with_retry end -Downloader.https = https.freeze - if $0 == __FILE__ since = true options = {} From 319062e4a0608c474e9c934fc4a1171ea2aa1269 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 3 Jul 2025 13:23:36 +0900 Subject: [PATCH 096/130] Prefer autotools repository mirror for build-aux files gcc master is still using 2021 version files. --- tool/downloader.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/tool/downloader.rb b/tool/downloader.rb index e266f3d173ba23..b5ca416ac5b7e3 100644 --- a/tool/downloader.rb +++ b/tool/downloader.rb @@ -20,13 +20,20 @@ def self.get_option(argv, options) end class GNU < self + Mirrors = %w[ + https://raw.githubusercontent.com/autotools-mirror/autoconf/refs/heads/master/build-aux/ + https://cdn.jsdelivr.net/gh/gcc-mirror/gcc@master + ] + def self.download(name, *rest, **options) - begin - super("https://cdn.jsdelivr.net/gh/gcc-mirror/gcc@master/#{name}", name, *rest, **options) + Mirrors.each_with_index do |url, i| + super("#{url}/#{name}", name, *rest, **options) rescue => e + raise if i + 1 == Mirrors.size # no more URLs m1, m2 = e.message.split("\n", 2) STDERR.puts "Download failed (#{m1}), try another URL\n#{m2}" - super("https://raw.githubusercontent.com/gcc-mirror/gcc/master/#{name}", name, *rest, **options) + else + return end end end From 5817e58a60354050cfa059ec8e89202b6e9598d4 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 3 Jul 2025 13:29:32 +0900 Subject: [PATCH 097/130] Extract last-modified time after fetch completes --- tool/downloader.rb | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tool/downloader.rb b/tool/downloader.rb index b5ca416ac5b7e3..14f18747f3849e 100644 --- a/tool/downloader.rb +++ b/tool/downloader.rb @@ -191,13 +191,7 @@ def self.download(url, name, dir = nil, since = true, mtime = nil options = options.merge(http_options(file, since.nil? ? true : since)) begin - data = with_retry(10) do - data = url.read(options) - if mtime = data.meta["last-modified"] - mtime = Time.httpdate(mtime) - end - data - end + data = with_retry(10) {url.read(options)} rescue OpenURI::HTTPError => http_error case http_error.message when /^304 / # 304 Not Modified @@ -225,6 +219,10 @@ def self.download(url, name, dir = nil, since = true, return file.to_path end raise + else + if mtime = data.meta["last-modified"] + mtime = Time.httpdate(mtime) + end end dest = (cache_save && cache && !cache.exist? ? cache : file) dest.parent.mkpath From c31bfd5467fb95c814db656a2d1d01641e10031b Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 3 Jul 2025 15:44:24 +0900 Subject: [PATCH 098/130] [DOC] Fix markup in security.rdoc --- doc/security.rdoc | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/security.rdoc b/doc/security.rdoc index e428036cf571de..fb0e4a52da39e4 100644 --- a/doc/security.rdoc +++ b/doc/security.rdoc @@ -37,7 +37,7 @@ programs for configuration and database persistence of Ruby object trees. Similar to +Marshal+, it is able to deserialize into arbitrary Ruby classes. For example, the following YAML data will create an +ERB+ object when -deserialized, using the `unsafe_load` method: +deserialized, using the +unsafe_load+ method: !ruby/object:ERB src: puts `uname` @@ -54,15 +54,15 @@ simply integers with names attached to them, so they are faster to look up in hashtables. Starting in version 2.2, most symbols can be garbage collected; these are -called mortal symbols. Most symbols you create (e.g. by calling +called _mortal_ symbols. Most symbols you create (e.g. by calling +to_sym+) are mortal. -Immortal symbols on the other hand will never be garbage collected. +_Immortal_ symbols on the other hand will never be garbage collected. They are created when modifying code: * defining a method (e.g. with +define_method+), * setting an instance variable (e.g. with +instance_variable_set+), * creating a variable or constant (e.g. with +const_set+) -C extensions that have not been updated and are still calling `SYM2ID` +C extensions that have not been updated and are still calling +SYM2ID+ will create immortal symbols. Bugs in 2.2.0: +send+ and +__send__+ also created immortal symbols, and calling methods with keyword arguments could also create some. @@ -136,4 +136,4 @@ expose to untrusted clients. When using DRb, try to avoid exposing it over the network if possible. If this isn't possible and you need to expose DRb to the world, you *must* configure an -appropriate security policy with DRb::ACL. +appropriate security policy with +DRb::ACL+. From a020e3490a1487d351868d3283e7881f03b3d7d2 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 3 Jul 2025 15:46:57 +0900 Subject: [PATCH 099/130] [DOC] Deleted the description about 2.2 and earlier --- doc/security.rdoc | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/doc/security.rdoc b/doc/security.rdoc index fb0e4a52da39e4..b7153ff0e96b0e 100644 --- a/doc/security.rdoc +++ b/doc/security.rdoc @@ -53,9 +53,8 @@ method, variable and constant names. The reason for this is that symbols are simply integers with names attached to them, so they are faster to look up in hashtables. -Starting in version 2.2, most symbols can be garbage collected; these are -called _mortal_ symbols. Most symbols you create (e.g. by calling -+to_sym+) are mortal. +Most symbols can be garbage collected; these are called _mortal_ +symbols. Most symbols you create (e.g. by calling +to_sym+) are mortal. _Immortal_ symbols on the other hand will never be garbage collected. They are created when modifying code: @@ -64,8 +63,6 @@ They are created when modifying code: * creating a variable or constant (e.g. with +const_set+) C extensions that have not been updated and are still calling +SYM2ID+ will create immortal symbols. -Bugs in 2.2.0: +send+ and +__send__+ also created immortal symbols, -and calling methods with keyword arguments could also create some. Don't create immortal symbols from user inputs. Otherwise, this would allow a user to mount a denial of service attack against your application by From 517c1067098957a7ad73cd611072f8d769db8139 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Wed, 2 Jul 2025 13:10:24 +0200 Subject: [PATCH 100/130] imemo_fields_set: save copying when reassigning a variable If we still fit in the existing imemo/fields object we can update it atomically, saving a reallocation. --- internal/class.h | 4 +--- internal/gc.h | 20 ++++++++++++++++++++ variable.c | 25 ++++++++++++++++++++----- 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/internal/class.h b/internal/class.h index f71583d61aef87..f8cfba3fd963b4 100644 --- a/internal/class.h +++ b/internal/class.h @@ -566,9 +566,7 @@ RCLASSEXT_SET_FIELDS_OBJ(VALUE obj, rb_classext_t *ext, VALUE fields_obj) { RUBY_ASSERT(RB_TYPE_P(obj, RUBY_T_CLASS) || RB_TYPE_P(obj, RUBY_T_MODULE)); - VALUE old_fields_obj = ext->fields_obj; - RUBY_ATOMIC_VALUE_SET(ext->fields_obj, fields_obj); - RB_OBJ_WRITTEN(obj, old_fields_obj, fields_obj); + RB_OBJ_ATOMIC_WRITE(obj, &ext->fields_obj, fields_obj); } static inline void diff --git a/internal/gc.h b/internal/gc.h index 06103ca25fd238..f0dc04fc58a954 100644 --- a/internal/gc.h +++ b/internal/gc.h @@ -264,6 +264,26 @@ int rb_gc_modular_gc_loaded_p(void); RUBY_SYMBOL_EXPORT_END +static inline VALUE +rb_obj_atomic_write( + VALUE a, VALUE *slot, VALUE b, + RBIMPL_ATTR_MAYBE_UNUSED() + const char *filename, + RBIMPL_ATTR_MAYBE_UNUSED() + int line) +{ +#ifdef RGENGC_LOGGING_WRITE + RGENGC_LOGGING_WRITE(a, slot, b, filename, line); +#endif + + RUBY_ATOMIC_VALUE_SET(*slot, b); + + rb_obj_written(a, RUBY_Qundef /* ignore `oldv' now */, b, filename, line); + return a; +} +#define RB_OBJ_ATOMIC_WRITE(old, slot, young) \ + RBIMPL_CAST(rb_obj_atomic_write((VALUE)(old), (VALUE *)(slot), (VALUE)(young), __FILE__, __LINE__)) + int rb_ec_stack_check(struct rb_execution_context_struct *ec); void rb_gc_writebarrier_remember(VALUE obj); const char *rb_obj_info(VALUE obj); diff --git a/variable.c b/variable.c index b450a51b496df9..66e17c43ad38ca 100644 --- a/variable.c +++ b/variable.c @@ -1844,6 +1844,9 @@ imemo_fields_set(VALUE klass, VALUE fields_obj, shape_id_t target_shape_id, ID f if (UNLIKELY(rb_shape_too_complex_p(target_shape_id))) { if (rb_shape_too_complex_p(current_shape_id)) { if (concurrent) { + // In multi-ractor case, we must always work on a copy because + // even if the field already exist, inserting in a st_table may + // cause a rebuild. fields_obj = rb_imemo_fields_clone(fields_obj); } } @@ -4680,9 +4683,7 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc attr_index_t next_capacity = RSHAPE_CAPACITY(next_shape_id); attr_index_t current_capacity = RSHAPE_CAPACITY(current_shape_id); - if (concurrent || next_capacity != current_capacity) { - RUBY_ASSERT(concurrent || next_capacity > current_capacity); - + if (next_capacity > current_capacity) { // We allocate a new fields_obj even when concurrency isn't a concern // so that we're embedded as long as possible. fields_obj = imemo_fields_copy_capa(rb_singleton_class(klass), fields_obj, next_capacity); @@ -4693,7 +4694,18 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc } VALUE *fields = rb_imemo_fields_ptr(fields_obj); - RB_OBJ_WRITE(fields_obj, &fields[index], val); + + if (concurrent && original_fields_obj == fields_obj) { + // In the concurrent case, if we're mutating the existing + // fields_obj, we must use an atomic write, because if we're + // adding a new field, the shape_id must be written after the field + // and if we're updating an existing field, we at least need a relaxed + // write to avoid reaping. + RB_OBJ_ATOMIC_WRITE(fields_obj, &fields[index], val); + } + else { + RB_OBJ_WRITE(fields_obj, &fields[index], val); + } if (!existing) { RBASIC_SET_SHAPE_ID(fields_obj, next_shape_id); @@ -4705,9 +4717,12 @@ class_fields_ivar_set(VALUE klass, VALUE fields_obj, ID id, VALUE val, bool conc too_complex: { if (concurrent && fields_obj == original_fields_obj) { - // If we're in the multi-ractor mode, we can't directly insert in the table. + // In multi-ractor case, we must always work on a copy because + // even if the field already exist, inserting in a st_table may + // cause a rebuild. fields_obj = rb_imemo_fields_clone(fields_obj); } + st_table *table = rb_imemo_fields_complex_tbl(fields_obj); existing = st_insert(table, (st_data_t)id, (st_data_t)val); RB_OBJ_WRITTEN(fields_obj, Qundef, val); From 1f976509a5c3c59939b5c0535ee4b69b1ea689cd Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 3 Jul 2025 10:52:32 +0200 Subject: [PATCH 101/130] symbol.c: enforce `intern_str` is always called with a lock Add missing locks in `rb_intern_str`, `rb_id_attrset` and `rb_intern3`. --- symbol.c | 57 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/symbol.c b/symbol.c index 0bd60aec342854..e9b01302922f21 100644 --- a/symbol.c +++ b/symbol.c @@ -173,8 +173,21 @@ rb_id_attrset(ID id) } } - /* make new symbol and ID */ - if (!(str = lookup_id_str(id))) { + bool error = false; + GLOBAL_SYMBOLS_LOCKING(symbols) { + /* make new symbol and ID */ + if ((str = lookup_id_str(id))) { + str = rb_str_dup(str); + rb_str_cat(str, "=", 1); + sym = lookup_str_sym(str); + id = sym ? rb_sym2id(sym) : intern_str(str, 1); + } + else { + error = true; + } + } + + if (error) { RBIMPL_ATTR_NONSTRING_ARRAY() static const char id_types[][8] = { "local", "instance", @@ -188,10 +201,7 @@ rb_id_attrset(ID id) rb_name_error(id, "cannot make anonymous %.*s ID %"PRIxVALUE" attrset", (int)sizeof(id_types[0]), id_types[scope], (VALUE)id); } - str = rb_str_dup(str); - rb_str_cat(str, "=", 1); - sym = lookup_str_sym(str); - id = sym ? rb_sym2id(sym) : intern_str(str, 1); + return id; } @@ -765,10 +775,20 @@ rb_intern3(const char *name, long len, rb_encoding *enc) struct RString fake_str; VALUE str = rb_setup_fake_str(&fake_str, name, len, enc); OBJ_FREEZE(str); - sym = lookup_str_sym(str); - if (sym) return rb_sym2id(sym); - str = rb_enc_str_new(name, len, enc); /* make true string */ - return intern_str(str, 1); + ID id; + + GLOBAL_SYMBOLS_LOCKING(symbols) { + sym = lookup_str_sym(str); + if (sym) { + id = rb_sym2id(sym); + } + else { + str = rb_enc_str_new(name, len, enc); /* make true string */ + id = intern_str(str, 1); + } + } + + return id; } static ID @@ -801,6 +821,8 @@ next_id_base(void) static ID intern_str(VALUE str, int mutable) { + ASSERT_vm_locking(); + ID id; ID nid; @@ -836,13 +858,18 @@ rb_intern(const char *name) ID rb_intern_str(VALUE str) { - VALUE sym = lookup_str_sym(str); - - if (sym) { - return SYM2ID(sym); + ID id; + GLOBAL_SYMBOLS_LOCKING(symbols) { + VALUE sym = lookup_str_sym(str); + if (sym) { + id = SYM2ID(sym); + } + else { + id = intern_str(str, 0); + } } - return intern_str(str, 0); + return id; } void From 4592d637399c105a19cd8d3d3d9038ba32af28a3 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 3 Jul 2025 21:44:08 +0900 Subject: [PATCH 102/130] Suppress a warning in code for SOCKS5 --- ext/socket/sockssocket.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ext/socket/sockssocket.c b/ext/socket/sockssocket.c index 1031812befd24d..f033f39b2e8888 100644 --- a/ext/socket/sockssocket.c +++ b/ext/socket/sockssocket.c @@ -30,7 +30,8 @@ socks_init(VALUE sock, VALUE host, VALUE port) static int init = 0; if (init == 0) { - SOCKSinit("ruby"); + char progname[] = "ruby"; + SOCKSinit(progname); init = 1; } From 8b2d76136ba00d634522fbe46e2131dd7c8fa99c Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 2 Jul 2025 11:26:28 -0400 Subject: [PATCH 103/130] Assume that the symbol is not garbage in rb_sym2id rb_sym2id is a public API, so it is always a bug if the user holds on to a dead object and passes it in. --- symbol.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/symbol.c b/symbol.c index e9b01302922f21..2f2960fbda3e02 100644 --- a/symbol.c +++ b/symbol.c @@ -953,7 +953,7 @@ rb_sym2id(VALUE sym) } else if (DYNAMIC_SYM_P(sym)) { GLOBAL_SYMBOLS_LOCKING(symbols) { - sym = dsymbol_check(symbols, sym); + RUBY_ASSERT(!rb_objspace_garbage_object_p(sym)); id = RSYMBOL(sym)->id; if (UNLIKELY(!(id & ~ID_SCOPE_MASK))) { From 57f4460f0c040bfaaa8486540bb88ffef6b8aa53 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 3 Jul 2025 09:22:45 -0700 Subject: [PATCH 104/130] ZJIT: Skip a hanging ractor test (#13774) --- bootstraptest/runner.rb | 4 ++++ bootstraptest/test_ractor.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/bootstraptest/runner.rb b/bootstraptest/runner.rb index 16bfdd9ea248d4..24bbdafe8ecd7a 100755 --- a/bootstraptest/runner.rb +++ b/bootstraptest/runner.rb @@ -891,4 +891,8 @@ def yjit_enabled? ENV.key?('RUBY_YJIT_ENABLE') || ENV.fetch('RUN_OPTS', '').include?('yjit') || BT.ruby.include?('yjit') end +def zjit_enabled? + ENV.key?('RUBY_ZJIT_ENABLE') || ENV.fetch('RUN_OPTS', '').include?('zjit') || BT.ruby.include?('zjit') +end + exit main diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 1c89cd40ee7e8b..834c7ceebb04b5 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -389,7 +389,7 @@ def test n end 3.times.map{Ractor.receive}.tally -} unless yjit_enabled? # `[BUG] Bus Error at 0x000000010b7002d0` in jit_exec() +} unless yjit_enabled? || zjit_enabled? # YJIT: `[BUG] Bus Error at 0x000000010b7002d0` in jit_exec(), ZJIT hangs # unshareable object are copied assert_equal 'false', %q{ From 0abe17dae0bad6ed60b13222d83355decc68ac7c Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 3 Jul 2025 09:30:45 -0700 Subject: [PATCH 105/130] ZJIT: Bail out on register spill (#13773) --- .github/workflows/zjit-macos.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 2 +- test/ruby/test_zjit.rb | 12 +++++++++--- zjit/src/backend/arm64/mod.rs | 2 +- zjit/src/backend/lir.rs | 28 +++++++++++++++++----------- zjit/src/backend/x86_64/mod.rs | 2 +- 6 files changed, 30 insertions(+), 18 deletions(-) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 7060d6a2529931..5260c3ecb1577e 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -128,6 +128,7 @@ jobs: ../src/bootstraptest/test_massign.rb \ ../src/bootstraptest/test_method.rb \ ../src/bootstraptest/test_objectspace.rb \ + ../src/bootstraptest/test_ractor.rb \ ../src/bootstraptest/test_string.rb \ ../src/bootstraptest/test_struct.rb \ ../src/bootstraptest/test_syntax.rb \ @@ -138,7 +139,6 @@ jobs: # ../src/bootstraptest/test_eval.rb \ # ../src/bootstraptest/test_insns.rb \ # ../src/bootstraptest/test_proc.rb \ - # ../src/bootstraptest/test_ractor.rb \ # ../src/bootstraptest/test_yjit.rb \ if: ${{ matrix.test_task == 'btest' }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 443c9c71df5980..a6a502057e846b 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -150,6 +150,7 @@ jobs: ../src/bootstraptest/test_massign.rb \ ../src/bootstraptest/test_method.rb \ ../src/bootstraptest/test_objectspace.rb \ + ../src/bootstraptest/test_ractor.rb \ ../src/bootstraptest/test_string.rb \ ../src/bootstraptest/test_struct.rb \ ../src/bootstraptest/test_syntax.rb \ @@ -160,7 +161,6 @@ jobs: # ../src/bootstraptest/test_eval.rb \ # ../src/bootstraptest/test_insns.rb \ # ../src/bootstraptest/test_proc.rb \ - # ../src/bootstraptest/test_ractor.rb \ # ../src/bootstraptest/test_yjit.rb \ if: ${{ matrix.test_task == 'btest' }} diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 0c73e6b456392d..7da5d96d356f11 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -713,8 +713,7 @@ def test(n1, n2) end def test_spilled_method_args - omit 'CCall with spilled arguments is not implemented yet' - assert_compiles '55', %q{ + assert_runs '55', %q{ def foo(n1, n2, n3, n4, n5, n6, n7, n8, n9, n10) n1 + n2 + n3 + n4 + n5 + n6 + n7 + n8 + n9 + n10 end @@ -906,10 +905,17 @@ def test_instruction_order # Assert that every method call in `test_script` can be compiled by ZJIT # at a given call_threshold def assert_compiles(expected, test_script, insns: [], **opts) + assert_runs(expected, test_script, insns:, assert_compiles: true, **opts) + end + + # Assert that `test_script` runs successfully with ZJIT enabled. + # Unlike `assert_compiles`, `assert_runs(assert_compiles: false)` + # allows ZJIT to skip compiling methods. + def assert_runs(expected, test_script, insns: [], assert_compiles: false, **opts) pipe_fd = 3 script = <<~RUBY - ret_val = (_test_proc = -> { RubyVM::ZJIT.assert_compiles; #{test_script.lstrip} }).call + ret_val = (_test_proc = -> { #{('RubyVM::ZJIT.assert_compiles; ' if assert_compiles)}#{test_script.lstrip} }).call result = { ret_val:, #{ unless insns.empty? diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 3c18a57dd05381..d44c482fe9c0ec 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -1297,7 +1297,7 @@ impl Assembler /// Optimize and compile the stored instructions pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec) -> Option<(CodePtr, Vec)> { let asm = self.arm64_split(); - let mut asm = asm.alloc_regs(regs); + let mut asm = asm.alloc_regs(regs)?; asm.compile_side_exits()?; // Create label instances in the code block diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 4cc093ed5e55e1..9ad36dcb44a3f5 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -3,12 +3,11 @@ use std::fmt; use std::mem::take; use crate::codegen::local_size_and_idx_to_ep_offset; use crate::cruby::{Qundef, RUBY_OFFSET_CFP_PC, RUBY_OFFSET_CFP_SP, SIZEOF_VALUE_I32}; +use crate::options::{debug, get_option}; use crate::{cruby::VALUE}; use crate::backend::current::*; use crate::virtualmem::CodePtr; use crate::asm::{CodeBlock, Label}; -#[cfg(feature = "disasm")] -use crate::options::*; pub const EC: Opnd = _EC; pub const CFP: Opnd = _CFP; @@ -1519,7 +1518,7 @@ impl Assembler /// Sets the out field on the various instructions that require allocated /// registers because their output is used as the operand on a subsequent /// instruction. This is our implementation of the linear scan algorithm. - pub(super) fn alloc_regs(mut self, regs: Vec) -> Assembler { + pub(super) fn alloc_regs(mut self, regs: Vec) -> Option { // Dump live registers for register spill debugging. fn dump_live_regs(insns: Vec, live_ranges: Vec, num_regs: usize, spill_index: usize) { // Convert live_ranges to live_regs: the number of live registers at each index @@ -1566,8 +1565,12 @@ impl Assembler // If C_RET_REG is in use, move it to another register. // This must happen before last-use registers are deallocated. if let Some(vreg_idx) = pool.vreg_for(&C_RET_REG) { - let new_reg = pool.alloc_reg(vreg_idx) - .expect("spilling VReg is not implemented yet, can't evacuate C_RET_REG on CCall"); // TODO: support spilling VReg + let new_reg = if let Some(new_reg) = pool.alloc_reg(vreg_idx) { + new_reg + } else { + debug!("spilling VReg is not implemented yet, can't evacuate C_RET_REG on CCall"); + return None; + }; asm.mov(Opnd::Reg(new_reg), C_RET_OPND); pool.dealloc_reg(&C_RET_REG); reg_mapping[vreg_idx] = Some(new_reg); @@ -1660,13 +1663,16 @@ impl Assembler _ => match pool.alloc_reg(vreg_idx.unwrap()) { Some(reg) => Some(reg), None => { - let mut insns = asm.insns; - insns.push(insn); - while let Some((_, insn)) = iterator.next() { + if get_option!(debug) { + let mut insns = asm.insns; insns.push(insn); + while let Some((_, insn)) = iterator.next() { + insns.push(insn); + } + dump_live_regs(insns, live_ranges, regs.len(), index); } - dump_live_regs(insns, live_ranges, regs.len(), index); - unreachable!("Register spill not supported"); + debug!("Register spill not supported"); + return None; } } }; @@ -1737,7 +1743,7 @@ impl Assembler } assert!(pool.is_empty(), "Expected all registers to be returned to the pool"); - asm + Some(asm) } /// Compile the instructions down to machine code. diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 793a096365df27..80fd7c714cede4 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -836,7 +836,7 @@ impl Assembler /// Optimize and compile the stored instructions pub fn compile_with_regs(self, cb: &mut CodeBlock, regs: Vec) -> Option<(CodePtr, Vec)> { let asm = self.x86_split(); - let mut asm = asm.alloc_regs(regs); + let mut asm = asm.alloc_regs(regs)?; asm.compile_side_exits()?; // Create label instances in the code block From c584cc079eef2f4e314a97eff310c9947e1d7010 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 3 Jul 2025 09:40:43 -0700 Subject: [PATCH 106/130] ZJIT: Enable one more btest (#13781) --- .github/workflows/zjit-macos.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 5260c3ecb1577e..5da40b152830e2 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -128,6 +128,7 @@ jobs: ../src/bootstraptest/test_massign.rb \ ../src/bootstraptest/test_method.rb \ ../src/bootstraptest/test_objectspace.rb \ + ../src/bootstraptest/test_proc.rb \ ../src/bootstraptest/test_ractor.rb \ ../src/bootstraptest/test_string.rb \ ../src/bootstraptest/test_struct.rb \ @@ -138,7 +139,6 @@ jobs: ../src/bootstraptest/test_yjit_rust_port.rb # ../src/bootstraptest/test_eval.rb \ # ../src/bootstraptest/test_insns.rb \ - # ../src/bootstraptest/test_proc.rb \ # ../src/bootstraptest/test_yjit.rb \ if: ${{ matrix.test_task == 'btest' }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index a6a502057e846b..bb4203d0be9670 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -150,6 +150,7 @@ jobs: ../src/bootstraptest/test_massign.rb \ ../src/bootstraptest/test_method.rb \ ../src/bootstraptest/test_objectspace.rb \ + ../src/bootstraptest/test_proc.rb \ ../src/bootstraptest/test_ractor.rb \ ../src/bootstraptest/test_string.rb \ ../src/bootstraptest/test_struct.rb \ @@ -160,7 +161,6 @@ jobs: ../src/bootstraptest/test_yjit_rust_port.rb # ../src/bootstraptest/test_eval.rb \ # ../src/bootstraptest/test_insns.rb \ - # ../src/bootstraptest/test_proc.rb \ # ../src/bootstraptest/test_yjit.rb \ if: ${{ matrix.test_task == 'btest' }} From ed3fd94e77862c8e8a81a06f69cad95c1ec31619 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Thu, 3 Jul 2025 13:09:10 -0700 Subject: [PATCH 107/130] ZJIT: Panic on BOP redefinition only when needed (#13782) --- .github/workflows/zjit-macos.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 2 +- test/ruby/test_zjit.rb | 15 +++++++++++++++ zjit/src/backend/arm64/mod.rs | 4 ---- zjit/src/backend/lir.rs | 11 ----------- zjit/src/backend/x86_64/mod.rs | 10 ---------- zjit/src/codegen.rs | 23 +++++++++++++++++++++-- zjit/src/invariants.rs | 23 +++++++++++++++++++---- zjit/src/virtualmem.rs | 2 +- 9 files changed, 58 insertions(+), 34 deletions(-) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 5da40b152830e2..0c7c2e32abd93e 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -119,6 +119,7 @@ jobs: ../src/bootstraptest/test_flow.rb \ ../src/bootstraptest/test_fork.rb \ ../src/bootstraptest/test_gc.rb \ + ../src/bootstraptest/test_insns.rb \ ../src/bootstraptest/test_io.rb \ ../src/bootstraptest/test_jump.rb \ ../src/bootstraptest/test_literal.rb \ @@ -138,7 +139,6 @@ jobs: ../src/bootstraptest/test_yjit_30k_methods.rb \ ../src/bootstraptest/test_yjit_rust_port.rb # ../src/bootstraptest/test_eval.rb \ - # ../src/bootstraptest/test_insns.rb \ # ../src/bootstraptest/test_yjit.rb \ if: ${{ matrix.test_task == 'btest' }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index bb4203d0be9670..268eb427f5aa26 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -141,6 +141,7 @@ jobs: ../src/bootstraptest/test_flow.rb \ ../src/bootstraptest/test_fork.rb \ ../src/bootstraptest/test_gc.rb \ + ../src/bootstraptest/test_insns.rb \ ../src/bootstraptest/test_io.rb \ ../src/bootstraptest/test_jump.rb \ ../src/bootstraptest/test_literal.rb \ @@ -160,7 +161,6 @@ jobs: ../src/bootstraptest/test_yjit_30k_methods.rb \ ../src/bootstraptest/test_yjit_rust_port.rb # ../src/bootstraptest/test_eval.rb \ - # ../src/bootstraptest/test_insns.rb \ # ../src/bootstraptest/test_yjit.rb \ if: ${{ matrix.test_task == 'btest' }} diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 7da5d96d356f11..58c9cd0970e139 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -815,6 +815,21 @@ def entry = jit_frame1 # 3 }, call_threshold: 2 end + def test_bop_invalidation + omit 'Invalidation on BOP redefinition is not implemented yet' + assert_compiles '', %q{ + def test + eval(<<~RUBY) + class Integer + def +(_) = 100 + end + RUBY + 1 + 2 + end + test + } + end + def test_defined_yield assert_compiles "nil", "defined?(yield)" assert_compiles '[nil, nil, "yield"]', %q{ diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index d44c482fe9c0ec..5cac6740e366e3 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -892,7 +892,6 @@ impl Assembler let mut pos_markers: Vec<(usize, CodePtr)> = vec![]; // For each instruction - //let start_write_pos = cb.get_write_pos(); let mut insn_idx: usize = 0; while let Some(insn) = self.insns.get(insn_idx) { //let src_ptr = cb.get_write_ptr(); @@ -1256,9 +1255,6 @@ impl Assembler csel(cb, out.into(), truthy.into(), falsy.into(), Condition::GE); } Insn::LiveReg { .. } => (), // just a reg alloc signal, no code - Insn::PadInvalPatch => { - unimplemented!("we haven't needed padding in ZJIT yet"); - } }; // On failure, jump to the next page and retry the current insn diff --git a/zjit/src/backend/lir.rs b/zjit/src/backend/lir.rs index 9ad36dcb44a3f5..f914870c84fed9 100644 --- a/zjit/src/backend/lir.rs +++ b/zjit/src/backend/lir.rs @@ -484,10 +484,6 @@ pub enum Insn { // binary OR operation. Or { left: Opnd, right: Opnd, out: Opnd }, - /// Pad nop instructions to accommodate Op::Jmp in case the block or the insn - /// is invalidated. - PadInvalPatch, - // Mark a position in the generated code PosMarker(PosMarkerFn), @@ -607,7 +603,6 @@ impl Insn { Insn::Mov { .. } => "Mov", Insn::Not { .. } => "Not", Insn::Or { .. } => "Or", - Insn::PadInvalPatch => "PadEntryExit", Insn::PosMarker(_) => "PosMarker", Insn::RShift { .. } => "RShift", Insn::Store { .. } => "Store", @@ -801,7 +796,6 @@ impl<'a> Iterator for InsnOpndIterator<'a> { Insn::CPushAll | Insn::FrameSetup | Insn::FrameTeardown | - Insn::PadInvalPatch | Insn::PosMarker(_) => None, Insn::CPopInto(opnd) | @@ -956,7 +950,6 @@ impl<'a> InsnOpndMutIterator<'a> { Insn::CPushAll | Insn::FrameSetup | Insn::FrameTeardown | - Insn::PadInvalPatch | Insn::PosMarker(_) => None, Insn::CPopInto(opnd) | @@ -2171,10 +2164,6 @@ impl Assembler { out } - pub fn pad_inval_patch(&mut self) { - self.push_insn(Insn::PadInvalPatch); - } - //pub fn pos_marker(&mut self, marker_fn: F) pub fn pos_marker(&mut self, marker_fn: impl Fn(CodePtr, &CodeBlock) + 'static) { self.push_insn(Insn::PosMarker(Box::new(marker_fn))); diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 80fd7c714cede4..4dd9877ea7125a 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -444,7 +444,6 @@ impl Assembler let mut pos_markers: Vec<(usize, CodePtr)> = vec![]; // For each instruction - //let start_write_pos = cb.get_write_pos(); let mut insn_idx: usize = 0; while let Some(insn) = self.insns.get(insn_idx) { //let src_ptr = cb.get_write_ptr(); @@ -795,15 +794,6 @@ impl Assembler emit_csel(cb, *truthy, *falsy, *out, cmovge, cmovl); } Insn::LiveReg { .. } => (), // just a reg alloc signal, no code - Insn::PadInvalPatch => { - unimplemented!("we don't need padding yet"); - /* - let code_size = cb.get_write_pos().saturating_sub(std::cmp::max(start_write_pos, cb.page_start_pos())); - if code_size < cb.jmp_ptr_bytes() { - nop(cb, (cb.jmp_ptr_bytes() - code_size) as u32); - } - */ - } }; // On failure, jump to the next page and retry the current insn diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 419fc509832bdb..306ba31aba117b 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -3,11 +3,12 @@ use std::rc::Rc; use std::num::NonZeroU32; use crate::backend::current::{Reg, ALLOC_REGS}; +use crate::invariants::track_bop_assumption; use crate::profile::get_or_create_iseq_payload; use crate::state::ZJITState; use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr}; use crate::backend::lir::{self, asm_comment, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, SP}; -use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, CallInfo, RangeType, SELF_PARAM_IDX, SpecialObjectType}; +use crate::hir::{iseq_to_hir, Block, BlockId, BranchEdge, CallInfo, Invariant, RangeType, SpecialObjectType, SELF_PARAM_IDX}; use crate::hir::{Const, FrameState, Function, Insn, InsnId}; use crate::hir_type::{types::Fixnum, Type}; use crate::options::get_option; @@ -286,7 +287,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::Test { val } => gen_test(asm, opnd!(val))?, Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state))?, Insn::GuardBitEquals { val, expected, state } => gen_guard_bit_equals(jit, asm, opnd!(val), *expected, &function.frame_state(*state))?, - Insn::PatchPoint(_) => return Some(()), // For now, rb_zjit_bop_redefined() panics. TODO: leave a patch point and fix rb_zjit_bop_redefined() + Insn::PatchPoint(invariant) => return gen_patch_point(asm, invariant), Insn::CCall { cfun, args, name: _, return_type: _, elidable: _ } => gen_ccall(jit, asm, *cfun, args)?, Insn::GetIvar { self_val, id, state: _ } => gen_getivar(asm, opnd!(self_val), *id), Insn::SetGlobal { id, val, state: _ } => return Some(gen_setglobal(asm, *id, opnd!(val))), @@ -422,6 +423,24 @@ fn gen_invokebuiltin(jit: &mut JITState, asm: &mut Assembler, state: &FrameState Some(val) } +/// Record a patch point that should be invalidated on a given invariant +fn gen_patch_point(asm: &mut Assembler, invariant: &Invariant) -> Option<()> { + let invariant = invariant.clone(); + asm.pos_marker(move |code_ptr, _cb| { + match invariant { + Invariant::BOPRedefined { klass, bop } => { + track_bop_assumption(klass, bop, code_ptr); + } + _ => { + debug!("ZJIT: gen_patch_point: unimplemented invariant {invariant:?}"); + return; + } + } + }); + // TODO: Make sure patch points do not overlap with each other. + Some(()) +} + /// Lowering for [`Insn::CCall`]. This is a low-level raw call that doesn't know /// anything about the callee, so handling for e.g. GC safety is dealt with elsewhere. fn gen_ccall(jit: &mut JITState, asm: &mut Assembler, cfun: *const u8, args: &[InsnId]) -> Option { diff --git a/zjit/src/invariants.rs b/zjit/src/invariants.rs index 77ccc7d04c43d2..9703656e709d98 100644 --- a/zjit/src/invariants.rs +++ b/zjit/src/invariants.rs @@ -1,6 +1,6 @@ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; -use crate::{cruby::{ruby_basic_operators, IseqPtr, RedefinitionFlag}, state::ZJITState, state::zjit_enabled_p}; +use crate::{cruby::{ruby_basic_operators, IseqPtr, RedefinitionFlag}, state::{zjit_enabled_p, ZJITState}, virtualmem::CodePtr}; /// Used to track all of the various block references that contain assumptions /// about the state of the virtual machine. @@ -11,19 +11,28 @@ pub struct Invariants { /// Set of ISEQs whose JIT code assumes that it doesn't escape EP no_ep_escape_iseqs: HashSet, + + /// Map from a class and its associated basic operator to a set of patch points + bop_patch_points: HashMap<(RedefinitionFlag, ruby_basic_operators), HashSet>, } /// Called when a basic operator is redefined. Note that all the blocks assuming /// the stability of different operators are invalidated together and we don't /// do fine-grained tracking. #[unsafe(no_mangle)] -pub extern "C" fn rb_zjit_bop_redefined(_klass: RedefinitionFlag, _bop: ruby_basic_operators) { +pub extern "C" fn rb_zjit_bop_redefined(klass: RedefinitionFlag, bop: ruby_basic_operators) { // If ZJIT isn't enabled, do nothing if !zjit_enabled_p() { return; } - unimplemented!("Invalidation on BOP redefinition is not implemented yet"); + let invariants = ZJITState::get_invariants(); + if let Some(code_ptrs) = invariants.bop_patch_points.get(&(klass, bop)) { + // Invalidate all patch points for this BOP + for &ptr in code_ptrs { + unimplemented!("Invalidation on BOP redefinition is not implemented yet: {ptr:?}"); + } + } } /// Invalidate blocks for a given ISEQ that assumes environment pointer is @@ -57,3 +66,9 @@ pub fn track_no_ep_escape_assumption(iseq: IseqPtr) { pub fn iseq_escapes_ep(iseq: IseqPtr) -> bool { ZJITState::get_invariants().ep_escape_iseqs.contains(&iseq) } + +/// Track a patch point for a basic operator in a given class. +pub fn track_bop_assumption(klass: RedefinitionFlag, bop: ruby_basic_operators, code_ptr: CodePtr) { + let invariants = ZJITState::get_invariants(); + invariants.bop_patch_points.entry((klass, bop)).or_default().insert(code_ptr); +} diff --git a/zjit/src/virtualmem.rs b/zjit/src/virtualmem.rs index d76c3d76d3719d..f62cccd3aafb45 100644 --- a/zjit/src/virtualmem.rs +++ b/zjit/src/virtualmem.rs @@ -62,7 +62,7 @@ pub trait Allocator { /// Pointer into a [VirtualMemory] represented as an offset from the base. /// Note: there is no NULL constant for [CodePtr]. You should use `Option` instead. -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Debug)] #[repr(C, packed)] pub struct CodePtr(u32); From 4f4408e98933f65f9d5b1752c2892218f2224de3 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Thu, 12 Jun 2025 15:13:08 -0400 Subject: [PATCH 108/130] Get transcoding to work across ractors by locking certain operations Ex: `str.encode` and `str.encode!` work across ractors now. The global table `transcoder_table` needs a lock around each st_lookup/st_insert, and it's a two-level table so the second level also needs to be locked around insertion/deletion. In addition to this, the transcoder entries (values in the second-level hash table) need to be locked around retrieving them and loading them as they are loaded lazily. The transcoding objects (`Encoding::Converter`) can't be made shareable, so their operations don't need to be locked. --- common.mk | 3 + transcode.c | 254 +++++++++++++++++++++++++++++----------------------- 2 files changed, 144 insertions(+), 113 deletions(-) diff --git a/common.mk b/common.mk index bb193320b8dd27..0c4428bef0c59b 100644 --- a/common.mk +++ b/common.mk @@ -19515,6 +19515,7 @@ transcode.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h transcode.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h transcode.$(OBJEXT): {$(VPATH)}config.h transcode.$(OBJEXT): {$(VPATH)}constant.h +transcode.$(OBJEXT): {$(VPATH)}debug_counter.h transcode.$(OBJEXT): {$(VPATH)}defines.h transcode.$(OBJEXT): {$(VPATH)}encoding.h transcode.$(OBJEXT): {$(VPATH)}id.h @@ -19678,6 +19679,8 @@ transcode.$(OBJEXT): {$(VPATH)}st.h transcode.$(OBJEXT): {$(VPATH)}subst.h transcode.$(OBJEXT): {$(VPATH)}transcode.c transcode.$(OBJEXT): {$(VPATH)}transcode_data.h +transcode.$(OBJEXT): {$(VPATH)}vm_debug.h +transcode.$(OBJEXT): {$(VPATH)}vm_sync.h util.$(OBJEXT): $(hdrdir)/ruby/ruby.h util.$(OBJEXT): $(top_srcdir)/internal/array.h util.$(OBJEXT): $(top_srcdir)/internal/compilers.h diff --git a/transcode.c b/transcode.c index bff9268e1cda9a..8e36fa13eb88cd 100644 --- a/transcode.c +++ b/transcode.c @@ -20,6 +20,7 @@ #include "internal/string.h" #include "internal/transcode.h" #include "ruby/encoding.h" +#include "vm_sync.h" #include "transcode_data.h" #include "id.h" @@ -209,19 +210,21 @@ make_transcoder_entry(const char *sname, const char *dname) st_data_t val; st_table *table2; - if (!st_lookup(transcoder_table, (st_data_t)sname, &val)) { - val = (st_data_t)st_init_strcasetable(); - st_add_direct(transcoder_table, (st_data_t)sname, val); - } - table2 = (st_table *)val; - if (!st_lookup(table2, (st_data_t)dname, &val)) { - transcoder_entry_t *entry = ALLOC(transcoder_entry_t); - entry->sname = sname; - entry->dname = dname; - entry->lib = NULL; - entry->transcoder = NULL; - val = (st_data_t)entry; - st_add_direct(table2, (st_data_t)dname, val); + RB_VM_LOCKING() { + if (!st_lookup(transcoder_table, (st_data_t)sname, &val)) { + val = (st_data_t)st_init_strcasetable(); + st_add_direct(transcoder_table, (st_data_t)sname, val); + } + table2 = (st_table *)val; + if (!st_lookup(table2, (st_data_t)dname, &val)) { + transcoder_entry_t *entry = ALLOC(transcoder_entry_t); + entry->sname = sname; + entry->dname = dname; + entry->lib = NULL; + entry->transcoder = NULL; + val = (st_data_t)entry; + st_add_direct(table2, (st_data_t)dname, val); + } } return (transcoder_entry_t *)val; } @@ -229,15 +232,15 @@ make_transcoder_entry(const char *sname, const char *dname) static transcoder_entry_t * get_transcoder_entry(const char *sname, const char *dname) { - st_data_t val; + st_data_t val = 0; st_table *table2; - - if (!st_lookup(transcoder_table, (st_data_t)sname, &val)) { - return NULL; - } - table2 = (st_table *)val; - if (!st_lookup(table2, (st_data_t)dname, &val)) { - return NULL; + RB_VM_LOCKING() { + if (st_lookup(transcoder_table, (st_data_t)sname, &val)) { + table2 = (st_table *)val; + if (!st_lookup(table2, (st_data_t)dname, &val)) { + val = 0; + } + } } return (transcoder_entry_t *)val; } @@ -250,13 +253,14 @@ rb_register_transcoder(const rb_transcoder *tr) transcoder_entry_t *entry; - entry = make_transcoder_entry(sname, dname); - if (entry->transcoder) { - rb_raise(rb_eArgError, "transcoder from %s to %s has been already registered", - sname, dname); + RB_VM_LOCKING() { + entry = make_transcoder_entry(sname, dname); + if (entry->transcoder) { + rb_raise(rb_eArgError, "transcoder from %s to %s has been already registered", + sname, dname); + } + entry->transcoder = tr; } - - entry->transcoder = tr; } static void @@ -323,8 +327,9 @@ transcode_search_path(const char *sname, const char *dname, search_path_queue_t *q; st_data_t val; st_table *table2; - int found; int pathlen = -1; + bool found = false; + bool lookup_res; if (encoding_equal(sname, dname)) return -1; @@ -338,34 +343,36 @@ transcode_search_path(const char *sname, const char *dname, bfs.visited = st_init_strcasetable(); st_add_direct(bfs.visited, (st_data_t)sname, (st_data_t)NULL); - while (bfs.queue) { - q = bfs.queue; - bfs.queue = q->next; - if (!bfs.queue) - bfs.queue_last_ptr = &bfs.queue; + RB_VM_LOCKING() { + while (bfs.queue) { + q = bfs.queue; + bfs.queue = q->next; + if (!bfs.queue) { + bfs.queue_last_ptr = &bfs.queue; + } - if (!st_lookup(transcoder_table, (st_data_t)q->enc, &val)) { - xfree(q); - continue; - } - table2 = (st_table *)val; + lookup_res = st_lookup(transcoder_table, (st_data_t)q->enc, &val); + if (!lookup_res) { + xfree(q); + continue; + } + table2 = (st_table *)val; - if (st_lookup(table2, (st_data_t)dname, &val)) { - st_add_direct(bfs.visited, (st_data_t)dname, (st_data_t)q->enc); - xfree(q); - found = 1; - goto cleanup; - } + if (st_lookup(table2, (st_data_t)dname, &val)) { + st_add_direct(bfs.visited, (st_data_t)dname, (st_data_t)q->enc); + xfree(q); + found = true; + break; + } - bfs.base_enc = q->enc; - st_foreach(table2, transcode_search_path_i, (st_data_t)&bfs); - bfs.base_enc = NULL; + bfs.base_enc = q->enc; + st_foreach(table2, transcode_search_path_i, (st_data_t)&bfs); - xfree(q); + bfs.base_enc = NULL; + xfree(q); + } } - found = 0; - cleanup: while (bfs.queue) { q = bfs.queue; bfs.queue = q->next; @@ -404,6 +411,8 @@ int rb_require_internal_silent(VALUE fname); static const rb_transcoder * load_transcoder_entry(transcoder_entry_t *entry) { + // changes result of entry->transcoder depending on if it's required or not, so needs lock + ASSERT_vm_locking(); if (entry->transcoder) return entry->transcoder; @@ -972,6 +981,7 @@ rb_econv_open_by_transcoder_entries(int n, transcoder_entry_t **entries) { rb_econv_t *ec; int i, ret; + ASSERT_vm_locking(); for (i = 0; i < n; i++) { const rb_transcoder *tr; @@ -1016,6 +1026,7 @@ rb_econv_open0(const char *sname, const char *dname, int ecflags) transcoder_entry_t **entries = NULL; int num_trans; rb_econv_t *ec; + ASSERT_vm_locking(); /* Just check if sname and dname are defined */ /* (This check is needed?) */ @@ -1106,19 +1117,23 @@ rb_econv_open(const char *sname, const char *dname, int ecflags) if (num_decorators == -1) return NULL; - ec = rb_econv_open0(sname, dname, ecflags & ECONV_ERROR_HANDLER_MASK); - if (!ec) - return NULL; - - for (i = 0; i < num_decorators; i++) - if (rb_econv_decorate_at_last(ec, decorators[i]) == -1) { - rb_econv_close(ec); - return NULL; + RB_VM_LOCKING() { + ec = rb_econv_open0(sname, dname, ecflags & ECONV_ERROR_HANDLER_MASK); + if (ec) { + for (i = 0; i < num_decorators; i++) { + if (rb_econv_decorate_at_last(ec, decorators[i]) == -1) { + rb_econv_close(ec); + ec = NULL; + break; + } + } } + } - ec->flags |= ecflags & ~ECONV_ERROR_HANDLER_MASK; - - return ec; + if (ec) { + ec->flags |= ecflags & ~ECONV_ERROR_HANDLER_MASK; + } + return ec; // can be NULL } static int @@ -1815,26 +1830,29 @@ rb_econv_asciicompat_encoding(const char *ascii_incompat_name) { st_data_t v; st_table *table2; - struct asciicompat_encoding_t data; + struct asciicompat_encoding_t data = {0}; + + RB_VM_LOCKING() { + if (st_lookup(transcoder_table, (st_data_t)ascii_incompat_name, &v)) { + table2 = (st_table *)v; + /* + * Assumption: + * There is at most one transcoder for + * converting from ASCII incompatible encoding. + * + * For ISO-2022-JP, there is ISO-2022-JP -> stateless-ISO-2022-JP and no others. + */ + if (table2->num_entries == 1) { + data.ascii_incompat_name = ascii_incompat_name; + data.ascii_compat_name = NULL; + st_foreach(table2, asciicompat_encoding_i, (st_data_t)&data); + } - if (!st_lookup(transcoder_table, (st_data_t)ascii_incompat_name, &v)) - return NULL; - table2 = (st_table *)v; + } - /* - * Assumption: - * There is at most one transcoder for - * converting from ASCII incompatible encoding. - * - * For ISO-2022-JP, there is ISO-2022-JP -> stateless-ISO-2022-JP and no others. - */ - if (table2->num_entries != 1) - return NULL; + } - data.ascii_incompat_name = ascii_incompat_name; - data.ascii_compat_name = NULL; - st_foreach(table2, asciicompat_encoding_i, (st_data_t)&data); - return data.ascii_compat_name; + return data.ascii_compat_name; // can be NULL } /* @@ -1937,19 +1955,20 @@ static int rb_econv_add_converter(rb_econv_t *ec, const char *sname, const char *dname, int n) { transcoder_entry_t *entry; - const rb_transcoder *tr; + const rb_transcoder *tr = NULL; if (ec->started != 0) return -1; - entry = get_transcoder_entry(sname, dname); - if (!entry) - return -1; + RB_VM_LOCKING() { + entry = get_transcoder_entry(sname, dname); + if (entry) { + tr = load_transcoder_entry(entry); + } - tr = load_transcoder_entry(entry); - if (!tr) return -1; + } - return rb_econv_add_transcoder_at(ec, tr, n); + return tr ? rb_econv_add_transcoder_at(ec, tr, n) : -1; } static int @@ -2662,24 +2681,25 @@ rb_econv_open_opts(const char *source_encoding, const char *destination_encoding replacement = rb_hash_aref(opthash, sym_replace); } - ec = rb_econv_open(source_encoding, destination_encoding, ecflags); - if (!ec) - return ec; - - if (!NIL_P(replacement)) { - int ret; - rb_encoding *enc = rb_enc_get(replacement); - - ret = rb_econv_set_replacement(ec, - (const unsigned char *)RSTRING_PTR(replacement), - RSTRING_LEN(replacement), - rb_enc_name(enc)); - if (ret == -1) { - rb_econv_close(ec); - return NULL; + RB_VM_LOCKING() { + ec = rb_econv_open(source_encoding, destination_encoding, ecflags); + if (ec) { + if (!NIL_P(replacement)) { + int ret; + rb_encoding *enc = rb_enc_get(replacement); + + ret = rb_econv_set_replacement(ec, + (const unsigned char *)RSTRING_PTR(replacement), + RSTRING_LEN(replacement), + rb_enc_name(enc)); + if (ret == -1) { + rb_econv_close(ec); + ec = NULL; + } + } } } - return ec; + return ec; // can be NULL } static int @@ -2979,9 +2999,11 @@ static rb_encoding * make_encoding(const char *name) { rb_encoding *enc; - enc = rb_enc_find(name); - if (!enc) - enc = make_dummy_encoding(name); + RB_VM_LOCKING() { + enc = rb_enc_find(name); + if (!enc) + enc = make_dummy_encoding(name); + } return enc; } @@ -3014,17 +3036,19 @@ econv_s_asciicompat_encoding(VALUE klass, VALUE arg) { const char *arg_name, *result_name; rb_encoding *arg_enc, *result_enc; + VALUE enc = Qnil; enc_arg(&arg, &arg_name, &arg_enc); - result_name = rb_econv_asciicompat_encoding(arg_name); - - if (result_name == NULL) - return Qnil; - - result_enc = make_encoding(result_name); + RB_VM_LOCKING() { + result_name = rb_econv_asciicompat_encoding(arg_name); - return rb_enc_from_encoding(result_enc); + if (result_name) { + result_enc = make_encoding(result_name); + enc = rb_enc_from_encoding(result_enc); + } + } + return enc; } static void @@ -3105,8 +3129,12 @@ decorate_convpath(VALUE convpath, int ecflags) if (RB_TYPE_P(pair, T_ARRAY)) { const char *sname = rb_enc_name(rb_to_encoding(RARRAY_AREF(pair, 0))); const char *dname = rb_enc_name(rb_to_encoding(RARRAY_AREF(pair, 1))); - transcoder_entry_t *entry = get_transcoder_entry(sname, dname); - const rb_transcoder *tr = load_transcoder_entry(entry); + transcoder_entry_t *entry; + const rb_transcoder *tr; + RB_VM_LOCKING() { + entry = get_transcoder_entry(sname, dname); + tr = load_transcoder_entry(entry); + } if (!tr) return -1; if (!DECORATOR_P(tr->src_encoding, tr->dst_encoding) && From cf4d37fbc079116453e69cf08ea8007d0e1c73e6 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Wed, 25 Jun 2025 12:44:40 -0400 Subject: [PATCH 109/130] Add locks around accesses/modifications to global encodings table This fixes segfaults and errors of the type "Encoding not found" when using encoding-related methods and internal encoding c functions across ractors. Example of a possible segfault in release mode or assertion error in debug mode: ```ruby rs = [] 100.times do rs << Ractor.new do "abc".force_encoding(Encoding.list.shuffle.first) end end while rs.any? r, obj = Ractor.select(*rs) rs.delete(r) end ``` --- encoding.c | 181 ++++++++++++++++++++++++++----------- test/ruby/test_encoding.rb | 18 ++++ 2 files changed, 145 insertions(+), 54 deletions(-) diff --git a/encoding.c b/encoding.c index 60d92690a72547..7f1d0011f8064e 100644 --- a/encoding.c +++ b/encoding.c @@ -93,12 +93,16 @@ static rb_encoding *global_enc_ascii, *global_enc_utf_8, *global_enc_us_ascii; +// re-entrant lock #define GLOBAL_ENC_TABLE_LOCKING(tbl) \ for (struct enc_table *tbl = &global_enc_table, **locking = &tbl; \ locking; \ locking = NULL) \ RB_VM_LOCKING() +#define GLOBAL_ENC_TABLE_LOCK_ENTER_LEV(tbl, lev) struct enc_table *tbl = &global_enc_table; RB_VM_LOCK_ENTER_LEV(lev) +#define GLOBAL_ENC_TABLE_LOCK_LEAVE_LEV(lev) RB_VM_LOCK_LEAVE_LEV(lev) +#define ASSERT_GLOBAL_ENC_TABLE_LOCKED() ASSERT_vm_locking() #define ENC_DUMMY_FLAG (1<<24) #define ENC_INDEX_MASK (~(~0U<<24)) @@ -140,6 +144,7 @@ enc_new(rb_encoding *encoding) static void enc_list_update(int index, rb_raw_encoding *encoding) { + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); RUBY_ASSERT(index < ENCODING_LIST_CAPA); VALUE list = rb_encoding_list; @@ -155,9 +160,11 @@ enc_list_lookup(int idx) VALUE list, enc = Qnil; if (idx < ENCODING_LIST_CAPA) { - list = rb_encoding_list; - RUBY_ASSERT(list); - enc = rb_ary_entry(list, idx); + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + list = rb_encoding_list; + RUBY_ASSERT(list); + enc = rb_ary_entry(list, idx); + } } if (NIL_P(enc)) { @@ -344,6 +351,7 @@ enc_table_expand(struct enc_table *enc_table, int newsize) static int enc_register_at(struct enc_table *enc_table, int index, const char *name, rb_encoding *base_encoding) { + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); struct rb_encoding_entry *ent = &enc_table->list[index]; rb_raw_encoding *encoding; @@ -376,6 +384,7 @@ enc_register_at(struct enc_table *enc_table, int index, const char *name, rb_enc static int enc_register(struct enc_table *enc_table, const char *name, rb_encoding *encoding) { + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); int index = enc_table->count; enc_table->count = enc_table_expand(enc_table, index + 1); @@ -388,28 +397,47 @@ static int enc_registered(struct enc_table *enc_table, const char *name); static rb_encoding * enc_from_index(struct enc_table *enc_table, int index) { - if (UNLIKELY(index < 0 || enc_table->count <= (index &= ENC_INDEX_MASK))) { - return 0; - } + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); return enc_table->list[index].enc; } rb_encoding * rb_enc_from_index(int index) { - return enc_from_index(&global_enc_table, index); + rb_encoding *enc; + switch (index) { + case ENCINDEX_US_ASCII: + return global_enc_us_ascii; + case ENCINDEX_UTF_8: + return global_enc_utf_8; + case ENCINDEX_ASCII_8BIT: + return global_enc_ascii; + default: + break; + } + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + if (UNLIKELY(index < 0 || enc_table->count <= (index &= ENC_INDEX_MASK))) { + enc = NULL; + } + else { + enc = enc_from_index(enc_table, index); + } + } + return enc; } int rb_enc_register(const char *name, rb_encoding *encoding) { int index; + unsigned int lev; - GLOBAL_ENC_TABLE_LOCKING(enc_table) { + GLOBAL_ENC_TABLE_LOCK_ENTER_LEV(enc_table, &lev); + { index = enc_registered(enc_table, name); if (index >= 0) { - rb_encoding *oldenc = enc_from_index(enc_table, index); + rb_encoding *oldenc = rb_enc_from_index(index); if (STRCASECMP(name, rb_enc_name(oldenc))) { index = enc_register(enc_table, name, encoding); } @@ -417,6 +445,7 @@ rb_enc_register(const char *name, rb_encoding *encoding) enc_register_at(enc_table, index, name, encoding); } else { + GLOBAL_ENC_TABLE_LOCK_LEAVE_LEV(&lev); rb_raise(rb_eArgError, "encoding %s is already registered", name); } } @@ -425,6 +454,7 @@ rb_enc_register(const char *name, rb_encoding *encoding) set_encoding_const(name, rb_enc_from_index(index)); } } + GLOBAL_ENC_TABLE_LOCK_LEAVE_LEV(&lev); return index; } @@ -432,6 +462,7 @@ int enc_registered(struct enc_table *enc_table, const char *name) { st_data_t idx = 0; + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); if (!name) return -1; if (!enc_table->names) return -1; @@ -467,6 +498,7 @@ enc_check_addable(struct enc_table *enc_table, const char *name) static rb_encoding* set_base_encoding(struct enc_table *enc_table, int index, rb_encoding *base) { + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); rb_encoding *enc = enc_table->list[index].enc; ASSUME(enc); @@ -504,6 +536,7 @@ static int enc_replicate(struct enc_table *enc_table, const char *name, rb_encoding *encoding) { int idx; + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); enc_check_addable(enc_table, name); idx = enc_register(enc_table, name, encoding); @@ -637,6 +670,7 @@ enc_dup_name(st_data_t name) static int enc_alias_internal(struct enc_table *enc_table, const char *alias, int idx) { + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); return st_insert2(enc_table->names, (st_data_t)alias, (st_data_t)idx, enc_dup_name); } @@ -644,9 +678,10 @@ enc_alias_internal(struct enc_table *enc_table, const char *alias, int idx) static int enc_alias(struct enc_table *enc_table, const char *alias, int idx) { + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); if (!valid_encoding_name_p(alias)) return -1; if (!enc_alias_internal(enc_table, alias, idx)) - set_encoding_const(alias, enc_from_index(enc_table, idx)); + set_encoding_const(alias, rb_enc_from_index(idx)); return idx; } @@ -728,6 +763,7 @@ int rb_require_internal_silent(VALUE fname); static int load_encoding(const char *name) { + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); VALUE enclib = rb_sprintf("enc/%s.so", name); VALUE debug = ruby_debug; VALUE errinfo; @@ -747,16 +783,14 @@ load_encoding(const char *name) ruby_debug = debug; rb_set_errinfo(errinfo); - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - if (loaded < 0 || 1 < loaded) { - idx = -1; - } - else if ((idx = enc_registered(enc_table, name)) < 0) { - idx = -1; - } - else if (rb_enc_autoload_p(enc_table->list[idx].enc)) { - idx = -1; - } + if (loaded < 0 || 1 < loaded) { + idx = -1; + } + else if ((idx = enc_registered(&global_enc_table, name)) < 0) { + idx = -1; + } + else if (rb_enc_autoload_p(global_enc_table.list[idx].enc)) { + idx = -1; } return idx; @@ -765,6 +799,7 @@ load_encoding(const char *name) static int enc_autoload_body(struct enc_table *enc_table, rb_encoding *enc) { + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); rb_encoding *base = enc_table->list[ENC_TO_ENCINDEX(enc)].base; if (base) { @@ -792,9 +827,9 @@ rb_enc_autoload(rb_encoding *enc) int i; GLOBAL_ENC_TABLE_LOCKING(enc_table) { i = enc_autoload_body(enc_table, enc); - } - if (i == -2) { - i = load_encoding(rb_enc_name(enc)); + if (i == -2) { + i = load_encoding(rb_enc_name(enc)); + } } return i; } @@ -803,13 +838,24 @@ rb_enc_autoload(rb_encoding *enc) int rb_enc_find_index(const char *name) { - int i = enc_registered(&global_enc_table, name); - rb_encoding *enc; - - if (i < 0) { - i = load_encoding(name); + int i; + rb_encoding *enc = NULL; + bool loaded_encoding = false; + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + i = enc_registered(enc_table, name); + if (i < 0) { + i = load_encoding(name); + loaded_encoding = true; + } + else { + enc = rb_enc_from_index(i); + } } - else if (!(enc = rb_enc_from_index(i))) { + if (loaded_encoding) { + return i; + } + + if (!enc) { if (i != UNSPECIFIED_ENCODING) { rb_raise(rb_eArgError, "encoding %s is not registered", name); } @@ -838,9 +884,13 @@ rb_enc_find_index2(const char *name, long len) rb_encoding * rb_enc_find(const char *name) { - int idx = rb_enc_find_index(name); - if (idx < 0) idx = 0; - return rb_enc_from_index(idx); + rb_encoding *enc; + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + int idx = rb_enc_find_index(name); + if (idx < 0) idx = 0; + enc = rb_enc_from_index(idx); + } + return enc; } static inline int @@ -1309,7 +1359,9 @@ enc_names(VALUE self) args[0] = (VALUE)rb_to_encoding_index(self); args[1] = rb_ary_new2(0); - st_foreach(global_enc_table.names, enc_names_i, (st_data_t)args); + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + st_foreach(enc_table->names, enc_names_i, (st_data_t)args); + } return args[1]; } @@ -1484,14 +1536,14 @@ rb_locale_encindex(void) if (idx < 0) idx = ENCINDEX_UTF_8; - if (enc_registered(&global_enc_table, "locale") < 0) { + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + if (enc_registered(enc_table, "locale") < 0) { # if defined _WIN32 - void Init_w32_codepage(void); - Init_w32_codepage(); + void Init_w32_codepage(void); + Init_w32_codepage(); # endif - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - enc_alias_internal(enc_table, "locale", idx); } + enc_alias_internal(enc_table, "locale", idx); } return idx; @@ -1506,7 +1558,10 @@ rb_locale_encoding(void) int rb_filesystem_encindex(void) { - int idx = enc_registered(&global_enc_table, "filesystem"); + int idx; + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + idx = enc_registered(enc_table, "filesystem"); + } if (idx < 0) idx = ENCINDEX_ASCII_8BIT; return idx; } @@ -1564,15 +1619,21 @@ enc_set_default_encoding(struct default_encoding *def, VALUE encoding, const cha rb_encoding * rb_default_external_encoding(void) { - if (default_external.enc) return default_external.enc; - - if (default_external.index >= 0) { - default_external.enc = rb_enc_from_index(default_external.index); - return default_external.enc; - } - else { - return rb_locale_encoding(); + rb_encoding *enc = NULL; + // TODO: make lock-free + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + if (default_external.enc) { + enc = default_external.enc; + } + else if (default_external.index >= 0) { + default_external.enc = rb_enc_from_index(default_external.index); + enc = default_external.enc; + } + else { + enc = rb_locale_encoding(); + } } + return enc; } VALUE @@ -1651,10 +1712,15 @@ static struct default_encoding default_internal = {-2}; rb_encoding * rb_default_internal_encoding(void) { - if (!default_internal.enc && default_internal.index >= 0) { - default_internal.enc = rb_enc_from_index(default_internal.index); + rb_encoding *enc = NULL; + // TODO: make lock-free + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + if (!default_internal.enc && default_internal.index >= 0) { + default_internal.enc = rb_enc_from_index(default_internal.index); + } + enc = default_internal.enc; } - return default_internal.enc; /* can be NULL */ + return enc; /* can be NULL */ } VALUE @@ -1803,8 +1869,11 @@ rb_enc_name_list_i(st_data_t name, st_data_t idx, st_data_t arg) static VALUE rb_enc_name_list(VALUE klass) { - VALUE ary = rb_ary_new2(global_enc_table.names->num_entries); - st_foreach(global_enc_table.names, rb_enc_name_list_i, (st_data_t)ary); + VALUE ary; + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + ary = rb_ary_new2(enc_table->names->num_entries); + st_foreach(enc_table->names, rb_enc_name_list_i, (st_data_t)ary); + } return ary; } @@ -1850,7 +1919,9 @@ rb_enc_aliases(VALUE klass) aliases[0] = rb_hash_new(); aliases[1] = rb_ary_new(); - st_foreach(global_enc_table.names, rb_enc_aliases_enc_i, (st_data_t)aliases); + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + st_foreach(enc_table->names, rb_enc_aliases_enc_i, (st_data_t)aliases); + } return aliases[0]; } @@ -1951,5 +2022,7 @@ Init_encodings(void) void rb_enc_foreach_name(int (*func)(st_data_t name, st_data_t idx, st_data_t arg), st_data_t arg) { - st_foreach(global_enc_table.names, func, arg); + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + st_foreach(enc_table->names, func, arg); + } } diff --git a/test/ruby/test_encoding.rb b/test/ruby/test_encoding.rb index 0ab357f53ad8b4..ae4e4a7cf7871a 100644 --- a/test/ruby/test_encoding.rb +++ b/test/ruby/test_encoding.rb @@ -136,4 +136,22 @@ def test_ractor_load_encoding assert "[Bug #19562]" end; end + + def test_ractor_force_encoding_parallel + assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") + begin; + $-w = nil + rs = [] + 100.times do + rs << Ractor.new do + "abc".force_encoding(Encoding.list.shuffle.first) + end + end + while rs.any? + r, _obj = Ractor.select(*rs) + rs.delete(r) + end + assert rs.empty? + end; + end end From dda5a04f2b4835582dba09ba33797258a61efafe Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Mon, 30 Jun 2025 11:20:05 -0400 Subject: [PATCH 110/130] Make get/set default internal/external encoding lock-free Also, make sure autoloading of encodings is safe across ractors. --- encoding.c | 184 +++++++++++++++++++++---------------- test/ruby/test_encoding.rb | 4 +- 2 files changed, 109 insertions(+), 79 deletions(-) diff --git a/encoding.c b/encoding.c index 7f1d0011f8064e..5305344a2e05df 100644 --- a/encoding.c +++ b/encoding.c @@ -26,6 +26,7 @@ #include "regenc.h" #include "ruby/encoding.h" #include "ruby/util.h" +#include "ruby/atomic.h" #include "ruby_assert.h" #include "vm_sync.h" @@ -53,6 +54,12 @@ int rb_encdb_alias(const char *alias, const char *orig); #pragma GCC visibility pop #endif +#if ENC_DEBUG +#define encdebug(...) fprintf(stderr, __VA_ARGS__) +#else +#define encdebug(...) (void)0 +#endif + static ID id_encoding; VALUE rb_cEncoding; @@ -144,11 +151,11 @@ enc_new(rb_encoding *encoding) static void enc_list_update(int index, rb_raw_encoding *encoding) { - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); RUBY_ASSERT(index < ENCODING_LIST_CAPA); VALUE list = rb_encoding_list; if (list && NIL_P(rb_ary_entry(list, index))) { + RUBY_ASSERT(!rb_multi_ractor_p()); /* initialize encoding data */ rb_ary_store(list, index, enc_new(encoding)); } @@ -160,11 +167,9 @@ enc_list_lookup(int idx) VALUE list, enc = Qnil; if (idx < ENCODING_LIST_CAPA) { - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - list = rb_encoding_list; - RUBY_ASSERT(list); - enc = rb_ary_entry(list, idx); - } + list = rb_encoding_list; + RUBY_ASSERT(list); + enc = rb_ary_entry(list, idx); } if (NIL_P(enc)) { @@ -206,10 +211,13 @@ static int check_encoding(rb_encoding *enc) { int index = rb_enc_to_index(enc); - if (rb_enc_from_index(index) != enc) + if (rb_enc_from_index(index) != enc) { + encdebug("check_encoding(%s): rb_enc_from_index(index) != enc, return -1\n", rb_enc_name(enc)); return -1; + } if (rb_enc_autoload_p(enc)) { index = rb_enc_autoload(enc); + encdebug("check_encoding(%s): rb_enc_autoload_p(enc), index after autoload:%d\n", rb_enc_name(enc), index); } return index; } @@ -340,7 +348,7 @@ rb_find_encoding(VALUE enc) } static int -enc_table_expand(struct enc_table *enc_table, int newsize) +enc_table_count_check(int newsize) { if (newsize > ENCODING_LIST_CAPA) { rb_raise(rb_eEncodingError, "too many encoding (> %d)", ENCODING_LIST_CAPA); @@ -348,46 +356,49 @@ enc_table_expand(struct enc_table *enc_table, int newsize) return newsize; } +// If called with a `base_encoding` of NULL, it is an autoloaded encoding static int enc_register_at(struct enc_table *enc_table, int index, const char *name, rb_encoding *base_encoding) { - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); struct rb_encoding_entry *ent = &enc_table->list[index]; rb_raw_encoding *encoding; - if (!valid_encoding_name_p(name)) return -1; - if (!ent->name) { - ent->name = name = strdup(name); - } - else if (STRCASECMP(name, ent->name)) { - return -1; - } - encoding = (rb_raw_encoding *)ent->enc; - if (!encoding) { - encoding = xmalloc(sizeof(rb_encoding)); - } - if (base_encoding) { - *encoding = *base_encoding; - } - else { - memset(encoding, 0, sizeof(*ent->enc)); + GLOBAL_ENC_TABLE_LOCKING(table) { + if (!ent->name) { + ent->name = name = strdup(name); + } + else if (STRCASECMP(name, ent->name)) { + index = -1; + } + if (index != -1) { + encoding = (rb_raw_encoding *)ent->enc; + if (!encoding) { + encoding = xmalloc(sizeof(rb_encoding)); + } + if (base_encoding) { + *encoding = *base_encoding; + } + else { + memset(encoding, 0, sizeof(*ent->enc)); + } + encoding->name = name; + ent->enc = encoding; + st_insert(table->names, (st_data_t)name, (st_data_t)index); + enc_list_update(index, encoding); + encoding->ruby_encoding_index = index; + } } - encoding->name = name; - encoding->ruby_encoding_index = index; - ent->enc = encoding; - st_insert(enc_table->names, (st_data_t)name, (st_data_t)index); - enc_list_update(index, encoding); return index; } static int enc_register(struct enc_table *enc_table, const char *name, rb_encoding *encoding) { - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); int index = enc_table->count; + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); - enc_table->count = enc_table_expand(enc_table, index + 1); + enc_table->count = enc_table_count_check(index + 1); return enc_register_at(enc_table, index, name, encoding); } @@ -397,43 +408,30 @@ static int enc_registered(struct enc_table *enc_table, const char *name); static rb_encoding * enc_from_index(struct enc_table *enc_table, int index) { - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); return enc_table->list[index].enc; } +// NOTE: there is no lock needed here, even with multi-ractor rb_encoding * rb_enc_from_index(int index) { rb_encoding *enc; - switch (index) { - case ENCINDEX_US_ASCII: - return global_enc_us_ascii; - case ENCINDEX_UTF_8: - return global_enc_utf_8; - case ENCINDEX_ASCII_8BIT: - return global_enc_ascii; - default: - break; + if (UNLIKELY(index < 0 || global_enc_table.count <= (index &= ENC_INDEX_MASK))) { + enc = NULL; } - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - if (UNLIKELY(index < 0 || enc_table->count <= (index &= ENC_INDEX_MASK))) { - enc = NULL; - } - else { - enc = enc_from_index(enc_table, index); - } + else { + enc = enc_from_index(&global_enc_table, index); } return enc; } +// This can be called from non-main ractors during autoloading of encodings int rb_enc_register(const char *name, rb_encoding *encoding) { int index; - unsigned int lev; - GLOBAL_ENC_TABLE_LOCK_ENTER_LEV(enc_table, &lev); - { + GLOBAL_ENC_TABLE_LOCKING(enc_table) { index = enc_registered(enc_table, name); if (index >= 0) { @@ -445,7 +443,6 @@ rb_enc_register(const char *name, rb_encoding *encoding) enc_register_at(enc_table, index, name, encoding); } else { - GLOBAL_ENC_TABLE_LOCK_LEAVE_LEV(&lev); rb_raise(rb_eArgError, "encoding %s is already registered", name); } } @@ -454,14 +451,14 @@ rb_enc_register(const char *name, rb_encoding *encoding) set_encoding_const(name, rb_enc_from_index(index)); } } - GLOBAL_ENC_TABLE_LOCK_LEAVE_LEV(&lev); return index; } -int +static int enc_registered(struct enc_table *enc_table, const char *name) { st_data_t idx = 0; + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); if (!name) return -1; @@ -472,6 +469,7 @@ enc_registered(struct enc_table *enc_table, const char *name) return -1; } +// Set up an encoding with a zeroed out entry->enc, which means it will be autoloaded void rb_encdb_declare(const char *name) { @@ -498,7 +496,6 @@ enc_check_addable(struct enc_table *enc_table, const char *name) static rb_encoding* set_base_encoding(struct enc_table *enc_table, int index, rb_encoding *base) { - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); rb_encoding *enc = enc_table->list[index].enc; ASSUME(enc); @@ -565,6 +562,7 @@ enc_replicate_with_index(struct enc_table *enc_table, const char *name, rb_encod return idx; } +// Setup a new encoding `name` that it a copy of `orig`, with a base of `orig`. Set `name` as dummy if `orig` is dummy. int rb_encdb_replicate(const char *name, const char *orig) { @@ -723,7 +721,7 @@ rb_encdb_alias(const char *alias, const char *orig) static void rb_enc_init(struct enc_table *enc_table) { - enc_table_expand(enc_table, ENCODING_COUNT + 1); + enc_table_count_check(ENCODING_COUNT + 1); if (!enc_table->names) { enc_table->names = st_init_strcasetable_with_size(ENCODING_LIST_CAPA); } @@ -777,6 +775,7 @@ load_encoding(const char *name) ++s; } enclib = rb_fstring(enclib); + encdebug("load_encoding(%s)\n", RSTRING_PTR(enclib)); ruby_debug = Qfalse; errinfo = rb_errinfo(); loaded = rb_require_internal_silent(enclib); @@ -784,15 +783,19 @@ load_encoding(const char *name) rb_set_errinfo(errinfo); if (loaded < 0 || 1 < loaded) { + encdebug("BAD load_encoding(%s): %d\n", name, loaded); idx = -1; } else if ((idx = enc_registered(&global_enc_table, name)) < 0) { + encdebug("load_encoding(%s) after, enc_registered(name) < 0: %d\n", name, idx); idx = -1; } else if (rb_enc_autoload_p(global_enc_table.list[idx].enc)) { + encdebug("load_encoding(%s) after, enc_autoload_p still true\n", name); idx = -1; } + encdebug("load_encoding(%s) returned index: %d\n", name, idx); return idx; } @@ -811,6 +814,7 @@ enc_autoload_body(struct enc_table *enc_table, rb_encoding *enc) if (rb_enc_autoload(base) < 0) return -1; } i = enc->ruby_encoding_index; + encdebug("enc_autoload_body of enc %s from base %s\n", rb_enc_name(enc), rb_enc_name(base)); enc_register_at(enc_table, i & ENC_INDEX_MASK, rb_enc_name(enc), base); ((rb_raw_encoding *)enc)->ruby_encoding_index = i; i &= ENC_INDEX_MASK; @@ -826,15 +830,25 @@ rb_enc_autoload(rb_encoding *enc) { int i; GLOBAL_ENC_TABLE_LOCKING(enc_table) { - i = enc_autoload_body(enc_table, enc); - if (i == -2) { - i = load_encoding(rb_enc_name(enc)); + if (rb_enc_autoload_p(enc)) { + i = enc_autoload_body(enc_table, enc); + if (i == -2) { + i = load_encoding(rb_enc_name(enc)); + encdebug("enc_autoload_body returned -2 (no base), loaded encoding %s, got ret i=%d\n", rb_enc_name(enc), i); + } + else if (i == -1) { + encdebug("enc_autoload_body returned -1 for encoding %s, autoload failed\n", rb_enc_name(enc)); + } + } + else { + encdebug("already autoloaded: %s\n", rb_enc_name(enc)); + i = check_encoding(enc); } } return i; } -/* Return encoding index or UNSPECIFIED_ENCODING from encoding name */ +/* Return encoding index or UNSPECIFIED_ENCODING from encoding name. Loads autoloaded and unregistered encodings. */ int rb_enc_find_index(const char *name) { @@ -844,6 +858,7 @@ rb_enc_find_index(const char *name) GLOBAL_ENC_TABLE_LOCKING(enc_table) { i = enc_registered(enc_table, name); if (i < 0) { + encdebug("rb_enc_find_index not found, loading encoding %s\n", name); i = load_encoding(name); loaded_encoding = true; } @@ -864,7 +879,7 @@ rb_enc_find_index(const char *name) if (rb_enc_autoload(enc) < 0) { rb_warn("failed to load encoding (%s); use ASCII-8BIT instead", name); - return 0; + return ENCINDEX_ASCII_8BIT; } } return i; @@ -1584,14 +1599,15 @@ enc_set_default_encoding(struct default_encoding *def, VALUE encoding, const cha { int overridden = FALSE; - if (def->index != -2) - /* Already set */ - overridden = TRUE; - GLOBAL_ENC_TABLE_LOCKING(enc_table) { + if (def->index != -2) { + /* Already set */ + overridden = TRUE; + } if (NIL_P(encoding)) { + RUBY_ASSERT(def != &default_external); def->index = -1; - def->enc = 0; + RUBY_ATOMIC_PTR_SET(def->enc, 0); char *name_dup = strdup(name); st_data_t existing_name = (st_data_t)name_dup; @@ -1603,9 +1619,10 @@ enc_set_default_encoding(struct default_encoding *def, VALUE encoding, const cha (st_data_t)UNSPECIFIED_ENCODING); } else { - def->index = rb_enc_to_index(rb_to_encoding(encoding)); - def->enc = 0; + rb_encoding *enc = rb_to_encoding(encoding); // NOTE: this autoloads the encoding if necessary + def->index = rb_enc_to_index(enc); enc_alias_internal(enc_table, name, def->index); + RUBY_ATOMIC_PTR_SET(def->enc, 0); } if (def == &default_external) { @@ -1619,15 +1636,18 @@ enc_set_default_encoding(struct default_encoding *def, VALUE encoding, const cha rb_encoding * rb_default_external_encoding(void) { - rb_encoding *enc = NULL; - // TODO: make lock-free + rb_encoding *enc; + enc = (rb_encoding*)RUBY_ATOMIC_PTR_LOAD(default_external.enc); + if (enc) { + return enc; + } GLOBAL_ENC_TABLE_LOCKING(enc_table) { if (default_external.enc) { enc = default_external.enc; } else if (default_external.index >= 0) { - default_external.enc = rb_enc_from_index(default_external.index); - enc = default_external.enc; + enc = rb_enc_from_index(default_external.index); + RUBY_ATOMIC_PTR_SET(default_external.enc, (void*)enc); } else { enc = rb_locale_encoding(); @@ -1712,15 +1732,20 @@ static struct default_encoding default_internal = {-2}; rb_encoding * rb_default_internal_encoding(void) { - rb_encoding *enc = NULL; - // TODO: make lock-free + rb_encoding *enc = RUBY_ATOMIC_PTR_LOAD(default_internal.enc); + if (enc) { + return enc; + } + else if (default_internal.index < 0) { + return NULL; + } GLOBAL_ENC_TABLE_LOCKING(enc_table) { if (!default_internal.enc && default_internal.index >= 0) { - default_internal.enc = rb_enc_from_index(default_internal.index); + enc = rb_enc_from_index(default_internal.index); + RUBY_ATOMIC_PTR_SET(default_internal.enc, (void*)enc); } - enc = default_internal.enc; } - return enc; /* can be NULL */ + return enc; } VALUE @@ -1962,6 +1987,7 @@ Init_Encoding(void) { VALUE list; int i; + encdebug("Init_Encoding\n"); rb_cEncoding = rb_define_class("Encoding", rb_cObject); rb_define_alloc_func(rb_cEncoding, enc_s_alloc); @@ -1993,6 +2019,7 @@ Init_Encoding(void) RBASIC_CLEAR_CLASS(list); rb_vm_register_global_object(list); + encdebug("enc_table->count: %d\n", enc_table->count); for (i = 0; i < enc_table->count; ++i) { rb_ary_push(list, enc_new(enc_table->list[i].enc)); } @@ -2014,6 +2041,7 @@ Init_unicode_version(void) void Init_encodings(void) { + encdebug("Init_encodings\n"); rb_enc_init(&global_enc_table); } diff --git a/test/ruby/test_encoding.rb b/test/ruby/test_encoding.rb index ae4e4a7cf7871a..0897908017eeb5 100644 --- a/test/ruby/test_encoding.rb +++ b/test/ruby/test_encoding.rb @@ -144,7 +144,9 @@ def test_ractor_force_encoding_parallel rs = [] 100.times do rs << Ractor.new do - "abc".force_encoding(Encoding.list.shuffle.first) + 10_000.times do + "abc".force_encoding(Encoding.list.shuffle.first) + end end end while rs.any? From 0c694b56880e8501f328cb3501ac622cc1313a68 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 3 Jul 2025 20:54:47 +0100 Subject: [PATCH 111/130] Add missed runtime_exact_ruby_class case for Regexp --- zjit/src/hir_type/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index 36e9a552d7688a..edde36d271bd70 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -403,6 +403,7 @@ impl Type { if self.is_subtype(types::NilClassExact) { return Some(unsafe { rb_cNilClass }); } if self.is_subtype(types::ObjectExact) { return Some(unsafe { rb_cObject }); } if self.is_subtype(types::RangeExact) { return Some(unsafe { rb_cRange }); } + if self.is_subtype(types::RegexpExact) { return Some(unsafe { rb_cRegexp }); } if self.is_subtype(types::SetExact) { return Some(unsafe { rb_cSet }); } if self.is_subtype(types::StringExact) { return Some(unsafe { rb_cString }); } if self.is_subtype(types::SymbolExact) { return Some(unsafe { rb_cSymbol }); } From 68af19290ad192c0dab50d35ae1f0d811077aee1 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Thu, 3 Jul 2025 20:55:27 +0100 Subject: [PATCH 112/130] Support inference of ClassExact type --- zjit/src/hir.rs | 75 ++++++++++++++++++++++++++++--- zjit/src/hir_type/gen_hir_type.rb | 1 + zjit/src/hir_type/hir_type.inc.rs | 73 +++++++++++++++++------------- zjit/src/hir_type/mod.rs | 14 +++++- 4 files changed, 123 insertions(+), 40 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index be320f6d741a5c..32afebce13e17a 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -4073,10 +4073,10 @@ mod tests { assert_method_hir("test", expect![[r#" fn test: bb0(v0:BasicObject, v1:BasicObject): - v3:BasicObject[VMFrozenCore] = Const Value(VALUE(0x1000)) + v3:ClassExact[VMFrozenCore] = Const Value(VALUE(0x1000)) v5:HashExact = NewHash v7:BasicObject = SendWithoutBlock v3, :core#hash_merge_kwd, v5, v1 - v8:BasicObject[VMFrozenCore] = Const Value(VALUE(0x1000)) + v8:ClassExact[VMFrozenCore] = Const Value(VALUE(0x1000)) v9:StaticSymbol[:b] = Const Value(VALUE(0x1008)) v10:Fixnum[1] = Const Value(1) v12:BasicObject = SendWithoutBlock v8, :core#hash_merge_ptr, v7, v9, v10 @@ -4525,7 +4525,7 @@ mod tests { assert_method_hir_with_opcode("test", YARVINSN_putspecialobject, expect![[r#" fn test: bb0(v0:BasicObject): - v2:BasicObject[VMFrozenCore] = Const Value(VALUE(0x1000)) + v2:ClassExact[VMFrozenCore] = Const Value(VALUE(0x1000)) v3:BasicObject = PutSpecialObject CBase v4:StaticSymbol[:aliased] = Const Value(VALUE(0x1008)) v5:StaticSymbol[:__callee__] = Const Value(VALUE(0x1010)) @@ -5753,6 +5753,69 @@ mod opt_tests { "#]]); } + #[test] + fn normal_class_type_inference() { + eval(" + class C; end + def test = C + test # Warm the constant cache + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, C) + v7:ClassExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + Return v7 + "#]]); + } + + #[test] + fn core_classes_type_inference() { + eval(" + def test = [String, Class, Module, BasicObject] + test # Warm the constant cache + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, String) + v15:ClassExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1010, Class) + v18:ClassExact[VALUE(0x1018)] = Const Value(VALUE(0x1018)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1020, Module) + v21:ClassExact[VALUE(0x1028)] = Const Value(VALUE(0x1028)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1030, BasicObject) + v24:ClassExact[VALUE(0x1038)] = Const Value(VALUE(0x1038)) + v11:ArrayExact = NewArray v15, v18, v21, v24 + Return v11 + "#]]); + } + + #[test] + fn module_instances_not_class_exact() { + eval(" + def test = [Enumerable, Kernel] + test # Warm the constant cache + "); + assert_optimized_method_hir("test", expect![[r#" + fn test: + bb0(v0:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, Enumerable) + v11:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1010, Kernel) + v14:BasicObject[VALUE(0x1018)] = Const Value(VALUE(0x1018)) + v7:ArrayExact = NewArray v11, v14 + Return v7 + "#]]); + } + #[test] fn eliminate_array_size() { eval(" @@ -5902,7 +5965,7 @@ mod opt_tests { bb0(v0:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, Foo::Bar::C) - v7:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v7:ClassExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) Return v7 "#]]); } @@ -5919,7 +5982,7 @@ mod opt_tests { bb0(v0:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, C) - v20:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v20:ClassExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v4:NilClassExact = Const Value(nil) v11:BasicObject = SendWithoutBlock v20, :new Return v11 @@ -5942,7 +6005,7 @@ mod opt_tests { bb0(v0:BasicObject): PatchPoint SingleRactorMode PatchPoint StableConstantNames(0x1000, C) - v22:BasicObject[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + v22:ClassExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) v4:NilClassExact = Const Value(nil) v5:Fixnum[1] = Const Value(1) v13:BasicObject = SendWithoutBlock v22, :new, v5 diff --git a/zjit/src/hir_type/gen_hir_type.rb b/zjit/src/hir_type/gen_hir_type.rb index 69252ee9427857..50259a9816e500 100644 --- a/zjit/src/hir_type/gen_hir_type.rb +++ b/zjit/src/hir_type/gen_hir_type.rb @@ -74,6 +74,7 @@ def base_type name base_type "Range" base_type "Set" base_type "Regexp" +base_type "Class" (integer, integer_exact) = base_type "Integer" # CRuby partitions Integer into immediate and non-immediate variants. diff --git a/zjit/src/hir_type/hir_type.inc.rs b/zjit/src/hir_type/hir_type.inc.rs index ff3eccfcb43ca6..57349aba147f22 100644 --- a/zjit/src/hir_type/hir_type.inc.rs +++ b/zjit/src/hir_type/hir_type.inc.rs @@ -9,7 +9,7 @@ mod bits { pub const BasicObjectSubclass: u64 = 1u64 << 3; pub const Bignum: u64 = 1u64 << 4; pub const BoolExact: u64 = FalseClassExact | TrueClassExact; - pub const BuiltinExact: u64 = ArrayExact | BasicObjectExact | FalseClassExact | FloatExact | HashExact | IntegerExact | NilClassExact | ObjectExact | RangeExact | RegexpExact | SetExact | StringExact | SymbolExact | TrueClassExact; + pub const BuiltinExact: u64 = ArrayExact | BasicObjectExact | ClassExact | FalseClassExact | FloatExact | HashExact | IntegerExact | NilClassExact | ObjectExact | RangeExact | RegexpExact | SetExact | StringExact | SymbolExact | TrueClassExact; pub const CBool: u64 = 1u64 << 5; pub const CDouble: u64 = 1u64 << 6; pub const CInt: u64 = CSigned | CUnsigned; @@ -27,53 +27,56 @@ mod bits { pub const CUnsigned: u64 = CUInt16 | CUInt32 | CUInt64 | CUInt8; pub const CValue: u64 = CBool | CDouble | CInt | CNull | CPtr; pub const CallableMethodEntry: u64 = 1u64 << 17; - pub const DynamicSymbol: u64 = 1u64 << 18; + pub const Class: u64 = ClassExact | ClassSubclass; + pub const ClassExact: u64 = 1u64 << 18; + pub const ClassSubclass: u64 = 1u64 << 19; + pub const DynamicSymbol: u64 = 1u64 << 20; pub const Empty: u64 = 0u64; pub const FalseClass: u64 = FalseClassExact | FalseClassSubclass; - pub const FalseClassExact: u64 = 1u64 << 19; - pub const FalseClassSubclass: u64 = 1u64 << 20; - pub const Fixnum: u64 = 1u64 << 21; + pub const FalseClassExact: u64 = 1u64 << 21; + pub const FalseClassSubclass: u64 = 1u64 << 22; + pub const Fixnum: u64 = 1u64 << 23; pub const Float: u64 = FloatExact | FloatSubclass; pub const FloatExact: u64 = Flonum | HeapFloat; - pub const FloatSubclass: u64 = 1u64 << 22; - pub const Flonum: u64 = 1u64 << 23; + pub const FloatSubclass: u64 = 1u64 << 24; + pub const Flonum: u64 = 1u64 << 25; pub const Hash: u64 = HashExact | HashSubclass; - pub const HashExact: u64 = 1u64 << 24; - pub const HashSubclass: u64 = 1u64 << 25; - pub const HeapFloat: u64 = 1u64 << 26; + pub const HashExact: u64 = 1u64 << 26; + pub const HashSubclass: u64 = 1u64 << 27; + pub const HeapFloat: u64 = 1u64 << 28; pub const Immediate: u64 = FalseClassExact | Fixnum | Flonum | NilClassExact | StaticSymbol | TrueClassExact | Undef; pub const Integer: u64 = IntegerExact | IntegerSubclass; pub const IntegerExact: u64 = Bignum | Fixnum; - pub const IntegerSubclass: u64 = 1u64 << 27; + pub const IntegerSubclass: u64 = 1u64 << 29; pub const NilClass: u64 = NilClassExact | NilClassSubclass; - pub const NilClassExact: u64 = 1u64 << 28; - pub const NilClassSubclass: u64 = 1u64 << 29; - pub const Object: u64 = Array | FalseClass | Float | Hash | Integer | NilClass | ObjectExact | ObjectSubclass | Range | Regexp | Set | String | Symbol | TrueClass; - pub const ObjectExact: u64 = 1u64 << 30; - pub const ObjectSubclass: u64 = 1u64 << 31; + pub const NilClassExact: u64 = 1u64 << 30; + pub const NilClassSubclass: u64 = 1u64 << 31; + pub const Object: u64 = Array | Class | FalseClass | Float | Hash | Integer | NilClass | ObjectExact | ObjectSubclass | Range | Regexp | Set | String | Symbol | TrueClass; + pub const ObjectExact: u64 = 1u64 << 32; + pub const ObjectSubclass: u64 = 1u64 << 33; pub const Range: u64 = RangeExact | RangeSubclass; - pub const RangeExact: u64 = 1u64 << 32; - pub const RangeSubclass: u64 = 1u64 << 33; + pub const RangeExact: u64 = 1u64 << 34; + pub const RangeSubclass: u64 = 1u64 << 35; pub const Regexp: u64 = RegexpExact | RegexpSubclass; - pub const RegexpExact: u64 = 1u64 << 34; - pub const RegexpSubclass: u64 = 1u64 << 35; + pub const RegexpExact: u64 = 1u64 << 36; + pub const RegexpSubclass: u64 = 1u64 << 37; pub const RubyValue: u64 = BasicObject | CallableMethodEntry | Undef; pub const Set: u64 = SetExact | SetSubclass; - pub const SetExact: u64 = 1u64 << 36; - pub const SetSubclass: u64 = 1u64 << 37; - pub const StaticSymbol: u64 = 1u64 << 38; + pub const SetExact: u64 = 1u64 << 38; + pub const SetSubclass: u64 = 1u64 << 39; + pub const StaticSymbol: u64 = 1u64 << 40; pub const String: u64 = StringExact | StringSubclass; - pub const StringExact: u64 = 1u64 << 39; - pub const StringSubclass: u64 = 1u64 << 40; - pub const Subclass: u64 = ArraySubclass | BasicObjectSubclass | FalseClassSubclass | FloatSubclass | HashSubclass | IntegerSubclass | NilClassSubclass | ObjectSubclass | RangeSubclass | RegexpSubclass | SetSubclass | StringSubclass | SymbolSubclass | TrueClassSubclass; + pub const StringExact: u64 = 1u64 << 41; + pub const StringSubclass: u64 = 1u64 << 42; + pub const Subclass: u64 = ArraySubclass | BasicObjectSubclass | ClassSubclass | FalseClassSubclass | FloatSubclass | HashSubclass | IntegerSubclass | NilClassSubclass | ObjectSubclass | RangeSubclass | RegexpSubclass | SetSubclass | StringSubclass | SymbolSubclass | TrueClassSubclass; pub const Symbol: u64 = SymbolExact | SymbolSubclass; pub const SymbolExact: u64 = DynamicSymbol | StaticSymbol; - pub const SymbolSubclass: u64 = 1u64 << 41; + pub const SymbolSubclass: u64 = 1u64 << 43; pub const TrueClass: u64 = TrueClassExact | TrueClassSubclass; - pub const TrueClassExact: u64 = 1u64 << 42; - pub const TrueClassSubclass: u64 = 1u64 << 43; - pub const Undef: u64 = 1u64 << 44; - pub const AllBitPatterns: [(&'static str, u64); 73] = [ + pub const TrueClassExact: u64 = 1u64 << 44; + pub const TrueClassSubclass: u64 = 1u64 << 45; + pub const Undef: u64 = 1u64 << 46; + pub const AllBitPatterns: [(&'static str, u64); 76] = [ ("Any", Any), ("RubyValue", RubyValue), ("Immediate", Immediate), @@ -123,6 +126,9 @@ mod bits { ("FalseClassSubclass", FalseClassSubclass), ("FalseClassExact", FalseClassExact), ("DynamicSymbol", DynamicSymbol), + ("Class", Class), + ("ClassSubclass", ClassSubclass), + ("ClassExact", ClassExact), ("CallableMethodEntry", CallableMethodEntry), ("CValue", CValue), ("CInt", CInt), @@ -148,7 +154,7 @@ mod bits { ("ArrayExact", ArrayExact), ("Empty", Empty), ]; - pub const NumTypeBits: u64 = 45; + pub const NumTypeBits: u64 = 47; } pub mod types { use super::*; @@ -179,6 +185,9 @@ pub mod types { pub const CUnsigned: Type = Type::from_bits(bits::CUnsigned); pub const CValue: Type = Type::from_bits(bits::CValue); pub const CallableMethodEntry: Type = Type::from_bits(bits::CallableMethodEntry); + pub const Class: Type = Type::from_bits(bits::Class); + pub const ClassExact: Type = Type::from_bits(bits::ClassExact); + pub const ClassSubclass: Type = Type::from_bits(bits::ClassSubclass); pub const DynamicSymbol: Type = Type::from_bits(bits::DynamicSymbol); pub const Empty: Type = Type::from_bits(bits::Empty); pub const FalseClass: Type = Type::from_bits(bits::FalseClass); diff --git a/zjit/src/hir_type/mod.rs b/zjit/src/hir_type/mod.rs index edde36d271bd70..422055e6d02aa2 100644 --- a/zjit/src/hir_type/mod.rs +++ b/zjit/src/hir_type/mod.rs @@ -1,6 +1,6 @@ #![allow(non_upper_case_globals)] -use crate::cruby::{Qfalse, Qnil, Qtrue, VALUE, RUBY_T_ARRAY, RUBY_T_STRING, RUBY_T_HASH}; -use crate::cruby::{rb_cInteger, rb_cFloat, rb_cArray, rb_cHash, rb_cString, rb_cSymbol, rb_cObject, rb_cTrueClass, rb_cFalseClass, rb_cNilClass, rb_cRange, rb_cSet, rb_cRegexp}; +use crate::cruby::{Qfalse, Qnil, Qtrue, VALUE, RUBY_T_ARRAY, RUBY_T_STRING, RUBY_T_HASH, RUBY_T_CLASS}; +use crate::cruby::{rb_cInteger, rb_cFloat, rb_cArray, rb_cHash, rb_cString, rb_cSymbol, rb_cObject, rb_cTrueClass, rb_cFalseClass, rb_cNilClass, rb_cRange, rb_cSet, rb_cRegexp, rb_cClass, rb_cModule}; use crate::cruby::ClassRelationship; use crate::cruby::get_class_name; use crate::cruby::ruby_sym_to_rust_string; @@ -145,6 +145,11 @@ fn is_range_exact(val: VALUE) -> bool { val.class_of() == unsafe { rb_cRange } } +fn is_class_exact(val: VALUE) -> bool { + // Objects with RUBY_T_CLASS type and not instances of Module + val.builtin_type() == RUBY_T_CLASS && val.class_of() != unsafe { rb_cModule } +} + impl Type { /// Create a `Type` from the given integer. pub const fn fixnum(val: i64) -> Type { @@ -197,6 +202,9 @@ impl Type { else if is_string_exact(val) { Type { bits: bits::StringExact, spec: Specialization::Object(val) } } + else if is_class_exact(val) { + Type { bits: bits::ClassExact, spec: Specialization::Object(val) } + } else if val.class_of() == unsafe { rb_cRegexp } { Type { bits: bits::RegexpExact, spec: Specialization::Object(val) } } @@ -288,6 +296,7 @@ impl Type { fn is_builtin(class: VALUE) -> bool { if class == unsafe { rb_cArray } { return true; } + if class == unsafe { rb_cClass } { return true; } if class == unsafe { rb_cFalseClass } { return true; } if class == unsafe { rb_cFloat } { return true; } if class == unsafe { rb_cHash } { return true; } @@ -396,6 +405,7 @@ impl Type { return Some(val); } if self.is_subtype(types::ArrayExact) { return Some(unsafe { rb_cArray }); } + if self.is_subtype(types::ClassExact) { return Some(unsafe { rb_cClass }); } if self.is_subtype(types::FalseClassExact) { return Some(unsafe { rb_cFalseClass }); } if self.is_subtype(types::FloatExact) { return Some(unsafe { rb_cFloat }); } if self.is_subtype(types::HashExact) { return Some(unsafe { rb_cHash }); } From 66aaf5b67f60cd45b8e7214d5bc8539e0063e328 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 4 Jul 2025 10:24:08 +0900 Subject: [PATCH 113/130] actions/cache is working with relative path --- .github/workflows/windows.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 9deb2c6c27a9e1..7c8fd75d2df2bf 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -121,7 +121,7 @@ jobs: - name: Restore vcpkg artifact uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: - path: ${{ github.workspace }}/src/vcpkg_installed + path: src\vcpkg_installed key: windows-${{ matrix.os }}-vcpkg-${{ hashFiles('src/vcpkg.json') }} - name: Install libraries with vcpkg From 9503a77da271d1684b6dd813f2ca569213419bfb Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 4 Jul 2025 13:01:35 +0900 Subject: [PATCH 114/130] DRb has been extracted as bundled gems --- doc/security.rdoc | 9 -- misc/expand_tabs.rb | 1 - sample/drb/README.ja.rdoc | 59 ---------- sample/drb/README.rdoc | 56 --------- sample/drb/acl.rb | 15 --- sample/drb/darray.rb | 12 -- sample/drb/darrayc.rb | 47 -------- sample/drb/dbiff.rb | 51 --------- sample/drb/dcdbiff.rb | 43 ------- sample/drb/dchatc.rb | 41 ------- sample/drb/dchats.rb | 69 ------------ sample/drb/dhasen.rb | 41 ------- sample/drb/dhasenc.rb | 14 --- sample/drb/dlogc.rb | 16 --- sample/drb/dlogd.rb | 38 ------- sample/drb/dqin.rb | 13 --- sample/drb/dqlib.rb | 14 --- sample/drb/dqout.rb | 14 --- sample/drb/dqueue.rb | 11 -- sample/drb/drbc.rb | 45 -------- sample/drb/drbch.rb | 48 -------- sample/drb/drbm.rb | 60 ---------- sample/drb/drbmc.rb | 22 ---- sample/drb/drbs-acl.rb | 51 --------- sample/drb/drbs.rb | 64 ----------- sample/drb/drbssl_c.rb | 19 ---- sample/drb/drbssl_s.rb | 31 ----- sample/drb/extserv_test.rb | 80 ------------- sample/drb/gw_ct.rb | 29 ----- sample/drb/gw_cu.rb | 28 ----- sample/drb/gw_s.rb | 10 -- sample/drb/holderc.rb | 22 ---- sample/drb/holders.rb | 63 ----------- sample/drb/http0.rb | 77 ------------- sample/drb/http0serv.rb | 120 -------------------- sample/drb/name.rb | 117 ------------------- sample/drb/namec.rb | 36 ------ sample/drb/old_tuplespace.rb | 212 ----------------------------------- sample/drb/rinda_ts.rb | 7 -- sample/drb/rindac.rb | 17 --- sample/drb/rindas.rb | 18 --- sample/drb/ring_echo.rb | 29 ----- sample/drb/ring_inspect.rb | 30 ----- sample/drb/ring_place.rb | 25 ----- sample/drb/simpletuple.rb | 89 --------------- sample/drb/speedc.rb | 21 ---- sample/drb/speeds.rb | 31 ----- 47 files changed, 1965 deletions(-) delete mode 100644 sample/drb/README.ja.rdoc delete mode 100644 sample/drb/README.rdoc delete mode 100644 sample/drb/acl.rb delete mode 100644 sample/drb/darray.rb delete mode 100644 sample/drb/darrayc.rb delete mode 100644 sample/drb/dbiff.rb delete mode 100644 sample/drb/dcdbiff.rb delete mode 100644 sample/drb/dchatc.rb delete mode 100644 sample/drb/dchats.rb delete mode 100644 sample/drb/dhasen.rb delete mode 100644 sample/drb/dhasenc.rb delete mode 100644 sample/drb/dlogc.rb delete mode 100644 sample/drb/dlogd.rb delete mode 100644 sample/drb/dqin.rb delete mode 100644 sample/drb/dqlib.rb delete mode 100644 sample/drb/dqout.rb delete mode 100644 sample/drb/dqueue.rb delete mode 100644 sample/drb/drbc.rb delete mode 100644 sample/drb/drbch.rb delete mode 100644 sample/drb/drbm.rb delete mode 100644 sample/drb/drbmc.rb delete mode 100644 sample/drb/drbs-acl.rb delete mode 100644 sample/drb/drbs.rb delete mode 100644 sample/drb/drbssl_c.rb delete mode 100644 sample/drb/drbssl_s.rb delete mode 100644 sample/drb/extserv_test.rb delete mode 100644 sample/drb/gw_ct.rb delete mode 100644 sample/drb/gw_cu.rb delete mode 100644 sample/drb/gw_s.rb delete mode 100644 sample/drb/holderc.rb delete mode 100644 sample/drb/holders.rb delete mode 100644 sample/drb/http0.rb delete mode 100644 sample/drb/http0serv.rb delete mode 100644 sample/drb/name.rb delete mode 100644 sample/drb/namec.rb delete mode 100644 sample/drb/old_tuplespace.rb delete mode 100644 sample/drb/rinda_ts.rb delete mode 100644 sample/drb/rindac.rb delete mode 100644 sample/drb/rindas.rb delete mode 100644 sample/drb/ring_echo.rb delete mode 100644 sample/drb/ring_inspect.rb delete mode 100644 sample/drb/ring_place.rb delete mode 100644 sample/drb/simpletuple.rb delete mode 100644 sample/drb/speedc.rb delete mode 100644 sample/drb/speeds.rb diff --git a/doc/security.rdoc b/doc/security.rdoc index b7153ff0e96b0e..af9970d336e5ff 100644 --- a/doc/security.rdoc +++ b/doc/security.rdoc @@ -125,12 +125,3 @@ Note that the use of +public_send+ is also dangerous, as +send+ itself is public: 1.public_send("send", "eval", "...ruby code to be executed...") - -== DRb - -As DRb allows remote clients to invoke arbitrary methods, it is not suitable to -expose to untrusted clients. - -When using DRb, try to avoid exposing it over the network if possible. If this -isn't possible and you need to expose DRb to the world, you *must* configure an -appropriate security policy with +DRb::ACL+. diff --git a/misc/expand_tabs.rb b/misc/expand_tabs.rb index a94eea5046f123..9df96ee84ee72c 100755 --- a/misc/expand_tabs.rb +++ b/misc/expand_tabs.rb @@ -68,7 +68,6 @@ def with_clean_env debug delegate did_you_mean - drb english erb fileutils diff --git a/sample/drb/README.ja.rdoc b/sample/drb/README.ja.rdoc deleted file mode 100644 index 1697b1b70416bc..00000000000000 --- a/sample/drb/README.ja.rdoc +++ /dev/null @@ -1,59 +0,0 @@ -= サンプルスクリプト - -* Arrayをリモートから利用してイテレータを試す。 - * darray.rb --- server - * darrayc.rb --- client - -* 簡易チャット - * dchats.rb --- server - * dchatc.rb --- client - -* 分散chasen - * dhasen.rb --- server - * dhasenc.rb --- client - -* 簡易ログサーバ - * dlogd.rb --- server - * dlogc.rb --- client - -* Queueサーバ。 - クライアントdqin.rbはQueueサーバの知らないオブジェクト(DQEntry)を - pushするがDRbUnknownによりクライアントdqout.rbがpopできる。 - * dqueue.rb --- server - * dqin.rb --- client。DQEntryオブジェクトをpushする - * dqout.rb --- client。DQEntryオブジェクトをpopする - * dqlib.rb --- DQEntryを定義したライブラリ - -* 名前による参照 - IdConvをカスタマイズしてidでなく名前で参照する例 - * name.rb --- server - * namec.rb --- client - -* extservのサンプル - * extserv_test.rb - -* TimerIdConvの使用例 - * holders.rb --- server。ruby -d hodlers.rbとするとTimerIdConvを使用する。 - * holderc.rb --- client - -* rinda.rbの使用例 - * rinda_ts.rb --- TupleSpaceサーバ。 - * rindac.rb --- TupleSpaceのclientでアプリケーションのclient - * rindas.rb --- TupleSpaceのclientでアプリケーションのserver - -* observerの使用例 - cdbiff - http://namazu.org/~satoru/cdbiff/ - * dbiff.rb --- dcdbiff server - * dcdbiff.rb --- dcdbiff client - -* drbsslの使用例 - * drbssl_s.rb - * drbssl_c.rb - -* DRbProtocolの追加例 - * http0.rb - * http0serv.rb - -* ringの使用例 - * ring_place.rb - * ring_echo.rb diff --git a/sample/drb/README.rdoc b/sample/drb/README.rdoc deleted file mode 100644 index e6b457bc5c170c..00000000000000 --- a/sample/drb/README.rdoc +++ /dev/null @@ -1,56 +0,0 @@ -= Sample scripts - -* array and iterator - * darray.rb --- server - * darrayc.rb --- client - -* simple chat - * dchats.rb --- server - * dchatc.rb --- client - -* distributed chasen (for Japanese) - * dhasen.rb --- server - * dhasenc.rb --- client - -* simple log server - * dlogd.rb --- server - * dlogc.rb --- client - -* Queue server, and DRbUnknown demo - * dqueue.rb --- server - * dqin.rb --- client. push DQEntry objects. - * dqout.rb --- client. pop DQEntry objects. - * dqlib.rb --- define DQEntry - -* IdConv customize demo: reference by name - * name.rb --- server - * namec.rb --- client - -* extserv - * extserv_test.rb - -* IdConv customize demo 2: using TimerIdConv - * holders.rb --- server - * holderc.rb --- client - -* rinda, remote tuplespace - * rinda_ts.rb --- TupleSpace server. - * rindas.rb --- provide simple service via TupleSpace. - * rindac.rb --- service user - -* observer - cdbiff - http://namazu.org/~satoru/cdbiff/ - * dbiff.rb --- dcdbiff server - * dcdbiff.rb --- dcdbiff client - -* drbssl - * drbssl_s.rb - * drbssl_c.rb - -* add DRbProtocol - * http0.rb - * http0serv.rb - -* Rinda::Ring - * ring_place.rb - * ring_echo.rb diff --git a/sample/drb/acl.rb b/sample/drb/acl.rb deleted file mode 100644 index d93eb9c1fcd119..00000000000000 --- a/sample/drb/acl.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'drb/acl' - -list = %w(deny all - allow 192.168.1.1 - allow ::ffff:192.168.1.2 - allow 192.168.1.3 -) - -addr = ["AF_INET", 10, "lc630", "192.168.1.3"] - -acl = ACL.new -p acl.allow_addr?(addr) - -acl = ACL.new(list, ACL::DENY_ALLOW) -p acl.allow_addr?(addr) diff --git a/sample/drb/darray.rb b/sample/drb/darray.rb deleted file mode 100644 index d2ac39513f4a6d..00000000000000 --- a/sample/drb/darray.rb +++ /dev/null @@ -1,12 +0,0 @@ -=begin - distributed Ruby --- Array - Copyright (c) 1999-2001 Masatoshi SEKI -=end - -require 'drb/drb' - -here = ARGV.shift -DRb.start_service(here, [1, 2, "III", 4, "five", 6]) -puts DRb.uri -DRb.thread.join - diff --git a/sample/drb/darrayc.rb b/sample/drb/darrayc.rb deleted file mode 100644 index 579e11564e9f96..00000000000000 --- a/sample/drb/darrayc.rb +++ /dev/null @@ -1,47 +0,0 @@ -=begin - distributed Ruby --- Array client - Copyright (c) 1999-2001 Masatoshi SEKI -=end - -require 'drb/drb' - -there = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service(nil, nil) -ro = DRbObject.new(nil, there) -p ro.size - -puts "# collect" -a = ro.collect { |x| - x + x -} -p a - -puts "# find" -p ro.find { |x| x.kind_of? String } - -puts "# each, break" -ro.each do |x| - next if x == "five" - puts x -end - -puts "# each, break" -ro.each do |x| - break if x == "five" - puts x -end - -puts "# each, next" -ro.each do |x| - next if x == "five" - puts x -end - -puts "# each, redo" -count = 0 -ro.each do |x| - count += 1 - puts count - redo if count == 3 -end diff --git a/sample/drb/dbiff.rb b/sample/drb/dbiff.rb deleted file mode 100644 index 290eb1d28b55fe..00000000000000 --- a/sample/drb/dbiff.rb +++ /dev/null @@ -1,51 +0,0 @@ -# -# dbiff.rb - distributed cdbiff (server) -# * original: cdbiff by Satoru Takabayashi - -require 'drb/drb' -require 'drb/eq' -require 'drb/observer' - -class Biff - include DRb::DRbObservable - - def initialize(filename, interval) - super() - @filename = filename - @interval = interval - end - - def run - last = Time.now - while true - begin - sleep(@interval) - current = File::mtime(@filename) - if current > last - changed - begin - notify_observers(@filename, current) - rescue Error - end - last = current - end - rescue - next - end - end - end -end - -def main - filename = "/var/mail/#{ENV['USER']}" - interval = 15 - uri = 'druby://:19903' - - biff = Biff.new(filename, interval) - - DRb.start_service(uri, biff) - biff.run -end - -main - diff --git a/sample/drb/dcdbiff.rb b/sample/drb/dcdbiff.rb deleted file mode 100644 index 6a24680c33fc56..00000000000000 --- a/sample/drb/dcdbiff.rb +++ /dev/null @@ -1,43 +0,0 @@ -# -# dcdbiff.rb - distributed cdbiff (client) -# * original: cdbiff by Satoru Takabayashi - -require 'drb/drb' -require 'drb/eq' - -class Notify - include DRbUndumped - - def initialize(biff, command) - @biff = biff - @command = command - - @biff.add_observer(self) - end - - def update(filename, time) - p [filename, time] if $DEBUG - system(@command) - end - - def done - begin - @biff.delete_observer(self) - rescue - end - end -end - -def main - command = 'eject' - uri = 'druby://localhost:19903' - - DRb.start_service - biff = DRbObject.new(nil, uri) - notify = Notify.new(biff, command) - - trap("INT"){ notify.done } - DRb.thread.join -end - -main diff --git a/sample/drb/dchatc.rb b/sample/drb/dchatc.rb deleted file mode 100644 index 2b8ddbf4cc649b..00000000000000 --- a/sample/drb/dchatc.rb +++ /dev/null @@ -1,41 +0,0 @@ -=begin - distributed Ruby --- chat client - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' - -class ChatClient - include DRbUndumped - - def initialize(name) - @name = name - @key = nil - end - attr_reader(:name) - attr_accessor(:key) - - def message(there, str) - raise 'invalid key' unless @key == there - puts str - end -end - -if __FILE__ == $0 - begin - there = ARGV.shift - name = ARGV.shift - raise "usage" unless (there and name) - rescue - $stderr.puts("usage: #{$0} ") - exit 1 - end - DRb.start_service - ro = DRbObject.new(nil, there) - - chat = ChatClient.new(name) - entry = ro.add_member(chat) - while gets - entry.say($_) - end -end diff --git a/sample/drb/dchats.rb b/sample/drb/dchats.rb deleted file mode 100644 index c96486a4527d97..00000000000000 --- a/sample/drb/dchats.rb +++ /dev/null @@ -1,69 +0,0 @@ -=begin - distributed Ruby --- chat server - Copyright (c) 1999-2000 Masatoshi SEKI -=end -require 'drb/drb' - -class ChatEntry - include DRbUndumped - - def initialize(server, there) - @server = server - @there = there - @name = there.name - @key = there.key = Time.now - end - attr :name, true - attr :there - - def say(str) - @server.distribute(@there, str) - end - - def listen(str) - @there.message(@key, str) - end -end - - -class ChatServer - def initialize - @mutex = Thread::Mutex.new - @members = {} - end - - def add_member(there) - client = ChatEntry.new(self, there) - @mutex.synchronize do - @members[there] = client - end - client - end - - def distribute(there, str) - name = @members[there].name - msg = "<#{name}> #{str}" - msg2 = ">#{name}< #{str}" - @mutex.synchronize do - for m in @members.keys - begin - if m == there - @members[m].listen(msg2) - else - @members[m].listen(msg) - end - rescue - p $! - @members.delete(m) - end - end - end - end -end - -if __FILE__ == $0 - here = ARGV.shift - DRb.start_service(here, ChatServer.new) - puts DRb.uri - DRb.thread.join -end diff --git a/sample/drb/dhasen.rb b/sample/drb/dhasen.rb deleted file mode 100644 index 13ff38940e7687..00000000000000 --- a/sample/drb/dhasen.rb +++ /dev/null @@ -1,41 +0,0 @@ -=begin - distributed Ruby --- dRuby Sample Server --- chasen server - Copyright (c) 1999-2001 Masatoshi SEKI -=end - -=begin - How to play. - - Terminal 1 - | % ruby dhasen.rb - | druby://yourhost:7640 - - Terminal 2 - | % ruby dhasenc.rb druby://yourhost:7640 - -=end - -require 'drb/drb' -require 'chasen' - -class Dhasen - include DRbUndumped - - def initialize - @mutex = Thread::Mutex.new - end - - def sparse(str, *arg) - @mutex.synchronize do - Chasen.getopt(*arg) - Chasen.sparse(str) - end - end -end - -if __FILE__ == $0 - DRb.start_service(nil, Dhasen.new) - puts DRb.uri - DRb.thread.join -end - diff --git a/sample/drb/dhasenc.rb b/sample/drb/dhasenc.rb deleted file mode 100644 index dddac9882cc970..00000000000000 --- a/sample/drb/dhasenc.rb +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 -*- -=begin - distributed Ruby --- dRuby Sample Client -- chasen client - Copyright (c) 1999-2001 Masatoshi SEKI -=end - -require 'drb/drb' - -there = ARGV.shift || raise("usage: #{$0} ") -DRb.start_service -dhasen = DRbObject.new(nil, there) - -print dhasen.sparse("本日は、晴天なり。", "-F", '(%BB %m %M)\n', "-j") -print dhasen.sparse("本日は、晴天なり。", "-F", '(%m %M)\n') diff --git a/sample/drb/dlogc.rb b/sample/drb/dlogc.rb deleted file mode 100644 index 3939a718271c74..00000000000000 --- a/sample/drb/dlogc.rb +++ /dev/null @@ -1,16 +0,0 @@ -=begin - distributed Ruby --- Log test - Copyright (c) 1999-2001 Masatoshi SEKI -=end - -require 'drb/drb' - -there = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service -ro = DRbObject.new(nil, there) -ro.log(123) -ro.log("hello") -sleep 2 -ro.log("wakeup") - diff --git a/sample/drb/dlogd.rb b/sample/drb/dlogd.rb deleted file mode 100644 index a87e6603464a73..00000000000000 --- a/sample/drb/dlogd.rb +++ /dev/null @@ -1,38 +0,0 @@ -=begin - distributed Ruby --- Log server - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' - -class Logger - def initialize(fname) - @fname = fname.to_s - @fp = File.open(@fname, "a+") - @queue = Thread::Queue.new - @th = Thread.new { self.flush } - end - - def log(str) - @queue.push("#{Time.now}\t" + str.to_s) - end - - def flush - begin - while(1) - @fp.puts(@queue.pop) - @fp.flush - end - ensure - @fp.close - end - end -end - -if __FILE__ == $0 - here = ARGV.shift - DRb.start_service(here, Logger.new('/usr/tmp/dlogd.log')) - puts DRb.uri - DRb.thread.join -end - diff --git a/sample/drb/dqin.rb b/sample/drb/dqin.rb deleted file mode 100644 index 4751335fff7c3e..00000000000000 --- a/sample/drb/dqin.rb +++ /dev/null @@ -1,13 +0,0 @@ -=begin - distributed Ruby --- store - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' -require 'dqlib' - -there = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service -queue = DRbObject.new(nil, there) -queue.push(DQEntry.new(DRb.uri)) diff --git a/sample/drb/dqlib.rb b/sample/drb/dqlib.rb deleted file mode 100644 index 75f2e6115b7829..00000000000000 --- a/sample/drb/dqlib.rb +++ /dev/null @@ -1,14 +0,0 @@ -class DQEntry - def initialize(name) - @name = name - end - - def greeting - "Hello, This is #{@name}." - end - alias to_s greeting -end - -if __FILE__ == $0 - puts DQEntry.new('DQEntry') -end diff --git a/sample/drb/dqout.rb b/sample/drb/dqout.rb deleted file mode 100644 index f2b0b4ac95a952..00000000000000 --- a/sample/drb/dqout.rb +++ /dev/null @@ -1,14 +0,0 @@ -=begin - distributed Ruby --- fetch - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' -require 'dqlib' - -there = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service -queue = DRbObject.new(nil, there) -entry = queue.pop -puts entry.greeting diff --git a/sample/drb/dqueue.rb b/sample/drb/dqueue.rb deleted file mode 100644 index a9afa8c8585ec8..00000000000000 --- a/sample/drb/dqueue.rb +++ /dev/null @@ -1,11 +0,0 @@ -=begin - distributed Ruby --- Queue - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' - -DRb.start_service(nil, Thread::Queue.new) -puts DRb.uri -DRb.thread.join - diff --git a/sample/drb/drbc.rb b/sample/drb/drbc.rb deleted file mode 100644 index 50a86c39e8a111..00000000000000 --- a/sample/drb/drbc.rb +++ /dev/null @@ -1,45 +0,0 @@ -=begin - distributed Ruby --- dRuby Sample Client - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' - -class DRbEx2 - include DRbUndumped - - def initialize(n) - @n = n - end - - def to_i - @n.to_i - end -end - -if __FILE__ == $0 - there = ARGV.shift - unless there - $stderr.puts("usage: #{$0} ") - exit 1 - end - - DRb.start_service() - ro = DRbObject.new_with_uri(there) - - puts ro - p ro.to_a - puts ro.hello - p ro.hello - puts ro.sample(DRbEx2.new(1), 2, 3) - puts ro.sample(1, ro.sample(DRbEx2.new(1), 2, 3), DRbEx2.new(3)) - - begin - ro.err - rescue DRb::DRbUnknownError - p $! - p $!.unknown - rescue RuntimeError - p $! - end -end diff --git a/sample/drb/drbch.rb b/sample/drb/drbch.rb deleted file mode 100644 index 07fdcd5fae1e67..00000000000000 --- a/sample/drb/drbch.rb +++ /dev/null @@ -1,48 +0,0 @@ -=begin - distributed Ruby --- dRuby Sample Client - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -require 'drb/drb' -require 'drb/http' - -class DRbEx2 - include DRbUndumped - - def initialize(n) - @n = n - end - - def to_i - @n.to_i - end -end - -if __FILE__ == $0 - there = ARGV.shift - unless there - $stderr.puts("usage: #{$0} ") - exit 1 - end - - DRb::DRbConn.proxy_map['x68k'] = 'http://x68k/~mas/http_cgi.rb' - - DRb.start_service() - ro = DRbObject.new(nil, there) - - puts ro - p ro.to_a - puts ro.hello - p ro.hello - puts ro.sample(DRbEx2.new(1), 2, 3) - puts ro.sample(1, ro.sample(DRbEx2.new(1), 2, 3), DRbEx2.new(3)) - - begin - ro.err - rescue DRb::DRbUnknownError - p $! - p $!.unknown - rescue RuntimeError - p $! - end -end diff --git a/sample/drb/drbm.rb b/sample/drb/drbm.rb deleted file mode 100644 index 3390608cd1889f..00000000000000 --- a/sample/drb/drbm.rb +++ /dev/null @@ -1,60 +0,0 @@ -=begin - multiple DRbServer - Copyright (c) 1999-2002 Masatoshi SEKI -=end - -=begin - How to play. - - Terminal 1 - | % ruby drbm.rb - | druby://yourhost:7640 druby://yourhost:7641 - - Terminal 2 - | % ruby drbmc.rb druby://yourhost:7640 druby://yourhost:7641 - | [#, "FOO"] - | [#, "FOO"] - -=end - -require 'drb/drb' - -class Hoge - include DRbUndumped - def initialize(s) - @str = s - end - - def to_s - @str - end -end - -class Foo - def initialize(s='FOO') - @hoge = Hoge.new(s) - end - - def hello - @hoge - end -end - -class Bar < Foo - def initialize(foo) - @hoge = foo.hello - end -end - - -if __FILE__ == $0 - foo = Foo.new - s1 = DRb::DRbServer.new('druby://:7640', foo) - s2 = DRb::DRbServer.new('druby://:7641', Bar.new(foo)) - - puts "#{s1.uri} #{s2.uri}" - - s1.thread.join - s2.thread.join -end - diff --git a/sample/drb/drbmc.rb b/sample/drb/drbmc.rb deleted file mode 100644 index fd191401e6d40b..00000000000000 --- a/sample/drb/drbmc.rb +++ /dev/null @@ -1,22 +0,0 @@ -=begin - multiple DRbServer client - Copyright (c) 1999-2002 Masatoshi SEKI -=end - -require 'drb/drb' - -if __FILE__ == $0 - s1 = ARGV.shift - s2 = ARGV.shift - unless s1 && s2 - $stderr.puts("usage: #{$0} ") - exit 1 - end - - DRb.start_service() - r1 = DRbObject.new(nil, s1) - r2 = DRbObject.new(nil, s2) - - p [r1.hello, r1.hello.to_s] - p [r2.hello, r2.hello.to_s] -end diff --git a/sample/drb/drbs-acl.rb b/sample/drb/drbs-acl.rb deleted file mode 100644 index 71c4f7bf428364..00000000000000 --- a/sample/drb/drbs-acl.rb +++ /dev/null @@ -1,51 +0,0 @@ -=begin - distributed Ruby --- dRuby Sample Server - Copyright (c) 1999-2000 Masatoshi SEKI -=end - -=begin - How to play. - - Terminal 1 - | % ruby drbs.rb - | druby://yourhost:7640 - - Terminal 2 - | % ruby drbc.rb druby://yourhost:7640 - | "hello" - | 6 - | 10 - -=end - -require 'drb/drb' -require 'acl' - -class DRbEx - def initialize - @hello = 'hello' - end - - def hello - info = Thread.current['DRb'] - p info['socket'].peeraddr if info - @hello - end - - def sample(a, b, c) - a.to_i + b.to_i + c.to_i - end -end - -if __FILE__ == $0 - acl = ACL.new(%w(deny all - allow 192.168.1.* - allow localhost)) - - DRb.install_acl(acl) - - DRb.start_service(nil, DRbEx.new) - puts DRb.uri - DRb.thread.join -end - diff --git a/sample/drb/drbs.rb b/sample/drb/drbs.rb deleted file mode 100644 index 5a913d9918bd4b..00000000000000 --- a/sample/drb/drbs.rb +++ /dev/null @@ -1,64 +0,0 @@ -=begin - distributed Ruby --- dRuby Sample Server - Copyright (c) 1999-2000,2002 Masatoshi SEKI -=end - -=begin - How to play. - - Terminal 1 - | % ruby drbs.rb - | druby://yourhost:7640 - - Terminal 2 - | % ruby drbc.rb druby://yourhost:7640 - | "hello" - | .... - -=end - -require 'drb/drb' - -class DRbEx - include DRbUndumped - - def initialize - @hello = 'hello' - end - - def hello - cntxt = Thread.current['DRb'] - if cntxt - p cntxt['server'].uri - p cntxt['client'].peeraddr - end - Foo::Unknown.new - end - - def err - raise FooError - end - - def sample(a, b, c) - a.to_i + b.to_i + c.to_i - end -end - -class Foo - class Unknown - end -end - -class FooError < RuntimeError -end - -if __FILE__ == $0 - DRb.start_service(ARGV.shift || 'druby://:7640', DRbEx.new) - puts DRb.uri - Thread.new do - sleep 10 - DRb.stop_service - end - DRb.thread.join -end - diff --git a/sample/drb/drbssl_c.rb b/sample/drb/drbssl_c.rb deleted file mode 100644 index 65112f6e7872a8..00000000000000 --- a/sample/drb/drbssl_c.rb +++ /dev/null @@ -1,19 +0,0 @@ -#!/usr/bin/env ruby - -require 'drb' -require 'drb/ssl' - -there = ARGV.shift || "drbssl://localhost:3456" - -config = Hash.new -config[:SSLVerifyMode] = OpenSSL::SSL::VERIFY_PEER -config[:SSLVerifyCallback] = lambda{|ok,x509_store| - p [ok, x509_store.error_string] - true -} - -DRb.start_service(nil,nil,config) -h = DRbObject.new(nil, there) -while line = gets - p h.hello(line.chomp) -end diff --git a/sample/drb/drbssl_s.rb b/sample/drb/drbssl_s.rb deleted file mode 100644 index 4d96f591d4edf4..00000000000000 --- a/sample/drb/drbssl_s.rb +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env ruby - -require 'drb' -require 'drb/ssl' - -here = ARGV.shift || "drbssl://localhost:3456" - -class HelloWorld - include DRbUndumped - - def hello(name) - "Hello, #{name}." - end -end - -config = Hash.new -config[:verbose] = true -begin - data = open("sample.key"){|io| io.read } - config[:SSLPrivateKey] = OpenSSL::PKey::RSA.new(data) - data = open("sample.crt"){|io| io.read } - config[:SSLCertificate] = OpenSSL::X509::Certificate.new(data) -rescue - $stderr.puts "Switching to use self-signed certificate" - config[:SSLCertName] = - [ ["C","JP"], ["O","Foo.DRuby.Org"], ["CN", "Sample"] ] -end - -DRb.start_service(here, HelloWorld.new, config) -puts DRb.uri -DRb.thread.join diff --git a/sample/drb/extserv_test.rb b/sample/drb/extserv_test.rb deleted file mode 100644 index 2c4f485dc67b45..00000000000000 --- a/sample/drb/extserv_test.rb +++ /dev/null @@ -1,80 +0,0 @@ -=begin - dRuby sample - Copyright (c) 2000 Masatoshi SEKI - -= How to play - -* Terminal 1 - - % ruby -I. extserv_test.rb server - druby://yourhost:12345 - -* Terminal 2 - - % ruby -I. extserv_test.rb druby://yourhost:12345 - ... - -=end - -require 'drb/drb' - -def ARGV.shift - it = super() - raise "usage:\nserver: #{$0} server []\nclient: #{$0} [quit] " unless it - it -end - -class Foo - include DRbUndumped - - def initialize(str) - @str = str - end - - def hello(it) - "#{it}: #{self}" - end - - def to_s - @str - end -end - -cmd = ARGV.shift -case cmd -when 'itest1', 'itest2' - require 'drb/extserv' - - front = Foo.new(cmd) - server = DRb::DRbServer.new(nil, front) - es = DRb::ExtServ.new(ARGV.shift, ARGV.shift, server) - server.thread.join - -when 'server' - require 'drb/extservm' - - DRb::ExtServManager.command['itest1'] = "ruby -I. #{$0} itest1" - DRb::ExtServManager.command['itest2'] = "ruby -I. #{$0} itest2" - - s = DRb::ExtServManager.new - DRb.start_service(ARGV.shift, s) - puts DRb.uri - DRb.thread.join - - -else - uri = (cmd == 'quit') ? ARGV.shift : cmd - - DRb.start_service - s = DRbObject.new(nil, uri) - t1 = s.service('itest1').front - puts t1 - t2 = s.service('itest2').front - puts t2 - puts t1.hello(t2) - if (cmd == 'quit') - s.service('itest1').stop_service - s.service('itest2').stop_service - end -end - diff --git a/sample/drb/gw_ct.rb b/sample/drb/gw_ct.rb deleted file mode 100644 index 062278401839c3..00000000000000 --- a/sample/drb/gw_ct.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'drb/drb' - -class Foo - include DRbUndumped - - def foo(n) - n + n - end - - def bar(n) - yield(n) + yield(n) - end -end - -DRb.start_service(nil) -puts DRb.uri - -ro = DRbObject.new(nil, ARGV.shift) -ro[:tcp] = Foo.new -gets - -it = ro[:unix] -p [it, it.foo(1)] -gets - -p it.bar('2') {|n| n * 3} -gets - - diff --git a/sample/drb/gw_cu.rb b/sample/drb/gw_cu.rb deleted file mode 100644 index 8079cbdc4fd6fb..00000000000000 --- a/sample/drb/gw_cu.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'drb/drb' -require 'drb/unix' - -class Foo - include DRbUndumped - - def foo(n) - n + n - end - - def bar(n) - yield(n) + yield(n) - end -end - -DRb.start_service('drbunix:', nil) -puts DRb.uri - -ro = DRbObject.new(nil, ARGV.shift) -ro[:unix] = Foo.new -gets - -it = ro[:tcp] -p [it, it.foo(1)] -gets - -p it.bar('2') {|n| n * 3} -gets diff --git a/sample/drb/gw_s.rb b/sample/drb/gw_s.rb deleted file mode 100644 index c2bea0baadf06a..00000000000000 --- a/sample/drb/gw_s.rb +++ /dev/null @@ -1,10 +0,0 @@ -require 'drb/drb' -require 'drb/unix' -require 'drb/gw' - -DRb.install_id_conv(DRb::GWIdConv.new) -gw = DRb::GW.new -s1 = DRb::DRbServer.new(ARGV.shift, gw) -s2 = DRb::DRbServer.new(ARGV.shift, gw) -s1.thread.join -s2.thread.join diff --git a/sample/drb/holderc.rb b/sample/drb/holderc.rb deleted file mode 100644 index e627916d76a844..00000000000000 --- a/sample/drb/holderc.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'drb/drb' - -begin - there = ARGV.shift || raise -rescue - $stderr.puts("usage: #{$0} ") - exit 1 -end - -DRb.start_service() -ro = DRbObject.new(nil, there) - -ary = [] -10.times do - ary.push(ro.gen) -end - -sleep 5 if $DEBUG - -ary.each do |e| - p e.sample([1]) -end diff --git a/sample/drb/holders.rb b/sample/drb/holders.rb deleted file mode 100644 index 293426faa5e37b..00000000000000 --- a/sample/drb/holders.rb +++ /dev/null @@ -1,63 +0,0 @@ -=begin -= How to play. - -== with timeridconv: - % ruby -d holders.rb - druby://yourhost:1234 - - % ruby holderc.rb druby://yourhost:1234 - - -== without timeridconv: - % ruby holders.rb - druby://yourhost:1234 - - % ruby holderc.rb druby://yourhost:1234 -=end - - -require 'drb/drb' - -class DRbEx3 - include DRbUndumped - - def initialize(n) - @v = n - end - - def sample(list) - sum = 0 - list.each do |e| - sum += e.to_i - end - @v * sum - end -end - -class DRbEx4 - include DRbUndumped - - def initialize - @curr = 1 - end - - def gen - begin - @curr += 1 - DRbEx3.new(@curr) - ensure - GC.start - end - end -end - -if __FILE__ == $0 - if $DEBUG - require 'drb/timeridconv' - DRb.install_id_conv(DRb::TimerIdConv.new(2)) - end - - DRb.start_service(nil, DRbEx4.new) - puts DRb.uri - DRb.thread.join -end diff --git a/sample/drb/http0.rb b/sample/drb/http0.rb deleted file mode 100644 index e40d81031169a4..00000000000000 --- a/sample/drb/http0.rb +++ /dev/null @@ -1,77 +0,0 @@ -require 'drb/drb' -require 'net/http' -require 'uri' - -module DRb - module HTTP0 - class StrStream - def initialize(str='') - @buf = str - end - attr_reader :buf - - def read(n) - begin - return @buf[0,n] - ensure - @buf[0,n] = '' - end - end - - def write(s) - @buf.concat s - end - end - - def self.uri_option(uri, config) - return uri, nil - end - - def self.open(uri, config) - unless /^http:/ =~ uri - raise(DRbBadScheme, uri) unless uri =~ /^http:/ - raise(DRbBadURI, 'can\'t parse uri:' + uri) - end - ClientSide.new(uri, config) - end - - class ClientSide - def initialize(uri, config) - @uri = uri - @res = nil - @config = config - @msg = DRbMessage.new(config) - @proxy = ENV['HTTP_PROXY'] - end - - def close; end - def alive?; false; end - - def send_request(ref, msg_id, *arg, &b) - stream = StrStream.new - @msg.send_request(stream, ref, msg_id, *arg, &b) - @reply_stream = StrStream.new - post(@uri, stream.buf) - end - - def recv_reply - @msg.recv_reply(@reply_stream) - end - - def post(url, data) - it = URI.parse(url) - path = [(it.path=='' ? '/' : it.path), it.query].compact.join('?') - http = Net::HTTP.new(it.host, it.port) - sio = StrStream.new - http.post(path, data, {'Content-Type'=>'application/octetstream;'}) do |str| - sio.write(str) - if @config[:load_limit] < sio.buf.size - raise TypeError, 'too large packet' - end - end - @reply_stream = sio - end - end - end - DRbProtocol.add_protocol(HTTP0) -end diff --git a/sample/drb/http0serv.rb b/sample/drb/http0serv.rb deleted file mode 100644 index 2e853312e12a6c..00000000000000 --- a/sample/drb/http0serv.rb +++ /dev/null @@ -1,120 +0,0 @@ -require 'webrick' -require 'drb/drb' -require_relative 'http0' - -module DRb - module HTTP0 - - def self.open_server(uri, config) - unless /^http:/ =~ uri - raise(DRbBadScheme, uri) unless uri =~ /^http:/ - raise(DRbBadURI, 'can\'t parse uri:' + uri) - end - Server.new(uri, config) - end - - class Callback < WEBrick::HTTPServlet::AbstractServlet - def initialize(config, drb) - @config = config - @drb = drb - @queue = Thread::Queue.new - end - - def do_POST(req, res) - @req = req - @res = res - @drb.push(self) - @res.body = @queue.pop - @res['content-type'] = 'application/octet-stream;' - end - - def req_body - @req.body - end - - def reply(body) - @queue.push(body) - end - - def close - @queue.push('') - end - end - - class Server - def initialize(uri, config) - @uri = uri - @config = config - @queue = Thread::Queue.new - setup_webrick(uri) - end - attr_reader :uri - - def close - @server.shutdown if @server - @server = nil - end - - def push(callback) - @queue.push(callback) - end - - def accept - client = @queue.pop - ServerSide.new(uri, client, @config) - end - - def setup_webrick(uri) - logger = WEBrick::Log::new($stderr, WEBrick::Log::FATAL) - u = URI.parse(uri) - s = WEBrick::HTTPServer.new(:Port => u.port, - :AddressFamily => Socket::AF_INET, - :BindAddress => u.host, - :Logger => logger, - :ServerType => Thread) - s.mount(u.path, Callback, self) - @server = s - s.start - end - end - - class ServerSide - def initialize(uri, callback, config) - @uri = uri - @callback = callback - @config = config - @msg = DRbMessage.new(@config) - @req_stream = StrStream.new(@callback.req_body) - end - attr_reader :uri - - def close - @callback.close if @callback - @callback = nil - end - - def alive?; false; end - - def recv_request - begin - @msg.recv_request(@req_stream) - rescue - close - raise $! - end - end - - def send_reply(succ, result) - begin - return unless @callback - stream = StrStream.new - @msg.send_reply(stream, succ, result) - @callback.reply(stream.buf) - rescue - close - raise $! - end - end - end - end -end diff --git a/sample/drb/name.rb b/sample/drb/name.rb deleted file mode 100644 index 6d88186dabf1d3..00000000000000 --- a/sample/drb/name.rb +++ /dev/null @@ -1,117 +0,0 @@ -=begin - distributed Ruby --- NamedObject Sample - Copyright (c) 2000-2001 Masatoshi SEKI -=end - -=begin -How to play. - -* start server - Terminal 1 - | % ruby name.rb druby://yourhost:7640 - | druby://yourhost:7640 - | [return] to exit - -* start client - Terminal 2 - | % ruby namec.rb druby://yourhost:7640 - | # - | # - | 1 - | 2 - | [return] to continue - -* restart server - Terminal 1 - type [return] - | % ruby name.rb druby://yourhost:7640 - | druby://yourhost:7640 - | [return] to exit - -* continue client - Terminal 2 - type [return] - | 1 - | 2 -=end - -require 'drb/drb' - -module DRbNamedObject - DRbNAMEDICT = {} - DRBNAMEMUTEX = Thread::Mutex.new - attr_reader(:drb_name) - - def drb_name=(name) - @drb_name = name - DRBNAMEMUTEX.synchronize do - raise(IndexError, name) if DRbNAMEDICT[name] - DRbNAMEDICT[name] = self - end - end -end - -class DRbNamedIdConv < DRb::DRbIdConv - def initialize - @dict = DRbNamedObject::DRbNAMEDICT - end - - def to_obj(ref) - @dict.fetch(ref) do super end - end - - def to_id(obj) - if obj.kind_of? DRbNamedObject - return obj.drb_name - else - return super - end - end -end - -class Seq - include DRbUndumped - include DRbNamedObject - - def initialize(v, name) - @counter = v - @mutex = Thread::Mutex.new - self.drb_name = name - end - - def next_value - @mutex.synchronize do - @counter += 1 - return @counter - end - end -end - -class Front - def initialize - seq = Seq.new(0, 'seq') - mutex = Thread::Mutex.new - mutex.extend(DRbUndumped) - mutex.extend(DRbNamedObject) - mutex.drb_name = 'mutex' - @name = {} - @name['seq'] = seq - @name['mutex'] = mutex - end - - def [](k) - @name[k] - end -end - -if __FILE__ == $0 - uri = ARGV.shift - - name_conv = DRbNamedIdConv.new - - DRb.install_id_conv(name_conv) - DRb.start_service(uri, Front.new) - puts DRb.uri - DRb.thread.join -end - diff --git a/sample/drb/namec.rb b/sample/drb/namec.rb deleted file mode 100644 index 98b9d0e532afbf..00000000000000 --- a/sample/drb/namec.rb +++ /dev/null @@ -1,36 +0,0 @@ -=begin - distributed Ruby --- NamedObject Sample Client - Copyright (c) 2000-2001 Masatoshi SEKI -=end - -require 'drb/drb' - -begin - there = ARGV.shift || raise -rescue - puts "usage: #{$0} " - exit 1 -end - -DRb.start_service() -ro = DRbObject.new(nil, there) - -seq = ro["seq"] -mutex = ro["mutex"] - -p seq -p mutex - -mutex.synchronize do - p seq.next_value - p seq.next_value -end - -puts '[return] to continue' -gets - -mutex.synchronize do - p seq.next_value - p seq.next_value -end - diff --git a/sample/drb/old_tuplespace.rb b/sample/drb/old_tuplespace.rb deleted file mode 100644 index 2d5310086e9362..00000000000000 --- a/sample/drb/old_tuplespace.rb +++ /dev/null @@ -1,212 +0,0 @@ -#!/usr/local/bin/ruby -# TupleSpace -# Copyright (c) 1999-2000 Masatoshi SEKI -# You can redistribute it and/or modify it under the same terms as Ruby. - -class TupleSpace - class Template - def initialize(list) - @list = list - @check_idx = [] - @list.each_with_index do |x, i| - @check_idx.push i if x - end - @size = @list.size - end - - attr :size - alias length size - - def match(tuple) - return nil if tuple.size != self.size - @check_idx.each do |i| - unless @list[i] === tuple[i] - return false - end - end - return true - end - end - - def initialize - @que = {} - @waiting = {} - @que.taint # enable tainted communication - @waiting.taint - self.taint - end - - def wakeup_waiting(tuple) - sz = tuple.length - return nil unless @waiting[sz] - - x = nil - i = -1 - found = false - @waiting[sz] = @waiting[sz].find_all { |x| - if x[0].match(tuple) - begin - x[1].wakeup - rescue ThreadError - end - false - else - true - end - } - end - - def put_waiting(template, thread) - sz = template.length - @waiting[sz] = [] unless @waiting[sz] - @waiting[sz].push([Template.new(template), thread]) - end - private :wakeup_waiting - private :put_waiting - - def get_que(template) - sz = template.length - return nil unless @que[sz] - - template = Template.new(template) - - x = nil - i = -1 - found = false - @que[sz].each_with_index do |x, i| - if template.match(x) - found = true - break - end - end - return nil unless found - - @que[sz].delete_at(i) - - return x - end - - def put_que(tuple) - sz = tuple.length - @que[sz] = [] unless @que[sz] - @que[sz].push tuple - end - private :get_que - private :put_que - - def out(*tuples) - tuples.each do |tuple| - Thread.critical = true - put_que(tuple) - wakeup_waiting(tuple) - Thread.critical = false - end - end - alias put out - alias write out - - def in(template, non_block=false) - begin - loop do - Thread.critical = true - tuple = get_que(template) - unless tuple - if non_block - raise ThreadError, "queue empty" - end - put_waiting(template, Thread.current) - Thread.stop - else - return tuple - end - end - ensure - Thread.critical = false - end - end - alias get in - alias take in - - def rd(template, non_block=false) - tuple = self.in(template, non_block) - out(tuple) - tuple - end - alias read rd - - def mv(dest, template, non_block=false) - tuple = self.in(template, non_block) - begin - dest.out(tuple) - rescue - self.out(tuple) - end - end - alias move mv -end - -if __FILE__ == $0 - ts = TupleSpace.new - clients = [] - servers = [] - - def server(ts, id) - Thread.start { - loop do - req = ts.in(['req', nil, nil]) - ac = req[1] - num = req[2] - sleep id - ts.out([ac, id, num, num * num]) - end - } - end - - def client(ts, n) - Thread.start { - ac = Object.new - tuples = (1..10).collect { |i| - ['req', ac, i * 10 + n] - } - ts.out(*tuples) - ts.out(tuples[0]) - puts "out: #{n}" - 11.times do |i| - ans = ts.in([ac, nil, nil, nil]) - puts "client(#{n}) server(#{ans[1]}) #{ans[2]} #{ans[3]}" - end - } - end - - def watcher(ts) - Thread.start { - loop do - begin - sleep 1 - p ts.rd(['req', nil, nil], true) - rescue ThreadError - puts "'req' not found." - end - end - } - end - - (0..3).each do |n| - servers.push(server(ts, n)) - end - - (1..6).each do |n| - clients.push(client(ts, n)) - end - - (1..3).each do - watcher(ts) - end - - clients.each do |t| - t.join - end -end - - - diff --git a/sample/drb/rinda_ts.rb b/sample/drb/rinda_ts.rb deleted file mode 100644 index 6f2fae5c0f20fe..00000000000000 --- a/sample/drb/rinda_ts.rb +++ /dev/null @@ -1,7 +0,0 @@ -require 'drb/drb' -require 'rinda/tuplespace' - -uri = ARGV.shift -DRb.start_service(uri, Rinda::TupleSpace.new) -puts DRb.uri -DRb.thread.join diff --git a/sample/drb/rindac.rb b/sample/drb/rindac.rb deleted file mode 100644 index 72be09deafda56..00000000000000 --- a/sample/drb/rindac.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'drb/drb' -require 'rinda/rinda' - -uri = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service -ts = Rinda::TupleSpaceProxy.new(DRbObject.new(nil, uri)) - -(1..10).each do |n| - ts.write(['sum', DRb.uri, n]) -end - -(1..10).each do |n| - ans = ts.take(['ans', DRb.uri, n, nil]) - p [ans[2], ans[3]] -end - diff --git a/sample/drb/rindas.rb b/sample/drb/rindas.rb deleted file mode 100644 index 9fd9ada2d10106..00000000000000 --- a/sample/drb/rindas.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'drb/drb' -require 'rinda/rinda' - -def do_it(v) - puts "do_it(#{v})" - v + v -end - -uri = ARGV.shift || raise("usage: #{$0} ") - -DRb.start_service -ts = Rinda::TupleSpaceProxy.new(DRbObject.new(nil, uri)) - -while true - r = ts.take(['sum', nil, nil]) - v = do_it(r[2]) - ts.write(['ans', r[1], r[2], v]) -end diff --git a/sample/drb/ring_echo.rb b/sample/drb/ring_echo.rb deleted file mode 100644 index c54628b54c2064..00000000000000 --- a/sample/drb/ring_echo.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'drb/drb' -require 'drb/eq' -require 'rinda/ring' - -class RingEcho - include DRbUndumped - def initialize(name) - @name = name - end - - def echo(str) - "#{@name}: #{str}" - end -end - -DRb.start_service - -renewer = Rinda::SimpleRenewer.new - -finder = Rinda::RingFinger.new -ts = finder.lookup_ring_any -ts.read_all([:name, :RingEcho, nil, nil]).each do |tuple| - p tuple[2] - puts tuple[2].echo('Hello, World') rescue nil -end -ts.write([:name, :RingEcho, RingEcho.new(DRb.uri), ''], renewer) - -DRb.thread.join - diff --git a/sample/drb/ring_inspect.rb b/sample/drb/ring_inspect.rb deleted file mode 100644 index c096cd7034932b..00000000000000 --- a/sample/drb/ring_inspect.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'rinda/ring' -require 'drb/drb' - -class Inspector - def initialize - end - - def primary - Rinda::RingFinger.primary - end - - def list_place - Rinda::RingFinger.to_a - end - - def list(idx = -1) - if idx < 0 - ts = primary - else - ts = list_place[idx] - raise "RingNotFound" unless ts - end - ts.read_all([:name, nil, nil, nil]) - end -end - -def main - DRb.start_service - r = Inspector.new -end diff --git a/sample/drb/ring_place.rb b/sample/drb/ring_place.rb deleted file mode 100644 index 11c6c2fe80287f..00000000000000 --- a/sample/drb/ring_place.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'drb/drb' -require 'rinda/ring' -require 'rinda/tuplespace' - -unless $DEBUG - # Run as a daemon... - exit!( 0 ) if fork - Process.setsid - exit!( 0 ) if fork -end - -DRb.start_service(ARGV.shift) - -ts = Rinda::TupleSpace.new -place = Rinda::RingServer.new(ts) - -if $DEBUG - puts DRb.uri - DRb.thread.join -else - STDIN.reopen(IO::NULL) - STDOUT.reopen(IO::NULL, 'w') - STDERR.reopen(IO::NULL, 'w') - DRb.thread.join -end diff --git a/sample/drb/simpletuple.rb b/sample/drb/simpletuple.rb deleted file mode 100644 index 4bb4b1cff91475..00000000000000 --- a/sample/drb/simpletuple.rb +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/local/bin/ruby -# SimpleTupleSpace -# Copyright (c) 1999-2000 Masatoshi SEKI -# You can redistribute it and/or modify it under the same terms as Ruby. - -class SimpleTupleSpace - def initialize - @hash = {} - @waiting = {} - @hash.taint - @waiting.taint - self.taint - end - - def out(key, obj) - Thread.critical = true - @hash[key] ||= [] - @waiting[key] ||= [] - @hash[key].push obj - begin - t = @waiting[key].shift - @waiting.delete(key) if @waiting[key].length == 0 - t.wakeup if t - rescue ThreadError - retry - ensure - Thread.critical = false - end - end - - def in(key) - Thread.critical = true - @hash[key] ||= [] - @waiting[key] ||= [] - begin - loop do - if @hash[key].length == 0 - @waiting[key].push Thread.current - Thread.stop - else - return @hash[key].shift - end - end - ensure - @hash.delete(key) if @hash[key].length == 0 - Thread.critical = false - end - end -end - -if __FILE__ == $0 - ts = SimpleTupleSpace.new - clients = [] - servers = [] - - def server(ts) - Thread.start { - loop do - req = ts.in('req') - ac = req[0] - num = req[1] - ts.out(ac, num * num) - end - } - end - - def client(ts, n) - Thread.start { - ac = Object.new - ts.out('req', [ac, n]) - ans = ts.in(ac) - puts "#{n}: #{ans}" - } - end - - 3.times do - servers.push(server(ts)) - end - - (1..6).each do |n| - clients.push(client(ts, n)) - end - - clients.each do |t| - t.join - end -end - - diff --git a/sample/drb/speedc.rb b/sample/drb/speedc.rb deleted file mode 100644 index 64b8a650216760..00000000000000 --- a/sample/drb/speedc.rb +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/local/bin/ruby - -uri = ARGV.shift || raise("usage: #{$0} URI") -N = (ARGV.shift || 100).to_i - -case uri -when /^tcpromp:/, /^unixromp:/ - require 'romp' - - client = ROMP::Client.new(uri, false) - foo = client.resolve("foo") -when /^druby:/ - require 'drb/drb' - - DRb.start_service - foo = DRbObject.new(nil, uri) -end - -N.times do |n| - foo.foo(n) -end diff --git a/sample/drb/speeds.rb b/sample/drb/speeds.rb deleted file mode 100644 index 79840594231e94..00000000000000 --- a/sample/drb/speeds.rb +++ /dev/null @@ -1,31 +0,0 @@ -class Foo - attr_reader :i - def initialize - @i = 0 - end - - def foo(i) - @i = i - i + i - end -end - -# server = ROMP::Server.new('tcpromp://localhost:4242', nil, true) - -uri = ARGV.shift || raise("usage: #{$0} URI") -foo = Foo.new - -case uri -when /^tcpromp:/, /^unixromp:/ - require 'romp' - - server = ROMP::Server.new(uri, nil, true) - server.bind(foo, "foo") - -when /^druby:/ - require 'drb/drb' - - DRb.start_service(uri, Foo.new) -end - -DRb.thread.join From 0ce4b43ebf1b4329023c0db84be5b7c1229e7bea Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 4 Jul 2025 13:14:45 +0900 Subject: [PATCH 115/130] Update default gems and bundled gems list at expand_tabs.rb --- misc/expand_tabs.rb | 33 ++------------------------------- 1 file changed, 2 insertions(+), 31 deletions(-) diff --git a/misc/expand_tabs.rb b/misc/expand_tabs.rb index 9df96ee84ee72c..d26568eefcd09c 100755 --- a/misc/expand_tabs.rb +++ b/misc/expand_tabs.rb @@ -59,52 +59,31 @@ def with_clean_env end DEFAULT_GEM_LIBS = %w[ - abbrev - base64 - benchmark bundler - cmath - csv - debug delegate did_you_mean english erb + error_highlight fileutils find forwardable - getoptlong ipaddr - irb - logger - mutex_m net-http net-protocol - observer open3 open-uri optparse ostruct pp prettyprint - prime - pstore - rdoc - readline - reline + prism resolv - resolv-replace - rexml - rinda - rss rubygems - scanf securerandom - set shellwords singleton tempfile - thwait time timeout tmpdir @@ -116,27 +95,19 @@ def with_clean_env ] DEFAULT_GEM_EXTS = %w[ - bigdecimal - cgi date digest etc fcntl - fiddle io-console io-nonblock io-wait json - nkf openssl pathname psych - racc - readline-ext stringio strscan - syslog - win32ole zlib ] From b88cbe49c3bbed50028e5e610ae492c736e367b1 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 4 Jul 2025 13:26:25 +0900 Subject: [PATCH 116/130] Added io-nonblock and io-wait entries --- doc/standard_library.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/standard_library.md b/doc/standard_library.md index f2700ef5c2ebd8..0c48ac0cdd7e0a 100644 --- a/doc/standard_library.md +++ b/doc/standard_library.md @@ -71,6 +71,8 @@ of each. - Etc ([GitHub][etc]): Provides access to information typically stored in the UNIX /etc directory - Fcntl ([GitHub][fcntl]): Loads constants defined in the OS fcntl.h C header file - IO.console ([GitHub][io-console]): Extensions for the IO class, including `IO.console`, `IO.winsize`, etc. +- IO#nonblock ([GitHub][io-nonblock]): Enable non-blocking mode with IO class. +- IO#wait ([GitHub][io-wait]): Provides the feature for waiting until IO is readable or writable without blocking. - JSON ([GitHub][json]): Implements JavaScript Object Notation for Ruby - OpenSSL ([GitHub][openssl]): Provides SSL, TLS, and general-purpose cryptography for Ruby - Pathname ([GitHub][pathname]): Representation of the name of a file or directory on the filesystem @@ -153,6 +155,8 @@ of each. [forwardable]: https://github.com/ruby/forwardable [getoptlong]: https://github.com/ruby/getoptlong [io-console]: https://github.com/ruby/io-console +[io-nonblock]: https://github.com/ruby/io-nonblock +[io-wait]: https://github.com/ruby/io-wait [ipaddr]: https://github.com/ruby/ipaddr [irb]: https://github.com/ruby/irb [json]: https://github.com/ruby/json From 50704fe8e6e5b75b5d0d4544247cdabf7c8411ea Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 3 Jul 2025 20:52:33 -0700 Subject: [PATCH 117/130] Revert "Make get/set default internal/external encoding lock-free" This reverts commit dda5a04f2b4835582dba09ba33797258a61efafe. --- encoding.c | 184 ++++++++++++++++--------------------- test/ruby/test_encoding.rb | 4 +- 2 files changed, 79 insertions(+), 109 deletions(-) diff --git a/encoding.c b/encoding.c index 5305344a2e05df..7f1d0011f8064e 100644 --- a/encoding.c +++ b/encoding.c @@ -26,7 +26,6 @@ #include "regenc.h" #include "ruby/encoding.h" #include "ruby/util.h" -#include "ruby/atomic.h" #include "ruby_assert.h" #include "vm_sync.h" @@ -54,12 +53,6 @@ int rb_encdb_alias(const char *alias, const char *orig); #pragma GCC visibility pop #endif -#if ENC_DEBUG -#define encdebug(...) fprintf(stderr, __VA_ARGS__) -#else -#define encdebug(...) (void)0 -#endif - static ID id_encoding; VALUE rb_cEncoding; @@ -151,11 +144,11 @@ enc_new(rb_encoding *encoding) static void enc_list_update(int index, rb_raw_encoding *encoding) { + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); RUBY_ASSERT(index < ENCODING_LIST_CAPA); VALUE list = rb_encoding_list; if (list && NIL_P(rb_ary_entry(list, index))) { - RUBY_ASSERT(!rb_multi_ractor_p()); /* initialize encoding data */ rb_ary_store(list, index, enc_new(encoding)); } @@ -167,9 +160,11 @@ enc_list_lookup(int idx) VALUE list, enc = Qnil; if (idx < ENCODING_LIST_CAPA) { - list = rb_encoding_list; - RUBY_ASSERT(list); - enc = rb_ary_entry(list, idx); + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + list = rb_encoding_list; + RUBY_ASSERT(list); + enc = rb_ary_entry(list, idx); + } } if (NIL_P(enc)) { @@ -211,13 +206,10 @@ static int check_encoding(rb_encoding *enc) { int index = rb_enc_to_index(enc); - if (rb_enc_from_index(index) != enc) { - encdebug("check_encoding(%s): rb_enc_from_index(index) != enc, return -1\n", rb_enc_name(enc)); + if (rb_enc_from_index(index) != enc) return -1; - } if (rb_enc_autoload_p(enc)) { index = rb_enc_autoload(enc); - encdebug("check_encoding(%s): rb_enc_autoload_p(enc), index after autoload:%d\n", rb_enc_name(enc), index); } return index; } @@ -348,7 +340,7 @@ rb_find_encoding(VALUE enc) } static int -enc_table_count_check(int newsize) +enc_table_expand(struct enc_table *enc_table, int newsize) { if (newsize > ENCODING_LIST_CAPA) { rb_raise(rb_eEncodingError, "too many encoding (> %d)", ENCODING_LIST_CAPA); @@ -356,49 +348,46 @@ enc_table_count_check(int newsize) return newsize; } -// If called with a `base_encoding` of NULL, it is an autoloaded encoding static int enc_register_at(struct enc_table *enc_table, int index, const char *name, rb_encoding *base_encoding) { + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); struct rb_encoding_entry *ent = &enc_table->list[index]; rb_raw_encoding *encoding; + if (!valid_encoding_name_p(name)) return -1; - GLOBAL_ENC_TABLE_LOCKING(table) { - if (!ent->name) { - ent->name = name = strdup(name); - } - else if (STRCASECMP(name, ent->name)) { - index = -1; - } - if (index != -1) { - encoding = (rb_raw_encoding *)ent->enc; - if (!encoding) { - encoding = xmalloc(sizeof(rb_encoding)); - } - if (base_encoding) { - *encoding = *base_encoding; - } - else { - memset(encoding, 0, sizeof(*ent->enc)); - } - encoding->name = name; - ent->enc = encoding; - st_insert(table->names, (st_data_t)name, (st_data_t)index); - enc_list_update(index, encoding); - encoding->ruby_encoding_index = index; - } + if (!ent->name) { + ent->name = name = strdup(name); + } + else if (STRCASECMP(name, ent->name)) { + return -1; + } + encoding = (rb_raw_encoding *)ent->enc; + if (!encoding) { + encoding = xmalloc(sizeof(rb_encoding)); + } + if (base_encoding) { + *encoding = *base_encoding; } + else { + memset(encoding, 0, sizeof(*ent->enc)); + } + encoding->name = name; + encoding->ruby_encoding_index = index; + ent->enc = encoding; + st_insert(enc_table->names, (st_data_t)name, (st_data_t)index); + enc_list_update(index, encoding); return index; } static int enc_register(struct enc_table *enc_table, const char *name, rb_encoding *encoding) { - int index = enc_table->count; ASSERT_GLOBAL_ENC_TABLE_LOCKED(); + int index = enc_table->count; - enc_table->count = enc_table_count_check(index + 1); + enc_table->count = enc_table_expand(enc_table, index + 1); return enc_register_at(enc_table, index, name, encoding); } @@ -408,30 +397,43 @@ static int enc_registered(struct enc_table *enc_table, const char *name); static rb_encoding * enc_from_index(struct enc_table *enc_table, int index) { + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); return enc_table->list[index].enc; } -// NOTE: there is no lock needed here, even with multi-ractor rb_encoding * rb_enc_from_index(int index) { rb_encoding *enc; - if (UNLIKELY(index < 0 || global_enc_table.count <= (index &= ENC_INDEX_MASK))) { - enc = NULL; + switch (index) { + case ENCINDEX_US_ASCII: + return global_enc_us_ascii; + case ENCINDEX_UTF_8: + return global_enc_utf_8; + case ENCINDEX_ASCII_8BIT: + return global_enc_ascii; + default: + break; } - else { - enc = enc_from_index(&global_enc_table, index); + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + if (UNLIKELY(index < 0 || enc_table->count <= (index &= ENC_INDEX_MASK))) { + enc = NULL; + } + else { + enc = enc_from_index(enc_table, index); + } } return enc; } -// This can be called from non-main ractors during autoloading of encodings int rb_enc_register(const char *name, rb_encoding *encoding) { int index; + unsigned int lev; - GLOBAL_ENC_TABLE_LOCKING(enc_table) { + GLOBAL_ENC_TABLE_LOCK_ENTER_LEV(enc_table, &lev); + { index = enc_registered(enc_table, name); if (index >= 0) { @@ -443,6 +445,7 @@ rb_enc_register(const char *name, rb_encoding *encoding) enc_register_at(enc_table, index, name, encoding); } else { + GLOBAL_ENC_TABLE_LOCK_LEAVE_LEV(&lev); rb_raise(rb_eArgError, "encoding %s is already registered", name); } } @@ -451,14 +454,14 @@ rb_enc_register(const char *name, rb_encoding *encoding) set_encoding_const(name, rb_enc_from_index(index)); } } + GLOBAL_ENC_TABLE_LOCK_LEAVE_LEV(&lev); return index; } -static int +int enc_registered(struct enc_table *enc_table, const char *name) { st_data_t idx = 0; - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); if (!name) return -1; @@ -469,7 +472,6 @@ enc_registered(struct enc_table *enc_table, const char *name) return -1; } -// Set up an encoding with a zeroed out entry->enc, which means it will be autoloaded void rb_encdb_declare(const char *name) { @@ -496,6 +498,7 @@ enc_check_addable(struct enc_table *enc_table, const char *name) static rb_encoding* set_base_encoding(struct enc_table *enc_table, int index, rb_encoding *base) { + ASSERT_GLOBAL_ENC_TABLE_LOCKED(); rb_encoding *enc = enc_table->list[index].enc; ASSUME(enc); @@ -562,7 +565,6 @@ enc_replicate_with_index(struct enc_table *enc_table, const char *name, rb_encod return idx; } -// Setup a new encoding `name` that it a copy of `orig`, with a base of `orig`. Set `name` as dummy if `orig` is dummy. int rb_encdb_replicate(const char *name, const char *orig) { @@ -721,7 +723,7 @@ rb_encdb_alias(const char *alias, const char *orig) static void rb_enc_init(struct enc_table *enc_table) { - enc_table_count_check(ENCODING_COUNT + 1); + enc_table_expand(enc_table, ENCODING_COUNT + 1); if (!enc_table->names) { enc_table->names = st_init_strcasetable_with_size(ENCODING_LIST_CAPA); } @@ -775,7 +777,6 @@ load_encoding(const char *name) ++s; } enclib = rb_fstring(enclib); - encdebug("load_encoding(%s)\n", RSTRING_PTR(enclib)); ruby_debug = Qfalse; errinfo = rb_errinfo(); loaded = rb_require_internal_silent(enclib); @@ -783,19 +784,15 @@ load_encoding(const char *name) rb_set_errinfo(errinfo); if (loaded < 0 || 1 < loaded) { - encdebug("BAD load_encoding(%s): %d\n", name, loaded); idx = -1; } else if ((idx = enc_registered(&global_enc_table, name)) < 0) { - encdebug("load_encoding(%s) after, enc_registered(name) < 0: %d\n", name, idx); idx = -1; } else if (rb_enc_autoload_p(global_enc_table.list[idx].enc)) { - encdebug("load_encoding(%s) after, enc_autoload_p still true\n", name); idx = -1; } - encdebug("load_encoding(%s) returned index: %d\n", name, idx); return idx; } @@ -814,7 +811,6 @@ enc_autoload_body(struct enc_table *enc_table, rb_encoding *enc) if (rb_enc_autoload(base) < 0) return -1; } i = enc->ruby_encoding_index; - encdebug("enc_autoload_body of enc %s from base %s\n", rb_enc_name(enc), rb_enc_name(base)); enc_register_at(enc_table, i & ENC_INDEX_MASK, rb_enc_name(enc), base); ((rb_raw_encoding *)enc)->ruby_encoding_index = i; i &= ENC_INDEX_MASK; @@ -830,25 +826,15 @@ rb_enc_autoload(rb_encoding *enc) { int i; GLOBAL_ENC_TABLE_LOCKING(enc_table) { - if (rb_enc_autoload_p(enc)) { - i = enc_autoload_body(enc_table, enc); - if (i == -2) { - i = load_encoding(rb_enc_name(enc)); - encdebug("enc_autoload_body returned -2 (no base), loaded encoding %s, got ret i=%d\n", rb_enc_name(enc), i); - } - else if (i == -1) { - encdebug("enc_autoload_body returned -1 for encoding %s, autoload failed\n", rb_enc_name(enc)); - } - } - else { - encdebug("already autoloaded: %s\n", rb_enc_name(enc)); - i = check_encoding(enc); + i = enc_autoload_body(enc_table, enc); + if (i == -2) { + i = load_encoding(rb_enc_name(enc)); } } return i; } -/* Return encoding index or UNSPECIFIED_ENCODING from encoding name. Loads autoloaded and unregistered encodings. */ +/* Return encoding index or UNSPECIFIED_ENCODING from encoding name */ int rb_enc_find_index(const char *name) { @@ -858,7 +844,6 @@ rb_enc_find_index(const char *name) GLOBAL_ENC_TABLE_LOCKING(enc_table) { i = enc_registered(enc_table, name); if (i < 0) { - encdebug("rb_enc_find_index not found, loading encoding %s\n", name); i = load_encoding(name); loaded_encoding = true; } @@ -879,7 +864,7 @@ rb_enc_find_index(const char *name) if (rb_enc_autoload(enc) < 0) { rb_warn("failed to load encoding (%s); use ASCII-8BIT instead", name); - return ENCINDEX_ASCII_8BIT; + return 0; } } return i; @@ -1599,15 +1584,14 @@ enc_set_default_encoding(struct default_encoding *def, VALUE encoding, const cha { int overridden = FALSE; + if (def->index != -2) + /* Already set */ + overridden = TRUE; + GLOBAL_ENC_TABLE_LOCKING(enc_table) { - if (def->index != -2) { - /* Already set */ - overridden = TRUE; - } if (NIL_P(encoding)) { - RUBY_ASSERT(def != &default_external); def->index = -1; - RUBY_ATOMIC_PTR_SET(def->enc, 0); + def->enc = 0; char *name_dup = strdup(name); st_data_t existing_name = (st_data_t)name_dup; @@ -1619,10 +1603,9 @@ enc_set_default_encoding(struct default_encoding *def, VALUE encoding, const cha (st_data_t)UNSPECIFIED_ENCODING); } else { - rb_encoding *enc = rb_to_encoding(encoding); // NOTE: this autoloads the encoding if necessary - def->index = rb_enc_to_index(enc); + def->index = rb_enc_to_index(rb_to_encoding(encoding)); + def->enc = 0; enc_alias_internal(enc_table, name, def->index); - RUBY_ATOMIC_PTR_SET(def->enc, 0); } if (def == &default_external) { @@ -1636,18 +1619,15 @@ enc_set_default_encoding(struct default_encoding *def, VALUE encoding, const cha rb_encoding * rb_default_external_encoding(void) { - rb_encoding *enc; - enc = (rb_encoding*)RUBY_ATOMIC_PTR_LOAD(default_external.enc); - if (enc) { - return enc; - } + rb_encoding *enc = NULL; + // TODO: make lock-free GLOBAL_ENC_TABLE_LOCKING(enc_table) { if (default_external.enc) { enc = default_external.enc; } else if (default_external.index >= 0) { - enc = rb_enc_from_index(default_external.index); - RUBY_ATOMIC_PTR_SET(default_external.enc, (void*)enc); + default_external.enc = rb_enc_from_index(default_external.index); + enc = default_external.enc; } else { enc = rb_locale_encoding(); @@ -1732,20 +1712,15 @@ static struct default_encoding default_internal = {-2}; rb_encoding * rb_default_internal_encoding(void) { - rb_encoding *enc = RUBY_ATOMIC_PTR_LOAD(default_internal.enc); - if (enc) { - return enc; - } - else if (default_internal.index < 0) { - return NULL; - } + rb_encoding *enc = NULL; + // TODO: make lock-free GLOBAL_ENC_TABLE_LOCKING(enc_table) { if (!default_internal.enc && default_internal.index >= 0) { - enc = rb_enc_from_index(default_internal.index); - RUBY_ATOMIC_PTR_SET(default_internal.enc, (void*)enc); + default_internal.enc = rb_enc_from_index(default_internal.index); } + enc = default_internal.enc; } - return enc; + return enc; /* can be NULL */ } VALUE @@ -1987,7 +1962,6 @@ Init_Encoding(void) { VALUE list; int i; - encdebug("Init_Encoding\n"); rb_cEncoding = rb_define_class("Encoding", rb_cObject); rb_define_alloc_func(rb_cEncoding, enc_s_alloc); @@ -2019,7 +1993,6 @@ Init_Encoding(void) RBASIC_CLEAR_CLASS(list); rb_vm_register_global_object(list); - encdebug("enc_table->count: %d\n", enc_table->count); for (i = 0; i < enc_table->count; ++i) { rb_ary_push(list, enc_new(enc_table->list[i].enc)); } @@ -2041,7 +2014,6 @@ Init_unicode_version(void) void Init_encodings(void) { - encdebug("Init_encodings\n"); rb_enc_init(&global_enc_table); } diff --git a/test/ruby/test_encoding.rb b/test/ruby/test_encoding.rb index 0897908017eeb5..ae4e4a7cf7871a 100644 --- a/test/ruby/test_encoding.rb +++ b/test/ruby/test_encoding.rb @@ -144,9 +144,7 @@ def test_ractor_force_encoding_parallel rs = [] 100.times do rs << Ractor.new do - 10_000.times do - "abc".force_encoding(Encoding.list.shuffle.first) - end + "abc".force_encoding(Encoding.list.shuffle.first) end end while rs.any? From 24ac9f11dedcf1b1003000dcb25774b0a3bc38a7 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Thu, 3 Jul 2025 20:52:33 -0700 Subject: [PATCH 118/130] Revert "Add locks around accesses/modifications to global encodings table" This reverts commit cf4d37fbc079116453e69cf08ea8007d0e1c73e6. --- encoding.c | 181 +++++++++++-------------------------- test/ruby/test_encoding.rb | 18 ---- 2 files changed, 54 insertions(+), 145 deletions(-) diff --git a/encoding.c b/encoding.c index 7f1d0011f8064e..60d92690a72547 100644 --- a/encoding.c +++ b/encoding.c @@ -93,16 +93,12 @@ static rb_encoding *global_enc_ascii, *global_enc_utf_8, *global_enc_us_ascii; -// re-entrant lock #define GLOBAL_ENC_TABLE_LOCKING(tbl) \ for (struct enc_table *tbl = &global_enc_table, **locking = &tbl; \ locking; \ locking = NULL) \ RB_VM_LOCKING() -#define GLOBAL_ENC_TABLE_LOCK_ENTER_LEV(tbl, lev) struct enc_table *tbl = &global_enc_table; RB_VM_LOCK_ENTER_LEV(lev) -#define GLOBAL_ENC_TABLE_LOCK_LEAVE_LEV(lev) RB_VM_LOCK_LEAVE_LEV(lev) -#define ASSERT_GLOBAL_ENC_TABLE_LOCKED() ASSERT_vm_locking() #define ENC_DUMMY_FLAG (1<<24) #define ENC_INDEX_MASK (~(~0U<<24)) @@ -144,7 +140,6 @@ enc_new(rb_encoding *encoding) static void enc_list_update(int index, rb_raw_encoding *encoding) { - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); RUBY_ASSERT(index < ENCODING_LIST_CAPA); VALUE list = rb_encoding_list; @@ -160,11 +155,9 @@ enc_list_lookup(int idx) VALUE list, enc = Qnil; if (idx < ENCODING_LIST_CAPA) { - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - list = rb_encoding_list; - RUBY_ASSERT(list); - enc = rb_ary_entry(list, idx); - } + list = rb_encoding_list; + RUBY_ASSERT(list); + enc = rb_ary_entry(list, idx); } if (NIL_P(enc)) { @@ -351,7 +344,6 @@ enc_table_expand(struct enc_table *enc_table, int newsize) static int enc_register_at(struct enc_table *enc_table, int index, const char *name, rb_encoding *base_encoding) { - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); struct rb_encoding_entry *ent = &enc_table->list[index]; rb_raw_encoding *encoding; @@ -384,7 +376,6 @@ enc_register_at(struct enc_table *enc_table, int index, const char *name, rb_enc static int enc_register(struct enc_table *enc_table, const char *name, rb_encoding *encoding) { - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); int index = enc_table->count; enc_table->count = enc_table_expand(enc_table, index + 1); @@ -397,47 +388,28 @@ static int enc_registered(struct enc_table *enc_table, const char *name); static rb_encoding * enc_from_index(struct enc_table *enc_table, int index) { - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); + if (UNLIKELY(index < 0 || enc_table->count <= (index &= ENC_INDEX_MASK))) { + return 0; + } return enc_table->list[index].enc; } rb_encoding * rb_enc_from_index(int index) { - rb_encoding *enc; - switch (index) { - case ENCINDEX_US_ASCII: - return global_enc_us_ascii; - case ENCINDEX_UTF_8: - return global_enc_utf_8; - case ENCINDEX_ASCII_8BIT: - return global_enc_ascii; - default: - break; - } - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - if (UNLIKELY(index < 0 || enc_table->count <= (index &= ENC_INDEX_MASK))) { - enc = NULL; - } - else { - enc = enc_from_index(enc_table, index); - } - } - return enc; + return enc_from_index(&global_enc_table, index); } int rb_enc_register(const char *name, rb_encoding *encoding) { int index; - unsigned int lev; - GLOBAL_ENC_TABLE_LOCK_ENTER_LEV(enc_table, &lev); - { + GLOBAL_ENC_TABLE_LOCKING(enc_table) { index = enc_registered(enc_table, name); if (index >= 0) { - rb_encoding *oldenc = rb_enc_from_index(index); + rb_encoding *oldenc = enc_from_index(enc_table, index); if (STRCASECMP(name, rb_enc_name(oldenc))) { index = enc_register(enc_table, name, encoding); } @@ -445,7 +417,6 @@ rb_enc_register(const char *name, rb_encoding *encoding) enc_register_at(enc_table, index, name, encoding); } else { - GLOBAL_ENC_TABLE_LOCK_LEAVE_LEV(&lev); rb_raise(rb_eArgError, "encoding %s is already registered", name); } } @@ -454,7 +425,6 @@ rb_enc_register(const char *name, rb_encoding *encoding) set_encoding_const(name, rb_enc_from_index(index)); } } - GLOBAL_ENC_TABLE_LOCK_LEAVE_LEV(&lev); return index; } @@ -462,7 +432,6 @@ int enc_registered(struct enc_table *enc_table, const char *name) { st_data_t idx = 0; - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); if (!name) return -1; if (!enc_table->names) return -1; @@ -498,7 +467,6 @@ enc_check_addable(struct enc_table *enc_table, const char *name) static rb_encoding* set_base_encoding(struct enc_table *enc_table, int index, rb_encoding *base) { - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); rb_encoding *enc = enc_table->list[index].enc; ASSUME(enc); @@ -536,7 +504,6 @@ static int enc_replicate(struct enc_table *enc_table, const char *name, rb_encoding *encoding) { int idx; - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); enc_check_addable(enc_table, name); idx = enc_register(enc_table, name, encoding); @@ -670,7 +637,6 @@ enc_dup_name(st_data_t name) static int enc_alias_internal(struct enc_table *enc_table, const char *alias, int idx) { - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); return st_insert2(enc_table->names, (st_data_t)alias, (st_data_t)idx, enc_dup_name); } @@ -678,10 +644,9 @@ enc_alias_internal(struct enc_table *enc_table, const char *alias, int idx) static int enc_alias(struct enc_table *enc_table, const char *alias, int idx) { - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); if (!valid_encoding_name_p(alias)) return -1; if (!enc_alias_internal(enc_table, alias, idx)) - set_encoding_const(alias, rb_enc_from_index(idx)); + set_encoding_const(alias, enc_from_index(enc_table, idx)); return idx; } @@ -763,7 +728,6 @@ int rb_require_internal_silent(VALUE fname); static int load_encoding(const char *name) { - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); VALUE enclib = rb_sprintf("enc/%s.so", name); VALUE debug = ruby_debug; VALUE errinfo; @@ -783,14 +747,16 @@ load_encoding(const char *name) ruby_debug = debug; rb_set_errinfo(errinfo); - if (loaded < 0 || 1 < loaded) { - idx = -1; - } - else if ((idx = enc_registered(&global_enc_table, name)) < 0) { - idx = -1; - } - else if (rb_enc_autoload_p(global_enc_table.list[idx].enc)) { - idx = -1; + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + if (loaded < 0 || 1 < loaded) { + idx = -1; + } + else if ((idx = enc_registered(enc_table, name)) < 0) { + idx = -1; + } + else if (rb_enc_autoload_p(enc_table->list[idx].enc)) { + idx = -1; + } } return idx; @@ -799,7 +765,6 @@ load_encoding(const char *name) static int enc_autoload_body(struct enc_table *enc_table, rb_encoding *enc) { - ASSERT_GLOBAL_ENC_TABLE_LOCKED(); rb_encoding *base = enc_table->list[ENC_TO_ENCINDEX(enc)].base; if (base) { @@ -827,9 +792,9 @@ rb_enc_autoload(rb_encoding *enc) int i; GLOBAL_ENC_TABLE_LOCKING(enc_table) { i = enc_autoload_body(enc_table, enc); - if (i == -2) { - i = load_encoding(rb_enc_name(enc)); - } + } + if (i == -2) { + i = load_encoding(rb_enc_name(enc)); } return i; } @@ -838,24 +803,13 @@ rb_enc_autoload(rb_encoding *enc) int rb_enc_find_index(const char *name) { - int i; - rb_encoding *enc = NULL; - bool loaded_encoding = false; - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - i = enc_registered(enc_table, name); - if (i < 0) { - i = load_encoding(name); - loaded_encoding = true; - } - else { - enc = rb_enc_from_index(i); - } - } - if (loaded_encoding) { - return i; - } + int i = enc_registered(&global_enc_table, name); + rb_encoding *enc; - if (!enc) { + if (i < 0) { + i = load_encoding(name); + } + else if (!(enc = rb_enc_from_index(i))) { if (i != UNSPECIFIED_ENCODING) { rb_raise(rb_eArgError, "encoding %s is not registered", name); } @@ -884,13 +838,9 @@ rb_enc_find_index2(const char *name, long len) rb_encoding * rb_enc_find(const char *name) { - rb_encoding *enc; - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - int idx = rb_enc_find_index(name); - if (idx < 0) idx = 0; - enc = rb_enc_from_index(idx); - } - return enc; + int idx = rb_enc_find_index(name); + if (idx < 0) idx = 0; + return rb_enc_from_index(idx); } static inline int @@ -1359,9 +1309,7 @@ enc_names(VALUE self) args[0] = (VALUE)rb_to_encoding_index(self); args[1] = rb_ary_new2(0); - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - st_foreach(enc_table->names, enc_names_i, (st_data_t)args); - } + st_foreach(global_enc_table.names, enc_names_i, (st_data_t)args); return args[1]; } @@ -1536,14 +1484,14 @@ rb_locale_encindex(void) if (idx < 0) idx = ENCINDEX_UTF_8; - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - if (enc_registered(enc_table, "locale") < 0) { + if (enc_registered(&global_enc_table, "locale") < 0) { # if defined _WIN32 - void Init_w32_codepage(void); - Init_w32_codepage(); + void Init_w32_codepage(void); + Init_w32_codepage(); # endif + GLOBAL_ENC_TABLE_LOCKING(enc_table) { + enc_alias_internal(enc_table, "locale", idx); } - enc_alias_internal(enc_table, "locale", idx); } return idx; @@ -1558,10 +1506,7 @@ rb_locale_encoding(void) int rb_filesystem_encindex(void) { - int idx; - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - idx = enc_registered(enc_table, "filesystem"); - } + int idx = enc_registered(&global_enc_table, "filesystem"); if (idx < 0) idx = ENCINDEX_ASCII_8BIT; return idx; } @@ -1619,21 +1564,15 @@ enc_set_default_encoding(struct default_encoding *def, VALUE encoding, const cha rb_encoding * rb_default_external_encoding(void) { - rb_encoding *enc = NULL; - // TODO: make lock-free - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - if (default_external.enc) { - enc = default_external.enc; - } - else if (default_external.index >= 0) { - default_external.enc = rb_enc_from_index(default_external.index); - enc = default_external.enc; - } - else { - enc = rb_locale_encoding(); - } + if (default_external.enc) return default_external.enc; + + if (default_external.index >= 0) { + default_external.enc = rb_enc_from_index(default_external.index); + return default_external.enc; + } + else { + return rb_locale_encoding(); } - return enc; } VALUE @@ -1712,15 +1651,10 @@ static struct default_encoding default_internal = {-2}; rb_encoding * rb_default_internal_encoding(void) { - rb_encoding *enc = NULL; - // TODO: make lock-free - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - if (!default_internal.enc && default_internal.index >= 0) { - default_internal.enc = rb_enc_from_index(default_internal.index); - } - enc = default_internal.enc; + if (!default_internal.enc && default_internal.index >= 0) { + default_internal.enc = rb_enc_from_index(default_internal.index); } - return enc; /* can be NULL */ + return default_internal.enc; /* can be NULL */ } VALUE @@ -1869,11 +1803,8 @@ rb_enc_name_list_i(st_data_t name, st_data_t idx, st_data_t arg) static VALUE rb_enc_name_list(VALUE klass) { - VALUE ary; - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - ary = rb_ary_new2(enc_table->names->num_entries); - st_foreach(enc_table->names, rb_enc_name_list_i, (st_data_t)ary); - } + VALUE ary = rb_ary_new2(global_enc_table.names->num_entries); + st_foreach(global_enc_table.names, rb_enc_name_list_i, (st_data_t)ary); return ary; } @@ -1919,9 +1850,7 @@ rb_enc_aliases(VALUE klass) aliases[0] = rb_hash_new(); aliases[1] = rb_ary_new(); - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - st_foreach(enc_table->names, rb_enc_aliases_enc_i, (st_data_t)aliases); - } + st_foreach(global_enc_table.names, rb_enc_aliases_enc_i, (st_data_t)aliases); return aliases[0]; } @@ -2022,7 +1951,5 @@ Init_encodings(void) void rb_enc_foreach_name(int (*func)(st_data_t name, st_data_t idx, st_data_t arg), st_data_t arg) { - GLOBAL_ENC_TABLE_LOCKING(enc_table) { - st_foreach(enc_table->names, func, arg); - } + st_foreach(global_enc_table.names, func, arg); } diff --git a/test/ruby/test_encoding.rb b/test/ruby/test_encoding.rb index ae4e4a7cf7871a..0ab357f53ad8b4 100644 --- a/test/ruby/test_encoding.rb +++ b/test/ruby/test_encoding.rb @@ -136,22 +136,4 @@ def test_ractor_load_encoding assert "[Bug #19562]" end; end - - def test_ractor_force_encoding_parallel - assert_ractor("#{<<~"begin;"}\n#{<<~'end;'}") - begin; - $-w = nil - rs = [] - 100.times do - rs << Ractor.new do - "abc".force_encoding(Encoding.list.shuffle.first) - end - end - while rs.any? - r, _obj = Ractor.select(*rs) - rs.delete(r) - end - assert rs.empty? - end; - end end From 95235fd528f38dcf2400abdc09a1ec2d71384ced Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 3 Jul 2025 11:56:12 +0200 Subject: [PATCH 119/130] benchmark_driver: Stop using `Ractor#take` --- benchmark/lib/benchmark_driver/runner/ractor.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/lib/benchmark_driver/runner/ractor.rb b/benchmark/lib/benchmark_driver/runner/ractor.rb index c730b8e4a54bf2..fd9c2dd4db731d 100644 --- a/benchmark/lib/benchmark_driver/runner/ractor.rb +++ b/benchmark/lib/benchmark_driver/runner/ractor.rb @@ -87,7 +87,7 @@ def render(results:) <% end %> # Wait for all Ractors before executing code to write results -__bmdv_ractors.map!(&:take) +__bmdv_ractors.map!(&:value) <% results.each do |result| %> File.write(<%= result.dump %>, __bmdv_ractors.shift) From 856962fa38bfe071baa22870aaa2bd8c1ce9f8f3 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Thu, 3 Jul 2025 11:46:28 +0200 Subject: [PATCH 120/130] ractor_sync.c: Optimize `ractor_set_successor_once` to be lock free --- ractor_sync.c | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/ractor_sync.c b/ractor_sync.c index 124ffc139cac09..eb967a73cbc324 100644 --- a/ractor_sync.c +++ b/ractor_sync.c @@ -729,20 +729,10 @@ static rb_ractor_t * ractor_set_successor_once(rb_ractor_t *r, rb_ractor_t *cr) { if (r->sync.successor == NULL) { - RACTOR_LOCK(r); - { - if (r->sync.successor != NULL) { - // already `value`ed - } - else { - r->sync.successor = cr; - } - } - RACTOR_UNLOCK(r); + rb_ractor_t *successor = ATOMIC_PTR_CAS(r->sync.successor, NULL, cr); + return successor == NULL ? cr : successor; } - VM_ASSERT(r->sync.successor != NULL); - return r->sync.successor; } From 5564e0a58d928d0f34bfd14989070c802f4eaa0f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 4 Jul 2025 15:30:56 +0900 Subject: [PATCH 121/130] Fixed wrong commit hash --- .github/workflows/scorecards.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 3b43080201e510..8cc7e00c47ec26 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -39,7 +39,7 @@ jobs: persist-credentials: false - name: "Run analysis" - uses: ossf/scorecard-action@f2ea147fec3c2f0d459703eba7405b5e9bcd8c8f # v2.4.2 + uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 with: results_file: results.sarif results_format: sarif From 4d6fac3e9515a71edd6d77e59c3a04dcbe0c444f Mon Sep 17 00:00:00 2001 From: git Date: Fri, 4 Jul 2025 07:05:42 +0000 Subject: [PATCH 122/130] Update bundled gems list as of 2025-07-03 --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 2af947b4cd258b..dcd67abbe261c3 100644 --- a/NEWS.md +++ b/NEWS.md @@ -119,7 +119,7 @@ The following bundled gems are promoted from default gems. * pstore 0.2.0 * benchmark 0.4.1 * logger 1.7.0 -* rdoc 6.14.1 +* rdoc 6.14.2 * win32ole 1.9.2 * irb 1.15.2 * reline 0.6.1 diff --git a/gems/bundled_gems b/gems/bundled_gems index 25f5fcbda0c681..7fcd0796aa4bec 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -39,7 +39,7 @@ ostruct 0.6.2 https://github.com/ruby/ostruct pstore 0.2.0 https://github.com/ruby/pstore benchmark 0.4.1 https://github.com/ruby/benchmark logger 1.7.0 https://github.com/ruby/logger -rdoc 6.14.1 https://github.com/ruby/rdoc +rdoc 6.14.2 https://github.com/ruby/rdoc win32ole 1.9.2 https://github.com/ruby/win32ole irb 1.15.2 https://github.com/ruby/irb 331c4e851296b115db766c291e8cf54a2492fb36 reline 0.6.1 https://github.com/ruby/reline From 38993efb27a35b37ecb938f7791fa7c51fbf4bac Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Fri, 20 Jun 2025 02:36:36 +0900 Subject: [PATCH 123/130] [ruby/openssl] ssl: rename SSLContext#ecdh_curves= to #groups= TLS 1.3 renamed the "elliptic_curves" extension to "supported_groups" to reflect that it now covers more than just ECDH groups. OpenSSL 1.1.1 followed this change by renaming the corresponding API from SSL_CTX_set1_curves_list() to SSL_CTX_set1_groups_list(). Update ruby/openssl to use the new name, too. The current method name SSLContext#ecdh_curves= is retained as an alias for #group=. https://github.com/ruby/openssl/commit/59e98604e0 --- ext/openssl/ossl_ssl.c | 32 +++++++++++++------------ test/openssl/test_ssl.rb | 50 +++++++++++++++++++++------------------- 2 files changed, 43 insertions(+), 39 deletions(-) diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index 30fbb3bbd14730..b5872f588125c2 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -1182,25 +1182,29 @@ ossl_sslctx_set_tmp_dh(VALUE self, VALUE arg) } #endif -#if !defined(OPENSSL_NO_EC) /* * call-seq: - * ctx.ecdh_curves = curve_list -> curve_list + * ctx.groups = groups_list + * ctx.ecdh_curves = groups_list * - * Sets the list of "supported elliptic curves" for this context. + * Sets the list of supported groups for key agreement for this context. * - * For a TLS client, the list is directly used in the Supported Elliptic Curves - * Extension. For a server, the list is used by OpenSSL to determine the set of - * shared curves. OpenSSL will pick the most appropriate one from it. + * For a TLS client, the list is directly used in the "supported_groups" + * extension. For a server, the list is used by OpenSSL to determine the set of + * shared supported groups. OpenSSL will pick the most appropriate one from it. + * + * #ecdh_curves= is a deprecated alias for #groups=. + * + * See also the man page SSL_CTX_set1_groups_list(3). * * === Example * ctx1 = OpenSSL::SSL::SSLContext.new - * ctx1.ecdh_curves = "X25519:P-256:P-224" + * ctx1.groups = "X25519:P-256:P-224" * svr = OpenSSL::SSL::SSLServer.new(tcp_svr, ctx1) * Thread.new { svr.accept } * * ctx2 = OpenSSL::SSL::SSLContext.new - * ctx2.ecdh_curves = "P-256" + * ctx2.groups = "P-256" * cli = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx2) * cli.connect * @@ -1208,7 +1212,7 @@ ossl_sslctx_set_tmp_dh(VALUE self, VALUE arg) * # => "prime256v1" (is an alias for NIST P-256) */ static VALUE -ossl_sslctx_set_ecdh_curves(VALUE self, VALUE arg) +ossl_sslctx_set_groups(VALUE self, VALUE arg) { SSL_CTX *ctx; @@ -1216,13 +1220,10 @@ ossl_sslctx_set_ecdh_curves(VALUE self, VALUE arg) GetSSLCTX(self, ctx); StringValueCStr(arg); - if (!SSL_CTX_set1_curves_list(ctx, RSTRING_PTR(arg))) - ossl_raise(eSSLError, NULL); + if (!SSL_CTX_set1_groups_list(ctx, RSTRING_PTR(arg))) + ossl_raise(eSSLError, "SSL_CTX_set1_groups_list"); return arg; } -#else -#define ossl_sslctx_set_ecdh_curves rb_f_notimplement -#endif /* * call-seq: @@ -2958,7 +2959,8 @@ Init_ossl_ssl(void) #ifndef OPENSSL_NO_DH rb_define_method(cSSLContext, "tmp_dh=", ossl_sslctx_set_tmp_dh, 1); #endif - rb_define_method(cSSLContext, "ecdh_curves=", ossl_sslctx_set_ecdh_curves, 1); + rb_define_method(cSSLContext, "groups=", ossl_sslctx_set_groups, 1); + rb_define_alias(cSSLContext, "ecdh_curves=", "groups="); rb_define_method(cSSLContext, "security_level", ossl_sslctx_get_security_level, 0); rb_define_method(cSSLContext, "security_level=", ossl_sslctx_set_security_level, 1); #ifdef SSL_MODE_SEND_FALLBACK_SCSV diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index 61c26b5dd5f504..febac061564dfd 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -1764,33 +1764,28 @@ def test_get_ephemeral_key end end - if !aws_lc? # AWS-LC does not support DHE ciphersuites. - # DHE - # TODO: SSL_CTX_set1_groups() is required for testing this with TLS 1.3 - ctx_proc2 = proc { |ctx| - ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION - ctx.ciphers = "EDH" - ctx.tmp_dh = Fixtures.pkey("dh-1") - } - start_server(ctx_proc: ctx_proc2) do |port| + # DHE + # OpenSSL 3.0 added support for named FFDHE groups in TLS 1.3 + # LibreSSL does not support named FFDHE groups currently + # AWS-LC does not support DHE ciphersuites + if openssl?(3, 0, 0) + start_server do |port| ctx = OpenSSL::SSL::SSLContext.new - ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION - ctx.ciphers = "EDH" + ctx.groups = "ffdhe3072" server_connect(port, ctx) { |ssl| assert_instance_of OpenSSL::PKey::DH, ssl.tmp_key + assert_equal 3072, ssl.tmp_key.p.num_bits + ssl.puts "abc"; assert_equal "abc\n", ssl.gets } end end # ECDHE ctx_proc3 = proc { |ctx| - ctx.ciphers = "DEFAULT:!kRSA:!kEDH" - ctx.ecdh_curves = "P-256" + ctx.groups = "P-256" } start_server(ctx_proc: ctx_proc3) do |port| - ctx = OpenSSL::SSL::SSLContext.new - ctx.ciphers = "DEFAULT:!kRSA:!kEDH" - server_connect(port, ctx) { |ssl| + server_connect(port) { |ssl| assert_instance_of OpenSSL::PKey::EC, ssl.tmp_key ssl.puts "abc"; assert_equal "abc\n", ssl.gets } @@ -2079,17 +2074,17 @@ def test_tmp_dh end end - def test_ecdh_curves_tls12 + def test_set_groups_tls12 ctx_proc = -> ctx { # Enable both ECDHE (~ TLS 1.2) cipher suites and TLS 1.3 ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION ctx.ciphers = "kEECDH" - ctx.ecdh_curves = "P-384:P-521" + ctx.groups = "P-384:P-521" } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| # Test 1: Client=P-256:P-384, Server=P-384:P-521 --> P-384 ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-256:P-384" + ctx.groups = "P-256:P-384" server_connect(port, ctx) { |ssl| cs = ssl.cipher[0] assert_match (/\AECDH/), cs @@ -2099,29 +2094,36 @@ def test_ecdh_curves_tls12 # Test 2: Client=P-256, Server=P-521:P-384 --> Fail ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-256" + ctx.groups = "P-256" assert_raise(OpenSSL::SSL::SSLError) { server_connect(port, ctx) { } } # Test 3: Client=P-521:P-384, Server=P-521:P-384 --> P-521 ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-521:P-384" + ctx.groups = "P-521:P-384" server_connect(port, ctx) { |ssl| assert_equal "secp521r1", ssl.tmp_key.group.curve_name ssl.puts "abc"; assert_equal "abc\n", ssl.gets } + + # Test 4: #ecdh_curves= alias + ctx = OpenSSL::SSL::SSLContext.new + ctx.ecdh_curves = "P-256:P-384" + server_connect(port, ctx) { |ssl| + assert_equal "secp384r1", ssl.tmp_key.group.curve_name + } end end - def test_ecdh_curves_tls13 + def test_set_groups_tls13 ctx_proc = -> ctx { # Assume TLS 1.3 is enabled and chosen by default - ctx.ecdh_curves = "P-384:P-521" + ctx.groups = "P-384:P-521" } start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| ctx = OpenSSL::SSL::SSLContext.new - ctx.ecdh_curves = "P-256:P-384" # disable P-521 + ctx.groups = "P-256:P-384" # disable P-521 server_connect(port, ctx) { |ssl| assert_equal "TLSv1.3", ssl.ssl_version From 350df4fbd96294ddfc3bf6ea7402ac99241e8912 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 4 Jul 2025 10:42:29 -0500 Subject: [PATCH 124/130] [DOC] Tweaks for Case Mapping doc --- doc/case_mapping.rdoc | 30 ++++++++++-------------------- string.c | 40 ++++++++++++++++++++-------------------- 2 files changed, 30 insertions(+), 40 deletions(-) diff --git a/doc/case_mapping.rdoc b/doc/case_mapping.rdoc index 57c037b9d8ef46..d40155db031f43 100644 --- a/doc/case_mapping.rdoc +++ b/doc/case_mapping.rdoc @@ -37,7 +37,7 @@ Context-dependent case mapping as described in {Table 3-17 (Context Specification for Casing) of the Unicode standard}[https://www.unicode.org/versions/latest/ch03.pdf] is currently not supported. -In most cases, case conversions of a string have the same number of characters. +In most cases, the case conversion of a string has the same number of characters as before. There are exceptions (see also +:fold+ below): s = "\u00DF" # => "ß" @@ -58,25 +58,18 @@ Case changes may not be reversible: s.downcase.upcase # => "HELLO WORLD!" # Different from original s. Case changing methods may not maintain Unicode normalization. -See String#unicode_normalize). +See String#unicode_normalize. -== Options for Case Mapping +== Case Mappings Except for +casecmp+ and +casecmp?+, each of the case-mapping methods listed above -accepts optional arguments, *options. +accepts an optional argument, mapping. -The arguments may be: +The argument is one of: -- +:ascii+ only. -- +:fold+ only. -- +:turkic+ or +:lithuanian+ or both. - -The options: - -- +:ascii+: - ASCII-only mapping: - uppercase letters ('A'..'Z') are mapped to lowercase letters ('a'..'z); +- +:ascii+: ASCII-only mapping. + Uppercase letters ('A'..'Z') are mapped to lowercase letters ('a'..'z); other characters are not changed s = "Foo \u00D8 \u00F8 Bar" # => "Foo Ø ø Bar" @@ -85,8 +78,8 @@ The options: s.upcase(:ascii) # => "FOO Ø ø BAR" s.downcase(:ascii) # => "foo Ø ø bar" -- +:turkic+: - Full Unicode case mapping, adapted for the Turkic languages +- +:turkic+: Full Unicode case mapping. + For the Turkic languages that distinguish dotted and dotless I, for example Turkish and Azeri. s = 'Türkiye' # => "Türkiye" @@ -97,11 +90,8 @@ The options: s.downcase # => "türkiye" s.downcase(:turkic) # => "türkıye" # No dot above. -- +:lithuanian+: - Not yet implemented. - - +:fold+ (available only for String#downcase, String#downcase!, - and Symbol#downcase): + and Symbol#downcase). Unicode case folding, which is more far-reaching than Unicode case mapping. diff --git a/string.c b/string.c index 6069a8751b1fe1..589946c9bc6094 100644 --- a/string.c +++ b/string.c @@ -8056,7 +8056,7 @@ upcase_single(VALUE str) /* * call-seq: - * upcase!(*options) -> self or nil + * upcase!(mapping) -> self or nil * * Upcases the characters in +self+; * returns +self+ if any changes were made, +nil+ otherwise: @@ -8066,7 +8066,7 @@ upcase_single(VALUE str) * s # => "HELLO WORLD!" * s.upcase! # => nil * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#upcase, String#downcase, String#downcase!. @@ -8098,14 +8098,14 @@ rb_str_upcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * upcase(*options) -> string + * upcase(mapping) -> string * * Returns a string containing the upcased characters in +self+: * * s = 'Hello World!' # => "Hello World!" * s.upcase # => "HELLO WORLD!" * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#upcase!, String#downcase, String#downcase!. @@ -8158,7 +8158,7 @@ downcase_single(VALUE str) /* * call-seq: - * downcase!(*options) -> self or nil + * downcase!(mapping) -> self or nil * * Downcases the characters in +self+; * returns +self+ if any changes were made, +nil+ otherwise: @@ -8168,7 +8168,7 @@ downcase_single(VALUE str) * s # => "hello world!" * s.downcase! # => nil * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#downcase, String#upcase, String#upcase!. @@ -8200,14 +8200,14 @@ rb_str_downcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * downcase(*options) -> string + * downcase(mapping) -> string * * Returns a string containing the downcased characters in +self+: * * s = 'Hello World!' # => "Hello World!" * s.downcase # => "hello world!" * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#downcase!, String#upcase, String#upcase!. @@ -8242,7 +8242,7 @@ rb_str_downcase(int argc, VALUE *argv, VALUE str) /* * call-seq: - * capitalize!(*options) -> self or nil + * capitalize!(mapping) -> self or nil * * Upcases the first character in +self+; * downcases the remaining characters; @@ -8253,7 +8253,7 @@ rb_str_downcase(int argc, VALUE *argv, VALUE str) * s # => "Hello world!" * s.capitalize! # => nil * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#capitalize. @@ -8282,7 +8282,7 @@ rb_str_capitalize_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * capitalize(*options) -> string + * capitalize(mapping) -> string * * Returns a string containing the characters in +self+; * the first character is upcased; @@ -8291,7 +8291,7 @@ rb_str_capitalize_bang(int argc, VALUE *argv, VALUE str) * s = 'hello World!' # => "hello World!" * s.capitalize # => "Hello world!" * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#capitalize!. @@ -8321,7 +8321,7 @@ rb_str_capitalize(int argc, VALUE *argv, VALUE str) /* * call-seq: - * swapcase!(*options) -> self or nil + * swapcase!(mapping) -> self or nil * * Upcases each lowercase character in +self+; * downcases uppercase character; @@ -8332,7 +8332,7 @@ rb_str_capitalize(int argc, VALUE *argv, VALUE str) * s # => "hELLO wORLD!" * ''.swapcase! # => nil * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#swapcase. @@ -8360,7 +8360,7 @@ rb_str_swapcase_bang(int argc, VALUE *argv, VALUE str) /* * call-seq: - * swapcase(*options) -> string + * swapcase(mapping) -> string * * Returns a string containing the characters in +self+, with cases reversed; * each uppercase character is downcased; @@ -8369,7 +8369,7 @@ rb_str_swapcase_bang(int argc, VALUE *argv, VALUE str) * s = 'Hello World!' # => "Hello World!" * s.swapcase # => "hELLO wORLD!" * - * The casing may be affected by the given +options+; + * The casing may be affected by the given +mapping+; * see {Case Mapping}[rdoc-ref:case_mapping.rdoc]. * * Related: String#swapcase!. @@ -12482,7 +12482,7 @@ sym_empty(VALUE sym) /* * call-seq: - * upcase(*options) -> symbol + * upcase(mapping) -> symbol * * Equivalent to sym.to_s.upcase.to_sym. * @@ -12498,7 +12498,7 @@ sym_upcase(int argc, VALUE *argv, VALUE sym) /* * call-seq: - * downcase(*options) -> symbol + * downcase(mapping) -> symbol * * Equivalent to sym.to_s.downcase.to_sym. * @@ -12516,7 +12516,7 @@ sym_downcase(int argc, VALUE *argv, VALUE sym) /* * call-seq: - * capitalize(*options) -> symbol + * capitalize(mapping) -> symbol * * Equivalent to sym.to_s.capitalize.to_sym. * @@ -12532,7 +12532,7 @@ sym_capitalize(int argc, VALUE *argv, VALUE sym) /* * call-seq: - * swapcase(*options) -> symbol + * swapcase(mapping) -> symbol * * Equivalent to sym.to_s.swapcase.to_sym. * From 116d11062f743a3d9bca556ce03ebfbe374fffea Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 3 Jul 2025 09:18:58 -0400 Subject: [PATCH 125/130] Assume that symbol in rb_check_symbol is not garbage rb_check_symbol is a public API, so it is always a bug if the user holds on to a dead object and passes it in. --- symbol.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/symbol.c b/symbol.c index 2f2960fbda3e02..e4f18197c92614 100644 --- a/symbol.c +++ b/symbol.c @@ -1208,13 +1208,7 @@ rb_check_symbol(volatile VALUE *namep) return name; } else if (DYNAMIC_SYM_P(name)) { - if (!SYMBOL_PINNED_P(name)) { - GLOBAL_SYMBOLS_LOCKING(symbols) { - name = dsymbol_check(symbols, name); - } - - *namep = name; - } + RUBY_ASSERT(!rb_objspace_garbage_object_p(name)); return name; } else if (!RB_TYPE_P(name, T_STRING)) { From 5f1ca8ffbe53f9d8d6c054f18d7524889498aedd Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 27 Jun 2025 20:51:08 -0700 Subject: [PATCH 126/130] Fix ractor imemo fields write barrier parent $ RUBY_GC_LIBRARY=wbcheck ./miniruby -e 's = String.new; s.instance_variable_set(:@x, []); Ractor.make_shareable(s, copy: true)' WBCHECK ERROR: Missed write barrier detected! Parent object: 0x7ba8162dc890 (wb_protected: true) rb_obj_info_dump: 0x00007ba8162dc890 T_IMEMO/ Reference counts - snapshot: 2, writebarrier: 0, current: 2, missed: 1 Missing reference to: 0x7ba8162dcad0 rb_obj_info_dump: 0x00007ba8162dcad0 T_ARRAY/Array [E ] len: 0 (embed) WBCHECK SUMMARY: Found 1 objects with missed write barriers (1 total violations) --- ractor.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/ractor.c b/ractor.c index 317b24dca24694..a1ce7967c8faba 100644 --- a/ractor.c +++ b/ractor.c @@ -1653,10 +1653,10 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) obj = replacement; } -#define CHECK_AND_REPLACE(v) do { \ +#define CHECK_AND_REPLACE(parent_obj, v) do { \ VALUE _val = (v); \ if (obj_traverse_replace_i(_val, data)) { return 1; } \ - else if (data->replacement != _val) { RB_OBJ_WRITE(obj, &v, data->replacement); } \ + else if (data->replacement != _val) { RB_OBJ_WRITE(parent_obj, &v, data->replacement); } \ } while (0) if (UNLIKELY(rb_obj_exivar_p(obj))) { @@ -1681,7 +1681,7 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) uint32_t fields_count = RSHAPE_LEN(RBASIC_SHAPE_ID(obj)); VALUE *fields = rb_imemo_fields_ptr(fields_obj); for (uint32_t i = 0; i < fields_count; i++) { - CHECK_AND_REPLACE(fields[i]); + CHECK_AND_REPLACE(fields_obj, fields[i]); } } } @@ -1720,7 +1720,7 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) VALUE *ptr = ROBJECT_FIELDS(obj); for (uint32_t i = 0; i < len; i++) { - CHECK_AND_REPLACE(ptr[i]); + CHECK_AND_REPLACE(obj, ptr[i]); } } } @@ -1773,18 +1773,18 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) const VALUE *ptr = RSTRUCT_CONST_PTR(obj); for (long i=0; inum); - CHECK_AND_REPLACE(RRATIONAL(obj)->den); + CHECK_AND_REPLACE(obj, RRATIONAL(obj)->num); + CHECK_AND_REPLACE(obj, RRATIONAL(obj)->den); break; case T_COMPLEX: - CHECK_AND_REPLACE(RCOMPLEX(obj)->real); - CHECK_AND_REPLACE(RCOMPLEX(obj)->imag); + CHECK_AND_REPLACE(obj, RCOMPLEX(obj)->real); + CHECK_AND_REPLACE(obj, RCOMPLEX(obj)->imag); break; case T_DATA: From 32453560de9ac10c4d234686c94696625916dea6 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Mon, 30 Jun 2025 16:28:49 -0700 Subject: [PATCH 127/130] Fix missed write barrier on Ractor send move When moving a "generic IV" object, we need a write barrier to the fields object. WBCHECK ERROR: Missed write barrier detected! Parent object: 0x7c913641d1a0 (wb_protected: true) rb_obj_info_dump: 0x00007c913641d1a0 T_ARRAY/Array [E ] len: 10 (embed) Reference counts - snapshot: 1, writebarrier: 0, current: 2, missed: 1 Missing reference to: 0x7bf1364e56d0 rb_obj_info_dump: 0x00007bf1364e56d0 T_IMEMO/ --- variable.c | 1 + 1 file changed, 1 insertion(+) diff --git a/variable.c b/variable.c index 66e17c43ad38ca..69aedf1133be7c 100644 --- a/variable.c +++ b/variable.c @@ -2355,6 +2355,7 @@ rb_replace_generic_ivar(VALUE clone, VALUE obj) st_data_t fields_tbl, obj_data = (st_data_t)obj; if (st_delete(generic_fields_tbl_, &obj_data, &fields_tbl)) { st_insert(generic_fields_tbl_, (st_data_t)clone, fields_tbl); + RB_OBJ_WRITTEN(clone, Qundef, fields_tbl); } else { rb_bug("unreachable"); From 8cd583269428bf0f83b474ea7718715ce96f441b Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 4 Jul 2025 11:58:39 -0700 Subject: [PATCH 128/130] Fix wrong write barrier on fields copy Previously this write barrier was using the destination object as the new parent, rather than the fields object. Found by wbcheck --- variable.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variable.c b/variable.c index 69aedf1133be7c..d9ef42e257b8bc 100644 --- a/variable.c +++ b/variable.c @@ -2331,7 +2331,7 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj) new_fields_obj = rb_imemo_fields_new(rb_obj_class(dest), RSHAPE_CAPACITY(dest_shape_id)); VALUE *src_buf = rb_imemo_fields_ptr(fields_obj); VALUE *dest_buf = rb_imemo_fields_ptr(new_fields_obj); - rb_shape_copy_fields(dest, dest_buf, dest_shape_id, obj, src_buf, src_shape_id); + rb_shape_copy_fields(new_fields_obj, dest_buf, dest_shape_id, obj, src_buf, src_shape_id); RBASIC_SET_SHAPE_ID(new_fields_obj, dest_shape_id); RB_VM_LOCKING() { From 12b0ce3875806f7f6a14f3d92cedb132bb57f6b4 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 4 Jul 2025 12:02:18 -0700 Subject: [PATCH 129/130] Remove unused src param from rb_shape_copy_fields --- object.c | 2 +- shape.c | 2 +- shape.h | 2 +- variable.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/object.c b/object.c index 61a485047eaf90..0ab93b7a711c02 100644 --- a/object.c +++ b/object.c @@ -363,7 +363,7 @@ rb_obj_copy_ivar(VALUE dest, VALUE obj) dest_buf = ROBJECT_FIELDS(dest); } - rb_shape_copy_fields(dest, dest_buf, dest_shape_id, obj, src_buf, src_shape_id); + rb_shape_copy_fields(dest, dest_buf, dest_shape_id, src_buf, src_shape_id); rb_obj_set_shape_id(dest, dest_shape_id); } diff --git a/shape.c b/shape.c index f799cdf11b5e76..b769aea78b90a3 100644 --- a/shape.c +++ b/shape.c @@ -1136,7 +1136,7 @@ rb_shape_rebuild(shape_id_t initial_shape_id, shape_id_t dest_shape_id) } void -rb_shape_copy_fields(VALUE dest, VALUE *dest_buf, shape_id_t dest_shape_id, VALUE src, VALUE *src_buf, shape_id_t src_shape_id) +rb_shape_copy_fields(VALUE dest, VALUE *dest_buf, shape_id_t dest_shape_id, VALUE *src_buf, shape_id_t src_shape_id) { rb_shape_t *dest_shape = RSHAPE(dest_shape_id); rb_shape_t *src_shape = RSHAPE(src_shape_id); diff --git a/shape.h b/shape.h index eab2a08f38886b..4354dd9ff65d50 100644 --- a/shape.h +++ b/shape.h @@ -217,7 +217,7 @@ shape_id_t rb_shape_object_id(shape_id_t original_shape_id); void rb_shape_free_all(void); shape_id_t rb_shape_rebuild(shape_id_t initial_shape_id, shape_id_t dest_shape_id); -void rb_shape_copy_fields(VALUE dest, VALUE *dest_buf, shape_id_t dest_shape_id, VALUE src, VALUE *src_buf, shape_id_t src_shape_id); +void rb_shape_copy_fields(VALUE dest, VALUE *dest_buf, shape_id_t dest_shape_id, VALUE *src_buf, shape_id_t src_shape_id); void rb_shape_copy_complex_ivars(VALUE dest, VALUE obj, shape_id_t src_shape_id, st_table *fields_table); static inline bool diff --git a/variable.c b/variable.c index d9ef42e257b8bc..0748885bcb8a3a 100644 --- a/variable.c +++ b/variable.c @@ -2331,7 +2331,7 @@ rb_copy_generic_ivar(VALUE dest, VALUE obj) new_fields_obj = rb_imemo_fields_new(rb_obj_class(dest), RSHAPE_CAPACITY(dest_shape_id)); VALUE *src_buf = rb_imemo_fields_ptr(fields_obj); VALUE *dest_buf = rb_imemo_fields_ptr(new_fields_obj); - rb_shape_copy_fields(new_fields_obj, dest_buf, dest_shape_id, obj, src_buf, src_shape_id); + rb_shape_copy_fields(new_fields_obj, dest_buf, dest_shape_id, src_buf, src_shape_id); RBASIC_SET_SHAPE_ID(new_fields_obj, dest_shape_id); RB_VM_LOCKING() { From 365317f6baa375a07ee11ad585d8c4ec55b46fcb Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Fri, 4 Jul 2025 12:10:45 -0700 Subject: [PATCH 130/130] Fix wrong GENIV WB on too_complex Ractor traversal WBCHECK ERROR: Missed write barrier detected! Parent object: 0x7c4a5f1f66c0 (wb_protected: true) rb_obj_info_dump: 0x00007c4a5f1f66c0 T_IMEMO/ Reference counts - snapshot: 2, writebarrier: 0, current: 2, missed: 1 Missing reference to: 0x7b6a5f2f7010 rb_obj_info_dump: 0x00007b6a5f2f7010 T_ARRAY/Array [E ] len: 1 (embed) --- bootstraptest/test_ractor.rb | 54 ++++++++++++++++++++++++++++++++++++ ractor.c | 2 +- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 834c7ceebb04b5..461c2c39549193 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1923,6 +1923,60 @@ def ==(o) roundtripped_obj.instance_variable_get(:@array) == [1] ? :ok : roundtripped_obj } +# move object with many generic ivars +assert_equal 'ok', %q{ + ractor = Ractor.new { Ractor.receive } + obj = Array.new(10, 42) + 0.upto(300) do |i| + obj.instance_variable_set(:"@array#{i}", [i]) + end + + ractor.send(obj, move: true) + roundtripped_obj = ractor.value + roundtripped_obj.instance_variable_get(:@array1) == [1] ? :ok : roundtripped_obj +} + +# move object with complex generic ivars +assert_equal 'ok', %q{ + # Make Array too_complex + 30.times { |i| [].instance_variable_set(:"@complex#{i}", 1) } + + ractor = Ractor.new { Ractor.receive } + obj = Array.new(10, 42) + obj.instance_variable_set(:@array1, [1]) + + ractor.send(obj, move: true) + roundtripped_obj = ractor.value + roundtripped_obj.instance_variable_get(:@array1) == [1] ? :ok : roundtripped_obj +} + +# copy object with complex generic ivars +assert_equal 'ok', %q{ + # Make Array too_complex + 30.times { |i| [].instance_variable_set(:"@complex#{i}", 1) } + + ractor = Ractor.new { Ractor.receive } + obj = Array.new(10, 42) + obj.instance_variable_set(:@array1, [1]) + + ractor.send(obj) + roundtripped_obj = ractor.value + roundtripped_obj.instance_variable_get(:@array1) == [1] ? :ok : roundtripped_obj +} + +# copy object with many generic ivars +assert_equal 'ok', %q{ + ractor = Ractor.new { Ractor.receive } + obj = Array.new(10, 42) + 0.upto(300) do |i| + obj.instance_variable_set(:"@array#{i}", [i]) + end + + ractor.send(obj) + roundtripped_obj = ractor.value + roundtripped_obj.instance_variable_get(:@array1) == [1] ? :ok : roundtripped_obj +} + # moved composite types move their non-shareable parts properly assert_equal 'ok', %q{ k, v = String.new("key"), String.new("value") diff --git a/ractor.c b/ractor.c index a1ce7967c8faba..5e4d10e8c8dc64 100644 --- a/ractor.c +++ b/ractor.c @@ -1667,7 +1667,7 @@ obj_traverse_replace_i(VALUE obj, struct obj_traverse_replace_data *data) struct obj_traverse_replace_callback_data d = { .stop = false, .data = data, - .src = obj, + .src = fields_obj, }; rb_st_foreach_with_replace( rb_imemo_fields_complex_tbl(fields_obj),