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