From 343619c93ca4d590189ca154e66a67776601aa26 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 16 Jul 2025 15:17:38 -0400 Subject: [PATCH 01/30] ZJIT: Remove dead have_two_fixnums function (#13913) --- zjit/src/profile.rs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index 2d0c7a4b9d3cb0..e5ec874f2aa9d8 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -1,7 +1,7 @@ // We use the YARV bytecode constants which have a CRuby-style name #![allow(non_upper_case_globals)] -use crate::{cruby::*, gc::get_or_create_iseq_payload, hir_type::{types::{Empty, Fixnum}, Type}, options::get_option}; +use crate::{cruby::*, gc::get_or_create_iseq_payload, hir_type::{types::{Empty}, Type}, options::get_option}; /// Ephemeral state for profiling runtime information struct Profiler { @@ -113,14 +113,6 @@ impl IseqProfile { self.opnd_types.get(insn_idx).map(|v| &**v) } - /// Return true if top-two stack operands are Fixnums - pub fn have_two_fixnums(&self, insn_idx: usize) -> bool { - match self.get_operand_types(insn_idx) { - Some([left, right]) => left.is_subtype(Fixnum) && right.is_subtype(Fixnum), - _ => false, - } - } - /// Run a given callback with every object in IseqProfile pub fn each_object(&self, callback: impl Fn(VALUE)) { for types in &self.opnd_types { From 900eb04853be06c3293bd77c699edfd4f9273d69 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 16 Jul 2025 15:50:33 -0400 Subject: [PATCH 02/30] ZJIT: Split shift with immediate operand (#13914) Fix https://github.com/Shopify/ruby/issues/627 --- test/ruby/test_zjit.rb | 10 ++++++++++ zjit/src/backend/arm64/mod.rs | 7 +------ zjit/src/backend/x86_64/mod.rs | 4 ++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/test/ruby/test_zjit.rb b/test/ruby/test_zjit.rb index 299600eef95d53..0f83a75db7c53e 100644 --- a/test/ruby/test_zjit.rb +++ b/test/ruby/test_zjit.rb @@ -382,6 +382,16 @@ def test(a, b) = a | b }, call_threshold: 2, insns: [:opt_or] end + def test_fixnum_mul + assert_compiles '12', %q{ + C = 3 + def test(n) = C * n + test(4) + test(4) + test(4) + }, call_threshold: 2, insns: [:opt_mult] + end + def test_opt_not assert_compiles('[true, true, false]', <<~RUBY, insns: [:opt_not]) def test(obj) = !obj diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 149d09813b2018..e97c06414d4862 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -667,12 +667,7 @@ impl Assembler Insn::URShift { opnd, .. } => { // The operand must be in a register, so // if we get anything else we need to load it first. - let opnd0 = match opnd { - Opnd::Mem(_) => split_load_operand(asm, *opnd), - _ => *opnd - }; - - *opnd = opnd0; + *opnd = split_load_operand(asm, *opnd); asm.push_insn(insn); }, Insn::Store { dest, src } => { diff --git a/zjit/src/backend/x86_64/mod.rs b/zjit/src/backend/x86_64/mod.rs index 1c905a475ca9ed..816f38db37f133 100644 --- a/zjit/src/backend/x86_64/mod.rs +++ b/zjit/src/backend/x86_64/mod.rs @@ -249,8 +249,8 @@ impl Assembler *opnd = asm.load(*opnd); } }, - // We have to load memory operands to avoid corrupting them - Opnd::Mem(_) | Opnd::Reg(_) => { + // We have to load non-reg operands to avoid corrupting them + Opnd::Mem(_) | Opnd::Reg(_) | Opnd::UImm(_) | Opnd::Imm(_) => { *opnd = asm.load(*opnd); }, _ => {} From abc8745fc34bfd366624da4e7d97a57a29c05529 Mon Sep 17 00:00:00 2001 From: Erik Berlin Date: Thu, 3 Jul 2025 21:23:51 -0400 Subject: [PATCH 03/30] Avoid second RHASH_AR_TABLE_REF lookup --- hash.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hash.c b/hash.c index 84996358173542..182b277f525c8a 100644 --- a/hash.c +++ b/hash.c @@ -882,8 +882,9 @@ ar_general_foreach(VALUE hash, st_foreach_check_callback_func *func, st_update_c if (replace) { (*replace)(&key, &val, arg, TRUE); - // TODO: pair should be same as pair before. - pair = RHASH_AR_TABLE_REF(hash, i); + // Pair should not have moved + HASH_ASSERT(pair == RHASH_AR_TABLE_REF(hash, i)); + pair->key = (VALUE)key; pair->val = (VALUE)val; } From e64a9e392ecfde5f46e8c9c12758e9db3144f6cc Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Sat, 12 Jul 2025 14:50:28 -0400 Subject: [PATCH 04/30] Alphabetize concurrent_set.h in string.c --- string.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/string.c b/string.c index 97a67a8030d878..fefe05073d7427 100644 --- a/string.c +++ b/string.c @@ -28,6 +28,7 @@ #include "internal/array.h" #include "internal/compar.h" #include "internal/compilers.h" +#include "internal/concurrent_set.h" #include "internal/encoding.h" #include "internal/error.h" #include "internal/gc.h" @@ -35,7 +36,6 @@ #include "internal/numeric.h" #include "internal/object.h" #include "internal/proc.h" -#include "internal/concurrent_set.h" #include "internal/re.h" #include "internal/sanitizers.h" #include "internal/string.h" From 4403cb2705cad11d464eaaffa97b784a140463a3 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 16 Jul 2025 17:17:01 -0400 Subject: [PATCH 05/30] ZJIT: Eagerly infer types of rewritten Const instructions (#13917) This helps us rewrite more SendWithoutBlock into SendWithoutBlockDirect. --- zjit/src/hir.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 1c6b55a9ff3a21..ce89501afe4a2a 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1546,6 +1546,7 @@ impl Function { // referenced after the PatchPoint. self.push_insn(block, Insn::PatchPoint { invariant: Invariant::StableConstantNames { idlist }, state }); let replacement = self.push_insn(block, Insn::Const { val: Const::Value(unsafe { (*ice).value }) }); + self.insn_types[replacement.0] = self.infer_type(replacement); self.make_equal_to(insn_id, replacement); } Insn::ObjToString { val, call_info, cd, state, .. } => { @@ -6327,6 +6328,26 @@ mod opt_tests { "#]]); } + #[test] + fn send_direct_to_module() { + eval(" + module M; end + def test = M.class + test + test + "); + assert_optimized_method_hir("test", expect![[r#" + fn test@:3: + bb0(v0:BasicObject): + PatchPoint SingleRactorMode + PatchPoint StableConstantNames(0x1000, M) + v9:ModuleExact[VALUE(0x1008)] = Const Value(VALUE(0x1008)) + PatchPoint MethodRedefined(Module@0x1010, class@0x1018) + v11:BasicObject = SendWithoutBlockDirect v9, :class (0x1020) + Return v11 + "#]]); + } + #[test] fn dont_specialize_call_to_iseq_with_opt() { eval(" From d207efecec6d133634b0d7378da66cff4de8c720 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 16 Jul 2025 21:30:09 +0100 Subject: [PATCH 06/30] ZJIT: Remove unneeded test exclusions After several ZJIT fix PRs, the number of failing/crashing tests for ZJIT has dropped significantly. This removes the unneeded test exclusions. --- .github/workflows/zjit-ubuntu.yml | 2 +- test/.excludes-zjit/TestArgf.rb | 1 - test/.excludes-zjit/TestArray.rb | 1 - test/.excludes-zjit/TestBignum.rb | 1 - test/.excludes-zjit/TestEncoding.rb | 1 - test/.excludes-zjit/TestEnumerator.rb | 1 - test/.excludes-zjit/TestEnv.rb | 1 - test/.excludes-zjit/TestInteger.rb | 1 - test/.excludes-zjit/TestIntegerComb.rb | 2 -- test/.excludes-zjit/TestMemoryView.rb | 8 ------- test/.excludes-zjit/TestPack.rb | 32 -------------------------- test/.excludes-zjit/TestProc.rb | 9 +++----- test/.excludes-zjit/TestRactor.rb | 1 - test/.excludes-zjit/TestRegexp.rb | 3 --- test/.excludes-zjit/TestSprintfComb.rb | 1 - test/.excludes-zjit/TestString.rb | 1 - test/.excludes-zjit/TestString2.rb | 1 - test/.excludes-zjit/TestVariable.rb | 1 - 18 files changed, 4 insertions(+), 64 deletions(-) delete mode 100644 test/.excludes-zjit/TestArgf.rb delete mode 100644 test/.excludes-zjit/TestArray.rb delete mode 100644 test/.excludes-zjit/TestBignum.rb delete mode 100644 test/.excludes-zjit/TestEncoding.rb delete mode 100644 test/.excludes-zjit/TestEnumerator.rb delete mode 100644 test/.excludes-zjit/TestEnv.rb delete mode 100644 test/.excludes-zjit/TestInteger.rb delete mode 100644 test/.excludes-zjit/TestIntegerComb.rb delete mode 100644 test/.excludes-zjit/TestMemoryView.rb delete mode 100644 test/.excludes-zjit/TestPack.rb delete mode 100644 test/.excludes-zjit/TestRactor.rb delete mode 100644 test/.excludes-zjit/TestSprintfComb.rb delete mode 100644 test/.excludes-zjit/TestString.rb delete mode 100644 test/.excludes-zjit/TestString2.rb delete mode 100644 test/.excludes-zjit/TestVariable.rb diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 3e2db58f72b959..de3e98d3585263 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -42,7 +42,7 @@ jobs: - test_task: 'zjit-test-all' configure: '--enable-zjit=dev' - testopts: '--seed=11831' + testopts: '--seed=18140' - test_task: 'btest' configure: '--enable-zjit=dev' diff --git a/test/.excludes-zjit/TestArgf.rb b/test/.excludes-zjit/TestArgf.rb deleted file mode 100644 index 07f305da5c7038..00000000000000 --- a/test/.excludes-zjit/TestArgf.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(:test_skip_in_each_byte, 'Test fails with ZJIT') diff --git a/test/.excludes-zjit/TestArray.rb b/test/.excludes-zjit/TestArray.rb deleted file mode 100644 index b2ca8c67f77f16..00000000000000 --- a/test/.excludes-zjit/TestArray.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(/test_/, 'Tests make ZJIT panic') diff --git a/test/.excludes-zjit/TestBignum.rb b/test/.excludes-zjit/TestBignum.rb deleted file mode 100644 index 66b0cc0c1be2a5..00000000000000 --- a/test/.excludes-zjit/TestBignum.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(:test_quad_pack, 'Test fails with ZJIT') diff --git a/test/.excludes-zjit/TestEncoding.rb b/test/.excludes-zjit/TestEncoding.rb deleted file mode 100644 index 2bb8e8df99fd2a..00000000000000 --- a/test/.excludes-zjit/TestEncoding.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(/test_ractor/, "Tests make ZJIT panic") diff --git a/test/.excludes-zjit/TestEnumerator.rb b/test/.excludes-zjit/TestEnumerator.rb deleted file mode 100644 index 2089cc15bb5c4b..00000000000000 --- a/test/.excludes-zjit/TestEnumerator.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(:test_size_for_loops, 'Test crashes on Ubuntu with ZJIT') diff --git a/test/.excludes-zjit/TestEnv.rb b/test/.excludes-zjit/TestEnv.rb deleted file mode 100644 index f52bdf6d30d6c6..00000000000000 --- a/test/.excludes-zjit/TestEnv.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(/test_/, 'Multiple tests make ZJIT panic') diff --git a/test/.excludes-zjit/TestInteger.rb b/test/.excludes-zjit/TestInteger.rb deleted file mode 100644 index a75fd5f1ea2962..00000000000000 --- a/test/.excludes-zjit/TestInteger.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(:test_ceildiv, 'Test crashes on Ubuntu with ZJIT') diff --git a/test/.excludes-zjit/TestIntegerComb.rb b/test/.excludes-zjit/TestIntegerComb.rb deleted file mode 100644 index 4ab5ade4c46a76..00000000000000 --- a/test/.excludes-zjit/TestIntegerComb.rb +++ /dev/null @@ -1,2 +0,0 @@ -exclude(:test_pack_utf8, 'Test crashes with ZJIT') -exclude(:test_pack_ber, 'Test fails with ZJIT') diff --git a/test/.excludes-zjit/TestMemoryView.rb b/test/.excludes-zjit/TestMemoryView.rb deleted file mode 100644 index dfc9b110e24d89..00000000000000 --- a/test/.excludes-zjit/TestMemoryView.rb +++ /dev/null @@ -1,8 +0,0 @@ -exclude(:test_rb_memory_view_get_item_pointer_multiple_members, 'Test crashes with ZJIT') -exclude(:test_rb_memory_view_extract_item_members_doble, 'Test crashes with ZJIT') -exclude(:test_rb_memory_view_extract_item_members_float_endianness, 'Test crashes with ZJIT') -exclude(:test_rb_memory_view_get_item_pointer_single_member, 'Test crashes with ZJIT') -exclude(:test_rb_memory_view_extract_item_members_doble_endianness, 'Test crashes with ZJIT') - -exclude(:test_rb_memory_view_extract_item_members_endianness, 'Test fails with ZJIT') -exclude(:test_rb_memory_view_extract_item_members, 'Test fails with ZJIT') diff --git a/test/.excludes-zjit/TestPack.rb b/test/.excludes-zjit/TestPack.rb deleted file mode 100644 index d505b1c232befe..00000000000000 --- a/test/.excludes-zjit/TestPack.rb +++ /dev/null @@ -1,32 +0,0 @@ -exclude(:test_ascii_incompatible, 'Test crashes with ZJIT') -exclude(:test_pack_U, 'Test crashes with ZJIT') -exclude(:test_unpack_garbage, 'Test crashes with ZJIT') -exclude(:test_pack_garbage, 'Test crashes with ZJIT') -exclude(:test_pack, 'Test crashes with ZJIT') - -exclude(:test_pack_unpack_lL, 'Test fails with ZJIT') -exclude(:test_pack_unpack_x, 'Test fails with ZJIT') -exclude(:test_unpack_n, 'Test fails with ZJIT') -exclude(:test_pack_N, 'Test fails with ZJIT') -exclude(:test_integer_endian, 'Test fails with ZJIT') -exclude(:test_pack_unpack_m0, 'Test fails with ZJIT') -exclude(:test_pack_p2, 'Test fails with ZJIT') -exclude(:test_pack_unpack_X, 'Test fails with ZJIT') -exclude(:test_pack_unpack_nN, 'Test fails with ZJIT') -exclude(:test_unpack1, 'Test fails with ZJIT') -exclude(:test_pack_n, 'Test fails with ZJIT') -exclude(:test_pack_unpack_percent, 'Test fails with ZJIT') -exclude(:test_unpack_N, 'Test fails with ZJIT') -exclude(:test_pack_unpack_cC, 'Test fails with ZJIT') -exclude(:test_pack_unpack_vV, 'Test fails with ZJIT') -exclude(:test_format_string_modified, 'Test fails with ZJIT') -exclude(:test_pack_unpack_atmark, 'Test fails with ZJIT') -exclude(:test_pack_unpack_iI, 'Test fails with ZJIT') -exclude(:test_illegal_bang, 'Test fails with ZJIT') -exclude(:test_pack_unpack_U, 'Test fails with ZJIT') -exclude(:test_pack_unpack_fdeEgG, 'Test fails with ZJIT') -exclude(:test_invalid_warning, 'Test fails with ZJIT') -exclude(:test_short_string, 'Test fails with ZJIT') -exclude(:test_pack_unpack_sS, 'Test fails with ZJIT') -exclude(:test_comment, 'Test fails with ZJIT') -exclude(:test_pack_unpack_w, 'Test fails with ZJIT') diff --git a/test/.excludes-zjit/TestProc.rb b/test/.excludes-zjit/TestProc.rb index 2667c3aa0bc373..aa6abbecbe3de6 100644 --- a/test/.excludes-zjit/TestProc.rb +++ b/test/.excludes-zjit/TestProc.rb @@ -1,6 +1,3 @@ -exclude(/test_/, 'Tests make ZJIT panic') - -# exclude(:test_proc_args_pos_rest_block, 'Test crashes with ZJIT') -# exclude(:test_proc_args_rest_post_block, 'Test crashes with ZJIT') - -# exclude(:test_binding_receiver, 'Test fails with ZJIT') +exclude(:test_proc_args_pos_rest_block, 'Test crashes with ZJIT') +exclude(:test_proc_args_rest_post_block, 'Test crashes with ZJIT') +exclude(:test_binding_receiver, 'Test fails with ZJIT') diff --git a/test/.excludes-zjit/TestRactor.rb b/test/.excludes-zjit/TestRactor.rb deleted file mode 100644 index b2ca8c67f77f16..00000000000000 --- a/test/.excludes-zjit/TestRactor.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(/test_/, 'Tests make ZJIT panic') diff --git a/test/.excludes-zjit/TestRegexp.rb b/test/.excludes-zjit/TestRegexp.rb index 3b396b1ca7a401..e344b6d803a905 100644 --- a/test/.excludes-zjit/TestRegexp.rb +++ b/test/.excludes-zjit/TestRegexp.rb @@ -1,4 +1 @@ -exclude(:test_inspect, 'Test fails with ZJIT') -exclude(:test_quote, 'Test fails with ZJIT') exclude(:test_union, 'Test fails with ZJIT') -exclude(:test_unescape, 'Test fails with ZJIT') diff --git a/test/.excludes-zjit/TestSprintfComb.rb b/test/.excludes-zjit/TestSprintfComb.rb deleted file mode 100644 index 321633f686a923..00000000000000 --- a/test/.excludes-zjit/TestSprintfComb.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(/test_/, 'Test fails with ZJIT') diff --git a/test/.excludes-zjit/TestString.rb b/test/.excludes-zjit/TestString.rb deleted file mode 100644 index 5d0f95e9389a4c..00000000000000 --- a/test/.excludes-zjit/TestString.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(:test_to_f, 'Test crashes with ZJIT') diff --git a/test/.excludes-zjit/TestString2.rb b/test/.excludes-zjit/TestString2.rb deleted file mode 100644 index 5d0f95e9389a4c..00000000000000 --- a/test/.excludes-zjit/TestString2.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(:test_to_f, 'Test crashes with ZJIT') diff --git a/test/.excludes-zjit/TestVariable.rb b/test/.excludes-zjit/TestVariable.rb deleted file mode 100644 index b2ca8c67f77f16..00000000000000 --- a/test/.excludes-zjit/TestVariable.rb +++ /dev/null @@ -1 +0,0 @@ -exclude(/test_/, 'Tests make ZJIT panic') From 616df508c796e98a6ca112627103c2e833c51619 Mon Sep 17 00:00:00 2001 From: Stan Lo Date: Wed, 16 Jul 2025 21:42:12 +0100 Subject: [PATCH 07/30] ZJIT: Add ZJIT test exclusion files to jit team review targets --- .github/auto_request_review.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/auto_request_review.yml b/.github/auto_request_review.yml index c4c94681f0e387..264e6ef1774018 100644 --- a/.github/auto_request_review.yml +++ b/.github/auto_request_review.yml @@ -10,6 +10,7 @@ files: 'zjit/src/cruby_bindings.inc.rs': [] 'doc/zjit*': [team:jit] 'test/ruby/test_zjit*': [team:jit] + 'test/.excludes-zjit/*': [team:jit] 'defs/jit.mk': [team:jit] options: ignore_draft: true From 15cf72dadea5cb3c96d1dca96c57308bce680a3b Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Wed, 16 Jul 2025 18:12:19 -0400 Subject: [PATCH 08/30] ZJIT: Check if BOP is redefined before rewriting (#13916) Fix https://github.com/Shopify/ruby/issues/592 --- zjit/src/hir.rs | 94 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index ce89501afe4a2a..fa72b569b49c57 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1382,6 +1382,11 @@ impl Function { } fn try_rewrite_fixnum_op(&mut self, block: BlockId, orig_insn_id: InsnId, f: &dyn Fn(InsnId, InsnId) -> Insn, bop: u32, left: InsnId, right: InsnId, state: InsnId) { + if !unsafe { rb_BASIC_OP_UNREDEFINED_P(bop, INTEGER_REDEFINED_OP_FLAG) } { + // If the basic operation is already redefined, we cannot optimize it. + self.push_insn_id(block, orig_insn_id); + return; + } if self.arguments_likely_fixnums(left, right, state) { if bop == BOP_NEQ { // For opt_neq, the interpreter checks that both neq and eq are unchanged. @@ -1399,6 +1404,11 @@ impl Function { } fn rewrite_if_frozen(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId, klass: u32, bop: u32, state: InsnId) { + if !unsafe { rb_BASIC_OP_UNREDEFINED_P(bop, klass) } { + // If the basic operation is already redefined, we cannot optimize it. + self.push_insn_id(block, orig_insn_id); + return; + } let self_type = self.type_of(self_val); if let Some(obj) = self_type.ruby_object() { if obj.is_frozen() { @@ -1431,6 +1441,11 @@ impl Function { } fn try_rewrite_aref(&mut self, block: BlockId, orig_insn_id: InsnId, self_val: InsnId, idx_val: InsnId, state: InsnId) { + if !unsafe { rb_BASIC_OP_UNREDEFINED_P(BOP_AREF, ARRAY_REDEFINED_OP_FLAG) } { + // If the basic operation is already redefined, we cannot optimize it. + self.push_insn_id(block, orig_insn_id); + return; + } let self_type = self.type_of(self_val); let idx_type = self.type_of(idx_val); if self_type.is_subtype(types::ArrayExact) { @@ -2667,6 +2682,11 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { break; // End the block }, }; + if !unsafe { rb_BASIC_OP_UNREDEFINED_P(bop, ARRAY_REDEFINED_OP_FLAG) } { + // If the basic operation is already redefined, we cannot optimize it. + fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::PatchPoint(Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop }) }); + break; // End the block + } fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::BOPRedefined { klass: ARRAY_REDEFINED_OP_FLAG, bop }, state: exit_id }); state.stack_push(fun.push_insn(block, insn)); } @@ -5590,6 +5610,25 @@ mod opt_tests { "#]]); } + #[test] + fn test_dont_optimize_fixnum_add_if_redefined() { + eval(" + class Integer + def +(other) + 100 + end + end + def test(a, b) = a + b + test(1,2); test(3,4) + "); + assert_optimized_method_hir("test", expect![[r#" + fn test@:7: + bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject): + v5:BasicObject = SendWithoutBlock v1, :+, v2 + Return v5 + "#]]); + } + #[test] fn test_optimize_send_into_fixnum_add_both_profiled() { eval(" @@ -6628,6 +6667,23 @@ mod opt_tests { "#]]); } + #[test] + fn test_dont_optimize_hash_freeze_if_redefined() { + eval(" + class Hash + def freeze; end + end + def test = {}.freeze + "); + assert_optimized_method_hir("test", expect![[r#" + fn test@:5: + bb0(v0:BasicObject): + v3:HashExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v4:BasicObject = SendWithoutBlock v3, :freeze + Return v4 + "#]]); + } + #[test] fn test_elide_freeze_with_refrozen_hash() { eval(" @@ -6973,6 +7029,44 @@ mod opt_tests { "#]]); } + #[test] + fn test_dont_optimize_array_aref_if_redefined() { + eval(r##" + class Array + def [](index); end + end + def test = [4,5,6].freeze[10] + "##); + assert_optimized_method_hir("test", expect![[r#" + fn test@:5: + bb0(v0:BasicObject): + v3:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + PatchPoint BOPRedefined(ARRAY_REDEFINED_OP_FLAG, BOP_FREEZE) + v5:Fixnum[10] = Const Value(10) + v7:BasicObject = SendWithoutBlock v3, :[], v5 + Return v7 + "#]]); + } + + #[test] + fn test_dont_optimize_array_max_if_redefined() { + eval(r##" + class Array + def max = 10 + end + def test = [4,5,6].max + "##); + assert_optimized_method_hir("test", expect![[r#" + fn test@:5: + bb0(v0:BasicObject): + v2:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000)) + v4:ArrayExact = ArrayDup v2 + PatchPoint MethodRedefined(Array@0x1008, max@0x1010) + v9:BasicObject = SendWithoutBlockDirect v4, :max (0x1018) + Return v9 + "#]]); + } + #[test] fn test_set_type_from_constant() { eval(" From 571a8d2753ab252b45afba862fc5917c1b8a1e09 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 16 Jul 2025 15:59:32 -0700 Subject: [PATCH 09/30] YJIT: Side-exit on String#dup when it's not leaf (#13921) * YJIT: Side-exit on String#dup when it's not leaf * Use an enum instead of a macro for bindgen --- Cargo.toml | 3 +-- shape.h | 4 +++- yjit/bindgen/src/main.rs | 2 +- yjit/src/codegen.rs | 7 ++++++- yjit/src/cruby_bindings.inc.rs | 3 ++- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6f61e5f95cfd35..3f373fdace9cbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,8 +22,7 @@ path = "jit.rs" [features] disasm = ["yjit?/disasm", "zjit?/disasm"] -# TODO(GH-642) Turning this on trips a btest failure. -runtime_checks = [] # ["yjit?/runtime_checks", "zjit?/runtime_checks"] +runtime_checks = ["yjit?/runtime_checks", "zjit?/runtime_checks"] yjit = [ "dep:yjit" ] zjit = [ "dep:zjit" ] diff --git a/shape.h b/shape.h index 63d5534d463dce..a418dc78218693 100644 --- a/shape.h +++ b/shape.h @@ -48,7 +48,9 @@ enum shape_id_fl_type { // This masks allows to check if a shape_id contains any ivar. // It rely on ROOT_SHAPE_WITH_OBJ_ID==1. -#define SHAPE_ID_HAS_IVAR_MASK (SHAPE_ID_FL_TOO_COMPLEX | (SHAPE_ID_OFFSET_MASK - 1)) +enum { + SHAPE_ID_HAS_IVAR_MASK = SHAPE_ID_FL_TOO_COMPLEX | (SHAPE_ID_OFFSET_MASK - 1), +}; // The interpreter doesn't care about frozen status or slot size when reading ivars. // So we normalize shape_id by clearing these bits to improve cache hits. diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs index a0446ad17b6a91..7c8ce7bce5c69d 100644 --- a/yjit/bindgen/src/main.rs +++ b/yjit/bindgen/src/main.rs @@ -103,6 +103,7 @@ fn main() { .allowlist_function("rb_yjit_shape_capacity") .allowlist_function("rb_yjit_shape_index") .allowlist_var("SHAPE_ID_NUM_BITS") + .allowlist_var("SHAPE_ID_HAS_IVAR_MASK") // From ruby/internal/intern/object.h .allowlist_function("rb_obj_is_kind_of") @@ -228,7 +229,6 @@ fn main() { .allowlist_function("rb_obj_as_string_result") .allowlist_function("rb_str_byte_substr") .allowlist_function("rb_str_substr_two_fixnums") - .allowlist_function("rb_str_dup_m") // From include/ruby/internal/intern/parse.h .allowlist_function("rb_backref_get") diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs index 66932fd46cc961..b8b15adc8b44a8 100644 --- a/yjit/src/codegen.rs +++ b/yjit/src/codegen.rs @@ -6278,9 +6278,14 @@ fn jit_rb_str_dup( let recv_opnd = asm.stack_pop(1); let recv_opnd = asm.load(recv_opnd); + let shape_id_offset = unsafe { rb_shape_id_offset() }; + let shape_opnd = Opnd::mem(64, recv_opnd, shape_id_offset); + asm.test(shape_opnd, Opnd::UImm(SHAPE_ID_HAS_IVAR_MASK as u64)); + asm.jnz(Target::side_exit(Counter::send_str_dup_exivar)); + // Call rb_str_dup let stack_ret = asm.stack_push(Type::CString); - let ret_opnd = asm.ccall(rb_str_dup_m as *const u8, vec![recv_opnd]); + let ret_opnd = asm.ccall(rb_str_dup as *const u8, vec![recv_opnd]); asm.mov(stack_ret, ret_opnd); true diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs index e36b6f9f5f73c7..9b871c9e7c88ed 100644 --- a/yjit/src/cruby_bindings.inc.rs +++ b/yjit/src/cruby_bindings.inc.rs @@ -689,6 +689,8 @@ pub const VM_ENV_FLAG_ISOLATED: vm_frame_env_flags = 16; pub type vm_frame_env_flags = u32; pub type attr_index_t = u16; pub type shape_id_t = u32; +pub const SHAPE_ID_HAS_IVAR_MASK: _bindgen_ty_37 = 134742014; +pub type _bindgen_ty_37 = u32; #[repr(C)] pub struct rb_cvar_class_tbl_entry { pub index: u32, @@ -1120,7 +1122,6 @@ extern "C" { pub fn rb_gvar_set(arg1: ID, arg2: VALUE) -> VALUE; pub fn rb_ensure_iv_list_size(obj: VALUE, current_len: u32, newsize: u32); pub fn rb_vm_barrier(); - pub fn rb_str_dup_m(str_: VALUE) -> VALUE; pub fn rb_str_byte_substr(str_: VALUE, beg: VALUE, len: VALUE) -> VALUE; pub fn rb_str_substr_two_fixnums( str_: VALUE, From 960fae438bf74d220b7795780f2a6461a9a30618 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 16 Jul 2025 19:25:37 -0400 Subject: [PATCH 10/30] ZJIT: Add missing write barrier in profiling (GH-13922) Fixes `TestZJIT::test_require_rubygems`. It was crashing locally due to false collection of a live object. See . Co-authored-by: Max Bernstein Co-authored-by: Takashi Kokubun Co-authored-by: Stan Lo --- zjit/bindgen/src/main.rs | 1 + zjit/src/cruby_bindings.inc.rs | 1 + zjit/src/profile.rs | 4 ++++ 3 files changed, 6 insertions(+) diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index dd167e9eb07ac3..cb15c3657e37e1 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -167,6 +167,7 @@ fn main() { .allowlist_function("rb_gc_mark_movable") .allowlist_function("rb_gc_location") .allowlist_function("rb_gc_writebarrier") + .allowlist_function("rb_gc_writebarrier_remember") // VALUE variables for Ruby class objects // From include/ruby/internal/globals.h diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 44e544e972f58f..9c860ab5092d6f 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -852,6 +852,7 @@ unsafe extern "C" { ) -> *const rb_callable_method_entry_t; pub fn rb_obj_info(obj: VALUE) -> *const ::std::os::raw::c_char; pub fn rb_ec_stack_check(ec: *mut rb_execution_context_struct) -> ::std::os::raw::c_int; + pub fn rb_gc_writebarrier_remember(obj: VALUE); pub fn rb_shape_id_offset() -> i32; pub fn rb_obj_shape_id(obj: VALUE) -> shape_id_t; pub fn rb_shape_get_iv_index(shape_id: shape_id_t, id: ID, value: *mut attr_index_t) -> bool; diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index e5ec874f2aa9d8..4b5c7b60d40384 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -89,6 +89,10 @@ fn profile_operands(profiler: &mut Profiler, profile: &mut IseqProfile, n: usize let opnd_type = Type::from_value(profiler.peek_at_stack((n - i - 1) as isize)); types[i] = types[i].union(opnd_type); } + // In the loop above, we probably added a new reference to the profile through + // the VALUE in Type. It's messy and relatively slow to conditionally run a + // write barrier for each Type, so just remember to re-mark the iseq. + unsafe { rb_gc_writebarrier_remember(profiler.iseq.into()) }; } #[derive(Debug)] From 116509670a3224abad3dc6c97142a41c27b2b9a9 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 16 Jul 2025 12:13:41 -0400 Subject: [PATCH 11/30] ZJIT: Define make recipes only when configured This gives a better signal when say you try to run `make zjit-test` on a YJIT-only build. --- zjit/zjit.mk | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/zjit/zjit.mk b/zjit/zjit.mk index 047f4ab3a3cf50..212ae592abc818 100644 --- a/zjit/zjit.mk +++ b/zjit/zjit.mk @@ -1,5 +1,8 @@ # -*- mode: makefile-gmake; indent-tabs-mode: t -*- +# Put no definitions when ZJIT isn't configured +ifneq ($(ZJIT_SUPPORT),no) + # Show Cargo progress when doing `make V=1` CARGO_VERBOSE_0 = -q CARGO_VERBOSE_1 = @@ -13,6 +16,8 @@ ZJIT_SRC_FILES = $(wildcard \ $(top_srcdir)/zjit/src/*/*/*/*.rs \ ) +$(RUST_LIB): $(ZJIT_SRC_FILES) + # Because of Cargo cache, if the actual binary is not changed from the # previous build, the mtime is preserved as the cached file. # This means the target is not updated actually, and it will need to @@ -31,10 +36,6 @@ $(BUILD_ZJIT_LIBS): $(ZJIT_SRC_FILES) $(ZJIT_LIB_TOUCH) endif -ifneq ($(ZJIT_SUPPORT),no) -$(RUST_LIB): $(ZJIT_SRC_FILES) -endif - # By using ZJIT_BENCH_OPTS instead of RUN_OPTS, you can skip passing the options to `make install` ZJIT_BENCH_OPTS = $(RUN_OPTS) --enable-gems ZJIT_BENCH = benchmarks/railsbench/benchmark.rb @@ -110,4 +111,6 @@ libminiruby.a: miniruby$(EXEEXT) $(Q) $(AR) $(ARFLAGS) $@ $(MINIOBJS) $(COMMONOBJS) libminiruby: libminiruby.a -endif + +endif # ifneq ($(strip $(CARGO)), +endif # ifneq ($(ZJIT_SUPPORT),no) From 1a207650745f812fa834198e7bb15756f174ca35 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Wed, 16 Jul 2025 12:16:39 -0400 Subject: [PATCH 12/30] DRY up CARGO_VERBOSE for JITs --- defs/jit.mk | 6 ++++++ yjit/yjit.mk | 5 ----- zjit/zjit.mk | 5 ----- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/defs/jit.mk b/defs/jit.mk index 99f21fb51399bb..28d8f2da3a87f7 100644 --- a/defs/jit.mk +++ b/defs/jit.mk @@ -7,6 +7,12 @@ RUST_LIB_TOUCH = touch $@ ifneq ($(JIT_CARGO_SUPPORT),no) + +# Show Cargo progress when doing `make V=1` +CARGO_VERBOSE_0 = -q +CARGO_VERBOSE_1 = +CARGO_VERBOSE = $(CARGO_VERBOSE_$(V)) + # NOTE: MACOSX_DEPLOYMENT_TARGET to match `rustc --print deployment-target` to avoid the warning below. # ld: warning: object file (target/debug/libjit.a()) was built for # newer macOS version (15.2) than being linked (15.0) diff --git a/yjit/yjit.mk b/yjit/yjit.mk index 98e4ed319699a0..c6571ecbc80c54 100644 --- a/yjit/yjit.mk +++ b/yjit/yjit.mk @@ -1,10 +1,5 @@ # -*- mode: makefile-gmake; indent-tabs-mode: t -*- -# Show Cargo progress when doing `make V=1` -CARGO_VERBOSE_0 = -q -CARGO_VERBOSE_1 = -CARGO_VERBOSE = $(CARGO_VERBOSE_$(V)) - YJIT_SRC_FILES = $(wildcard \ $(top_srcdir)/yjit/Cargo.* \ $(top_srcdir)/yjit/src/*.rs \ diff --git a/zjit/zjit.mk b/zjit/zjit.mk index 212ae592abc818..9a018f1b1ac4cf 100644 --- a/zjit/zjit.mk +++ b/zjit/zjit.mk @@ -3,11 +3,6 @@ # Put no definitions when ZJIT isn't configured ifneq ($(ZJIT_SUPPORT),no) -# Show Cargo progress when doing `make V=1` -CARGO_VERBOSE_0 = -q -CARGO_VERBOSE_1 = -CARGO_VERBOSE = $(CARGO_VERBOSE_$(V)) - ZJIT_SRC_FILES = $(wildcard \ $(top_srcdir)/zjit/Cargo.* \ $(top_srcdir)/zjit/src/*.rs \ From 5239dc8a0b310a1de8f2b7a8ad96877d4c78e649 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 17 Jul 2025 08:38:57 +0900 Subject: [PATCH 13/30] Revert "Suppress occasional autoconf warnings" This reverts commit 32bfb61d349b49ddedb7d34d9e434063324aafcc, that requires autoconf 2.70's improved whitespace handling. It is too early for some platforms yet. Fix GH-13910 --- configure.ac | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/configure.ac b/configure.ac index 8e08b91edbbc32..07a6d823142853 100644 --- a/configure.ac +++ b/configure.ac @@ -2815,12 +2815,12 @@ AS_IF([test "$THREAD_MODEL" = pthread], [ ], [ AC_DEFINE(NON_SCALAR_THREAD_ID) ]) - AC_CHECK_FUNCS([sched_yield pthread_attr_setinheritsched - pthread_attr_get_np pthread_attr_getstack pthread_attr_getguardsize - pthread_get_stackaddr_np pthread_get_stacksize_np - thr_stksegment pthread_stackseg_np pthread_getthrds_np - pthread_condattr_setclock - pthread_setname_np pthread_set_name_np]) + AC_CHECK_FUNCS(sched_yield pthread_attr_setinheritsched \ + pthread_attr_get_np pthread_attr_getstack pthread_attr_getguardsize \ + pthread_get_stackaddr_np pthread_get_stacksize_np \ + thr_stksegment pthread_stackseg_np pthread_getthrds_np \ + pthread_condattr_setclock \ + pthread_setname_np pthread_set_name_np) AS_CASE(["$target_os"],[emscripten*],[ac_cv_func_pthread_sigmask=no],[AC_CHECK_FUNCS(pthread_sigmask)]) AS_CASE(["$target_os"],[aix*],[ac_cv_func_pthread_getattr_np=no],[AC_CHECK_FUNCS(pthread_getattr_np)]) set_current_thread_name= From c451f478e6469f02bc5767f8ce4d4a2af0ca14b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 15 Jul 2025 17:28:35 +0200 Subject: [PATCH 14/30] [rubygems/rubygems] Refactor remembered flag deprecation logic https://github.com/rubygems/rubygems/commit/88dd7d2d45 --- lib/bundler/cli.rb | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index bba60ddab4d3dc..8b7e5fb25eab5d 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -240,7 +240,7 @@ def install print_remembered_flag_deprecation("--system", "path.system", "true") if ARGV.include?("--system") - remembered_negative_flag_deprecation("no-deployment") + remembered_flag_deprecation("deployment", negative: true) require_relative "cli/install" Bundler.settings.temporary(no_install: false) do @@ -743,17 +743,10 @@ def warn_on_outdated_bundler nil end - def remembered_negative_flag_deprecation(name) - positive_name = name.gsub(/\Ano-/, "") - option = current_command.options[positive_name] - flag_name = "--no-" + option.switch_name.gsub(/\A--/, "") - - flag_deprecation(positive_name, flag_name, option) - end - - def remembered_flag_deprecation(name) + def remembered_flag_deprecation(name, negative: false) option = current_command.options[name] flag_name = option.switch_name + flag_name = "--no-" + flag_name.gsub(/\A--/, "") if negative flag_deprecation(name, flag_name, option) end From 249cf5397f5f40e8379bef24b62bc7e0401b8c03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 15 Jul 2025 20:56:24 +0200 Subject: [PATCH 15/30] [rubygems/rubygems] Remove unnecessary `flag_deprecation` method https://github.com/rubygems/rubygems/commit/d1f8e1c4ac --- lib/bundler/cli.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 8b7e5fb25eab5d..0ccd58e1793126 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -748,10 +748,6 @@ def remembered_flag_deprecation(name, negative: false) flag_name = option.switch_name flag_name = "--no-" + flag_name.gsub(/\A--/, "") if negative - flag_deprecation(name, flag_name, option) - end - - def flag_deprecation(name, flag_name, option) name_index = ARGV.find {|arg| flag_name == arg.split("=")[0] } return unless name_index From 19d931b50d1799fcaeb4da06d9159610272dbb0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 15 Jul 2025 21:09:51 +0200 Subject: [PATCH 16/30] [rubygems/rubygems] Fix `bundle cache path=foo` not printing a deprecation message https://github.com/rubygems/rubygems/commit/0af03eea5d --- lib/bundler/cli.rb | 10 +++++---- spec/bundler/other/major_deprecation_spec.rb | 22 ++++++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 0ccd58e1793126..f2a3faf6def79c 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -414,7 +414,7 @@ def fund def cache print_remembered_flag_deprecation("--all", "cache_all", "true") if ARGV.include?("--all") - if ARGV.include?("--path") + if flag_passed?("--path") message = "The `--path` flag is deprecated because its semantics are unclear. " \ "Use `bundle config cache_path` to configure the path of your cache of gems, " \ @@ -747,9 +747,7 @@ def remembered_flag_deprecation(name, negative: false) option = current_command.options[name] flag_name = option.switch_name flag_name = "--no-" + flag_name.gsub(/\A--/, "") if negative - - name_index = ARGV.find {|arg| flag_name == arg.split("=")[0] } - return unless name_index + return unless flag_passed?(flag_name) value = options[name] value = value.join(" ").to_s if option.type == :array @@ -771,5 +769,9 @@ def print_remembered_flag_deprecation(flag_name, option_name, option_value) "#{option_value}`, and stop using this flag" Bundler::SharedHelpers.major_deprecation 2, message, removed_message: removed_message end + + def flag_passed?(name) + ARGV.any? {|arg| name == arg.split("=")[0] } + end end end diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index a0a19582449473..b3d7442f53ed84 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -199,6 +199,28 @@ pending "fails with a helpful error", bundler: "4" end + context "bundle cache --path=" do + before do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + bundle "cache --path=foo", raise_on_error: false + end + + it "should print a deprecation warning" do + expect(deprecations).to include( + "The `--path` flag is deprecated because its semantics are unclear. " \ + "Use `bundle config cache_path` to configure the path of your cache of gems, " \ + "and `bundle config path` to configure the path where your gems are installed, " \ + "and stop using this flag" + ) + end + + pending "fails with a helpful error", bundler: "4" + end + describe "bundle config" do describe "old list interface" do before do From 44dd27c430df6e39243639edb7b62cc5e257f1a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 15 Jul 2025 21:20:01 +0200 Subject: [PATCH 17/30] [rubygems/rubygems] Fix `bundle binstub --path=foo` not printing a deprecation warning Like others, it's a remembered option which we are deprecating in favor of configuration. https://github.com/rubygems/rubygems/commit/801d5dd943 --- lib/bundler/cli.rb | 6 ++++-- spec/bundler/other/major_deprecation_spec.rb | 22 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index f2a3faf6def79c..321a6247b8e088 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -331,6 +331,8 @@ def info(gem_name) method_option "all", type: :boolean, banner: "Install binstubs for all gems" method_option "all-platforms", type: :boolean, default: false, banner: "Install binstubs for all platforms" def binstubs(*gems) + remembered_flag_deprecation("path", option_name: "bin") + require_relative "cli/binstubs" Binstubs.new(options, gems).run end @@ -743,7 +745,7 @@ def warn_on_outdated_bundler nil end - def remembered_flag_deprecation(name, negative: false) + def remembered_flag_deprecation(name, negative: false, option_name: nil) option = current_command.options[name] flag_name = option.switch_name flag_name = "--no-" + flag_name.gsub(/\A--/, "") if negative @@ -753,7 +755,7 @@ def remembered_flag_deprecation(name, negative: false) value = value.join(" ").to_s if option.type == :array value = "'#{value}'" unless option.type == :boolean - print_remembered_flag_deprecation(flag_name, name.tr("-", "_"), value) + print_remembered_flag_deprecation(flag_name, option_name || name.tr("-", "_"), value) end def print_remembered_flag_deprecation(flag_name, option_name, option_value) diff --git a/spec/bundler/other/major_deprecation_spec.rb b/spec/bundler/other/major_deprecation_spec.rb index b3d7442f53ed84..51d490ea7247ac 100644 --- a/spec/bundler/other/major_deprecation_spec.rb +++ b/spec/bundler/other/major_deprecation_spec.rb @@ -155,6 +155,28 @@ pending "fails with a helpful error", bundler: "4" end + context "bundle binstubs --path=" do + before do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + bundle "binstubs myrack --path=binpath", raise_on_error: false + end + + it "should print a deprecation warning" do + expect(deprecations).to include( + "The `--path` flag is deprecated because it relies on being " \ + "remembered across bundler invocations, which bundler will no " \ + "longer do in future versions. Instead please use `bundle config set " \ + "bin 'binpath'`, and stop using this flag" + ) + end + + pending "fails with a helpful error", bundler: "4" + end + context "bundle cache --all" do before do install_gemfile <<-G From fb4f9030cbcc50ca22eead3f23ecb7c157ee2bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Mon, 14 Jul 2025 09:42:46 +0200 Subject: [PATCH 18/30] [rubygems/rubygems] Regenerate VCR cassettes https://github.com/rubygems/rubygems/commit/8530965b9e --- spec/bundler/realworld/slow_perf_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/bundler/realworld/slow_perf_spec.rb b/spec/bundler/realworld/slow_perf_spec.rb index 5b74b9ea9eb493..d813406e335143 100644 --- a/spec/bundler/realworld/slow_perf_spec.rb +++ b/spec/bundler/realworld/slow_perf_spec.rb @@ -30,7 +30,7 @@ G bundle "lock", env: { "DEBUG_RESOLVER" => "1" } - expect(out).to include("Solution found after 1 attempts") + expect(out).to include("Solution found after 2 attempts") end it "resolves big gemfile quickly" do From 45b53c0be13b921bcf16f1e7013c42c625a4a124 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Mon, 14 Jul 2025 10:10:22 +0200 Subject: [PATCH 19/30] [rubygems/rubygems] Remove some realworld specs These two specs need the version of Bundler to be faked to pass. In one of them we're doing it already, and in the other one it will be needed when bumping the major version of Bundler. So they are no longer truly realworld anymore. Realworld specs are hard to maintain, particularly the ones that depend on the version of Bundler itself, so I'm slowly moving away from them. I checked the changes that introduced them and I believe it's really unlikely that any of these catches turns out to be the only spec to catch a resolver regression. We've completely changed the resolution engine since and a lot of extra coverage has been added so I believe it's fine to let these two go. https://github.com/rubygems/rubygems/commit/a363f0168c --- spec/bundler/realworld/edgecases_spec.rb | 135 ----------------------- spec/bundler/realworld/slow_perf_spec.rb | 103 ----------------- 2 files changed, 238 deletions(-) diff --git a/spec/bundler/realworld/edgecases_spec.rb b/spec/bundler/realworld/edgecases_spec.rb index fb434aba7069b9..86b4c91a073685 100644 --- a/spec/bundler/realworld/edgecases_spec.rb +++ b/spec/bundler/realworld/edgecases_spec.rb @@ -63,141 +63,6 @@ def rubygems_version(name, requirement) expect(lockfile).to include(rubygems_version("activesupport", "~> 3.0")) end - it "is able to update a top-level dependency when there is a conflict on a shared transitive child" do - # from https://github.com/rubygems/bundler/issues/5031 - - pristine_system_gems "bundler-1.99.0" - - gemfile <<-G - source "https://rubygems.org" - gem 'rails', '~> 4.2.7.1' - gem 'paperclip', '~> 5.1.0' - G - - lockfile <<-L - GEM - remote: https://rubygems.org/ - specs: - actionmailer (4.2.7.1) - actionpack (= 4.2.7.1) - actionview (= 4.2.7.1) - activejob (= 4.2.7.1) - mail (~> 2.5, >= 2.5.4) - rails-dom-testing (~> 1.0, >= 1.0.5) - actionpack (4.2.7.1) - actionview (= 4.2.7.1) - activesupport (= 4.2.7.1) - rack (~> 1.6) - rack-test (~> 0.6.2) - rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (4.2.7.1) - activesupport (= 4.2.7.1) - builder (~> 3.1) - erubis (~> 2.7.0) - rails-dom-testing (~> 1.0, >= 1.0.5) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - activejob (4.2.7.1) - activesupport (= 4.2.7.1) - globalid (>= 0.3.0) - activemodel (4.2.7.1) - activesupport (= 4.2.7.1) - builder (~> 3.1) - activerecord (4.2.7.1) - activemodel (= 4.2.7.1) - activesupport (= 4.2.7.1) - arel (~> 6.0) - activesupport (4.2.7.1) - i18n (~> 0.7) - json (~> 1.7, >= 1.7.7) - minitest (~> 5.1) - thread_safe (~> 0.3, >= 0.3.4) - tzinfo (~> 1.1) - arel (6.0.3) - builder (3.2.2) - climate_control (0.0.3) - activesupport (>= 3.0) - cocaine (0.5.8) - climate_control (>= 0.0.3, < 1.0) - concurrent-ruby (1.0.2) - erubis (2.7.0) - globalid (0.3.7) - activesupport (>= 4.1.0) - i18n (0.7.0) - json (1.8.3) - loofah (2.0.3) - nokogiri (>= 1.5.9) - mail (2.6.4) - mime-types (>= 1.16, < 4) - mime-types (3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) - mimemagic (0.3.2) - mini_portile2 (2.1.0) - minitest (5.9.1) - nokogiri (1.6.8) - mini_portile2 (~> 2.1.0) - pkg-config (~> 1.1.7) - paperclip (5.1.0) - activemodel (>= 4.2.0) - activesupport (>= 4.2.0) - cocaine (~> 0.5.5) - mime-types - mimemagic (~> 0.3.0) - pkg-config (1.1.7) - rack (1.6.4) - rack-test (0.6.3) - rack (>= 1.0) - rails (4.2.7.1) - actionmailer (= 4.2.7.1) - actionpack (= 4.2.7.1) - actionview (= 4.2.7.1) - activejob (= 4.2.7.1) - activemodel (= 4.2.7.1) - activerecord (= 4.2.7.1) - activesupport (= 4.2.7.1) - bundler (>= 1.3.0, < 2.0) - railties (= 4.2.7.1) - sprockets-rails - rails-deprecated_sanitizer (1.0.3) - activesupport (>= 4.2.0.alpha) - rails-dom-testing (1.0.7) - activesupport (>= 4.2.0.beta, < 5.0) - nokogiri (~> 1.6.0) - rails-deprecated_sanitizer (>= 1.0.1) - rails-html-sanitizer (1.0.3) - loofah (~> 2.0) - railties (4.2.7.1) - actionpack (= 4.2.7.1) - activesupport (= 4.2.7.1) - rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) - rake (11.3.0) - sprockets (3.7.0) - concurrent-ruby (~> 1.0) - rack (> 1, < 3) - sprockets-rails (3.2.0) - actionpack (>= 4.0) - activesupport (>= 4.0) - sprockets (>= 3.0.0) - thor (0.19.1) - thread_safe (0.3.5) - tzinfo (1.2.2) - thread_safe (~> 0.1) - - PLATFORMS - ruby - - DEPENDENCIES - paperclip (~> 5.1.0) - rails (~> 4.2.7.1) - L - - bundle "lock --update paperclip", env: { "BUNDLER_VERSION" => "1.99.0" } - - expect(lockfile).to include(rubygems_version("paperclip", "~> 5.1.0")) - end - it "outputs a helpful warning when gems have a gemspec with invalid `require_paths`" do install_gemfile <<-G, standalone: true, env: { "BUNDLE_FORCE_RUBY_PLATFORM" => "1" } source 'https://rubygems.org' diff --git a/spec/bundler/realworld/slow_perf_spec.rb b/spec/bundler/realworld/slow_perf_spec.rb index d813406e335143..5d36ba7455b5c3 100644 --- a/spec/bundler/realworld/slow_perf_spec.rb +++ b/spec/bundler/realworld/slow_perf_spec.rb @@ -32,107 +32,4 @@ bundle "lock", env: { "DEBUG_RESOLVER" => "1" } expect(out).to include("Solution found after 2 attempts") end - - it "resolves big gemfile quickly" do - gemfile <<~G - # frozen_string_literal: true - - source "https://rubygems.org" - - gem "rails" - gem "pg", ">= 0.18", "< 2.0" - gem "goldiloader" - gem "awesome_nested_set" - gem "circuitbox" - gem "passenger" - gem "globalid" - gem "rack-cors" - gem "rails-pg-extras" - gem "linear_regression_trend" - gem "rack-protection" - gem "pundit" - gem "remote_ip_proxy_scrubber" - gem "bcrypt" - gem "searchkick" - gem "excon" - gem "faraday_middleware-aws-sigv4" - gem "typhoeus" - gem "sidekiq" - gem "sidekiq-undertaker" - gem "sidekiq-cron" - gem "storext" - gem "appsignal" - gem "fcm" - gem "business_time" - gem "tzinfo" - gem "holidays" - gem "bigdecimal" - gem "progress_bar" - gem "redis" - gem "hiredis" - gem "state_machines" - gem "state_machines-audit_trail" - gem "state_machines-activerecord" - gem "interactor" - gem "ar_transaction_changes" - gem "redis-rails" - gem "seed_migration" - gem "lograge" - gem "graphiql-rails", group: :development - gem "graphql" - gem "pusher" - gem "rbnacl" - gem "jwt" - gem "json-schema" - gem "discard" - gem "money" - gem "strip_attributes" - gem "validates_email_format_of" - gem "audited" - gem "concurrent-ruby" - gem "with_advisory_lock" - - group :test do - gem "rspec-sidekiq" - gem "simplecov", require: false - end - - group :development, :test do - gem "byebug", platform: :mri - gem "guard" - gem "guard-bundler" - gem "guard-rspec" - gem "rb-fsevent" - gem "rspec_junit_formatter" - gem "rspec-collection_matchers" - gem "rspec-rails" - gem "rspec-retry" - gem "state_machines-rspec" - gem "dotenv-rails" - gem "database_cleaner-active_record" - gem "database_cleaner-redis" - gem "timecop" - end - - gem "factory_bot_rails" - gem "faker" - - group :development do - gem "listen" - gem "sql_queries_count" - gem "rubocop" - gem "rubocop-performance" - gem "rubocop-rspec" - gem "rubocop-rails" - gem "brakeman" - gem "bundler-audit" - gem "solargraph" - gem "annotate" - end - G - - bundle "lock", env: { "DEBUG_RESOLVER" => "1" } - - expect(out).to include("Solution found after 10 attempts") - end end From 851a3e772480308a3dd014fef9af901ef29eb9b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 16 Jul 2025 18:29:13 +0200 Subject: [PATCH 20/30] [rubygems/rubygems] Restore treating "--" as an unknown platform Rather than crashing when parsing it. https://github.com/rubygems/rubygems/commit/aa0064e4c7 --- lib/rubygems/platform.rb | 2 +- test/rubygems/test_gem_platform.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/rubygems/platform.rb b/lib/rubygems/platform.rb index 8b82292a46919a..e30c266fab1c39 100644 --- a/lib/rubygems/platform.rb +++ b/lib/rubygems/platform.rb @@ -90,7 +90,7 @@ def initialize(arch) when String then cpu, os = arch.sub(/-+$/, "").split("-", 2) - @cpu = if cpu.match?(/i\d86/) + @cpu = if cpu&.match?(/i\d86/) "x86" else cpu diff --git a/test/rubygems/test_gem_platform.rb b/test/rubygems/test_gem_platform.rb index a35332408a48d0..a3ae919809d370 100644 --- a/test/rubygems/test_gem_platform.rb +++ b/test/rubygems/test_gem_platform.rb @@ -162,6 +162,8 @@ def test_initialize "x86_64-dotnetx86" => ["x86_64", "dotnet", nil], "x86_64-dalvik0" => ["x86_64", "dalvik", "0"], "x86_64-dotnet1." => ["x86_64", "dotnet", "1"], + + "--" => [nil, "unknown", nil], } test_cases.each do |arch, expected| From 60fca1defc4b730877a474efeb0f22f39dce9af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 15 Jul 2025 13:35:23 +0200 Subject: [PATCH 21/30] Cancel `--force` deprecation in favor of `--redownload` I realized `--redownload` is not a good name, because it does not necessarily redownloads gems. It only forces reinstallation even if gem is already installed. So I believe `--force` is actually a better name and the introduction of `--force` was a misunderstanding of what the `--force` flag did at the time. Let's cancel the deprecation of `--force`. For now the `--redownload` alias is left around until we decide what to do with it. --- lib/bundler/cli.rb | 7 +-- lib/bundler/cli/install.rb | 2 +- lib/bundler/cli/update.rb | 2 +- lib/bundler/man/bundle-install.1 | 6 +-- lib/bundler/man/bundle-install.1.ronn | 7 ++- lib/bundler/man/bundle-update.1 | 8 ++-- lib/bundler/man/bundle-update.1.ronn | 8 ++-- spec/bundler/commands/install_spec.rb | 6 +-- .../{redownload_spec.rb => force_spec.rb} | 26 ++--------- spec/bundler/update/force_spec.rb | 30 +++++++++++++ spec/bundler/update/redownload_spec.rb | 44 ------------------- 11 files changed, 54 insertions(+), 92 deletions(-) rename spec/bundler/install/{redownload_spec.rb => force_spec.rb} (62%) create mode 100644 spec/bundler/update/force_spec.rb delete mode 100644 spec/bundler/update/redownload_spec.rb diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index 321a6247b8e088..ea85f9af22ab24 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -220,7 +220,7 @@ def remove(*gems) method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead" method_option "prefer-local", type: :boolean, banner: "Only attempt to fetch gems remotely if not present locally, even if newer versions are available remotely" method_option "no-cache", type: :boolean, banner: "Don't update the existing gem cache." - method_option "redownload", type: :boolean, aliases: "--force", banner: "Force downloading every gem." + method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed" method_option "no-prune", type: :boolean, banner: "Don't remove stale gems from the cache." method_option "path", type: :string, banner: "Specify a different path than the system default ($BUNDLE_PATH or $GEM_HOME).#{" Bundler will remember this value for future installs on this machine" unless Bundler.feature_flag.forget_cli_options?}" method_option "quiet", type: :boolean, banner: "Only output warnings and errors." @@ -232,8 +232,6 @@ def remove(*gems) method_option "without", type: :array, banner: "Exclude gems that are part of the specified named group." method_option "with", type: :array, banner: "Include gems that are part of the specified named group." def install - SharedHelpers.major_deprecation(2, "The `--force` option has been renamed to `--redownload`") if ARGV.include?("--force") - %w[clean deployment frozen no-prune path shebang without with].each do |option| remembered_flag_deprecation(option) end @@ -263,7 +261,7 @@ def install method_option "local", type: :boolean, banner: "Do not attempt to fetch gems remotely and use the gem cache instead" method_option "quiet", type: :boolean, banner: "Only output warnings and errors." method_option "source", type: :array, banner: "Update a specific source (and all gems associated with it)" - method_option "redownload", type: :boolean, aliases: "--force", banner: "Force downloading every gem." + method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed" method_option "ruby", type: :boolean, banner: "Update ruby specified in Gemfile.lock" method_option "bundler", type: :string, lazy_default: "> 0.a", banner: "Update the locked version of bundler" method_option "patch", type: :boolean, banner: "Prefer updating only to next patch version" @@ -274,7 +272,6 @@ def install method_option "conservative", type: :boolean, banner: "Use bundle install conservative update behavior and do not allow shared dependencies to be updated." method_option "all", type: :boolean, banner: "Update everything." def update(*gems) - SharedHelpers.major_deprecation(2, "The `--force` option has been renamed to `--redownload`") if ARGV.include?("--force") require_relative "cli/update" Bundler.settings.temporary(no_install: false) do Update.new(options, gems).run diff --git a/lib/bundler/cli/install.rb b/lib/bundler/cli/install.rb index c31be40e551c35..074afd64ebd078 100644 --- a/lib/bundler/cli/install.rb +++ b/lib/bundler/cli/install.rb @@ -179,7 +179,7 @@ def normalize_settings normalize_groups if options[:without] || options[:with] - options[:force] = options[:redownload] + options[:force] = options[:redownload] if options[:redownload] end def warn_ambiguous_gems diff --git a/lib/bundler/cli/update.rb b/lib/bundler/cli/update.rb index ba3f1ec05691fd..13f576cfa75b15 100644 --- a/lib/bundler/cli/update.rb +++ b/lib/bundler/cli/update.rb @@ -63,7 +63,7 @@ def run opts = options.dup opts["update"] = true opts["local"] = options[:local] - opts["force"] = options[:redownload] + opts["force"] = options[:redownload] if options[:redownload] Bundler.settings.set_command_option_if_given :jobs, opts["jobs"] diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1 index a9bd751994e558..bf067475584a03 100644 --- a/lib/bundler/man/bundle-install.1 +++ b/lib/bundler/man/bundle-install.1 @@ -4,7 +4,7 @@ .SH "NAME" \fBbundle\-install\fR \- Install the dependencies specified in your Gemfile .SH "SYNOPSIS" -\fBbundle install\fR [\-\-binstubs[=DIRECTORY]] [\-\-clean] [\-\-deployment] [\-\-frozen] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-no\-cache] [\-\-no\-prune] [\-\-path PATH] [\-\-prefer\-local] [\-\-quiet] [\-\-redownload] [\-\-retry=NUMBER] [\-\-shebang=SHEBANG] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-system] [\-\-trust\-policy=TRUST\-POLICY] [\-\-target\-rbconfig=TARGET\-RBCONFIG] [\-\-with=GROUP[ GROUP\|\.\|\.\|\.]] [\-\-without=GROUP[ GROUP\|\.\|\.\|\.]] +\fBbundle install\fR [\-\-binstubs[=DIRECTORY]] [\-\-clean] [\-\-deployment] [\-\-force] [\-\-frozen] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-local] [\-\-no\-cache] [\-\-no\-prune] [\-\-path PATH] [\-\-prefer\-local] [\-\-quiet] [\-\-retry=NUMBER] [\-\-shebang=SHEBANG] [\-\-standalone[=GROUP[ GROUP\|\.\|\.\|\.]]] [\-\-system] [\-\-trust\-policy=TRUST\-POLICY] [\-\-target\-rbconfig=TARGET\-RBCONFIG] [\-\-with=GROUP[ GROUP\|\.\|\.\|\.]] [\-\-without=GROUP[ GROUP\|\.\|\.\|\.]] .SH "DESCRIPTION" Install the gems specified in your Gemfile(5)\. If this is the first time you run bundle install (and a \fBGemfile\.lock\fR does not exist), Bundler will fetch all remote sources, resolve dependencies and install all needed gems\. .P @@ -29,8 +29,8 @@ In \fIdeployment mode\fR, Bundler will 'roll\-out' the bundle for production or .IP This option is deprecated in favor of the \fBdeployment\fR setting\. .TP -\fB\-\-redownload\fR, \fB\-\-force\fR -Force download every gem, even if the required versions are already available locally\. +\fB\-\-force\fR, \fB\-\-redownload\fR +Force reinstalling every gem, even if already installed\. .TP \fB\-\-frozen\fR Do not allow the Gemfile\.lock to be updated after this install\. Exits non\-zero if there are going to be changes to the Gemfile\.lock\. diff --git a/lib/bundler/man/bundle-install.1.ronn b/lib/bundler/man/bundle-install.1.ronn index a3606826dfce9a..cc6241dd672921 100644 --- a/lib/bundler/man/bundle-install.1.ronn +++ b/lib/bundler/man/bundle-install.1.ronn @@ -6,6 +6,7 @@ bundle-install(1) -- Install the dependencies specified in your Gemfile `bundle install` [--binstubs[=DIRECTORY]] [--clean] [--deployment] + [--force] [--frozen] [--full-index] [--gemfile=GEMFILE] @@ -16,7 +17,6 @@ bundle-install(1) -- Install the dependencies specified in your Gemfile [--path PATH] [--prefer-local] [--quiet] - [--redownload] [--retry=NUMBER] [--shebang=SHEBANG] [--standalone[=GROUP[ GROUP...]]] @@ -80,9 +80,8 @@ automatically and that requires `bundler` to silently remember them. Since This option is deprecated in favor of the `deployment` setting. -* `--redownload`, `--force`: - Force download every gem, even if the required versions are already available - locally. +* `--force`, `--redownload`: + Force reinstalling every gem, even if already installed. * `--frozen`: Do not allow the Gemfile.lock to be updated after this install. Exits diff --git a/lib/bundler/man/bundle-update.1 b/lib/bundler/man/bundle-update.1 index 5b8acdd0b5b614..8655aa5cd3545e 100644 --- a/lib/bundler/man/bundle-update.1 +++ b/lib/bundler/man/bundle-update.1 @@ -4,7 +4,7 @@ .SH "NAME" \fBbundle\-update\fR \- Update your gems to the latest available versions .SH "SYNOPSIS" -\fBbundle update\fR \fI*gems\fR [\-\-all] [\-\-group=NAME] [\-\-source=NAME] [\-\-local] [\-\-ruby] [\-\-bundler[=VERSION]] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-quiet] [\-\-patch|\-\-minor|\-\-major] [\-\-pre] [\-\-redownload] [\-\-strict] [\-\-conservative] +\fBbundle update\fR \fI*gems\fR [\-\-all] [\-\-group=NAME] [\-\-source=NAME] [\-\-local] [\-\-ruby] [\-\-bundler[=VERSION]] [\-\-force] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-jobs=NUMBER] [\-\-quiet] [\-\-patch|\-\-minor|\-\-major] [\-\-pre] [\-\-strict] [\-\-conservative] .SH "DESCRIPTION" Update the gems specified (all gems, if \fB\-\-all\fR flag is used), ignoring the previously installed gems specified in the \fBGemfile\.lock\fR\. In general, you should use bundle install(1) \fIbundle\-install\.1\.html\fR to install the same exact gems and versions across machines\. .P @@ -29,6 +29,9 @@ Update the locked version of Ruby to the current version of Ruby\. \fB\-\-bundler[=BUNDLER]\fR Update the locked version of bundler to the invoked bundler version\. .TP +\fB\-\-force\fR, \fB\-\-redownload\fR +Force reinstalling every gem, even if already installed\. +.TP \fB\-\-full\-index\fR Fall back to using the single\-file index of all gems\. .TP @@ -44,9 +47,6 @@ Retry failed network or git requests for \fInumber\fR times\. \fB\-\-quiet\fR Only output warnings and errors\. .TP -\fB\-\-redownload\fR, \fB\-\-force\fR -Force downloading every gem\. -.TP \fB\-\-patch\fR Prefer updating only to next patch version\. .TP diff --git a/lib/bundler/man/bundle-update.1.ronn b/lib/bundler/man/bundle-update.1.ronn index 1b8b31951dfb98..bfe381677c8efc 100644 --- a/lib/bundler/man/bundle-update.1.ronn +++ b/lib/bundler/man/bundle-update.1.ronn @@ -9,13 +9,13 @@ bundle-update(1) -- Update your gems to the latest available versions [--local] [--ruby] [--bundler[=VERSION]] + [--force] [--full-index] [--gemfile=GEMFILE] [--jobs=NUMBER] [--quiet] [--patch|--minor|--major] [--pre] - [--redownload] [--strict] [--conservative] @@ -54,6 +54,9 @@ gem. * `--bundler[=BUNDLER]`: Update the locked version of bundler to the invoked bundler version. +* `--force`, `--redownload`: + Force reinstalling every gem, even if already installed. + * `--full-index`: Fall back to using the single-file index of all gems. @@ -70,9 +73,6 @@ gem. * `--quiet`: Only output warnings and errors. -* `--redownload`, `--force`: - Force downloading every gem. - * `--patch`: Prefer updating only to next patch version. diff --git a/spec/bundler/commands/install_spec.rb b/spec/bundler/commands/install_spec.rb index 4a581b3058e76e..22eb64ca81a0e0 100644 --- a/spec/bundler/commands/install_spec.rb +++ b/spec/bundler/commands/install_spec.rb @@ -1105,7 +1105,7 @@ def run FileUtils.chmod("-x", foo_path) begin - bundle "install --redownload", raise_on_error: false + bundle "install --force", raise_on_error: false ensure FileUtils.chmod("+x", foo_path) end @@ -1141,7 +1141,7 @@ def run FileUtils.chmod("-w", gem_home) begin - bundle "install --redownload" + bundle "install --force" ensure FileUtils.chmod("+w", gem_home) end @@ -1175,7 +1175,7 @@ def run FileUtils.chmod(0o777, gems_path) - bundle "install --redownload", raise_on_error: false + bundle "install --force", raise_on_error: false expect(err).to include("Bundler cannot reinstall foo-1.0.0 because there's a previous installation of it at #{gems_path}/foo-1.0.0 that is unsafe to remove") end diff --git a/spec/bundler/install/redownload_spec.rb b/spec/bundler/install/force_spec.rb similarity index 62% rename from spec/bundler/install/redownload_spec.rb rename to spec/bundler/install/force_spec.rb index 0b2e2b2f49d076..e0f6fb636498bf 100644 --- a/spec/bundler/install/redownload_spec.rb +++ b/spec/bundler/install/force_spec.rb @@ -8,7 +8,7 @@ G end - shared_examples_for "an option to force redownloading gems" do + shared_examples_for "an option to force reinstalling gems" do it "re-installs installed gems" do myrack_lib = default_bundle_path("gems/myrack-1.0.0/lib/myrack.rb") @@ -58,34 +58,14 @@ end describe "with --force" do - it_behaves_like "an option to force redownloading gems" do + it_behaves_like "an option to force reinstalling gems" do let(:flag) { "force" } end - - it "shows a deprecation when single flag passed" do - bundle "install --force" - expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - - it "shows a deprecation when multiple flags passed" do - bundle "install --no-color --force" - expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end end describe "with --redownload" do - it_behaves_like "an option to force redownloading gems" do + it_behaves_like "an option to force reinstalling gems" do let(:flag) { "redownload" } end - - it "does not show a deprecation when single flag passed" do - bundle "install --redownload" - expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - - it "does not show a deprecation when single multiple flags passed" do - bundle "install --no-color --redownload" - expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end end end diff --git a/spec/bundler/update/force_spec.rb b/spec/bundler/update/force_spec.rb new file mode 100644 index 00000000000000..325f58088a963e --- /dev/null +++ b/spec/bundler/update/force_spec.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +RSpec.describe "bundle update" do + before :each do + install_gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + end + + it "re-installs installed gems with --force" do + myrack_lib = default_bundle_path("gems/myrack-1.0.0/lib/myrack.rb") + myrack_lib.open("w") {|f| f.write("blah blah blah") } + bundle :update, force: true + + expect(out).to include "Installing myrack 1.0.0" + expect(myrack_lib.open(&:read)).to eq("MYRACK = '1.0.0'\n") + expect(the_bundle).to include_gems "myrack 1.0.0" + end + + it "re-installs installed gems with --redownload" do + myrack_lib = default_bundle_path("gems/myrack-1.0.0/lib/myrack.rb") + myrack_lib.open("w") {|f| f.write("blah blah blah") } + bundle :update, redownload: true + + expect(out).to include "Installing myrack 1.0.0" + expect(myrack_lib.open(&:read)).to eq("MYRACK = '1.0.0'\n") + expect(the_bundle).to include_gems "myrack 1.0.0" + end +end diff --git a/spec/bundler/update/redownload_spec.rb b/spec/bundler/update/redownload_spec.rb deleted file mode 100644 index 1fe25f56066c6d..00000000000000 --- a/spec/bundler/update/redownload_spec.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -RSpec.describe "bundle update" do - before :each do - install_gemfile <<-G - source "https://gem.repo1" - gem "myrack" - G - end - - describe "with --force" do - it "shows a deprecation when single flag passed" do - bundle "update myrack --force" - expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - - it "shows a deprecation when multiple flags passed" do - bundle "update myrack --no-color --force" - expect(err).to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - end - - describe "with --redownload" do - it "does not show a deprecation when single flag passed" do - bundle "update myrack --redownload" - expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - - it "does not show a deprecation when single multiple flags passed" do - bundle "update myrack --no-color --redownload" - expect(err).not_to include "[DEPRECATED] The `--force` option has been renamed to `--redownload`" - end - - it "re-installs installed gems" do - myrack_lib = default_bundle_path("gems/myrack-1.0.0/lib/myrack.rb") - myrack_lib.open("w") {|f| f.write("blah blah blah") } - bundle :update, redownload: true - - expect(out).to include "Installing myrack 1.0.0" - expect(myrack_lib.open(&:read)).to eq("MYRACK = '1.0.0'\n") - expect(the_bundle).to include_gems "myrack 1.0.0" - end - end -end From d5f98b9e7eb9253ec5d090eb730d7468f858f71b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Mon, 14 Jul 2025 10:45:32 +0200 Subject: [PATCH 22/30] Set development version to Bundler 2.8.0.dev and RubyGems 3.8.0.dev Next version for both will be 4.0.0, however, extra work is necessary to get CI passing against the new major. So for now, I'm bumping just the minor version. --- lib/bundler/version.rb | 2 +- lib/rubygems.rb | 2 +- tool/bundler/dev_gems.rb.lock | 2 +- tool/bundler/rubocop_gems.rb.lock | 2 +- tool/bundler/standard_gems.rb.lock | 2 +- tool/bundler/test_gems.rb.lock | 2 +- tool/bundler/vendor_gems.rb.lock | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/bundler/version.rb b/lib/bundler/version.rb index f2e654b08a5b7c..5a55b23ac18bcb 100644 --- a/lib/bundler/version.rb +++ b/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "2.7.0.dev".freeze + VERSION = "2.8.0.dev".freeze def self.bundler_major_version @bundler_major_version ||= gem_version.segments.first diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 6c104ae10bb31e..e4eca64fe14981 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "3.7.0.dev" + VERSION = "3.8.0.dev" end # Must be first since it unloads the prelude from 1.9.2 diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index 68106f71911944..182e346f060cb6 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -118,4 +118,4 @@ CHECKSUMS turbo_tests (2.2.5) sha256=3fa31497d12976d11ccc298add29107b92bda94a90d8a0a5783f06f05102509f BUNDLED WITH - 2.7.0.dev + 2.8.0.dev diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index e793b23cbf0f6d..aa2fedcce80432 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -147,4 +147,4 @@ CHECKSUMS unicode-emoji (4.0.4) sha256=2c2c4ef7f353e5809497126285a50b23056cc6e61b64433764a35eff6c36532a BUNDLED WITH - 2.7.0.dev + 2.8.0.dev diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index 3ec7b9376967ac..655b5c7296130c 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -167,4 +167,4 @@ CHECKSUMS unicode-emoji (4.0.4) sha256=2c2c4ef7f353e5809497126285a50b23056cc6e61b64433764a35eff6c36532a BUNDLED WITH - 2.7.0.dev + 2.8.0.dev diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index cbc682cd5c45e0..99760ac573af8d 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -105,4 +105,4 @@ CHECKSUMS tilt (2.6.0) sha256=263d748466e0d83e510aa1a2e2281eff547937f0ef06be33d3632721e255f76b BUNDLED WITH - 2.7.0.dev + 2.8.0.dev diff --git a/tool/bundler/vendor_gems.rb.lock b/tool/bundler/vendor_gems.rb.lock index dd005ee791ffcd..82dbe8963c46bf 100644 --- a/tool/bundler/vendor_gems.rb.lock +++ b/tool/bundler/vendor_gems.rb.lock @@ -82,4 +82,4 @@ CHECKSUMS uri (1.0.3) sha256=e9f2244608eea2f7bc357d954c65c910ce0399ca5e18a7a29207ac22d8767011 BUNDLED WITH - 2.7.0.dev + 2.8.0.dev From 146ddf614bbc917d7cc039ea7b502330ea4b6b6f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 15 Jul 2025 15:03:33 +0900 Subject: [PATCH 23/30] [ruby/io-wait] Bump up 0.3.2 https://github.com/ruby/io-wait/commit/1d2b668457 --- ext/io/wait/io-wait.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/io/wait/io-wait.gemspec b/ext/io/wait/io-wait.gemspec index 016b43100ef6bc..1554dcdb304bef 100644 --- a/ext/io/wait/io-wait.gemspec +++ b/ext/io/wait/io-wait.gemspec @@ -1,4 +1,4 @@ -_VERSION = "0.3.1" +_VERSION = "0.3.2" Gem::Specification.new do |spec| spec.name = "io-wait" From 4dfac710c26d4298aedc410335af687e6940a45b Mon Sep 17 00:00:00 2001 From: git Date: Thu, 17 Jul 2025 02:32:03 +0000 Subject: [PATCH 24/30] Update default gems list at 146ddf614bbc917d7cc039ea7b5023 [ci skip] --- NEWS.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index d0e585a44c1980..d6ce052062994a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -143,12 +143,13 @@ The following default gem is added. The following default gems are updated. -* RubyGems 3.7.0.dev -* bundler 2.7.0.dev +* RubyGems 3.8.0.dev +* bundler 2.8.0.dev * erb 5.0.2 * etc 1.4.6 * io-console 0.8.1 * io-nonblock 0.3.2 +* io-wait 0.3.2 * json 2.12.2 * optparse 0.7.0.dev.2 * prism 1.4.0 From 84253ce38cf53fd31b467e37e7e08ca4394c4692 Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Thu, 17 Jul 2025 08:59:02 +0900 Subject: [PATCH 25/30] fix obsolete doc with `Ractor::Port` --- doc/ractor.md | 357 +++++++++++++------------------------------------- ractor.rb | 26 ++-- 2 files changed, 102 insertions(+), 281 deletions(-) diff --git a/doc/ractor.md b/doc/ractor.md index 6a1cff4e7be83f..eecff5fc5e1c52 100644 --- a/doc/ractor.md +++ b/doc/ractor.md @@ -10,9 +10,9 @@ You can make multiple Ractors and they run in parallel. * `Ractor.new{ expr }` creates a new Ractor and `expr` is run in parallel on a parallel computer. * Interpreter invokes with the first Ractor (called *main Ractor*). -* If main Ractor terminated, all Ractors receive terminate request like Threads (if main thread (first invoked Thread), Ruby interpreter sends all running threads to terminate execution). -* Each Ractor has 1 or more Threads. - * Threads in a Ractor shares a Ractor-wide global lock like GIL (GVL in MRI terminology), so they can't run in parallel (without releasing GVL explicitly in C-level). Threads in different ractors run in parallel. +* If the main Ractor terminates, all other Ractors receive termination requests, similar to how threads behave. (if main thread (first invoked Thread), Ruby interpreter sends all running threads to terminate execution). +* Each Ractor contains one or more Threads. + * Threads within the same Ractor share a Ractor-wide global lock like GIL (GVL in MRI terminology), so they can't run in parallel (without releasing GVL explicitly in C-level). Threads in different ractors run in parallel. * The overhead of creating a Ractor is similar to overhead of one Thread creation. ### Limited sharing between multiple ractors @@ -31,20 +31,23 @@ Ractors don't share everything, unlike threads. * Ractor object itself. * And more... -### Two-types communication between Ractors +### Communication between Ractors with `Ractor::Port` -Ractors communicate with each other and synchronize the execution by message exchanging between Ractors. There are two message exchange protocols: push type (message passing) and pull type. +Ractors communicate with each other and synchronize the execution by message exchanging between Ractors. `Ractor::Port` is provided for this communication. -* Push type message passing: `Ractor#send(obj)` and `Ractor.receive()` pair. - * Sender ractor passes the `obj` to the ractor `r` by `r.send(obj)` and receiver ractor receives the message with `Ractor.receive`. - * Sender knows the destination Ractor `r` and the receiver does not know the sender (accept all messages from any ractors). - * Receiver has infinite queue and sender enqueues the message. Sender doesn't block to put message into this queue. - * This type of message exchanging is employed by many other Actor-based languages. - * `Ractor.receive_if{ filter_expr }` is a variant of `Ractor.receive` to select a message. -* Pull type communication: `Ractor.yield(obj)` and `Ractor#take()` pair. - * Sender ractor declare to yield the `obj` by `Ractor.yield(obj)` and receiver Ractor take it with `r.take`. - * Sender doesn't know a destination Ractor and receiver knows the sender Ractor `r`. - * Sender or receiver will block if there is no other side. +```ruby +port = Ractor::Port.new + +Ractor.new port do |port| + # Other ractors can send to the port + port << 42 +end + +port.receive # get a message to the port. Only the creator Ractor can receive from the port +#=> 42 +``` + +Ractors have its own deafult port and `Ractor#send`, `Ractor.receive` will use it. ### Copy & Move semantics to send messages @@ -66,7 +69,7 @@ Ractor helps to write a thread-safe concurrent program, but we can make thread-u * To make it compatible with old behavior, classes and modules can introduce data-race and so on. * Ruby programmers should take care if they modify class/module objects on multi Ractor programs. * BAD: Ractor can't solve all thread-safety problems - * There are several blocking operations (waiting send, waiting yield and waiting take) so you can make a program which has dead-lock and live-lock issues. + * There are several blocking operations (waiting send) so you can make a program which has dead-lock and live-lock issues. * Some kind of shareable objects can introduce transactions (STM, for example). However, misusing transactions will generate inconsistent state. Without Ractor, we need to trace all state-mutations to debug thread-safety issues. @@ -105,7 +108,7 @@ begin r = Ractor.new do a #=> ArgumentError because this block accesses `a`. end - r.take # see later + r.join # see later rescue ArgumentError end ``` @@ -117,7 +120,7 @@ r = Ractor.new do p self.class #=> Ractor self.object_id end -r.take == self.object_id #=> false +r.value == self.object_id #=> false ``` Passed arguments to `Ractor.new()` becomes block parameters for the given block. However, an interpreter does not pass the parameter object references, but send them as messages (see below for details). @@ -126,7 +129,7 @@ Passed arguments to `Ractor.new()` becomes block parameters for the given block. r = Ractor.new 'ok' do |msg| msg #=> 'ok' end -r.take #=> 'ok' +r.value #=> 'ok' ``` ```ruby @@ -136,7 +139,7 @@ r = Ractor.new do msg end r.send 'ok' -r.take #=> 'ok' +r.value #=> 'ok' ``` ### An execution result of given block @@ -147,15 +150,7 @@ Return value of the given block becomes an outgoing message (see below for detai r = Ractor.new do 'ok' end -r.take #=> `ok` -``` - -```ruby -# almost similar to the last example -r = Ractor.new do - Ractor.yield 'ok' -end -r.take #=> 'ok' +r.value #=> `ok` ``` Error in the given block will be propagated to the receiver of an outgoing message. @@ -166,7 +161,7 @@ r = Ractor.new do end begin - r.take + r.value rescue Ractor::RemoteError => e e.cause.class #=> RuntimeError e.cause.message #=> 'ok' @@ -178,9 +173,7 @@ end Communication between Ractors is achieved by sending and receiving messages. There are two ways to communicate with each other. -* (1) Message sending/receiving - * (1-1) push type send/receive (sender knows receiver). Similar to the Actor model. - * (1-2) pull type yield/take (receiver knows sender). +* (1) Message sending/receiving via `Ractor::Port` * (2) Using shareable container objects * Ractor::TVar gem ([ko1/ractor-tvar](https://github.com/ko1/ractor-tvar)) * more? @@ -189,18 +182,12 @@ Users can control program execution timing with (1), but should not control with For message sending and receiving, there are two types of APIs: push type and pull type. -* (1-1) send/receive (push type) - * `Ractor#send(obj)` (`Ractor#<<(obj)` is an alias) send a message to the Ractor's incoming port. Incoming port is connected to the infinite size incoming queue so `Ractor#send` will never block. - * `Ractor.receive` dequeue a message from its own incoming queue. If the incoming queue is empty, `Ractor.receive` calling will block. - * `Ractor.receive_if{|msg| filter_expr }` is variant of `Ractor.receive`. `receive_if` only receives a message which `filter_expr` is true (So `Ractor.receive` is the same as `Ractor.receive_if{ true }`. -* (1-2) yield/take (pull type) - * `Ractor.yield(obj)` send an message to a Ractor which are calling `Ractor#take` via outgoing port . If no Ractors are waiting for it, the `Ractor.yield(obj)` will block. If multiple Ractors are waiting for `Ractor.yield(obj)`, only one Ractor can receive the message. - * `Ractor#take` receives a message which is waiting by `Ractor.yield(obj)` method from the specified Ractor. If the Ractor does not call `Ractor.yield` yet, the `Ractor#take` call will block. -* `Ractor.select()` can wait for the success of `take`, `yield` and `receive`. -* You can close the incoming port or outgoing port. - * You can close then with `Ractor#close_incoming` and `Ractor#close_outgoing`. - * If the incoming port is closed for a Ractor, you can't `send` to the Ractor. If `Ractor.receive` is blocked for the closed incoming port, then it will raise an exception. - * If the outgoing port is closed for a Ractor, you can't call `Ractor#take` and `Ractor.yield` on the Ractor. If ractors are blocking by `Ractor#take` or `Ractor.yield`, closing outgoing port will raise an exception on these blocking ractors. +* (1) send/receive via `Ractor::Port`. + * `Ractor::Port#send(obj)` (`Ractor::Port#<<(obj)` is an alias) send a message to the port. Ports are connected to the infinite size incoming queue so `Ractor::Port#send` will never block. + * `Ractor::Port#receive` dequeue a message from its own incoming queue. If the incoming queue is empty, `Ractor::Port#receive` calling will block the execution of a thread. +* `Ractor.select()` can wait for the success of `Ractor::Port#receive`. +* You can close `Ractor::Port` by `Ractor::Port#close` only by the creator Ractor of the port. + * If the port is closed, you can't `send` to the port. If `Ractor::Port#receive` is blocked for the closed port, then it will raise an exception. * When a Ractor is terminated, the Ractor's ports are closed. * There are 3 ways to send an object as a message * (1) Send a reference: Sending a shareable object, send only a reference to the object (fast) @@ -208,104 +195,15 @@ For message sending and receiving, there are two types of APIs: push type and pu * (3) Move an object: Sending an unshareable object reference with a membership. Sender Ractor can not access moved objects anymore (raise an exception) after moving it. Current implementation makes new object as a moved object for receiver Ractor and copies references of sending object to moved object. `T_DATA` objects are not supported. * You can choose "Copy" and "Move" by the `move:` keyword, `Ractor#send(obj, move: true/false)` and `Ractor.yield(obj, move: true/false)` (default is `false` (COPY)). -### Sending/Receiving ports - -Each Ractor has _incoming-port_ and _outgoing-port_. Incoming-port is connected to the infinite sized incoming queue. - -``` - Ractor r - +-------------------------------------------+ - | incoming outgoing | - | port port | - r.send(obj) ->*->[incoming queue] Ractor.yield(obj) ->*-> r.take - | | | - | v | - | Ractor.receive | - +-------------------------------------------+ - - -Connection example: r2.send obj on r1态Ractor.receive on r2 - +----+ +----+ - * r1 |---->* r2 * - +----+ +----+ - - -Connection example: Ractor.yield(obj) on r1, r1.take on r2 - +----+ +----+ - * r1 *---->- r2 * - +----+ +----+ - -Connection example: Ractor.yield(obj) on r1 and r2, - and waiting for both simultaneously by Ractor.select(r1, r2) - - +----+ - * r1 *------+ - +----+ | - +----> Ractor.select(r1, r2) - +----+ | - * r2 *------| - +----+ -``` - -```ruby -r = Ractor.new do - msg = Ractor.receive # Receive from r's incoming queue - msg # send back msg as block return value -end -r.send 'ok' # Send 'ok' to r's incoming port -> incoming queue -r.take # Receive from r's outgoing port -``` - -The last example shows the following ractor network. - -``` - +------+ +---+ - * main |------> * r *---+ - +------+ +---+ | - ^ | - +-------------------+ -``` - -And this code can be simplified by using an argument for `Ractor.new`. - -```ruby -# Actual argument 'ok' for `Ractor.new()` will be sent to created Ractor. -r = Ractor.new 'ok' do |msg| - # Values for formal parameters will be received from incoming queue. - # Similar to: msg = Ractor.receive - - msg # Return value of the given block will be sent via outgoing port -end - -# receive from the r's outgoing port. -r.take #=> `ok` -``` - -### Return value of a block for `Ractor.new` - -As already explained, the return value of `Ractor.new` (an evaluated value of `expr` in `Ractor.new{ expr }`) can be taken by `Ractor#take`. - -```ruby -Ractor.new{ 42 }.take #=> 42 -``` - -When the block return value is available, the Ractor is dead so that no ractors except taken Ractor can touch the return value, so any values can be sent with this communication path without any modification. - -```ruby -r = Ractor.new do - a = "hello" - binding -end - -r.take.eval("p a") #=> "hello" (other communication path can not send a Binding object directly) -``` - ### Wait for multiple Ractors with `Ractor.select` -You can wait multiple Ractor's `yield` with `Ractor.select(*ractors)`. -The return value of `Ractor.select()` is `[r, msg]` where `r` is yielding Ractor and `msg` is yielded message. +You can wait multiple Ractor port's receiving. +The return value of `Ractor.select()` is `[port, msg]` where `port` is a ready port and `msg` is received message. -Wait for a single ractor (same as `Ractor.take`): +To make convenient, `Ractor.select` can also accept Ractors to wait the termination of Ractors. +The return value of `Ractor.select()` is `[r, msg]` where `r` is a terminated Ractor and `msg` is the value of Ractor's blcok. + +Wait for a single ractor (same as `Ractor#value`): ```ruby r1 = Ractor.new{'r1'} @@ -314,7 +212,7 @@ r, obj = Ractor.select(r1) r == r1 and obj == 'r1' #=> true ``` -Wait for two ractors: +Waiting for two ractors: ```ruby r1 = Ractor.new{'r1'} @@ -334,85 +232,29 @@ as << obj as.sort == ['r1', 'r2'] #=> true ``` -\Complex example: - -```ruby -pipe = Ractor.new do - loop do - Ractor.yield Ractor.receive - end -end - -RN = 10 -rs = RN.times.map{|i| - Ractor.new pipe, i do |pipe, i| - msg = pipe.take - msg # ping-pong - end -} -RN.times{|i| - pipe << i -} -RN.times.map{ - r, n = Ractor.select(*rs) - rs.delete r - n -}.sort #=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -``` - -Multiple Ractors can send to one Ractor. - -```ruby -# Create 10 ractors and they send objects to pipe ractor. -# pipe ractor yield received objects - -pipe = Ractor.new do - loop do - Ractor.yield Ractor.receive - end -end - -RN = 10 -rs = RN.times.map{|i| - Ractor.new pipe, i do |pipe, i| - pipe << i - end -} - -RN.times.map{ - pipe.take -}.sort #=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] -``` - TODO: Current `Ractor.select()` has the same issue of `select(2)`, so this interface should be refined. TODO: `select` syntax of go-language uses round-robin technique to make fair scheduling. Now `Ractor.select()` doesn't use it. ### Closing Ractor's ports -* `Ractor#close_incoming/outgoing` close incoming/outgoing ports (similar to `Queue#close`). -* `Ractor#close_incoming` - * `r.send(obj)` where `r`'s incoming port is closed, will raise an exception. - * When the incoming queue is empty and incoming port is closed, `Ractor.receive` raises an exception. If the incoming queue is not empty, it dequeues an object without exceptions. -* `Ractor#close_outgoing` - * `Ractor.yield` on a Ractor which closed the outgoing port, it will raise an exception. - * `Ractor#take` for a Ractor which closed the outgoing port, it will raise an exception. If `Ractor#take` is blocking, it will raise an exception. +* `Ractor::Port#close` close the ports (similar to `Queue#close`). + * `port.send(obj)` where `port` is closed, will raise an exception. + * When the queue connected to the port is empty and port is closed, `Ractor::Port#receive` raises an exception. If the queue is not empty, it dequeues an object without exceptions. * When a Ractor terminates, the ports are closed automatically. - * Return value of the Ractor's block will be yielded as `Ractor.yield(ret_val)`, even if the implementation terminates the based native thread. -Example (try to take from closed Ractor): +Example (try to get a result from closed Ractor): ```ruby r = Ractor.new do 'finish' end -r.take # success (will return 'finish') -begin - o = r.take # try to take from closed Ractor -rescue Ractor::ClosedError - 'ok' -else - "ng: #{o}" +r.join # success (wait for the termination) +r.value # success (will return 'finish') + +# the first Ractor which success the `Ractor#value` can get the result +Ractor.new r do |r| + r.value #=> Ractor::Error end ``` @@ -422,7 +264,7 @@ Example (try to send to closed (terminated) Ractor): r = Ractor.new do end -r.take # wait terminate +r.join # wait terminate begin r.send(1) @@ -433,11 +275,9 @@ else end ``` -When multiple Ractors are waiting for `Ractor.yield()`, `Ractor#close_outgoing` will cancel all blocking by raising an exception (`ClosedError`). - ### Send a message by copying -`Ractor#send(obj)` or `Ractor.yield(obj)` copy `obj` deeply if `obj` is an unshareable object. +`Ractor::Port#send(obj)` copy `obj` deeply if `obj` is an unshareable object. ```ruby obj = 'str'.dup @@ -446,7 +286,7 @@ r = Ractor.new obj do |msg| msg.object_id end -obj.object_id == r.take #=> false +obj.object_id == r.value #=> false ``` Some objects are not supported to copy the value, and raise an exception. @@ -466,7 +306,7 @@ end ### Send a message by moving -`Ractor#send(obj, move: true)` or `Ractor.yield(obj, move: true)` move `obj` to the destination Ractor. +`Ractor::Port#send(obj, move: true)` moves `obj` to the destination Ractor. If the source Ractor touches the moved object (for example, call the method like `obj.foo()`), it will be an error. ```ruby @@ -478,7 +318,7 @@ end str = 'hello' r.send str, move: true -modified = r.take #=> 'hello world' +modified = r.value #=> 'hello world' # str is moved, and accessing str from this Ractor is prohibited @@ -492,22 +332,6 @@ else end ``` -```ruby -# move with Ractor.yield -r = Ractor.new do - obj = 'hello' - Ractor.yield obj, move: true - obj << 'world' # raise Ractor::MovedError -end - -str = r.take -begin - r.take -rescue Ractor::RemoteError - p str #=> "hello" -end -``` - Some objects are not supported to move, and an exception will be raised. ```ruby @@ -554,13 +378,13 @@ r = Ractor.new do end begin - r.take + r.join rescue Ractor::RemoteError => e e.cause.message #=> 'can not access global variables from non-main Ractors' end ``` -Note that some special global variables are ractor-local, like `$stdin`, `$stdout`, `$stderr`. See [[Bug #17268]](https://bugs.ruby-lang.org/issues/17268) for more details. +Note that some special global variables, such as `$stdin`, `$stdout` and `$stderr` are Ractor-lcoal. See [[Bug #17268]](https://bugs.ruby-lang.org/issues/17268) for more details. ### Instance variables of shareable objects @@ -575,7 +399,7 @@ p Ractor.new do class C @iv end -end.take #=> 1 +end.value #=> 1 ``` Otherwise, only the main Ractor can access instance variables of shareable objects. @@ -601,7 +425,7 @@ Ractor.new do #=> "can not set instance variables of classes/modules by non-main Ractors" end end -end.take +end.join ``` @@ -615,7 +439,7 @@ r = Ractor.new shared do |shared| end begin - r.take + r.join rescue Ractor::RemoteError => e e.cause.message #=> can not access instance variables of shareable objects from non-main Ractors (Ractor::IsolationError) end @@ -640,7 +464,7 @@ end begin - r.take + r.join rescue => e e.class #=> Ractor::IsolationError end @@ -658,7 +482,7 @@ r = Ractor.new do C::CONST end begin - r.take + r.join rescue => e e.class #=> Ractor::IsolationError end @@ -673,7 +497,7 @@ r = Ractor.new do C::CONST = 'str' end begin - r.take + r.join rescue => e e.class #=> Ractor::IsolationError end @@ -770,54 +594,51 @@ end ### Worker pool +(1) One ractor has a pool + ```ruby require 'prime' -pipe = Ractor.new do - loop do - Ractor.yield Ractor.receive - end -end - N = 1000 RN = 10 + +# make RN workers workers = (1..RN).map do - Ractor.new pipe do |pipe| - while n = pipe.take - Ractor.yield [n, n.prime?] + Ractor.new do |; result_port| + loop do + n, result_port = Ractor.receive + result_port << [n, n.prime?, Ractor.current] end end end -(1..N).each{|i| - pipe << i -} - -pp (1..N).map{ - _r, (n, b) = Ractor.select(*workers) - [n, b] -}.sort_by{|(n, b)| n} -``` - -### Pipeline +result_port = Ractor::Port.new +results = [] -```ruby -# pipeline with yield/take -r1 = Ractor.new do - 'r1' -end +(1..N).each do |i| + if workers.empty? + # receive a result + n, result, w = result_port.receive + results << [n, result] + else + w = workers.pop + end -r2 = Ractor.new r1 do |r1| - r1.take + 'r2' + # send a task to the idle worker ractor + w << [i, result_port] end -r3 = Ractor.new r2 do |r2| - r2.take + 'r3' +# receive a result +while results.size != N + n, result, _w = result_port.receive + results << [n, result] end -p r3.take #=> 'r1r2r3' +pp results.sort_by{|n, result| n} ``` +### Pipeline + ```ruby # pipeline with send/receive diff --git a/ractor.rb b/ractor.rb index 20fc622d772896..ee6135b81e5841 100644 --- a/ractor.rb +++ b/ractor.rb @@ -78,7 +78,7 @@ # puts "In ractor: #{data2.object_id}, #{data2[0].object_id}, #{data2[1].object_id}" # end # r.send(data) -# r.take +# r.join # puts "Outside : #{data.object_id}, #{data[0].object_id}, #{data[1].object_id}" # # This will output something like: @@ -100,7 +100,7 @@ # puts "In ractor: #{data_in_ractor.object_id}, #{data_in_ractor[0].object_id}" # end # r.send(data, move: true) -# r.take +# r.join # puts "Outside: moved? #{Ractor::MovedObject === data}" # puts "Outside: #{data.inspect}" # @@ -135,7 +135,7 @@ # puts "I can't see #{cls.tricky}" # cls.tricky = true # doesn't get here, but this would also raise an error # end -# r.take +# r.join # # I see C # # can not access instance variables of classes/modules from non-main Ractors (RuntimeError) # @@ -149,7 +149,7 @@ # puts "GOOD=#{GOOD}" # puts "BAD=#{BAD}" # end -# r.take +# r.join # # GOOD=good # # can not access non-shareable objects in constant Object::BAD by non-main Ractor. (NameError) # @@ -159,7 +159,7 @@ # puts "I see #{C}" # puts "I can't see #{C.tricky}" # end -# r.take +# r.join # # I see C # # can not access instance variables of classes/modules from non-main Ractors (RuntimeError) # @@ -175,7 +175,7 @@ # a = 1 # Thread.new {puts "Thread in ractor: a=#{a}"}.join # end -# r.take +# r.join # # Here "Thread in ractor: a=1" will be printed # # == Note on code examples @@ -188,7 +188,7 @@ # end # # It is **only for demonstration purposes** and shouldn't be used in a real code. -# Most of the time, #take is used to wait for ractors to finish. +# Most of the time, #join is used to wait for ractors to finish. # # == Reference # @@ -205,7 +205,7 @@ class Ractor # inside the block will refer to the current \Ractor. # # r = Ractor.new { puts "Hi, I am #{self.inspect}" } - # r.take + # r.join # # Prints "Hi, I am #" # # Any +args+ passed are propagated to the block arguments by the same rules as @@ -217,14 +217,14 @@ class Ractor # r = Ractor.new(arg) {|received_arg| # puts "Received: #{received_arg} (##{received_arg.object_id})" # } - # r.take + # r.join # # Prints: # # Passing: [1, 2, 3] (#280) # # Received: [1, 2, 3] (#300) # # Ractor's +name+ can be set for debugging purposes: # - # r = Ractor.new(name: 'my ractor') {}; r.take + # r = Ractor.new(name: 'my ractor') {}; r.join # p r # #=> # # @@ -252,10 +252,10 @@ def self.current # Returns the number of Ractors currently running or blocking (waiting). # # Ractor.count #=> 1 - # r = Ractor.new(name: 'example') { Ractor.yield(1) } + # r = Ractor.new(name: 'example') { Ractor.receive } # Ractor.count #=> 2 (main + example ractor) - # r.take # wait for Ractor.yield(1) - # r.take # wait until r will finish + # r << 42 # r's Ractor.receive will resume + # r.join # wait for r's termination # Ractor.count #=> 1 def self.count __builtin_cexpr! %q{ From 0482b11ee5bb66ccf2f9db1ed6a35e6fdeca07ae Mon Sep 17 00:00:00 2001 From: Koichi Sasada Date: Thu, 17 Jul 2025 12:36:59 +0900 Subject: [PATCH 26/30] use ostruct 0.6.3 --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index 6e748c375cff89..a040f93a34db31 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -35,7 +35,7 @@ nkf 0.2.0 https://github.com/ruby/nkf syslog 0.3.0 https://github.com/ruby/syslog csv 3.3.5 https://github.com/ruby/csv repl_type_completor 0.1.11 https://github.com/ruby/repl_type_completor 25108aa8d69ddaba0b5da3feff1c0035371524b2 -ostruct 0.6.2 https://github.com/ruby/ostruct +ostruct 0.6.3 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 From 552e44912e076f643541ef49da2e5bc026507e5a Mon Sep 17 00:00:00 2001 From: git Date: Thu, 17 Jul 2025 03:37:57 +0000 Subject: [PATCH 27/30] [DOC] Update bundled gems list at 0482b11ee5bb66ccf2f9db1ed6a35e --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index d6ce052062994a..35a16c239f6e7e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -120,7 +120,7 @@ Note: We're only listing outstanding class updates. The following bundled gems are promoted from default gems. -* ostruct 0.6.2 +* ostruct 0.6.3 * pstore 0.2.0 * benchmark 0.4.1 * logger 1.7.0 From 7a3b6d30a8eb4e8a678d61d803d302218a19a014 Mon Sep 17 00:00:00 2001 From: Kazuhiro NISHIYAMA Date: Thu, 17 Jul 2025 14:32:42 +0900 Subject: [PATCH 28/30] [DOC] Sort links [ci skip] --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 35a16c239f6e7e..fa4d6ba8ddf6f1 100644 --- a/NEWS.md +++ b/NEWS.md @@ -229,6 +229,7 @@ The following bundled gems are updated. ## JIT +[Feature #17473]: https://bugs.ruby-lang.org/issues/17473 [Feature #18455]: https://bugs.ruby-lang.org/issues/18455 [Feature #19908]: https://bugs.ruby-lang.org/issues/19908 [Feature #20610]: https://bugs.ruby-lang.org/issues/20610 @@ -242,4 +243,3 @@ The following bundled gems are updated. [Feature #21262]: https://bugs.ruby-lang.org/issues/21262 [Feature #21287]: https://bugs.ruby-lang.org/issues/21287 [Feature #21347]: https://bugs.ruby-lang.org/issues/21347 -[Feature #17473]: https://bugs.ruby-lang.org/issues/17473 From a7992400f18b2cdabe97a3e2a356cb5e0b5d4454 Mon Sep 17 00:00:00 2001 From: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Date: Sun, 6 Jul 2025 07:41:46 +0800 Subject: [PATCH 29/30] `atomic.h`: Use explicit logic for 32-bit #else branches These branches are only active for 32-bit Windows and Solaris platforms, codify the fact by changing `#else` to `#elif`'s that explicitly include those targets and `#error`-out otherwise. --- include/ruby/atomic.h | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/include/ruby/atomic.h b/include/ruby/atomic.h index 4cf891b345c3b9..8ab563de92142c 100644 --- a/include/ruby/atomic.h +++ b/include/ruby/atomic.h @@ -436,12 +436,14 @@ rbimpl_atomic_size_fetch_add(volatile size_t *ptr, size_t val) RBIMPL_ASSERT_OR_ASSUME(val <= LONG_MAX); atomic_add_long(ptr, val); -#else +#elif defined(__sun) && defined(HAVE_ATOMIC_H) RBIMPL_STATIC_ASSERT(size_of_rb_atomic_t, sizeof *ptr == sizeof(rb_atomic_t)); volatile rb_atomic_t *const tmp = RBIMPL_CAST((volatile rb_atomic_t *)ptr); rbimpl_atomic_fetch_add(tmp, val); +#else +# error Unsupported platform. #endif } @@ -505,12 +507,14 @@ rbimpl_atomic_size_add(volatile size_t *ptr, size_t val) RBIMPL_ASSERT_OR_ASSUME(val <= LONG_MAX); atomic_add_long(ptr, val); -#else +#elif defined(_WIN32) || (defined(__sun) && defined(HAVE_ATOMIC_H)) RBIMPL_STATIC_ASSERT(size_of_rb_atomic_t, sizeof *ptr == sizeof(rb_atomic_t)); volatile rb_atomic_t *const tmp = RBIMPL_CAST((volatile rb_atomic_t *)ptr); rbimpl_atomic_add(tmp, val); +#else +# error Unsupported platform. #endif } @@ -532,8 +536,7 @@ rbimpl_atomic_inc(volatile rb_atomic_t *ptr) atomic_inc_uint(ptr); #else - rbimpl_atomic_add(ptr, 1); - +# error Unsupported platform. #endif } @@ -554,11 +557,13 @@ rbimpl_atomic_size_inc(volatile size_t *ptr) #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) atomic_inc_ulong(ptr); -#else +#elif defined(_WIN32) || (defined(__sun) && defined(HAVE_ATOMIC_H)) RBIMPL_STATIC_ASSERT(size_of_size_t, sizeof *ptr == sizeof(rb_atomic_t)); rbimpl_atomic_size_add(ptr, 1); +#else +# error Unsupported platform. #endif } @@ -641,12 +646,14 @@ rbimpl_atomic_size_sub(volatile size_t *ptr, size_t val) RBIMPL_ASSERT_OR_ASSUME(val <= LONG_MAX); atomic_add_long(ptr, neg * val); -#else +#elif defined(_WIN32) || (defined(__sun) && defined(HAVE_ATOMIC_H)) RBIMPL_STATIC_ASSERT(size_of_rb_atomic_t, sizeof *ptr == sizeof(rb_atomic_t)); volatile rb_atomic_t *const tmp = RBIMPL_CAST((volatile rb_atomic_t *)ptr); rbimpl_atomic_sub(tmp, val); +#else +# error Unsupported platform. #endif } @@ -668,8 +675,7 @@ rbimpl_atomic_dec(volatile rb_atomic_t *ptr) atomic_dec_uint(ptr); #else - rbimpl_atomic_sub(ptr, 1); - +# error Unsupported platform. #endif } @@ -690,11 +696,13 @@ rbimpl_atomic_size_dec(volatile size_t *ptr) #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) atomic_dec_ulong(ptr); -#else +#elif defined(_WIN32) || (defined(__sun) && defined(HAVE_ATOMIC_H)) RBIMPL_STATIC_ASSERT(size_of_size_t, sizeof *ptr == sizeof(rb_atomic_t)); rbimpl_atomic_size_sub(ptr, 1); +#else +# error Unsupported platform. #endif } @@ -790,13 +798,15 @@ rbimpl_atomic_size_exchange(volatile size_t *ptr, size_t val) #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) return atomic_swap_ulong(ptr, val); -#else +#elif defined(_WIN32) || (defined(__sun) && defined(HAVE_ATOMIC_H)) RBIMPL_STATIC_ASSERT(size_of_size_t, sizeof *ptr == sizeof(rb_atomic_t)); volatile rb_atomic_t *const tmp = RBIMPL_CAST((volatile rb_atomic_t *)ptr); const rb_atomic_t ret = rbimpl_atomic_exchange(tmp, val); return RBIMPL_CAST((size_t)ret); +#else +# error Unsupported platform. #endif } @@ -983,12 +993,14 @@ rbimpl_atomic_size_cas(volatile size_t *ptr, size_t oldval, size_t newval) #elif defined(__sun) && defined(HAVE_ATOMIC_H) && (defined(_LP64) || defined(_I32LPx)) return atomic_cas_ulong(ptr, oldval, newval); -#else +#elif defined(_WIN32) || (defined(__sun) && defined(HAVE_ATOMIC_H)) RBIMPL_STATIC_ASSERT(size_of_size_t, sizeof *ptr == sizeof(rb_atomic_t)); volatile rb_atomic_t *tmp = RBIMPL_CAST((volatile rb_atomic_t *)ptr); return rbimpl_atomic_cas(tmp, oldval, newval); +#else +# error Unsupported platform. #endif } From 98aa2a6608b026c56130154aa07b1635e05d95e8 Mon Sep 17 00:00:00 2001 From: fuhsnn <66062782+fuhsnn@users.noreply.github.com> Date: Sun, 6 Jul 2025 08:03:36 +0800 Subject: [PATCH 30/30] `atomic.h`: Add C11 implementation The implementation is only active if `HAVE_STDATOMIC_H` is defined, and only after the compiler fails to match all currently supported systems. --- include/ruby/atomic.h | 55 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/include/ruby/atomic.h b/include/ruby/atomic.h index 8ab563de92142c..b778276f62d887 100644 --- a/include/ruby/atomic.h +++ b/include/ruby/atomic.h @@ -77,6 +77,9 @@ typedef unsigned int rb_atomic_t; typedef LONG rb_atomic_t; #elif defined(__sun) && defined(HAVE_ATOMIC_H) typedef unsigned int rb_atomic_t; +#elif defined(HAVE_STDATOMIC_H) +# include +typedef unsigned int rb_atomic_t; #else # error No atomic operation found #endif @@ -408,6 +411,9 @@ rbimpl_atomic_fetch_add(volatile rb_atomic_t *ptr, rb_atomic_t val) RBIMPL_ASSERT_OR_ASSUME(val <= INT_MAX); return atomic_add_int_nv(ptr, val) - val; +#elif defined(HAVE_STDATOMIC_H) + return atomic_fetch_add((_Atomic volatile rb_atomic_t *)ptr, val); + #else # error Unsupported platform. #endif @@ -442,6 +448,9 @@ rbimpl_atomic_size_fetch_add(volatile size_t *ptr, size_t val) volatile rb_atomic_t *const tmp = RBIMPL_CAST((volatile rb_atomic_t *)ptr); rbimpl_atomic_fetch_add(tmp, val); +#elif defined(HAVE_STDATOMIC_H) + return atomic_fetch_add((_Atomic volatile size_t *)ptr, val); + #else # error Unsupported platform. #endif @@ -479,6 +488,9 @@ rbimpl_atomic_add(volatile rb_atomic_t *ptr, rb_atomic_t val) RBIMPL_ASSERT_OR_ASSUME(val <= INT_MAX); atomic_add_int(ptr, val); +#elif defined(HAVE_STDATOMIC_H) + *(_Atomic volatile rb_atomic_t *)ptr += val; + #else # error Unsupported platform. #endif @@ -513,6 +525,9 @@ rbimpl_atomic_size_add(volatile size_t *ptr, size_t val) volatile rb_atomic_t *const tmp = RBIMPL_CAST((volatile rb_atomic_t *)ptr); rbimpl_atomic_add(tmp, val); +#elif defined(HAVE_STDATOMIC_H) + *(_Atomic volatile size_t *)ptr += val; + #else # error Unsupported platform. #endif @@ -535,6 +550,9 @@ rbimpl_atomic_inc(volatile rb_atomic_t *ptr) #elif defined(__sun) && defined(HAVE_ATOMIC_H) atomic_inc_uint(ptr); +#elif defined(HAVE_STDATOMIC_H) + rbimpl_atomic_add(ptr, 1); + #else # error Unsupported platform. #endif @@ -562,6 +580,9 @@ rbimpl_atomic_size_inc(volatile size_t *ptr) rbimpl_atomic_size_add(ptr, 1); +#elif defined(HAVE_STDATOMIC_H) + rbimpl_atomic_size_add(ptr, 1); + #else # error Unsupported platform. #endif @@ -591,6 +612,9 @@ rbimpl_atomic_fetch_sub(volatile rb_atomic_t *ptr, rb_atomic_t val) RBIMPL_ASSERT_OR_ASSUME(val <= INT_MAX); return atomic_add_int_nv(ptr, neg * val) + val; +#elif defined(HAVE_STDATOMIC_H) + return atomic_fetch_sub((_Atomic volatile rb_atomic_t *)ptr, val); + #else # error Unsupported platform. #endif @@ -618,6 +642,9 @@ rbimpl_atomic_sub(volatile rb_atomic_t *ptr, rb_atomic_t val) RBIMPL_ASSERT_OR_ASSUME(val <= INT_MAX); atomic_add_int(ptr, neg * val); +#elif defined(HAVE_STDATOMIC_H) + *(_Atomic volatile rb_atomic_t *)ptr -= val; + #else # error Unsupported platform. #endif @@ -652,6 +679,9 @@ rbimpl_atomic_size_sub(volatile size_t *ptr, size_t val) volatile rb_atomic_t *const tmp = RBIMPL_CAST((volatile rb_atomic_t *)ptr); rbimpl_atomic_sub(tmp, val); +#elif defined(HAVE_STDATOMIC_H) + *(_Atomic volatile size_t *)ptr -= val; + #else # error Unsupported platform. #endif @@ -674,6 +704,9 @@ rbimpl_atomic_dec(volatile rb_atomic_t *ptr) #elif defined(__sun) && defined(HAVE_ATOMIC_H) atomic_dec_uint(ptr); +#elif defined(HAVE_STDATOMIC_H) + rbimpl_atomic_sub(ptr, 1); + #else # error Unsupported platform. #endif @@ -701,6 +734,9 @@ rbimpl_atomic_size_dec(volatile size_t *ptr) rbimpl_atomic_size_sub(ptr, 1); +#elif defined(HAVE_STDATOMIC_H) + rbimpl_atomic_size_sub(ptr, 1); + #else # error Unsupported platform. #endif @@ -739,6 +775,9 @@ rbimpl_atomic_or(volatile rb_atomic_t *ptr, rb_atomic_t val) #elif defined(__sun) && defined(HAVE_ATOMIC_H) atomic_or_uint(ptr, val); +#elif !defined(_WIN32) && defined(HAVE_STDATOMIC_H) + *(_Atomic volatile rb_atomic_t *)ptr |= val; + #else # error Unsupported platform. #endif @@ -773,6 +812,9 @@ rbimpl_atomic_exchange(volatile rb_atomic_t *ptr, rb_atomic_t val) #elif defined(__sun) && defined(HAVE_ATOMIC_H) return atomic_swap_uint(ptr, val); +#elif defined(HAVE_STDATOMIC_H) + return atomic_exchange((_Atomic volatile rb_atomic_t *)ptr, val); + #else # error Unsupported platform. #endif @@ -805,6 +847,9 @@ rbimpl_atomic_size_exchange(volatile size_t *ptr, size_t val) const rb_atomic_t ret = rbimpl_atomic_exchange(tmp, val); return RBIMPL_CAST((size_t)ret); +#elif defined(HAVE_STDATOMIC_H) + return atomic_exchange((_Atomic volatile size_t *)ptr, val); + #else # error Unsupported platform. #endif @@ -957,6 +1002,11 @@ rbimpl_atomic_cas(volatile rb_atomic_t *ptr, rb_atomic_t oldval, rb_atomic_t new #elif defined(__sun) && defined(HAVE_ATOMIC_H) return atomic_cas_uint(ptr, oldval, newval); +#elif defined(HAVE_STDATOMIC_H) + atomic_compare_exchange_strong( + (_Atomic volatile rb_atomic_t *)ptr, &oldval, newval); + return oldval; + #else # error Unsupported platform. #endif @@ -999,6 +1049,11 @@ rbimpl_atomic_size_cas(volatile size_t *ptr, size_t oldval, size_t newval) volatile rb_atomic_t *tmp = RBIMPL_CAST((volatile rb_atomic_t *)ptr); return rbimpl_atomic_cas(tmp, oldval, newval); +#elif defined(HAVE_STDATOMIC_H) + atomic_compare_exchange_strong( + (_Atomic volatile size_t *)ptr, &oldval, newval); + return oldval; + #else # error Unsupported platform. #endif