From 5b6c6414cf7f1dbdcdc771192f5b2e124dbf61f3 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Tue, 3 Mar 2026 00:29:23 +0100 Subject: [PATCH 01/15] [ruby/rubygems] Add a new Bundler config to control how many specs are fetched: - ### Problem In #9071, I increased the API_REQUEST_SIZE constants to fetch 100 specifications at once instead of 50. Worth to remember that this codepath is exclusively used for servers that don't implement the compact index API and where Bundler has to fallback on the `/v1/dependencies` endpoint. Fetching 100 gems at once seems not supported by some gem servers. See https://github.com/ruby/rubygems/issues/9345 ### Solution I'd like to provide a new Bundler configuration `BUNDLE_API_REQUEST_SIZE` to let users of those servers control how many dependencies should be fetched at once. ### Alternatives The other alternative is to revert #9071 and always fetch 50 specs. I tried the number 100 on a single rubygem registry (cloudsmith), and I don't have data point to know whether this value is supported by most registries. https://github.com/ruby/rubygems/commit/1a3bace42e --- lib/bundler/fetcher/dependency.rb | 8 +++++++- lib/bundler/man/bundle-config.1 | 5 ++++- lib/bundler/man/bundle-config.1.ronn | 5 +++++ spec/bundler/bundler/fetcher/dependency_spec.rb | 12 ++++++++++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/bundler/fetcher/dependency.rb b/lib/bundler/fetcher/dependency.rb index 994b415e9c81bc..4f2414e33d3265 100644 --- a/lib/bundler/fetcher/dependency.rb +++ b/lib/bundler/fetcher/dependency.rb @@ -50,7 +50,7 @@ def dependency_specs(gem_names) def unmarshalled_dep_gems(gem_names) gem_list = [] - gem_names.each_slice(Source::Rubygems::API_REQUEST_SIZE) do |names| + gem_names.each_slice(api_request_size) do |names| marshalled_deps = downloader.fetch(dependency_api_uri(names)).body gem_list.concat(Bundler.safe_load_marshal(marshalled_deps)) end @@ -74,6 +74,12 @@ def dependency_api_uri(gem_names = []) uri.query = "gems=#{CGI.escape(gem_names.sort.join(","))}" if gem_names.any? uri end + + private + + def api_request_size + Bundler.settings[:api_request_size]&.to_i || Source::Rubygems::API_REQUEST_SIZE + end end end end diff --git a/lib/bundler/man/bundle-config.1 b/lib/bundler/man/bundle-config.1 index 2a342298aeba45..000fe664da6c3f 100644 --- a/lib/bundler/man/bundle-config.1 +++ b/lib/bundler/man/bundle-config.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CONFIG" "1" "February 2026" "" +.TH "BUNDLE\-CONFIG" "1" "March 2026" "" .SH "NAME" \fBbundle\-config\fR \- Set bundler configuration options .SH "SYNOPSIS" @@ -70,6 +70,9 @@ Any periods in the configuration keys must be replaced with two underscores when .SH "LIST OF AVAILABLE KEYS" The following is a list of all configuration keys and their purpose\. You can learn more about their operation in bundle install(1) \fIbundle\-install\.1\.html\fR\. .TP +\fBapi_request_size\fR (\fBBUNDLE_API_REQUEST_SIZE\fR) +Configure how many dependencies to fetch when resolving the specifications\. This configuration is only used when fetchig specifications from RubyGems servers that didn't implement the Compact Index API\. Defaults to 100\. +.TP \fBauto_install\fR (\fBBUNDLE_AUTO_INSTALL\fR) Automatically run \fBbundle install\fR when gems are missing\. .TP diff --git a/lib/bundler/man/bundle-config.1.ronn b/lib/bundler/man/bundle-config.1.ronn index eb5a3b045b3e57..a8670a36709d49 100644 --- a/lib/bundler/man/bundle-config.1.ronn +++ b/lib/bundler/man/bundle-config.1.ronn @@ -106,6 +106,11 @@ the environment variable `BUNDLE_LOCAL__RACK`. The following is a list of all configuration keys and their purpose. You can learn more about their operation in [bundle install(1)](bundle-install.1.html). +* `api_request_size` (`BUNDLE_API_REQUEST_SIZE`): + Configure how many dependencies to fetch when resolving the specifications. + This configuration is only used when fetchig specifications from RubyGems + servers that didn't implement the Compact Index API. + Defaults to 100. * `auto_install` (`BUNDLE_AUTO_INSTALL`): Automatically run `bundle install` when gems are missing. * `bin` (`BUNDLE_BIN`): diff --git a/spec/bundler/bundler/fetcher/dependency_spec.rb b/spec/bundler/bundler/fetcher/dependency_spec.rb index 61e32acfd969ee..501bc269a55610 100644 --- a/spec/bundler/bundler/fetcher/dependency_spec.rb +++ b/spec/bundler/bundler/fetcher/dependency_spec.rb @@ -222,6 +222,18 @@ expect(Bundler).to receive(:safe_load_marshal).with(fetch_response.body).and_return([unmarshalled_gems]) expect(subject.unmarshalled_dep_gems(gem_names)).to eq([unmarshalled_gems]) end + + it "should fetch as many dependencies as specified" do + allow(subject).to receive(:dependency_api_uri).with([%w[foo bar]]).and_return(dep_api_uri) + allow(subject).to receive(:dependency_api_uri).with([%w[bundler rubocop]]).and_return(dep_api_uri) + + expect(downloader).to receive(:fetch).twice.with(dep_api_uri).and_return(fetch_response) + expect(Bundler).to receive(:safe_load_marshal).twice.with(fetch_response.body).and_return([unmarshalled_gems]) + + Bundler.settings.temporary(api_request_size: 1) do + expect(subject.unmarshalled_dep_gems(gem_names)).to eq([unmarshalled_gems, unmarshalled_gems]) + end + end end describe "#get_formatted_specs_and_deps" do From f604d1a879454f8cc807e31f65ee230327aae9de Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Tue, 3 Mar 2026 00:50:48 +0100 Subject: [PATCH 02/15] [ruby/rubygems] Update the man pages header to be in sync with the config one https://github.com/ruby/rubygems/commit/77def7f4e0 --- lib/bundler/man/bundle-add.1 | 2 +- lib/bundler/man/bundle-binstubs.1 | 2 +- lib/bundler/man/bundle-cache.1 | 2 +- lib/bundler/man/bundle-check.1 | 2 +- lib/bundler/man/bundle-clean.1 | 2 +- lib/bundler/man/bundle-console.1 | 2 +- lib/bundler/man/bundle-doctor.1 | 2 +- lib/bundler/man/bundle-env.1 | 2 +- lib/bundler/man/bundle-exec.1 | 2 +- lib/bundler/man/bundle-fund.1 | 2 +- lib/bundler/man/bundle-gem.1 | 2 +- lib/bundler/man/bundle-help.1 | 2 +- lib/bundler/man/bundle-info.1 | 2 +- lib/bundler/man/bundle-init.1 | 2 +- lib/bundler/man/bundle-install.1 | 2 +- lib/bundler/man/bundle-issue.1 | 2 +- lib/bundler/man/bundle-licenses.1 | 2 +- lib/bundler/man/bundle-list.1 | 2 +- lib/bundler/man/bundle-lock.1 | 2 +- lib/bundler/man/bundle-open.1 | 2 +- lib/bundler/man/bundle-outdated.1 | 2 +- lib/bundler/man/bundle-platform.1 | 2 +- lib/bundler/man/bundle-plugin.1 | 2 +- lib/bundler/man/bundle-pristine.1 | 2 +- lib/bundler/man/bundle-remove.1 | 2 +- lib/bundler/man/bundle-show.1 | 2 +- lib/bundler/man/bundle-update.1 | 2 +- lib/bundler/man/bundle-version.1 | 2 +- lib/bundler/man/bundle.1 | 2 +- lib/bundler/man/gemfile.5 | 2 +- 30 files changed, 30 insertions(+), 30 deletions(-) diff --git a/lib/bundler/man/bundle-add.1 b/lib/bundler/man/bundle-add.1 index f5975c70230cdf..89771d343340b9 100644 --- a/lib/bundler/man/bundle-add.1 +++ b/lib/bundler/man/bundle-add.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-ADD" "1" "February 2026" "" +.TH "BUNDLE\-ADD" "1" "March 2026" "" .SH "NAME" \fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-binstubs.1 b/lib/bundler/man/bundle-binstubs.1 index 314d4ef6130892..2a78f530ccefd0 100644 --- a/lib/bundler/man/bundle-binstubs.1 +++ b/lib/bundler/man/bundle-binstubs.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-BINSTUBS" "1" "February 2026" "" +.TH "BUNDLE\-BINSTUBS" "1" "March 2026" "" .SH "NAME" \fBbundle\-binstubs\fR \- Install the binstubs of the listed gems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-cache.1 b/lib/bundler/man/bundle-cache.1 index 0d20662b7b69dc..a2b0fc6dff194e 100644 --- a/lib/bundler/man/bundle-cache.1 +++ b/lib/bundler/man/bundle-cache.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CACHE" "1" "February 2026" "" +.TH "BUNDLE\-CACHE" "1" "March 2026" "" .SH "NAME" \fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-check.1 b/lib/bundler/man/bundle-check.1 index 154fb06818f474..d03b4dc6bdd2ea 100644 --- a/lib/bundler/man/bundle-check.1 +++ b/lib/bundler/man/bundle-check.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CHECK" "1" "February 2026" "" +.TH "BUNDLE\-CHECK" "1" "March 2026" "" .SH "NAME" \fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-clean.1 b/lib/bundler/man/bundle-clean.1 index 47fb8f92aa2c75..13bd586f486551 100644 --- a/lib/bundler/man/bundle-clean.1 +++ b/lib/bundler/man/bundle-clean.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CLEAN" "1" "February 2026" "" +.TH "BUNDLE\-CLEAN" "1" "March 2026" "" .SH "NAME" \fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-console.1 b/lib/bundler/man/bundle-console.1 index 3e3db532e43478..4594cb74be4470 100644 --- a/lib/bundler/man/bundle-console.1 +++ b/lib/bundler/man/bundle-console.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-CONSOLE" "1" "February 2026" "" +.TH "BUNDLE\-CONSOLE" "1" "March 2026" "" .SH "NAME" \fBbundle\-console\fR \- Open an IRB session with the bundle pre\-loaded .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-doctor.1 b/lib/bundler/man/bundle-doctor.1 index f5b2be400722bd..e94ebbd8342ba1 100644 --- a/lib/bundler/man/bundle-doctor.1 +++ b/lib/bundler/man/bundle-doctor.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-DOCTOR" "1" "February 2026" "" +.TH "BUNDLE\-DOCTOR" "1" "March 2026" "" .SH "NAME" \fBbundle\-doctor\fR \- Checks the bundle for common problems .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-env.1 b/lib/bundler/man/bundle-env.1 index a6d11a3d3c087f..c57bec014eff16 100644 --- a/lib/bundler/man/bundle-env.1 +++ b/lib/bundler/man/bundle-env.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-ENV" "1" "February 2026" "" +.TH "BUNDLE\-ENV" "1" "March 2026" "" .SH "NAME" \fBbundle\-env\fR \- Print information about the environment Bundler is running under .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-exec.1 b/lib/bundler/man/bundle-exec.1 index 62c9245b003ac3..36fed764aca840 100644 --- a/lib/bundler/man/bundle-exec.1 +++ b/lib/bundler/man/bundle-exec.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-EXEC" "1" "February 2026" "" +.TH "BUNDLE\-EXEC" "1" "March 2026" "" .SH "NAME" \fBbundle\-exec\fR \- Execute a command in the context of the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-fund.1 b/lib/bundler/man/bundle-fund.1 index a09a4ed97cfc17..96f182e05f9fbc 100644 --- a/lib/bundler/man/bundle-fund.1 +++ b/lib/bundler/man/bundle-fund.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-FUND" "1" "February 2026" "" +.TH "BUNDLE\-FUND" "1" "March 2026" "" .SH "NAME" \fBbundle\-fund\fR \- Lists information about gems seeking funding assistance .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-gem.1 b/lib/bundler/man/bundle-gem.1 index 33cacbe5323bff..68c77a03ce187a 100644 --- a/lib/bundler/man/bundle-gem.1 +++ b/lib/bundler/man/bundle-gem.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-GEM" "1" "February 2026" "" +.TH "BUNDLE\-GEM" "1" "March 2026" "" .SH "NAME" \fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-help.1 b/lib/bundler/man/bundle-help.1 index 3a97e56a1fcbbd..17e1d4a90449cb 100644 --- a/lib/bundler/man/bundle-help.1 +++ b/lib/bundler/man/bundle-help.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-HELP" "1" "February 2026" "" +.TH "BUNDLE\-HELP" "1" "March 2026" "" .SH "NAME" \fBbundle\-help\fR \- Displays detailed help for each subcommand .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-info.1 b/lib/bundler/man/bundle-info.1 index 39ff74a7d97580..50f5e36f18c18d 100644 --- a/lib/bundler/man/bundle-info.1 +++ b/lib/bundler/man/bundle-info.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-INFO" "1" "February 2026" "" +.TH "BUNDLE\-INFO" "1" "March 2026" "" .SH "NAME" \fBbundle\-info\fR \- Show information for the given gem in your bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-init.1 b/lib/bundler/man/bundle-init.1 index a053ac4c4c35cc..14fd0a73cb4cbf 100644 --- a/lib/bundler/man/bundle-init.1 +++ b/lib/bundler/man/bundle-init.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-INIT" "1" "February 2026" "" +.TH "BUNDLE\-INIT" "1" "March 2026" "" .SH "NAME" \fBbundle\-init\fR \- Generates a Gemfile into the current working directory .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-install.1 b/lib/bundler/man/bundle-install.1 index 21997fb290917b..1d52335644f46a 100644 --- a/lib/bundler/man/bundle-install.1 +++ b/lib/bundler/man/bundle-install.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-INSTALL" "1" "February 2026" "" +.TH "BUNDLE\-INSTALL" "1" "March 2026" "" .SH "NAME" \fBbundle\-install\fR \- Install the dependencies specified in your Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-issue.1 b/lib/bundler/man/bundle-issue.1 index f5033d6e076ca5..7e2fcaf0fa5e6e 100644 --- a/lib/bundler/man/bundle-issue.1 +++ b/lib/bundler/man/bundle-issue.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-ISSUE" "1" "February 2026" "" +.TH "BUNDLE\-ISSUE" "1" "March 2026" "" .SH "NAME" \fBbundle\-issue\fR \- Get help reporting Bundler issues .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-licenses.1 b/lib/bundler/man/bundle-licenses.1 index 39dcf0bd38b943..9170fecd73518f 100644 --- a/lib/bundler/man/bundle-licenses.1 +++ b/lib/bundler/man/bundle-licenses.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-LICENSES" "1" "February 2026" "" +.TH "BUNDLE\-LICENSES" "1" "March 2026" "" .SH "NAME" \fBbundle\-licenses\fR \- Print the license of all gems in the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-list.1 b/lib/bundler/man/bundle-list.1 index 34d25ae12c2648..165a99bb58a24a 100644 --- a/lib/bundler/man/bundle-list.1 +++ b/lib/bundler/man/bundle-list.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-LIST" "1" "February 2026" "" +.TH "BUNDLE\-LIST" "1" "March 2026" "" .SH "NAME" \fBbundle\-list\fR \- List all the gems in the bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-lock.1 b/lib/bundler/man/bundle-lock.1 index eb5c8732cc0115..426e80ec77e1bc 100644 --- a/lib/bundler/man/bundle-lock.1 +++ b/lib/bundler/man/bundle-lock.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-LOCK" "1" "February 2026" "" +.TH "BUNDLE\-LOCK" "1" "March 2026" "" .SH "NAME" \fBbundle\-lock\fR \- Creates / Updates a lockfile without installing .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-open.1 b/lib/bundler/man/bundle-open.1 index 1d9463d12b1b3e..caeb223844391a 100644 --- a/lib/bundler/man/bundle-open.1 +++ b/lib/bundler/man/bundle-open.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-OPEN" "1" "February 2026" "" +.TH "BUNDLE\-OPEN" "1" "March 2026" "" .SH "NAME" \fBbundle\-open\fR \- Opens the source directory for a gem in your bundle .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-outdated.1 b/lib/bundler/man/bundle-outdated.1 index b1ddd0d31530a6..744be279c90128 100644 --- a/lib/bundler/man/bundle-outdated.1 +++ b/lib/bundler/man/bundle-outdated.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-OUTDATED" "1" "February 2026" "" +.TH "BUNDLE\-OUTDATED" "1" "March 2026" "" .SH "NAME" \fBbundle\-outdated\fR \- List installed gems with newer versions available .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-platform.1 b/lib/bundler/man/bundle-platform.1 index cb53cd192dcf43..b859ead72f8c5f 100644 --- a/lib/bundler/man/bundle-platform.1 +++ b/lib/bundler/man/bundle-platform.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-PLATFORM" "1" "February 2026" "" +.TH "BUNDLE\-PLATFORM" "1" "March 2026" "" .SH "NAME" \fBbundle\-platform\fR \- Displays platform compatibility information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-plugin.1 b/lib/bundler/man/bundle-plugin.1 index 5c019b305d80b7..450d3e0862ea65 100644 --- a/lib/bundler/man/bundle-plugin.1 +++ b/lib/bundler/man/bundle-plugin.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-PLUGIN" "1" "February 2026" "" +.TH "BUNDLE\-PLUGIN" "1" "March 2026" "" .SH "NAME" \fBbundle\-plugin\fR \- Manage Bundler plugins .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-pristine.1 b/lib/bundler/man/bundle-pristine.1 index 0973d63e4edd49..f8722bff3eace1 100644 --- a/lib/bundler/man/bundle-pristine.1 +++ b/lib/bundler/man/bundle-pristine.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-PRISTINE" "1" "February 2026" "" +.TH "BUNDLE\-PRISTINE" "1" "March 2026" "" .SH "NAME" \fBbundle\-pristine\fR \- Restores installed gems to their pristine condition .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-remove.1 b/lib/bundler/man/bundle-remove.1 index 3398d6cd330c86..df00b8dbdcf0f1 100644 --- a/lib/bundler/man/bundle-remove.1 +++ b/lib/bundler/man/bundle-remove.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-REMOVE" "1" "February 2026" "" +.TH "BUNDLE\-REMOVE" "1" "March 2026" "" .SH "NAME" \fBbundle\-remove\fR \- Removes gems from the Gemfile .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-show.1 b/lib/bundler/man/bundle-show.1 index 05ff3205766f72..4f6109a4a6c081 100644 --- a/lib/bundler/man/bundle-show.1 +++ b/lib/bundler/man/bundle-show.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-SHOW" "1" "February 2026" "" +.TH "BUNDLE\-SHOW" "1" "March 2026" "" .SH "NAME" \fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-update.1 b/lib/bundler/man/bundle-update.1 index db078d74fc8473..9e6076a2c2bf06 100644 --- a/lib/bundler/man/bundle-update.1 +++ b/lib/bundler/man/bundle-update.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-UPDATE" "1" "February 2026" "" +.TH "BUNDLE\-UPDATE" "1" "March 2026" "" .SH "NAME" \fBbundle\-update\fR \- Update your gems to the latest available versions .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle-version.1 b/lib/bundler/man/bundle-version.1 index 8f9088451bb076..bc0cf692b3da65 100644 --- a/lib/bundler/man/bundle-version.1 +++ b/lib/bundler/man/bundle-version.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE\-VERSION" "1" "February 2026" "" +.TH "BUNDLE\-VERSION" "1" "March 2026" "" .SH "NAME" \fBbundle\-version\fR \- Prints Bundler version information .SH "SYNOPSIS" diff --git a/lib/bundler/man/bundle.1 b/lib/bundler/man/bundle.1 index 8613924602a045..c69f0e26bc6f9f 100644 --- a/lib/bundler/man/bundle.1 +++ b/lib/bundler/man/bundle.1 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "BUNDLE" "1" "February 2026" "" +.TH "BUNDLE" "1" "March 2026" "" .SH "NAME" \fBbundle\fR \- Ruby Dependency Management .SH "SYNOPSIS" diff --git a/lib/bundler/man/gemfile.5 b/lib/bundler/man/gemfile.5 index 4fefd12a58eec2..2818b122103045 100644 --- a/lib/bundler/man/gemfile.5 +++ b/lib/bundler/man/gemfile.5 @@ -1,6 +1,6 @@ .\" generated with Ronn-NG/v0.10.1 .\" http://github.com/apjanke/ronn-ng/tree/0.10.1 -.TH "GEMFILE" "5" "February 2026" "" +.TH "GEMFILE" "5" "March 2026" "" .SH "NAME" \fBGemfile\fR \- A format for describing gem dependencies for Ruby programs .SH "SYNOPSIS" From 3f57ba8e586e72baa17cefa900a880a62b087e21 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Mar 2026 02:15:30 +0000 Subject: [PATCH 03/15] Bump taiki-e/install-action Bumps the github-actions group with 1 update in the / directory: [taiki-e/install-action](https://github.com/taiki-e/install-action). Updates `taiki-e/install-action` from 2.68.16 to 2.68.18 - [Release notes](https://github.com/taiki-e/install-action/releases) - [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/taiki-e/install-action/compare/d6e286fa45544157a02d45a43742857ebbc25d12...205eb1d74c6feda89abb1f3a09360601953286c0) --- updated-dependencies: - dependency-name: taiki-e/install-action dependency-version: 2.68.18 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: github-actions ... Signed-off-by: dependabot[bot] --- .github/workflows/zjit-macos.yml | 2 +- .github/workflows/zjit-ubuntu.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/zjit-macos.yml b/.github/workflows/zjit-macos.yml index 6ff1f977e85e9b..baf1b1bc0a6cd6 100644 --- a/.github/workflows/zjit-macos.yml +++ b/.github/workflows/zjit-macos.yml @@ -92,7 +92,7 @@ jobs: rustup install ${{ matrix.rust_version }} --profile minimal rustup default ${{ matrix.rust_version }} - - uses: taiki-e/install-action@d6e286fa45544157a02d45a43742857ebbc25d12 # v2.68.16 + - uses: taiki-e/install-action@205eb1d74c6feda89abb1f3a09360601953286c0 # v2.68.18 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} diff --git a/.github/workflows/zjit-ubuntu.yml b/.github/workflows/zjit-ubuntu.yml index 4bc959d6d66144..6c529ed6bc40c4 100644 --- a/.github/workflows/zjit-ubuntu.yml +++ b/.github/workflows/zjit-ubuntu.yml @@ -119,7 +119,7 @@ jobs: ruby-version: '3.1' bundler: none - - uses: taiki-e/install-action@d6e286fa45544157a02d45a43742857ebbc25d12 # v2.68.16 + - uses: taiki-e/install-action@205eb1d74c6feda89abb1f3a09360601953286c0 # v2.68.18 with: tool: nextest@0.9 if: ${{ matrix.test_task == 'zjit-check' }} From 115b4c65440557eaa89c6ceadc39fe6eea1b984d Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 4 Mar 2026 15:50:58 +0900 Subject: [PATCH 04/15] Add NEWS.md entry for ENV.fetch_values and that tests From https://github.com/ruby/rubygems/pull/8501 Co-authored-by: Suleyman Musayev <96992680+msuliq@users.noreply.github.com> --- NEWS.md | 6 ++++++ test/ruby/test_env.rb | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/NEWS.md b/NEWS.md index 54d875f92457e5..d9aaa08be6c754 100644 --- a/NEWS.md +++ b/NEWS.md @@ -11,6 +11,12 @@ Note that each entry is kept to a minimum, see links for details. Note: We're only listing outstanding class updates. +* ENV + + * `ENV.fetch_values` is added. It returns an array of values for the + given names, raising `KeyError` for missing names unless a block is + given. [[Feature #21781]] + * Kernel * `Kernel#autoload_relative` and `Module#autoload_relative` are added. diff --git a/test/ruby/test_env.rb b/test/ruby/test_env.rb index d17e300bceb2f1..dd526544af3aad 100644 --- a/test/ruby/test_env.rb +++ b/test/ruby/test_env.rb @@ -281,6 +281,26 @@ def test_values_at assert_equal(["foo", "foo"], ENV.values_at("test", "test")) end + def test_fetch_values + ENV["test"] = "foo" + ENV["test2"] = "bar" + assert_equal(["foo", "bar"], ENV.fetch_values("test", "test2")) + assert_equal(["foo", "foo"], ENV.fetch_values("test", "test")) + assert_equal([], ENV.fetch_values) + + ENV.delete("test2") + assert_raise(KeyError) { ENV.fetch_values("test", "test2") } + + assert_equal(["foo", "default"], ENV.fetch_values("test", "test2") { "default" }) + assert_equal(["foo", "TEST2"], ENV.fetch_values("test", "test2") { |k| k.upcase }) + + e = assert_raise(KeyError) { ENV.fetch_values("test2") } + assert_same(ENV, e.receiver) + assert_equal("test2", e.key) + + assert_invalid_env {|v| ENV.fetch_values(v)} + end + def test_select ENV["test"] = "foo" h = ENV.select {|k| IGNORE_CASE ? k.upcase == "TEST" : k == "test" } From f57af84dba44365ef175026f0c8cb1bd52949c47 Mon Sep 17 00:00:00 2001 From: Edouard CHIN Date: Fri, 27 Feb 2026 19:47:47 -0500 Subject: [PATCH 05/15] [ruby/rubygems] Fix plugin new version not registering: - ### Problem When a plugin in the Gemfile is updated to a new version, it will be downloaded but will not be registered. The old version of the plugin will be loaded when Bundler is invoked. ### Context The problem is in the `Index#installed?` method that only checks for the plugin name in the index. If it finds one, it skips the registration. ### Solution Check whether the registed plugin load paths matche the new plugin one. If not, register the new plugin which will override the previous one in the index. https://github.com/ruby/rubygems/commit/ac65001055 --- lib/bundler/plugin.rb | 4 ++-- lib/bundler/plugin/index.rb | 6 ++++++ spec/bundler/bundler/plugin_spec.rb | 16 ++++++++-------- spec/bundler/plugins/install_spec.rb | 25 +++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 10 deletions(-) diff --git a/lib/bundler/plugin.rb b/lib/bundler/plugin.rb index 3c68e5181224db..faca6bea53069f 100644 --- a/lib/bundler/plugin.rb +++ b/lib/bundler/plugin.rb @@ -113,7 +113,7 @@ def gemfile_install(gemfile = nil, &inline) return if definition.dependencies.empty? - plugins = definition.dependencies.map(&:name).reject {|p| index.installed? p } + plugins = definition.dependencies.map(&:name) installed_specs = Installer.new.install_definition(definition) save_plugins plugins, installed_specs, builder.inferred_plugins @@ -258,7 +258,7 @@ def save_plugins(plugins, specs, optional_plugins = []) # It's possible that the `plugin` found in the Gemfile don't appear in the specs. For instance when # calling `BUNDLE_WITHOUT=default bundle install`, the plugins will not get installed. next if spec.nil? - next if index.installed?(name) + next if index.up_to_date?(spec) save_plugin(name, spec, optional_plugins.include?(name)) end diff --git a/lib/bundler/plugin/index.rb b/lib/bundler/plugin/index.rb index 0682d37772b2ef..94683a5e544f49 100644 --- a/lib/bundler/plugin/index.rb +++ b/lib/bundler/plugin/index.rb @@ -119,6 +119,12 @@ def installed?(name) @plugin_paths[name] end + def up_to_date?(spec) + path = installed?(spec.name) + + path == spec.full_gem_path + end + def installed_plugins @plugin_paths.keys end diff --git a/spec/bundler/bundler/plugin_spec.rb b/spec/bundler/bundler/plugin_spec.rb index e416772a367240..b379594c6f9a90 100644 --- a/spec/bundler/bundler/plugin_spec.rb +++ b/spec/bundler/bundler/plugin_spec.rb @@ -65,8 +65,8 @@ end it "passes the name and options to installer" do - allow(index).to receive(:installed?). - with("new-plugin") + allow(index).to receive(:up_to_date?). + with(spec) allow(installer).to receive(:install).with(["new-plugin"], opts) do { "new-plugin" => spec } end.once @@ -75,8 +75,8 @@ end it "validates the installed plugin" do - allow(index).to receive(:installed?). - with("new-plugin") + allow(index).to receive(:up_to_date?). + with(spec) allow(subject). to receive(:validate_plugin!).with(lib_path("new-plugin")).once @@ -84,8 +84,8 @@ end it "registers the plugin with index" do - allow(index).to receive(:installed?). - with("new-plugin") + allow(index).to receive(:up_to_date?). + with(spec) allow(index).to receive(:register_plugin). with("new-plugin", lib_path("new-plugin").to_s, [lib_path("new-plugin").join("lib").to_s], []).once subject.install ["new-plugin"], opts @@ -102,7 +102,7 @@ end.once allow(subject).to receive(:validate_plugin!).twice - allow(index).to receive(:installed?).twice + allow(index).to receive(:up_to_date?).twice allow(index).to receive(:register_plugin).twice subject.install ["new-plugin", "another-plugin"], opts end @@ -138,7 +138,7 @@ end before do - allow(index).to receive(:installed?) { nil } + allow(index).to receive(:up_to_date?) { nil } allow(definition).to receive(:dependencies) { [Bundler::Dependency.new("new-plugin", ">=0"), Bundler::Dependency.new("another-plugin", ">=0")] } allow(installer).to receive(:install_definition) { plugin_specs } end diff --git a/spec/bundler/plugins/install_spec.rb b/spec/bundler/plugins/install_spec.rb index 36672f0085e617..dc3a12913eefe4 100644 --- a/spec/bundler/plugins/install_spec.rb +++ b/spec/bundler/plugins/install_spec.rb @@ -265,6 +265,31 @@ def exec(command, args) plugin_should_be_installed("foo") end + it "overrides the index with the new plugin version" do + gemfile <<-G + source 'https://gem.repo2' + plugin 'foo', "1.0" + gem 'myrack', "1.0.0" + G + + bundle "install" + + update_repo2 do + build_plugin "foo", "2.0.0" + end + + gemfile <<-G + source 'https://gem.repo2' + plugin 'foo', "2.0" + gem 'myrack', "1.0.0" + G + + bundle "install" + + expected = local_plugin_gem("foo-2.0.0", "lib").to_s + expect(Bundler::Plugin.index.load_paths("foo")).to eq([expected]) + end + it "respects bundler groups" do gemfile <<-G source 'https://gem.repo2' From d920d3995689cb2cc46c00d16cabaaaca5abfd6c Mon Sep 17 00:00:00 2001 From: Andrii Furmanets Date: Mon, 2 Mar 2026 18:54:29 +0200 Subject: [PATCH 06/15] [ruby/rubygems] Fix Gem::Request proxy env lookup when required directly https://github.com/ruby/rubygems/commit/db8fd6becb --- lib/rubygems/request.rb | 1 + test/rubygems/test_gem_request.rb | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/rubygems/request.rb b/lib/rubygems/request.rb index 9116785231ea10..e817ee57042d10 100644 --- a/lib/rubygems/request.rb +++ b/lib/rubygems/request.rb @@ -2,6 +2,7 @@ require_relative "vendored_net_http" require_relative "user_interaction" +require_relative "uri_formatter" class Gem::Request extend Gem::UserInteraction diff --git a/test/rubygems/test_gem_request.rb b/test/rubygems/test_gem_request.rb index cd0a416e79c02d..74e2e03ae73579 100644 --- a/test/rubygems/test_gem_request.rb +++ b/test/rubygems/test_gem_request.rb @@ -2,6 +2,7 @@ require_relative "helper" require "rubygems/request" +require "open3" unless Gem::HAVE_OPENSSL warn "Skipping Gem::Request tests. openssl not found." @@ -189,6 +190,18 @@ def test_get_proxy_from_env_empty assert_nil request.proxy_uri end + def test_get_proxy_from_env_when_requiring_request_directly + script = <<~RUBY + require "rubygems/request" + ENV["HTTP_PROXY"] = "fakeurl:12345" + Gem::Request.get_proxy_from_env("http") + RUBY + + output, status = Open3.capture2e(*ruby_with_rubygems_in_load_path, "-e", script) + + assert status.success?, output + end + def test_fetch uri = Gem::Uri.new(Gem::URI.parse("#{@gem_repo}/specs.#{Gem.marshal_version}")) response = util_stub_net_http(body: :junk, code: 200) do From d3b6bfe6f530cb5b4a03918bf12b0e6315c53513 Mon Sep 17 00:00:00 2001 From: Andrii Furmanets Date: Wed, 4 Mar 2026 10:21:52 +0200 Subject: [PATCH 07/15] [ruby/rubygems] Drop subprocess regression test for request proxy lookup https://github.com/ruby/rubygems/commit/fdae8c7698 --- test/rubygems/test_gem_request.rb | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/test/rubygems/test_gem_request.rb b/test/rubygems/test_gem_request.rb index 74e2e03ae73579..cd0a416e79c02d 100644 --- a/test/rubygems/test_gem_request.rb +++ b/test/rubygems/test_gem_request.rb @@ -2,7 +2,6 @@ require_relative "helper" require "rubygems/request" -require "open3" unless Gem::HAVE_OPENSSL warn "Skipping Gem::Request tests. openssl not found." @@ -190,18 +189,6 @@ def test_get_proxy_from_env_empty assert_nil request.proxy_uri end - def test_get_proxy_from_env_when_requiring_request_directly - script = <<~RUBY - require "rubygems/request" - ENV["HTTP_PROXY"] = "fakeurl:12345" - Gem::Request.get_proxy_from_env("http") - RUBY - - output, status = Open3.capture2e(*ruby_with_rubygems_in_load_path, "-e", script) - - assert status.success?, output - end - def test_fetch uri = Gem::Uri.new(Gem::URI.parse("#{@gem_repo}/specs.#{Gem.marshal_version}")) response = util_stub_net_http(body: :junk, code: 200) do From fa8b6aff710e97923cbf80ac8627b798cfe476e4 Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Thu, 12 Feb 2026 15:20:56 +0000 Subject: [PATCH 08/15] [ruby/mmtk] Buffer obj_free candidates. Previously, every object allocation in rb_gc_impl_new_obj made a per-object FFI call into Rust (mmtk_add_obj_free_candidate), which acquired a mutex on one of the WeakProcessor's candidate vecs, pushed a single element, and released the mutex. That's an FFI crossing + mutex lock/unlock on every single allocation. Now, each MMTk_ractor_cache has two local buffers (parallel-freeable and non-parallel-freeable, 128 entries each). On allocation, we just store the pointer into the local buffer. When a buffer fills up, we flush the entire batch in one FFI call using mmtk_add_obj_free_candidates, which does a single mutex acquisition and extend_from_slice for the whole batch. We picked 128 as our buffer size at random. We should probably investigate further what an optimum size for this is https://github.com/ruby/mmtk/commit/23c4a9a676 --- gc/mmtk/mmtk.c | 56 ++++++++++++++++++++++++++++++++++++++-- gc/mmtk/mmtk.h | 4 ++- gc/mmtk/src/api.rs | 10 ++++--- gc/mmtk/src/weak_proc.rs | 17 +++++++----- 4 files changed, 74 insertions(+), 13 deletions(-) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index 4832916ce6ea2f..d5503956e3830b 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -48,6 +48,8 @@ struct objspace { unsigned int fork_hook_vm_lock_lev; }; +#define OBJ_FREE_BUF_CAPACITY 128 + struct MMTk_ractor_cache { struct ccan_list_node list_node; @@ -55,6 +57,11 @@ struct MMTk_ractor_cache { bool gc_mutator_p; MMTk_BumpPointer *bump_pointer; + + MMTk_ObjectReference obj_free_parallel_buf[OBJ_FREE_BUF_CAPACITY]; + size_t obj_free_parallel_count; + MMTk_ObjectReference obj_free_non_parallel_buf[OBJ_FREE_BUF_CAPACITY]; + size_t obj_free_non_parallel_count; }; struct MMTk_final_job { @@ -143,6 +150,8 @@ rb_mmtk_resume_mutators(void) } } +static void mmtk_flush_obj_free_buffer(struct MMTk_ractor_cache *cache); + static void rb_mmtk_block_for_gc(MMTk_VMMutatorThread mutator) { @@ -173,6 +182,11 @@ rb_mmtk_block_for_gc(MMTk_VMMutatorThread mutator) rb_gc_vm_barrier(); + struct MMTk_ractor_cache *rc; + ccan_list_for_each(&objspace->ractor_caches, rc, list_node) { + mmtk_flush_obj_free_buffer(rc); + } + objspace->world_stopped = true; pthread_cond_broadcast(&objspace->cond_world_stopped); @@ -584,7 +598,7 @@ rb_gc_impl_ractor_cache_alloc(void *objspace_ptr, void *ractor) } objspace->live_ractor_cache_count++; - struct MMTk_ractor_cache *cache = malloc(sizeof(struct MMTk_ractor_cache)); + struct MMTk_ractor_cache *cache = calloc(1, sizeof(struct MMTk_ractor_cache)); ccan_list_add(&objspace->ractor_caches, &cache->list_node); cache->mutator = mmtk_bind_mutator(cache); @@ -601,6 +615,8 @@ rb_gc_impl_ractor_cache_free(void *objspace_ptr, void *cache_ptr) ccan_list_del(&cache->list_node); + mmtk_flush_obj_free_buffer(cache); + if (ruby_free_at_exit_p()) { MMTK_ASSERT(objspace->live_ractor_cache_count > 0); } @@ -801,6 +817,42 @@ obj_can_parallel_free_p(VALUE obj) } } +static void +mmtk_flush_obj_free_buffer(struct MMTk_ractor_cache *cache) +{ + if (cache->obj_free_parallel_count > 0) { + mmtk_add_obj_free_candidates(cache->obj_free_parallel_buf, + cache->obj_free_parallel_count, true); + cache->obj_free_parallel_count = 0; + } + if (cache->obj_free_non_parallel_count > 0) { + mmtk_add_obj_free_candidates(cache->obj_free_non_parallel_buf, + cache->obj_free_non_parallel_count, false); + cache->obj_free_non_parallel_count = 0; + } +} + +static inline void +mmtk_buffer_obj_free_candidate(struct MMTk_ractor_cache *cache, VALUE obj) +{ + if (obj_can_parallel_free_p(obj)) { + cache->obj_free_parallel_buf[cache->obj_free_parallel_count++] = (MMTk_ObjectReference)obj; + if (cache->obj_free_parallel_count >= OBJ_FREE_BUF_CAPACITY) { + mmtk_add_obj_free_candidates(cache->obj_free_parallel_buf, + cache->obj_free_parallel_count, true); + cache->obj_free_parallel_count = 0; + } + } + else { + cache->obj_free_non_parallel_buf[cache->obj_free_non_parallel_count++] = (MMTk_ObjectReference)obj; + if (cache->obj_free_non_parallel_count >= OBJ_FREE_BUF_CAPACITY) { + mmtk_add_obj_free_candidates(cache->obj_free_non_parallel_buf, + cache->obj_free_non_parallel_count, false); + cache->obj_free_non_parallel_count = 0; + } + } +} + VALUE rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags, bool wb_protected, size_t alloc_size) { @@ -837,7 +889,7 @@ rb_gc_impl_new_obj(void *objspace_ptr, void *cache_ptr, VALUE klass, VALUE flags mmtk_post_alloc(ractor_cache->mutator, (void*)alloc_obj, alloc_size, MMTK_ALLOCATION_SEMANTICS_DEFAULT); // TODO: only add when object needs obj_free to be called - mmtk_add_obj_free_candidate(alloc_obj, obj_can_parallel_free_p((VALUE)alloc_obj)); + mmtk_buffer_obj_free_candidate(ractor_cache, (VALUE)alloc_obj); objspace->total_allocated_objects++; diff --git a/gc/mmtk/mmtk.h b/gc/mmtk/mmtk.h index ffbad1a025cce0..20d268419ddea0 100644 --- a/gc/mmtk/mmtk.h +++ b/gc/mmtk/mmtk.h @@ -123,7 +123,9 @@ void mmtk_post_alloc(MMTk_Mutator *mutator, size_t bytes, MMTk_AllocationSemantics semantics); -void mmtk_add_obj_free_candidate(MMTk_ObjectReference object, bool can_parallel_free); +void mmtk_add_obj_free_candidates(const MMTk_ObjectReference *objects, + size_t count, + bool can_parallel_free); void mmtk_declare_weak_references(MMTk_ObjectReference object); diff --git a/gc/mmtk/src/api.rs b/gc/mmtk/src/api.rs index 5eac068672b549..b9797f6fe2df6f 100644 --- a/gc/mmtk/src/api.rs +++ b/gc/mmtk/src/api.rs @@ -297,12 +297,16 @@ pub unsafe extern "C" fn mmtk_post_alloc( memory_manager::post_alloc::(unsafe { &mut *mutator }, refer, bytes, semantics) } -// TODO: Replace with buffered mmtk_add_obj_free_candidates #[no_mangle] -pub extern "C" fn mmtk_add_obj_free_candidate(object: ObjectReference, can_parallel_free: bool) { +pub unsafe extern "C" fn mmtk_add_obj_free_candidates( + objects: *const ObjectReference, + count: usize, + can_parallel_free: bool, +) { + let objects = unsafe { std::slice::from_raw_parts(objects, count) }; binding() .weak_proc - .add_obj_free_candidate(object, can_parallel_free) + .add_obj_free_candidates_batch(objects, can_parallel_free) } // =============== Weak references =============== diff --git a/gc/mmtk/src/weak_proc.rs b/gc/mmtk/src/weak_proc.rs index f103822b737272..7841db4f229af0 100644 --- a/gc/mmtk/src/weak_proc.rs +++ b/gc/mmtk/src/weak_proc.rs @@ -48,13 +48,16 @@ impl WeakProcessor { } } - /// Add an object as a candidate for `obj_free`. + /// Add a batch of objects as candidates for `obj_free`. /// - /// Multiple mutators can call it concurrently, so it has `&self`. - pub fn add_obj_free_candidate(&self, object: ObjectReference, can_parallel_free: bool) { + /// Amortizes mutex acquisition over the entire batch. Called when a + /// mutator's local buffer is flushed (buffer full or stop-the-world). + pub fn add_obj_free_candidates_batch(&self, objects: &[ObjectReference], can_parallel_free: bool) { + if objects.is_empty() { + return; + } + if can_parallel_free { - // Newly allocated objects are placed in parallel_obj_free_candidates using - // round-robin. This may not be ideal for load balancing. let idx = self .parallel_obj_free_candidates_counter .fetch_add(1, Ordering::Relaxed) @@ -63,12 +66,12 @@ impl WeakProcessor { self.parallel_obj_free_candidates[idx] .lock() .unwrap() - .push(object); + .extend_from_slice(objects); } else { self.non_parallel_obj_free_candidates .lock() .unwrap() - .push(object); + .extend_from_slice(objects); } } From 092620090a3e639b223a7f03fbdfdd088d93a141 Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Thu, 12 Feb 2026 15:40:56 +0000 Subject: [PATCH 09/15] [ruby/mmtk] Fix Cargo format issues https://github.com/ruby/mmtk/commit/26ec9f7f89 --- gc/mmtk/src/weak_proc.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gc/mmtk/src/weak_proc.rs b/gc/mmtk/src/weak_proc.rs index 7841db4f229af0..01bac8a23b0d9c 100644 --- a/gc/mmtk/src/weak_proc.rs +++ b/gc/mmtk/src/weak_proc.rs @@ -52,7 +52,11 @@ impl WeakProcessor { /// /// Amortizes mutex acquisition over the entire batch. Called when a /// mutator's local buffer is flushed (buffer full or stop-the-world). - pub fn add_obj_free_candidates_batch(&self, objects: &[ObjectReference], can_parallel_free: bool) { + pub fn add_obj_free_candidates_batch( + &self, + objects: &[ObjectReference], + can_parallel_free: bool, + ) { if objects.is_empty() { return; } From 3ec9bafea1a08eee3a8c76dc01d01ef0ba61f81c Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Thu, 12 Feb 2026 22:40:01 +0000 Subject: [PATCH 10/15] [ruby/mmtk] Flush obj_free buffers before shutdown finalizers shutdown_call_finalizer reads candidates from the Rust-side WeakProcessor, but the main ractor's C-side buffer may not have been flushed yet (ractor_cache_free runs later). Flush all remaining buffers before reading candidates. https://github.com/ruby/mmtk/commit/7e01232134 --- gc/mmtk/mmtk.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gc/mmtk/mmtk.c b/gc/mmtk/mmtk.c index d5503956e3830b..21d97b81efa611 100644 --- a/gc/mmtk/mmtk.c +++ b/gc/mmtk/mmtk.c @@ -1329,6 +1329,11 @@ rb_gc_impl_shutdown_call_finalizer(void *objspace_ptr) unsigned int lev = RB_GC_VM_LOCK(); { + struct MMTk_ractor_cache *rc; + ccan_list_for_each(&objspace->ractor_caches, rc, list_node) { + mmtk_flush_obj_free_buffer(rc); + } + struct MMTk_RawVecOfObjRef registered_candidates = mmtk_get_all_obj_free_candidates(); for (size_t i = 0; i < registered_candidates.len; i++) { VALUE obj = (VALUE)registered_candidates.ptr[i]; From 45dabe3382bc71fbb40ec60eb3eaa0e21de2564d Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Thu, 12 Feb 2026 22:59:02 +0000 Subject: [PATCH 11/15] [ruby/mmtk] Distribute batch candidates across parallel buckets Instead of sending all 128 buffered objects to one bucket, round-robin distribute them across all worker buckets so parallel obj_free work stays balanced. https://github.com/ruby/mmtk/commit/e1f926cd21 --- gc/mmtk/src/weak_proc.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/gc/mmtk/src/weak_proc.rs b/gc/mmtk/src/weak_proc.rs index 01bac8a23b0d9c..5b568c6b1107bc 100644 --- a/gc/mmtk/src/weak_proc.rs +++ b/gc/mmtk/src/weak_proc.rs @@ -1,5 +1,3 @@ -use std::sync::atomic::AtomicUsize; -use std::sync::atomic::Ordering; use std::sync::Mutex; use mmtk::scheduler::GCWork; @@ -15,7 +13,6 @@ use crate::Ruby; pub struct WeakProcessor { non_parallel_obj_free_candidates: Mutex>, parallel_obj_free_candidates: Vec>>, - parallel_obj_free_candidates_counter: AtomicUsize, /// Objects that needs `obj_free` called when dying. /// If it is a bottleneck, replace it with a lock-free data structure, @@ -34,7 +31,6 @@ impl WeakProcessor { Self { non_parallel_obj_free_candidates: Mutex::new(Vec::new()), parallel_obj_free_candidates: vec![Mutex::new(Vec::new())], - parallel_obj_free_candidates_counter: AtomicUsize::new(0), weak_references: Mutex::new(Vec::new()), } } @@ -62,15 +58,17 @@ impl WeakProcessor { } if can_parallel_free { - let idx = self - .parallel_obj_free_candidates_counter - .fetch_add(1, Ordering::Relaxed) - % self.parallel_obj_free_candidates.len(); - - self.parallel_obj_free_candidates[idx] - .lock() - .unwrap() - .extend_from_slice(objects); + let num_buckets = self.parallel_obj_free_candidates.len(); + for idx in 0..num_buckets { + let mut bucket = self.parallel_obj_free_candidates[idx] + .lock() + .unwrap(); + for (i, &obj) in objects.iter().enumerate() { + if i % num_buckets == idx { + bucket.push(obj); + } + } + } } else { self.non_parallel_obj_free_candidates .lock() From 4d7f3ba286f3bc67554417008d4707f014520ce6 Mon Sep 17 00:00:00 2001 From: Matt Valentine-House Date: Thu, 12 Feb 2026 23:12:19 +0000 Subject: [PATCH 12/15] [ruby/mmtk] Cargo format https://github.com/ruby/mmtk/commit/7889da7c0e --- gc/mmtk/src/weak_proc.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/gc/mmtk/src/weak_proc.rs b/gc/mmtk/src/weak_proc.rs index 5b568c6b1107bc..d38dbe04a4f15f 100644 --- a/gc/mmtk/src/weak_proc.rs +++ b/gc/mmtk/src/weak_proc.rs @@ -60,9 +60,7 @@ impl WeakProcessor { if can_parallel_free { let num_buckets = self.parallel_obj_free_candidates.len(); for idx in 0..num_buckets { - let mut bucket = self.parallel_obj_free_candidates[idx] - .lock() - .unwrap(); + let mut bucket = self.parallel_obj_free_candidates[idx].lock().unwrap(); for (i, &obj) in objects.iter().enumerate() { if i % num_buckets == idx { bucket.push(obj); From 795619ec11b9803f38a32edf1daba6557dddb149 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Wed, 4 Mar 2026 12:49:25 +0100 Subject: [PATCH 13/15] [ruby/prism] Revert "Reject infix operators on command call on writes" (https://github.com/ruby/prism/pull/3960) This reverts commit https://github.com/ruby/prism/commit/4e71dbfc7bd9. And also add a regression test. Seems like currently prism parses these the same that parse.y does. https://github.com/ruby/prism/commit/03993421f2 --- prism/prism.c | 30 ++----------------- test/prism/errors/command_call_in.txt | 2 -- test/prism/errors/write_command.txt | 4 --- .../prism/fixtures/write_command_operator.txt | 3 ++ 4 files changed, 6 insertions(+), 33 deletions(-) delete mode 100644 test/prism/errors/write_command.txt create mode 100644 test/prism/fixtures/write_command_operator.txt diff --git a/prism/prism.c b/prism/prism.c index ebd86b01fe089b..a131f3791277b2 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -21619,26 +21619,6 @@ pm_call_node_command_p(const pm_call_node_t *node) { ); } -/** - * Determine if a given write node has a command call as its right-hand side. We - * need this because command calls as the values of writes cannot be extended by - * infix operators. - */ -static inline bool -pm_write_node_command_p(const pm_node_t *node) { - pm_node_t *value; - switch (PM_NODE_TYPE(node)) { - case PM_CLASS_VARIABLE_WRITE_NODE: value = ((pm_class_variable_write_node_t *) node)->value; break; - case PM_CONSTANT_PATH_WRITE_NODE: value = ((pm_constant_path_write_node_t *) node)->value; break; - case PM_CONSTANT_WRITE_NODE: value = ((pm_constant_write_node_t *) node)->value; break; - case PM_GLOBAL_VARIABLE_WRITE_NODE: value = ((pm_global_variable_write_node_t *) node)->value; break; - case PM_INSTANCE_VARIABLE_WRITE_NODE: value = ((pm_instance_variable_write_node_t *) node)->value; break; - case PM_LOCAL_VARIABLE_WRITE_NODE: value = ((pm_local_variable_write_node_t *) node)->value; break; - default: return false; - } - return PM_NODE_TYPE_P(value, PM_CALL_NODE) && pm_call_node_command_p((pm_call_node_t *) value); -} - /** * Parse an expression at the given point of the parser using the given binding * power to parse subsequent chains. If this function finds a syntax error, it @@ -21723,13 +21703,9 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, bool acc case PM_INSTANCE_VARIABLE_WRITE_NODE: case PM_LOCAL_VARIABLE_WRITE_NODE: // These expressions are statements, by virtue of the right-hand - // side of their write being an implicit array or a command call. - // This mirrors parse.y's behavior where `lhs = command_call` - // reduces to stmt (not expr), preventing and/or from following. - if (pm_binding_powers[parser->current.type].left > PM_BINDING_POWER_MODIFIER) { - if (PM_NODE_FLAG_P(node, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY) || pm_write_node_command_p(node)) { - return node; - } + // side of their write being an implicit array. + if (PM_NODE_FLAG_P(node, PM_WRITE_NODE_FLAGS_IMPLICIT_ARRAY) && pm_binding_powers[parser->current.type].left > PM_BINDING_POWER_MODIFIER) { + return node; } break; case PM_CALL_NODE: diff --git a/test/prism/errors/command_call_in.txt b/test/prism/errors/command_call_in.txt index e9e8e82d3a64ac..2fdcf0973897b7 100644 --- a/test/prism/errors/command_call_in.txt +++ b/test/prism/errors/command_call_in.txt @@ -2,6 +2,4 @@ foo 1 in a ^~ unexpected 'in', expecting end-of-input ^~ unexpected 'in', ignoring it a = foo 2 in b - ^~ unexpected 'in', expecting end-of-input - ^~ unexpected 'in', ignoring it diff --git a/test/prism/errors/write_command.txt b/test/prism/errors/write_command.txt deleted file mode 100644 index 5024f8452a276f..00000000000000 --- a/test/prism/errors/write_command.txt +++ /dev/null @@ -1,4 +0,0 @@ -a = b c and 1 - ^~~ unexpected 'and', expecting end-of-input - ^~~ unexpected 'and', ignoring it - diff --git a/test/prism/fixtures/write_command_operator.txt b/test/prism/fixtures/write_command_operator.txt new file mode 100644 index 00000000000000..d719d24f873027 --- /dev/null +++ b/test/prism/fixtures/write_command_operator.txt @@ -0,0 +1,3 @@ +foo = 123 | '456' or return + +foo = 123 | '456' in BAR From d2516d32418e7414667d1e1cd16b02cc622e1881 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 3 Mar 2026 23:48:27 +0900 Subject: [PATCH 14/15] Calculate padding by alignment size --- compile.c | 115 +++++++++++++++++++++++++++--------------------------- 1 file changed, 58 insertions(+), 57 deletions(-) diff --git a/compile.c b/compile.c index c9806947fd8669..e3d984324e2e23 100644 --- a/compile.c +++ b/compile.c @@ -1056,48 +1056,45 @@ rb_iseq_original_iseq(const rb_iseq_t *iseq) /* cold path */ #define ALIGNMENT_SIZE SIZEOF_VALUE #endif #define PADDING_SIZE_MAX ((size_t)((ALIGNMENT_SIZE) - 1)) - #define ALIGNMENT_SIZE_MASK PADDING_SIZE_MAX - /* Note: ALIGNMENT_SIZE == (2 ** N) is expected. */ #else #define PADDING_SIZE_MAX 0 #endif /* STRICT_ALIGNMENT */ -#ifdef STRICT_ALIGNMENT +#define ALIGNMENT_SIZE_OF(type) alignment_size_assert(RUBY_ALIGNOF(type), #type) + +static inline size_t +alignment_size_assert(size_t align, const char *type) +{ + RUBY_ASSERT((align & (align - 1)) == 0, + "ALIGNMENT_SIZE_OF(%s):%zd == (2 ** N) is expected", type, align); + return align; +} + /* calculate padding size for aligned memory access */ -static size_t -calc_padding(void *ptr, size_t size) +static inline size_t +calc_padding(void *ptr, size_t align) { +#ifdef STRICT_ALIGNMENT size_t mis; size_t padding = 0; - mis = (size_t)ptr & ALIGNMENT_SIZE_MASK; + mis = (size_t)ptr & (align - 1); if (mis > 0) { - padding = ALIGNMENT_SIZE - mis; + padding = align - mis; } -/* - * On 32-bit sparc or equivalents, when a single VALUE is requested - * and padding == sizeof(VALUE), it is clear that no padding is needed. - */ -#if ALIGNMENT_SIZE > SIZEOF_VALUE - if (size == sizeof(VALUE) && padding == sizeof(VALUE)) { - padding = 0; - } -#endif return padding; +#else + return 0; /* expected to be optimized by compiler */ +#endif } -#endif /* STRICT_ALIGNMENT */ static void * -compile_data_alloc_with_arena(struct iseq_compile_data_storage **arena, size_t size) +compile_data_alloc_with_arena(struct iseq_compile_data_storage **arena, size_t size, size_t align) { void *ptr = 0; struct iseq_compile_data_storage *storage = *arena; -#ifdef STRICT_ALIGNMENT - size_t padding = calc_padding((void *)&storage->buff[storage->pos], size); -#else - const size_t padding = 0; /* expected to be optimized by compiler */ -#endif /* STRICT_ALIGNMENT */ + size_t padding = calc_padding((void *)&storage->buff[storage->pos], align); if (size >= INT_MAX - padding) rb_memerror(); if (storage->pos + size + padding > storage->size) { @@ -1113,14 +1110,10 @@ compile_data_alloc_with_arena(struct iseq_compile_data_storage **arena, size_t s storage->next = 0; storage->pos = 0; storage->size = alloc_size; -#ifdef STRICT_ALIGNMENT - padding = calc_padding((void *)&storage->buff[storage->pos], size); -#endif /* STRICT_ALIGNMENT */ + padding = calc_padding((void *)&storage->buff[storage->pos], align); } -#ifdef STRICT_ALIGNMENT storage->pos += (int)padding; -#endif /* STRICT_ALIGNMENT */ ptr = (void *)&storage->buff[storage->pos]; storage->pos += (int)size; @@ -1128,51 +1121,60 @@ compile_data_alloc_with_arena(struct iseq_compile_data_storage **arena, size_t s } static void * -compile_data_alloc(rb_iseq_t *iseq, size_t size) +compile_data_alloc(rb_iseq_t *iseq, size_t size, size_t align) { struct iseq_compile_data_storage ** arena = &ISEQ_COMPILE_DATA(iseq)->node.storage_current; - return compile_data_alloc_with_arena(arena, size); + return compile_data_alloc_with_arena(arena, size, align); } +#define compile_data_alloc_type(iseq, type) \ + (type *)compile_data_alloc(iseq, sizeof(type), ALIGNMENT_SIZE_OF(type)) + static inline void * -compile_data_alloc2(rb_iseq_t *iseq, size_t x, size_t y) +compile_data_alloc2(rb_iseq_t *iseq, size_t elsize, size_t num, size_t align) { - size_t size = rb_size_mul_or_raise(x, y, rb_eRuntimeError); - return compile_data_alloc(iseq, size); + size_t size = rb_size_mul_or_raise(elsize, num, rb_eRuntimeError); + return compile_data_alloc(iseq, size, align); } +#define compile_data_alloc2_type(iseq, type, num) \ + (type *)compile_data_alloc2(iseq, sizeof(type), num, ALIGNMENT_SIZE_OF(type)) + static inline void * -compile_data_calloc2(rb_iseq_t *iseq, size_t x, size_t y) +compile_data_calloc2(rb_iseq_t *iseq, size_t elsize, size_t num, size_t align) { - size_t size = rb_size_mul_or_raise(x, y, rb_eRuntimeError); - void *p = compile_data_alloc(iseq, size); + size_t size = rb_size_mul_or_raise(elsize, num, rb_eRuntimeError); + void *p = compile_data_alloc(iseq, size, align); memset(p, 0, size); return p; } +#define compile_data_calloc2_type(iseq, type, num) \ + (type *)compile_data_calloc2(iseq, sizeof(type), num, ALIGNMENT_SIZE_OF(type)) + static INSN * compile_data_alloc_insn(rb_iseq_t *iseq) { struct iseq_compile_data_storage ** arena = &ISEQ_COMPILE_DATA(iseq)->insn.storage_current; - return (INSN *)compile_data_alloc_with_arena(arena, sizeof(INSN)); + return (INSN *)compile_data_alloc_with_arena(arena, sizeof(INSN), ALIGNMENT_SIZE_OF(INSN)); } static LABEL * compile_data_alloc_label(rb_iseq_t *iseq) { - return (LABEL *)compile_data_alloc(iseq, sizeof(LABEL)); + return compile_data_alloc_type(iseq, LABEL); } static ADJUST * compile_data_alloc_adjust(rb_iseq_t *iseq) { - return (ADJUST *)compile_data_alloc(iseq, sizeof(ADJUST)); + return compile_data_alloc_type(iseq, ADJUST); } static TRACE * compile_data_alloc_trace(rb_iseq_t *iseq) { - return (TRACE *)compile_data_alloc(iseq, sizeof(TRACE)); + return compile_data_alloc_type(iseq, TRACE); } /* @@ -1433,7 +1435,7 @@ new_insn_body(rb_iseq_t *iseq, int line_no, int node_id, enum ruby_vminsn_type i if (argc > 0) { int i; va_start(argv, argc); - operands = compile_data_alloc2(iseq, sizeof(VALUE), argc); + operands = compile_data_alloc2_type(iseq, VALUE, argc); for (i = 0; i < argc; i++) { VALUE v = va_arg(argv, VALUE); operands[i] = v; @@ -1451,7 +1453,7 @@ insn_replace_with_operands(rb_iseq_t *iseq, INSN *iobj, enum ruby_vminsn_type in if (argc > 0) { int i; va_start(argv, argc); - operands = compile_data_alloc2(iseq, sizeof(VALUE), argc); + operands = compile_data_alloc2_type(iseq, VALUE, argc); for (i = 0; i < argc; i++) { VALUE v = va_arg(argv, VALUE); operands[i] = v; @@ -1491,7 +1493,7 @@ new_callinfo(rb_iseq_t *iseq, ID mid, int argc, unsigned int flag, struct rb_cal static INSN * new_insn_send(rb_iseq_t *iseq, int line_no, int node_id, ID id, VALUE argc, const rb_iseq_t *blockiseq, VALUE flag, struct rb_callinfo_kwarg *keywords) { - VALUE *operands = compile_data_calloc2(iseq, sizeof(VALUE), 2); + VALUE *operands = compile_data_calloc2_type(iseq, VALUE, 2); VALUE ci = (VALUE)new_callinfo(iseq, id, FIX2INT(argc), FIX2INT(flag), keywords, blockiseq != NULL); operands[0] = ci; operands[1] = (VALUE)blockiseq; @@ -4552,7 +4554,7 @@ new_unified_insn(rb_iseq_t *iseq, } if (argc > 0) { - ptr = operands = compile_data_alloc2(iseq, sizeof(VALUE), argc); + ptr = operands = compile_data_alloc2_type(iseq, VALUE, argc); } /* copy operands */ @@ -6483,7 +6485,7 @@ add_ensure_range(rb_iseq_t *iseq, struct ensure_range *erange, LABEL *lstart, LABEL *lend) { struct ensure_range *ne = - compile_data_alloc(iseq, sizeof(struct ensure_range)); + compile_data_alloc_type(iseq, struct ensure_range); while (erange->next != 0) { erange = erange->next; @@ -10765,8 +10767,10 @@ compile_shareable_literal_constant(rb_iseq_t *iseq, LINK_ANCHOR *ret, enum rb_pa ADD_INSN1(anchor, node, newarray, INT2FIX(RNODE_LIST(node)->as.nd_alen)); } else if (nd_type(node) == NODE_HASH) { - int len = (int)RNODE_LIST(RNODE_HASH(node)->nd_head)->as.nd_alen; - ADD_INSN1(anchor, node, newhash, INT2FIX(len)); + long len = RNODE_LIST(RNODE_HASH(node)->nd_head)->as.nd_alen; + RBIMPL_ASSERT_OR_ASSUME(len >= 0); + RBIMPL_ASSERT_OR_ASSUME(RB_POSFIXABLE(len)); + ADD_INSN1(anchor, node, newhash, LONG2FIX(len)); } *value_p = Qundef; *shareable_literal_p = 0; @@ -10780,8 +10784,10 @@ compile_shareable_literal_constant(rb_iseq_t *iseq, LINK_ANCHOR *ret, enum rb_pa ADD_INSN1(anchor, node, newarray, INT2FIX(RNODE_LIST(node)->as.nd_alen)); } else if (nd_type(node) == NODE_HASH) { - int len = (int)RNODE_LIST(RNODE_HASH(node)->nd_head)->as.nd_alen; - ADD_INSN1(anchor, node, newhash, INT2FIX(len)); + long len = RNODE_LIST(RNODE_HASH(node)->nd_head)->as.nd_alen; + RBIMPL_ASSERT_OR_ASSUME(len >= 0); + RBIMPL_ASSERT_OR_ASSUME(RB_POSFIXABLE(len)); + ADD_INSN1(anchor, node, newhash, LONG2FIX(len)); } CHECK(compile_make_shareable_node(iseq, ret, anchor, node, false)); *value_p = Qundef; @@ -12077,7 +12083,7 @@ iseq_build_from_ary_body(rb_iseq_t *iseq, LINK_ANCHOR *const anchor, } if (argc > 0) { - argv = compile_data_calloc2(iseq, sizeof(VALUE), argc); + argv = compile_data_calloc2_type(iseq, VALUE, argc); // add element before operand setup to make GC root ADD_ELEM(anchor, @@ -12304,23 +12310,18 @@ rb_iseq_mark_and_move_insn_storage(struct iseq_compile_data_storage *storage) { INSN *iobj = 0; size_t size = sizeof(INSN); + size_t align = ALIGNMENT_SIZE_OF(INSN); unsigned int pos = 0; while (storage) { -#ifdef STRICT_ALIGNMENT - size_t padding = calc_padding((void *)&storage->buff[pos], size); -#else - const size_t padding = 0; /* expected to be optimized by compiler */ -#endif /* STRICT_ALIGNMENT */ + size_t padding = calc_padding((void *)&storage->buff[pos], align); size_t offset = pos + size + padding; if (offset > storage->size || offset > storage->pos) { pos = 0; storage = storage->next; } else { -#ifdef STRICT_ALIGNMENT pos += (int)padding; -#endif /* STRICT_ALIGNMENT */ iobj = (INSN *)&storage->buff[pos]; From 75d6b159eba73d4effe5989052f621177423e9db Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Wed, 4 Mar 2026 20:37:32 +0900 Subject: [PATCH 15/15] Always strictly align iseq compile data --- compile.c | 38 +++++--------------------------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/compile.c b/compile.c index e3d984324e2e23..ecc19f6b651d72 100644 --- a/compile.c +++ b/compile.c @@ -1029,36 +1029,12 @@ rb_iseq_original_iseq(const rb_iseq_t *iseq) /* cold path */ /* definition of data structure for compiler */ /*********************************************/ -/* - * On 32-bit SPARC, GCC by default generates SPARC V7 code that may require - * 8-byte word alignment. On the other hand, Oracle Solaris Studio seems to - * generate SPARCV8PLUS code with unaligned memory access instructions. - * That is why the STRICT_ALIGNMENT is defined only with GCC. - */ -#if defined(__sparc) && SIZEOF_VOIDP == 4 && defined(__GNUC__) - #define STRICT_ALIGNMENT -#endif - -/* - * Some OpenBSD platforms (including sparc64) require strict alignment. - */ -#if defined(__OpenBSD__) - #include - #ifdef __STRICT_ALIGNMENT - #define STRICT_ALIGNMENT - #endif -#endif - -#ifdef STRICT_ALIGNMENT - #if defined(HAVE_TRUE_LONG_LONG) && SIZEOF_LONG_LONG > SIZEOF_VALUE - #define ALIGNMENT_SIZE SIZEOF_LONG_LONG - #else - #define ALIGNMENT_SIZE SIZEOF_VALUE - #endif - #define PADDING_SIZE_MAX ((size_t)((ALIGNMENT_SIZE) - 1)) +#if defined(HAVE_TRUE_LONG_LONG) && SIZEOF_LONG_LONG > SIZEOF_VALUE +# define ALIGNMENT_SIZE SIZEOF_LONG_LONG #else - #define PADDING_SIZE_MAX 0 -#endif /* STRICT_ALIGNMENT */ +# define ALIGNMENT_SIZE SIZEOF_VALUE +#endif +#define PADDING_SIZE_MAX ((size_t)((ALIGNMENT_SIZE) - 1)) #define ALIGNMENT_SIZE_OF(type) alignment_size_assert(RUBY_ALIGNOF(type), #type) @@ -1074,7 +1050,6 @@ alignment_size_assert(size_t align, const char *type) static inline size_t calc_padding(void *ptr, size_t align) { -#ifdef STRICT_ALIGNMENT size_t mis; size_t padding = 0; @@ -1084,9 +1059,6 @@ calc_padding(void *ptr, size_t align) } return padding; -#else - return 0; /* expected to be optimized by compiler */ -#endif } static void *