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 "); }