From 2d428da1f4e54990af29aff792540f7b446465b5 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sun, 26 Jul 2026 15:53:11 -0500 Subject: [PATCH 01/14] [DOC] Doc for Pathname#writable? --- pathname_builtin.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index c6edf14e58039c..93ade85a3c4842 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -2425,7 +2425,24 @@ def sticky?() FileTest.sticky?(@path) end # def symlink?() FileTest.symlink?(@path) end - # See FileTest.writable?. + # :markup: markdown + # + # call-seq: + # writable? => true or false + # + # Returns whether the path in `self` points to an entry that is writable + # by the owner and group of the current process: + # + # ```ruby + # pn = Pathname('/tmp/secret.txt') + # pn.write('foo') + # pn.writable? # => true + # pn.chmod(0o000) + # pn.writable? # => false + # pn.delete + # Pathname('nosuch').writable? # => false + # ``` + # def writable?() FileTest.writable?(@path) end # :markup: markdown From 7404a901825423ef470d1a6ddb1ec2f6d537140b Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Sun, 26 Jul 2026 16:00:31 -0500 Subject: [PATCH 02/14] [DOC] Doc for Pathname#writable_real? --- pathname_builtin.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index 93ade85a3c4842..cf8f8665d4eb13 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -2283,7 +2283,6 @@ def world_readable?() File.world_readable?(@path) end # :markup: markdown # # call-seq: - # # readable_real? -> true or false # # Like #readable?, but checks against the real user and group ids @@ -2469,7 +2468,13 @@ def writable?() FileTest.writable?(@path) end # def world_writable?() File.world_writable?(@path) end - # See FileTest.writable_real?. + # :markup: markdown + # + # call-seq: + # writable_real? -> true or false + # + # Like #writable?, but checks against the real user and group ids + # instead of the effective ids. def writable_real?() FileTest.writable_real?(@path) end # See FileTest.zero?. From a9a3ace7e5a3866a069a606cd26eb26e681374bc Mon Sep 17 00:00:00 2001 From: Ajay Krishnan <50063680+ajaynomics@users.noreply.github.com> Date: Mon, 27 Jul 2026 06:24:51 -0700 Subject: [PATCH 03/14] [DOC] Fix Dir scan documentation --- dir.c | 55 +++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/dir.c b/dir.c index c24030b59f9f0e..d9536b200ba22b 100644 --- a/dir.c +++ b/dir.c @@ -3731,14 +3731,26 @@ dir_collect_children(VALUE dir) /* * call-seq: - * children -> array + * scan -> entries + * scan {|entry_name, entry_type| ... } -> nil * - * Returns an array of the entry names in +self+ along with their type - * except for '.' and '..': + * Scans the entries in +self+, except for '.' and '..'. + * + * With no block given, returns +entries+, an array of 2-element arrays. + * Each nested array contains an entry name and its type: * * dir = Dir.new('/example') * dir.scan # => [["config.h", :file], ["lib", :directory], ["main.rb", :file]] * + * With a block given, calls the block with each entry name and its type; + * returns +nil+: + * + * entries = [] + * dir.scan do |entry_name, entry_type| + * entries << [entry_name, entry_type] + * end # => nil + * entries # => [["config.h", :file], ["lib", :directory], ["main.rb", :file]] + * */ static VALUE dir_scan_children(VALUE dir) @@ -3784,25 +3796,36 @@ dir_s_children(int argc, VALUE *argv, VALUE io) /* * call-seq: + * Dir.scan(dirpath) -> entries + * Dir.scan(dirpath, encoding: 'UTF-8') -> entries * Dir.scan(dirpath) {|entry_name, entry_type| ... } -> nil * Dir.scan(dirpath, encoding: 'UTF-8') {|entry_name, entry_type| ... } -> nil - * Dir.scan(dirpath) -> [[entry_name, entry_type], ...] - * Dir.scan(dirpath, encoding: 'UTF-8') -> [[entry_name, entry_type], ...] * - * Yields or returns an array of the entry names in the directory at +dirpath+ - * associated with their type, except for '.' and '..'; - * sets the given encoding onto each returned entry name. + * Scans the entries in the directory at +dirpath+, except for '.' + * and '..'. + * + * With no block given, returns +entries+, an array of 2-element arrays. + * Each nested array contains an entry name and its type: * - * The type symbol is one of: - * ``:file'', ``:directory'', - * ``:characterSpecial'', ``:blockSpecial'', - * ``:fifo'', ``:link'', - * or ``:socket'': + * Dir.scan('/example') # => [["config.h", :file], ["lib", :directory], ["main.rb", :file]] * - * Dir.children('/example') # => [["config.h", :file], ["lib", :directory], ["main.rb", :file]] - * Dir.children('/example').first.first.encoding + * With a block given, calls the block with each entry name and its type; + * returns +nil+: + * + * entries = [] + * Dir.scan('/example') do |entry_name, entry_type| + * entries << [entry_name, entry_type] + * end # => nil + * entries # => [["config.h", :file], ["lib", :directory], ["main.rb", :file]] + * + * The type symbol is one of +:file+, +:directory+, +:characterSpecial+, + * +:blockSpecial+, +:fifo+, +:link+, +:socket+, or +:unknown+. + * + * The given +encoding+ is used as the external encoding for each entry name: + * + * Dir.scan('/example').first.first.encoding * # => # - * Dir.children('/example', encoding: 'US-ASCII').first.encoding + * Dir.scan('/example', encoding: 'US-ASCII').first.first.encoding * # => # * * See {String Encoding}[rdoc-ref:encodings.rdoc@String+Encoding]. From 86df3aa1bc121cd61c55f71f75c75adcd2822e69 Mon Sep 17 00:00:00 2001 From: Ajay Krishnan <50063680+ajaynomics@users.noreply.github.com> Date: Mon, 27 Jul 2026 06:28:47 -0700 Subject: [PATCH 04/14] [DOC] Fix String matching offset semantics (#17932) --- string.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/string.c b/string.c index b54051f1a6f000..33377dc39497d3 100644 --- a/string.c +++ b/string.c @@ -5136,10 +5136,8 @@ static VALUE get_pat(VALUE); * * regexp = Regexp.new(pattern) * - * - Computes +matchdata+, which will be either a MatchData object or +nil+ - * (see Regexp#match): - * - * matchdata = regexp.match(self[offset..]) + * - Calls regexp.match with +self+ to compute +matchdata+. + * If +offset+ is given, it is also passed (see Regexp#match). * * With no block given, returns the computed +matchdata+ or +nil+: * @@ -5187,8 +5185,8 @@ rb_str_match_m(int argc, VALUE *argv, VALUE str) * * regexp = Regexp.new(pattern) * - * Returns +true+ if self[offset..].match(regexp) returns a MatchData object, - * +false+ otherwise: + * The search for +regexp+ in +self+ begins at the given character +offset+. + * Returns +true+ if a match is found, +false+ otherwise: * * 'foo'.match?(/o/) # => true * 'foo'.match?('o') # => true From a8b36afa98dca48c33e66197d90f63e61d7a3a20 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 27 Jul 2026 09:44:30 -0500 Subject: [PATCH 05/14] [DOC] Doc for Pathname#zero? (#18075) --- pathname_builtin.rb | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/pathname_builtin.rb b/pathname_builtin.rb index cf8f8665d4eb13..d20c5b0b3228dd 100644 --- a/pathname_builtin.rb +++ b/pathname_builtin.rb @@ -2477,7 +2477,31 @@ def world_writable?() File.world_writable?(@path) end # instead of the effective ids. def writable_real?() FileTest.writable_real?(@path) end - # See FileTest.zero?. + # :markup: markdown + # + # call-seq: + # zero? -> true or false + # + # Returns whether the entry represented by `self` exists and has size zero: + # + # ``` + # dir_pn = Pathname('example_dir') + # dir_pn.zero? # => false # Dir does not exist. + # dir_pn.mkdir + # dir_pn.zero? # => false # Directory never has size zero. + # dir_pn.empty? # => true # But this one is empty. + # + # file_pn = Pathname('example_dir/example.txt') + # file_pn.zero? # => false # File does not exist. + # file_pn.write('') + # file_pn.zero? # => true + # file_pn.write('foo') + # file_pn.zero? # => false + # + # file_pn.delete + # dir_pn.delete + # ``` + # def zero?() FileTest.zero?(@path) end end From 388ea3c3cf19380bf47916a4e6babc16886cd8bc Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Fri, 24 Jul 2026 08:36:08 -0700 Subject: [PATCH 06/14] Compact sets after removal of items for more methods This changes the following methods to compact the set after removal: * `^` * `-` * `difference` * `subtract` * `keep_if` * `select!` * `filter!` Fixes [#22210] --- set.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/set.c b/set.c index 379abadee8bec1..39a2f7d1a53391 100644 --- a/set.c +++ b/set.c @@ -1472,6 +1472,7 @@ set_i_xor(VALUE set, VALUE other) set_merge_enum_into(tmp, other); set_iter(tmp, set_xor_i, (st_data_t)new_set); } + set_compact_after_delete(set); return new_set; } @@ -1523,6 +1524,7 @@ set_remove_enum_from(VALUE set, VALUE arg) else { rb_block_call(arg, enum_method_id(arg), 0, 0, set_remove_block, (VALUE)set); } + set_compact_after_delete(set); } /* @@ -1667,6 +1669,7 @@ set_i_keep_if(VALUE set) rb_check_frozen(set); set_iter(set, set_keep_if_i, (st_data_t)RSET_TABLE(set)); + set_compact_after_delete(set); return set; } @@ -1696,6 +1699,7 @@ set_i_select(VALUE set) set_table *table = RSET_TABLE(set); size_t n = set_table_size(table); set_iter(set, set_keep_if_i, (st_data_t)table); + set_compact_after_delete(set); return (n == set_table_size(table)) ? Qnil : set; } @@ -1731,6 +1735,7 @@ set_i_replace(VALUE set, VALUE other) set_table_clear(RSET_TABLE(set)); set_merge_enum_into(set, other); } + set_compact_after_delete(set); return set; } From 6731f1b1cde96677548d531c5fb10be0b44f5f0b Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 27 Jul 2026 13:10:13 -0500 Subject: [PATCH 07/14] [DOC] Fix link in maintainers doc --- doc/maintainers.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/maintainers.md b/doc/maintainers.md index 51aeb41d13db30..494716c6dba5e0 100644 --- a/doc/maintainers.md +++ b/doc/maintainers.md @@ -126,7 +126,7 @@ consensus on ruby-core/ruby-dev. * *No maintainer* * https://github.com/ruby/English -* https://rubygems.org/gems/English +* https://rubygems.org/gems/english #### lib/delegate.rb From fa14b4c051bb0a79f7b725004d96fd3c556a7597 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Mon, 27 Jul 2026 11:29:05 -0700 Subject: [PATCH 08/14] ZJIT: Strength-reduce Integer#[] with a constant index (#18056) --- zjit/src/codegen_tests.rs | 32 ++++++++++++++++++++ zjit/src/cruby_methods.rs | 18 ++++++++++++ zjit/src/hir/opt_tests.rs | 61 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+) diff --git a/zjit/src/codegen_tests.rs b/zjit/src/codegen_tests.rs index 3cd3a9cfa67819..22fde4a9ef21c7 100644 --- a/zjit/src/codegen_tests.rs +++ b/zjit/src/codegen_tests.rs @@ -2842,6 +2842,38 @@ fn test_fixnum_mod_negative() { assert_snapshot!(assert_compiles("[test(-7, 3), test(7, -3), test(-7, -3)]"), @"[2, -2, -1]"); } +#[test] +fn test_fixnum_aref_constant_index() { + eval(" + def test(a) = a[12] + test(4096) # profile opt_aref + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(assert_compiles("[test(4096), test(4095), test(0), test(-1), test(-4096)]"), @"[1, 0, 0, 1, 1]"); +} + +#[test] +fn test_fixnum_aref_constant_index_beyond_fixnum_width() { + // An index beyond the fixnum width is not strength-reduced; FixnumAref handles it + eval(" + def test(a) = a[100] + test(1) # profile opt_aref + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(assert_compiles("[test(1), test(-1), test(4611686018427387903), test(-4611686018427387904)]"), @"[0, 1, 0, 1]"); +} + +#[test] +fn test_fixnum_aref_constant_index_bignum_receiver() { + // A Bignum receiver fails the Fixnum guard and side-exits to the correct result + eval(" + def test(a) = a[1] + test(5) # profile opt_aref + "); + assert_contains_opcode("test", YARVINSN_opt_aref); + assert_snapshot!(assert_compiles_allowing_exits("[test(5), test(2**100 + 2)]"), @"[0, 1]"); +} + #[test] fn test_fixnum_mod_by_zero() { eval(" diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index f2a62456bc8279..43bda091ae2996 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -800,6 +800,24 @@ fn inline_integer_rshift(fun: &mut hir::Function, block: hir::BlockId, recv: hir fn inline_integer_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { let &[index] = args else { return None; }; + + // Optimize a compile-time constant index that fits in the Fixnum payload as a bit test. + // A Fixnum payload is VALUE_BITS - 1 bits wide (one bit is the tag), so its highest + // (sign) bit is at index VALUE_BITS - 2. + const FIXNUM_SIGN_BIT_INDEX: i64 = VALUE_BITS as i64 - 2; + if fun.likely_a(recv, types::Fixnum, state) { + if let Some(index_value) = fun.type_of(index).fixnum_value() { + if (0..=FIXNUM_SIGN_BIT_INDEX).contains(&index_value) { + // Optimize `recv[i]` into `(recv >> i) & 1`. + let recv = fun.coerce_to(block, recv, types::Fixnum, state); + let shift = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(VALUE::fixnum_from_usize(index_value as usize)) }); + let shifted = fun.push_insn(block, hir::Insn::FixnumRShift { left: recv, right: shift }); + let mask = fun.push_insn(block, hir::Insn::Const { val: hir::Const::Value(VALUE::fixnum_from_usize(1)) }); + return Some(fun.push_insn(block, hir::Insn::FixnumAnd { left: shifted, right: mask })); + } + } + } + // Use a C call (FixnumAref) for any other (e.g. non-constant) index. if fun.likely_a(recv, types::Fixnum, state) && fun.likely_a(index, types::Fixnum, state) { let recv = fun.coerce_to(block, recv, types::Fixnum, state); let index = fun.coerce_to(block, index, types::Fixnum, state); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index 85b887983606b0..9cc439834b07f0 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -2064,6 +2064,67 @@ mod hir_opt_tests { "); } + #[test] + fn integer_aref_with_constant_index_strength_reduced() { + eval(" + def test(a) = a[12] + test(4096) + "); + assert_snapshot!(hir_string("test"), @" + fn test@:2: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:CPtr = LoadSP + v3:BasicObject = LoadField v2, :a@0x1000 + Jump bb3(v1, v3) + bb2(): + EntryPoint JIT(0) + v6:BasicObject = LoadArg :self@0 + v7:BasicObject = LoadArg :a@1 + Jump bb3(v6, v7) + bb3(v9:BasicObject, v10:BasicObject): + v15:Fixnum[12] = Const Value(12) + PatchPoint MethodRedefined(Integer@0x1008, []@0x1010, cme:0x1018) + v26:Fixnum = GuardType v10, Fixnum recompile + v27:Fixnum[12] = Const Value(12) + v28:Fixnum = FixnumRShift v26, v27 + v29:Fixnum[1] = Const Value(1) + v30:Fixnum = FixnumAnd v28, v29 + CheckInterrupts + Return v30 + "); + } + + #[test] + fn integer_aref_with_constant_index_beyond_fixnum_width() { + eval(" + def test(a) = a[100] + test(-1) + "); + assert_snapshot!(hir_string("test"), @" + fn test@:2: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:CPtr = LoadSP + v3:BasicObject = LoadField v2, :a@0x1000 + Jump bb3(v1, v3) + bb2(): + EntryPoint JIT(0) + v6:BasicObject = LoadArg :self@0 + v7:BasicObject = LoadArg :a@1 + Jump bb3(v6, v7) + bb3(v9:BasicObject, v10:BasicObject): + v15:Fixnum[100] = Const Value(100) + PatchPoint MethodRedefined(Integer@0x1008, []@0x1010, cme:0x1018) + v26:Fixnum = GuardType v10, Fixnum recompile + v27:Fixnum = FixnumAref v26, v15 + CheckInterrupts + Return v27 + "); + } + #[test] fn elide_fixnum_aref() { eval(" From 7c5e7a5032242076912bebd6d6f1924ba9006504 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 27 Jul 2026 16:00:41 -0500 Subject: [PATCH 09/14] [DOC] Remove stale link in Set doc --- set.c | 1 - 1 file changed, 1 deletion(-) diff --git a/set.c b/set.c index 39a2f7d1a53391..289898a6fc1041 100644 --- a/set.c +++ b/set.c @@ -2389,7 +2389,6 @@ rb_set_size(VALUE set) * - {Assigning}[rdoc-ref:Set@Methods+for+Assigning] * - {Deleting}[rdoc-ref:Set@Methods+for+Deleting] * - {Converting}[rdoc-ref:Set@Methods+for+Converting] - * - {Iterating}[rdoc-ref:Set@Methods+for+Iterating] * - {And more....}[rdoc-ref:Set@Other+Methods] * * === Methods for Creating a \Set From f4cd656f2e1607775905c65447797b55aa575547 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Mon, 27 Jul 2026 16:01:38 -0500 Subject: [PATCH 10/14] [DOC] Fix link in documentation guide --- doc/contributing/documentation_guide.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/contributing/documentation_guide.md b/doc/contributing/documentation_guide.md index 7c73ad1c50b0f2..e2d1f590247a10 100644 --- a/doc/contributing/documentation_guide.md +++ b/doc/contributing/documentation_guide.md @@ -27,7 +27,7 @@ the browser when you edit source files: make html-server ``` -Then visit http://localhost:4000 in your browser. +Then visit `http://localhost:4000` in your browser. To use a different port: `make html-server RDOC_SERVER_PORT=8080`. If you don't have a build directory, follow the [quick start From 80cb94bcd6c7c9d9738f5bc2aea5e9e16a39003e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Tue, 21 Jul 2026 19:58:42 +0200 Subject: [PATCH 11/14] YJIT (arm64): fix off-by-one in the single-branch range check jmp_ptr_bytes() reserves room for a patchable jump -- it sets page_end_reserve and bounds conditional jump width. A single `b` suffices when every branch in the code region is in imm26 range; otherwise it reserves 5 instructions for an absolute load-address plus br. The range test was `virtual_region_size() / 4`, but two instructions in an S-byte region are at most S-4 bytes apart. At exactly 128MiB, S/4 is 2^25 while the widest imm26 offset is 2^25-1, so the check gave up one instruction early and took the 5-instruction fallback -- despite the comment right above it saying <= 128 should work. That boundary is the default. exec_mem_size falls back to mem_size in CodegenGlobals::init and mem_size defaults to 128MiB, so every arm64 run without an explicit --yjit-exec-mem-size reserved 20 bytes per patchable jump where 4 would do. On a branch-heavy method that is the difference between 1336 and 956 bytes of inline code. Test (S-4)/4 instead. The decision moves into a small jmp_ptr_bytes_for_region() so a unit test can pin both sides of the boundary without allocating a region that large. --yjit-exec-mem-size=129 still takes the fallback, as before. Checked with `make yjit-check` on arm64 macOS and on Linux/Graviton 4: 374 cargo tests, all bootstraptest at --yjit-call-threshold=1, and test_yjit.rb (142 tests, 0 failures). Since the default size is the boundary case, the whole suite ran on the tightened reservation. --- yjit/src/backend/arm64/mod.rs | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/yjit/src/backend/arm64/mod.rs b/yjit/src/backend/arm64/mod.rs index 1712ad0302c641..c6ded9abd29403 100644 --- a/yjit/src/backend/arm64/mod.rs +++ b/yjit/src/backend/arm64/mod.rs @@ -37,9 +37,16 @@ pub const C_SP_STEP: i32 = 16; impl CodeBlock { // The maximum number of bytes that can be generated by emit_jmp_ptr. pub fn jmp_ptr_bytes(&self) -> usize { + Self::jmp_ptr_bytes_for_region(self.virtual_region_size()) + } + + // Split out of jmp_ptr_bytes() so the range boundary can be tested without + // allocating a region that large. + fn jmp_ptr_bytes_for_region(region_size: usize) -> usize { // b instruction's offset is encoded as imm26 times 4. It can jump to // +/-128MiB, so this can be used when --yjit-exec-mem-size <= 128. - let num_insns = if b_offset_fits_bits(self.virtual_region_size() as i64 / 4) { + // The widest in-region branch spans the region minus one instruction. + let num_insns = if b_offset_fits_bits((region_size as i64 - 4) / 4) { 1 // b instruction } else { 5 // 4 instructions to load a 64-bit absolute address + br instruction @@ -1382,6 +1389,18 @@ mod tests { (Assembler::new(0), CodeBlock::new_dummy(1024)) } + #[test] + fn test_jmp_ptr_bytes_at_b_range_boundary() { + // The widest branch in a 128MiB region spans 128MiB - 4 bytes, which is + // still the largest offset a b can encode, so one instruction is enough. + // This is the default region size, so it is the common case. + assert_eq!(4, CodeBlock::jmp_ptr_bytes_for_region(128 * 1024 * 1024)); + + // One instruction further and a b can no longer span the region, so we + // have to reserve room for an absolute load-address plus br. + assert_eq!(20, CodeBlock::jmp_ptr_bytes_for_region(128 * 1024 * 1024 + 4)); + } + #[test] fn test_emit_add() { let (mut asm, mut cb) = setup_asm(); From 28d74f4a11a402b25a0a3c7240c0509c287d5d6e Mon Sep 17 00:00:00 2001 From: XrXr Date: Thu, 23 Jul 2026 12:23:20 -0400 Subject: [PATCH 12/14] ZJIT: A64: No padding for LIR Jump as we do not re-patch Unlike in YJIT, we don't re-patch particular jump instructions, so there is no need to pad. --- zjit/src/backend/arm64/mod.rs | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index cc3eec92df10e0..9d7fcda0f1860b 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -113,7 +113,7 @@ fn emit_jmp_ptr_with_invalidation(cb: &mut CodeBlock, dst_ptr: CodePtr) { }); } -fn emit_jmp_ptr(cb: &mut CodeBlock, dst_ptr: CodePtr, padding: bool) { +fn emit_jmp_ptr(cb: &mut CodeBlock, dst_ptr: CodePtr) { let src_addr = cb.get_write_ptr().as_offset(); let dst_addr = dst_ptr.as_offset(); @@ -121,7 +121,7 @@ fn emit_jmp_ptr(cb: &mut CodeBlock, dst_ptr: CodePtr, padding: bool) { // branch instruction. Otherwise, we'll move the // destination into a register and use the branch // register instruction. - let num_insns = if b_offset_fits_bits((dst_addr - src_addr) / 4) { + if b_offset_fits_bits((dst_addr - src_addr) / 4) { b(cb, InstructionOffset::from_bytes((dst_addr - src_addr) as i32)); 1 } else { @@ -129,16 +129,6 @@ fn emit_jmp_ptr(cb: &mut CodeBlock, dst_ptr: CodePtr, padding: bool) { br(cb, Assembler::EMIT_OPND); num_insns + 1 }; - - if padding { - // Make sure it's always a consistent number of - // instructions in case it gets patched and has to - // use the other branch. - assert!(num_insns * 4 <= cb.jmp_ptr_bytes()); - for _ in num_insns..(cb.jmp_ptr_bytes() / 4) { - nop(cb); - } - } } /// Emit the required instructions to load the given value into the @@ -1478,7 +1468,7 @@ impl Assembler { Insn::Jmp(target) => { match *target { Target::CodePtr(dst_ptr) => { - emit_jmp_ptr(cb, dst_ptr, true); + emit_jmp_ptr(cb, dst_ptr); }, Target::Label(label_idx) => { // Reserve space for a single B instruction From 2529d20b7e1de87db471e74d8c1273561e455317 Mon Sep 17 00:00:00 2001 From: XrXr Date: Thu, 23 Jul 2026 12:23:36 -0400 Subject: [PATCH 13/14] ZJIT: A64: Delete emit_jmp_ptr_with_invalidation() It's only for page switching, which is not a thing in ZJIT. --- zjit/src/backend/arm64/mod.rs | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index 9d7fcda0f1860b..f74098f900c1b9 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -99,20 +99,6 @@ impl From<&Opnd> for A64Opnd { } } -/// Call emit_jmp_ptr and immediately invalidate the written range. -/// This is needed when next_page also moves other_cb that is not invalidated -/// by compile_with_regs. Doing it here allows you to avoid invalidating a lot -/// more than necessary when other_cb jumps from a position early in the page. -/// This invalidates a small range of cb twice, but we accept the small cost. -fn emit_jmp_ptr_with_invalidation(cb: &mut CodeBlock, dst_ptr: CodePtr) { - let start = cb.get_write_ptr(); - emit_jmp_ptr(cb, dst_ptr, true); - let end = cb.get_write_ptr(); - trace_compile_phase("invalidate_icache", || { - unsafe { rb_jit_icache_invalidate(start.raw_ptr(cb) as _, end.raw_ptr(cb) as _) }; - }); -} - fn emit_jmp_ptr(cb: &mut CodeBlock, dst_ptr: CodePtr) { let src_addr = cb.get_write_ptr().as_offset(); let dst_addr = dst_ptr.as_offset(); From 5cf431492cce2017a2d8bb715e64c0bf54dcbe73 Mon Sep 17 00:00:00 2001 From: John Hawthorn Date: Mon, 27 Jul 2026 14:07:07 -0700 Subject: [PATCH 14/14] Add Integer#bit_count to NEWS --- NEWS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/NEWS.md b/NEWS.md index 5fc669c630797e..5d3ec3a703d9c7 100644 --- a/NEWS.md +++ b/NEWS.md @@ -39,6 +39,12 @@ Note: We're only listing outstanding class updates. given names, raising `KeyError` for missing names unless a block is given. [[Feature #21781]] +* Integer + + * `Integer#bit_count` is added. It returns the number of `1` bits in the + binary representation of a non-negative integer (its population count). + [[Feature #20163]] + * Kernel * `Kernel#autoload_relative` and `Module#autoload_relative` are added. @@ -242,6 +248,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [Feature #8948]: https://bugs.ruby-lang.org/issues/8948 [Feature #15330]: https://bugs.ruby-lang.org/issues/15330 +[Feature #20163]: https://bugs.ruby-lang.org/issues/20163 [Feature #21390]: https://bugs.ruby-lang.org/issues/21390 [Feature #21768]: https://bugs.ruby-lang.org/issues/21768 [Feature #21781]: https://bugs.ruby-lang.org/issues/21781