From 019357d131b819847f7c0094dc5a5d16302709fd Mon Sep 17 00:00:00 2001 From: git Date: Thu, 30 Jul 2026 07:38:51 +0000 Subject: [PATCH 1/9] Update bundled gems list as of 2026-07-30 --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.md b/NEWS.md index 5d3ec3a703d9c7..a4de9bdb18ef3a 100644 --- a/NEWS.md +++ b/NEWS.md @@ -176,7 +176,7 @@ releases. * 0.3.0 to [v0.4.0][syslog-v0.4.0] * csv 3.3.6 * 3.3.5 to [v3.3.6][csv-v3.3.6] -* repl_type_completor 0.1.15 +* repl_type_completor 0.1.16 * 0.1.12 to [v0.1.13][repl_type_completor-v0.1.13], [v0.1.14][repl_type_completor-v0.1.14], [v0.1.15][repl_type_completor-v0.1.15] * pstore 0.2.1 * 0.2.0 to [v0.2.1][pstore-v0.2.1] diff --git a/gems/bundled_gems b/gems/bundled_gems index 70fc7e7997c27f..3e4355a07d92e7 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -32,7 +32,7 @@ drb 2.2.3 https://github.com/ruby/drb nkf 0.3.0 https://github.com/ruby/nkf syslog 0.4.0 https://github.com/ruby/syslog csv 3.3.6 https://github.com/ruby/csv -repl_type_completor 0.1.15 https://github.com/ruby/repl_type_completor +repl_type_completor 0.1.16 https://github.com/ruby/repl_type_completor ostruct 0.6.3 https://github.com/ruby/ostruct pstore 0.2.1 https://github.com/ruby/pstore benchmark 0.5.0 https://github.com/ruby/benchmark From c418600fc5152192a2ac4df53e76cc99a4b57d5e Mon Sep 17 00:00:00 2001 From: git Date: Thu, 30 Jul 2026 07:48:21 +0000 Subject: [PATCH 2/9] [DOC] Update bundled gems list at 019357d131b819847f7c0094dc5a5d --- NEWS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index a4de9bdb18ef3a..2501bfd1449a4e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -177,7 +177,7 @@ releases. * csv 3.3.6 * 3.3.5 to [v3.3.6][csv-v3.3.6] * repl_type_completor 0.1.16 - * 0.1.12 to [v0.1.13][repl_type_completor-v0.1.13], [v0.1.14][repl_type_completor-v0.1.14], [v0.1.15][repl_type_completor-v0.1.15] + * 0.1.12 to [v0.1.13][repl_type_completor-v0.1.13], [v0.1.14][repl_type_completor-v0.1.14], [v0.1.15][repl_type_completor-v0.1.15], [v0.1.16][repl_type_completor-v0.1.16] * pstore 0.2.1 * 0.2.0 to [v0.2.1][pstore-v0.2.1] * rdoc 8.0.0 @@ -355,6 +355,7 @@ A lot of work has gone into making Ractors more stable, performant, and usable. [repl_type_completor-v0.1.13]: https://github.com/ruby/repl_type_completor/releases/tag/v0.1.13 [repl_type_completor-v0.1.14]: https://github.com/ruby/repl_type_completor/releases/tag/v0.1.14 [repl_type_completor-v0.1.15]: https://github.com/ruby/repl_type_completor/releases/tag/v0.1.15 +[repl_type_completor-v0.1.16]: https://github.com/ruby/repl_type_completor/releases/tag/v0.1.16 [pstore-v0.2.1]: https://github.com/ruby/pstore/releases/tag/v0.2.1 [rdoc-v7.0.4]: https://github.com/ruby/rdoc/releases/tag/v7.0.4 [rdoc-v7.1.0]: https://github.com/ruby/rdoc/releases/tag/v7.1.0 From c692cc8fba74f4f01e9248172344e2db2060993f Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 30 Jul 2026 16:44:10 +0900 Subject: [PATCH 3/9] [ruby/rubygems] Point bundler.io URLs at guides.rubygems.org bundler.io has been wound down and its content now lives on RubyGems Guides: the Bundler man pages under /command-reference/, the Gemfile reference at /gemfile/, and the remaining guides as individual pages. The redirects still work, but linking to the real locations keeps `gem help gem_dependencies`, `bundle issue`, and the man page cross references from depending on them. Two references stay on bundler.io. The homepage in lib/bundler/source/metadata.rb has to match the published bundler gemspec, and the team pages linked from doc/POLICIES.md have no replacement yet. https://github.com/ruby/rubygems/commit/69aa22cd41 Co-Authored-By: Claude Opus 5 --- lib/bundler.rb | 2 +- lib/bundler/cli/issue.rb | 4 ++-- lib/bundler/man/bundle-add.1 | 2 +- lib/bundler/man/bundle-add.1.ronn | 2 +- lib/bundler/man/bundle-console.1 | 4 ++-- lib/bundler/man/bundle-console.1.ronn | 4 ++-- lib/bundler/man/bundle-init.1 | 2 +- lib/bundler/man/bundle-init.1.ronn | 2 +- lib/bundler/man/bundle-plugin.1 | 2 +- lib/bundler/man/bundle-plugin.1.ronn | 2 +- lib/bundler/man/bundle.1 | 2 +- lib/bundler/man/bundle.1.ronn | 2 +- lib/bundler/man/gemfile.5 | 2 +- lib/bundler/man/gemfile.5.ronn | 2 +- lib/rubygems/commands/help_command.rb | 4 ++-- 15 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/bundler.rb b/lib/bundler.rb index 12dde90fc5d835..5cd92a7f27180c 100644 --- a/lib/bundler.rb +++ b/lib/bundler.rb @@ -26,7 +26,7 @@ # or Bundler.setup to setup environment where only specified gems and their # specified versions could be used. # -# See {Bundler website}[https://bundler.io/docs.html] for extensive documentation +# See {Bundler documentation}[https://guides.rubygems.org/command-reference/bundle/] for extensive documentation # on gemfiles creation and Bundler usage. # # As a standard library inside project, Bundler could be used for introspection diff --git a/lib/bundler/cli/issue.rb b/lib/bundler/cli/issue.rb index cbfb7da2d8723f..332e41afa04c51 100644 --- a/lib/bundler/cli/issue.rb +++ b/lib/bundler/cli/issue.rb @@ -13,10 +13,10 @@ def run https://github.com/ruby/rubygems/blob/master/doc/bundler/TROUBLESHOOTING.md 2. Instructions for common Bundler uses can be found on the documentation - site: https://bundler.io/ + site: https://guides.rubygems.org/ 3. Information about each Bundler command can be found in the Bundler - man pages: https://bundler.io/man/bundle.1.html + man pages: https://guides.rubygems.org/command-reference/bundle/ Hopefully the troubleshooting steps above resolved your problem! If things still aren't working the way you expect them to, please let us know so diff --git a/lib/bundler/man/bundle-add.1 b/lib/bundler/man/bundle-add.1 index 0956aa83f14f54..53fb689658885a 100644 --- a/lib/bundler/man/bundle-add.1 +++ b/lib/bundler/man/bundle-add.1 @@ -79,4 +79,4 @@ You can assign the gem to more than one group\. \fBbundle add rails \-\-group "development, test"\fR .IP "" 0 .SH "SEE ALSO" -Gemfile(5) \fIhttps://bundler\.io/man/gemfile\.5\.html\fR, bundle\-remove(1) \fIbundle\-remove\.1\.html\fR +Gemfile(5) \fIhttps://guides\.rubygems\.org/gemfile/\fR, bundle\-remove(1) \fIbundle\-remove\.1\.html\fR diff --git a/lib/bundler/man/bundle-add.1.ronn b/lib/bundler/man/bundle-add.1.ronn index 8c65af0cc00fd0..4b5648e440d4ee 100644 --- a/lib/bundler/man/bundle-add.1.ronn +++ b/lib/bundler/man/bundle-add.1.ronn @@ -91,5 +91,5 @@ Adds the named gem to the [`Gemfile(5)`][Gemfile(5)] and run `bundle install`. ## SEE ALSO -[Gemfile(5)](https://bundler.io/man/gemfile.5.html), +[Gemfile(5)](https://guides.rubygems.org/gemfile/), [bundle-remove(1)](bundle-remove.1.html) diff --git a/lib/bundler/man/bundle-console.1 b/lib/bundler/man/bundle-console.1 index 5d3f65365faaef..af58cb668ce0b7 100644 --- a/lib/bundler/man/bundle-console.1 +++ b/lib/bundler/man/bundle-console.1 @@ -8,7 +8,7 @@ .SH "DESCRIPTION" Starts an interactive Ruby console session in the context of the current bundle\. .P -If no \fBGROUP\fR is specified, all gems in the \fBdefault\fR group in the Gemfile(5) \fIhttps://bundler\.io/man/gemfile\.5\.html\fR are preliminarily loaded\. +If no \fBGROUP\fR is specified, all gems in the \fBdefault\fR group in the Gemfile(5) \fIhttps://guides\.rubygems\.org/gemfile/\fR are preliminarily loaded\. .P If \fBGROUP\fR is specified, all gems in the given group in the Gemfile in addition to the gems in \fBdefault\fR group are loaded\. Even if the given group does not exist in the Gemfile, IRB console starts without any warning or error\. .P @@ -30,4 +30,4 @@ Resolving dependencies\|\.\|\.\|\. [1] pry(main)> .fi .SH "SEE ALSO" -Gemfile(5) \fIhttps://bundler\.io/man/gemfile\.5\.html\fR +Gemfile(5) \fIhttps://guides\.rubygems\.org/gemfile/\fR diff --git a/lib/bundler/man/bundle-console.1.ronn b/lib/bundler/man/bundle-console.1.ronn index ed842ae1c36ecc..41a0b634333bba 100644 --- a/lib/bundler/man/bundle-console.1.ronn +++ b/lib/bundler/man/bundle-console.1.ronn @@ -9,7 +9,7 @@ bundle-console(1) -- Open an IRB session with the bundle pre-loaded Starts an interactive Ruby console session in the context of the current bundle. -If no `GROUP` is specified, all gems in the `default` group in the [Gemfile(5)](https://bundler.io/man/gemfile.5.html) are +If no `GROUP` is specified, all gems in the `default` group in the [Gemfile(5)](https://guides.rubygems.org/gemfile/) are preliminarily loaded. If `GROUP` is specified, all gems in the given group in the Gemfile in addition @@ -36,4 +36,4 @@ the shell from the following: ## SEE ALSO -[Gemfile(5)](https://bundler.io/man/gemfile.5.html) +[Gemfile(5)](https://guides.rubygems.org/gemfile/) diff --git a/lib/bundler/man/bundle-init.1 b/lib/bundler/man/bundle-init.1 index 63e2376c3fd3fb..52dd08171cf1e1 100644 --- a/lib/bundler/man/bundle-init.1 +++ b/lib/bundler/man/bundle-init.1 @@ -17,4 +17,4 @@ Use the specified name for the gemfile instead of \fBGemfile\fR .SH "FILES" Included in the default [\fBGemfile(5)\fR][Gemfile(5)] generated is the line \fB# frozen_string_literal: true\fR\. This is a magic comment supported for the first time in Ruby 2\.3\. The presence of this line results in all string literals in the file being implicitly frozen\. .SH "SEE ALSO" -Gemfile(5) \fIhttps://bundler\.io/man/gemfile\.5\.html\fR +Gemfile(5) \fIhttps://guides\.rubygems\.org/gemfile/\fR diff --git a/lib/bundler/man/bundle-init.1.ronn b/lib/bundler/man/bundle-init.1.ronn index ab3c427b52ce06..39238931b8fb91 100644 --- a/lib/bundler/man/bundle-init.1.ronn +++ b/lib/bundler/man/bundle-init.1.ronn @@ -29,4 +29,4 @@ results in all string literals in the file being implicitly frozen. ## SEE ALSO -[Gemfile(5)](https://bundler.io/man/gemfile.5.html) +[Gemfile(5)](https://guides.rubygems.org/gemfile/) diff --git a/lib/bundler/man/bundle-plugin.1 b/lib/bundler/man/bundle-plugin.1 index d182c7789ba12f..e200dcfbd1d1ba 100644 --- a/lib/bundler/man/bundle-plugin.1 +++ b/lib/bundler/man/bundle-plugin.1 @@ -71,6 +71,6 @@ Describe subcommands or one specific subcommand\. No options\. .SH "SEE ALSO" .IP "\(bu" 4 -How to write a Bundler plugin \fIhttps://bundler\.io/guides/bundler_plugins\.html\fR +How to write a Bundler plugin \fIhttps://guides\.rubygems\.org/bundler_plugins/\fR .IP "" 0 diff --git a/lib/bundler/man/bundle-plugin.1.ronn b/lib/bundler/man/bundle-plugin.1.ronn index b54e0c08b4e424..e5cf9eb1341ce9 100644 --- a/lib/bundler/man/bundle-plugin.1.ronn +++ b/lib/bundler/man/bundle-plugin.1.ronn @@ -81,4 +81,4 @@ No options. ## SEE ALSO -* [How to write a Bundler plugin](https://bundler.io/guides/bundler_plugins.html) +* [How to write a Bundler plugin](https://guides.rubygems.org/bundler_plugins/) diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1 index b8e8aefa66228f..be5d3960ad09df 100644 --- a/lib/bundler/man/bundle.1 +++ b/lib/bundler/man/bundle.1 @@ -8,7 +8,7 @@ .SH "DESCRIPTION" Bundler manages an \fBapplication's dependencies\fR through its entire life across many machines systematically and repeatably\. .P -See the bundler website \fIhttps://bundler\.io\fR for information on getting started, and Gemfile(5) for more information on the \fBGemfile\fR format\. +See the RubyGems guides \fIhttps://guides\.rubygems\.org/\fR for information on getting started, and Gemfile(5) for more information on the \fBGemfile\fR format\. .SH "OPTIONS" .TP \fB\-\-no\-color\fR diff --git a/lib/bundler/man/bundle.1.ronn b/lib/bundler/man/bundle.1.ronn index 3f993df1a5b6f0..bf322f79410d6b 100644 --- a/lib/bundler/man/bundle.1.ronn +++ b/lib/bundler/man/bundle.1.ronn @@ -10,7 +10,7 @@ bundle(1) -- Ruby Dependency Management Bundler manages an `application's dependencies` through its entire life across many machines systematically and repeatably. -See [the bundler website](https://bundler.io) for information on getting +See [the RubyGems guides](https://guides.rubygems.org/) for information on getting started, and Gemfile(5) for more information on the `Gemfile` format. ## OPTIONS diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index 20ae73f5db14d3..7af10fa00fc95f 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -166,7 +166,7 @@ bundle config set \-\-local without development test .P Also, calling \fBBundler\.setup\fR with no parameters, or calling \fBrequire "bundler/setup"\fR will setup all groups except for the ones you excluded via \fB\-\-without\fR (since they are not available)\. .P -Note that on \fBbundle install\fR, bundler downloads and evaluates all gems, in order to create a single canonical list of all of the required gems and their dependencies\. This means that you cannot list different versions of the same gems in different groups\. For more details, see Understanding Bundler \fIhttps://bundler\.io/rationale\.html\fR\. +Note that on \fBbundle install\fR, bundler downloads and evaluates all gems, in order to create a single canonical list of all of the required gems and their dependencies\. This means that you cannot list different versions of the same gems in different groups\. For more details, see Dependency management \fIhttps://guides\.rubygems\.org/dependency_management/\fR\. .SS "PLATFORMS" If a gem should only be used in a particular platform or set of platforms, you can specify them\. Platforms are essentially identical to groups, except that you do not need to use the \fB\-\-without\fR install\-time flag to exclude groups of gems for other platforms\. .P diff --git a/lib/bundler/man/gemfile.5.ronn b/lib/bundler/man/gemfile.5.ronn index a733ae866e040a..09fd1a46865804 100644 --- a/lib/bundler/man/gemfile.5.ronn +++ b/lib/bundler/man/gemfile.5.ronn @@ -200,7 +200,7 @@ are not available). Note that on `bundle install`, bundler downloads and evaluates all gems, in order to create a single canonical list of all of the required gems and their dependencies. This means that you cannot list different versions of the same gems in different -groups. For more details, see [Understanding Bundler](https://bundler.io/rationale.html). +groups. For more details, see [Dependency management](https://guides.rubygems.org/dependency_management/). ### PLATFORMS diff --git a/lib/rubygems/commands/help_command.rb b/lib/rubygems/commands/help_command.rb index 664f400561a481..3e77004d926d7b 100644 --- a/lib/rubygems/commands/help_command.rb +++ b/lib/rubygems/commands/help_command.rb @@ -59,7 +59,7 @@ class Gem::Commands::HelpCommand < Gem::Command compatible with Bundler's Gemfile format. You can see additional documentation on the format at: - https://bundler.io + https://guides.rubygems.org/ RubyGems automatically looks for these gem dependencies files: @@ -174,7 +174,7 @@ class Gem::Commands::HelpCommand < Gem::Command See the bundler Gemfile manual page for a list of platforms supported in a gem dependencies file.: - https://bundler.io/v2.5/man/gemfile.5.html + https://guides.rubygems.org/gemfile/ Ruby Version and Engine Dependency ================================== From f275e95ff23f1a299b2a72fc47758de539db8dde Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 30 Jul 2026 16:40:33 +0900 Subject: [PATCH 4/9] Re-bootstrap vcpkg after updating its registry microsoft/vcpkg bumped `scripts/vcpkg-tools.json` to schema version 2, which the vcpkg.exe bundled in the runner images cannot parse, so every Windows job started failing at `vcpkg install` right after the `git pull`. Co-Authored-By: Claude Opus 5 --- .github/workflows/tarball-windows.yml | 4 ++++ .github/workflows/windows.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/tarball-windows.yml b/.github/workflows/tarball-windows.yml index 0bd476b8092029..940c6db591871b 100644 --- a/.github/workflows/tarball-windows.yml +++ b/.github/workflows/tarball-windows.yml @@ -90,8 +90,12 @@ jobs: "VCPKG_BINARY_SOURCES=clear;nuget,$Env:FEED_URL,$Env:FEED_MODE" >> $Env:GITHUB_ENV - name: Install libraries with vcpkg + # The registry pinned by `builtin-baseline` can need a newer tool + # than the vcpkg.exe shipped in the runner image, so re-bootstrap + # it to the version the pulled registry asks for. run: | git -C "%VCPKG_INSTALLATION_ROOT%" pull --quiet + call "%VCPKG_INSTALLATION_ROOT%\bootstrap-vcpkg.bat" vcpkg install working-directory: ${{ inputs.archname }} diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index c871faaa06cbd6..6547818299c9fc 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -109,8 +109,12 @@ jobs: "VCPKG_BINARY_SOURCES=clear;nuget,$Env:FEED_URL,$Env:FEED_MODE" >> $Env:GITHUB_ENV - name: Install libraries with vcpkg + # The registry pinned by `builtin-baseline` can need a newer tool + # than the vcpkg.exe shipped in the runner image, so re-bootstrap + # it to the version the pulled registry asks for. run: | git -C "%VCPKG_INSTALLATION_ROOT%" pull --quiet + call "%VCPKG_INSTALLATION_ROOT%\bootstrap-vcpkg.bat" vcpkg install working-directory: src From 79e6d37b5569be4c772e9d41e8a5447e82c09683 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 30 Jul 2026 22:37:00 +1200 Subject: [PATCH 5/9] Preserve line events across jump optimization. (#18122) [Bug #22218] --- compile.c | 22 +++++-------- test/ruby/test_settracefunc.rb | 56 ++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 15 deletions(-) diff --git a/compile.c b/compile.c index 0d4d6ee2002bdc..f654514256c404 100644 --- a/compile.c +++ b/compile.c @@ -3677,30 +3677,22 @@ iseq_peephole_optimize(rb_iseq_t *iseq, LINK_ELEMENT *list, const int do_tailcal */ INSN *nobj = (INSN *)get_destination_insn(iobj); - /* This is super nasty hack!!! - * - * This jump-jump optimization may ignore event flags of the jump - * instruction being skipped. Actually, Line 2 TracePoint event - * is never fired in the following code: + /* This jump-jump optimization may ignore line events on the jump + * instruction being skipped. For example, the Line 2 TracePoint + * event would otherwise never fire in the following code: * * 1: raise if 1 == 2 * 2: while true * 3: break * 4: end * - * This is critical for coverage measurement. [Bug #15980] - * - * This is a stopgap measure: stop the jump-jump optimization if - * coverage measurement is enabled and if the skipped instruction - * has any event flag. - * - * Note that, still, TracePoint Line event does not occur on Line 2. - * This should be fixed in future. + * Do not skip a jump that carries a line event. This applies even + * when coverage is disabled because TracePoint consumes the same + * event. [Bug #15980] */ int stop_optimization = - ISEQ_COVERAGE(iseq) && ISEQ_LINE_COVERAGE(iseq) && nobj->link.type == ISEQ_ELEMENT_INSN && - nobj->insn_info.events; + (nobj->insn_info.events & (RUBY_EVENT_LINE | RUBY_EVENT_COVERAGE_LINE)); if (!stop_optimization) { INSN *pobj = (INSN *)iobj->link.prev; int prev_dup = 0; diff --git a/test/ruby/test_settracefunc.rb b/test/ruby/test_settracefunc.rb index 95fc7f45536217..86e6fdebeaa662 100644 --- a/test/ruby/test_settracefunc.rb +++ b/test/ruby/test_settracefunc.rb @@ -2808,6 +2808,62 @@ def test_while_in_while assert_equal [__LINE__ - 5, __LINE__ - 4, __LINE__ - 3], lines, 'Bug #17868' end + def test_line_event_after_guard_before_while + lines = [] + while_line = body_line = nil + + TracePoint.new(:line) {|tp| + next unless target_thread? + lines << tp.lineno + }.enable { + raise if 1 == 2 + while_line = __LINE__ + 1 + while true + body_line = __LINE__ + 1 + break + end + } + + assert_includes lines, while_line + assert_includes lines, body_line + assert_operator lines.index(while_line), :<, lines.index(body_line) + end + + def test_line_event_after_guard_before_while_predicate + parent = Class.new do + def read + @values.shift + end + end + + child = Class.new(parent) do + def initialize + @values = ["chunk", nil] + end + end + + start_line = __LINE__ + 2 + while_line = start_line + 2 + child.class_eval <<~RUBY, __FILE__, start_line + def read + return if @finished + while chunk = super + chunk.upcase + end + end + RUBY + + lines = [] + TracePoint.new(:line) {|tp| + next unless target_thread? + lines << tp.lineno + }.enable { + child.new.read + } + + assert_includes lines, while_line + end + def test_allow_reentry event_lines = [] _l1 = _l2 = _l3 = _l4 = nil From a2ddafb4f66586230855cbc441335ca6bdc41da2 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 29 Jul 2026 15:06:12 -0400 Subject: [PATCH 6/9] [ruby/mmtk] Add obj_need_obj_free_p MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don't need to call obj_free on certain types of objects anymore including T_FLOAT, T_RATIONAL, T_COMPLEX, and T_OBJECT. It makes freeing those objects faster. Benchmark: i = 0 while i < 10_000_000 Object.new i += 1 end Before: Time (mean ± σ): 1.435 s ± 0.013 s [User: 1.706 s, System: 1.970 s] Range (min … max): 1.418 s … 1.461 s 10 runs After: Time (mean ± σ): 1.230 s ± 0.027 s [User: 1.335 s, System: 1.743 s] Range (min … max): 1.206 s … 1.294 s 10 runs https://github.com/ruby/mmtk/commit/52f01ba90f --- gc/mmtk/mmtk.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 60bbbfc9a1b610..d7228c43becbcf 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -906,6 +906,20 @@ obj_can_parallel_free_p(VALUE obj) } } +static bool +obj_need_obj_free_p(VALUE obj) +{ + switch (RB_BUILTIN_TYPE(obj)) { + case T_FLOAT: + case T_RATIONAL: + case T_COMPLEX: + case T_OBJECT: + return false; + default: + return true; + } +} + static void mmtk_flush_obj_free_buffer(struct MMTk_ractor_cache *cache) { @@ -1001,8 +1015,9 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags mmtk_post_alloc_fast_immix(objspace, ractor_cache, (uintptr_t)alloc_obj); } - // TODO: only add when object needs obj_free to be called - mmtk_buffer_obj_free_candidate(ractor_cache, (VALUE)alloc_obj); + if (obj_need_obj_free_p((VALUE)alloc_obj)) { + mmtk_buffer_obj_free_candidate(ractor_cache, (VALUE)alloc_obj); + } objspace->total_allocated_objects++; From 762fa2fc27d0cc03ef1818d66aa5c025db44cacf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Jul 2026 15:49:31 +0000 Subject: [PATCH 7/9] Increase SYNTAX_SUGGEST_TIMEOUT for ppc64le/s390x from 10 to 60 seconds The "does not timeout on massive files" integration test in spec/syntax_suggest/integration/syntax_suggest_spec.rb is only activated when SYNTAX_SUGGEST_TIMEOUT is set. The CI workflow sets it to 10 for ppc64le/s390x runners, but this is insufficient: on ppc64le, processing the 9,234-line syntax_tree.rb.txt fixture takes more than 10 seconds, causing the test to fail. Increase SYNTAX_SUGGEST_TIMEOUT to 60 seconds (consistent with the 10x scale factor applied to RUBY_TEST_TIMEOUT_SCALE for these slower architectures) to give the algorithm enough time to complete. Fixes: make-ibm (check, ubuntu-24.04-ppc64le) CI failure --- .github/workflows/ubuntu.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 6b6bd7f0901e02..c7b9aef0396bd6 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -147,11 +147,13 @@ jobs: # assertion timeouts (e.g. assert_separately) flake. Scale them up, as # macos.yml does for slower/contended runners. SYNTAX_SUGGEST_TIMEOUT # covers syntax_suggest's own internal search timeout, which - # RUBY_TEST_TIMEOUT_SCALE does not affect. + # RUBY_TEST_TIMEOUT_SCALE does not affect. Note: setting SYNTAX_SUGGEST_TIMEOUT + # also activates the "does not timeout on massive files" integration test, so + # it must be large enough for the slower architecture to complete the search. - name: Set timeout scale for emulated runners run: | echo "RUBY_TEST_TIMEOUT_SCALE=10" >> $GITHUB_ENV - echo "SYNTAX_SUGGEST_TIMEOUT=10" >> $GITHUB_ENV + echo "SYNTAX_SUGGEST_TIMEOUT=60" >> $GITHUB_ENV if: ${{ endsWith(matrix.os, 'ppc64le') || endsWith(matrix.os, 's390x') }} - name: make ${{ matrix.test_task }} From 03e2f86c0ad5432df5150c8950bbf67dac876e41 Mon Sep 17 00:00:00 2001 From: Nozomi Hijikata <121233810+nozomemein@users.noreply.github.com> Date: Fri, 31 Jul 2026 03:09:06 +0900 Subject: [PATCH 8/9] ZJIT: Profile caller splat length distributions (#17967) * ZJIT: Profile caller splat length distributions Related to: https://github.com/Shopify/ruby/issues/1004 Before specializing sends with caller splats, collect the distribution of caller splat array lengths. The splat length determines how many `ArrayAref` operations and positional arguments a `SendDirect` path needs. Specializing for only one observed length could cause repeated side exits when a call site sees varying lengths, so we need to understand whether these profiles are monomorphic or polymorphic first. This does not change caller splat specialization or fallback behavior yet. * ZJIT: Refine caller splat length profiling Store splat length distributions in a side map since only splat call sites use them. Record unknown lengths instead of dropping those samples so the length profile stays aligned with the operand type profile. Co-authored-by: Max Bernstein --- zjit.rb | 1 + zjit/src/hir.rs | 30 ++++++++++++--- zjit/src/hir/opt_tests.rs | 78 +++++++++++++++++++++++++++++++++++++++ zjit/src/profile.rs | 45 +++++++++++++++++++++- zjit/src/stats.rs | 8 ++++ 5 files changed, 155 insertions(+), 7 deletions(-) diff --git a/zjit.rb b/zjit.rb index 4f8dabc4aaed4d..a579827bcd7062 100644 --- a/zjit.rb +++ b/zjit.rb @@ -116,6 +116,7 @@ def stats_string # use multiple complex features, a decrease in this number does not # necessarily mean an increase in number of optimized calls. print_counters_with_prefix(prefix: 'complex_arg_pass_', prompt: 'popular complex argument-parameter features not optimized', buf:, stats:, limit: 10) + print_counters_with_prefix(prefix: 'caller_splat_profile_', prompt: 'caller splat length profiles', buf:, stats:, limit: 10) # Show exit counters, ordered by the typical amount of exits for the prefix at the time print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index e849f83e9da33e..861a6d0c26828b 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -3912,9 +3912,12 @@ impl Function { result } - fn count_complex_call_features(&mut self, block: BlockId, ci_flags: c_uint) { + fn count_complex_call_features(&mut self, block: BlockId, ci_flags: c_uint, state: InsnId) { use Counter::*; - if 0 != ci_flags & VM_CALL_ARGS_SPLAT { self.count(block, complex_arg_pass_caller_splat); } + if 0 != ci_flags & VM_CALL_ARGS_SPLAT { + self.count(block, complex_arg_pass_caller_splat); + self.count_caller_splat_profile(block, state); + } if 0 != ci_flags & VM_CALL_ARGS_BLOCKARG { self.count(block, complex_arg_pass_caller_blockarg); } if 0 != ci_flags & VM_CALL_KWARG { self.count(block, complex_arg_pass_caller_kwarg); } if 0 != ci_flags & VM_CALL_KW_SPLAT { self.count(block, complex_arg_pass_caller_kw_splat); } @@ -3924,6 +3927,21 @@ impl Function { if 0 != ci_flags & VM_CALL_FORWARDING { self.count(block, complex_arg_pass_caller_forwarding); } } + fn count_caller_splat_profile(&mut self, block: BlockId, state: InsnId) { + let state = self.frame_state(state); + let summary = get_or_create_iseq_payload(state.iseq).profile.get_splat_length_summary(state.insn_idx); + let counter = match summary { + None => Counter::caller_splat_profile_no_profiles, + Some(summary) if summary.is_monomorphic() => Counter::caller_splat_profile_monomorphic, + Some(summary) if summary.is_polymorphic() => Counter::caller_splat_profile_polymorphic, + Some(summary) if summary.is_skewed_polymorphic() => Counter::caller_splat_profile_skewed_polymorphic, + Some(summary) if summary.is_megamorphic() => Counter::caller_splat_profile_megamorphic, + Some(summary) if summary.is_skewed_megamorphic() => Counter::caller_splat_profile_skewed_megamorphic, + Some(_) => unreachable!(), + }; + self.count(block, counter); + } + 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. @@ -4242,7 +4260,7 @@ impl Function { // Mask out ARGS_BLOCKARG only if we've already handled the nil block arg case above. let flags_for_check = if stripped_nil_block { flags & !VM_CALL_ARGS_BLOCKARG } else { flags }; if def_type != VM_METHOD_TYPE_OPTIMIZED && def_type != VM_METHOD_TYPE_CFUNC && unspecializable_call_type(flags_for_check) { - self.count_complex_call_features(block, flags); + self.count_complex_call_features(block, flags, state); self.set_dynamic_send_reason(insn_id, ComplexArgPass); self.push_insn_id(block, insn_id); continue; } @@ -4384,7 +4402,7 @@ impl Function { match (opt_type, args.as_slice()) { (OptimizedMethodType::Call, _) => { if flags & (VM_CALL_ARGS_SPLAT | VM_CALL_KWARG) != 0 { - self.count_complex_call_features(block, flags); + self.count_complex_call_features(block, flags, state); self.set_dynamic_send_reason(insn_id, ComplexArgPass); self.push_insn_id(block, insn_id); continue; } @@ -4403,7 +4421,7 @@ impl Function { } (OptimizedMethodType::StructAref, &[]) | (OptimizedMethodType::StructAset, &[_]) => { if unspecializable_call_type(flags) { - self.count_complex_call_features(block, flags); + self.count_complex_call_features(block, flags, state); self.set_dynamic_send_reason(insn_id, ComplexArgPass); self.push_insn_id(block, insn_id); continue; } @@ -4492,7 +4510,7 @@ impl Function { if unspecializable_c_call_type(ci_flags) { // Only count features NOT already counted in type_specialize. if !unspecializable_call_type(ci_flags) { - fun.count_complex_call_features(block, ci_flags); + fun.count_complex_call_features(block, ci_flags, state); } fun.set_dynamic_send_reason(send_insn_id, ComplexArgPass); return Err(()); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index ae4322702c02f1..739d382b3a5b1a 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -13654,6 +13654,84 @@ mod hir_opt_tests { "); } + #[test] + fn dont_specialize_call_to_iseq_with_monomorphic_caller_splat() { + enable_zjit_stats(); + eval(" + def foo(*args) = args + def test(args) = foo(*args) + test([1]) + test([2]) + "); + assert_snapshot!(hir_string("test"), @" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:CPtr = LoadSP + v3:BasicObject = LoadField v2, :args@0x1000 + Jump bb3(v1, v3) + bb2(): + EntryPoint JIT(0) + v6:BasicObject = LoadArg :self@0 + v7:BasicObject = LoadArg :args@1 + IncrCounterPtr + Jump bb3(v6, v7) + bb3(v10:BasicObject, v11:BasicObject): + IncrCounter zjit_insn_count + IncrCounter zjit_insn_count + IncrCounter zjit_insn_count + v20:ArrayExact = ToArray v11 + IncrCounter zjit_insn_count + IncrCounter complex_arg_pass_caller_splat + IncrCounter caller_splat_profile_monomorphic + v23:BasicObject = Send v10, :foo, v20 # SendFallbackReason: Complex argument passing + IncrCounter zjit_insn_count + CheckInterrupts + Return v23 + "); + } + + #[test] + fn dont_specialize_call_to_iseq_with_polymorphic_caller_splat() { + enable_zjit_stats(); + set_call_threshold(3); + eval(" + def foo(*args) = args + def test(args) = foo(*args) + test([1]) + test([1, 2]) + test([3]) + "); + assert_snapshot!(hir_string("test"), @" + fn test@:3: + bb1(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + v2:CPtr = LoadSP + v3:BasicObject = LoadField v2, :args@0x1000 + Jump bb3(v1, v3) + bb2(): + EntryPoint JIT(0) + v6:BasicObject = LoadArg :self@0 + v7:BasicObject = LoadArg :args@1 + IncrCounterPtr + Jump bb3(v6, v7) + bb3(v10:BasicObject, v11:BasicObject): + IncrCounter zjit_insn_count + IncrCounter zjit_insn_count + IncrCounter zjit_insn_count + v20:ArrayExact = ToArray v11 + IncrCounter zjit_insn_count + IncrCounter complex_arg_pass_caller_splat + IncrCounter caller_splat_profile_polymorphic + v23:BasicObject = Send v10, :foo, v20 # SendFallbackReason: Complex argument passing + IncrCounter zjit_insn_count + CheckInterrupts + Return v23 + "); + } + #[test] fn test_inline_symbol_to_sym() { eval(r#" diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index a8df3b6f1e5f54..e47fb47d4ddf6d 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -100,6 +100,7 @@ fn profile_insn_sample( let argc = num_arguments_on_stack(cd); // Profile all the arguments and self (+1). profile_operands(profiler, profile, argc + 1); + profile_splat_length(profiler, profile, unsafe { (*cd).ci }); } YARVINSN_splatkw => profile_operands(profiler, profile, 2), _ => return false, @@ -154,6 +155,14 @@ pub type TypeDistribution = Distribution; pub type TypeDistributionSummary = DistributionSummary; +pub type SplatLength = u32; + +/// `None` records an unknown length so this distribution covers the same +/// executions as the operand type profile. +pub type SplatLengthDistribution = Distribution, DISTRIBUTION_SIZE>; + +pub type SplatLengthDistributionSummary = DistributionSummary, DISTRIBUTION_SIZE>; + /// Profile the Type of top-`n` stack operands fn profile_operands(profiler: &mut Profiler, profile: &mut IseqProfile, n: usize) { let entry = profile.entry_mut(profiler.insn_idx); @@ -171,6 +180,30 @@ fn profile_operands(profiler: &mut Profiler, profile: &mut IseqProfile, n: usize } } +fn profile_splat_length(profiler: &mut Profiler, profile: &mut IseqProfile, ci: *const rb_callinfo) { + let flags = unsafe { rb_vm_ci_flag(ci) }; + // Only call sites with VM_CALL_ARGS_SPLAT have a splat array on the stack. + if flags & VM_CALL_ARGS_SPLAT == 0 { + return; + } + + let kwarg = unsafe { rb_vm_ci_kwarg(ci) }; + let caller_kw_count = if kwarg.is_null() { 0 } else { (unsafe { get_cikw_keyword_len(kwarg) }) as usize }; + // Starting at the top of the stack, skip the block argument, keyword-splat + // hash, and explicit keyword values to reach the splat array. + let splat_pos = usize::from(flags & VM_CALL_ARGS_BLOCKARG != 0) + + usize::from(flags & VM_CALL_KW_SPLAT != 0) + + caller_kw_count; + let splat_array = profiler.peek_at_stack(splat_pos as isize); + let length = if unsafe { RB_TYPE_P(splat_array, RUBY_T_ARRAY) } { + SplatLength::try_from(unsafe { rb_jit_array_len(splat_array) }).ok() + } else { + None + }; + profile.splat_lengths.entry(profiler.insn_idx) + .or_insert_with(SplatLengthDistribution::new).observe(length); +} + fn profile_self(profiler: &mut Profiler, profile: &mut IseqProfile) { let entry = profile.entry_mut(profiler.insn_idx); if entry.opnd_types.is_empty() { @@ -225,6 +258,7 @@ fn profile_invokesuper(profiler: &mut Profiler, profile: &mut IseqProfile) { // Profile all the arguments and self (+1). profile_operands(profiler, profile, (argc + 1) as usize); + profile_splat_length(profiler, profile, unsafe { (*cd).ci }); } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -415,7 +449,10 @@ pub struct IseqProfile { entries: Vec, /// Method entries for `super` calls (stored as VALUE to be GC-safe) - super_cme: HashMap + super_cme: HashMap, + + /// Observed lengths of caller splat arrays for call instructions. + splat_lengths: HashMap, } impl IseqProfile { @@ -423,6 +460,7 @@ impl IseqProfile { Self { entries: Vec::new(), super_cme: HashMap::new(), + splat_lengths: HashMap::new(), } } @@ -459,6 +497,11 @@ impl IseqProfile { self.entry(insn_idx).map(|e| e.opnd_types.as_slice()).filter(|s| !s.is_empty()) } + pub fn get_splat_length_summary(&self, insn_idx: YarvInsnIdx) -> Option { + self.splat_lengths.get(&insn_idx) + .map(SplatLengthDistributionSummary::new) + } + pub fn get_super_method_entry(&self, insn_idx: YarvInsnIdx) -> Option<*const rb_callable_method_entry_t> { let Some(entry) = self.super_cme.get(&insn_idx) else { return None }; let summary = TypeDistributionSummary::new(entry); diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 7ebfb62309a0d9..8347246f2b77af 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -434,6 +434,14 @@ make_counters! { // Unsupported argument conversions complex_arg_pass_keyword_to_positional_hash, + // Caller splat length profile shapes + caller_splat_profile_no_profiles, + caller_splat_profile_monomorphic, + caller_splat_profile_polymorphic, + caller_splat_profile_skewed_polymorphic, + caller_splat_profile_megamorphic, + caller_splat_profile_skewed_megamorphic, + // Writes to the VM frame vm_write_jit_frame_count, vm_write_sp_count, From daab42084328d615f18e393086add7526dbe5023 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 30 Jul 2026 14:22:38 -0400 Subject: [PATCH 9/9] ZJIT: Simplify and optimize ProfiledType::new (#18126) Reduce the number of branches and only look up the shape once instead of twice. Brings a profiling-heavy run (profiling for 1000 calls) of lobsters from 700ms to 660ms profile time. Unfortunately getting the current time for stats is still 50% of all profile time. --- zjit/src/profile.rs | 34 +++++----------------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/zjit/src/profile.rs b/zjit/src/profile.rs index e47fb47d4ddf6d..a118254ac21ad3 100644 --- a/zjit/src/profile.rs +++ b/zjit/src/profile.rs @@ -320,38 +320,14 @@ impl ProfiledType { /// Profile the class and shape of the given object fn new(obj: VALUE) -> Self { - if obj == Qfalse { - return Self { class: unsafe { rb_cFalseClass }, - shape: INVALID_SHAPE_ID, - flags: Flags::immediate() }; - } - if obj == Qtrue { - return Self { class: unsafe { rb_cTrueClass }, - shape: INVALID_SHAPE_ID, - flags: Flags::immediate() }; - } - if obj == Qnil { - return Self { class: unsafe { rb_cNilClass }, - shape: INVALID_SHAPE_ID, - flags: Flags::immediate() }; - } - if obj.fixnum_p() { - return Self { class: unsafe { rb_cInteger }, - shape: INVALID_SHAPE_ID, - flags: Flags::immediate() }; - } - if obj.flonum_p() { - return Self { class: unsafe { rb_cFloat }, - shape: INVALID_SHAPE_ID, - flags: Flags::immediate() }; - } - if obj.static_sym_p() { - return Self { class: unsafe { rb_cSymbol }, + if obj.special_const_p() { + return Self { class: obj.class_of(), shape: INVALID_SHAPE_ID, flags: Flags::immediate() }; } let mut flags = Flags::none(); - if obj.shape_id_of().layout() == ShapeLayout::RObject { + let shape = obj.shape_id_of(); + if shape.layout() == ShapeLayout::RObject { flags.0 |= Flags::IS_EMBEDDED; } if obj.struct_embedded_p() { @@ -360,7 +336,7 @@ impl ProfiledType { if unsafe { RB_TYPE_P(obj, RUBY_T_OBJECT) } { flags.0 |= Flags::IS_T_OBJECT; } - Self { class: obj.class_of(), shape: obj.shape_id_of(), flags } + Self { class: obj.class_of(), shape, flags } } pub fn empty() -> Self {