From af6a6a2a62cec1e363b622cc801aa745e71977df Mon Sep 17 00:00:00 2001 From: Kazuki Tsujimoto Date: Thu, 6 Nov 2025 00:35:37 +0900 Subject: [PATCH 1/9] Update power_assert to 3.0.1 --- gems/bundled_gems | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/bundled_gems b/gems/bundled_gems index 3ff5e3f6b013f4..01929b290b2186 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -7,7 +7,7 @@ # if `revision` is not given, "v"+`version` or `version` will be used. minitest 5.26.0 https://github.com/minitest/minitest -power_assert 3.0.0 https://github.com/ruby/power_assert +power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake test-unit 3.7.0 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml From 57040636df10cf46d8ad0c294dbbe3b5756e8c21 Mon Sep 17 00:00:00 2001 From: git Date: Wed, 5 Nov 2025 15:36:26 +0000 Subject: [PATCH 2/9] Update bundled gems list as of 2025-11-05 --- NEWS.md | 5 +++-- gems/bundled_gems | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/NEWS.md b/NEWS.md index 0aa4aa63b36dac..b22cf5c290e9ae 100644 --- a/NEWS.md +++ b/NEWS.md @@ -213,9 +213,9 @@ The following bundled gems are added. The following bundled gems are updated. * minitest 5.26.0 -* power_assert 3.0.0 +* power_assert 3.0.1 * rake 13.3.1 -* test-unit 3.7.0 +* test-unit 3.7.1 * rexml 3.4.4 * net-ftp 0.3.9 * net-imap 0.5.12 @@ -223,6 +223,7 @@ The following bundled gems are updated. * matrix 0.4.3 * prime 0.1.4 * rbs 3.9.5 +* typeprof 0.31.0 * debug 1.11.0 * base64 0.3.0 * bigdecimal 3.3.1 diff --git a/gems/bundled_gems b/gems/bundled_gems index 01929b290b2186..114e2f4e6c2de5 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -9,7 +9,7 @@ minitest 5.26.0 https://github.com/minitest/minitest power_assert 3.0.1 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake -test-unit 3.7.0 https://github.com/test-unit/test-unit +test-unit 3.7.1 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.9 https://github.com/ruby/net-ftp @@ -19,7 +19,7 @@ net-smtp 0.5.1 https://github.com/ruby/net-smtp matrix 0.4.3 https://github.com/ruby/matrix prime 0.1.4 https://github.com/ruby/prime rbs 3.9.5 https://github.com/ruby/rbs -typeprof 0.30.1 https://github.com/ruby/typeprof +typeprof 0.31.0 https://github.com/ruby/typeprof debug 1.11.0 https://github.com/ruby/debug racc 1.8.1 https://github.com/ruby/racc mutex_m 0.3.0 https://github.com/ruby/mutex_m From 54907db8f3daa6d096e78e7eb78e515842c47789 Mon Sep 17 00:00:00 2001 From: Luke Gruber Date: Wed, 5 Nov 2025 12:42:08 -0500 Subject: [PATCH 3/9] Fix ractor move of object with generic ivars (#15056) This bug was happening only when the `id2ref` table exists. We need to replace the generic fields before replacing the object id of the newly moved object. Fixes [Bug #21664] --- bootstraptest/test_ractor.rb | 13 +++++++++++++ ractor.c | 5 ++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/bootstraptest/test_ractor.rb b/bootstraptest/test_ractor.rb index 25af87b610ef83..834eb627ef0e90 100644 --- a/bootstraptest/test_ractor.rb +++ b/bootstraptest/test_ractor.rb @@ -1969,6 +1969,19 @@ def ==(o) roundtripped_obj.instance_variable_get(:@array1) == [1] ? :ok : roundtripped_obj } +# move object with generic ivars and existing id2ref table +# [Bug #21664] +assert_equal 'ok', %q{ + obj = [1] + obj.instance_variable_set("@field", :ok) + ObjectSpace._id2ref(obj.object_id) # build id2ref table + + ractor = Ractor.new { Ractor.receive } + ractor.send(obj, move: true) + obj = ractor.value + obj.instance_variable_get("@field") +} + # copy object with complex generic ivars assert_equal 'ok', %q{ # Make Array too_complex diff --git a/ractor.c b/ractor.c index 70594443d6ffbf..48fbf7cfb94404 100644 --- a/ractor.c +++ b/ractor.c @@ -1973,13 +1973,12 @@ move_leave(VALUE obj, struct obj_traverse_replace_data *data) rb_gc_writebarrier_remember(data->replacement); void rb_replace_generic_ivar(VALUE clone, VALUE obj); // variable.c - - rb_gc_obj_id_moved(data->replacement); - if (UNLIKELY(rb_obj_exivar_p(obj))) { rb_replace_generic_ivar(data->replacement, obj); } + rb_gc_obj_id_moved(data->replacement); + VALUE flags = T_OBJECT | FL_FREEZE | (RBASIC(obj)->flags & FL_PROMOTED); // Avoid mutations using bind_call, etc. From d3d2357a6c87328769ca7496bf8525e8072b1834 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 5 Nov 2025 09:57:59 -0800 Subject: [PATCH 4/9] ZJIT: Run ruby-bench CI for macOS arm64 as well (#15040) --- .github/workflows/zjit-macos.yml | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index f687672ac7a9e3..85c4302737a9b2 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -150,6 +150,58 @@ jobs: working-directory: if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }} + # Separated from `make` job to avoid making it a required status check for now + ruby-bench: + strategy: + matrix: + include: + # Test --call-threshold=2 with 2 iterations in total + - ruby_opts: '--zjit-call-threshold=2' + bench_opts: '--warmup=1 --bench=1' + configure: '--enable-zjit=dev_nodebug' # --enable-zjit=dev is too slow + + runs-on: macos-14 + + if: >- + ${{!(false + || contains(github.event.head_commit.message, '[DOC]') + || contains(github.event.pull_request.title, '[DOC]') + || contains(github.event.pull_request.labels.*.name, 'Documentation') + || (github.event_name == 'push' && github.event.pull_request.user.login == 'dependabot[bot]') + )}} + + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - uses: ./.github/actions/setup/macos + + - uses: ./.github/actions/setup/directories + with: + srcdir: src + builddir: build + makeup: true + + - name: Run configure + run: ../src/configure -C --disable-install-doc --prefix="$(pwd)/install" ${{ matrix.configure }} + + - run: make install + + - name: Checkout ruby-bench + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + repository: ruby/ruby-bench + path: ruby-bench + + - name: Run ruby-bench + run: ruby run_benchmarks.rb -e "zjit::../build/install/bin/ruby ${{ matrix.ruby_opts }}" ${{ matrix.bench_opts }} + working-directory: ruby-bench + + - uses: ./.github/actions/slack + with: + label: ruby-bench ${{ matrix.bench_opts }} ${{ matrix.ruby_opts }} + SLACK_WEBHOOK_URL: ${{ secrets.SIMPLER_ALERTS_URL }} # ruby-lang slack: ruby/simpler-alerts-bot + if: ${{ failure() }} + defaults: run: working-directory: build From 7334244e001910ed1fc224cb7a59c7e9c7ae8d53 Mon Sep 17 00:00:00 2001 From: Sam Partington Date: Wed, 5 Nov 2025 18:00:57 +0000 Subject: [PATCH 5/9] [ruby/erb] Fix tag shown in example of ERB expression tag and execution tag (https://github.com/ruby/erb/pull/92) These were the wrong way around. https://github.com/ruby/erb/commit/50a5cd76fe --- lib/erb.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/erb.rb b/lib/erb.rb index d3bbc4979c9375..b1f4e9361b6384 100644 --- a/lib/erb.rb +++ b/lib/erb.rb @@ -60,7 +60,7 @@ # \ERB supports tags of three kinds: # # - [Expression tags][expression tags]: -# each begins with `'<%'`, ends with `'%>'`; contains a Ruby expression; +# each begins with `'<%='`, ends with `'%>'`; contains a Ruby expression; # in the result, the value of the expression replaces the entire tag: # # template = 'The magic word is <%= magic_word %>.' @@ -77,7 +77,7 @@ # ERB.new('Today is <%= Date::DAYNAMES[Date.today.wday] %>.').result # => "Today is Monday." # # - [Execution tags][execution tags]: -# each begins with `'<%='`, ends with `'%>'`; contains Ruby code to be executed: +# each begins with `'<%'`, ends with `'%>'`; contains Ruby code to be executed: # # template = '<% File.write("t.txt", "Some stuff.") %>' # ERB.new(template).result From 242d8edbebc34f88313748f9fedf14367c6d409b Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 5 Nov 2025 10:23:35 -0800 Subject: [PATCH 6/9] Extend timeout for unstable tests https://github.com/ruby/ruby/actions/runs/19111531630/job/54609629054 --- test/ruby/test_autoload.rb | 6 ++++++ test/test_extlibs.rb | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/test/ruby/test_autoload.rb b/test/ruby/test_autoload.rb index 607f0e3355efe9..7b6a0b5712556d 100644 --- a/test/ruby/test_autoload.rb +++ b/test/ruby/test_autoload.rb @@ -613,4 +613,10 @@ module SomeNamespace RUBY end end + + private + + def assert_separately(*args, **kwargs) + super(*args, **{ timeout: 60 }.merge(kwargs)) + end end diff --git a/test/test_extlibs.rb b/test/test_extlibs.rb index 8969c3c50fb4d7..122eca3f5c5f29 100644 --- a/test/test_extlibs.rb +++ b/test/test_extlibs.rb @@ -10,7 +10,7 @@ def self.check_existence(ext, add_msg = nil) add_msg = ". #{add_msg}" if add_msg log = "#{@extdir}/#{ext}/mkmf.log" define_method("test_existence_of_#{ext}") do - assert_separately([], <<-"end;", ignore_stderr: true) # do + assert_separately([], <<-"end;", ignore_stderr: true, timeout: 60) # do log = #{log.dump} msg = proc { "extension library `#{ext}' is not found#{add_msg}\n" << From df290e11d928e67b0ffdf3cc767e0d4ad6f01b29 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 5 Nov 2025 10:28:18 -0800 Subject: [PATCH 7/9] Skip an unstable IO test for mswin https://github.com/ruby/ruby/actions/runs/19107764906/job/54596244201 --- bootstraptest/test_io.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bootstraptest/test_io.rb b/bootstraptest/test_io.rb index 4e5d6d59c9bd36..4081769a8c05ac 100644 --- a/bootstraptest/test_io.rb +++ b/bootstraptest/test_io.rb @@ -85,7 +85,7 @@ ARGF.set_encoding "foo" } -/freebsd/ =~ RUBY_PLATFORM or +/(freebsd|mswin)/ =~ RUBY_PLATFORM or 10.times do assert_normal_exit %q{ at_exit { p :foo } From d327eb6046ad9dc0bb6c24ceb23ce69061011164 Mon Sep 17 00:00:00 2001 From: Randy Stauner Date: Wed, 5 Nov 2025 11:30:00 -0700 Subject: [PATCH 8/9] ZJIT: Track guard shape exit ratio (#15052) new ZJIT stats excerpt from liquid-runtime: ``` vm_read_from_parent_iseq_local_count: 10,909,753 guard_type_count: 45,109,441 guard_type_exit_ratio: 4.3% guard_shape_count: 15,272,133 guard_shape_exit_ratio: 20.1% code_region_bytes: 3,899,392 ``` lobsters ``` guard_type_count: 71,765,580 guard_type_exit_ratio: 4.3% guard_shape_count: 21,872,560 guard_shape_exit_ratio: 8.0% ``` railsbench ``` guard_type_count: 117,661,124 guard_type_exit_ratio: 0.7% guard_shape_count: 28,032,665 guard_shape_exit_ratio: 5.1% ``` shipit ``` guard_type_count: 106,195,615 guard_type_exit_ratio: 3.5% guard_shape_count: 33,672,673 guard_shape_exit_ratio: 10.1% ``` --- zjit.rb | 5 ++++- zjit/src/codegen.rs | 1 + zjit/src/stats.rs | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/zjit.rb b/zjit.rb index cfe8bcd6e2cfb1..5156c72f43410d 100644 --- a/zjit.rb +++ b/zjit.rb @@ -152,6 +152,7 @@ def stats_string stats = self.stats stats[:guard_type_exit_ratio] = stats[:exit_guard_type_failure].to_f / stats[:guard_type_count] * 100 + stats[:guard_shape_exit_ratio] = stats[:exit_guard_shape_failure].to_f / stats[:guard_shape_count] * 100 # Show counters independent from exit_* or dynamic_send_* print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20) @@ -206,6 +207,8 @@ def stats_string :guard_type_count, :guard_type_exit_ratio, + :guard_shape_count, + :guard_shape_exit_ratio, :code_region_bytes, :side_exit_count, @@ -242,7 +245,7 @@ def print_counters(keys, buf:, stats:, right_align: false, base: nil) case key when :ratio_in_zjit value = '%0.1f%%' % value - when :guard_type_exit_ratio + when :guard_type_exit_ratio, :guard_shape_exit_ratio value = '%0.1f%%' % value when /_time_ns\z/ key = key.to_s.sub(/_time_ns\z/, '_time') diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 01212ac88cdbfa..364b9225fe07e6 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -964,6 +964,7 @@ fn gen_array_extend(jit: &mut JITState, asm: &mut Assembler, left: Opnd, right: } fn gen_guard_shape(jit: &mut JITState, asm: &mut Assembler, val: Opnd, shape: ShapeId, state: &FrameState) -> Opnd { + gen_incr_counter(asm, Counter::guard_shape_count); let shape_id_offset = unsafe { rb_shape_id_offset() }; let val = asm.load(val); let shape_opnd = Opnd::mem(SHAPE_ID_NUM_BITS as u8, val, shape_id_offset); diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 30a09669940679..aea28db28b8804 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -286,6 +286,7 @@ make_counters! { // The number of times we ran a dynamic check guard_type_count, + guard_shape_count, } /// Increase a counter by a specified amount From bf0331b907ae7f25b9464081682218ab74e3ccb6 Mon Sep 17 00:00:00 2001 From: Takashi Kokubun Date: Wed, 5 Nov 2025 10:44:40 -0800 Subject: [PATCH 9/9] ZJIT: Add zjit_alloc_bytes and total_mem_bytes stats (#15059) --- zjit.rb | 3 +++ zjit/src/stats.rs | 7 +++++-- zjit/src/virtualmem.rs | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/zjit.rb b/zjit.rb index 5156c72f43410d..3ee9880a5dde29 100644 --- a/zjit.rb +++ b/zjit.rb @@ -211,6 +211,9 @@ def stats_string :guard_shape_exit_ratio, :code_region_bytes, + :zjit_alloc_bytes, + :total_mem_bytes, + :side_exit_count, :total_insn_count, :vm_insn_count, diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index aea28db28b8804..12e6e3aa8d0f87 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -550,7 +550,10 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) -> } // Memory usage stats - set_stat_usize!(hash, "code_region_bytes", ZJITState::get_code_block().mapped_region_size()); + let code_region_bytes = ZJITState::get_code_block().mapped_region_size(); + set_stat_usize!(hash, "code_region_bytes", code_region_bytes); + set_stat_usize!(hash, "zjit_alloc_bytes", zjit_alloc_bytes()); + set_stat_usize!(hash, "total_mem_bytes", code_region_bytes + zjit_alloc_bytes()); // End of default stats. Every counter beyond this is provided only for --zjit-stats. if !get_option!(stats) { @@ -645,7 +648,7 @@ pub fn with_time_stat(counter: Counter, func: F) -> R where F: FnOnce() -> } /// The number of bytes ZJIT has allocated on the Rust heap. -pub fn zjit_alloc_size() -> usize { +pub fn zjit_alloc_bytes() -> usize { jit::GLOBAL_ALLOCATOR.alloc_size.load(Ordering::SeqCst) } diff --git a/zjit/src/virtualmem.rs b/zjit/src/virtualmem.rs index 2717dfcf225f23..770fbfba47d1c8 100644 --- a/zjit/src/virtualmem.rs +++ b/zjit/src/virtualmem.rs @@ -5,7 +5,7 @@ use std::ptr::NonNull; use crate::cruby::*; -use crate::stats::zjit_alloc_size; +use crate::stats::zjit_alloc_bytes; pub type VirtualMem = VirtualMemory; @@ -199,7 +199,7 @@ impl VirtualMemory { // Ignore zjit_alloc_size() if self.memory_limit_bytes is None for testing let mut required_region_bytes = page_addr + page_size - start as usize; if self.memory_limit_bytes.is_some() { - required_region_bytes += zjit_alloc_size(); + required_region_bytes += zjit_alloc_bytes(); } assert!((start..=whole_region_end).contains(&mapped_region_end));