diff --git a/benchmark/string_ascii_only_p.yml b/benchmark/string_ascii_only_p.yml new file mode 100644 index 00000000000000..06690527c5bbfe --- /dev/null +++ b/benchmark/string_ascii_only_p.yml @@ -0,0 +1,7 @@ +prelude: | + ascii = "hello world" + utf8 = "héllo wörld 晦" +benchmark: + ascii_only_true: ascii.ascii_only? + ascii_only_false: utf8.ascii_only? +loop_count: 20000000 diff --git a/benchmark/string_valid_encoding_p.yml b/benchmark/string_valid_encoding_p.yml new file mode 100644 index 00000000000000..a78bbaab925892 --- /dev/null +++ b/benchmark/string_valid_encoding_p.yml @@ -0,0 +1,7 @@ +prelude: | + utf8 = "héllo wörld 晦" + broken = "\xff\xfe".dup.force_encoding("UTF-8") +benchmark: + valid_enc_true: utf8.valid_encoding? + valid_enc_false: broken.valid_encoding? +loop_count: 20000000 diff --git a/common.mk b/common.mk index 5b16d87e0e479e..3c32e9fb70b693 100644 --- a/common.mk +++ b/common.mk @@ -1199,6 +1199,7 @@ BUILTIN_RB_SRCS = \ $(srcdir)/kernel.rb \ $(srcdir)/pathname_builtin.rb \ $(srcdir)/ractor.rb \ + $(srcdir)/string.rb \ $(srcdir)/symbol.rb \ $(srcdir)/timev.rb \ $(srcdir)/thread_sync.rb \ diff --git a/depend b/depend index 7ae13bd63d6eb3..493858c5e05b87 100644 --- a/depend +++ b/depend @@ -17718,6 +17718,7 @@ string.$(OBJEXT): {$(VPATH)}backward/2/limits.h string.$(OBJEXT): {$(VPATH)}backward/2/long_long.h string.$(OBJEXT): {$(VPATH)}backward/2/stdalign.h string.$(OBJEXT): {$(VPATH)}backward/2/stdarg.h +string.$(OBJEXT): {$(VPATH)}builtin.h string.$(OBJEXT): {$(VPATH)}config.h string.$(OBJEXT): {$(VPATH)}constant.h string.$(OBJEXT): {$(VPATH)}debug_counter.h @@ -17896,6 +17897,7 @@ string.$(OBJEXT): {$(VPATH)}rubyparser.h string.$(OBJEXT): {$(VPATH)}shape.h string.$(OBJEXT): {$(VPATH)}st.h string.$(OBJEXT): {$(VPATH)}string.c +string.$(OBJEXT): {$(VPATH)}string.rbinc string.$(OBJEXT): {$(VPATH)}subst.h string.$(OBJEXT): {$(VPATH)}thread.h string.$(OBJEXT): {$(VPATH)}thread_$(THREAD_MODEL).h diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index e1943b8496bb32..be8ee53a427802 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -1448,7 +1448,7 @@ rsock_revlookup_flag(VALUE revlookup, int *norevlookup) id = SYM2ID(revlookup); if (id == id_numeric) return_norevlookup(1); if (id == id_hostname) return_norevlookup(0); - rb_raise(rb_eArgError, "invalid reverse_lookup flag: :%s", rb_id2name(id)); + rb_raise(rb_eArgError, "invalid reverse_lookup flag: :%"PRIsVALUE, rb_sym2str(revlookup)); } return 0; #undef return_norevlookup diff --git a/inits.c b/inits.c index 4988fa09d0b6f6..1be4916e87b357 100644 --- a/inits.c +++ b/inits.c @@ -105,6 +105,7 @@ rb_call_builtin_inits(void) BUILTIN(warning); BUILTIN(array); BUILTIN(hash); + BUILTIN(string); BUILTIN(symbol); BUILTIN(timev); BUILTIN(thread_sync); diff --git a/lib/bundler/spec_set.rb b/lib/bundler/spec_set.rb index ae5e5cbaa9852d..47306b7104fe1b 100644 --- a/lib/bundler/spec_set.rb +++ b/lib/bundler/spec_set.rb @@ -5,7 +5,7 @@ module Bundler class SpecSet include Enumerable - include TSort + include Gem::TSort def initialize(specs) @specs = specs @@ -327,7 +327,7 @@ def valid_dependencies?(s) def sorted @sorted ||= ([lookup["rake"]&.first] + tsort).compact.uniq - rescue TSort::Cyclic => error + rescue Gem::TSort::Cyclic => error cgems = extract_circular_gems(error) raise CyclicDependencyError, "Your bundle requires gems that depend" \ " on each other, creating an infinite loop. Please remove either" \ diff --git a/lib/bundler/vendor/tsort/lib/tsort.rb b/lib/bundler/vendor/tsort/lib/tsort.rb deleted file mode 100644 index cf8731f7608dcb..00000000000000 --- a/lib/bundler/vendor/tsort/lib/tsort.rb +++ /dev/null @@ -1,455 +0,0 @@ -# frozen_string_literal: true - -#-- -# tsort.rb - provides a module for topological sorting and strongly connected components. -#++ -# - -# -# Bundler::TSort implements topological sorting using Tarjan's algorithm for -# strongly connected components. -# -# Bundler::TSort is designed to be able to be used with any object which can be -# interpreted as a directed graph. -# -# Bundler::TSort requires two methods to interpret an object as a graph, -# tsort_each_node and tsort_each_child. -# -# * tsort_each_node is used to iterate for all nodes over a graph. -# * tsort_each_child is used to iterate for child nodes of a given node. -# -# The equality of nodes are defined by eql? and hash since -# Bundler::TSort uses Hash internally. -# -# == A Simple Example -# -# The following example demonstrates how to mix the Bundler::TSort module into an -# existing class (in this case, Hash). Here, we're treating each key in -# the hash as a node in the graph, and so we simply alias the required -# #tsort_each_node method to Hash's #each_key method. For each key in the -# hash, the associated value is an array of the node's child nodes. This -# choice in turn leads to our implementation of the required #tsort_each_child -# method, which fetches the array of child nodes and then iterates over that -# array using the user-supplied block. -# -# require 'bundler/vendor/tsort/lib/tsort' -# -# class Hash -# include Bundler::TSort -# alias tsort_each_node each_key -# def tsort_each_child(node, &block) -# fetch(node).each(&block) -# end -# end -# -# {1=>[2, 3], 2=>[3], 3=>[], 4=>[]}.tsort -# #=> [3, 2, 1, 4] -# -# {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}.strongly_connected_components -# #=> [[4], [2, 3], [1]] -# -# == A More Realistic Example -# -# A very simple `make' like tool can be implemented as follows: -# -# require 'bundler/vendor/tsort/lib/tsort' -# -# class Make -# def initialize -# @dep = {} -# @dep.default = [] -# end -# -# def rule(outputs, inputs=[], &block) -# triple = [outputs, inputs, block] -# outputs.each {|f| @dep[f] = [triple]} -# @dep[triple] = inputs -# end -# -# def build(target) -# each_strongly_connected_component_from(target) {|ns| -# if ns.length != 1 -# fs = ns.delete_if {|n| Array === n} -# raise Bundler::TSort::Cyclic.new("cyclic dependencies: #{fs.join ', '}") -# end -# n = ns.first -# if Array === n -# outputs, inputs, block = n -# inputs_time = inputs.map {|f| File.mtime f}.max -# begin -# outputs_time = outputs.map {|f| File.mtime f}.min -# rescue Errno::ENOENT -# outputs_time = nil -# end -# if outputs_time == nil || -# inputs_time != nil && outputs_time <= inputs_time -# sleep 1 if inputs_time != nil && inputs_time.to_i == Time.now.to_i -# block.call -# end -# end -# } -# end -# -# def tsort_each_child(node, &block) -# @dep[node].each(&block) -# end -# include Bundler::TSort -# end -# -# def command(arg) -# print arg, "\n" -# system arg -# end -# -# m = Make.new -# m.rule(%w[t1]) { command 'date > t1' } -# m.rule(%w[t2]) { command 'date > t2' } -# m.rule(%w[t3]) { command 'date > t3' } -# m.rule(%w[t4], %w[t1 t3]) { command 'cat t1 t3 > t4' } -# m.rule(%w[t5], %w[t4 t2]) { command 'cat t4 t2 > t5' } -# m.build('t5') -# -# == Bugs -# -# * 'tsort.rb' is wrong name because this library uses -# Tarjan's algorithm for strongly connected components. -# Although 'strongly_connected_components.rb' is correct but too long. -# -# == References -# -# R. E. Tarjan, "Depth First Search and Linear Graph Algorithms", -# SIAM Journal on Computing, Vol. 1, No. 2, pp. 146-160, June 1972. -# - -module Bundler::TSort - - VERSION = "0.2.0" - - class Cyclic < StandardError - end - - # Returns a topologically sorted array of nodes. - # The array is sorted from children to parents, i.e. - # the first element has no child and the last node has no parent. - # - # If there is a cycle, Bundler::TSort::Cyclic is raised. - # - # class G - # include Bundler::TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # p graph.tsort #=> [4, 2, 3, 1] - # - # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) - # p graph.tsort # raises Bundler::TSort::Cyclic - # - def tsort - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - Bundler::TSort.tsort(each_node, each_child) - end - - # Returns a topologically sorted array of nodes. - # The array is sorted from children to parents, i.e. - # the first element has no child and the last node has no parent. - # - # The graph is represented by _each_node_ and _each_child_. - # _each_node_ should have +call+ method which yields for each node in the graph. - # _each_child_ should have +call+ method which takes a node argument and yields for each child node. - # - # If there is a cycle, Bundler::TSort::Cyclic is raised. - # - # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # p Bundler::TSort.tsort(each_node, each_child) #=> [4, 2, 3, 1] - # - # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # p Bundler::TSort.tsort(each_node, each_child) # raises Bundler::TSort::Cyclic - # - def self.tsort(each_node, each_child) - tsort_each(each_node, each_child).to_a - end - - # The iterator version of the #tsort method. - # obj.tsort_each is similar to obj.tsort.each, but - # modification of _obj_ during the iteration may lead to unexpected results. - # - # #tsort_each returns +nil+. - # If there is a cycle, Bundler::TSort::Cyclic is raised. - # - # class G - # include Bundler::TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # graph.tsort_each {|n| p n } - # #=> 4 - # # 2 - # # 3 - # # 1 - # - def tsort_each(&block) # :yields: node - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - Bundler::TSort.tsort_each(each_node, each_child, &block) - end - - # The iterator version of the Bundler::TSort.tsort method. - # - # The graph is represented by _each_node_ and _each_child_. - # _each_node_ should have +call+ method which yields for each node in the graph. - # _each_child_ should have +call+ method which takes a node argument and yields for each child node. - # - # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # Bundler::TSort.tsort_each(each_node, each_child) {|n| p n } - # #=> 4 - # # 2 - # # 3 - # # 1 - # - def self.tsort_each(each_node, each_child) # :yields: node - return to_enum(__method__, each_node, each_child) unless block_given? - - each_strongly_connected_component(each_node, each_child) {|component| - if component.size == 1 - yield component.first - else - raise Cyclic.new("topological sort failed: #{component.inspect}") - end - } - end - - # Returns strongly connected components as an array of arrays of nodes. - # The array is sorted from children to parents. - # Each elements of the array represents a strongly connected component. - # - # class G - # include Bundler::TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # p graph.strongly_connected_components #=> [[4], [2], [3], [1]] - # - # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) - # p graph.strongly_connected_components #=> [[4], [2, 3], [1]] - # - def strongly_connected_components - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - Bundler::TSort.strongly_connected_components(each_node, each_child) - end - - # Returns strongly connected components as an array of arrays of nodes. - # The array is sorted from children to parents. - # Each elements of the array represents a strongly connected component. - # - # The graph is represented by _each_node_ and _each_child_. - # _each_node_ should have +call+ method which yields for each node in the graph. - # _each_child_ should have +call+ method which takes a node argument and yields for each child node. - # - # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # p Bundler::TSort.strongly_connected_components(each_node, each_child) - # #=> [[4], [2], [3], [1]] - # - # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # p Bundler::TSort.strongly_connected_components(each_node, each_child) - # #=> [[4], [2, 3], [1]] - # - def self.strongly_connected_components(each_node, each_child) - each_strongly_connected_component(each_node, each_child).to_a - end - - # The iterator version of the #strongly_connected_components method. - # obj.each_strongly_connected_component is similar to - # obj.strongly_connected_components.each, but - # modification of _obj_ during the iteration may lead to unexpected results. - # - # #each_strongly_connected_component returns +nil+. - # - # class G - # include Bundler::TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # graph.each_strongly_connected_component {|scc| p scc } - # #=> [4] - # # [2] - # # [3] - # # [1] - # - # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) - # graph.each_strongly_connected_component {|scc| p scc } - # #=> [4] - # # [2, 3] - # # [1] - # - def each_strongly_connected_component(&block) # :yields: nodes - each_node = method(:tsort_each_node) - each_child = method(:tsort_each_child) - Bundler::TSort.each_strongly_connected_component(each_node, each_child, &block) - end - - # The iterator version of the Bundler::TSort.strongly_connected_components method. - # - # The graph is represented by _each_node_ and _each_child_. - # _each_node_ should have +call+ method which yields for each node in the graph. - # _each_child_ should have +call+ method which takes a node argument and yields for each child node. - # - # g = {1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # Bundler::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } - # #=> [4] - # # [2] - # # [3] - # # [1] - # - # g = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_node = lambda {|&b| g.each_key(&b) } - # each_child = lambda {|n, &b| g[n].each(&b) } - # Bundler::TSort.each_strongly_connected_component(each_node, each_child) {|scc| p scc } - # #=> [4] - # # [2, 3] - # # [1] - # - def self.each_strongly_connected_component(each_node, each_child) # :yields: nodes - return to_enum(__method__, each_node, each_child) unless block_given? - - id_map = {} - stack = [] - each_node.call {|node| - unless id_map.include? node - each_strongly_connected_component_from(node, each_child, id_map, stack) {|c| - yield c - } - end - } - nil - end - - # Iterates over strongly connected component in the subgraph reachable from - # _node_. - # - # Return value is unspecified. - # - # #each_strongly_connected_component_from doesn't call #tsort_each_node. - # - # class G - # include Bundler::TSort - # def initialize(g) - # @g = g - # end - # def tsort_each_child(n, &b) @g[n].each(&b) end - # def tsort_each_node(&b) @g.each_key(&b) end - # end - # - # graph = G.new({1=>[2, 3], 2=>[4], 3=>[2, 4], 4=>[]}) - # graph.each_strongly_connected_component_from(2) {|scc| p scc } - # #=> [4] - # # [2] - # - # graph = G.new({1=>[2], 2=>[3, 4], 3=>[2], 4=>[]}) - # graph.each_strongly_connected_component_from(2) {|scc| p scc } - # #=> [4] - # # [2, 3] - # - def each_strongly_connected_component_from(node, id_map={}, stack=[], &block) # :yields: nodes - Bundler::TSort.each_strongly_connected_component_from(node, method(:tsort_each_child), id_map, stack, &block) - end - - # Iterates over strongly connected components in a graph. - # The graph is represented by _node_ and _each_child_. - # - # _node_ is the first node. - # _each_child_ should have +call+ method which takes a node argument - # and yields for each child node. - # - # Return value is unspecified. - # - # #Bundler::TSort.each_strongly_connected_component_from is a class method and - # it doesn't need a class to represent a graph which includes Bundler::TSort. - # - # graph = {1=>[2], 2=>[3, 4], 3=>[2], 4=>[]} - # each_child = lambda {|n, &b| graph[n].each(&b) } - # Bundler::TSort.each_strongly_connected_component_from(1, each_child) {|scc| - # p scc - # } - # #=> [4] - # # [2, 3] - # # [1] - # - def self.each_strongly_connected_component_from(node, each_child, id_map={}, stack=[]) # :yields: nodes - return to_enum(__method__, node, each_child, id_map, stack) unless block_given? - - minimum_id = node_id = id_map[node] = id_map.size - stack_length = stack.length - stack << node - - each_child.call(node) {|child| - if id_map.include? child - child_id = id_map[child] - minimum_id = child_id if child_id && child_id < minimum_id - else - sub_minimum_id = - each_strongly_connected_component_from(child, each_child, id_map, stack) {|c| - yield c - } - minimum_id = sub_minimum_id if sub_minimum_id < minimum_id - end - } - - if node_id == minimum_id - component = stack.slice!(stack_length .. -1) - component.each {|n| id_map[n] = nil} - yield component - end - - minimum_id - end - - # Should be implemented by a extended class. - # - # #tsort_each_node is used to iterate for all nodes over a graph. - # - def tsort_each_node # :yields: node - raise NotImplementedError.new - end - - # Should be implemented by a extended class. - # - # #tsort_each_child is used to iterate for child nodes of _node_. - # - def tsort_each_child(node) # :yields: child - raise NotImplementedError.new - end -end diff --git a/lib/bundler/vendored_tsort.rb b/lib/bundler/vendored_tsort.rb index 38aed0b5defe83..8d8ab26ecef91c 100644 --- a/lib/bundler/vendored_tsort.rb +++ b/lib/bundler/vendored_tsort.rb @@ -1,4 +1,8 @@ # frozen_string_literal: true -module Bundler; end -require_relative "vendor/tsort/lib/tsort" +begin + require "rubygems/vendored_tsort" +rescue LoadError + require "tsort" + Gem::TSort = TSort +end diff --git a/re.c b/re.c index 63e2db81ac2570..ee7f3ba3eb3e8c 100644 --- a/re.c +++ b/re.c @@ -3633,11 +3633,15 @@ rb_reg_equal(VALUE re1, VALUE re2) if (re1 == re2) return Qtrue; if (!RB_TYPE_P(re2, T_REGEXP)) return Qfalse; rb_reg_check(re1); rb_reg_check(re2); + + // src is a fstring, so a pointer comparison is enough + RUBY_ASSERT(FL_TEST_RAW(RREGEXP_SRC(re1), RSTRING_FSTR)); + RUBY_ASSERT(FL_TEST_RAW(RREGEXP_SRC(re2), RSTRING_FSTR)); + if (RBOOL(RREGEXP_SRC(re1) != RREGEXP_SRC(re2))) return Qfalse; + if (FL_TEST(re1, KCODE_FIXED) != FL_TEST(re2, KCODE_FIXED)) return Qfalse; if (RREGEXP_PTR(re1)->options != RREGEXP_PTR(re2)->options) return Qfalse; - if (RREGEXP_SRC_LEN(re1) != RREGEXP_SRC_LEN(re2)) return Qfalse; - if (ENCODING_GET(re1) != ENCODING_GET(re2)) return Qfalse; - return RBOOL(memcmp(RREGEXP_SRC_PTR(re1), RREGEXP_SRC_PTR(re2), RREGEXP_SRC_LEN(re1)) == 0); + return RBOOL(ENCODING_GET(re1) == ENCODING_GET(re2)); } /* diff --git a/string.c b/string.c index d1d9da2428a959..074a4a514bbfa3 100644 --- a/string.c +++ b/string.c @@ -11736,14 +11736,7 @@ rb_str_b(VALUE str) return str2; } -/* - * call-seq: - * valid_encoding? -> true or false - * - * :include: doc/string/valid_encoding_p.rdoc - * - */ - +/* Defined as a leaf builtin in string.rb, so this must never raise or call into Ruby. */ static VALUE rb_str_valid_encoding_p(VALUE str) { @@ -11752,18 +11745,7 @@ rb_str_valid_encoding_p(VALUE str) return RBOOL(cr != ENC_CODERANGE_BROKEN); } -/* - * call-seq: - * ascii_only? -> true or false - * - * Returns whether +self+ contains only ASCII characters: - * - * 'abc'.ascii_only? # => true - * "abc\u{6666}".ascii_only? # => false - * - * Related: see {Querying}[rdoc-ref:String@Querying]. - */ - +/* Defined as a leaf builtin in string.rb, so this must never raise or call into Ruby. */ static VALUE rb_str_is_ascii_only_p(VALUE str) { @@ -13053,8 +13035,6 @@ Init_String(void) rb_define_method(rb_cString, "encoding", rb_obj_encoding, 0); /* in encoding.c */ rb_define_method(rb_cString, "force_encoding", rb_str_force_encoding, 1); rb_define_method(rb_cString, "b", rb_str_b, 0); - rb_define_method(rb_cString, "valid_encoding?", rb_str_valid_encoding_p, 0); - rb_define_method(rb_cString, "ascii_only?", rb_str_is_ascii_only_p, 0); /* define UnicodeNormalize module here so that we don't have to look it up */ mUnicodeNormalize = rb_define_module("UnicodeNormalize"); @@ -13107,3 +13087,4 @@ Init_String(void) rb_define_method(rb_cSymbol, "encoding", sym_encoding, 0); } +#include "string.rbinc" diff --git a/string.rb b/string.rb new file mode 100644 index 00000000000000..85e14be72b003d --- /dev/null +++ b/string.rb @@ -0,0 +1,25 @@ +class String + # call-seq: + # valid_encoding? -> true or false + # + # :include: doc/string/valid_encoding_p.rdoc + # + def valid_encoding? + Primitive.attr! :leaf + Primitive.cexpr! 'rb_str_valid_encoding_p(self)' + end + + # call-seq: + # ascii_only? -> true or false + # + # Returns whether +self+ contains only ASCII characters: + # + # 'abc'.ascii_only? # => true + # "abc\u{6666}".ascii_only? # => false + # + # Related: see {Querying}[rdoc-ref:String@Querying]. + def ascii_only? + Primitive.attr! :leaf + Primitive.cexpr! 'rb_str_is_ascii_only_p(self)' + end +end diff --git a/struct.c b/struct.c index 7a592b42c548c8..59ac221b681a0e 100644 --- a/struct.c +++ b/struct.c @@ -1016,7 +1016,7 @@ inspect_struct(VALUE s, VALUE prefix, int recur) slot = RARRAY_AREF(members, i); id = SYM2ID(slot); if (rb_is_local_id(id) || rb_is_const_id(id)) { - rb_str_append(str, rb_id2str(id)); + rb_str_append(str, rb_sym2str(slot)); } else { rb_str_append(str, rb_inspect(slot)); diff --git a/zjit/bindgen/src/main.rs b/zjit/bindgen/src/main.rs index 01aead4914c6ab..3e43d40efd6c74 100644 --- a/zjit/bindgen/src/main.rs +++ b/zjit/bindgen/src/main.rs @@ -80,6 +80,9 @@ fn main() { .allowlist_type("ruby_preserved_encindex") .allowlist_function("rb_class2name") + // Coderange constants (ENC_CODERANGE_*) for inlining String predicates + .allowlist_type("ruby_coderange_type") + // This struct is public to Ruby C extensions .allowlist_type("RBasic") diff --git a/zjit/src/cruby_bindings.inc.rs b/zjit/src/cruby_bindings.inc.rs index 1bbafc315b738d..a54f0982019afd 100644 --- a/zjit/src/cruby_bindings.inc.rs +++ b/zjit/src/cruby_bindings.inc.rs @@ -355,6 +355,12 @@ pub type rb_block_call_func = ::std::option::Option< ) -> VALUE, >; pub type rb_block_call_func_t = rb_block_call_func; +pub const RUBY_ENC_CODERANGE_UNKNOWN: ruby_coderange_type = 0; +pub const RUBY_ENC_CODERANGE_7BIT: ruby_coderange_type = 1048576; +pub const RUBY_ENC_CODERANGE_VALID: ruby_coderange_type = 2097152; +pub const RUBY_ENC_CODERANGE_BROKEN: ruby_coderange_type = 3145728; +pub const RUBY_ENC_CODERANGE_MASK: ruby_coderange_type = 3145728; +pub type ruby_coderange_type = u32; pub const RUBY_ENCODING_INLINE_MAX: ruby_encoding_consts = 127; pub const RUBY_ENCODING_SHIFT: ruby_encoding_consts = 22; pub const RUBY_ENCODING_MASK: ruby_encoding_consts = 532676608; diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 68c0ea4d9014ee..616808f84c88ab 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -222,9 +222,6 @@ pub fn init() -> Annotations { annotate!(rb_cString, "<<", inline_string_append); annotate!(rb_cString, "==", inline_string_eq); // Not elidable; has a side effect of setting the encoding if ENC_CODERANGE_UNKNOWN. - // TOOD(max): Turn this into a load/compare. Will need to side-exit or do the full call if - // ENC_CODERANGE_UNKNOWN. - annotate!(rb_cString, "ascii_only?", types::BoolExact, no_gc, leaf); annotate!(rb_cModule, "name", types::StringExact.union(types::NilClass), no_gc, leaf, elidable); annotate!(rb_cModule, "===", inline_module_eqq, types::BoolExact, no_gc, leaf); annotate!(rb_cArray, "length", inline_array_length, types::Fixnum, no_gc, leaf, elidable); @@ -291,6 +288,8 @@ pub fn init() -> Annotations { annotate_builtin!(rb_mKernel, "frozen?", types::BoolExact); annotate_builtin!(rb_cSymbol, "name", types::StringExact); annotate_builtin!(rb_cSymbol, "to_s", types::StringExact); + annotate_builtin!(rb_cString, "ascii_only?", inline_string_ascii_only_p, types::BoolExact, no_gc, leaf); + annotate_builtin!(rb_cString, "valid_encoding?", inline_string_valid_encoding_p, types::BoolExact, no_gc, leaf); // Array iteration builtins (used in with_jit Array#each, map, select, find) builtin_funcs.insert(rb_jit_fixnum_inc as *mut c_void, FnProperties { inline: inline_fixnum_inc, return_type: types::Fixnum, ..Default::default() }); @@ -551,6 +550,36 @@ fn inline_string_empty_p(fun: &mut hir::Function, block: hir::BlockId, recv: hir Some(result) } +// Load self's cached coderange (flags & MASK), guarding it's been computed +// (UNKNOWN is 0, so side-exit there and let the builtin scan the string). +fn guard_string_coderange(fun: &mut hir::Function, block: hir::BlockId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + use crate::hir::SideExitReason; + let &[recv] = args else { return None; }; + if !fun.likely_a(recv, types::String, state) { return None; } + let recv = fun.coerce_to(block, recv, types::String, state); + let flags = fun.load_rbasic_flags(block, recv); + let mask = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CUInt64(RUBY_ENC_CODERANGE_MASK.into()) }); + let cr = fun.push_insn(block, hir::Insn::IntAnd { left: flags, right: mask }); + let min_known = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(RUBY_ENC_CODERANGE_7BIT.into()) }); + Some(fun.push_insn(block, hir::Insn::GuardGreaterEq { left: cr, right: min_known, reason: SideExitReason::GuardGreaterEq, state })) +} + +// Inlines String#ascii_only? (rb_str_is_ascii_only_p): coderange == 7BIT. +fn inline_string_ascii_only_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + let cr = guard_string_coderange(fun, block, args, state)?; + let seven_bit = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(RUBY_ENC_CODERANGE_7BIT.into()) }); + let is_7bit = fun.push_insn(block, hir::Insn::IsBitEqual { left: cr, right: seven_bit }); + Some(fun.push_insn(block, hir::Insn::BoxBool { val: is_7bit })) +} + +// Inlines String#valid_encoding? (rb_str_valid_encoding_p): coderange != BROKEN. +fn inline_string_valid_encoding_p(fun: &mut hir::Function, block: hir::BlockId, _recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { + let cr = guard_string_coderange(fun, block, args, state)?; + let broken = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(RUBY_ENC_CODERANGE_BROKEN.into()) }); + let is_valid = fun.push_insn(block, hir::Insn::IsBitNotEqual { left: cr, right: broken }); + Some(fun.push_insn(block, hir::Insn::BoxBool { val: is_valid })) +} + fn inline_string_append(fun: &mut hir::Function, block: hir::BlockId, recv: hir::InsnId, args: &[hir::InsnId], state: hir::InsnId) -> Option { let &[other] = args else { return None; }; // Inline only StringExact << String, which matches original type check from diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 0e61035a0cee72..ac48e538227090 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1913,6 +1913,17 @@ static REGEXP_FLAGS: &[(u32, &str)] = &[ impl<'a> std::fmt::Display for InsnPrinter<'a> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + macro_rules! write_separated { + ($f:expr, $start:expr, $sep: expr, $vec:expr) => { + { + let mut sep = $start; + for item in $vec { + write!($f, "{sep}{item}")?; + sep = $sep; + } + } + }; + } match &self.inner { Insn::Comment { message } => write!(f, "# {message}"), Insn::Const { val } => { write!(f, "Const {}", val.print(self.ptr_map)) } @@ -1920,20 +1931,12 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::LoadArg { idx, id, .. } => { write!(f, "LoadArg :{id}@{idx}") } Insn::Entries { targets } => { write!(f, "Entries")?; - let mut prefix = " "; - for target in targets { - write!(f, "{prefix}{target}")?; - prefix = ", "; - } + write_separated!(f, " ", ", ", targets); Ok(()) } Insn::NewArray { elements, .. } => { write!(f, "NewArray")?; - let mut prefix = " "; - for element in elements { - write!(f, "{prefix}{element}")?; - prefix = ", "; - } + write_separated!(f, " ", ", ", elements); Ok(()) } Insn::ArrayAref { array, index, .. } => { @@ -1970,38 +1973,22 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { } Insn::ArrayMax { elements, .. } => { write!(f, "ArrayMax")?; - let mut prefix = " "; - for element in elements { - write!(f, "{prefix}{element}")?; - prefix = ", "; - } + write_separated!(f, " ", ", ", elements); Ok(()) } Insn::ArrayMin { elements, .. } => { write!(f, "ArrayMin")?; - let mut prefix = " "; - for element in elements { - write!(f, "{prefix}{element}")?; - prefix = ", "; - } + write_separated!(f, " ", ", ", elements); Ok(()) } Insn::ArrayHash { elements, .. } => { write!(f, "ArrayHash")?; - let mut prefix = " "; - for element in elements { - write!(f, "{prefix}{element}")?; - prefix = ", "; - } + write_separated!(f, " ", ", ", elements); Ok(()) } Insn::ArrayInclude { elements, target, .. } => { write!(f, "ArrayInclude")?; - let mut prefix = " "; - for element in elements { - write!(f, "{prefix}{element}")?; - prefix = ", "; - } + write_separated!(f, " ", ", ", elements); write!(f, " | {target}") } Insn::ArrayPackBuffer { elements, fmt, buffer, .. } => { @@ -2030,12 +2017,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::StringCopy { val, .. } => { write!(f, "StringCopy {val}") } Insn::StringConcat { strings, .. } => { write!(f, "StringConcat")?; - let mut prefix = " "; - for string in strings { - write!(f, "{prefix}{string}")?; - prefix = ", "; - } - + write_separated!(f, " ", ", ", strings); Ok(()) } Insn::StringGetbyte { string, index, .. } => { @@ -2055,11 +2037,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { } Insn::ToRegexp { values, opt, .. } => { write!(f, "ToRegexp")?; - let mut prefix = " "; - for value in values { - write!(f, "{prefix}{value}")?; - prefix = ", "; - } + write_separated!(f, " ", ", ", values); let opt = *opt as u32; if opt != 0 { @@ -2089,16 +2067,12 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { let blockiseq = block.map(|bh| match bh { BlockHandler::BlockIseq(iseq) => iseq, BlockHandler::BlockArg => unreachable!() }); let method_name = unsafe { (**cme).called_id }; write!(f, "SendDirect {recv}, {:p}, :{} ({:?})", self.ptr_map.map_ptr(&blockiseq), method_name, self.ptr_map.map_ptr(iseq))?; - for arg in args { - write!(f, ", {arg}")?; - } + write_separated!(f, ", ", ", ", args); Ok(()) } Insn::PushInlineFrame { recv, iseq, args, .. } => { write!(f, "PushInlineFrame {recv} ({:?})", self.ptr_map.map_ptr(iseq))?; - for arg in args { - write!(f, ", {arg}")?; - } + write_separated!(f, ", ", ", ", args); Ok(()) } Insn::PopInlineFrame { .. } => { @@ -2116,56 +2090,42 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { None => write!(f, "Send {recv}, :{}", ruby_call_method_name(*cd))?, } - for arg in args { - write!(f, ", {arg}")?; - } + write_separated!(f, ", ", ", ", args); write!(f, " # SendFallbackReason: {reason}")?; Ok(()) } Insn::SendForward { recv, cd, args, blockiseq, reason, .. } => { write!(f, "SendForward {recv}, {:p}, :{}", self.ptr_map.map_ptr(blockiseq), ruby_call_method_name(*cd))?; - for arg in args { - write!(f, ", {arg}")?; - } + write_separated!(f, ", ", ", ", args); write!(f, " # SendFallbackReason: {reason}")?; Ok(()) } Insn::InvokeSuper { recv, blockiseq, args, reason, .. } => { write!(f, "InvokeSuper {recv}, {:p}", self.ptr_map.map_ptr(blockiseq))?; - for arg in args { - write!(f, ", {arg}")?; - } + write_separated!(f, ", ", ", ", args); write!(f, " # SendFallbackReason: {reason}")?; Ok(()) } Insn::InvokeSuperForward { recv, blockiseq, args, reason, .. } => { write!(f, "InvokeSuperForward {recv}, {:p}", self.ptr_map.map_ptr(blockiseq))?; - for arg in args { - write!(f, ", {arg}")?; - } + write_separated!(f, ", ", ", ", args); write!(f, " # SendFallbackReason: {reason}")?; Ok(()) } Insn::InvokeBlock { args, reason, .. } => { write!(f, "InvokeBlock")?; - for arg in args { - write!(f, ", {arg}")?; - } + write_separated!(f, " ", ", ", args); write!(f, " # SendFallbackReason: {reason}")?; Ok(()) } Insn::InvokeBlockIfunc { block_handler, args, .. } => { write!(f, "InvokeBlockIfunc {block_handler}")?; - for arg in args { - write!(f, ", {arg}")?; - } + write_separated!(f, ", ", ", ", args); Ok(()) } Insn::InvokeProc { recv, args, kw_splat, .. } => { write!(f, "InvokeProc {recv}")?; - for arg in args { - write!(f, ", {arg}")?; - } + write_separated!(f, ", ", ", ", args); if *kw_splat { write!(f, ", kw_splat")?; } @@ -2177,9 +2137,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { if *leaf { " leaf" } else { "" }, // e.g. Code that use `Primitive.cexpr!`. From BUILTIN_INLINE_PREFIX. if bf_name.starts_with("_bi") { "" } else { bf_name })?; - for arg in args { - write!(f, ", {arg}")?; - } + write_separated!(f, ", ", ", ", args); Ok(()) } &Insn::EntryPoint { jit_entry_idx: Some(idx) } => write!(f, "EntryPoint JIT({idx})"), @@ -2252,16 +2210,12 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::CCall { cfunc, recv, args, name, owner, return_type: _, elidable: _ } => { let display_name = if *owner == Qnil { name.contents_lossy().to_string() } else { qualified_method_name(*owner, *name) }; write!(f, "CCall {recv}, :{}@{:p}", display_name, self.ptr_map.map_ptr(cfunc))?; - for arg in args { - write!(f, ", {arg}")?; - } + write_separated!(f, ", ", ", ", args); Ok(()) }, Insn::CCallWithFrame { cfunc, recv, args, name, cme, block, .. } => { write!(f, "CCallWithFrame {recv}, :{}@{:p}", qualified_method_name(unsafe { (**cme).owner }, *name), self.ptr_map.map_ptr(cfunc))?; - for arg in args { - write!(f, ", {arg}")?; - } + write_separated!(f, ", ", ", ", args); match block { Some(BlockHandler::BlockIseq(blockiseq)) => write!(f, ", block={:p}", self.ptr_map.map_ptr(blockiseq))?, @@ -2273,9 +2227,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { }, Insn::CCallVariadic { cfunc, recv, args, name, cme, .. } => { write!(f, "CCallVariadic {recv}, :{}@{:p}", qualified_method_name(unsafe { (**cme).owner }, *name), self.ptr_map.map_ptr(cfunc))?; - for arg in args { - write!(f, ", {arg}")?; - } + write_separated!(f, ", ", ", ", args); Ok(()) }, Insn::IncrCounterPtr { .. } => write!(f, "IncrCounterPtr"), @@ -3942,14 +3894,25 @@ impl Function { } self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); + + let id = unsafe { get_cme_def_body_attr_id(cme) }; if let Some(profiled_type) = profiled_type { let argc = unsafe { vm_ci_argc(ci) } as i32; recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state, recompile: Some(Recompile::ProfileSend{ argc }) }); - } - let id = unsafe { get_cme_def_body_attr_id(cme) }; - let getivar = self.push_insn(block, Insn::GetIvar { self_val: recv, id, ic: std::ptr::null(), state }); - self.make_equal_to(insn_id, getivar); + let replacement = self.try_emit_optimized_getivar(block, recv, id, profiled_type, state).unwrap_or_else(|counter| { + self.count(block, counter); + self.push_insn(block, Insn::GetIvar { self_val: recv, id, ic: std::ptr::null(), state }) + }); + self.make_equal_to(insn_id, replacement); + } else { + // No shape information, just static class information + let resolution = self.resolve_receiver_type_from_profile(recv, state); + let counter = Self::getivar_fallback_reason(resolution, std::ptr::null()); + self.count(block, counter); + let getivar = self.push_insn(block, Insn::GetIvar { self_val: recv, id, ic: std::ptr::null(), state }); + self.make_equal_to(insn_id, getivar); + } } else if let (false, VM_METHOD_TYPE_ATTRSET, &[val]) = (has_block, def_type, args.as_slice()) { // Check if we're accessing ivars of a Class or Module object as they require single-ractor mode. // We omit gen_prepare_non_leaf_call on gen_getivar, so it's unsafe to raise for multi-ractor mode. @@ -3959,13 +3922,22 @@ impl Function { } self.push_insn(block, Insn::PatchPoint { invariant: Invariant::MethodRedefined { klass, method: mid, cme }, state }); + let id = unsafe { get_cme_def_body_attr_id(cme) }; if let Some(profiled_type) = profiled_type { let argc = unsafe { vm_ci_argc(ci) } as i32; + // TODO: attr_writer SetIvar has a null inline cache and may target a receiver + // operand other than CFP self. Support it with a reprofile strategy that + // profiles the receiver operand even after the send insn has finished profiling. + let recompile = None; recv = self.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state, recompile: Some(Recompile::ProfileSend{ argc }) }); + self.try_emit_optimized_setivar(block, recv, id, val, profiled_type, state, recompile).unwrap_or_else(|counter| { + self.count(block, counter); + self.push_insn(block, Insn::SetIvar { self_val: recv, id, ic: std::ptr::null(), val, state }); + }); + } else { + // No shape information, just static class information + self.push_insn(block, Insn::SetIvar { self_val: recv, id, ic: std::ptr::null(), val, state }); } - let id = unsafe { get_cme_def_body_attr_id(cme) }; - - self.push_insn(block, Insn::SetIvar { self_val: recv, id, ic: std::ptr::null(), val, state }); self.make_equal_to(insn_id, val); } else if !has_block && def_type == VM_METHOD_TYPE_OPTIMIZED { let opt_type: OptimizedMethodType = unsafe { get_cme_def_body_optimized_type(cme) }.into(); @@ -4952,137 +4924,105 @@ impl Function { } } - fn optimize_getivar(&mut self) { - for block in self.reverse_post_order() { - let old_insns = std::mem::take(&mut self.blocks[block.0].insns); - assert!(self.blocks[block.0].insns.is_empty()); - for insn_id in old_insns { - match self.find(insn_id) { - Insn::GetIvar { self_val, id, ic, state } => { - let Some(recv_type) = self.profiled_type_of_at(self_val, state) else { - let resolution = self.resolve_receiver_type_from_profile(self_val, state); - let counter = Self::getivar_fallback_reason(resolution, ic); - self.count(block, counter); - self.push_insn_id(block, insn_id); continue; - }; - if recv_type.flags().is_immediate() { - // Instance variable lookups on immediate values are always nil - self.count(block, Counter::getivar_fallback_immediate); - self.push_insn_id(block, insn_id); continue; - } - assert!(recv_type.shape().is_valid()); - if recv_type.shape().is_complex() { - // too-complex shapes can't use index access - self.count(block, Counter::getivar_fallback_complex); - self.push_insn_id(block, insn_id); continue; - } - if self.policy.no_side_exits { - // On the final version, skip GetIvar shape specialization. - // iseq_to_hir already generates polymorphic branches with a - // GetIvar C call fallback for getinstancevariable, so we don't - // need to wrap it again here. - self.push_insn_id(block, insn_id); continue; - } - let self_val = self.guard_heap(block, self_val, state); - let shape = self.load_shape(block, self_val); - self.guard_shape(block, shape, recv_type.shape(), state, Some(Recompile::ProfileSelf)); - let replacement = self.load_ivar(block, self_val, recv_type, id); - self.make_equal_to(insn_id, replacement); - } - Insn::SetIvar { self_val, id, val, state, ic } => { - let Some(recv_type) = self.profiled_type_of_at(self_val, state) else { - // No (monomorphic/skewed polymorphic) profile info - self.count(block, Counter::setivar_fallback_not_monomorphic); - self.push_insn_id(block, insn_id); continue; - }; - if recv_type.flags().is_immediate() { - // Instance variable lookups on immediate values are always nil - self.count(block, Counter::setivar_fallback_immediate); - self.push_insn_id(block, insn_id); continue; - } - assert!(recv_type.shape().is_valid()); - if !recv_type.flags().is_t_object() { - // Check if the receiver is a T_OBJECT - self.count(block, Counter::setivar_fallback_not_t_object); - self.push_insn_id(block, insn_id); continue; - } - if recv_type.shape().is_complex() { - // too-complex shapes can't use index access - self.count(block, Counter::setivar_fallback_complex); - self.push_insn_id(block, insn_id); continue; - } - if recv_type.shape().is_frozen() { - // Can't set ivars on frozen objects - self.count(block, Counter::setivar_fallback_frozen); - self.push_insn_id(block, insn_id); continue; - } - if self.policy.no_side_exits { - // TODO: Support polymorphic SetIvar shape-specialized paths. - // https://github.com/Shopify/ruby/issues/927 - // On the final version, keep the SetIvar fallback instead of another shape guard. - self.push_insn_id(block, insn_id); continue; - } - let mut ivar_index: attr_index_t = 0; - let mut next_shape_id = recv_type.shape(); - if !unsafe { rb_shape_get_iv_index(recv_type.shape().0, id, &mut ivar_index) } { - // Current shape does not contain this ivar; do a shape transition. - let current_shape_id = recv_type.shape(); - let class = recv_type.class(); - // We're only looking at T_OBJECT so ignore all of the imemo stuff. - assert!(recv_type.flags().is_t_object()); - next_shape_id = ShapeId(unsafe { rb_shape_transition_add_ivar_no_warnings(current_shape_id.0, id, class) }); - // If the VM ran out of shapes, or this class generated too many leaf, - // it may be de-optimized into OBJ_COMPLEX_SHAPE (hash-table). - let new_shape_complex = unsafe { rb_jit_shape_complex_p(next_shape_id.0) }; - // TODO(max): Is it OK to bail out here after making a shape transition? - if new_shape_complex { - self.count(block, Counter::setivar_fallback_new_shape_complex); - self.push_insn_id(block, insn_id); continue; - } - let ivar_result = unsafe { rb_shape_get_iv_index(next_shape_id.0, id, &mut ivar_index) }; - assert!(ivar_result, "New shape must have the ivar index"); - let current_capacity = unsafe { rb_jit_shape_capacity(current_shape_id.0) }; - let next_capacity = unsafe { rb_jit_shape_capacity(next_shape_id.0) }; - // If the new shape has a different capacity, or is COMPLEX, we'll have to - // reallocate it. - let needs_extension = next_capacity != current_capacity; - if needs_extension { - self.count(block, Counter::setivar_fallback_new_shape_needs_extension); - self.push_insn_id(block, insn_id); continue; - } - // Fall through to emitting the ivar write - } - let self_val = self.guard_heap(block, self_val, state); - let shape = self.load_shape(block, self_val); - // TODO: attr_writer SetIvar has a null inline cache and may target a receiver - // operand other than CFP self. Support it with a reprofile strategy that - // profiles the receiver operand even after the send insn has finished profiling. - let recompile = if ic.is_null() { None } else { Some(Recompile::ProfileSelf) }; - self.guard_shape(block, shape, recv_type.shape(), state, recompile); - // Current shape contains this ivar - let (ivar_storage, offset) = if recv_type.flags().is_embedded() { - // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h - let offset = ROBJECT_OFFSET_AS_ARY + (SIZEOF_VALUE * ivar_index.to_usize()) as i32; - (self_val, offset) - } else { - let as_heap = self.push_insn(block, Insn::LoadField { recv: self_val, id: FieldName::as_heap, offset: ROBJECT_OFFSET_AS_HEAP_FIELDS, return_type: types::CPtr }); - let offset = SIZEOF_VALUE_I32 * ivar_index as i32; - (as_heap, offset) - }; - self.push_insn(block, Insn::StoreField { recv: ivar_storage, id: id.into(), offset, val }); - self.push_insn(block, Insn::WriteBarrier { recv: self_val, val }); - if next_shape_id != recv_type.shape() { - // Write the new shape ID - let shape_id = self.push_insn(block, Insn::Const { val: Const::CShape(next_shape_id) }); - let shape_id_offset = unsafe { rb_shape_id_offset() }; - self.push_insn(block, Insn::StoreField { recv: self_val, id: FieldName::shape_id, offset: shape_id_offset, val: shape_id }); - } - } - _ => { self.push_insn_id(block, insn_id); } - } - } + fn try_emit_optimized_getivar(&mut self, block: BlockId, self_val: InsnId, id: ID, profiled_type: ProfiledType, state: InsnId) -> Result { + if profiled_type.flags().is_immediate() { + // Instance variable lookups on immediate values are always nil + return Err(Counter::getivar_fallback_immediate); } - crate::stats::trace_compile_phase("infer_types", || self.infer_types()); + assert!(profiled_type.shape().is_valid()); + if profiled_type.shape().is_complex() { + // too-complex shapes can't use index access + return Err(Counter::getivar_fallback_complex); + } + if self.policy.no_side_exits { + // On the final version, skip GetIvar shape specialization. + // iseq_to_hir already generates polymorphic branches with a + // GetIvar C call fallback for getinstancevariable, so we don't + // need to wrap it again here. + return Err(Counter::getivar_fallback_no_side_exits); + } + let self_val = self.guard_heap(block, self_val, state); + let shape = self.load_shape(block, self_val); + self.guard_shape(block, shape, profiled_type.shape(), state, Some(Recompile::ProfileSelf)); + Ok(self.load_ivar(block, self_val, profiled_type, id)) + } + + fn try_emit_optimized_setivar(&mut self, block: BlockId, self_val: InsnId, id: ID, val: InsnId, profiled_type: ProfiledType, state: InsnId, recompile: Option) -> Result<(), Counter> { + if profiled_type.flags().is_immediate() { + // Instance variable lookups on immediate values are always nil + return Err(Counter::setivar_fallback_immediate); + } + if !profiled_type.flags().is_t_object() { + // Check if the receiver is a T_OBJECT + return Err(Counter::setivar_fallback_not_t_object); + } + assert!(profiled_type.shape().is_valid()); + if profiled_type.shape().is_frozen() { + // Can't set ivars on frozen objects + return Err(Counter::setivar_fallback_frozen); + } + if profiled_type.shape().is_complex() { + // too-complex shapes can't use index access + return Err(Counter::setivar_fallback_complex); + } + if self.policy.no_side_exits { + // On the final version, skip GetIvar shape specialization. + // iseq_to_hir already generates polymorphic branches with a + // GetIvar C call fallback for getinstancevariable, so we don't + // need to wrap it again here. + return Err(Counter::setivar_fallback_no_side_exits); + } + + let mut ivar_index: attr_index_t = 0; + let mut next_shape_id = profiled_type.shape(); + if !unsafe { rb_shape_get_iv_index(profiled_type.shape().0, id, &mut ivar_index) } { + // Current shape does not contain this ivar; do a shape transition. + let current_shape_id = profiled_type.shape(); + let class = profiled_type.class(); + // We're only looking at T_OBJECT so ignore all of the imemo stuff. + assert!(profiled_type.flags().is_t_object()); + next_shape_id = ShapeId(unsafe { rb_shape_transition_add_ivar_no_warnings(current_shape_id.0, id, class) }); + // If the VM ran out of shapes, or this class generated too many leaf, + // it may be de-optimized into OBJ_COMPLEX_SHAPE (hash-table). + let new_shape_complex = unsafe { rb_jit_shape_complex_p(next_shape_id.0) }; + // TODO(max): Is it OK to bail out here after making a shape transition? + if new_shape_complex { + return Err(Counter::setivar_fallback_new_shape_complex); + } + let ivar_result = unsafe { rb_shape_get_iv_index(next_shape_id.0, id, &mut ivar_index) }; + assert!(ivar_result, "New shape must have the ivar index"); + let current_capacity = unsafe { rb_jit_shape_capacity(current_shape_id.0) }; + let next_capacity = unsafe { rb_jit_shape_capacity(next_shape_id.0) }; + // If the new shape has a different capacity, or is COMPLEX, we'll have to + // reallocate it. + let needs_extension = next_capacity != current_capacity; + if needs_extension { + return Err(Counter::setivar_fallback_new_shape_needs_extension); + } + // Fall through to emitting the ivar write + } + let self_val = self.guard_heap(block, self_val, state); + let shape = self.load_shape(block, self_val); + self.guard_shape(block, shape, profiled_type.shape(), state, recompile); + // Current shape contains this ivar + let (ivar_storage, offset) = if profiled_type.flags().is_embedded() { + // See ROBJECT_FIELDS() from include/ruby/internal/core/robject.h + let offset = ROBJECT_OFFSET_AS_ARY + (SIZEOF_VALUE * ivar_index.to_usize()) as i32; + (self_val, offset) + } else { + let as_heap = self.push_insn(block, Insn::LoadField { recv: self_val, id: FieldName::as_heap, offset: ROBJECT_OFFSET_AS_HEAP_FIELDS, return_type: types::CPtr }); + let offset = SIZEOF_VALUE_I32 * ivar_index as i32; + (as_heap, offset) + }; + self.push_insn(block, Insn::StoreField { recv: ivar_storage, id: id.into(), offset, val }); + self.push_insn(block, Insn::WriteBarrier { recv: self_val, val }); + if next_shape_id != profiled_type.shape() { + // Write the new shape ID + let shape_id = self.push_insn(block, Insn::Const { val: Const::CShape(next_shape_id) }); + let shape_id_offset = unsafe { rb_shape_id_offset() }; + self.push_insn(block, Insn::StoreField { recv: self_val, id: FieldName::shape_id, offset: shape_id_offset, val: shape_id }); + } + Ok(()) } fn gen_patch_points_for_optimized_ccall(&mut self, block: BlockId, recv_class: VALUE, method_id: ID, cme: *const rb_callable_method_entry_struct, state: InsnId) { @@ -6245,7 +6185,6 @@ impl Function { // Bucket all strength reduction together (type_specialize) => { Counter::compile_hir_strength_reduce_time_ns }; (inline_trivial) => { Counter::compile_hir_strength_reduce_time_ns }; - (optimize_getivar) => { Counter::compile_hir_strength_reduce_time_ns }; (optimize_c_calls) => { Counter::compile_hir_strength_reduce_time_ns }; (convert_no_profile_sends) => { Counter::compile_hir_strength_reduce_time_ns }; // End strength reduction bucket @@ -6295,7 +6234,6 @@ impl Function { // inliner then handles more complex methods that require full inlining. run_pass!(inline_trivial); let did_inline = run_pass!(inline_methods); - run_pass!(optimize_getivar); run_pass!(optimize_c_calls); run_pass!(convert_no_profile_sends); run_pass!(optimize_load_store); @@ -9093,14 +9031,24 @@ fn add_iseq_to_hir( // make the right number of Params block = join_block; } else { - // Possibly monomorphic case; handled in optimize_getivar - let result = fun.push_insn(block, Insn::GetIvar { self_val: self_param, id, ic, state: exit_id }); - state.stack_push(result); + if let Some(profiled_type) = fun.monomorphic_summary(&profiles, self_param, exit_id) { + let result = fun.try_emit_optimized_getivar(block, self_param, id, profiled_type, exit_id).unwrap_or_else(|counter| { + fun.count(block, counter); + fun.push_insn(block, Insn::GetIvar { self_val: self_param, id, ic: std::ptr::null(), state: exit_id }) + }); + state.stack_push(result); + } else { + let resolution = fun.resolve_receiver_type_from_profile(self_param, exit_id); + let counter = Function::getivar_fallback_reason(resolution, ic); + fun.count(block, counter); + let result = fun.push_insn(block, Insn::GetIvar { self_val: self_param, id, ic, state: exit_id }); + state.stack_push(result); + } } } YARVINSN_setinstancevariable => { let id = ID(get_arg(pc, 0).as_u64()); - let ic = get_arg(pc, 1).as_ptr(); + let ic: *const iseq_inline_iv_cache_entry = get_arg(pc, 1).as_ptr(); // Assume single-Ractor mode to omit gen_prepare_non_leaf_call on gen_setivar // TODO: We only really need this if self_val is a class/module if !fun.assume_single_ractor_mode(block, exit_id) { @@ -9109,7 +9057,16 @@ fn add_iseq_to_hir( break; // End the block } let val = state.stack_pop()?; - fun.push_insn(block, Insn::SetIvar { self_val: self_param, id, ic, val, state: exit_id }); + if let Some(profiled_type) = fun.monomorphic_summary(&profiles, self_param, exit_id) { + // TODO(max): Assert ic is never null + let recompile = if ic.is_null() { None } else { Some(Recompile::ProfileSelf) }; + fun.try_emit_optimized_setivar(block, self_param, id, val, profiled_type, exit_id, recompile).unwrap_or_else(|counter| { + fun.count(block, counter); + fun.push_insn(block, Insn::SetIvar { self_val: self_param, id, ic, val, state: exit_id }); + }); + } else { + fun.push_insn(block, Insn::SetIvar { self_val: self_param, id, ic, val, state: exit_id }); + } // SetIvar will raise if self is an immediate. If it raises, we will have // exited JIT code. So upgrade the type within JIT code to a heap object. self_param = fun.push_insn(block, Insn::RefineType { val: self_param, new_type: types::HeapBasicObject }); diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index e60226bb6f91a8..c2b31cb2b1a665 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -6299,11 +6299,11 @@ mod hir_opt_tests { bb3(v6:BasicObject): v10:Fixnum[5] = Const Value(5) PatchPoint SingleRactorMode - v21:HeapBasicObject = GuardType v6, HeapBasicObject - v22:CShape = LoadField v21, :shape_id@0x1000 - v23:CShape[0x1001] = GuardBitEquals v22, CShape(0x1001) recompile - StoreField v21, :@foo@0x1002, v10 - WriteBarrier v21, v10 + v14:HeapBasicObject = GuardType v6, HeapBasicObject + v15:CShape = LoadField v14, :shape_id@0x1000 + v16:CShape[0x1001] = GuardBitEquals v15, CShape(0x1001) recompile + StoreField v14, :@foo@0x1002, v10 + WriteBarrier v14, v10 CheckInterrupts Return v10 "); @@ -6328,13 +6328,13 @@ mod hir_opt_tests { bb3(v6:BasicObject): v10:Fixnum[5] = Const Value(5) PatchPoint SingleRactorMode - v21:HeapBasicObject = GuardType v6, HeapBasicObject - v22:CShape = LoadField v21, :shape_id@0x1000 - v23:CShape[0x1001] = GuardBitEquals v22, CShape(0x1001) recompile - StoreField v21, :@foo@0x1002, v10 - WriteBarrier v21, v10 - v26:CShape[0x1003] = Const CShape(0x1003) - StoreField v21, :shape_id@0x1000, v26 + v14:HeapBasicObject = GuardType v6, HeapBasicObject + v15:CShape = LoadField v14, :shape_id@0x1000 + v16:CShape[0x1001] = GuardBitEquals v15, CShape(0x1001) recompile + StoreField v14, :@foo@0x1002, v10 + WriteBarrier v14, v10 + v19:CShape[0x1003] = Const CShape(0x1003) + StoreField v14, :shape_id@0x1000, v19 CheckInterrupts Return v10 "); @@ -6373,21 +6373,21 @@ mod hir_opt_tests { bb3(v6:BasicObject): v10:Fixnum[1] = Const Value(1) PatchPoint SingleRactorMode - v28:HeapBasicObject = GuardType v6, HeapBasicObject - v29:CShape = LoadField v28, :shape_id@0x1000 - v30:CShape[0x1001] = GuardBitEquals v29, CShape(0x1001) recompile - StoreField v28, :@foo@0x1002, v10 - WriteBarrier v28, v10 - v33:CShape[0x1003] = Const CShape(0x1003) - StoreField v28, :shape_id@0x1000, v33 - v17:Fixnum[2] = Const Value(2) + v13:HeapBasicObject = GuardType v6, HeapBasicObject + v14:CShape = LoadField v13, :shape_id@0x1000 + v15:CShape[0x1001] = GuardBitEquals v14, CShape(0x1001) recompile + StoreField v13, :@foo@0x1002, v10 + WriteBarrier v13, v10 + v18:CShape[0x1003] = Const CShape(0x1003) + StoreField v13, :shape_id@0x1000, v18 + v23:Fixnum[2] = Const Value(2) PatchPoint SingleRactorMode - StoreField v28, :@bar@0x1004, v17 - WriteBarrier v28, v17 - v40:CShape[0x1005] = Const CShape(0x1005) - StoreField v28, :shape_id@0x1000, v40 + StoreField v13, :@bar@0x1004, v23 + WriteBarrier v13, v23 + v32:CShape[0x1005] = Const CShape(0x1005) + StoreField v13, :shape_id@0x1000, v32 CheckInterrupts - Return v17 + Return v23 "); } @@ -8197,11 +8197,11 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018) v23:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] recompile - v26:CShape = LoadField v23, :shape_id@0x1040 - v27:CShape[0x1041] = GuardBitEquals v26, CShape(0x1041) recompile - v28:BasicObject = LoadField v23, :@foo@0x1042 + v25:CShape = LoadField v23, :shape_id@0x1040 + v26:CShape[0x1041] = GuardBitEquals v25, CShape(0x1041) recompile + v27:BasicObject = LoadField v23, :@foo@0x1042 CheckInterrupts - Return v28 + Return v27 "); } @@ -8495,12 +8495,12 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v17:HeapBasicObject = GuardType v6, HeapBasicObject - v18:CShape = LoadField v17, :shape_id@0x1000 - v19:CShape[0x1001] = GuardBitEquals v18, CShape(0x1001) recompile - v20:NilClass = Const Value(nil) + v11:HeapBasicObject = GuardType v6, HeapBasicObject + v12:CShape = LoadField v11, :shape_id@0x1000 + v13:CShape[0x1001] = GuardBitEquals v12, CShape(0x1001) recompile + v14:NilClass = Const Value(nil) CheckInterrupts - Return v20 + Return v14 "); } @@ -8527,12 +8527,12 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v17:HeapBasicObject = GuardType v6, HeapBasicObject - v18:CShape = LoadField v17, :shape_id@0x1000 - v19:CShape[0x1001] = GuardBitEquals v18, CShape(0x1001) recompile - v20:NilClass = Const Value(nil) + v11:HeapBasicObject = GuardType v6, HeapBasicObject + v12:CShape = LoadField v11, :shape_id@0x1000 + v13:CShape[0x1001] = GuardBitEquals v12, CShape(0x1001) recompile + v14:NilClass = Const Value(nil) CheckInterrupts - Return v20 + Return v14 "); } @@ -8703,8 +8703,8 @@ mod hir_opt_tests { v17:Fixnum[5] = Const Value(5) PatchPoint MethodRedefined(C@0x1008, foo=@0x1010, cme:0x1018) v28:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] recompile - v31:CShape = LoadField v28, :shape_id@0x1040 - v32:CShape[0x1041] = GuardBitEquals v31, CShape(0x1041) + v30:CShape = LoadField v28, :shape_id@0x1040 + v31:CShape[0x1041] = GuardBitEquals v30, CShape(0x1041) StoreField v28, :@foo@0x1042, v17 WriteBarrier v28, v17 CheckInterrupts @@ -8733,13 +8733,13 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v17:HeapBasicObject = GuardType v6, HeapBasicObject - v18:CShape = LoadField v17, :shape_id@0x1000 - v19:CShape[0x1001] = GuardBitEquals v18, CShape(0x1001) recompile - v20:RubyValue = LoadField v17, :fields_obj@0x1002 - v21:BasicObject = LoadField v20, :@foo@0x1003 + v11:HeapBasicObject = GuardType v6, HeapBasicObject + v12:CShape = LoadField v11, :shape_id@0x1000 + v13:CShape[0x1001] = GuardBitEquals v12, CShape(0x1001) recompile + v14:RubyValue = LoadField v11, :fields_obj@0x1002 + v15:BasicObject = LoadField v14, :@foo@0x1003 CheckInterrupts - Return v21 + Return v15 "); } @@ -8804,13 +8804,13 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v17:HeapBasicObject = GuardType v6, HeapBasicObject - v18:CShape = LoadField v17, :shape_id@0x1000 - v19:CShape[0x1001] = GuardBitEquals v18, CShape(0x1001) recompile - v20:RubyValue = LoadField v17, :fields_obj@0x1002 - v21:BasicObject = LoadField v20, :@foo@0x1003 + v11:HeapBasicObject = GuardType v6, HeapBasicObject + v12:CShape = LoadField v11, :shape_id@0x1000 + v13:CShape[0x1001] = GuardBitEquals v12, CShape(0x1001) recompile + v14:RubyValue = LoadField v11, :fields_obj@0x1002 + v15:BasicObject = LoadField v14, :@foo@0x1003 CheckInterrupts - Return v21 + Return v15 "); } @@ -8868,13 +8868,13 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v17:HeapBasicObject = GuardType v6, HeapBasicObject - v18:CShape = LoadField v17, :shape_id@0x1000 - v19:CShape[0x1001] = GuardBitEquals v18, CShape(0x1001) recompile - v20:CAttrIndex[0] = Const CAttrIndex(0) - v21:BasicObject = CCall v17, :rb_ivar_get_at_no_ractor_check@0x1008, v20 + v11:HeapBasicObject = GuardType v6, HeapBasicObject + v12:CShape = LoadField v11, :shape_id@0x1000 + v13:CShape[0x1001] = GuardBitEquals v12, CShape(0x1001) recompile + v14:CAttrIndex[0] = Const CAttrIndex(0) + v15:BasicObject = CCall v11, :rb_ivar_get_at_no_ractor_check@0x1008, v14 CheckInterrupts - Return v21 + Return v15 "); } @@ -8903,13 +8903,13 @@ mod hir_opt_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v17:HeapBasicObject = GuardType v6, HeapBasicObject - v18:CShape = LoadField v17, :shape_id@0x1000 - v19:CShape[0x1001] = GuardBitEquals v18, CShape(0x1001) recompile - v20:RubyValue = LoadField v17, :fields_obj@0x1002 - v21:BasicObject = LoadField v20, :@a@0x1002 + v11:HeapBasicObject = GuardType v6, HeapBasicObject + v12:CShape = LoadField v11, :shape_id@0x1000 + v13:CShape[0x1001] = GuardBitEquals v12, CShape(0x1001) recompile + v14:RubyValue = LoadField v11, :fields_obj@0x1002 + v15:BasicObject = LoadField v14, :@a@0x1002 CheckInterrupts - Return v21 + Return v15 "); } @@ -9131,18 +9131,15 @@ mod hir_opt_tests { v24:BasicObject = LoadField v11, :@foo@0x1005 Jump bb4(v24) bb8(): - v39:CShape = LoadField v11, :shape_id@0x1000 - v40:CShape[0x1001] = GuardBitEquals v39, CShape(0x1001) recompile - v41:CPtr = LoadField v11, :as_heap@0x1002 - v42:BasicObject = LoadField v41, :@foo@0x1003 - Jump bb4(v42) + v26:BasicObject = GetIvar v11, :@foo + Jump bb4(v26) bb4(v12:BasicObject): v29:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018) - v45:Fixnum = GuardType v12, Fixnum recompile - v46:Fixnum = FixnumAdd v45, v29 + v40:Fixnum = GuardType v12, Fixnum recompile + v41:Fixnum = FixnumAdd v40, v29 CheckInterrupts - Return v46 + Return v41 "); } @@ -9679,11 +9676,11 @@ mod hir_opt_tests { v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(C@0x1010) PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) - v25:CShape = LoadField v12, :shape_id@0x1048 - v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) recompile - v27:NilClass = Const Value(nil) + v24:CShape = LoadField v12, :shape_id@0x1048 + v25:CShape[0x1049] = GuardBitEquals v24, CShape(0x1049) recompile + v26:NilClass = Const Value(nil) CheckInterrupts - Return v27 + Return v26 "); } @@ -9715,11 +9712,11 @@ mod hir_opt_tests { v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(C@0x1010) PatchPoint MethodRedefined(C@0x1010, foo@0x1018, cme:0x1020) - v25:CShape = LoadField v12, :shape_id@0x1048 - v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) recompile - v27:NilClass = Const Value(nil) + v24:CShape = LoadField v12, :shape_id@0x1048 + v25:CShape[0x1049] = GuardBitEquals v24, CShape(0x1049) recompile + v26:NilClass = Const Value(nil) CheckInterrupts - Return v27 + Return v26 "); } @@ -9751,11 +9748,11 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018) v23:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] recompile - v26:CShape = LoadField v23, :shape_id@0x1040 - v27:CShape[0x1041] = GuardBitEquals v26, CShape(0x1041) recompile - v28:NilClass = Const Value(nil) + v25:CShape = LoadField v23, :shape_id@0x1040 + v26:CShape[0x1041] = GuardBitEquals v25, CShape(0x1041) recompile + v27:NilClass = Const Value(nil) CheckInterrupts - Return v28 + Return v27 "); } @@ -9787,11 +9784,11 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(C@0x1008) PatchPoint MethodRedefined(C@0x1008, foo@0x1010, cme:0x1018) v23:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] recompile - v26:CShape = LoadField v23, :shape_id@0x1040 - v27:CShape[0x1041] = GuardBitEquals v26, CShape(0x1041) recompile - v28:NilClass = Const Value(nil) + v25:CShape = LoadField v23, :shape_id@0x1040 + v26:CShape[0x1041] = GuardBitEquals v25, CShape(0x1041) recompile + v27:NilClass = Const Value(nil) CheckInterrupts - Return v28 + Return v27 "); } @@ -9823,12 +9820,12 @@ mod hir_opt_tests { v17:Fixnum[5] = Const Value(5) PatchPoint MethodRedefined(C@0x1008, foo=@0x1010, cme:0x1018) v28:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] recompile - v31:CShape = LoadField v28, :shape_id@0x1040 - v32:CShape[0x1041] = GuardBitEquals v31, CShape(0x1041) + v30:CShape = LoadField v28, :shape_id@0x1040 + v31:CShape[0x1041] = GuardBitEquals v30, CShape(0x1041) StoreField v28, :@foo@0x1042, v17 WriteBarrier v28, v17 - v35:CShape[0x1043] = Const CShape(0x1043) - StoreField v28, :shape_id@0x1040, v35 + v34:CShape[0x1043] = Const CShape(0x1043) + StoreField v28, :shape_id@0x1040, v34 CheckInterrupts Return v17 "); @@ -9862,12 +9859,12 @@ mod hir_opt_tests { v17:Fixnum[5] = Const Value(5) PatchPoint MethodRedefined(C@0x1008, foo=@0x1010, cme:0x1018) v28:ObjectSubclass[class_exact:C] = GuardType v10, ObjectSubclass[class_exact:C] recompile - v31:CShape = LoadField v28, :shape_id@0x1040 - v32:CShape[0x1041] = GuardBitEquals v31, CShape(0x1041) + v30:CShape = LoadField v28, :shape_id@0x1040 + v31:CShape[0x1041] = GuardBitEquals v30, CShape(0x1041) StoreField v28, :@foo@0x1042, v17 WriteBarrier v28, v17 - v35:CShape[0x1043] = Const CShape(0x1043) - StoreField v28, :shape_id@0x1040, v35 + v34:CShape[0x1043] = Const CShape(0x1043) + StoreField v28, :shape_id@0x1040, v34 CheckInterrupts Return v17 "); @@ -11913,10 +11910,17 @@ mod hir_opt_tests { bb3(v9:BasicObject, v10:BasicObject): PatchPoint NoSingletonClass(String@0x1008) PatchPoint MethodRedefined(String@0x1008, ascii_only?@0x1010, cme:0x1018) - v24:StringExact = GuardType v10, StringExact recompile - v25:BoolExact = CCall v24, :String#ascii_only?@0x1040 + v23:StringExact = GuardType v10, StringExact recompile + v26:CUInt64 = LoadField v23, :RBASIC_FLAGS@0x1040 + v27:CUInt64[3145728] = Const CUInt64(3145728) + v28:CInt64 = IntAnd v26, v27 + v29:CInt64[1048576] = Const CInt64(1048576) + v30:CInt64 = GuardGreaterEq v28, v29 + v31:CInt64[1048576] = Const CInt64(1048576) + v32:CBool = IsBitEqual v30, v31 + v33:BoolExact = BoxBool v32 CheckInterrupts - Return v25 + Return v33 "); } @@ -14689,9 +14693,9 @@ mod hir_opt_tests { v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestFrozen@0x1010) PatchPoint MethodRedefined(TestFrozen@0x1010, a@0x1018, cme:0x1020) - v29:Fixnum[1] = Const Value(1) + v28:Fixnum[1] = Const Value(1) CheckInterrupts - Return v29 + Return v28 "); } @@ -14730,9 +14734,9 @@ mod hir_opt_tests { v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestMultiIvars@0x1010) PatchPoint MethodRedefined(TestMultiIvars@0x1010, b@0x1018, cme:0x1020) - v29:Fixnum[20] = Const Value(20) + v28:Fixnum[20] = Const Value(20) CheckInterrupts - Return v29 + Return v28 "); } @@ -14769,9 +14773,9 @@ mod hir_opt_tests { v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestFrozenStr@0x1010) PatchPoint MethodRedefined(TestFrozenStr@0x1010, name@0x1018, cme:0x1020) - v29:StringExact[VALUE(0x1048)] = Const Value(VALUE(0x1048)) + v28:StringExact[VALUE(0x1048)] = Const Value(VALUE(0x1048)) CheckInterrupts - Return v29 + Return v28 "); } @@ -14808,9 +14812,9 @@ mod hir_opt_tests { v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestFrozenNil@0x1010) PatchPoint MethodRedefined(TestFrozenNil@0x1010, value@0x1018, cme:0x1020) - v29:NilClass = Const Value(nil) + v28:NilClass = Const Value(nil) CheckInterrupts - Return v29 + Return v28 "); } @@ -14847,11 +14851,11 @@ mod hir_opt_tests { v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestUnfrozen@0x1010) PatchPoint MethodRedefined(TestUnfrozen@0x1010, a@0x1018, cme:0x1020) - v25:CShape = LoadField v12, :shape_id@0x1048 - v26:CShape[0x1049] = GuardBitEquals v25, CShape(0x1049) recompile - v27:BasicObject = LoadField v12, :@a@0x104a + v24:CShape = LoadField v12, :shape_id@0x1048 + v25:CShape[0x1049] = GuardBitEquals v24, CShape(0x1049) recompile + v26:BasicObject = LoadField v12, :@a@0x104a CheckInterrupts - Return v27 + Return v26 "); } @@ -14888,9 +14892,9 @@ mod hir_opt_tests { v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestAttrReader@0x1010) PatchPoint MethodRedefined(TestAttrReader@0x1010, value@0x1018, cme:0x1020) - v29:Fixnum[42] = Const Value(42) + v28:Fixnum[42] = Const Value(42) CheckInterrupts - Return v29 + Return v28 "); } @@ -14927,9 +14931,9 @@ mod hir_opt_tests { v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestFrozenSym@0x1010) PatchPoint MethodRedefined(TestFrozenSym@0x1010, sym@0x1018, cme:0x1020) - v29:StaticSymbol[:hello] = Const Value(VALUE(0x1048)) + v28:StaticSymbol[:hello] = Const Value(VALUE(0x1048)) CheckInterrupts - Return v29 + Return v28 "); } @@ -14966,9 +14970,9 @@ mod hir_opt_tests { v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestFrozenBool@0x1010) PatchPoint MethodRedefined(TestFrozenBool@0x1010, flag@0x1018, cme:0x1020) - v29:TrueClass = Const Value(true) + v28:TrueClass = Const Value(true) CheckInterrupts - Return v29 + Return v28 "); } @@ -15005,11 +15009,11 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(TestDynamic@0x1008) PatchPoint MethodRedefined(TestDynamic@0x1008, val@0x1010, cme:0x1018) v23:ObjectSubclass[class_exact:TestDynamic] = GuardType v10, ObjectSubclass[class_exact:TestDynamic] recompile - v26:CShape = LoadField v23, :shape_id@0x1040 - v27:CShape[0x1041] = GuardBitEquals v26, CShape(0x1041) recompile - v28:BasicObject = LoadField v23, :@val@0x1042 + v25:CShape = LoadField v23, :shape_id@0x1040 + v26:CShape[0x1041] = GuardBitEquals v25, CShape(0x1041) recompile + v27:BasicObject = LoadField v23, :@val@0x1042 CheckInterrupts - Return v28 + Return v27 "); } @@ -15047,15 +15051,15 @@ mod hir_opt_tests { v12:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint NoSingletonClass(TestNestedAccess@0x1010) PatchPoint MethodRedefined(TestNestedAccess@0x1010, x@0x1018, cme:0x1020) - v51:Fixnum[100] = Const Value(100) + v49:Fixnum[100] = Const Value(100) PatchPoint StableConstantNames(0x1048, NESTED_FROZEN) v18:ObjectSubclass[VALUE(0x1008)] = Const Value(VALUE(0x1008)) PatchPoint MethodRedefined(TestNestedAccess@0x1010, y@0x1050, cme:0x1058) - v53:Fixnum[200] = Const Value(200) + v51:Fixnum[200] = Const Value(200) PatchPoint MethodRedefined(Integer@0x1080, +@0x1088, cme:0x1090) - v54:Fixnum[300] = Const Value(300) + v52:Fixnum[300] = Const Value(300) CheckInterrupts - Return v54 + Return v52 "); } @@ -16595,7 +16599,7 @@ mod hir_opt_tests { v88:Array = RefineType v67, Array v89:CInt64 = UnboxFixnum v68 v90:BasicObject = ArrayAref v88, v89 - v74:BasicObject = InvokeBlock, v90 # SendFallbackReason: InvokeBlock: not yet specialized + v74:BasicObject = InvokeBlock v90 # SendFallbackReason: InvokeBlock: not yet specialized v91:Fixnum[1] = Const Value(1) v92:Fixnum = FixnumAdd v68, v91 PatchPoint NoEPEscape(each) @@ -16635,16 +16639,16 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:NilClass): v13:Fixnum[1] = Const Value(1) PatchPoint SingleRactorMode - v35:HeapBasicObject = GuardType v8, HeapBasicObject - v36:CShape = LoadField v35, :shape_id@0x1000 - v37:CShape[0x1001] = GuardBitEquals v36, CShape(0x1001) recompile - StoreField v35, :@a@0x1002, v13 - WriteBarrier v35, v13 - v40:CShape[0x1003] = Const CShape(0x1003) - StoreField v35, :shape_id@0x1000, v40 + v19:HeapBasicObject = GuardType v8, HeapBasicObject + v20:CShape = LoadField v19, :shape_id@0x1000 + v21:CShape[0x1001] = GuardBitEquals v20, CShape(0x1001) recompile + StoreField v19, :@a@0x1002, v13 + WriteBarrier v19, v13 + v24:CShape[0x1003] = Const CShape(0x1003) + StoreField v19, :shape_id@0x1000, v24 PatchPoint NoEPEscape(initialize) PatchPoint SingleRactorMode - WriteBarrier v35, v13 + WriteBarrier v19, v13 CheckInterrupts Return v13 "); @@ -16682,19 +16686,19 @@ mod hir_opt_tests { bb3(v10:BasicObject, v11:NilClass, v12:NilClass): v16:Fixnum[1] = Const Value(1) PatchPoint SingleRactorMode - v49:HeapBasicObject = GuardType v10, HeapBasicObject - v50:CShape = LoadField v49, :shape_id@0x1000 - v51:CShape[0x1001] = GuardBitEquals v50, CShape(0x1001) recompile - StoreField v49, :@a@0x1002, v16 - WriteBarrier v49, v16 - v54:CShape[0x1003] = Const CShape(0x1003) - StoreField v49, :shape_id@0x1000, v54 - v26:Fixnum[5] = Const Value(5) + v22:HeapBasicObject = GuardType v10, HeapBasicObject + v23:CShape = LoadField v22, :shape_id@0x1000 + v24:CShape[0x1001] = GuardBitEquals v23, CShape(0x1001) recompile + StoreField v22, :@a@0x1002, v16 + WriteBarrier v22, v16 + v27:CShape[0x1003] = Const CShape(0x1003) + StoreField v22, :shape_id@0x1000, v27 + v32:Fixnum[5] = Const Value(5) PatchPoint NoEPEscape(initialize) PatchPoint MethodRedefined(Integer@0x1008, +@0x1010, cme:0x1018) - v65:Fixnum[6] = Const Value(6) + v63:Fixnum[6] = Const Value(6) PatchPoint SingleRactorMode - WriteBarrier v49, v16 + WriteBarrier v22, v16 CheckInterrupts Return v16 "); @@ -16729,17 +16733,17 @@ mod hir_opt_tests { bb3(v8:BasicObject, v9:NilClass): v13:Fixnum[1] = Const Value(1) PatchPoint SingleRactorMode - v43:HeapBasicObject = GuardType v8, HeapBasicObject - v44:CShape = LoadField v43, :shape_id@0x1000 - v45:CShape[0x1001] = GuardBitEquals v44, CShape(0x1001) recompile - StoreField v43, :@a@0x1002, v13 - WriteBarrier v43, v13 - v48:CShape[0x1003] = Const CShape(0x1003) - StoreField v43, :shape_id@0x1000, v48 + v19:HeapBasicObject = GuardType v8, HeapBasicObject + v20:CShape = LoadField v19, :shape_id@0x1000 + v21:CShape[0x1001] = GuardBitEquals v20, CShape(0x1001) recompile + StoreField v19, :@a@0x1002, v13 + WriteBarrier v19, v13 + v24:CShape[0x1003] = Const CShape(0x1003) + StoreField v19, :shape_id@0x1000, v24 PatchPoint NoEPEscape(initialize) PatchPoint SingleRactorMode - WriteBarrier v43, v13 - WriteBarrier v43, v13 + WriteBarrier v19, v13 + WriteBarrier v19, v13 CheckInterrupts Return v13 "); @@ -16943,7 +16947,7 @@ mod hir_opt_tests { v20:BasicObject = InvokeBlockIfunc v13, v10 Jump bb4(v20) bb6(): - v22:BasicObject = InvokeBlock, v10 # SendFallbackReason: InvokeBlock: not yet specialized + v22:BasicObject = InvokeBlock v10 # SendFallbackReason: InvokeBlock: not yet specialized Jump bb4(v22) bb4(v18:BasicObject): v27:Fixnum[2] = Const Value(2) @@ -16958,7 +16962,7 @@ mod hir_opt_tests { v37:BasicObject = InvokeBlockIfunc v30, v27 Jump bb7(v37) bb9(): - v39:BasicObject = InvokeBlock, v27 # SendFallbackReason: InvokeBlock: not yet specialized + v39:BasicObject = InvokeBlock v27 # SendFallbackReason: InvokeBlock: not yet specialized Jump bb7(v39) bb7(v35:BasicObject): CheckInterrupts @@ -17104,9 +17108,9 @@ mod hir_opt_tests { bb6(v19:HeapBasicObject, v20:Fixnum): v24:Fixnum[10] = Const Value(10) PatchPoint MethodRedefined(Integer@0x1000, <@0x1008, cme:0x1010) - v105:BoolExact = FixnumLt v20, v24 + v100:BoolExact = FixnumLt v20, v24 CheckInterrupts - v30:CBool = Test v105 + v30:CBool = Test v100 CondBranch v30, bb4(v19, v20), bb7() bb4(v40:HeapBasicObject, v41:Fixnum): PatchPoint SingleRactorMode @@ -17126,10 +17130,8 @@ mod hir_opt_tests { v58:NilClass = Const Value(nil) Jump bb8(v58) bb12(): - v92:CShape = LoadField v40, :shape_id@0x1038 - v93:CShape[0x1039] = GuardBitEquals v92, CShape(0x1039) recompile - v94:BasicObject = LoadField v40, :@levar@0x103a - Jump bb8(v94) + v60:BasicObject = GetIvar v40, :@levar + Jump bb8(v60) bb8(v47:BasicObject): CheckInterrupts v64:CBool = Test v47 @@ -17137,19 +17139,19 @@ mod hir_opt_tests { bb13(): PatchPoint NoEPEscape(set_value_loop) PatchPoint SingleRactorMode - v96:CShape = LoadField v40, :shape_id@0x1038 - v97:CShape[0x103b] = GuardBitEquals v96, CShape(0x103b) recompile + v74:CShape = LoadField v40, :shape_id@0x1038 + v75:CShape[0x103b] = GuardBitEquals v74, CShape(0x103b) recompile StoreField v40, :@levar@0x103a, v41 WriteBarrier v40, v41 - v100:CShape[0x1039] = Const CShape(0x1039) - StoreField v40, :shape_id@0x1038, v100 + v78:CShape[0x1039] = Const CShape(0x1039) + StoreField v40, :shape_id@0x1038, v78 Jump bb5(v40, v41) - bb5(v76:HeapBasicObject, v77:Fixnum): + bb5(v82:HeapBasicObject, v83:Fixnum): PatchPoint NoEPEscape(set_value_loop) - v84:Fixnum[1] = Const Value(1) + v90:Fixnum[1] = Const Value(1) PatchPoint MethodRedefined(Integer@0x1000, +@0x103c, cme:0x1040) - v109:Fixnum = FixnumAdd v77, v84 - Jump bb6(v76, v109) + v104:Fixnum = FixnumAdd v83, v90 + Jump bb6(v82, v104) bb7(): v35:NilClass = Const Value(nil) CheckInterrupts @@ -18858,7 +18860,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Object@0x1008, with_yield@0x1010, cme:0x1018) v27:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v9, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile PushInlineFrame v27 (0x1040), v10 - v35:BasicObject = InvokeBlock, v10 # SendFallbackReason: InvokeBlock: not yet specialized + v35:BasicObject = InvokeBlock v10 # SendFallbackReason: InvokeBlock: not yet specialized CheckInterrupts PopInlineFrame PatchPoint NoEPEscape(test) @@ -19052,18 +19054,18 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Point@0x1008) PatchPoint MethodRedefined(Point@0x1008, initialize@0x1038, cme:0x1040) PushInlineFrame v91 (0x1068), v17, v19 - v209:CShape = LoadField v91, :shape_id@0x1070 - v210:CShape[0x1071] = GuardBitEquals v209, CShape(0x1071) recompile + v116:CShape = LoadField v91, :shape_id@0x1070 + v117:CShape[0x1071] = GuardBitEquals v116, CShape(0x1071) recompile StoreField v91, :@x@0x1072, v17 WriteBarrier v91, v17 - v213:CShape[0x1073] = Const CShape(0x1073) - StoreField v91, :shape_id@0x1070, v213 + v120:CShape[0x1073] = Const CShape(0x1073) + StoreField v91, :shape_id@0x1070, v120 PatchPoint NoEPEscape(initialize) PatchPoint SingleRactorMode StoreField v91, :@y@0x1074, v19 WriteBarrier v91, v19 - v220:CShape[0x1075] = Const CShape(0x1075) - StoreField v91, :shape_id@0x1070, v220 + v135:CShape[0x1075] = Const CShape(0x1075) + StoreField v91, :shape_id@0x1070, v135 CheckInterrupts PopInlineFrame PatchPoint SingleRactorMode @@ -19077,55 +19079,55 @@ mod hir_opt_tests { PatchPoint NoSingletonClass(Point@0x1008) PatchPoint MethodRedefined(Point@0x1008, initialize@0x1038, cme:0x1040) PushInlineFrame v98 (0x1068), v52, v54 - v223:CShape = LoadField v98, :shape_id@0x1070 - v224:CShape[0x1071] = GuardBitEquals v223, CShape(0x1071) recompile + v155:CShape = LoadField v98, :shape_id@0x1070 + v156:CShape[0x1071] = GuardBitEquals v155, CShape(0x1071) recompile StoreField v98, :@x@0x1072, v52 WriteBarrier v98, v52 - v227:CShape[0x1073] = Const CShape(0x1073) - StoreField v98, :shape_id@0x1070, v227 + v159:CShape[0x1073] = Const CShape(0x1073) + StoreField v98, :shape_id@0x1070, v159 PatchPoint NoEPEscape(initialize) PatchPoint SingleRactorMode StoreField v98, :@y@0x1074, v54 WriteBarrier v98, v54 - v234:CShape[0x1075] = Const CShape(0x1075) - StoreField v98, :shape_id@0x1070, v234 + v174:CShape[0x1075] = Const CShape(0x1075) + StoreField v98, :shape_id@0x1070, v174 CheckInterrupts PopInlineFrame PatchPoint NoSingletonClass(Point@0x1008) PatchPoint MethodRedefined(Point@0x1008, ==@0x1080, cme:0x1088) PushInlineFrame v91 (0x1068), v98 PatchPoint SingleRactorMode - v237:CShape = LoadField v91, :shape_id@0x1070 - v238:CShape[0x1075] = GuardBitEquals v237, CShape(0x1075) recompile - v239:BasicObject = LoadField v91, :@x@0x1072 + v192:CShape = LoadField v91, :shape_id@0x1070 + v193:CShape[0x1075] = GuardBitEquals v192, CShape(0x1075) recompile + v194:BasicObject = LoadField v91, :@x@0x1072 PatchPoint NoEPEscape(==) PatchPoint MethodRedefined(Point@0x1008, x@0x10b0, cme:0x10b8) PatchPoint MethodRedefined(Integer@0x10e0, ==@0x1080, cme:0x10e8) - v246:Fixnum = GuardType v239, Fixnum recompile - v248:BoolExact = FixnumEq v246, v52 - v179:CBool = Test v248 - v180:FalseClass = RefineType v248, Falsy - CondBranch v179, bb17(), bb16(v91, v98, v180) + v240:Fixnum = GuardType v194, Fixnum recompile + v242:BoolExact = FixnumEq v240, v52 + v206:CBool = Test v242 + v207:FalseClass = RefineType v242, Falsy + CondBranch v206, bb17(), bb16(v91, v98, v207) bb17(): PatchPoint SingleRactorMode - v241:CShape = LoadField v91, :shape_id@0x1070 - v242:CShape[0x1075] = GuardBitEquals v241, CShape(0x1075) recompile - v243:BasicObject = LoadField v91, :@y@0x1074 + v214:CShape = LoadField v91, :shape_id@0x1070 + v215:CShape[0x1075] = GuardBitEquals v214, CShape(0x1075) recompile + v216:BasicObject = LoadField v91, :@y@0x1074 PatchPoint NoEPEscape(==) PatchPoint NoSingletonClass(Point@0x1008) PatchPoint MethodRedefined(Point@0x1008, y@0x1110, cme:0x1118) - v269:CShape = LoadField v98, :shape_id@0x1070 - v270:CShape[0x1075] = GuardBitEquals v269, CShape(0x1075) recompile - v271:BasicObject = LoadField v98, :@y@0x1074 + v261:CShape = LoadField v98, :shape_id@0x1070 + v262:CShape[0x1075] = GuardBitEquals v261, CShape(0x1075) recompile + v263:BasicObject = LoadField v98, :@y@0x1074 PatchPoint MethodRedefined(Integer@0x10e0, ==@0x1080, cme:0x10e8) - v251:Fixnum = GuardType v243, Fixnum recompile - v252:Fixnum = GuardType v271, Fixnum - v253:BoolExact = FixnumEq v251, v252 - Jump bb16(v91, v98, v253) - bb16(v196:ObjectSubclass[class_exact:Point], v197:ObjectSubclass[class_exact:Point], v198:BoolExact): + v245:Fixnum = GuardType v216, Fixnum recompile + v246:Fixnum = GuardType v263, Fixnum + v247:BoolExact = FixnumEq v245, v246 + Jump bb16(v91, v98, v247) + bb16(v226:ObjectSubclass[class_exact:Point], v227:ObjectSubclass[class_exact:Point], v228:BoolExact): CheckInterrupts PopInlineFrame - Return v198 + Return v228 "); } diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 86e8dbb9dc9c53..91243bde0ebd73 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -3232,9 +3232,12 @@ pub(crate) mod hir_build_tests { Jump bb3(v4) bb3(v6:BasicObject): PatchPoint SingleRactorMode - v11:BasicObject = GetIvar v6, :@foo + v11:HeapBasicObject = GuardType v6, HeapBasicObject + v12:CShape = LoadField v11, :shape_id@0x1000 + v13:CShape[0x1001] = GuardBitEquals v12, CShape(0x1001) recompile + v14:NilClass = Const Value(nil) CheckInterrupts - Return v11 + Return v14 "); } @@ -3258,8 +3261,14 @@ pub(crate) mod hir_build_tests { bb3(v6:BasicObject): v10:Fixnum[1] = Const Value(1) PatchPoint SingleRactorMode - SetIvar v6, :@foo, v10 - v15:HeapBasicObject = RefineType v6, HeapBasicObject + v14:HeapBasicObject = GuardType v6, HeapBasicObject + v15:CShape = LoadField v14, :shape_id@0x1000 + v16:CShape[0x1001] = GuardBitEquals v15, CShape(0x1001) recompile + StoreField v14, :@foo@0x1002, v10 + WriteBarrier v14, v10 + v19:CShape[0x1003] = Const CShape(0x1003) + StoreField v14, :shape_id@0x1000, v19 + v21:HeapBasicObject = RefineType v6, HeapBasicObject CheckInterrupts Return v10 "); @@ -4829,7 +4838,7 @@ pub(crate) mod hir_build_tests { CondBranch v47, bb8(), bb4(v18, v19, v20, v21, v34, v27) bb8(): v50:Truthy = RefineType v33, Truthy - v54:BasicObject = InvokeBlock, v27 # SendFallbackReason: InvokeBlock: not yet specialized + v54:BasicObject = InvokeBlock v27 # SendFallbackReason: InvokeBlock: not yet specialized v57:BasicObject = InvokeBuiltin dir_s_close, v18, v27 CheckInterrupts Return v54 @@ -5213,7 +5222,7 @@ pub(crate) mod hir_build_tests { v9:BasicObject = LoadArg :y@2 Jump bb3(v7, v8, v9) bb3(v11:BasicObject, v12:BasicObject, v13:BasicObject): - v19:BasicObject = InvokeBlock, v12, v13 # SendFallbackReason: InvokeBlock: not yet specialized + v19:BasicObject = InvokeBlock v12, v13 # SendFallbackReason: InvokeBlock: not yet specialized CheckInterrupts Return v19 "); @@ -5496,7 +5505,7 @@ pub(crate) mod hir_build_tests { Return v48 bb7(v67:BasicObject, v68:Fixnum): v72:BasicObject = InvokeBuiltin rb_jit_ary_at, v67, v68 - v74:BasicObject = InvokeBlock, v72 # SendFallbackReason: InvokeBlock: not yet specialized + v74:BasicObject = InvokeBlock v72 # SendFallbackReason: InvokeBlock: not yet specialized v78:Fixnum = InvokeBuiltin rb_jit_fixnum_inc, v67, v68 PatchPoint NoEPEscape(each) Jump bb8(v67, v78) diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs index 7e20704bf32ad2..f8ed7ae028d70d 100644 --- a/zjit/src/stats.rs +++ b/zjit/src/stats.rs @@ -318,6 +318,7 @@ make_counters! { setivar_fallback_shape_transition, setivar_fallback_new_shape_complex, setivar_fallback_new_shape_needs_extension, + setivar_fallback_no_side_exits, } // Ivar fallback counters that are summed as dynamic_getivar_count @@ -332,6 +333,7 @@ make_counters! { getivar_fallback_immediate, getivar_fallback_not_t_object, getivar_fallback_complex, + getivar_fallback_no_side_exits, } // Ivar fallback counters that are summed as dynamic_definedivar_count