diff --git a/jit.c b/jit.c
index 114d69808a8f37..fa672f1feef23a 100644
--- a/jit.c
+++ b/jit.c
@@ -586,6 +586,12 @@ rb_jit_multi_ractor_p(void)
return rb_multi_ractor_p();
}
+bool
+rb_jit_constcache_shareable(const struct iseq_inline_constant_cache_entry *ice)
+{
+ return (ice->flags & IMEMO_CONST_CACHE_SHAREABLE) != 0;
+}
+
// Acquire the VM lock and then signal all other Ruby threads (ractors) to
// contend for the VM lock, putting them to sleep. ZJIT and YJIT use this to
// evict threads running inside generated code so among other things, it can
diff --git a/lib/bundler/endpoint_specification.rb b/lib/bundler/endpoint_specification.rb
index 5ef2d53eae3517..02d3ac37028166 100644
--- a/lib/bundler/endpoint_specification.rb
+++ b/lib/bundler/endpoint_specification.rb
@@ -50,6 +50,19 @@ def require_paths
end
end
+ # `require_paths` is overridden above, but `full_require_paths` (and so
+ # `load_paths`) is computed from `raw_require_paths`, which would otherwise
+ # report the default `lib` for every gem
+ def raw_require_paths
+ if @remote_specification
+ @remote_specification.raw_require_paths
+ elsif _local_specification
+ _local_specification.raw_require_paths
+ else
+ super
+ end
+ end
+
# needed for inline
def load_paths
# remote specs aren't installed, and can't have load_paths
diff --git a/lib/bundler/installer.rb b/lib/bundler/installer.rb
index 0f6d59a7161c61..47f7b5b1e9e473 100644
--- a/lib/bundler/installer.rb
+++ b/lib/bundler/installer.rb
@@ -83,6 +83,12 @@ def run(options)
install(options)
Gem::Specification.reset # invalidate gem specification cache so that installed gems are immediately available
+ # Drop the source caches too, since releasing resolution memory dropped
+ # the source indexes. A resolution happening after this point would
+ # otherwise rebuild them around a snapshot of installed gems taken
+ # before the install, and materialize against remote specs that don't
+ # know where the gems they stand for ended up.
+ @definition.sources.clear_cache
lock
Standalone.new(options[:standalone], @definition).generate if options[:standalone]
diff --git a/lib/rubygems/commands/install_command.rb b/lib/rubygems/commands/install_command.rb
index 6d3beec0b43261..2ebbc40a03080f 100644
--- a/lib/rubygems/commands/install_command.rb
+++ b/lib/rubygems/commands/install_command.rb
@@ -152,6 +152,7 @@ def execute
end
@installed_specs = []
+ @cooldown_skipped = []
ENV.delete "GEM_PATH" if options[:install_dir].nil?
@@ -163,6 +164,8 @@ def execute
show_installed
+ Gem::Cooldown.output_skipped_summary @cooldown_skipped
+
say update_suggestion if eligible_for_update?
terminate_interaction exit_code
@@ -184,6 +187,8 @@ def install_from_gemdeps # :nodoc:
@installed_specs = specs
+ Gem::Cooldown.output_skipped_summary rs.resolver&.cooldown_skipped
+
terminate_interaction
end
@@ -207,6 +212,8 @@ def install_gem(name, version) # :nodoc:
@installed_specs.concat request_set.install options
end
+ (@cooldown_skipped ||= []).concat dinst.cooldown_skipped
+
show_install_errors dinst.errors
end
diff --git a/lib/rubygems/commands/outdated_command.rb b/lib/rubygems/commands/outdated_command.rb
index 08a9221a261a9d..7721be88e71f4a 100644
--- a/lib/rubygems/commands/outdated_command.rb
+++ b/lib/rubygems/commands/outdated_command.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
require_relative "../command"
+require_relative "../cooldown"
+require_relative "../cooldown_option"
require_relative "../local_remote_options"
require_relative "../spec_fetcher"
require_relative "../version_option"
@@ -8,12 +10,14 @@
class Gem::Commands::OutdatedCommand < Gem::Command
include Gem::LocalRemoteOptions
include Gem::VersionOption
+ include Gem::CooldownOption
def initialize
super "outdated", "Display all gems that need updates"
add_local_remote_options
add_platform_option
+ add_cooldown_option
end
def description # :nodoc:
@@ -26,8 +30,79 @@ def description # :nodoc:
end
def execute
- Gem::Specification.outdated_and_latest_version.each do |spec, remote_version|
- say "#{spec.name} (#{spec.version} < #{remote_version})"
+ @cooldown = Gem::Cooldown.from_options options
+
+ unless @cooldown.active?
+ Gem::Specification.outdated_and_latest_version.each do |spec, remote_version|
+ say "#{spec.name} (#{spec.version} < #{remote_version})"
+ end
+
+ return
end
+
+ execute_with_cooldown
+ end
+
+ private
+
+ ##
+ # Like Gem::Specification.outdated_and_latest_version, but the newest
+ # version outside the cooldown period becomes the update candidate, and
+ # newer versions still within the period are annotated.
+
+ def execute_with_cooldown
+ fetcher = Gem::SpecFetcher.fetcher
+
+ Gem::Specification.latest_specs(true).each do |local_spec|
+ dependency = Gem::Dependency.new local_spec.name, ">= #{local_spec.version}"
+
+ # The :latest index carries only the newest version of each gem,
+ # which leaves nothing to fall back to when the cooldown excludes
+ # it, so search the full index instead.
+ remotes, = fetcher.search_for_dependency dependency,
+ type: dependency.prerelease? ? :complete : :released
+
+ selectable, embargoed = partition_by_cooldown remotes
+
+ candidate = selectable.max
+ candidate = nil unless candidate && local_spec.version < candidate
+
+ pending = embargoed.max
+ pending = nil unless pending && local_spec.version < pending &&
+ (candidate.nil? || candidate < pending)
+
+ next unless candidate || pending
+
+ pending = "#{pending} (cooldown #{@cooldown.days}d)" if pending
+ say "#{local_spec.name} (#{local_spec.version} < #{[candidate, pending].compact.join(", ")})"
+ end
+ end
+
+ ##
+ # Splits [NameTuple, Gem::Source] pairs into versions outside and within
+ # the cooldown period. Tuples with an unknown publish time count as
+ # outside the period, so the cooldown fails open.
+
+ def partition_by_cooldown(spec_tuples)
+ selectable = []
+ embargoed = []
+
+ with_times = spec_tuples.map do |tup, source|
+ [tup, source, source.created_at(tup.name, tup.version, tup.platform)]
+ end
+
+ if !with_times.empty? && with_times.none? {|_, _, created_at| created_at }
+ Gem::Cooldown.warn_missing_created_at with_times.first[1]
+ end
+
+ with_times.each do |tup, _, created_at|
+ if @cooldown.skip?(created_at)
+ embargoed << tup.version
+ else
+ selectable << tup.version
+ end
+ end
+
+ [selectable, embargoed]
end
end
diff --git a/lib/rubygems/commands/update_command.rb b/lib/rubygems/commands/update_command.rb
index cb0a2660dde47d..71942f920d377a 100644
--- a/lib/rubygems/commands/update_command.rb
+++ b/lib/rubygems/commands/update_command.rb
@@ -2,6 +2,7 @@
require_relative "../command"
require_relative "../command_manager"
+require_relative "../cooldown"
require_relative "../dependency_installer"
require_relative "../install_update_options"
require_relative "../local_remote_options"
@@ -95,8 +96,12 @@ def check_update_arguments # :nodoc:
end
def execute
+ @cooldown = Gem::Cooldown.from_options options
+ @cooldown_skipped = []
+
if options[:system]
update_rubygems
+ output_cooldown_skipped_summary
return
end
@@ -132,6 +137,8 @@ def execute
end
say "Gems already up-to-date: #{up_to_date_names.join(" ")}" unless up_to_date_names.empty?
say "Gems not currently installed: #{not_installed_names.join(" ")}" unless not_installed_names.empty?
+
+ output_cooldown_skipped_summary
end
def fetch_remote_gems(spec) # :nodoc:
@@ -140,7 +147,12 @@ def fetch_remote_gems(spec) # :nodoc:
fetcher = Gem::SpecFetcher.fetcher
- spec_tuples, errors = fetcher.search_for_dependency dependency
+ # The default indexes carry only the newest version of each gem, which
+ # leaves nothing to fall back to when the cooldown excludes it, so
+ # search the full index instead.
+ type = dependency.prerelease? ? :complete : :released if @cooldown&.active?
+
+ spec_tuples, errors = fetcher.search_for_dependency dependency, type: type
error = errors.find {|e| e.respond_to? :exception }
@@ -167,12 +179,78 @@ def highest_installed_gems # :nodoc:
def highest_remote_name_tuple(spec) # :nodoc:
spec_tuples = fetch_remote_gems spec
- highest_remote_gem = spec_tuples.max
+ highest_remote_gem = filter_cooldown_tuples(spec_tuples).max
return unless highest_remote_gem
highest_remote_gem.first
end
+ ##
+ # Rejects [NameTuple, Gem::Source] pairs published within the cooldown
+ # period. Tuples with an unknown publish time are kept, so the cooldown
+ # fails open.
+
+ def filter_cooldown_tuples(spec_tuples) # :nodoc:
+ return spec_tuples unless @cooldown&.active?
+
+ with_times = spec_tuples.map do |tup, source|
+ [tup, source, source.created_at(tup.name, tup.version, tup.platform)]
+ end
+
+ if !with_times.empty? && with_times.none? {|_, _, created_at| created_at }
+ Gem::Cooldown.warn_missing_created_at with_times.first[1]
+ end
+
+ with_times.reject do |tup, _, created_at|
+ next false unless @cooldown.skip?(created_at)
+
+ (@cooldown_skipped_tuples ||= {})[[tup.name, tup.version]] ||= created_at
+ true
+ end.map {|tup, source, _| [tup, source] }
+ end
+
+ ##
+ # Summary entries for tuples the cooldown kept out of the update, kept
+ # only when newer than the version the update actually settled on (the
+ # updated version, or the one already installed when nothing moved).
+
+ def cooldown_skipped_tuple_entries # :nodoc:
+ skipped = @cooldown_skipped_tuples
+ return [] unless skipped
+
+ resolved = {}
+ @updated.each do |spec|
+ version = resolved[spec.name]
+ resolved[spec.name] = spec.version if version.nil? || spec.version > version
+ end
+
+ skipped.filter_map do |(name, version), created_at|
+ resolved_version = resolved[name] || resolved_fallback_version(name)
+ next unless resolved_version && version > resolved_version
+
+ {
+ name: name,
+ version: version,
+ resolved: resolved_version,
+ available_in_days: @cooldown.remaining_days(created_at),
+ }
+ end
+ end
+
+ def resolved_fallback_version(name) # :nodoc:
+ if name == "rubygems-update"
+ Gem::Version.new Gem::VERSION
+ else
+ Gem::Specification.find_all_by_name(name).map(&:version).max
+ end
+ end
+
+ def output_cooldown_skipped_summary # :nodoc:
+ entries = (@cooldown_skipped || []) + cooldown_skipped_tuple_entries
+
+ Gem::Cooldown.output_skipped_summary entries
+ end
+
def install_rubygems(spec) # :nodoc:
args = update_rubygems_arguments
version = spec.version
@@ -256,6 +334,10 @@ def update_gem(name, version = Gem::Requirement.default)
@installer.installed_gems.each do |spec|
@updated << spec
end
+
+ (@cooldown_skipped ||= []).concat @installer.cooldown_skipped
+
+ @installer.installed_gems
end
def update_gems(gems_to_update)
diff --git a/lib/rubygems/config_file.rb b/lib/rubygems/config_file.rb
index d5e9eb4e33aec6..3d63af60487b78 100644
--- a/lib/rubygems/config_file.rb
+++ b/lib/rubygems/config_file.rb
@@ -27,6 +27,7 @@
#
# +:backtrace+:: See #backtrace
# +:bulk_threshold+:: See #bulk_threshold
+# +:cooldown+:: See #cooldown
# +:verbose+:: See #verbose
# +:update_sources+:: See #update_sources
# +:concurrent_downloads+:: See #concurrent_downloads
@@ -55,6 +56,7 @@ class Gem::ConfigFile
DEFAULT_BACKTRACE = true
DEFAULT_BULK_THRESHOLD = 1000
+ DEFAULT_COOLDOWN = 0
DEFAULT_VERBOSITY = true
DEFAULT_UPDATE_SOURCES = true
DEFAULT_CONCURRENT_DOWNLOADS = 8
@@ -116,6 +118,13 @@ class Gem::ConfigFile
attr_accessor :bulk_threshold
+ ##
+ # Number of days a newly published gem version must wait before it is
+ # considered for installation or update (the cooldown period). 0
+ # disables the cooldown.
+
+ attr_accessor :cooldown
+
##
# Verbose level of output:
# * false -- No output
@@ -211,6 +220,7 @@ def initialize(args)
@backtrace = DEFAULT_BACKTRACE
@bulk_threshold = DEFAULT_BULK_THRESHOLD
+ @cooldown = DEFAULT_COOLDOWN
@verbose = DEFAULT_VERBOSITY
@update_sources = DEFAULT_UPDATE_SOURCES
@concurrent_downloads = DEFAULT_CONCURRENT_DOWNLOADS
@@ -239,7 +249,7 @@ def initialize(args)
@hash.transform_keys! do |k|
# gemhome and gempath are not working with symbol keys
- if %w[backtrace bulk_threshold verbose update_sources cert_expiration_length_days
+ if %w[backtrace bulk_threshold cooldown verbose update_sources cert_expiration_length_days
concurrent_downloads install_extension_in_lib ipv4_fallback_enabled
global_gem_cache use_psych sources
disable_default_gem_server ssl_verify_mode ssl_ca_cert ssl_client_cert].include?(k)
@@ -252,6 +262,7 @@ def initialize(args)
# HACK: these override command-line args, which is bad
@backtrace = @hash[:backtrace] if @hash.key? :backtrace
@bulk_threshold = @hash[:bulk_threshold] if @hash.key? :bulk_threshold
+ @cooldown = @hash[:cooldown] if @hash.key? :cooldown
@verbose = @hash[:verbose] if @hash.key? :verbose
@update_sources = @hash[:update_sources] if @hash.key? :update_sources
@concurrent_downloads = @hash[:concurrent_downloads] if @hash.key? :concurrent_downloads
diff --git a/lib/rubygems/cooldown.rb b/lib/rubygems/cooldown.rb
new file mode 100644
index 00000000000000..499e08b3be1985
--- /dev/null
+++ b/lib/rubygems/cooldown.rb
@@ -0,0 +1,101 @@
+# frozen_string_literal: true
+
+require_relative "user_interaction"
+
+##
+# Applies a cooldown period to remote gem versions as a supply chain attack
+# mitigation. When a cooldown of N days is configured, gem versions
+# published within the last N days are not considered for installation or
+# update. Versions whose publish time is unknown are never excluded, so
+# sources that do not provide publish times keep working.
+#
+# The cooldown period comes from the --cooldown DAYS option when
+# given, falling back to the :cooldown: setting in the gemrc file.
+# A value of 0 disables the cooldown.
+
+class Gem::Cooldown
+ ##
+ # The cooldown period in days.
+
+ attr_reader :days
+
+ ##
+ # Creates a Cooldown from the command line +options+, preferring the
+ # --cooldown option over the :cooldown: gemrc setting.
+
+ def self.from_options(options)
+ new(options[:cooldown] || Gem.configuration.cooldown)
+ end
+
+ def initialize(days, now: Time.now)
+ @days = days.to_i
+ @now = now
+ end
+
+ ##
+ # True when a cooldown period is configured.
+
+ def active?
+ @days > 0
+ end
+
+ ##
+ # True when a gem version published at +created_at+ must not be
+ # considered. Versions with an unknown publish time (+nil+) are kept.
+
+ def skip?(created_at)
+ return false unless active?
+ return false unless created_at
+
+ (@now - created_at) < @days * 86_400
+ end
+
+ ##
+ # Number of days until a gem version published at +created_at+ leaves
+ # the cooldown period, rounded up and at least 1.
+
+ def remaining_days(created_at)
+ remaining = @days * 86_400 - (@now - created_at)
+
+ [(remaining / 86_400.0).ceil, 1].max
+ end
+
+ ##
+ # Reports, per gem, the newest version the cooldown kept out of a
+ # completed installation or update. +entries+ are hashes with :name,
+ # :version, :resolved and :available_in_days keys; when several entries
+ # name the same gem only the newest version is shown.
+
+ def self.output_skipped_summary(entries)
+ return if entries.nil? || entries.empty?
+
+ newest = {}
+ entries.each do |entry|
+ current = newest[entry[:name]]
+ newest[entry[:name]] = entry if current.nil? || entry[:version] > current[:version]
+ end
+
+ ui = Gem::DefaultUserInteraction.ui
+ ui.say "The following gem versions were skipped by the cooldown setting:"
+ newest.values.sort_by {|entry| entry[:name] }.each do |entry|
+ days = entry[:available_in_days]
+ ui.say " * #{entry[:name]} #{entry[:version]} (available in #{days} #{days == 1 ? "day" : "days"}), resolved #{entry[:resolved]} instead"
+ end
+ end
+
+ ##
+ # Warns once per process that +source+ did not provide publish times, so
+ # the cooldown cannot be applied to gems from it.
+
+ def self.warn_missing_created_at(source)
+ return if @warned
+ @warned = true
+
+ Gem::DefaultUserInteraction.ui.alert_warning \
+ "#{source.uri} does not provide gem publish times, the cooldown period does not apply to gems from this source"
+ end
+
+ def self.reset_warned_missing_created_at # :nodoc:
+ @warned = nil
+ end
+end
diff --git a/lib/rubygems/cooldown_option.rb b/lib/rubygems/cooldown_option.rb
new file mode 100644
index 00000000000000..84195d39f1abaa
--- /dev/null
+++ b/lib/rubygems/cooldown_option.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require_relative "cooldown"
+
+##
+# Mixin methods for the cooldown option for Gem::Commands.
+
+module Gem::CooldownOption
+ ##
+ # Add the --cooldown option to the option parser.
+
+ def add_cooldown_option(group = nil)
+ args = [group, "--cooldown DAYS", Integer,
+ "Do not use gem versions published within",
+ "the last DAYS days (0 disables the cooldown)"].compact
+
+ add_option(*args) do |value, options|
+ options[:cooldown] = value
+ end
+ end
+end
diff --git a/lib/rubygems/dependency_installer.rb b/lib/rubygems/dependency_installer.rb
index c842714d9580f4..f3076472850467 100644
--- a/lib/rubygems/dependency_installer.rb
+++ b/lib/rubygems/dependency_installer.rb
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative "../rubygems"
+require_relative "cooldown"
require_relative "dependency_list"
require_relative "package"
require_relative "installer"
@@ -43,6 +44,12 @@ class Gem::DependencyInstaller
attr_reader :installed_gems
+ ##
+ # Per-gem summary entries for the newest versions the cooldown kept out
+ # of the last resolution. See Gem::Cooldown.output_skipped_summary.
+
+ attr_reader :cooldown_skipped
+
##
# Creates a new installer instance.
#
@@ -90,6 +97,8 @@ def initialize(options = {})
@prog_mode = options[:prog_mode]
@build_extension = options[:build_extension]
@install_plugin = options[:install_plugin]
+ @cooldown = Gem::Cooldown.from_options options
+ @cooldown_skipped = []
# Indicates that we should not try to update any deps unless
# we absolutely must.
@@ -210,6 +219,7 @@ def resolve_dependencies(dep_or_name, version) # :nodoc:
request_set.development_shallow = @dev_shallow
request_set.soft_missing = @force
request_set.prerelease = @prerelease
+ request_set.cooldown = @cooldown
installer_set = Gem::Resolver::InstallerSet.new @domain
installer_set.ignore_installed = (@minimal_deps == false) || @only_install_dir
@@ -258,6 +268,7 @@ def resolve_dependencies(dep_or_name, version) # :nodoc:
request_set.resolve installer_set
@errors.concat request_set.errors
+ @cooldown_skipped = request_set.resolver&.cooldown_skipped || []
request_set
end
diff --git a/lib/rubygems/install_update_options.rb b/lib/rubygems/install_update_options.rb
index e8859cadaf159d..942b1af214c031 100644
--- a/lib/rubygems/install_update_options.rb
+++ b/lib/rubygems/install_update_options.rb
@@ -7,12 +7,14 @@
#++
require_relative "../rubygems"
+require_relative "cooldown_option"
require_relative "security_option"
##
# Mixin methods for install and update options for Gem::Commands
module Gem::InstallUpdateOptions
+ include Gem::CooldownOption
include Gem::SecurityOption
##
@@ -204,6 +206,8 @@ def add_install_update_options
"Defaults to true") do |v, _o|
options[:install_plugin] = v
end
+
+ add_cooldown_option :"Install/Update"
end
##
diff --git a/lib/rubygems/request_set.rb b/lib/rubygems/request_set.rb
index 9737cba7bfb7e4..d97c313da3020b 100644
--- a/lib/rubygems/request_set.rb
+++ b/lib/rubygems/request_set.rb
@@ -1,5 +1,6 @@
# frozen_string_literal: true
+require_relative "cooldown"
require_relative "vendored_tsort"
##
@@ -23,6 +24,11 @@ class Gem::RequestSet
attr_accessor :always_install
+ ##
+ # The Gem::Cooldown applied to release candidates, if any.
+
+ attr_accessor :cooldown
+
attr_reader :dependencies
attr_accessor :development
@@ -96,6 +102,7 @@ def initialize(*deps)
@always_install = []
@conservative = false
+ @cooldown = nil
@dependency_names = {}
@development = false
@development_shallow = false
@@ -223,6 +230,7 @@ def install_from_gemdeps(options, &block)
@prerelease = options[:prerelease]
@remote = options[:domain] != :local
@conservative = true if options[:conservative]
+ @cooldown = Gem::Cooldown.from_options options
gem_deps_api = load_gemdeps gemdeps, options[:without_groups], true
@@ -442,6 +450,7 @@ def resolve(set = Gem::Resolver::BestSet.new)
set.prerelease = @prerelease
resolver = Gem::Resolver.new @dependencies, set
+ resolver.cooldown = @cooldown
resolver.development = @development
resolver.development_shallow = @development_shallow
resolver.ignore_dependencies = @ignore_dependencies
diff --git a/lib/rubygems/resolver.rb b/lib/rubygems/resolver.rb
index 788206c0566fd5..4964f5be217ec9 100644
--- a/lib/rubygems/resolver.rb
+++ b/lib/rubygems/resolver.rb
@@ -34,6 +34,19 @@ class Gem::Resolver
attr_accessor :ignore_dependencies
+ ##
+ # The Gem::Cooldown applied to release candidates, if any.
+
+ attr_accessor :cooldown
+
+ ##
+ # Per-gem summary entries for the newest versions the cooldown kept out
+ # of a successful resolution. See Gem::Cooldown.output_skipped_summary.
+
+ def cooldown_skipped
+ @cooldown_skipped || []
+ end
+
##
# Hash of gems to skip resolution. Keyed by gem name, with arrays of
# gem specifications as values.
@@ -94,6 +107,7 @@ def initialize(needed, set = nil)
@set = set || Gem::Resolver::IndexSet.new
@needed = needed
+ @cooldown = nil
@development = false
@development_shallow = false
@ignore_dependencies = false
@@ -158,13 +172,17 @@ def resolve
# Convert to Array
needed_by_name = @needed.group_by(&:name)
- result.filter_map do |package, version|
+ requests = result.filter_map do |package, version|
next if Gem::PubGrub::Package.root?(package)
spec = spec_for(package.to_s, version)
dep = needed_by_name[package.to_s]&.first || Gem::Dependency.new(package.to_s)
dep_request = DependencyRequest.new(dep, nil)
ActivationRequest.new(spec, dep_request)
end
+
+ @cooldown_skipped = cooldown_skipped_summary(requests) if @cooldown&.active?
+
+ requests
rescue Gem::PubGrub::SolveFailure => e
extended = extract_extended_explanation(e.incompatibility)
if extended
@@ -373,9 +391,77 @@ def filter_specs(specs)
end
end
+ filtered = filter_cooldown_specs(filtered) if @cooldown&.active?
+
filtered
end
+ ##
+ # Rejects specs published within the cooldown period. Specs with an
+ # unknown publish time (installed gems, lockfiles, sources without
+ # timestamps) are kept, so the cooldown fails open.
+
+ def filter_cooldown_specs(specs)
+ remote = specs.select do |s|
+ Gem::Resolver::APISpecification === s || Gem::Resolver::IndexSpecification === s
+ end
+ if remote.any? && remote.none?(&:created_at)
+ Gem::Cooldown.warn_missing_created_at remote.first.source
+ end
+
+ specs.reject do |s|
+ next false unless @cooldown.skip?(s.created_at)
+
+ (@cooldown_skipped_specs ||= {})[[s.name, s.version]] ||= s
+ true
+ end
+ end
+
+ ##
+ # Reports, per gem, the newest version that the cooldown kept out of a
+ # successful resolution. A skipped version is only worth reporting when
+ # it is newer than the resolved version and satisfies every requirement
+ # the final resolution places on that gem, so we don't claim a version
+ # the resolver could never have picked anyway.
+
+ def cooldown_skipped_summary(requests)
+ skipped_specs = @cooldown_skipped_specs || {}
+ return [] if skipped_specs.empty?
+
+ requirements = Hash.new {|h, name| h[name] = [] }
+ @needed.each {|dep| requirements[dep.name] << dep.requirement }
+ requests.each do |request|
+ request.spec.dependencies.each do |dep|
+ next if dep.type == :development && !@development
+ requirements[dep.name] << dep.requirement
+ end
+ end
+
+ resolved = {}
+ requests.each do |request|
+ version = resolved[request.spec.name]
+ resolved[request.spec.name] = request.spec.version if version.nil? || request.spec.version > version
+ end
+
+ newest_skipped = {}
+ skipped_specs.each do |(name, version), spec|
+ resolved_version = resolved[name]
+ next unless resolved_version && version > resolved_version
+ next unless requirements[name].all? {|req| req.satisfied_by?(version) }
+ newest = newest_skipped[name]
+ newest_skipped[name] = spec if newest.nil? || version > newest.version
+ end
+
+ newest_skipped.values.sort_by(&:name).map do |spec|
+ {
+ name: spec.name,
+ version: spec.version,
+ resolved: resolved[spec.name],
+ available_in_days: @cooldown.remaining_days(spec.created_at),
+ }
+ end
+ end
+
def spec_for(name, version)
@spec_for_cache[name][version]
end
@@ -486,6 +572,17 @@ def build_extended_explanation(name, constraint)
hints << "#{name} #{versions.join(", ")} requires Ruby #{ruby_req} (you have #{Gem.ruby_version})"
end
+ # Check for specs filtered by the cooldown period
+ if @cooldown&.active?
+ cooldown_specs = installable.select do |s|
+ constraint.range.include?(s.version) && @cooldown.skip?(s.created_at)
+ end
+ if cooldown_specs.any?
+ versions = cooldown_specs.map(&:version).uniq.sort.reverse.first(3)
+ hints << "#{name} #{versions.join(", ")} published within the cooldown period (#{@cooldown.days} days). Use --cooldown 0 to bypass it."
+ end
+ end
+
# Check for specs filtered by prerelease status
if prerelease_gated
prerelease_versions = @all_versions[pkg].select(&:prerelease?)
diff --git a/lib/rubygems/source.rb b/lib/rubygems/source.rb
index b01619371efc4e..3060bfaa3129f1 100644
--- a/lib/rubygems/source.rb
+++ b/lib/rubygems/source.rb
@@ -184,6 +184,42 @@ def compact_index_client # :nodoc:
end
end
+ ##
+ # The publish time of gem +name+ at +version+ for +platform+, when this
+ # source provides it through the compact index created_at metadata.
+ # Returns nil when the source, the gem or the version has no known
+ # publish time.
+
+ def created_at(name, version, platform = Gem::Platform::RUBY)
+ return unless %w[http https].include?(uri.scheme)
+
+ @created_at_info ||= {}
+ info = @created_at_info[name] ||= begin
+ compact_index_client.fetch_info(name)
+ rescue Gem::RemoteFetcher::FetchError, Gem::CompactIndexClient::Error
+ []
+ end
+
+ platform = (platform || Gem::Platform::RUBY).to_s
+ version = version.to_s
+
+ row = info.find do |row_info|
+ row_info[Gem::CompactIndexClient::INFO_VERSION] == version &&
+ (row_info[Gem::CompactIndexClient::INFO_PLATFORM] || Gem::Platform::RUBY) == platform
+ end
+ return unless row
+
+ value = row[Gem::CompactIndexClient::INFO_REQS].assoc("created_at")&.last&.first
+ return unless value.is_a?(String)
+
+ require "time"
+ begin
+ Time.iso8601(value)
+ rescue ArgumentError
+ nil
+ end
+ end
+
##
# Downloads +spec+ and writes it to +dir+. See also
# Gem::RemoteFetcher#download.
diff --git a/lib/rubygems/spec_fetcher.rb b/lib/rubygems/spec_fetcher.rb
index 835dedf9489a2c..6f06b554d2c143 100644
--- a/lib/rubygems/spec_fetcher.rb
+++ b/lib/rubygems/spec_fetcher.rb
@@ -82,13 +82,16 @@ def initialize(sources = nil)
# Find and fetch gem name tuples that match +dependency+.
#
# If +matching_platform+ is false, gems for all platforms are returned.
+ #
+ # +type+ overrides the index type derived from +dependency+. See
+ # #available_specs for the list of types.
- def search_for_dependency(dependency, matching_platform = true)
+ def search_for_dependency(dependency, matching_platform = true, type: nil)
found = {}
rejected_specs = {}
- list, errors = available_specs(dependency.identity)
+ list, errors = available_specs(type || dependency.identity)
list.each do |source, specs|
if dependency.name.is_a?(String) && specs.respond_to?(:bsearch)
diff --git a/lib/rubygems/uninstaller.rb b/lib/rubygems/uninstaller.rb
index fe4c3a80cf923c..28bf33ea710605 100644
--- a/lib/rubygems/uninstaller.rb
+++ b/lib/rubygems/uninstaller.rb
@@ -64,7 +64,8 @@ def initialize(gem, options = {})
@gem = gem
@version = options[:version] || Gem::Requirement.default
@install_dir = options[:install_dir]
- @gem_home = File.realpath(@install_dir || Gem.dir)
+ gem_home = @install_dir || Gem.dir
+ @gem_home = File.exist?(gem_home) ? File.realpath(gem_home) : gem_home
@user_dir = File.exist?(Gem.user_dir) ? File.realpath(Gem.user_dir) : Gem.user_dir
@force_executables = options[:executables]
@force_all = options[:all]
diff --git a/spec/bundler/runtime/inline_spec.rb b/spec/bundler/runtime/inline_spec.rb
index c6f9bbdbd7326f..487ac38fe2aa04 100644
--- a/spec/bundler/runtime/inline_spec.rb
+++ b/spec/bundler/runtime/inline_spec.rb
@@ -706,6 +706,38 @@ def confirm(msg, newline = nil)
expect(err).to be_empty
end
+ it "sets up custom require paths for gems installed in the same process" do
+ build_repo4 do
+ build_gem "securerandom", "999"
+
+ build_gem "unusual_paths", "1.0.0", no_default: true do |s|
+ s.require_paths = ["lib/unusual_paths"]
+ s.write "lib/unusual_paths/unusual_paths.rb", "UNUSUAL_PATHS = 'loaded'"
+ end
+ end
+
+ script <<-RUBY, env: { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
+ # Simulate a platform where installing in a subprocess is not possible, so
+ # that the conflicting default gem forces a re-resolution in this process,
+ # after the gems have been installed.
+ class << Process
+ undef_method :fork
+ end
+
+ gemfile(true) do
+ source "https://gem.repo4"
+ gem "securerandom"
+ gem "unusual_paths"
+ end
+
+ puts UNUSUAL_PATHS
+ RUBY
+
+ expect(out).to include("Installing unusual_paths 1.0.0")
+ expect(out).to include("loaded")
+ expect(err).to be_empty
+ end
+
it "installs a conflicting default gem alongside git sources" do
build_repo4 do
build_gem "securerandom", "999"
diff --git a/test/rubygems/helper.rb b/test/rubygems/helper.rb
index 174bd258168b0d..dbb53600ba650a 100644
--- a/test/rubygems/helper.rb
+++ b/test/rubygems/helper.rb
@@ -543,6 +543,10 @@ def teardown
Gem::RemoteFetcher.fetcher = nil
end
+ if defined? Gem::Cooldown
+ Gem::Cooldown.reset_warned_missing_created_at
+ end
+
Dir.chdir @current_dir
ENV.replace(@orig_env)
diff --git a/test/rubygems/test_gem_commands_install_command.rb b/test/rubygems/test_gem_commands_install_command.rb
index d75ba349f96ada..6ae389db2fba49 100644
--- a/test/rubygems/test_gem_commands_install_command.rb
+++ b/test/rubygems/test_gem_commands_install_command.rb
@@ -729,6 +729,132 @@ def test_execute_remote
assert_match "1 gem installed", @ui.output
end
+ def util_setup_cooldown_repo(created_at: {})
+ spec_fetcher
+
+ a1, a1_gem = util_gem "a", 1
+ a2, a2_gem = util_gem "a", 2
+
+ util_setup_compact_index a1, a2, created_at: created_at
+
+ add_to_fetcher a1, a1_gem
+ add_to_fetcher a2, a2_gem
+ end
+
+ def util_cooldown_time(days_ago)
+ (Time.now - days_ago * 86_400).utc.strftime("%Y-%m-%dT%H:%M:%SZ")
+ end
+
+ def test_execute_remote_cooldown_falls_back_to_older_version
+ util_setup_cooldown_repo created_at: {
+ "a-1" => util_cooldown_time(30),
+ "a-2" => util_cooldown_time(1),
+ }
+
+ @cmd.options[:cooldown] = 7
+ @cmd.options[:args] = %w[a]
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
+ @cmd.execute
+ end
+ end
+
+ assert_equal %w[a-1], @cmd.installed_specs.map(&:full_name)
+ assert_match "The following gem versions were skipped by the cooldown setting:", @ui.output
+ assert_match "* a 2 (available in 6 days), resolved 1 instead", @ui.output
+ end
+
+ def test_execute_remote_cooldown_explicit_version_error
+ util_setup_cooldown_repo created_at: {
+ "a-1" => util_cooldown_time(30),
+ "a-2" => util_cooldown_time(1),
+ }
+
+ @cmd.options[:cooldown] = 7
+ @cmd.options[:version] = Gem::Requirement.new("= 2")
+ @cmd.options[:args] = %w[a]
+
+ use_ui @ui do
+ e = assert_raise Gem::MockGemUi::TermError do
+ @cmd.execute
+ end
+
+ assert_equal 2, e.exit_code
+ end
+
+ assert_empty @cmd.installed_specs
+ assert_match "cooldown period (7 days)", @ui.error
+ assert_match "--cooldown 0", @ui.error
+ end
+
+ def test_execute_remote_cooldown_missing_created_at_fails_open
+ util_setup_cooldown_repo
+
+ @cmd.options[:cooldown] = 7
+ @cmd.options[:args] = %w[a]
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
+ @cmd.execute
+ end
+ end
+
+ assert_equal %w[a-2], @cmd.installed_specs.map(&:full_name)
+ assert_equal 1, @ui.error.scan("publish times").size
+ end
+
+ def test_execute_remote_cooldown_from_gemrc
+ util_setup_cooldown_repo created_at: {
+ "a-1" => util_cooldown_time(30),
+ "a-2" => util_cooldown_time(1),
+ }
+
+ orig_cooldown = Gem.configuration.cooldown
+ Gem.configuration.cooldown = 7
+
+ @cmd.options[:args] = %w[a]
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
+ @cmd.execute
+ end
+ end
+
+ assert_equal %w[a-1], @cmd.installed_specs.map(&:full_name)
+ ensure
+ Gem.configuration.cooldown = orig_cooldown
+ end
+
+ def test_execute_remote_cooldown_zero_overrides_gemrc
+ util_setup_cooldown_repo created_at: {
+ "a-1" => util_cooldown_time(30),
+ "a-2" => util_cooldown_time(1),
+ }
+
+ orig_cooldown = Gem.configuration.cooldown
+ Gem.configuration.cooldown = 7
+
+ @cmd.options[:cooldown] = 0
+ @cmd.options[:args] = %w[a]
+
+ use_ui @ui do
+ assert_raise Gem::MockGemUi::SystemExitException, @ui.error do
+ @cmd.execute
+ end
+ end
+
+ assert_equal %w[a-2], @cmd.installed_specs.map(&:full_name)
+ ensure
+ Gem.configuration.cooldown = orig_cooldown
+ end
+
+ def test_cooldown_option
+ @cmd.handle_options %w[--cooldown 7 a]
+
+ assert_equal 7, @cmd.options[:cooldown]
+ end
+
def test_execute_with_invalid_gem_file
FileUtils.touch("a.gem")
diff --git a/test/rubygems/test_gem_commands_outdated_command.rb b/test/rubygems/test_gem_commands_outdated_command.rb
index 07289d821fba4a..4f88aef0f03bef 100644
--- a/test/rubygems/test_gem_commands_outdated_command.rb
+++ b/test/rubygems/test_gem_commands_outdated_command.rb
@@ -50,6 +50,69 @@ def test_execute_compact_index
assert_equal "", @ui.error
end
+ def util_cooldown_time(days_ago)
+ (Time.now - days_ago * 86_400).utc.strftime("%Y-%m-%dT%H:%M:%SZ")
+ end
+
+ def util_setup_cooldown_repo(created_at)
+ spec_fetcher do |fetcher|
+ fetcher.gem "foo", "0.1"
+ end
+
+ specs = created_at.keys.map {|full_name| util_spec "foo", full_name.delete_prefix("foo-") }
+ util_setup_compact_index(*specs, created_at: created_at.compact)
+
+ # drop the in-memory tuples spec_fetcher pre-populated so the lookup
+ # goes through Gem::Source#load_specs
+ Gem::SpecFetcher.fetcher = nil
+ end
+
+ def test_execute_cooldown_annotates_newer_version_within_period
+ util_setup_cooldown_repo "foo-0.2" => util_cooldown_time(30),
+ "foo-0.3" => util_cooldown_time(1)
+
+ @cmd.options[:cooldown] = 7
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ assert_equal "foo (0.1 < 0.2, 0.3 (cooldown 7d))\n", @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_execute_cooldown_only_version_within_period
+ util_setup_cooldown_repo "foo-0.3" => util_cooldown_time(1)
+
+ @cmd.options[:cooldown] = 7
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ assert_equal "foo (0.1 < 0.3 (cooldown 7d))\n", @ui.output
+ assert_equal "", @ui.error
+ end
+
+ def test_execute_cooldown_missing_created_at_fails_open
+ util_setup_cooldown_repo "foo-0.2" => nil, "foo-0.3" => nil
+
+ @cmd.options[:cooldown] = 7
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ assert_equal "foo (0.1 < 0.3)\n", @ui.output
+ assert_equal 1, @ui.error.scan("publish times").size
+ end
+
+ def test_cooldown_option
+ @cmd.handle_options %w[--cooldown 7]
+
+ assert_equal 7, @cmd.options[:cooldown]
+ end
+
def test_execute_with_up_to_date_platform_specific_gem
spec_fetcher do |fetcher|
fetcher.download "foo", "2.0"
diff --git a/test/rubygems/test_gem_commands_update_command.rb b/test/rubygems/test_gem_commands_update_command.rb
index 89b56e0f7cfd30..9d15406bd13fc0 100644
--- a/test/rubygems/test_gem_commands_update_command.rb
+++ b/test/rubygems/test_gem_commands_update_command.rb
@@ -70,6 +70,127 @@ def test_execute_compact_index
assert_path_exist File.join(@gemhome, "specifications", "b-2.gemspec")
end
+ def util_cooldown_time(days_ago)
+ (Time.now - days_ago * 86_400).utc.strftime("%Y-%m-%dT%H:%M:%SZ")
+ end
+
+ def util_setup_cooldown_repo(b2_created_at:, b3_created_at:)
+ spec_fetcher do |fetcher|
+ fetcher.gem "b", 1
+ end
+
+ b2, b2_gem = util_gem "b", 2
+ b3, b3_gem = util_gem "b", 3
+ util_setup_compact_index b2, b3, created_at: {
+ "b-2" => b2_created_at,
+ "b-3" => b3_created_at,
+ }.compact
+ add_to_fetcher b2, b2_gem
+ add_to_fetcher b3, b3_gem
+
+ # drop the in-memory tuples spec_fetcher pre-populated so the lookup
+ # goes through Gem::Source#load_specs
+ Gem::SpecFetcher.fetcher = nil
+ end
+
+ def test_execute_cooldown_falls_back_to_older_version
+ util_setup_cooldown_repo b2_created_at: util_cooldown_time(30),
+ b3_created_at: util_cooldown_time(1)
+
+ @cmd.options[:cooldown] = 7
+ @cmd.options[:args] = []
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ out = @ui.output.split "\n"
+ assert_equal "Updating installed gems", out.shift
+ assert_equal "Updating b", out.shift
+ assert_equal "Gems updated: b", out.shift
+ assert_equal "The following gem versions were skipped by the cooldown setting:", out.shift
+ assert_equal " * b 3 (available in 6 days), resolved 2 instead", out.shift
+ assert_empty out
+
+ assert_path_exist File.join(@gemhome, "specifications", "b-2.gemspec")
+ assert_path_not_exist File.join(@gemhome, "specifications", "b-3.gemspec")
+ end
+
+ def test_execute_cooldown_all_new_versions_within_period
+ util_setup_cooldown_repo b2_created_at: util_cooldown_time(1),
+ b3_created_at: util_cooldown_time(1)
+
+ @cmd.options[:cooldown] = 7
+ @cmd.options[:args] = []
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ out = @ui.output.split "\n"
+ assert_equal "Updating installed gems", out.shift
+ assert_equal "Nothing to update", out.shift
+ assert_equal "The following gem versions were skipped by the cooldown setting:", out.shift
+ assert_equal " * b 3 (available in 6 days), resolved 1 instead", out.shift
+ assert_empty out
+ end
+
+ def test_execute_cooldown_missing_created_at_fails_open
+ util_setup_cooldown_repo b2_created_at: nil, b3_created_at: nil
+
+ @cmd.options[:cooldown] = 7
+ @cmd.options[:args] = []
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ out = @ui.output.split "\n"
+ assert_equal "Updating installed gems", out.shift
+ assert_equal "Updating b", out.shift
+ assert_equal "Gems updated: b", out.shift
+ assert_empty out
+
+ assert_path_exist File.join(@gemhome, "specifications", "b-3.gemspec")
+ assert_equal 1, @ui.error.scan("publish times").size
+ end
+
+ def test_execute_system_cooldown
+ spec_fetcher
+
+ ru8, ru8_gem = util_gem "rubygems-update", 8 do |s|
+ s.files = %w[setup.rb]
+ end
+ ru9, ru9_gem = util_gem "rubygems-update", 9 do |s|
+ s.files = %w[setup.rb]
+ end
+
+ util_setup_compact_index ru8, ru9, created_at: {
+ "rubygems-update-8" => util_cooldown_time(30),
+ "rubygems-update-9" => util_cooldown_time(1),
+ }
+ add_to_fetcher ru8, ru8_gem
+ add_to_fetcher ru9, ru9_gem
+
+ Gem::SpecFetcher.fetcher = nil
+
+ @cmd.options[:args] = []
+ @cmd.options[:system] = true
+ @cmd.options[:cooldown] = 7
+
+ use_ui @ui do
+ @cmd.execute
+ end
+
+ out = @ui.output.split "\n"
+ assert_equal "Installing RubyGems 8", out.shift
+ assert_equal "RubyGems system software updated", out.shift
+ assert_equal "The following gem versions were skipped by the cooldown setting:", out.shift
+ assert_equal " * rubygems-update 9 (available in 6 days), resolved 8 instead", out.shift
+
+ assert_empty out
+ end
+
def test_execute_multiple
spec_fetcher do |fetcher|
fetcher.download "a", 2
diff --git a/test/rubygems/test_gem_config_file.rb b/test/rubygems/test_gem_config_file.rb
index 3c79cb0762d783..0ca05e7203ae00 100644
--- a/test/rubygems/test_gem_config_file.rb
+++ b/test/rubygems/test_gem_config_file.rb
@@ -44,6 +44,7 @@ def test_initialize
assert_equal 365, @cfg.cert_expiration_length_days
assert_equal false, @cfg.ipv4_fallback_enabled
assert_equal true, @cfg.install_extension_in_lib
+ assert_equal Gem::ConfigFile::DEFAULT_COOLDOWN, @cfg.cooldown
File.open @temp_conf, "w" do |fp|
fp.puts ":backtrace: true"
@@ -63,6 +64,7 @@ def test_initialize
fp.puts ":install_extension_in_lib: false"
fp.puts ":ipv4_fallback_enabled: true"
fp.puts ":use_psych: true"
+ fp.puts ":cooldown: 7"
end
util_config_file
@@ -81,6 +83,7 @@ def test_initialize
assert_equal false, @cfg.install_extension_in_lib
assert_equal true, @cfg.ipv4_fallback_enabled
assert_equal true, @cfg.use_psych
+ assert_equal 7, @cfg.cooldown
end
def test_initialize_ipv4_fallback_enabled_env
diff --git a/test/rubygems/test_gem_cooldown.rb b/test/rubygems/test_gem_cooldown.rb
new file mode 100644
index 00000000000000..704b6c169f26f0
--- /dev/null
+++ b/test/rubygems/test_gem_cooldown.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+
+require_relative "helper"
+require "rubygems/cooldown"
+
+class TestGemCooldown < Gem::TestCase
+ def test_skip_eh
+ now = Time.now
+ cooldown = Gem::Cooldown.new 7, now: now
+
+ assert cooldown.skip?(now - 6 * 86_400)
+ refute cooldown.skip?(now - 8 * 86_400)
+ end
+
+ def test_skip_eh_boundary
+ now = Time.now
+ cooldown = Gem::Cooldown.new 7, now: now
+
+ assert cooldown.skip?(now - 7 * 86_400 + 1)
+ refute cooldown.skip?(now - 7 * 86_400)
+ end
+
+ def test_skip_eh_unknown_publish_time
+ refute Gem::Cooldown.new(7).skip?(nil)
+ end
+
+ def test_skip_eh_inactive
+ cooldown = Gem::Cooldown.new 0
+
+ refute cooldown.active?
+ refute cooldown.skip?(Time.now)
+ end
+
+ def test_from_options
+ orig_cooldown = Gem.configuration.cooldown
+ Gem.configuration.cooldown = 5
+
+ assert_equal 5, Gem::Cooldown.from_options({}).days
+ assert_equal 7, Gem::Cooldown.from_options(cooldown: 7).days
+ refute Gem::Cooldown.from_options(cooldown: 0).active?
+ ensure
+ Gem.configuration.cooldown = orig_cooldown
+ end
+
+ def test_warn_missing_created_at_warns_once
+ source = Gem::Source.new @gem_repo
+
+ use_ui @ui do
+ Gem::Cooldown.warn_missing_created_at source
+ Gem::Cooldown.warn_missing_created_at source
+ end
+
+ assert_equal 1, @ui.error.scan("publish times").size
+ assert_match @gem_repo, @ui.error
+ end
+end
diff --git a/test/rubygems/test_gem_source.rb b/test/rubygems/test_gem_source.rb
index a030b58851c796..e63a5e61fa7c97 100644
--- a/test/rubygems/test_gem_source.rb
+++ b/test/rubygems/test_gem_source.rb
@@ -208,6 +208,39 @@ def test_load_specs_compact_index_skips_invalid_version
assert_equal %w[a-1], released
end
+ def test_created_at
+ a1 = util_spec "a", "1"
+ a2 = util_spec "a", "2"
+ b2_java = util_spec "b", "2" do |s|
+ s.platform = "java"
+ end
+
+ util_setup_compact_index a1, a2, b2_java, created_at: {
+ "a-2" => "2026-06-05T10:30:45Z",
+ "b-2-java" => "2026-06-06T00:00:00Z",
+ }
+
+ assert_equal Time.utc(2026, 6, 5, 10, 30, 45), @source.created_at("a", v(2))
+ assert_equal Time.utc(2026, 6, 6), @source.created_at("b", v(2), "java")
+
+ # no created_at metadata for this version
+ assert_nil @source.created_at("a", v(1))
+
+ # unknown version and unknown gem
+ assert_nil @source.created_at("a", v(9))
+ assert_nil @source.created_at("c", v(1))
+ end
+
+ def test_created_at_file_uri
+ source = Gem::Source.new "file:///tmp/gems"
+
+ assert_nil source.created_at("a", v(1))
+ end
+
+ def test_created_at_fetch_error
+ assert_nil @source.created_at("a", v(1))
+ end
+
def test_compact_index_cache_dir_removes_tmpdir_at_exit
source = Gem::Source.new @gem_repo
def source.update_cache?
diff --git a/test/rubygems/test_gem_uninstaller.rb b/test/rubygems/test_gem_uninstaller.rb
index 92ea01a3bca2dd..d1aacb40536bd0 100644
--- a/test/rubygems/test_gem_uninstaller.rb
+++ b/test/rubygems/test_gem_uninstaller.rb
@@ -451,6 +451,24 @@ def test_uninstall_user_install
assert_same uninstaller, @post_uninstall_hook_arg
end
+ def test_uninstall_user_install_with_missing_gem_home
+ FileUtils.rm_rf Gem.dir
+
+ Gem::Specification.dirs = [Gem.user_dir]
+
+ uninstaller = Gem::Uninstaller.new(@user_spec.name,
+ executables: true,
+ user_install: true)
+
+ gem_dir = @user_spec.gem_dir
+
+ assert_path_exist gem_dir
+
+ uninstaller.uninstall
+
+ assert_path_not_exist gem_dir
+ end
+
def test_uninstall_user_install_with_symlinked_home
pend "Symlinks not supported or not enabled" unless symlink_supported?
diff --git a/yjit.c b/yjit.c
index 01e14fb772628c..e10c10f26d992b 100644
--- a/yjit.c
+++ b/yjit.c
@@ -373,12 +373,6 @@ rb_ENCODING_GET(VALUE obj)
return RB_ENCODING_GET(obj);
}
-bool
-rb_yjit_constcache_shareable(const struct iseq_inline_constant_cache_entry *ice)
-{
- return (ice->flags & IMEMO_CONST_CACHE_SHAREABLE) != 0;
-}
-
// For running write barriers from Rust. Required when we add a new edge in the
// object graph from `old` to `young`.
void
diff --git a/yjit/bindgen/src/main.rs b/yjit/bindgen/src/main.rs
index c5b5fbf451e5eb..93dc9b4be6a5b4 100644
--- a/yjit/bindgen/src/main.rs
+++ b/yjit/bindgen/src/main.rs
@@ -258,7 +258,6 @@ fn main() {
.allowlist_function("rb_full_cfunc_return")
.allowlist_function("rb_assert_(iseq|cme)_handle")
.allowlist_function("rb_IMEMO_TYPE_P")
- .allowlist_function("rb_yjit_constcache_shareable")
.allowlist_function("rb_iseq_reset_jit_func")
.allowlist_function("rb_yjit_dump_iseq_loc")
.allowlist_function("rb_yjit_obj_written")
@@ -279,6 +278,7 @@ fn main() {
.allowlist_function("rb_assert_holding_vm_lock")
.allowlist_function("rb_jit_shape_complex_p")
.allowlist_function("rb_jit_multi_ractor_p")
+ .allowlist_function("rb_jit_constcache_shareable")
.allowlist_function("rb_jit_vm_lock_then_barrier")
.allowlist_function("rb_jit_vm_unlock")
.allowlist_function("rb_jit_for_each_iseq")
diff --git a/yjit/src/codegen.rs b/yjit/src/codegen.rs
index 556181e3e36bf2..2fefb0319406bc 100644
--- a/yjit/src/codegen.rs
+++ b/yjit/src/codegen.rs
@@ -10481,7 +10481,7 @@ fn gen_opt_getconstant_path(
}
let cref_sensitive = !unsafe { (*ice).ic_cref }.is_null();
- let is_shareable = unsafe { rb_yjit_constcache_shareable(ice) };
+ let is_shareable = unsafe { rb_jit_constcache_shareable(ice) };
let needs_checks = cref_sensitive || (!is_shareable && !assume_single_ractor_mode(jit, asm));
if needs_checks {
diff --git a/yjit/src/cruby_bindings.inc.rs b/yjit/src/cruby_bindings.inc.rs
index 9a915a14ebc2ef..75ff2642244b79 100644
--- a/yjit/src/cruby_bindings.inc.rs
+++ b/yjit/src/cruby_bindings.inc.rs
@@ -1208,7 +1208,6 @@ extern "C" {
pub fn rb_yjit_iseq_inspect(iseq: *const rb_iseq_t) -> *mut ::std::os::raw::c_char;
pub fn rb_RSTRUCT_SET(st: VALUE, k: ::std::os::raw::c_int, v: VALUE);
pub fn rb_ENCODING_GET(obj: VALUE) -> ::std::os::raw::c_int;
- pub fn rb_yjit_constcache_shareable(ice: *const iseq_inline_constant_cache_entry) -> bool;
pub fn rb_yjit_obj_written(
old: VALUE,
young: VALUE,
@@ -1328,6 +1327,7 @@ extern "C" {
pub fn rb_set_cfp_sp(cfp: *mut rb_control_frame_struct, sp: *mut VALUE);
pub fn rb_jit_shape_complex_p(shape_id: shape_id_t) -> bool;
pub fn rb_jit_multi_ractor_p() -> bool;
+ pub fn rb_jit_constcache_shareable(ice: *const iseq_inline_constant_cache_entry) -> bool;
pub fn rb_jit_vm_lock_then_barrier(
recursive_lock_level: *mut ::std::os::raw::c_uint,
file: *const ::std::os::raw::c_char,
diff --git a/zjit.c b/zjit.c
index 52b80f536ce289..f915b4a8b5a525 100644
--- a/zjit.c
+++ b/zjit.c
@@ -86,12 +86,6 @@ rb_zjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec, bool jit
extern VALUE *rb_vm_base_ptr(struct rb_control_frame_struct *cfp);
-bool
-rb_zjit_constcache_shareable(const struct iseq_inline_constant_cache_entry *ice)
-{
- return (ice->flags & IMEMO_CONST_CACHE_SHAREABLE) != 0;
-}
-
// Convert a given ISEQ's instructions to zjit_* instructions
void
rb_zjit_profile_enable(const rb_iseq_t *iseq)
diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs
index 30e0aa5578aa10..773c24aff45d04 100644
--- a/zjit/bindgen/src/main.rs
+++ b/zjit/bindgen/src/main.rs
@@ -352,6 +352,7 @@ fn main() {
.allowlist_function("rb_assert_holding_vm_lock")
.allowlist_function("rb_jit_shape_complex_p")
.allowlist_function("rb_jit_multi_ractor_p")
+ .allowlist_function("rb_jit_constcache_shareable")
.allowlist_function("rb_jit_vm_lock_then_barrier")
.allowlist_function("rb_jit_vm_unlock")
.allowlist_function("rb_jit_for_each_iseq")
@@ -407,7 +408,6 @@ fn main() {
.allowlist_function("rb_get_cfp_ep")
.allowlist_function("rb_get_cfp_ep_level")
.allowlist_function("rb_get_cme_def_type")
- .allowlist_function("rb_zjit_constcache_shareable")
.allowlist_function("rb_zjit_vm_search_method")
.allowlist_function("rb_zjit_cme_is_cfunc")
.allowlist_function("rb_get_cme_def_body_attr_id")
diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs
index a7087f8cf5a6a6..32b76962b8262c 100644
--- a/zjit/src/cruby_bindings.inc.rs
+++ b/zjit/src/cruby_bindings.inc.rs
@@ -2335,7 +2335,6 @@ unsafe extern "C" {
pub fn rb_zjit_profile_disable(iseq: *const rb_iseq_t);
pub fn rb_zjit_insn_to_bare_insn(insn: ::std::os::raw::c_int) -> ::std::os::raw::c_int;
pub fn rb_vm_base_ptr(cfp: *mut rb_control_frame_struct) -> *mut VALUE;
- pub fn rb_zjit_constcache_shareable(ice: *const iseq_inline_constant_cache_entry) -> bool;
pub fn rb_zjit_iseq_insn_set(
iseq: *const rb_iseq_t,
insn_idx: ::std::os::raw::c_uint,
@@ -2459,6 +2458,7 @@ unsafe extern "C" {
pub fn rb_set_cfp_sp(cfp: *mut rb_control_frame_struct, sp: *mut VALUE);
pub fn rb_jit_shape_complex_p(shape_id: shape_id_t) -> bool;
pub fn rb_jit_multi_ractor_p() -> bool;
+ pub fn rb_jit_constcache_shareable(ice: *const iseq_inline_constant_cache_entry) -> bool;
pub fn rb_jit_vm_lock_then_barrier(
recursive_lock_level: *mut ::std::os::raw::c_uint,
file: *const ::std::os::raw::c_char,
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index 1ca36fe0048b64..da312c1e980d2a 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -2972,7 +2972,7 @@ fn iseq_get_return_value(iseq: IseqPtr, captured_opnd: Option, ci_flags:
}
/// True if the `invokeblock` call flags permit inlining the block dispatch.
-fn block_call_inlinable(flags: u32) -> bool {
+fn can_direct_invoke_block(flags: u32) -> bool {
(flags & (VM_CALL_ARGS_SPLAT | VM_CALL_KW_SPLAT | VM_CALL_KWARG | VM_CALL_ARGS_BLOCKARG)) == 0
}
@@ -2981,7 +2981,7 @@ fn block_call_inlinable(flags: u32) -> bool {
/// with an exact arity match, avoid arg0 auto-splat, and contain no `throw` (break /
/// non-local return). Anything else falls back to the generic `invokeblock` dispatch,
/// with the returned reason attached to the fallback instruction.
-fn block_call_inlinable_iseq(iseq: IseqPtr, argc: usize) -> Result<(), SendFallbackReason> {
+fn can_direct_invoke_block_iseq(iseq: IseqPtr, argc: usize) -> Result<(), SendFallbackReason> {
if !unsafe { rb_simple_iseq_p(iseq) } {
return Err(InvokeBlockNotSpecialized);
}
@@ -3091,34 +3091,98 @@ impl Function {
self.load_field(block, captured, FieldName::code_iseq, offset, types::CPtr)
}
- /// Emit the fast-path `yield` dispatch to a known ISEQ block.
- /// When `guarded`, the block handler is read from the runtime LEP and guarded (tag + iseq
- /// identity) because the profiled block can differ per caller. When the enclosing method is
+ /// Untag an ISEQ block handler into its `struct rb_captured_block *`:
+ /// captured = block_handler & ~0x3
+ fn untag_block_handler(&mut self, block: BlockId, block_handler: InsnId) -> InsnId {
+ let untag_mask = self.push_insn(block, Insn::Const { val: Const::CInt64(!0x3) });
+ self.push_insn(block, Insn::IntAnd { left: block_handler, right: untag_mask })
+ }
+
+ /// Dispatch `yield` to a known ISEQ block without guarding tag or ISEQ. When the enclosing method is
/// inlined and the caller passed a literal block, [`Insn::PushInlineFrame`] wrote that exact
/// block into this frame's EP from a compile-time constant, so both guards are unnecessary.
- fn push_invoke_block_iseq_direct(&mut self, block: BlockId, block_iseq: IseqPtr, level: u32, args: Vec, state: InsnId, guarded: bool) -> InsnId {
+ fn push_invoke_block_iseq_direct(&mut self, block: BlockId, block_iseq: IseqPtr, level: u32, args: Vec, state: InsnId) -> InsnId {
let ep = self.get_ep(block, level);
let block_handler = self.load_ep_env_field(block, ep, FieldName::VM_ENV_DATA_INDEX_SPECVAL, VM_ENV_DATA_INDEX_SPECVAL, types::CInt64);
+ let captured = self.untag_block_handler(block, block_handler);
+ self.push_insn(block, Insn::InvokeBlockIseqDirect { iseq: block_iseq, captured, args, state })
+ }
- if guarded {
- // Guard the handler is an ISEQ block: VM_BH_ISEQ_BLOCK_P is `& 0x3 == 0x1`.
- let tag_mask = self.push_insn(block, Insn::Const { val: Const::CInt64(0x3) });
- let tag = self.push_insn(block, Insn::IntAnd { left: block_handler, right: tag_mask });
- self.push_insn(block, Insn::GuardBitEquals { val: tag, expected: Const::CInt64(0x1), reason: Box::new(SideExitReason::InvokeBlockHandlerNotIseq), state, recompile: Some(Recompile) });
- }
+ /// Dispatch `yield` to the profiled ISEQ blocks.
+ fn dispatch_invoke_block_iseqs(
+ &mut self,
+ iseqs: &[IseqPtr],
+ block: BlockId,
+ insn_idx: u32,
+ level: u32,
+ cd: *const rb_call_data,
+ args: Vec,
+ state: InsnId,
+ ) -> (BlockId, InsnId) {
+ assert!(!iseqs.is_empty());
+ let ep = self.get_ep(block, level);
+ let block_handler = self.load_ep_env_field(block, ep, FieldName::VM_ENV_DATA_INDEX_SPECVAL, VM_ENV_DATA_INDEX_SPECVAL, types::CInt64);
- // captured = block_handler & ~0x3 (struct rb_captured_block *)
- let untag_mask = self.push_insn(block, Insn::Const { val: Const::CInt64(!0x3) });
- let captured = self.push_insn(block, Insn::IntAnd { left: block_handler, right: untag_mask });
+ // The handler must be an ISEQ block: VM_BH_ISEQ_BLOCK_P is `& 0x3 == 0x1`.
+ let tag_mask = self.push_insn(block, Insn::Const { val: Const::CInt64(0x3) });
+ let tag = self.push_insn(block, Insn::IntAnd { left: block_handler, right: tag_mask });
- if guarded {
- // Guard captured->code.iseq is the comptime block iseq. Compare the raw imemo pointer:
+ // Monomorphic: guard the tag and the ISEQ, then invoke directly in-place.
+ // No need for new HIR blocks.
+ if iseqs.len() == 1 && !self.policy.no_side_exits {
+ let block_iseq = iseqs[0];
+ self.push_insn(block, Insn::GuardBitEquals { val: tag, expected: Const::CInt64(0x1), reason: Box::new(SideExitReason::InvokeBlockHandlerNotIseq), state, recompile: Some(Recompile) });
+ let captured = self.untag_block_handler(block, block_handler);
+
+ // Guard captured->code.iseq is the profiled block iseq. Compare the raw imemo pointer:
// type inference (from_value) can't type an iseq imemo, so guard it as a CPtr identity.
let captured_iseq = self.load_captured_code_iseq(block, captured);
self.push_insn(block, Insn::GuardBitEquals { val: captured_iseq, expected: Const::CPtr(block_iseq as *const u8), reason: Box::new(SideExitReason::InvokeBlockIseqChanged), state, recompile: Some(Recompile) });
+
+ let result = self.push_insn(block, Insn::InvokeBlockIseqDirect { iseq: block_iseq, captured, args, state });
+ return (block, result);
}
- self.push_insn(block, Insn::InvokeBlockIseqDirect { iseq: block_iseq, captured, args, state })
+ // Otherwise, compare the handler against each candidate and use the fallback if missed.
+ let join_block = self.new_block(insn_idx);
+ let join_param = self.push_insn(join_block, Insn::Param);
+ let dispatch_block = self.new_block(insn_idx);
+ let fallback_block = self.new_block(insn_idx);
+
+ let iseq_tag = self.push_insn(block, Insn::Const { val: Const::CInt64(0x1) });
+ let tag_matches = self.push_insn(block, Insn::IsBitEqual { left: tag, right: iseq_tag });
+ self.push_insn(block, Insn::CondBranch {
+ val: tag_matches,
+ if_true: BranchEdge { target: dispatch_block, args: vec![] },
+ if_false: BranchEdge { target: fallback_block, args: vec![] },
+ });
+
+ let captured = self.untag_block_handler(dispatch_block, block_handler);
+ let captured_iseq = self.load_captured_code_iseq(dispatch_block, captured);
+
+ let mut compare_block = dispatch_block;
+ for &block_iseq in iseqs {
+ let expected = self.push_insn(compare_block, Insn::Const { val: Const::CPtr(block_iseq as *const u8) });
+ let iseq_matches = self.push_insn(compare_block, Insn::IsBitEqual { left: captured_iseq, right: expected });
+ let direct_block = self.new_block(insn_idx);
+ let miss_block = self.new_block(insn_idx);
+ self.push_insn(compare_block, Insn::CondBranch {
+ val: iseq_matches,
+ if_true: BranchEdge { target: direct_block, args: vec![] },
+ if_false: BranchEdge { target: miss_block, args: vec![] },
+ });
+ let direct_result = self.push_insn(direct_block, Insn::InvokeBlockIseqDirect { iseq: block_iseq, captured, args: args.clone(), state });
+ self.push_insn(direct_block, Insn::Jump(BranchEdge { target: join_block, args: vec![direct_result] }));
+ compare_block = miss_block;
+ }
+ self.push_insn(compare_block, Insn::Jump(BranchEdge { target: fallback_block, args: vec![] }));
+
+ let fallback_result = self.push_insn(fallback_block, Insn::InvokeBlock {
+ cd, args, state, reason: InvokeBlockPolymorphicMiss,
+ });
+ self.push_insn(fallback_block, Insn::Jump(BranchEdge { target: join_block, args: vec![fallback_result] }));
+
+ (join_block, join_param)
}
// Add an instruction to an SSA block
@@ -9035,7 +9099,10 @@ fn add_iseq_to_hir(
let ic: *const iseq_inline_constant_cache = get_arg(pc, 0).as_ptr();
let idlist: *const ID = unsafe { (*ic).segments };
let ice = unsafe { (*ic).entry };
- let result = if !ice.is_null() && (unsafe { (*ice).ic_cref }.is_null() && fun.assume_single_ractor_mode(block, exit_id)) {
+ let can_fold = !ice.is_null()
+ && unsafe { (*ice).ic_cref }.is_null()
+ && (unsafe { rb_jit_constcache_shareable(ice) } || fun.assume_single_ractor_mode(block, exit_id));
+ let result = if can_fold {
// Invalidate output code on any constant writes associated with constants
// referenced after the PatchPoint.
fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::StableConstantNames { idlist }, state: exit_id });
@@ -9988,30 +10055,13 @@ fn add_iseq_to_hir(
let is_ifunc = (flags & (VM_CALL_ARGS_SPLAT | VM_CALL_KW_SPLAT | VM_CALL_KWARG)) == 0
&& block_handler_class.is_some_and(|obj| unsafe { rb_IMEMO_TYPE_P(obj, imemo_ifunc) == 1 });
- // If the block handler is a known simple ISEQ block with exact arity and no
- // non-local exit, push its frame inline instead of calling rb_vm_invokeblock.
+ // Collect the profiled ISEQ blocks that can be invoked directly with a JIT-to-JIT call.
let mut fallback_reason = InvokeBlockNotSpecialized;
- let inline_iseq = if block_call_inlinable(flags) {
- block_handler_class.and_then(|obj| {
- if unsafe { rb_IMEMO_TYPE_P(obj, imemo_iseq) == 1 } {
- let iseq = obj.as_iseq();
- match block_call_inlinable_iseq(iseq, args.len()) {
- Ok(()) => return Some(iseq),
- Err(reason) => fallback_reason = reason,
- }
- }
- None
- })
- } else { None };
-
- // For polymorphic yield sites, collect the profiled ISEQ blocks that can
- // dispatch directly. Iterators like Integer#times are typically called with a
- // different block per call site, so requiring a monomorphic profile would
- // leave every such shared yield site on the generic fallback. Buckets are
- // ordered by frequency, so the hottest block is compared first below.
- let mut polymorphic_iseqs: Vec = vec![];
+ // The first entry of buckets is the most common type, so it will be inserted to direct_iseqs first too.
+ let mut direct_iseqs: Vec = vec![];
if let Some(summary) = block_handler_summary.as_ref() {
- if block_call_inlinable(flags) && (summary.is_polymorphic() || summary.is_skewed_polymorphic()) {
+ if can_direct_invoke_block(flags)
+ && (summary.is_monomorphic() || summary.is_polymorphic() || summary.is_skewed_polymorphic()) {
for &profiled_type in summary.buckets() {
if profiled_type.is_empty() {
break;
@@ -10019,8 +10069,12 @@ fn add_iseq_to_hir(
let obj = profiled_type.class();
if unsafe { rb_IMEMO_TYPE_P(obj, imemo_iseq) == 1 } {
let iseq = obj.as_iseq();
- if !polymorphic_iseqs.contains(&iseq) && block_call_inlinable_iseq(iseq, args.len()).is_ok() {
- polymorphic_iseqs.push(iseq);
+ match can_direct_invoke_block_iseq(iseq, args.len()) {
+ // Push an ISEQ that can be dispatched directly, deduplicating direct_iseqs.
+ Ok(()) => if !direct_iseqs.contains(&iseq) {
+ direct_iseqs.push(iseq);
+ }
+ Err(reason) => fallback_reason = reason,
}
}
}
@@ -10028,7 +10082,7 @@ fn add_iseq_to_hir(
}
let inlined_known_block = if let AddIseqMode::Inlined { blockiseq: Some(bi), .. } = mode {
- if block_call_inlinable(flags)
+ if can_direct_invoke_block(flags)
// Only methods are inlined today, so exit_state.iseq is always a method iseq and this is
// always 0. That matters because the emit below is guard-free and bakes in both level 0
// and *this* frame's block (bi) — sound only when the yield resolves to this exact frame.
@@ -10036,7 +10090,7 @@ fn add_iseq_to_hir(
// (level > 0). To stay guard-free we'd bake in get_lvar_level(...) as the level and fetch
// that ancestor's blockiseq from the inline caller chain instead of bi.
&& get_lvar_level(exit_state.iseq) == 0 {
- match block_call_inlinable_iseq(bi, args.len()) {
+ match can_direct_invoke_block_iseq(bi, args.len()) {
Ok(()) => Some(bi),
Err(reason) => {
fallback_reason = reason;
@@ -10047,65 +10101,13 @@ fn add_iseq_to_hir(
} else { None };
let result = if let Some(block_iseq) = inlined_known_block {
- fun.push_invoke_block_iseq_direct(block, block_iseq, 0, args, exit_id, false)
- } else if let Some(block_iseq) = inline_iseq {
+ fun.push_invoke_block_iseq_direct(block, block_iseq, 0, args, exit_id)
+ } else if !direct_iseqs.is_empty() {
let level = get_lvar_level(exit_state.iseq);
- fun.push_invoke_block_iseq_direct(block, block_iseq, level, args, exit_id, true)
- } else if !polymorphic_iseqs.is_empty() {
- // Dispatch on the runtime block ISEQ over the profiled candidates, joining
- // on the generic fallback for anything else. Unlike the monomorphic path
- // above, a miss must not side-exit: the site is known to see multiple
- // blocks, so a guard would keep failing and recompiling.
- let level = get_lvar_level(exit_state.iseq);
- let ep = fun.get_ep(block, level);
- let block_handler = fun.load_ep_env_field(block, ep, FieldName::VM_ENV_DATA_INDEX_SPECVAL, VM_ENV_DATA_INDEX_SPECVAL, types::CInt64);
-
- let join_block = fun.new_block(insn_idx);
- let join_param = fun.push_insn(join_block, Insn::Param);
- let dispatch_block = fun.new_block(insn_idx);
- let fallback_block = fun.new_block(insn_idx);
-
- // The handler must be an ISEQ block: VM_BH_ISEQ_BLOCK_P is `& 0x3 == 0x1`.
- let tag_mask = fun.push_insn(block, Insn::Const { val: Const::CInt64(0x3) });
- let tag = fun.push_insn(block, Insn::IntAnd { left: block_handler, right: tag_mask });
- let iseq_tag = fun.push_insn(block, Insn::Const { val: Const::CInt64(0x1) });
- let tag_matches = fun.push_insn(block, Insn::IsBitEqual { left: tag, right: iseq_tag });
- fun.push_insn(block, Insn::CondBranch {
- val: tag_matches,
- if_true: BranchEdge { target: dispatch_block, args: vec![] },
- if_false: BranchEdge { target: fallback_block, args: vec![] },
- });
-
- // captured = block_handler & ~0x3 (struct rb_captured_block *)
- let untag_mask = fun.push_insn(dispatch_block, Insn::Const { val: Const::CInt64(!0x3) });
- let captured = fun.push_insn(dispatch_block, Insn::IntAnd { left: block_handler, right: untag_mask });
- let captured_iseq = fun.load_captured_code_iseq(dispatch_block, captured);
-
- let mut compare_block = dispatch_block;
- for &block_iseq in &polymorphic_iseqs {
- let expected = fun.push_insn(compare_block, Insn::Const { val: Const::CPtr(block_iseq as *const u8) });
- let iseq_matches = fun.push_insn(compare_block, Insn::IsBitEqual { left: captured_iseq, right: expected });
- let direct_block = fun.new_block(insn_idx);
- let miss_block = fun.new_block(insn_idx);
- fun.push_insn(compare_block, Insn::CondBranch {
- val: iseq_matches,
- if_true: BranchEdge { target: direct_block, args: vec![] },
- if_false: BranchEdge { target: miss_block, args: vec![] },
- });
- let direct_result = fun.push_insn(direct_block, Insn::InvokeBlockIseqDirect { iseq: block_iseq, captured, args: args.clone(), state: exit_id });
- fun.push_insn(direct_block, Insn::Jump(BranchEdge { target: join_block, args: vec![direct_result] }));
- compare_block = miss_block;
- }
- fun.push_insn(compare_block, Insn::Jump(BranchEdge { target: fallback_block, args: vec![] }));
-
- let fallback_result = fun.push_insn(fallback_block, Insn::InvokeBlock {
- cd, args, state: exit_id, reason: InvokeBlockPolymorphicMiss,
- });
- fun.push_insn(fallback_block, Insn::Jump(BranchEdge { target: join_block, args: vec![fallback_result] }));
-
- // Continue compilation from the join block
- block = join_block;
- join_param
+ let (continue_block, result) = fun.dispatch_invoke_block_iseqs(&direct_iseqs, block, insn_idx, level, cd, args, exit_id);
+ // Continue compilation from the block the dispatch ended in
+ block = continue_block;
+ result
} else if is_ifunc {
// Load the block handler from the current frame's LEP. In inlined
// code, the function ISEQ is the caller while `exit_state.iseq` is the
diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs
index 06733fb301484e..82037629c170a9 100644
--- a/zjit/src/hir/opt_tests.rs
+++ b/zjit/src/hir/opt_tests.rs
@@ -2056,13 +2056,12 @@ mod hir_opt_tests {
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v28:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
PushInlineFrame :foo, v28 (0x1038), num_args=0
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1060, DEBUG)
- v59:NilClass = Const Value(nil)
+ v58:NilClass = Const Value(nil)
CheckInterrupts
PopInlineFrame
- v123:NilClass = Const Value(nil)
- Return v123
+ v120:NilClass = Const Value(nil)
+ Return v120
");
}
@@ -2097,13 +2096,12 @@ mod hir_opt_tests {
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v28:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
PushInlineFrame :foo, v28 (0x1038), num_args=0
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1060, CALL_BLOCK)
- v62:NilClass = Const Value(nil)
+ v61:NilClass = Const Value(nil)
CheckInterrupts
PopInlineFrame
- v132:NilClass = Const Value(nil)
- Return v132
+ v129:NilClass = Const Value(nil)
+ Return v129
");
}
@@ -3680,16 +3678,15 @@ mod hir_opt_tests {
v6:NilClass = Const Value(nil)
Jump bb3(v5, v6)
bb3(v8:BasicObject, v9:NilClass):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, M)
- v15:ModuleExact[M@0x1008] = Const Value(VALUE(0x1008))
+ v14:ModuleExact[M@0x1008] = Const Value(VALUE(0x1008))
PatchPoint NoSingletonClass(Module@0x1010)
PatchPoint MethodRedefined(Module@0x1010, name@0x1018, cme:0x1020)
- v33:StringExact|NilClass = CCall v15, :Module#name@0x1048
+ v32:StringExact|NilClass = CCall v14, :Module#name@0x1048
PatchPoint NoEPEscape(test)
- v23:Fixnum[1] = Const Value(1)
+ v22:Fixnum[1] = Const Value(1)
CheckInterrupts
- Return v23
+ Return v22
");
}
@@ -3739,11 +3736,10 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, C)
- v12:ClassSubclass[C@0x1008] = Const Value(VALUE(0x1008))
+ v11:ClassSubclass[C@0x1008] = Const Value(VALUE(0x1008))
CheckInterrupts
- Return v12
+ Return v11
");
}
@@ -3764,18 +3760,17 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, String)
- v12:ClassSubclass[String@0x1008] = Const Value(VALUE(0x1008))
+ v11:ClassSubclass[String@0x1008] = Const Value(VALUE(0x1008))
PatchPoint StableConstantNames(0x1010, Class)
- v16:ClassSubclass[Class@0x1018] = Const Value(VALUE(0x1018))
+ v14:ClassSubclass[Class@0x1018] = Const Value(VALUE(0x1018))
PatchPoint StableConstantNames(0x1020, Module)
- v20:ClassSubclass[Module@0x1028] = Const Value(VALUE(0x1028))
+ v17:ClassSubclass[Module@0x1028] = Const Value(VALUE(0x1028))
PatchPoint StableConstantNames(0x1030, BasicObject)
- v24:ClassSubclass[BasicObject@0x1038] = Const Value(VALUE(0x1038))
- v26:ArrayExact = NewArray v12, v16, v20, v24
+ v20:ClassSubclass[BasicObject@0x1038] = Const Value(VALUE(0x1038))
+ v22:ArrayExact = NewArray v11, v14, v17, v20
CheckInterrupts
- Return v26
+ Return v22
");
}
@@ -3796,14 +3791,13 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, Enumerable)
- v12:ModuleExact[Enumerable@0x1008] = Const Value(VALUE(0x1008))
+ v11:ModuleExact[Enumerable@0x1008] = Const Value(VALUE(0x1008))
PatchPoint StableConstantNames(0x1010, Kernel)
- v16:ModuleSubclass[Kernel@0x1018] = Const Value(VALUE(0x1018))
- v18:ArrayExact = NewArray v12, v16
+ v14:ModuleSubclass[Kernel@0x1018] = Const Value(VALUE(0x1018))
+ v16:ArrayExact = NewArray v11, v14
CheckInterrupts
- Return v18
+ Return v16
");
}
@@ -3826,11 +3820,10 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, MY_MODULE)
- v12:ModuleSubclass[MY_MODULE@0x1008] = Const Value(VALUE(0x1008))
+ v11:ModuleSubclass[MY_MODULE@0x1008] = Const Value(VALUE(0x1008))
CheckInterrupts
- Return v12
+ Return v11
");
}
@@ -4049,14 +4042,13 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, M)
- v12:ModuleExact[M@0x1008] = Const Value(VALUE(0x1008))
+ v11:ModuleExact[M@0x1008] = Const Value(VALUE(0x1008))
PatchPoint NoSingletonClass(Module@0x1010)
PatchPoint MethodRedefined(Module@0x1010, class@0x1018, cme:0x1020)
- v23:ClassSubclass[Module@0x1010] = Const Value(VALUE(0x1010))
+ v22:ClassSubclass[Module@0x1010] = Const Value(VALUE(0x1010))
CheckInterrupts
- Return v23
+ Return v22
");
}
@@ -4363,10 +4355,10 @@ mod hir_opt_tests {
v10:Fixnum[10] = Const Value(10)
v12:CPtr = GetEP 0
v13:CInt64 = LoadField v12, :VM_ENV_DATA_INDEX_SPECVAL@0x1000
- v15:CInt64[3] = Const CInt64(3)
- v16:CInt64 = IntAnd v13, v15
+ v14:CInt64[3] = Const CInt64(3)
+ v15:CInt64 = IntAnd v13, v14
v17:CInt64[1] = Const CInt64(1)
- v18:CBool = IsBitEqual v16, v17
+ v18:CBool = IsBitEqual v15, v17
CondBranch v18, bb5(), bb6()
bb5():
v20:CInt64[-4] = Const CInt64(-4)
@@ -4390,9 +4382,9 @@ mod hir_opt_tests {
bb6():
v34:BasicObject = InvokeBlock v10 # SendFallbackReason: InvokeBlock: polymorphic dispatch miss
Jump bb4(v34)
- bb4(v14:BasicObject):
+ bb4(v16:BasicObject):
CheckInterrupts
- Return v14
+ Return v16
");
}
@@ -4428,10 +4420,10 @@ mod hir_opt_tests {
v10:Fixnum[10] = Const Value(10)
v12:CPtr = GetEP 0
v13:CInt64 = LoadField v12, :VM_ENV_DATA_INDEX_SPECVAL@0x1000
- v15:CInt64[3] = Const CInt64(3)
- v16:CInt64 = IntAnd v13, v15
+ v14:CInt64[3] = Const CInt64(3)
+ v15:CInt64 = IntAnd v13, v14
v17:CInt64[1] = Const CInt64(1)
- v18:CBool = IsBitEqual v16, v17
+ v18:CBool = IsBitEqual v15, v17
CondBranch v18, bb5(), bb6()
bb5():
v20:CInt64[-4] = Const CInt64(-4)
@@ -4455,9 +4447,9 @@ mod hir_opt_tests {
bb6():
v34:BasicObject = InvokeBlock v10 # SendFallbackReason: InvokeBlock: polymorphic dispatch miss
Jump bb4(v34)
- bb4(v14:BasicObject):
+ bb4(v16:BasicObject):
CheckInterrupts
- Return v14
+ Return v16
");
}
@@ -4492,10 +4484,10 @@ mod hir_opt_tests {
v10:Fixnum[10] = Const Value(10)
v12:CPtr = GetEP 0
v13:CInt64 = LoadField v12, :VM_ENV_DATA_INDEX_SPECVAL@0x1000
- v15:CInt64[3] = Const CInt64(3)
- v16:CInt64 = IntAnd v13, v15
+ v14:CInt64[3] = Const CInt64(3)
+ v15:CInt64 = IntAnd v13, v14
v17:CInt64[1] = Const CInt64(1)
- v18:CBool = IsBitEqual v16, v17
+ v18:CBool = IsBitEqual v15, v17
CondBranch v18, bb5(), bb6()
bb5():
v20:CInt64[-4] = Const CInt64(-4)
@@ -4519,9 +4511,67 @@ mod hir_opt_tests {
bb6():
v34:BasicObject = InvokeBlock v10 # SendFallbackReason: InvokeBlock: polymorphic dispatch miss
Jump bb4(v34)
- bb4(v14:BasicObject):
+ bb4(v16:BasicObject):
CheckInterrupts
- Return v14
+ Return v16
+ ");
+ }
+
+ #[test]
+ fn test_specialize_monomorphic_invokeblock_on_final_version() {
+ // A monomorphic yield site compiled under the no-side-exits policy (the final
+ // version after an invalidation) must not guard, so instead of the in-place
+ // guarded dispatch it gets the same compare-plus-fallback chain as a polymorphic
+ // site, with a single ISEQ arm. The constant reassignment invalidates the first
+ // version through its constant cache patchpoint without a second block ever
+ // reaching the yield site, keeping its profile monomorphic.
+ set_max_versions(2);
+ set_inline_threshold(0);
+ eval("
+ X = 10
+ def invoke = yield(X)
+ def add_one = invoke { |x| x + 1 }
+ add_one; add_one
+ Object.send(:remove_const, :X)
+ X = 11
+ ");
+ assert_snapshot!(hir_string("invoke"), @"
+ fn invoke@:3:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb3(v1)
+ bb2():
+ EntryPoint JIT(0)
+ v4:BasicObject = LoadArg :self@0
+ Jump bb3(v4)
+ bb3(v6:BasicObject):
+ v10:BasicObject = GetConstantPath 0x1000
+ v12:CPtr = GetEP 0
+ v13:CInt64 = LoadField v12, :VM_ENV_DATA_INDEX_SPECVAL@0x1010
+ v14:CInt64[3] = Const CInt64(3)
+ v15:CInt64 = IntAnd v13, v14
+ v17:CInt64[1] = Const CInt64(1)
+ v18:CBool = IsBitEqual v15, v17
+ CondBranch v18, bb5(), bb6()
+ bb5():
+ v20:CInt64[-4] = Const CInt64(-4)
+ v21:CInt64 = IntAnd v13, v20
+ v22:CPtr = LoadField v21, :code_iseq@0x1011
+ v23:CPtr[CPtr(0x1012)] = Const CPtr(0x1012)
+ v24:CBool = IsBitEqual v22, v23
+ CondBranch v24, bb7(), bb8()
+ bb7():
+ v26:BasicObject = InvokeBlockIseqDirect (0x1012), v21, v10
+ Jump bb4(v26)
+ bb8():
+ Jump bb6()
+ bb6():
+ v29:BasicObject = InvokeBlock v10 # SendFallbackReason: InvokeBlock: polymorphic dispatch miss
+ Jump bb4(v29)
+ bb4(v16:BasicObject):
+ CheckInterrupts
+ Return v16
");
}
@@ -5982,11 +6032,10 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, Kernel)
- v12:ModuleSubclass[Kernel@0x1008] = Const Value(VALUE(0x1008))
+ v11:ModuleSubclass[Kernel@0x1008] = Const Value(VALUE(0x1008))
CheckInterrupts
- Return v12
+ Return v11
");
}
@@ -6013,11 +6062,10 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, Foo::Bar::C)
- v12:ClassSubclass[Foo::Bar::C@0x1008] = Const Value(VALUE(0x1008))
+ v11:ClassSubclass[Foo::Bar::C@0x1008] = Const Value(VALUE(0x1008))
CheckInterrupts
- Return v12
+ Return v11
");
}
@@ -6039,16 +6087,15 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, C)
- v12:ClassSubclass[C@0x1008] = Const Value(VALUE(0x1008))
- v14:NilClass = Const Value(nil)
+ v11:ClassSubclass[C@0x1008] = Const Value(VALUE(0x1008))
+ v13:NilClass = Const Value(nil)
PatchPoint MethodRedefined(C@0x1008, new@0x1009, cme:0x1010)
- v44:ObjectSubclass[class_exact:C] = ObjectAllocClass C:VALUE(0x1008)
+ v43:ObjectSubclass[class_exact:C] = ObjectAllocClass C:VALUE(0x1008)
PatchPoint NoSingletonClass(C@0x1008)
PatchPoint MethodRedefined(C@0x1008, initialize@0x1038, cme:0x1040)
CheckInterrupts
- Return v44
+ Return v43
");
}
@@ -6074,25 +6121,25 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, C)
- v12:ClassSubclass[C@0x1008] = Const Value(VALUE(0x1008))
- v14:NilClass = Const Value(nil)
- v17:Fixnum[1] = Const Value(1)
+ v11:ClassSubclass[C@0x1008] = Const Value(VALUE(0x1008))
+ v13:NilClass = Const Value(nil)
+ v16:Fixnum[1] = Const Value(1)
PatchPoint MethodRedefined(C@0x1008, new@0x1009, cme:0x1010)
- v47:ObjectSubclass[class_exact:C] = ObjectAllocClass C:VALUE(0x1008)
+ v46:ObjectSubclass[class_exact:C] = ObjectAllocClass C:VALUE(0x1008)
PatchPoint NoSingletonClass(C@0x1008)
PatchPoint MethodRedefined(C@0x1008, initialize@0x1038, cme:0x1040)
- PushInlineFrame :initialize, v47 (0x1068), num_args=1
- v65:CShape = LoadField v47, :shape_id@0x1090
- v66:CShape[0x1091] = GuardBitEquals v65, CShape(0x1091) recompile
- StoreField v47, :@x@0x1092, v17
- WriteBarrier v47, v17
- v69:CShape[0x1093] = Const CShape(0x1093)
- StoreField v47, :shape_id@0x1090, v69
+ PushInlineFrame :initialize, v46 (0x1068), num_args=1
+ PatchPoint SingleRactorMode
+ v64:CShape = LoadField v46, :shape_id@0x1090
+ v65:CShape[0x1091] = GuardBitEquals v64, CShape(0x1091) recompile
+ StoreField v46, :@x@0x1092, v16
+ WriteBarrier v46, v16
+ v68:CShape[0x1093] = Const CShape(0x1093)
+ StoreField v46, :shape_id@0x1090, v68
CheckInterrupts
PopInlineFrame
- Return v47
+ Return v46
");
}
@@ -6113,16 +6160,92 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, Object)
- v12:ClassSubclass[Object@0x1008] = Const Value(VALUE(0x1008))
- v14:NilClass = Const Value(nil)
+ v11:ClassSubclass[Object@0x1008] = Const Value(VALUE(0x1008))
+ v13:NilClass = Const Value(nil)
PatchPoint MethodRedefined(Object@0x1008, new@0x1009, cme:0x1010)
- v44:ObjectExact = ObjectAllocClass Object:VALUE(0x1008)
+ v43:ObjectExact = ObjectAllocClass Object:VALUE(0x1008)
PatchPoint NoSingletonClass(Object@0x1008)
PatchPoint MethodRedefined(Object@0x1008, initialize@0x1038, cme:0x1040)
CheckInterrupts
- Return v44
+ Return v43
+ ");
+ }
+
+ #[test]
+ fn test_opt_new_with_shareable_object_multi_ractor() {
+ eval("
+ def test = Object.new
+ Ractor.new {}.value
+ test
+ ");
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:2:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb3(v1)
+ bb2():
+ EntryPoint JIT(0)
+ v4:BasicObject = LoadArg :self@0
+ Jump bb3(v4)
+ bb3(v6:BasicObject):
+ PatchPoint StableConstantNames(0x1000, Object)
+ v11:ClassSubclass[Object@0x1008] = Const Value(VALUE(0x1008))
+ v13:NilClass = Const Value(nil)
+ PatchPoint MethodRedefined(Object@0x1008, new@0x1009, cme:0x1010)
+ v43:ObjectExact = ObjectAllocClass Object:VALUE(0x1008)
+ PatchPoint NoSingletonClass(Object@0x1008)
+ PatchPoint MethodRedefined(Object@0x1008, initialize@0x1038, cme:0x1040)
+ CheckInterrupts
+ Return v43
+ ");
+ }
+
+ #[test]
+ fn test_opt_new_with_non_shareable_object_multi_ractor() {
+ eval("
+ class Factory
+ def new = Object.new
+ end
+ FACTORY = Factory.new
+ def test = FACTORY.new
+ Ractor.new {}.value
+ test
+ ");
+ assert_snapshot!(hir_string("test"), @"
+ fn test@:6:
+ bb1():
+ EntryPoint interpreter
+ v1:BasicObject = LoadSelf
+ Jump bb3(v1)
+ bb2():
+ EntryPoint JIT(0)
+ v4:BasicObject = LoadArg :self@0
+ Jump bb3(v4)
+ bb3(v6:BasicObject):
+ v10:BasicObject = GetConstantPath 0x1000
+ v12:NilClass = Const Value(nil)
+ v15:CBool = IsMethodCFunc v10, :new
+ CondBranch v15, bb6(), bb4()
+ bb6():
+ v17:HeapBasicObject = ObjectAlloc v10
+ SideExit NoProfileSend recompile
+ bb4():
+ PatchPoint NoSingletonClass(Factory@0x1010)
+ PatchPoint MethodRedefined(Factory@0x1010, new@0x1018, cme:0x1020)
+ v42:ObjectSubclass[class_exact:Factory] = GuardType v10, ObjectSubclass[class_exact:Factory] recompile
+ PushInlineFrame :new, v42 (0x1048), num_args=0
+ PatchPoint StableConstantNames(0x1070, Object)
+ v50:ClassSubclass[Object@0x1078] = Const Value(VALUE(0x1078))
+ v52:NilClass = Const Value(nil)
+ PatchPoint MethodRedefined(Object@0x1078, new@0x1018, cme:0x1080)
+ v86:ObjectExact = ObjectAllocClass Object:VALUE(0x1078)
+ PatchPoint NoSingletonClass(Object@0x1078)
+ PatchPoint MethodRedefined(Object@0x1078, initialize@0x10a8, cme:0x10b0)
+ CheckInterrupts
+ PopInlineFrame
+ Return v86
");
}
@@ -6143,16 +6266,15 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, BasicObject)
- v12:ClassSubclass[BasicObject@0x1008] = Const Value(VALUE(0x1008))
- v14:NilClass = Const Value(nil)
+ v11:ClassSubclass[BasicObject@0x1008] = Const Value(VALUE(0x1008))
+ v13:NilClass = Const Value(nil)
PatchPoint MethodRedefined(BasicObject@0x1008, new@0x1009, cme:0x1010)
- v44:BasicObjectExact = ObjectAllocClass BasicObject:VALUE(0x1008)
+ v43:BasicObjectExact = ObjectAllocClass BasicObject:VALUE(0x1008)
PatchPoint NoSingletonClass(BasicObject@0x1008)
PatchPoint MethodRedefined(BasicObject@0x1008, initialize@0x1038, cme:0x1040)
CheckInterrupts
- Return v44
+ Return v43
");
}
@@ -6173,34 +6295,33 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, Hash)
- v12:ClassSubclass[Hash@0x1008] = Const Value(VALUE(0x1008))
- v14:NilClass = Const Value(nil)
+ v11:ClassSubclass[Hash@0x1008] = Const Value(VALUE(0x1008))
+ v13:NilClass = Const Value(nil)
PatchPoint MethodRedefined(Hash@0x1008, new@0x1009, cme:0x1010)
- v44:HashExact = ObjectAllocClass Hash:VALUE(0x1008)
+ v43:HashExact = ObjectAllocClass Hash:VALUE(0x1008)
PatchPoint NoSingletonClass(Hash@0x1008)
PatchPoint MethodRedefined(Hash@0x1008, initialize@0x1038, cme:0x1040)
- v48:Fixnum[0] = Const Value(0)
- v97:Fixnum[0] = Const Value(0)
- v98:NilClass = Const Value(nil)
- PushInlineFrame :initialize, v44 (0x1068), num_args=1
- v64:TrueClass = Const Value(true)
- v82:CPtr = GetEP 0
- v83:CUInt64 = LoadField v82, :VM_ENV_DATA_INDEX_FLAGS@0x1090
- v84:CBool = IsBlockParamModified v83
- CondBranch v84, bb11(), bb12()
+ v47:Fixnum[0] = Const Value(0)
+ v96:Fixnum[0] = Const Value(0)
+ v97:NilClass = Const Value(nil)
+ PushInlineFrame :initialize, v43 (0x1068), num_args=1
+ v63:TrueClass = Const Value(true)
+ v81:CPtr = GetEP 0
+ v82:CUInt64 = LoadField v81, :VM_ENV_DATA_INDEX_FLAGS@0x1090
+ v83:CBool = IsBlockParamModified v82
+ CondBranch v83, bb11(), bb12()
bb11():
- v86:BasicObject = LoadField v82, :block@0x1091
- Jump bb13(v86)
+ v85:BasicObject = LoadField v81, :block@0x1091
+ Jump bb13(v85)
bb12():
- v88:BasicObject = GetBlockParam :block, l0, EP@4
- Jump bb13(v88)
- bb13(v81:BasicObject):
- v91:BasicObject = InvokeBuiltin rb_hash_init, v44, v48, v64, v64, v81
+ v87:BasicObject = GetBlockParam :block, l0, EP@4
+ Jump bb13(v87)
+ bb13(v80:BasicObject):
+ v90:BasicObject = InvokeBuiltin rb_hash_init, v43, v47, v63, v63, v80
CheckInterrupts
PopInlineFrame
- Return v44
+ Return v43
");
assert_snapshot!(inspect("test"), @"{}");
}
@@ -6222,16 +6343,15 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, Array)
- v12:ClassSubclass[Array@0x1008] = Const Value(VALUE(0x1008))
- v14:NilClass = Const Value(nil)
- v17:Fixnum[1] = Const Value(1)
+ v11:ClassSubclass[Array@0x1008] = Const Value(VALUE(0x1008))
+ v13:NilClass = Const Value(nil)
+ v16:Fixnum[1] = Const Value(1)
PatchPoint MethodRedefined(Array@0x1008, new@0x1009, cme:0x1010)
PatchPoint MethodRedefined(Class@0x1038, new@0x1009, cme:0x1010)
- v55:BasicObject = CCallVariadic v12, :Array.new@0x1040, v17
+ v54:BasicObject = CCallVariadic v11, :Array.new@0x1040, v16
CheckInterrupts
- Return v55
+ Return v54
");
}
@@ -6252,18 +6372,17 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, Set)
- v12:ClassSubclass[Set@0x1008] = Const Value(VALUE(0x1008))
- v14:NilClass = Const Value(nil)
+ v11:ClassSubclass[Set@0x1008] = Const Value(VALUE(0x1008))
+ v13:NilClass = Const Value(nil)
PatchPoint MethodRedefined(Set@0x1008, new@0x1009, cme:0x1010)
- v19:HeapBasicObject = ObjectAlloc v12
+ v18:HeapBasicObject = ObjectAlloc v11
PatchPoint NoSingletonClass(Set@0x1008)
PatchPoint MethodRedefined(Set@0x1008, initialize@0x1038, cme:0x1040)
- v47:SetExact = GuardType v19, SetExact recompile
- v48:BasicObject = CCallVariadic v47, :Set#initialize@0x1068
+ v46:SetExact = GuardType v18, SetExact recompile
+ v47:BasicObject = CCallVariadic v46, :Set#initialize@0x1068
CheckInterrupts
- Return v47
+ Return v46
");
}
@@ -6284,15 +6403,14 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, String)
- v12:ClassSubclass[String@0x1008] = Const Value(VALUE(0x1008))
- v14:NilClass = Const Value(nil)
+ v11:ClassSubclass[String@0x1008] = Const Value(VALUE(0x1008))
+ v13:NilClass = Const Value(nil)
PatchPoint MethodRedefined(String@0x1008, new@0x1009, cme:0x1010)
PatchPoint MethodRedefined(Class@0x1038, new@0x1009, cme:0x1010)
- v52:BasicObject = CCallVariadic v12, :String.new@0x1040
+ v51:BasicObject = CCallVariadic v11, :String.new@0x1040
CheckInterrupts
- Return v52
+ Return v51
");
}
@@ -6313,19 +6431,18 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, Regexp)
- v12:ClassSubclass[Regexp@0x1008] = Const Value(VALUE(0x1008))
- v14:NilClass = Const Value(nil)
- v17:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010))
- v18:StringExact = StringCopy v17
+ v11:ClassSubclass[Regexp@0x1008] = Const Value(VALUE(0x1008))
+ v13:NilClass = Const Value(nil)
+ v16:StringExact[VALUE(0x1010)] = Const Value(VALUE(0x1010))
+ v17:StringExact = StringCopy v16
PatchPoint MethodRedefined(Regexp@0x1008, new@0x1018, cme:0x1020)
- v48:RegexpExact = ObjectAllocClass Regexp:VALUE(0x1008)
+ v47:RegexpExact = ObjectAllocClass Regexp:VALUE(0x1008)
PatchPoint NoSingletonClass(Regexp@0x1008)
PatchPoint MethodRedefined(Regexp@0x1008, initialize@0x1048, cme:0x1050)
- v53:BasicObject = CCallVariadic v48, :Regexp#initialize@0x1078, v18
+ v52:BasicObject = CCallVariadic v47, :Regexp#initialize@0x1078, v17
CheckInterrupts
- Return v48
+ Return v47
");
}
@@ -6347,13 +6464,12 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, C)
- v12:ClassSubclass[C@0x1008] = Const Value(VALUE(0x1008))
+ v11:ClassSubclass[C@0x1008] = Const Value(VALUE(0x1008))
PatchPoint MethodRedefined(Class@0x1010, allocate@0x1018, cme:0x1020)
- v23:ObjectSubclass[class_exact:C] = ObjectAllocClass C:VALUE(0x1008)
+ v22:ObjectSubclass[class_exact:C] = ObjectAllocClass C:VALUE(0x1008)
CheckInterrupts
- Return v23
+ Return v22
");
}
@@ -6377,13 +6493,12 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, C)
- v12:ClassSubclass[C@0x1008] = Const Value(VALUE(0x1008))
- v14:Fixnum[1] = Const Value(1)
- v16:BasicObject = Send v12, :allocate, v14 # SendFallbackReason: Argument count does not match parameter count
+ v11:ClassSubclass[C@0x1008] = Const Value(VALUE(0x1008))
+ v13:Fixnum[1] = Const Value(1)
+ v15:BasicObject = Send v11, :allocate, v13 # SendFallbackReason: Argument count does not match parameter count
CheckInterrupts
- Return v16
+ Return v15
");
}
@@ -6407,13 +6522,12 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, SC)
- v12:ClassSubclass[Class@0x1008] = Const Value(VALUE(0x1008))
+ v11:ClassSubclass[Class@0x1008] = Const Value(VALUE(0x1008))
PatchPoint MethodRedefined(Class@0x1010, allocate@0x1018, cme:0x1020)
- v23:BasicObject = CCallWithFrame v12, :Class.allocate@0x1048
+ v22:BasicObject = CCallWithFrame v11, :Class.allocate@0x1048
CheckInterrupts
- Return v23
+ Return v22
");
}
@@ -9088,13 +9202,12 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, Foo)
- v12:ClassSubclass[Foo@0x1008] = Const Value(VALUE(0x1008))
- v14:Fixnum[100] = Const Value(100)
+ v11:ClassSubclass[Foo@0x1008] = Const Value(VALUE(0x1008))
+ v13:Fixnum[100] = Const Value(100)
PatchPoint MethodRedefined(Class@0x1010, identity@0x1018, cme:0x1020)
CheckInterrupts
- Return v14
+ Return v13
");
}
@@ -12227,15 +12340,14 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, H)
- v12:HashExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
- v14:StaticSymbol[:a] = Const Value(VALUE(0x1010))
+ v11:HashExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v13:StaticSymbol[:a] = Const Value(VALUE(0x1010))
PatchPoint NoSingletonClass(Hash@0x1018)
PatchPoint MethodRedefined(Hash@0x1018, []@0x1020, cme:0x1028)
- v27:BasicObject = HashAref v12, v14
+ v26:BasicObject = HashAref v11, v13
CheckInterrupts
- Return v27
+ Return v26
");
}
@@ -12360,15 +12472,14 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, Thread)
- v12:ClassSubclass[Thread@0x1008] = Const Value(VALUE(0x1008))
+ v11:ClassSubclass[Thread@0x1008] = Const Value(VALUE(0x1008))
PatchPoint MethodRedefined(Class@0x1010, current@0x1018, cme:0x1020)
- v23:CPtr = LoadEC
- v24:CPtr = LoadField v23, :thread_ptr@0x1048
- v25:BasicObject = LoadField v24, :self@0x1049
+ v22:CPtr = LoadEC
+ v23:CPtr = LoadField v22, :thread_ptr@0x1048
+ v24:BasicObject = LoadField v23, :self@0x1049
CheckInterrupts
- Return v25
+ Return v24
");
}
@@ -15692,14 +15803,13 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :o@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1008, String)
- v16:ClassSubclass[String@0x1010] = Const Value(VALUE(0x1010))
+ v15:ClassSubclass[String@0x1010] = Const Value(VALUE(0x1010))
PatchPoint NoEPEscape(test)
PatchPoint MethodRedefined(Class@0x1018, ===@0x1020, cme:0x1028)
- v30:BoolExact = IsA v10, v16
+ v29:BoolExact = IsA v10, v15
CheckInterrupts
- Return v30
+ Return v29
");
}
@@ -15723,14 +15833,13 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :o@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1008, Kernel)
- v16:ModuleSubclass[Kernel@0x1010] = Const Value(VALUE(0x1010))
+ v15:ModuleSubclass[Kernel@0x1010] = Const Value(VALUE(0x1010))
PatchPoint NoEPEscape(test)
PatchPoint MethodRedefined(Module@0x1018, ===@0x1020, cme:0x1028)
- v30:BoolExact = CCall v16, :Module#===@0x1050, v10
+ v29:BoolExact = CCall v15, :Module#===@0x1050, v10
CheckInterrupts
- Return v30
+ Return v29
");
}
@@ -15754,15 +15863,14 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :o@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1008, String)
- v17:ClassSubclass[String@0x1010] = Const Value(VALUE(0x1010))
+ v16:ClassSubclass[String@0x1010] = Const Value(VALUE(0x1010))
PatchPoint NoSingletonClass(String@0x1010)
PatchPoint MethodRedefined(String@0x1010, is_a?@0x1011, cme:0x1018)
- v28:StringExact = GuardType v10, StringExact recompile
- v29:BoolExact = IsA v28, v17
+ v27:StringExact = GuardType v10, StringExact recompile
+ v28:BoolExact = IsA v27, v16
CheckInterrupts
- Return v29
+ Return v28
");
}
@@ -15786,15 +15894,14 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :o@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1008, Kernel)
- v17:ModuleSubclass[Kernel@0x1010] = Const Value(VALUE(0x1010))
+ v16:ModuleSubclass[Kernel@0x1010] = Const Value(VALUE(0x1010))
PatchPoint NoSingletonClass(String@0x1018)
PatchPoint MethodRedefined(String@0x1018, is_a?@0x1020, cme:0x1028)
- v28:StringExact = GuardType v10, StringExact recompile
- v29:BasicObject = CCallWithFrame v28, :Kernel#is_a?@0x1050, v17
+ v27:StringExact = GuardType v10, StringExact recompile
+ v28:BasicObject = CCallWithFrame v27, :Kernel#is_a?@0x1050, v16
CheckInterrupts
- Return v29
+ Return v28
");
}
@@ -15821,15 +15928,14 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :o@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1008, Integer)
- v17:ClassSubclass[Integer@0x1010] = Const Value(VALUE(0x1010))
+ v16:ClassSubclass[Integer@0x1010] = Const Value(VALUE(0x1010))
PatchPoint NoSingletonClass(String@0x1018)
PatchPoint MethodRedefined(String@0x1018, is_a?@0x1020, cme:0x1028)
- v32:StringExact = GuardType v10, StringExact recompile
- v23:Fixnum[5] = Const Value(5)
+ v31:StringExact = GuardType v10, StringExact recompile
+ v22:Fixnum[5] = Const Value(5)
CheckInterrupts
- Return v23
+ Return v22
");
}
@@ -15856,14 +15962,13 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :o@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1008, Integer)
- v16:ClassSubclass[Integer@0x1010] = Const Value(VALUE(0x1010))
+ v15:ClassSubclass[Integer@0x1010] = Const Value(VALUE(0x1010))
PatchPoint NoEPEscape(test)
PatchPoint MethodRedefined(Class@0x1018, ===@0x1020, cme:0x1028)
- v25:Fixnum[5] = Const Value(5)
+ v24:Fixnum[5] = Const Value(5)
CheckInterrupts
- Return v25
+ Return v24
");
}
@@ -15887,15 +15992,14 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :o@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1008, String)
- v17:ClassSubclass[String@0x1010] = Const Value(VALUE(0x1010))
+ v16:ClassSubclass[String@0x1010] = Const Value(VALUE(0x1010))
PatchPoint NoSingletonClass(String@0x1010)
PatchPoint MethodRedefined(String@0x1010, kind_of?@0x1011, cme:0x1018)
- v28:StringExact = GuardType v10, StringExact recompile
- v29:BoolExact = IsA v28, v17
+ v27:StringExact = GuardType v10, StringExact recompile
+ v28:BoolExact = IsA v27, v16
CheckInterrupts
- Return v29
+ Return v28
");
}
@@ -15919,15 +16023,14 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :o@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1008, Kernel)
- v17:ModuleSubclass[Kernel@0x1010] = Const Value(VALUE(0x1010))
+ v16:ModuleSubclass[Kernel@0x1010] = Const Value(VALUE(0x1010))
PatchPoint NoSingletonClass(String@0x1018)
PatchPoint MethodRedefined(String@0x1018, kind_of?@0x1020, cme:0x1028)
- v28:StringExact = GuardType v10, StringExact recompile
- v29:BasicObject = CCallWithFrame v28, :Kernel#kind_of?@0x1050, v17
+ v27:StringExact = GuardType v10, StringExact recompile
+ v28:BasicObject = CCallWithFrame v27, :Kernel#kind_of?@0x1050, v16
CheckInterrupts
- Return v29
+ Return v28
");
}
@@ -15954,15 +16057,14 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :o@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1008, Integer)
- v17:ClassSubclass[Integer@0x1010] = Const Value(VALUE(0x1010))
+ v16:ClassSubclass[Integer@0x1010] = Const Value(VALUE(0x1010))
PatchPoint NoSingletonClass(String@0x1018)
PatchPoint MethodRedefined(String@0x1018, kind_of?@0x1020, cme:0x1028)
- v32:StringExact = GuardType v10, StringExact recompile
- v23:Fixnum[5] = Const Value(5)
+ v31:StringExact = GuardType v10, StringExact recompile
+ v22:Fixnum[5] = Const Value(5)
CheckInterrupts
- Return v23
+ Return v22
");
}
@@ -15984,13 +16086,12 @@ mod hir_opt_tests {
Jump bb3(v4)
bb3(v6:BasicObject):
v10:Fixnum[5] = Const Value(5)
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, Integer)
- v14:ClassSubclass[Integer@0x1008] = Const Value(VALUE(0x1008))
+ v13:ClassSubclass[Integer@0x1008] = Const Value(VALUE(0x1008))
PatchPoint MethodRedefined(Integer@0x1008, is_a?@0x1009, cme:0x1010)
- v26:TrueClass = Const Value(true)
+ v25:TrueClass = Const Value(true)
CheckInterrupts
- Return v26
+ Return v25
");
}
@@ -16012,13 +16113,12 @@ mod hir_opt_tests {
Jump bb3(v4)
bb3(v6:BasicObject):
v10:Fixnum[5] = Const Value(5)
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, String)
- v14:ClassSubclass[String@0x1008] = Const Value(VALUE(0x1008))
+ v13:ClassSubclass[String@0x1008] = Const Value(VALUE(0x1008))
PatchPoint MethodRedefined(Integer@0x1010, is_a?@0x1018, cme:0x1020)
- v26:FalseClass = Const Value(false)
+ v25:FalseClass = Const Value(false)
CheckInterrupts
- Return v26
+ Return v25
");
}
@@ -16045,12 +16145,12 @@ mod hir_opt_tests {
PatchPoint StableConstantNames(0x1000, O)
v12:ArraySubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
PatchPoint StableConstantNames(0x1010, Array)
- v16:ClassSubclass[Array@0x1018] = Const Value(VALUE(0x1018))
+ v15:ClassSubclass[Array@0x1018] = Const Value(VALUE(0x1018))
PatchPoint NoSingletonClass(C@0x1020)
PatchPoint MethodRedefined(C@0x1020, is_a?@0x1028, cme:0x1030)
- v29:TrueClass = Const Value(true)
+ v28:TrueClass = Const Value(true)
CheckInterrupts
- Return v29
+ Return v28
");
}
@@ -16077,12 +16177,12 @@ mod hir_opt_tests {
PatchPoint StableConstantNames(0x1000, O)
v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
PatchPoint StableConstantNames(0x1010, C)
- v16:ClassSubclass[C@0x1018] = Const Value(VALUE(0x1018))
+ v15:ClassSubclass[C@0x1018] = Const Value(VALUE(0x1018))
PatchPoint NoSingletonClass(C@0x1018)
PatchPoint MethodRedefined(C@0x1018, is_a?@0x1019, cme:0x1020)
- v29:TrueClass = Const Value(true)
+ v28:TrueClass = Const Value(true)
CheckInterrupts
- Return v29
+ Return v28
");
}
@@ -16104,15 +16204,14 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, O)
- v12:StaticSymbol[:my_static_symbol] = Const Value(VALUE(0x1008))
+ v11:StaticSymbol[:my_static_symbol] = Const Value(VALUE(0x1008))
PatchPoint StableConstantNames(0x1010, Symbol)
- v16:ClassSubclass[Symbol@0x1018] = Const Value(VALUE(0x1018))
+ v14:ClassSubclass[Symbol@0x1018] = Const Value(VALUE(0x1018))
PatchPoint MethodRedefined(Symbol@0x1018, is_a?@0x1019, cme:0x1020)
- v28:TrueClass = Const Value(true)
+ v26:TrueClass = Const Value(true)
CheckInterrupts
- Return v28
+ Return v26
");
}
@@ -16372,15 +16471,14 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, X)
- v12:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
- v14:Fixnum[0] = Const Value(0)
+ v11:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v13:Fixnum[0] = Const Value(0)
PatchPoint NoSingletonClass(Array@0x1010)
PatchPoint MethodRedefined(Array@0x1010, []@0x1018, cme:0x1020)
- v36:ModuleExact[VALUE(0x1048)] = Const Value(VALUE(0x1048))
+ v35:ModuleExact[VALUE(0x1048)] = Const Value(VALUE(0x1048))
CheckInterrupts
- Return v36
+ Return v35
");
}
@@ -16488,14 +16586,13 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, FROZEN_OBJ)
- v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v11:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
PatchPoint NoSingletonClass(TestFrozen@0x1010)
PatchPoint MethodRedefined(TestFrozen@0x1010, a@0x1018, cme:0x1020)
- v28:Fixnum[1] = Const Value(1)
+ v27:Fixnum[1] = Const Value(1)
CheckInterrupts
- Return v28
+ Return v27
");
}
@@ -16529,14 +16626,13 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, MULTI_FROZEN)
- v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v11:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
PatchPoint NoSingletonClass(TestMultiIvars@0x1010)
PatchPoint MethodRedefined(TestMultiIvars@0x1010, b@0x1018, cme:0x1020)
- v28:Fixnum[20] = Const Value(20)
+ v27:Fixnum[20] = Const Value(20)
CheckInterrupts
- Return v28
+ Return v27
");
}
@@ -16607,14 +16703,13 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, FROZEN_NIL)
- v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v11:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
PatchPoint NoSingletonClass(TestFrozenNil@0x1010)
PatchPoint MethodRedefined(TestFrozenNil@0x1010, value@0x1018, cme:0x1020)
- v28:NilClass = Const Value(nil)
+ v27:NilClass = Const Value(nil)
CheckInterrupts
- Return v28
+ Return v27
");
}
@@ -16687,14 +16782,13 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, FROZEN_READER)
- v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v11:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
PatchPoint NoSingletonClass(TestAttrReader@0x1010)
PatchPoint MethodRedefined(TestAttrReader@0x1010, value@0x1018, cme:0x1020)
- v28:Fixnum[42] = Const Value(42)
+ v27:Fixnum[42] = Const Value(42)
CheckInterrupts
- Return v28
+ Return v27
");
}
@@ -16726,14 +16820,13 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, FROZEN_SYM)
- v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v11:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
PatchPoint NoSingletonClass(TestFrozenSym@0x1010)
PatchPoint MethodRedefined(TestFrozenSym@0x1010, sym@0x1018, cme:0x1020)
- v28:StaticSymbol[:hello] = Const Value(VALUE(0x1048))
+ v27:StaticSymbol[:hello] = Const Value(VALUE(0x1048))
CheckInterrupts
- Return v28
+ Return v27
");
}
@@ -16765,14 +16858,13 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, FROZEN_TRUE)
- v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v11:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
PatchPoint NoSingletonClass(TestFrozenBool@0x1010)
PatchPoint MethodRedefined(TestFrozenBool@0x1010, flag@0x1018, cme:0x1020)
- v28:TrueClass = Const Value(true)
+ v27:TrueClass = Const Value(true)
CheckInterrupts
- Return v28
+ Return v27
");
}
@@ -16846,20 +16938,19 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, NESTED_FROZEN)
- v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v11:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
PatchPoint NoSingletonClass(TestNestedAccess@0x1010)
PatchPoint MethodRedefined(TestNestedAccess@0x1010, x@0x1018, cme:0x1020)
- v49:Fixnum[100] = Const Value(100)
+ v47:Fixnum[100] = Const Value(100)
PatchPoint StableConstantNames(0x1048, NESTED_FROZEN)
- v18:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v16:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008))
PatchPoint MethodRedefined(TestNestedAccess@0x1010, y@0x1050, cme:0x1058)
- v51:Fixnum[200] = Const Value(200)
+ v49:Fixnum[200] = Const Value(200)
PatchPoint MethodRedefined(Integer@0x1080, +@0x1088, cme:0x1090)
- v52:Fixnum[300] = Const Value(300)
+ v50:Fixnum[300] = Const Value(300)
CheckInterrupts
- Return v52
+ Return v50
");
}
@@ -16881,15 +16972,14 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, S)
- v12:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v11:StringExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
PatchPoint NoSingletonClass(String@0x1010)
PatchPoint MethodRedefined(String@0x1010, bytesize@0x1018, cme:0x1020)
- v24:CInt64 = LoadField v12, :len@0x1048
- v25:Fixnum = BoxFixnum v24
+ v23:CInt64 = LoadField v11, :len@0x1048
+ v24:Fixnum = BoxFixnum v23
CheckInterrupts
- Return v25
+ Return v24
");
}
@@ -18443,32 +18533,31 @@ mod hir_opt_tests {
bb11():
v85:Falsy = RefineType v75, Falsy
PatchPoint MethodRedefined(Object@0x1010, lambda@0x1018, cme:0x1020)
- v131:ObjectSubclass[class_exact*:Object@VALUE(0x1010)] = GuardType v73, ObjectSubclass[class_exact*:Object@VALUE(0x1010)] recompile
- v132:BasicObject = CCallWithFrame v131, :Kernel#lambda@0x1048, block=0x1050
+ v130:ObjectSubclass[class_exact*:Object@VALUE(0x1010)] = GuardType v73, ObjectSubclass[class_exact*:Object@VALUE(0x1010)] recompile
+ v131:BasicObject = CCallWithFrame v130, :Kernel#lambda@0x1048, block=0x1050
v89:CPtr = GetEP 0
v90:BasicObject = LoadField v89, :list@0x1001
v91:BasicObject = LoadField v89, :sep@0x1002
v92:BasicObject = LoadField v89, :iter_method@0x1005
v93:BasicObject = LoadField v89, :kwsplat@0x1006
- SetLocal :sep, l0, EP@5, v132
- Jump bb8(v90, v132, v92, v93)
+ SetLocal :sep, l0, EP@5, v131
+ Jump bb8(v90, v131, v92, v93)
bb8(v98:BasicObject, v99:BasicObject, v100:BasicObject, v101:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1078, CONST)
- v107:HashExact[VALUE(0x1080)] = Const Value(VALUE(0x1080))
- SetLocal :kwsplat, l0, EP@3, v107
- v112:CPtr = GetEP 0
- v113:BasicObject = LoadField v112, :list@0x1001
- v115:CPtr = GetEP 0
- v116:BasicObject = LoadField v115, :iter_method@0x1005
- v118:BasicObject = Send v113, 0x1088, :__send__, v116 # SendFallbackReason: Send: unsupported method type Optimized
- v119:CPtr = GetEP 0
- v120:BasicObject = LoadField v119, :list@0x1001
- v121:BasicObject = LoadField v119, :sep@0x1002
- v122:BasicObject = LoadField v119, :iter_method@0x1005
- v123:BasicObject = LoadField v119, :kwsplat@0x1006
+ v106:HashExact[VALUE(0x1080)] = Const Value(VALUE(0x1080))
+ SetLocal :kwsplat, l0, EP@3, v106
+ v111:CPtr = GetEP 0
+ v112:BasicObject = LoadField v111, :list@0x1001
+ v114:CPtr = GetEP 0
+ v115:BasicObject = LoadField v114, :iter_method@0x1005
+ v117:BasicObject = Send v112, 0x1088, :__send__, v115 # SendFallbackReason: Send: unsupported method type Optimized
+ v118:CPtr = GetEP 0
+ v119:BasicObject = LoadField v118, :list@0x1001
+ v120:BasicObject = LoadField v118, :sep@0x1002
+ v121:BasicObject = LoadField v118, :iter_method@0x1005
+ v122:BasicObject = LoadField v118, :kwsplat@0x1006
CheckInterrupts
- Return v118
+ Return v117
");
}
@@ -19935,15 +20024,14 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, A)
- v12:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
+ v11:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
PatchPoint NoSingletonClass(Array@0x1010)
PatchPoint MethodRedefined(Array@0x1010, length@0x1018, cme:0x1020)
- v27:CInt64[4] = Const CInt64(4)
- v26:Fixnum = BoxFixnum v27
+ v26:CInt64[4] = Const CInt64(4)
+ v25:Fixnum = BoxFixnum v26
CheckInterrupts
- Return v26
+ Return v25
");
}
@@ -21571,91 +21659,91 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, Point)
- v12:ClassSubclass[Point@0x1008] = Const Value(VALUE(0x1008))
- v14:NilClass = Const Value(nil)
- v17:Fixnum[1] = Const Value(1)
- v19:Fixnum[2] = Const Value(2)
+ v11:ClassSubclass[Point@0x1008] = Const Value(VALUE(0x1008))
+ v13:NilClass = Const Value(nil)
+ v16:Fixnum[1] = Const Value(1)
+ v18:Fixnum[2] = Const Value(2)
PatchPoint MethodRedefined(Point@0x1008, new@0x1009, cme:0x1010)
- v89:ObjectSubclass[class_exact:Point] = ObjectAllocClass Point:VALUE(0x1008)
+ v87:ObjectSubclass[class_exact:Point] = ObjectAllocClass Point:VALUE(0x1008)
PatchPoint NoSingletonClass(Point@0x1008)
PatchPoint MethodRedefined(Point@0x1008, initialize@0x1038, cme:0x1040)
- PushInlineFrame :initialize, v89 (0x1068), num_args=2
- v121:CShape = LoadField v89, :shape_id@0x1090
- v122:CShape[0x1091] = GuardBitEquals v121, CShape(0x1091) recompile
- StoreField v89, :@x@0x1092, v17
- WriteBarrier v89, v17
- v125:CShape[0x1093] = Const CShape(0x1093)
- StoreField v89, :shape_id@0x1090, v125
+ PushInlineFrame :initialize, v87 (0x1068), num_args=2
+ PatchPoint SingleRactorMode
+ v119:CShape = LoadField v87, :shape_id@0x1090
+ v120:CShape[0x1091] = GuardBitEquals v119, CShape(0x1091) recompile
+ StoreField v87, :@x@0x1092, v16
+ WriteBarrier v87, v16
+ v123:CShape[0x1093] = Const CShape(0x1093)
+ StoreField v87, :shape_id@0x1090, v123
PatchPoint NoEPEscape(initialize)
PatchPoint SingleRactorMode
- StoreField v89, :@y@0x1094, v19
- WriteBarrier v89, v19
- v140:CShape[0x1095] = Const CShape(0x1095)
- StoreField v89, :shape_id@0x1090, v140
+ StoreField v87, :@y@0x1094, v18
+ WriteBarrier v87, v18
+ v138:CShape[0x1095] = Const CShape(0x1095)
+ StoreField v87, :shape_id@0x1090, v138
CheckInterrupts
PopInlineFrame
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1098, Point)
- v46:ClassSubclass[Point@0x1008] = Const Value(VALUE(0x1008))
- v48:NilClass = Const Value(nil)
- v51:Fixnum[1] = Const Value(1)
- v53:Fixnum[2] = Const Value(2)
+ v44:ClassSubclass[Point@0x1008] = Const Value(VALUE(0x1008))
+ v46:NilClass = Const Value(nil)
+ v49:Fixnum[1] = Const Value(1)
+ v51:Fixnum[2] = Const Value(2)
PatchPoint MethodRedefined(Point@0x1008, new@0x1009, cme:0x1010)
- v99:ObjectSubclass[class_exact:Point] = ObjectAllocClass Point:VALUE(0x1008)
+ v97:ObjectSubclass[class_exact:Point] = ObjectAllocClass Point:VALUE(0x1008)
PatchPoint NoSingletonClass(Point@0x1008)
PatchPoint MethodRedefined(Point@0x1008, initialize@0x1038, cme:0x1040)
- PushInlineFrame :initialize, v99 (0x1068), num_args=2
- v161:CShape = LoadField v99, :shape_id@0x1090
- v162:CShape[0x1091] = GuardBitEquals v161, CShape(0x1091) recompile
- StoreField v99, :@x@0x1092, v51
- WriteBarrier v99, v51
- v165:CShape[0x1093] = Const CShape(0x1093)
- StoreField v99, :shape_id@0x1090, v165
+ PushInlineFrame :initialize, v97 (0x1068), num_args=2
+ PatchPoint SingleRactorMode
+ v159:CShape = LoadField v97, :shape_id@0x1090
+ v160:CShape[0x1091] = GuardBitEquals v159, CShape(0x1091) recompile
+ StoreField v97, :@x@0x1092, v49
+ WriteBarrier v97, v49
+ v163:CShape[0x1093] = Const CShape(0x1093)
+ StoreField v97, :shape_id@0x1090, v163
PatchPoint NoEPEscape(initialize)
PatchPoint SingleRactorMode
- StoreField v99, :@y@0x1094, v53
- WriteBarrier v99, v53
- v180:CShape[0x1095] = Const CShape(0x1095)
- StoreField v99, :shape_id@0x1090, v180
+ StoreField v97, :@y@0x1094, v51
+ WriteBarrier v97, v51
+ v178:CShape[0x1095] = Const CShape(0x1095)
+ StoreField v97, :shape_id@0x1090, v178
CheckInterrupts
PopInlineFrame
PatchPoint NoSingletonClass(Point@0x1008)
PatchPoint MethodRedefined(Point@0x1008, ==@0x10a0, cme:0x10a8)
- PushInlineFrame :==, v89 (0x10d0), num_args=1
+ PushInlineFrame :==, v87 (0x10d0), num_args=1
PatchPoint SingleRactorMode
- v199:CShape = LoadField v89, :shape_id@0x1090
- v200:CShape[0x1095] = GuardBitEquals v199, CShape(0x1095) recompile
- v201:BasicObject = LoadField v89, :@x@0x1092
+ v197:CShape = LoadField v87, :shape_id@0x1090
+ v198:CShape[0x1095] = GuardBitEquals v197, CShape(0x1095) recompile
+ v199:BasicObject = LoadField v87, :@x@0x1092
PatchPoint NoEPEscape(==)
PatchPoint MethodRedefined(Point@0x1008, x@0x10f8, cme:0x1100)
PatchPoint MethodRedefined(Integer@0x1128, ==@0x10a0, cme:0x1130)
- v255:Fixnum = GuardType v201, Fixnum recompile
- v257:BoolExact = FixnumEq v255, v51
- v212:CBool = Test v257
- v213:FalseClass = RefineType v257, Falsy
- CondBranch v212, bb19(), bb18(v213)
+ v253:Fixnum = GuardType v199, Fixnum recompile
+ v255:BoolExact = FixnumEq v253, v49
+ v210:CBool = Test v255
+ v211:FalseClass = RefineType v255, Falsy
+ CondBranch v210, bb19(), bb18(v211)
bb19():
PatchPoint SingleRactorMode
- v220:CShape = LoadField v89, :shape_id@0x1090
- v221:CShape[0x1095] = GuardBitEquals v220, CShape(0x1095) recompile
- v222:BasicObject = LoadField v89, :@y@0x1094
+ v218:CShape = LoadField v87, :shape_id@0x1090
+ v219:CShape[0x1095] = GuardBitEquals v218, CShape(0x1095) recompile
+ v220:BasicObject = LoadField v87, :@y@0x1094
PatchPoint NoEPEscape(==)
PatchPoint NoSingletonClass(Point@0x1008)
PatchPoint MethodRedefined(Point@0x1008, y@0x1158, cme:0x1160)
- v262:CShape = LoadField v99, :shape_id@0x1090
- v263:CShape[0x1095] = GuardBitEquals v262, CShape(0x1095) recompile
- v264:BasicObject = LoadField v99, :@y@0x1094
+ v260:CShape = LoadField v97, :shape_id@0x1090
+ v261:CShape[0x1095] = GuardBitEquals v260, CShape(0x1095) recompile
+ v262:BasicObject = LoadField v97, :@y@0x1094
PatchPoint MethodRedefined(Integer@0x1128, ==@0x10a0, cme:0x1130)
- v267:Fixnum = GuardType v222, Fixnum recompile
- v268:Fixnum = GuardType v264, Fixnum
- v269:BoolExact = FixnumEq v267, v268
- Jump bb18(v269)
- bb18(v234:BoolExact):
+ v265:Fixnum = GuardType v220, Fixnum recompile
+ v266:Fixnum = GuardType v262, Fixnum
+ v267:BoolExact = FixnumEq v265, v266
+ Jump bb18(v267)
+ bb18(v232:BoolExact):
CheckInterrupts
PopInlineFrame
- Return v234
+ Return v232
");
}
diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs
index edf7389b714929..444e81f0751fa7 100644
--- a/zjit/src/hir/tests.rs
+++ b/zjit/src/hir/tests.rs
@@ -476,22 +476,21 @@ pub(crate) mod hir_build_tests {
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
v14:NilClass = Const Value(nil)
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1008, Integer)
- v20:ClassSubclass[Integer@0x1010] = Const Value(VALUE(0x1010))
- v22:BasicObject = CheckMatch v10, v20, CASE
- v24:CBool = Test v22
- v25:Truthy = RefineType v22, Truthy
- CondBranch v24, bb4(v9, v10, v14, v10), bb5()
- bb4(v37:BasicObject, v38:BasicObject, v39:NilClass, v40:BasicObject):
- v45:Fixnum[1] = Const Value(1)
- CheckInterrupts
- Return v45
+ v19:ClassSubclass[Integer@0x1010] = Const Value(VALUE(0x1010))
+ v21:BasicObject = CheckMatch v10, v19, CASE
+ v23:CBool = Test v21
+ v24:Truthy = RefineType v21, Truthy
+ CondBranch v23, bb4(v9, v10, v14, v10), bb5()
+ bb4(v36:BasicObject, v37:BasicObject, v38:NilClass, v39:BasicObject):
+ v44:Fixnum[1] = Const Value(1)
+ CheckInterrupts
+ Return v44
bb5():
- v27:Falsy = RefineType v22, Falsy
- v32:Fixnum[2] = Const Value(2)
+ v26:Falsy = RefineType v21, Falsy
+ v31:Fixnum[2] = Const Value(2)
CheckInterrupts
- Return v32
+ Return v31
");
}
@@ -2211,9 +2210,8 @@ pub(crate) mod hir_build_tests {
PatchPoint NoEPEscape(test)
v18:CPtr = LoadSP
v19:BasicObject = LoadField v18, :block@0x1000
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1030, ::RubyVM::ZJIT)
- v25:ModuleSubclass[RubyVM::ZJIT@0x1038] = Const Value(VALUE(0x1038))
+ v24:ModuleSubclass[RubyVM::ZJIT@0x1038] = Const Value(VALUE(0x1038))
SideExit DirectiveInduced
");
}
@@ -2252,9 +2250,8 @@ pub(crate) mod hir_build_tests {
v17:Fixnum[1] = Const Value(1)
v22:BasicObject = Send v11, 0x1008, :consume # SendFallbackReason: Uncategorized(send)
PatchPoint NoEPEscape(test)
- PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1030, ::RubyVM::ZJIT)
- v30:ModuleSubclass[RubyVM::ZJIT@0x1038] = Const Value(VALUE(0x1038))
+ v29:ModuleSubclass[RubyVM::ZJIT@0x1038] = Const Value(VALUE(0x1038))
SideExit DirectiveInduced
");
}