From cb37ef5b2adb01dfa1609df6b962ba798ca9ad72 Mon Sep 17 00:00:00 2001 From: Earlopain <14981592+Earlopain@users.noreply.github.com> Date: Mon, 9 Feb 2026 16:36:18 +0100 Subject: [PATCH 1/7] [ruby/prism] Improve node docs (https://github.com/ruby/prism/pull/3912) Currently I find them hard to follow because there are just so many methods. This groups them by topic instead so that the more unique methods appear first. I also added call-seqs because we have the type information, so why not. https://github.com/ruby/prism/commit/caf018ec50 --- prism/templates/lib/prism/node.rb.erb | 192 +++++++++++++++++--------- prism/templates/template.rb | 70 ++++++++++ 2 files changed, 198 insertions(+), 64 deletions(-) diff --git a/prism/templates/lib/prism/node.rb.erb b/prism/templates/lib/prism/node.rb.erb index e2f8c0e53c0a17..8c88529c664f3c 100644 --- a/prism/templates/lib/prism/node.rb.erb +++ b/prism/templates/lib/prism/node.rb.erb @@ -1,9 +1,11 @@ +# :markup: markdown + module Prism # This represents a node in the tree. It is the parent class of all of the # various node types. class Node # A pointer to the source that this node was created from. - attr_reader :source + attr_reader :source # :nodoc: private :source # A unique identifier for this node. This is used in a very specific @@ -30,99 +32,108 @@ module Prism repository.enter(node_id, :location) end - # Delegates to the start_line of the associated location object. + # -------------------------------------------------------------------------- + # :section: Location Delegators + # These methods provide convenient access to the underlying Location object. + # -------------------------------------------------------------------------- + + # Delegates to [`start_line`](rdoc-ref:Location#start_line) of the associated location object. def start_line location.start_line end - # Delegates to the end_line of the associated location object. + # Delegates to [`end_line`](rdoc-ref:Location#end_line) of the associated location object. def end_line location.end_line end - # The start offset of the node in the source. This method is effectively a - # delegate method to the location object. + # Delegates to [`start_offset`](rdoc-ref:Location#start_offset) of the associated location object. def start_offset location = @location location.is_a?(Location) ? location.start_offset : location >> 32 end - # The end offset of the node in the source. This method is effectively a - # delegate method to the location object. + # Delegates to [`end_offset`](rdoc-ref:Location#end_offset) of the associated location object. def end_offset location = @location location.is_a?(Location) ? location.end_offset : ((location >> 32) + (location & 0xFFFFFFFF)) end - # Delegates to the start_character_offset of the associated location object. + # Delegates to [`start_character_offset`](rdoc-ref:Location#start_character_offset) + # of the associated location object. def start_character_offset location.start_character_offset end - # Delegates to the end_character_offset of the associated location object. + # Delegates to [`end_character_offset`](rdoc-ref:Location#end_character_offset) + # of the associated location object. def end_character_offset location.end_character_offset end - # Delegates to the cached_start_code_units_offset of the associated location - # object. + # Delegates to [`cached_start_code_units_offset`](rdoc-ref:Location#cached_start_code_units_offset) + # of the associated location object. def cached_start_code_units_offset(cache) location.cached_start_code_units_offset(cache) end - # Delegates to the cached_end_code_units_offset of the associated location - # object. + # Delegates to [`cached_end_code_units_offset`](rdoc-ref:Location#cached_end_code_units_offset) + # of the associated location object. def cached_end_code_units_offset(cache) location.cached_end_code_units_offset(cache) end - # Delegates to the start_column of the associated location object. + # Delegates to [`start_column`](rdoc-ref:Location#start_column) of the associated location object. def start_column location.start_column end - # Delegates to the end_column of the associated location object. + # Delegates to [`end_column`](rdoc-ref:Location#end_column) of the associated location object. def end_column location.end_column end - # Delegates to the start_character_column of the associated location object. + # Delegates to [`start_character_column`](rdoc-ref:Location#start_character_column) + # of the associated location object. def start_character_column location.start_character_column end - # Delegates to the end_character_column of the associated location object. + # Delegates to [`end_character_column`](rdoc-ref:Location#end_character_column) + # of the associated location object. def end_character_column location.end_character_column end - # Delegates to the cached_start_code_units_column of the associated location - # object. + # Delegates to [`cached_start_code_units_column`](rdoc-ref:Location#cached_start_code_units_column) + # of the associated location object. def cached_start_code_units_column(cache) location.cached_start_code_units_column(cache) end - # Delegates to the cached_end_code_units_column of the associated location - # object. + # Delegates to [`cached_end_code_units_column`](rdoc-ref:Location#cached_end_code_units_column) + # of the associated location object. def cached_end_code_units_column(cache) location.cached_end_code_units_column(cache) end - # Delegates to the leading_comments of the associated location object. + # Delegates to [`leading_comments`](rdoc-ref:Location#leading_comments) of the associated location object. def leading_comments location.leading_comments end - # Delegates to the trailing_comments of the associated location object. + # Delegates to [`trailing_comments`](rdoc-ref:Location#trailing_comments) of the associated location object. def trailing_comments location.trailing_comments end - # Delegates to the comments of the associated location object. + # Delegates to [`comments`](rdoc-ref:Location#comments) of the associated location object. def comments location.comments end + # :section: + # Returns all of the lines of the source code associated with this node. def source_lines location.source_lines @@ -146,7 +157,7 @@ module Prism # An bitset of flags for this node. There are certain flags that are common # for all nodes, and then some nodes have specific flags. - attr_reader :flags + attr_reader :flags # :nodoc: protected :flags # Returns true if the node has the newline flag set. @@ -248,10 +259,9 @@ module Prism end # -------------------------------------------------------------------------- - # :section: Node interface - # These methods are effectively abstract methods that must be implemented by - # the various subclasses of Node. They are here to make it easier to work - # with typecheckers. + # :section: Node Interface + # These methods are effectively abstract methods that are implemented by + # the various subclasses of Node. # -------------------------------------------------------------------------- # Accepts a visitor and calls back into the specialized visit function. @@ -335,12 +345,23 @@ module Prism <%- end -%> end - # def accept: (Visitor visitor) -> void + # --------- + # :section: Repository + # Methods related to Relocation. + # --------- + + # ---------------------------------------------------------------------------------- + # :section: Node Interface + # These methods are present on all subclasses of Node. + # Read the [node interface docs](rdoc-ref:Node@node-interface) for more information. + # ---------------------------------------------------------------------------------- + + # See Node.accept. def accept(visitor) visitor.visit_<%= node.human %>(self) end - # def child_nodes: () -> Array[Node?] + # See Node.child_nodes. def child_nodes [<%= node.fields.map { |field| case field @@ -350,7 +371,7 @@ module Prism }.compact.join(", ") %>] end - # def each_child_node: () { (Prism::node) -> void } -> void | () -> Enumerator[Prism::node] + # See Node.each_child_node. def each_child_node return to_enum(:each_child_node) unless block_given? @@ -366,7 +387,7 @@ module Prism <%- end -%> end - # def compact_child_nodes: () -> Array[Node] + # See Node.compact_child_nodes. def compact_child_nodes <%- if node.fields.any? { |field| field.is_a?(Prism::Template::OptionalNodeField) } -%> compact = [] #: Array[Prism::node] @@ -391,7 +412,7 @@ module Prism <%- end -%> end - # def comment_targets: () -> Array[Node | Location] + # See Node.comment_targets. def comment_targets [<%= node.fields.map { |field| case field @@ -401,49 +422,85 @@ module Prism }.compact.join(", ") %>] #: Array[Prism::node | Location] end - # def copy: (<%= (["?node_id: Integer", "?location: Location", "?flags: Integer"] + node.fields.map { |field| "?#{field.name}: #{field.rbs_class}" }).join(", ") %>) -> <%= node.name %> + # :call-seq: + # copy(**fields) -> <%= node.name %> + # + # Creates a copy of self with the given fields, using self as the template. def copy(<%= (["node_id", "location", "flags"] + node.fields.map(&:name)).map { |field| "#{field}: self.#{field}" }.join(", ") %>) <%= node.name %>.new(<%= ["source", "node_id", "location", "flags", *node.fields.map(&:name)].join(", ") %>) end - # def deconstruct: () -> Array[Node?] alias deconstruct child_nodes def deconstruct_keys(keys) # :nodoc: { <%= (["node_id: node_id", "location: location"] + node.fields.map { |field| "#{field.name}: #{field.name}" }).join(", ") %> } end + + # See `Node#type`. + def type + :<%= node.human %> + end + + # See `Node.type`. + def self.type + :<%= node.human %> + end + + def inspect # :nodoc: + InspectVisitor.compose(self) + end + + # :section: + <%- if (node_flags = node.flags) -%> <%- node_flags.values.each do |value| -%> - - # def <%= value.name.downcase %>?: () -> bool + # :category: Flags + # <%= value.comment %> def <%= value.name.downcase %>? flags.anybits?(<%= node_flags.name %>::<%= value.name %>) end + <%- end -%> <%- end -%> <%- node.fields.each do |field| -%> - + <%- case field -%> + <%- when Prism::Template::LocationField -%> + # :category: Locations + # :call-seq: + # <%= field.name %> -> <%= field.call_seq_type %> + # <%- if field.comment.nil? -%> - # attr_reader <%= field.name %>: <%= field.rbs_class %> + # Returns the Location represented by `<%= field.name %>`. <%- else -%> <%- field.each_comment_line do |line| -%> #<%= line %> <%- end -%> <%- end -%> - <%- case field -%> - <%- when Prism::Template::LocationField -%> def <%= field.name %> location = @<%= field.name %> return location if location.is_a?(Location) @<%= field.name %> = Location.new(source, location >> 32, location & 0xFFFFFFFF) end + # :category: Repository # Save the <%= field.name %> location using the given saved source so that # it can be retrieved later. def save_<%= field.name %>(repository) repository.enter(node_id, :<%= field.name %>) end + <%- when Prism::Template::OptionalLocationField -%> + # :category: Locations + # :call-seq: + # <%= field.name %> -> <%= field.call_seq_type %> + # + <%- if field.comment.nil? -%> + # Returns the Location represented by `<%= field.name %>`. + <%- else -%> + <%- field.each_comment_line do |line| -%> + #<%= line %> + <%- end -%> + <%- end -%> def <%= field.name %> location = @<%= field.name %> case location @@ -456,53 +513,60 @@ module Prism end end + # :category: Repository # Save the <%= field.name %> location using the given saved source so that # it can be retrieved later. def save_<%= field.name %>(repository) repository.enter(node_id, :<%= field.name %>) unless @<%= field.name %>.nil? end <%- else -%> - attr_reader :<%= field.name %> + # :call-seq: + # <%= field.name %> -> <%= field.call_seq_type %> + # + <%- if field.comment.nil? -%> + # Returns the `<%= field.name %>` attribute. + <%- else -%> + <%- field.each_comment_line do |line| -%> + #<%= line %> + <%- end -%> + <%- end -%> + def <%= field.name %> + @<%= field.name %> + end + <%- end -%> <%- end -%> + # :section: Slicing + <%- node.fields.each do |field| -%> <%- case field -%> <%- when Prism::Template::LocationField -%> <%- raise unless field.name.end_with?("_loc") -%> <%- next if node.fields.any? { |other| other.name == field.name.delete_suffix("_loc") } -%> - - # def <%= field.name.delete_suffix("_loc") %>: () -> String + # :call-seq: + # <%= field.name.delete_suffix("_loc") %> -> String + # + # Slice the location of <%= field.name %> from the source. def <%= field.name.delete_suffix("_loc") %> <%= field.name %>.slice end + <%- when Prism::Template::OptionalLocationField -%> <%- raise unless field.name.end_with?("_loc") -%> <%- next if node.fields.any? { |other| other.name == field.name.delete_suffix("_loc") } -%> - - # def <%= field.name.delete_suffix("_loc") %>: () -> String? + # :call-seq: + # <%= field.name.delete_suffix("_loc") %> -> String | nil + # + # Slice the location of <%= field.name %> from the source. def <%= field.name.delete_suffix("_loc") %> <%= field.name %>&.slice end + <%- end -%> <%- end -%> + # :section: - def inspect # :nodoc: - InspectVisitor.compose(self) - end - - # Return a symbol representation of this node type. See `Node#type`. - def type - :<%= node.human %> - end - - # Return a symbol representation of this node type. See `Node::type`. - def self.type - :<%= node.human %> - end - - # Implements case-equality for the node. This is effectively == but without - # comparing the value of locations. Locations are checked only for presence. - def ===(other) + def ===(other) # :nodoc: other.is_a?(<%= node.name %>)<%= " &&" if (fields = [*node.flags, *node.fields]).any? %> <%- fields.each_with_index do |field, index| -%> <%- if field.is_a?(Prism::Template::LocationField) || field.is_a?(Prism::Template::OptionalLocationField) -%> diff --git a/prism/templates/template.rb b/prism/templates/template.rb index aca626b5eba181..0c695fade5a008 100755 --- a/prism/templates/template.rb +++ b/prism/templates/template.rb @@ -156,6 +156,16 @@ def rbs_class end end + def call_seq_type + if specific_kind + specific_kind + elsif union_kind + union_kind.join(" | ") + else + "Node" + end + end + def rbi_class if specific_kind "Prism::#{specific_kind}" @@ -188,6 +198,16 @@ def rbs_class end end + def call_seq_type + if specific_kind + "#{specific_kind} | nil" + elsif union_kind + [*union_kind, "nil"].join(" | ") + else + "Node | nil" + end + end + def rbi_class if specific_kind "T.nilable(Prism::#{specific_kind})" @@ -220,6 +240,16 @@ def rbs_class end end + def call_seq_type + if specific_kind + "Array[#{specific_kind}]" + elsif union_kind + "Array[#{union_kind.join(" | ")}]" + else + "Array[Node]" + end + end + def rbi_class if specific_kind "T::Array[Prism::#{specific_kind}]" @@ -250,6 +280,10 @@ def rbs_class "Symbol" end + def call_seq_type + "Symbol" + end + def rbi_class "Symbol" end @@ -266,6 +300,10 @@ def rbs_class "Symbol?" end + def call_seq_type + "Symbol | nil" + end + def rbi_class "T.nilable(Symbol)" end @@ -282,6 +320,10 @@ def rbs_class "Array[Symbol]" end + def call_seq_type + "Array[Symbol]" + end + def rbi_class "T::Array[Symbol]" end @@ -297,6 +339,10 @@ def rbs_class "String" end + def call_seq_type + "String" + end + def rbi_class "String" end @@ -316,6 +362,10 @@ def rbs_class "Location" end + def call_seq_type + "Location" + end + def rbi_class "Prism::Location" end @@ -335,6 +385,10 @@ def rbs_class "Location?" end + def call_seq_type + "Location | nil" + end + def rbi_class "T.nilable(Prism::Location)" end @@ -350,6 +404,10 @@ def rbs_class "Integer" end + def call_seq_type + "Integer" + end + def rbi_class "Integer" end @@ -365,6 +423,10 @@ def rbs_class "Integer" end + def call_seq_type + "Integer" + end + def rbi_class "Integer" end @@ -381,6 +443,10 @@ def rbs_class "Integer" end + def call_seq_type + "Integer" + end + def rbi_class "Integer" end @@ -397,6 +463,10 @@ def rbs_class "Float" end + def call_seq_type + "Float" + end + def rbi_class "Float" end From 0679e3b6bc76509d7c65ae15f4516efabbd62b26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Feb 2026 02:18:55 +0000 Subject: [PATCH 2/7] Bump the jit group across 2 directories with 1 update Bumps the jit group with 1 update in the /yjit directory: [capstone](https://github.com/capstone-rust/capstone-rs). Bumps the jit group with 1 update in the /zjit directory: [capstone](https://github.com/capstone-rust/capstone-rs). Updates `capstone` from 0.13.0 to 0.14.0 - [Release notes](https://github.com/capstone-rust/capstone-rs/releases) - [Changelog](https://github.com/capstone-rust/capstone-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/capstone-rust/capstone-rs/compare/capstone-v0.13.0...capstone-v0.14.0) Updates `capstone` from 0.13.0 to 0.14.0 - [Release notes](https://github.com/capstone-rust/capstone-rs/releases) - [Changelog](https://github.com/capstone-rust/capstone-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/capstone-rust/capstone-rs/compare/capstone-v0.13.0...capstone-v0.14.0) --- updated-dependencies: - dependency-name: capstone dependency-version: 0.14.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: jit - dependency-name: capstone dependency-version: 0.14.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: jit ... Signed-off-by: dependabot[bot] --- yjit/Cargo.lock | 22 +++-- yjit/Cargo.toml | 2 +- zjit/Cargo.lock | 243 ++++++++++++++++++++++++++++++++++++++++++++---- zjit/Cargo.toml | 2 +- 4 files changed, 242 insertions(+), 27 deletions(-) diff --git a/yjit/Cargo.lock b/yjit/Cargo.lock index 8b6ac398065192..78e6c409866a21 100644 --- a/yjit/Cargo.lock +++ b/yjit/Cargo.lock @@ -4,22 +4,21 @@ version = 3 [[package]] name = "capstone" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "015ef5d5ca1743e3f94af9509ba6bd2886523cfee46e48d15c2ef5216fd4ac9a" +checksum = "f442ae0f2f3f1b923334b4a5386c95c69c1cfa072bafa23d6fae6d9682eb1dd4" dependencies = [ "capstone-sys", - "libc", + "static_assertions", ] [[package]] name = "capstone-sys" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2267cb8d16a1e4197863ec4284ffd1aec26fe7e57c58af46b02590a0235809a0" +checksum = "a4e8087cab6731295f5a2a2bd82989ba4f41d3a428aab2e7c98d8f4db38aac05" dependencies = [ "cc", - "libc", ] [[package]] @@ -29,14 +28,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" [[package]] -name = "libc" -version = "0.2.124" +name = "jit" +version = "0.1.0" + +[[package]] +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a41fed9d98f27ab1c6d161da622a4fa35e8a54a8adc24bbf3ddd0ef70b0e50" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "yjit" version = "0.1.0" dependencies = [ "capstone", + "jit", ] diff --git a/yjit/Cargo.toml b/yjit/Cargo.toml index d3124e608c6a3c..36c4a99edcdc47 100644 --- a/yjit/Cargo.toml +++ b/yjit/Cargo.toml @@ -12,7 +12,7 @@ publish = false # Don't publish to crates.io [dependencies] # No required dependencies to simplify build process. # Optional For development and testing purposes. -capstone = { version = "0.13.0", optional = true } +capstone = { version = "0.14.0", optional = true } jit = { version = "0.1.0", path = "../jit" } # NOTE: Development builds select a set of these via configure.ac diff --git a/zjit/Cargo.lock b/zjit/Cargo.lock index 57bfb31cd7012b..b126e46befe2b4 100644 --- a/zjit/Cargo.lock +++ b/zjit/Cargo.lock @@ -2,24 +2,29 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + [[package]] name = "capstone" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "015ef5d5ca1743e3f94af9509ba6bd2886523cfee46e48d15c2ef5216fd4ac9a" +checksum = "f442ae0f2f3f1b923334b4a5386c95c69c1cfa072bafa23d6fae6d9682eb1dd4" dependencies = [ "capstone-sys", - "libc", + "static_assertions", ] [[package]] name = "capstone-sys" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2267cb8d16a1e4197863ec4284ffd1aec26fe7e57c58af46b02590a0235809a0" +checksum = "a4e8087cab6731295f5a2a2bd82989ba4f41d3a428aab2e7c98d8f4db38aac05" dependencies = [ "cc", - "libc", ] [[package]] @@ -32,26 +37,84 @@ dependencies = [ ] [[package]] -name = "dissimilar" -version = "1.0.10" +name = "cfg-if" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8975ffdaa0ef3661bfe02dbdcc06c9f829dfafe6a3c474de366a8d5e44276921" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "expect-test" -version = "1.5.1" +name = "console" +version = "0.15.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63af43ff4431e848fb47472a920f14fa71c24de13255a5692e93d4e90302acb0" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" dependencies = [ - "dissimilar", + "encode_unicode", + "libc", "once_cell", + "windows-sys 0.59.0", ] +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "insta" +version = "1.46.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e82db8c87c7f1ccecb34ce0c24399b8a73081427f3c7c50a5d597925356115e4" +dependencies = [ + "console", + "once_cell", + "similar", + "tempfile", +] + +[[package]] +name = "jit" +version = "0.1.0" + [[package]] name = "libc" -version = "0.2.169" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" [[package]] name = "once_cell" @@ -59,16 +122,164 @@ version = "1.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "similar" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbb5d9659141646ae647b42fe094daf6c6192d1620870b449d9557f748b2daa" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "tempfile" +version = "3.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + [[package]] name = "zjit" version = "0.0.1" dependencies = [ "capstone", - "expect-test", + "insta", + "jit", ] diff --git a/zjit/Cargo.toml b/zjit/Cargo.toml index ef656c5252027c..098fb6c39a9a02 100644 --- a/zjit/Cargo.toml +++ b/zjit/Cargo.toml @@ -8,7 +8,7 @@ publish = false # Don't publish to crates.io [dependencies] # No required dependencies to simplify build process. # Optional For development and testing purposes. -capstone = { version = "0.13.0", optional = true } +capstone = { version = "0.14.0", optional = true } jit = { version = "0.1.0", path = "../jit" } [dev-dependencies] From 84c9822fefa442ef2fff174437a8bd723c8c658b Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 9 Feb 2026 10:16:03 -0500 Subject: [PATCH 3/7] Update disassembly snapshots for capstone 0.14.0 Capstone 0.14.0 uses canonical ARM64 aliases in its disassembly output: - `orr x, xzr, #imm` is now shown as `mov x, #imm` - `.byte` sequences matching `udf` are now decoded as `udf #imm` These are cosmetic changes to the disassembly text only; the underlying machine code (verified by hex snapshots) is unchanged. Also update the root Cargo.lock which was missed by Dependabot. Co-Authored-By: Claude Opus 4.6 (1M context) --- Cargo.lock | 17 +++++++++++------ yjit/src/backend/arm64/mod.rs | 2 +- zjit/src/asm/arm64/mod.rs | 2 +- zjit/src/backend/arm64/mod.rs | 16 ++++++++-------- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9a4b2ebbbaf2ec..c5c9ee932463e9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,22 +4,21 @@ version = 3 [[package]] name = "capstone" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "015ef5d5ca1743e3f94af9509ba6bd2886523cfee46e48d15c2ef5216fd4ac9a" +checksum = "f442ae0f2f3f1b923334b4a5386c95c69c1cfa072bafa23d6fae6d9682eb1dd4" dependencies = [ "capstone-sys", - "libc", + "static_assertions", ] [[package]] name = "capstone-sys" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2267cb8d16a1e4197863ec4284ffd1aec26fe7e57c58af46b02590a0235809a0" +checksum = "a4e8087cab6731295f5a2a2bd82989ba4f41d3a428aab2e7c98d8f4db38aac05" dependencies = [ "cc", - "libc", ] [[package]] @@ -102,6 +101,12 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32fea41aca09ee824cc9724996433064c89f7777e60762749a4170a14abbfa21" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/yjit/src/backend/arm64/mod.rs b/yjit/src/backend/arm64/mod.rs index 0521e09d0bf5de..1712ad0302c641 100644 --- a/yjit/src/backend/arm64/mod.rs +++ b/yjit/src/backend/arm64/mod.rs @@ -1777,7 +1777,7 @@ mod tests { assert_disasm!(cb, "e1ff9fd2e10370b2", {" 0x0: mov x1, #0xffff - 0x4: orr x1, xzr, #0x10000 + 0x4: mov x1, #0x10000 "}); } diff --git a/zjit/src/asm/arm64/mod.rs b/zjit/src/asm/arm64/mod.rs index c30520cd693f14..154e7ebd195da3 100644 --- a/zjit/src/asm/arm64/mod.rs +++ b/zjit/src/asm/arm64/mod.rs @@ -1666,7 +1666,7 @@ mod tests { #[test] fn test_mov_immediate() { let cb = compile(|cb| mov(cb, X10, A64Opnd::new_uimm(0x5555555555555555))); - assert_disasm_snapshot!(cb.disasm(), @" 0x0: orr x10, xzr, #0x5555555555555555"); + assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x10, #0x5555555555555555"); assert_snapshot!(cb.hexdump(), @"eaf300b2"); } diff --git a/zjit/src/backend/arm64/mod.rs b/zjit/src/backend/arm64/mod.rs index a1836ea9dfb3a4..20e80ebcd1b173 100644 --- a/zjit/src/backend/arm64/mod.rs +++ b/zjit/src/backend/arm64/mod.rs @@ -1902,7 +1902,7 @@ mod tests { 0x0: ldnp d8, d25, [x10, #-0x140] 0x4: .byte 0x6f, 0x2c, 0x20, 0x77 0x8: .byte 0x6f, 0x72, 0x6c, 0x64 - 0xc: .byte 0x21, 0x00, 0x00, 0x00 + 0xc: udf #0x21 "); assert_snapshot!(cb.hexdump(), @"48656c6c6f2c20776f726c6421000000"); } @@ -2053,7 +2053,7 @@ mod tests { asm.compile_with_num_regs(&mut cb, 0); assert_disasm_snapshot!(cb.disasm(), @r" - 0x0: orr x0, xzr, #0x7fffffff + 0x0: mov x0, #0x7fffffff 0x4: add x0, sp, x0 0x8: mov x0, #8 0xc: movk x0, #1, lsl #16 @@ -2069,7 +2069,7 @@ mod tests { 0x34: movk x0, #0xffff, lsl #32 0x38: movk x0, #0xffff, lsl #48 0x3c: add x0, sp, x0 - 0x40: orr x0, xzr, #0xffffffff80000000 + 0x40: mov x0, #-0x80000000 0x44: add x0, sp, x0 "); assert_snapshot!(cb.hexdump(), @"e07b40b2e063208b000180d22000a0f2e063208b000083d2e063208be0230891e02308d1e0ff8292e063208b00ff9fd2c0ffbff2e0ffdff2e0fffff2e063208be08361b2e063208b"); @@ -2137,8 +2137,8 @@ mod tests { assert_disasm_snapshot!(cb.disasm(), @" 0x0: ldr x16, #8 0x4: b #0x10 - 0x8: .byte 0x00, 0x10, 0x00, 0x00 - 0xc: .byte 0x00, 0x00, 0x00, 0x00 + 0x8: udf #0x1000 + 0xc: udf #0 0x10: stur x16, [x21] "); assert_snapshot!(cb.hexdump(), @"50000058030000140010000000000000b00200f8"); @@ -2200,7 +2200,7 @@ mod tests { 0x8: ldnp d8, d25, [x10, #-0x140] 0xc: .byte 0x6f, 0x2c, 0x20, 0x77 0x10: .byte 0x6f, 0x72, 0x6c, 0x64 - 0x14: .byte 0x21, 0x00, 0x00, 0x00 + 0x14: udf #0x21 0x18: stur x0, [x21] "); assert_snapshot!(cb.hexdump(), @"50000010e00310aa48656c6c6f2c20776f726c6421000000a00200f8"); @@ -2304,7 +2304,7 @@ mod tests { asm.compile_with_num_regs(&mut cb, 1); assert_disasm_snapshot!(cb.disasm(), @r" - 0x0: orr x0, xzr, #0xffffffff + 0x0: mov x0, #0xffffffff 0x4: tst w0, w0 "); assert_snapshot!(cb.hexdump(), @"e07f40b21f00006a"); @@ -2566,7 +2566,7 @@ mod tests { assert_disasm_snapshot!(cb.disasm(), @" 0x0: mov x1, #0xffff - 0x4: orr x1, xzr, #0x10000 + 0x4: mov x1, #0x10000 "); assert_snapshot!(cb.hexdump(), @"e1ff9fd2e10370b2"); } From 21862be0afdd76d80875b4f98ce104113090c0c3 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 9 Feb 2026 12:40:27 -0500 Subject: [PATCH 4/7] ZJIT: Add mask_name to GuardXYZBitsSet (#16064) This makes HIR easier to debug. --- zjit/src/codegen.rs | 4 ++-- zjit/src/cruby.rs | 9 +++++++++ zjit/src/hir.rs | 18 ++++++++++-------- zjit/src/hir/opt_tests.rs | 24 ++++++++++++------------ zjit/src/hir/tests.rs | 4 ++-- 5 files changed, 35 insertions(+), 24 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index a1ac23311b71db..2fe9958e62e211 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -536,8 +536,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio Insn::GuardType { val, guard_type, state } => gen_guard_type(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), Insn::GuardTypeNot { val, guard_type, state } => gen_guard_type_not(jit, asm, opnd!(val), *guard_type, &function.frame_state(*state)), &Insn::GuardBitEquals { val, expected, reason, state } => gen_guard_bit_equals(jit, asm, opnd!(val), expected, reason, &function.frame_state(state)), - &Insn::GuardAnyBitSet { val, mask, reason, state } => gen_guard_any_bit_set(jit, asm, opnd!(val), mask, reason, &function.frame_state(state)), - &Insn::GuardNoBitsSet { val, mask, reason, state } => gen_guard_no_bits_set(jit, asm, opnd!(val), mask, reason, &function.frame_state(state)), + &Insn::GuardAnyBitSet { val, mask, reason, state, .. } => gen_guard_any_bit_set(jit, asm, opnd!(val), mask, reason, &function.frame_state(state)), + &Insn::GuardNoBitsSet { val, mask, reason, state, .. } => gen_guard_no_bits_set(jit, asm, opnd!(val), mask, reason, &function.frame_state(state)), &Insn::GuardLess { left, right, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), &Insn::GuardGreaterEq { left, right, state } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), diff --git a/zjit/src/cruby.rs b/zjit/src/cruby.rs index 8c57e4cc7178e1..8e569793a87eb5 100644 --- a/zjit/src/cruby.rs +++ b/zjit/src/cruby.rs @@ -783,6 +783,12 @@ impl ID { } } +impl Display for ID { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.contents_lossy()) + } +} + /// Produce a Ruby string from a Rust string slice pub fn rust_str_to_ruby(str: &str) -> VALUE { unsafe { rb_utf8_str_new(str.as_ptr() as *const _, str.len() as i64) } @@ -1401,6 +1407,9 @@ pub(crate) mod ids { name: _ep_method_entry name: _ep_specval name: _rbasic_flags + name: RUBY_FL_FREEZE + name: RUBY_ELTS_SHARED + name: VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM } /// Get an CRuby `ID` to an interned string, e.g. a particular method name. diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index eba3edee9904a6..5d8cb2f7c0df57 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1027,9 +1027,9 @@ pub enum Insn { /// Side-exit if val is not the expected Const. GuardBitEquals { val: InsnId, expected: Const, reason: SideExitReason, state: InsnId }, /// Side-exit if (val & mask) == 0 - GuardAnyBitSet { val: InsnId, mask: Const, reason: SideExitReason, state: InsnId }, + GuardAnyBitSet { val: InsnId, mask: Const, mask_name: Option, reason: SideExitReason, state: InsnId }, /// Side-exit if (val & mask) != 0 - GuardNoBitsSet { val: InsnId, mask: Const, reason: SideExitReason, state: InsnId }, + GuardNoBitsSet { val: InsnId, mask: Const, mask_name: Option, reason: SideExitReason, state: InsnId }, /// Side-exit if left is not greater than or equal to right (both operands are C long). GuardGreaterEq { left: InsnId, right: InsnId, state: InsnId }, /// Side-exit if left is not less than right (both operands are C long). @@ -1550,7 +1550,9 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> { Insn::HasType { val, expected, .. } => { write!(f, "HasType {val}, {}", expected.print(self.ptr_map)) }, Insn::GuardTypeNot { val, guard_type, .. } => { write!(f, "GuardTypeNot {val}, {}", guard_type.print(self.ptr_map)) }, Insn::GuardBitEquals { val, expected, .. } => { write!(f, "GuardBitEquals {val}, {}", expected.print(self.ptr_map)) }, + Insn::GuardAnyBitSet { val, mask, mask_name: Some(name), .. } => { write!(f, "GuardAnyBitSet {val}, {name}={}", mask.print(self.ptr_map)) }, Insn::GuardAnyBitSet { val, mask, .. } => { write!(f, "GuardAnyBitSet {val}, {}", mask.print(self.ptr_map)) }, + Insn::GuardNoBitsSet { val, mask, mask_name: Some(name), .. } => { write!(f, "GuardNoBitsSet {val}, {name}={}", mask.print(self.ptr_map)) }, Insn::GuardNoBitsSet { val, mask, .. } => { write!(f, "GuardNoBitsSet {val}, {}", mask.print(self.ptr_map)) }, Insn::GuardLess { left, right, .. } => write!(f, "GuardLess {left}, {right}"), Insn::GuardGreaterEq { left, right, .. } => write!(f, "GuardGreaterEq {left}, {right}"), @@ -2236,8 +2238,8 @@ impl Function { &GuardType { val, guard_type, state } => GuardType { val: find!(val), guard_type, state }, &GuardTypeNot { val, guard_type, state } => GuardTypeNot { val: find!(val), guard_type, state }, &GuardBitEquals { val, expected, reason, state } => GuardBitEquals { val: find!(val), expected, reason, state }, - &GuardAnyBitSet { val, mask, reason, state } => GuardAnyBitSet { val: find!(val), mask, reason, state }, - &GuardNoBitsSet { val, mask, reason, state } => GuardNoBitsSet { val: find!(val), mask, reason, state }, + &GuardAnyBitSet { val, mask, mask_name, reason, state } => GuardAnyBitSet { val: find!(val), mask, mask_name, reason, state }, + &GuardNoBitsSet { val, mask, mask_name, reason, state } => GuardNoBitsSet { val: find!(val), mask, mask_name, reason, state }, &GuardGreaterEq { left, right, state } => GuardGreaterEq { left: find!(left), right: find!(right), state }, &GuardLess { left, right, state } => GuardLess { left: find!(left), right: find!(right), state }, &IsBlockGiven { lep } => IsBlockGiven { lep: find!(lep) }, @@ -3004,12 +3006,12 @@ impl Function { pub fn guard_not_frozen(&mut self, block: BlockId, recv: InsnId, state: InsnId) { let flags = self.load_rbasic_flags(block, recv); - self.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(RUBY_FL_FREEZE as u64), reason: SideExitReason::GuardNotFrozen, state }); + self.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(RUBY_FL_FREEZE as u64), mask_name: Some(ID!(RUBY_FL_FREEZE)), reason: SideExitReason::GuardNotFrozen, state }); } pub fn guard_not_shared(&mut self, block: BlockId, recv: InsnId, state: InsnId) { let flags = self.load_rbasic_flags(block, recv); - self.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(RUBY_ELTS_SHARED as u64), reason: SideExitReason::GuardNotShared, state }); + self.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(RUBY_ELTS_SHARED as u64), mask_name: Some(ID!(RUBY_ELTS_SHARED)), reason: SideExitReason::GuardNotShared, state }); } /// Rewrite eligible Send/SendWithoutBlock opcodes into SendDirect @@ -6794,7 +6796,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let ep = fun.push_insn(block, Insn::GetEP { level }); let flags = fun.push_insn(block, Insn::LoadField { recv: ep, id: ID!(_env_data_index_flags), offset: SIZEOF_VALUE_I32 * (VM_ENV_DATA_INDEX_FLAGS as i32), return_type: types::CInt64 }); - fun.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM.into()), reason: SideExitReason::BlockParamProxyModified, state: exit_id }); + fun.push_insn(block, Insn::GuardNoBitsSet { val: flags, mask: Const::CUInt64(VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM.into()), mask_name: Some(ID!(VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM)), reason: SideExitReason::BlockParamProxyModified, state: exit_id }); let block_handler = fun.push_insn(block, Insn::LoadField { recv: ep, id: ID!(_env_data_index_specval), offset: SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL, return_type: types::CInt64 }); @@ -6812,7 +6814,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { const _: () = assert!(RUBY_SYMBOL_FLAG & 1 == 0, "guard below rejects symbol block handlers"); // Bail out if the block handler is neither ISEQ nor ifunc - fun.push_insn(block, Insn::GuardAnyBitSet { val: block_handler, mask: Const::CUInt64(0x1), reason: SideExitReason::BlockParamProxyNotIseqOrIfunc, state: exit_id }); + fun.push_insn(block, Insn::GuardAnyBitSet { val: block_handler, mask: Const::CUInt64(0x1), mask_name: None, reason: SideExitReason::BlockParamProxyNotIseqOrIfunc, state: exit_id }); // TODO(Shopify/ruby#753): GC root, so we should be able to avoid unnecessary GC tracing state.stack_push(fun.push_insn(block, Insn::Const { val: Const::Value(unsafe { rb_block_param_proxy }) })); } diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs index e80175c6794d56..d0d2e190784d4b 100644 --- a/zjit/src/hir/opt_tests.rs +++ b/zjit/src/hir/opt_tests.rs @@ -3888,7 +3888,7 @@ mod hir_opt_tests { bb2(v8:BasicObject, v9:BasicObject): v14:CPtr = GetEP 0 v15:CInt64 = LoadField v14, :_env_data_index_flags@0x1000 - v16:CInt64 = GuardNoBitsSet v15, CUInt64(512) + v16:CInt64 = GuardNoBitsSet v15, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM=CUInt64(512) v17:CInt64 = LoadField v14, :_env_data_index_specval@0x1001 v18:CInt64 = GuardAnyBitSet v17, CUInt64(1) v19:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1008)) @@ -6560,7 +6560,7 @@ mod hir_opt_tests { v13:ArrayExact = NewArray v15:CPtr = GetEP 0 v16:CInt64 = LoadField v15, :_env_data_index_flags@0x1000 - v17:CInt64 = GuardNoBitsSet v16, CUInt64(512) + v17:CInt64 = GuardNoBitsSet v16, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM=CUInt64(512) v18:CInt64 = LoadField v15, :_env_data_index_specval@0x1001 v19:CInt64 = GuardAnyBitSet v18, CUInt64(1) v20:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1008)) @@ -6591,7 +6591,7 @@ mod hir_opt_tests { v13:ArrayExact = NewArray v15:CPtr = GetEP 0 v16:CInt64 = LoadField v15, :_env_data_index_flags@0x1000 - v17:CInt64 = GuardNoBitsSet v16, CUInt64(512) + v17:CInt64 = GuardNoBitsSet v16, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM=CUInt64(512) v18:CInt64 = LoadField v15, :_env_data_index_specval@0x1001 v19:CInt64[0] = GuardBitEquals v18, CInt64(0) v20:NilClass = Const Value(nil) @@ -6625,7 +6625,7 @@ mod hir_opt_tests { v10:ArrayExact = NewArray v12:CPtr = GetEP 1 v13:CInt64 = LoadField v12, :_env_data_index_flags@0x1000 - v14:CInt64 = GuardNoBitsSet v13, CUInt64(512) + v14:CInt64 = GuardNoBitsSet v13, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM=CUInt64(512) v15:CInt64 = LoadField v12, :_env_data_index_specval@0x1001 v16:CInt64 = GuardAnyBitSet v15, CUInt64(1) v17:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1008)) @@ -6987,7 +6987,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) v29:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] v30:CUInt64 = LoadField v29, :_rbasic_flags@0x1038 - v31:CUInt64 = GuardNoBitsSet v30, CUInt64(2048) + v31:CUInt64 = GuardNoBitsSet v30, RUBY_FL_FREEZE=CUInt64(2048) StoreField v29, :foo=@0x1039, v12 WriteBarrier v29, v12 CheckInterrupts @@ -7020,7 +7020,7 @@ mod hir_opt_tests { PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010) v29:HeapObject[class_exact:C] = GuardType v11, HeapObject[class_exact:C] v30:CUInt64 = LoadField v29, :_rbasic_flags@0x1038 - v31:CUInt64 = GuardNoBitsSet v30, CUInt64(2048) + v31:CUInt64 = GuardNoBitsSet v30, RUBY_FL_FREEZE=CUInt64(2048) v32:CPtr = LoadField v29, :_as_heap@0x1039 StoreField v32, :foo=@0x103a, v12 WriteBarrier v29, v12 @@ -7641,9 +7641,9 @@ mod hir_opt_tests { PatchPoint MethodRedefined(Array@0x1000, []=@0x1008, cme:0x1010) v31:ArrayExact = GuardType v9, ArrayExact v32:CUInt64 = LoadField v31, :_rbasic_flags@0x1038 - v33:CUInt64 = GuardNoBitsSet v32, CUInt64(2048) + v33:CUInt64 = GuardNoBitsSet v32, RUBY_FL_FREEZE=CUInt64(2048) v34:CUInt64 = LoadField v31, :_rbasic_flags@0x1038 - v35:CUInt64 = GuardNoBitsSet v34, CUInt64(4096) + v35:CUInt64 = GuardNoBitsSet v34, RUBY_ELTS_SHARED=CUInt64(4096) v36:CInt64[1] = UnboxFixnum v16 v37:CInt64 = ArrayLength v31 v38:CInt64[1] = GuardLess v36, v37 @@ -7683,9 +7683,9 @@ mod hir_opt_tests { v35:ArrayExact = GuardType v13, ArrayExact v36:Fixnum = GuardType v14, Fixnum v37:CUInt64 = LoadField v35, :_rbasic_flags@0x1038 - v38:CUInt64 = GuardNoBitsSet v37, CUInt64(2048) + v38:CUInt64 = GuardNoBitsSet v37, RUBY_FL_FREEZE=CUInt64(2048) v39:CUInt64 = LoadField v35, :_rbasic_flags@0x1038 - v40:CUInt64 = GuardNoBitsSet v39, CUInt64(4096) + v40:CUInt64 = GuardNoBitsSet v39, RUBY_ELTS_SHARED=CUInt64(4096) v41:CInt64 = UnboxFixnum v36 v42:CInt64 = ArrayLength v35 v43:CInt64 = GuardLess v41, v42 @@ -8011,7 +8011,7 @@ mod hir_opt_tests { v36:CInt64[0] = Const CInt64(0) v37:CInt64 = GuardGreaterEq v35, v36 v38:CUInt64 = LoadField v30, :_rbasic_flags@0x1039 - v39:CUInt64 = GuardNoBitsSet v38, CUInt64(2048) + v39:CUInt64 = GuardNoBitsSet v38, RUBY_FL_FREEZE=CUInt64(2048) v40:Fixnum = StringSetbyteFixnum v30, v31, v32 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts @@ -8053,7 +8053,7 @@ mod hir_opt_tests { v36:CInt64[0] = Const CInt64(0) v37:CInt64 = GuardGreaterEq v35, v36 v38:CUInt64 = LoadField v30, :_rbasic_flags@0x1039 - v39:CUInt64 = GuardNoBitsSet v38, CUInt64(2048) + v39:CUInt64 = GuardNoBitsSet v38, RUBY_FL_FREEZE=CUInt64(2048) v40:Fixnum = StringSetbyteFixnum v30, v31, v32 IncrCounter inline_cfunc_optimized_send_count CheckInterrupts diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index 17b156a663abba..fab8c6e2620090 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -2058,7 +2058,7 @@ pub mod hir_build_tests { PatchPoint NoEPEscape(test) v33:CPtr = GetEP 0 v34:CInt64 = LoadField v33, :_env_data_index_flags@0x1000 - v35:CInt64 = GuardNoBitsSet v34, CUInt64(512) + v35:CInt64 = GuardNoBitsSet v34, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM=CUInt64(512) v36:CInt64 = LoadField v33, :_env_data_index_specval@0x1001 v37:CInt64 = GuardAnyBitSet v36, CUInt64(1) v38:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1008)) @@ -3435,7 +3435,7 @@ pub mod hir_build_tests { PatchPoint NoEPEscape(open) v31:CPtr = GetEP 0 v32:CInt64 = LoadField v31, :_env_data_index_flags@0x1000 - v33:CInt64 = GuardNoBitsSet v32, CUInt64(512) + v33:CInt64 = GuardNoBitsSet v32, VM_FRAME_FLAG_MODIFIED_BLOCK_PARAM=CUInt64(512) v34:CInt64 = LoadField v31, :_env_data_index_specval@0x1001 v35:CInt64 = GuardAnyBitSet v34, CUInt64(1) v36:HeapObject[BlockParamProxy] = Const Value(VALUE(0x1008)) From 4d13aeb3caedbecab58e24c601ab8c907fac9800 Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Mon, 9 Feb 2026 12:52:35 -0500 Subject: [PATCH 5/7] ZJIT: Make defined?(yield) in non-method local iseq emit const nil (#16063) No sense doing this in codegen; we can pull this up to the HIR level. --- zjit/src/codegen.rs | 16 +++++++-------- zjit/src/hir.rs | 13 +++++++++++- zjit/src/hir/tests.rs | 46 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 10 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 2fe9958e62e211..3ba90851db0a23 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -672,15 +672,13 @@ fn gen_defined(jit: &JITState, asm: &mut Assembler, op_type: usize, obj: VALUE, // // Similar to gen_is_block_given let local_iseq = unsafe { rb_get_iseq_body_local_iseq(jit.iseq) }; - if unsafe { rb_get_iseq_body_type(local_iseq) } == ISEQ_TYPE_METHOD { - let lep = gen_get_lep(jit, asm); - let block_handler = asm.load(Opnd::mem(64, lep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL)); - let pushval = asm.load(pushval.into()); - asm.cmp(block_handler, VM_BLOCK_HANDLER_NONE.into()); - asm.csel_e(Qnil.into(), pushval) - } else { - Qnil.into() - } + assert_eq!(unsafe { rb_get_iseq_body_type(local_iseq) }, ISEQ_TYPE_METHOD, + "defined?(yield) in non-method iseq should be handled by HIR construction"); + let lep = gen_get_lep(jit, asm); + let block_handler = asm.load(Opnd::mem(64, lep, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL)); + let pushval = asm.load(pushval.into()); + asm.cmp(block_handler, VM_BLOCK_HANDLER_NONE.into()); + asm.csel_e(Qnil.into(), pushval) } _ => { // Save the PC and SP because the callee may allocate or call #respond_to? diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 5d8cb2f7c0df57..65f7c3396b8528 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -6587,7 +6587,18 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let obj = get_arg(pc, 1); let pushval = get_arg(pc, 2); let v = state.stack_pop()?; - state.stack_push(fun.push_insn(block, Insn::Defined { op_type, obj, pushval, v, state: exit_id })); + let local_iseq = unsafe { rb_get_iseq_body_local_iseq(iseq) }; + let insn = if op_type == DEFINED_YIELD as usize && unsafe { rb_get_iseq_body_type(local_iseq) } != ISEQ_TYPE_METHOD { + // `yield` goes to the block handler stowed in the "local" iseq which is + // the current iseq or a parent. Only the "method" iseq type can be passed a + // block handler. (e.g. `yield` in the top level script is a syntax error.) + // + // Similar to gen_is_block_given + Insn::Const { val: Const::Value(Qnil) } + } else { + Insn::Defined { op_type, obj, pushval, v, state: exit_id } + }; + state.stack_push(fun.push_insn(block, insn)); } YARVINSN_definedivar => { // (ID id, IVC ic, VALUE pushval) diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index fab8c6e2620090..d8975f677f7182 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -1123,6 +1123,52 @@ pub mod hir_build_tests { "); } + #[test] + fn defined_yield_in_method_local_iseq_returns_defined() { + eval(" + def test = defined?(yield) + "); + assert_contains_opcode("test", YARVINSN_defined); + assert_snapshot!(hir_string("test"), @r" + fn test@:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:NilClass = Const Value(nil) + v12:StringExact|NilClass = Defined yield, v10 + CheckInterrupts + Return v12 + "); + } + + #[test] + fn defined_yield_in_non_method_local_iseq_returns_nil() { + eval(" + define_method(:test) { defined?(yield) } + "); + assert_contains_opcode("test", YARVINSN_defined); + assert_snapshot!(hir_string("test"), @r" + fn block in @:2: + bb0(): + EntryPoint interpreter + v1:BasicObject = LoadSelf + Jump bb2(v1) + bb1(v4:BasicObject): + EntryPoint JIT(0) + Jump bb2(v4) + bb2(v6:BasicObject): + v10:NilClass = Const Value(nil) + v12:NilClass = Const Value(nil) + CheckInterrupts + Return v12 + "); + } + #[test] fn test_return_const() { eval(" From be54efb40378314cf74515cfefcf3c318fdd0e5a Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 5 Feb 2026 13:52:15 -0500 Subject: [PATCH 6/7] ZJIT: Add reason to GuardGreaterEq --- zjit/src/codegen.rs | 2 +- zjit/src/cruby_methods.rs | 12 ++++++++---- zjit/src/hir.rs | 6 +++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs index 3ba90851db0a23..e9949705fc8b50 100644 --- a/zjit/src/codegen.rs +++ b/zjit/src/codegen.rs @@ -539,7 +539,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio &Insn::GuardAnyBitSet { val, mask, reason, state, .. } => gen_guard_any_bit_set(jit, asm, opnd!(val), mask, reason, &function.frame_state(state)), &Insn::GuardNoBitsSet { val, mask, reason, state, .. } => gen_guard_no_bits_set(jit, asm, opnd!(val), mask, reason, &function.frame_state(state)), &Insn::GuardLess { left, right, state } => gen_guard_less(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), - &Insn::GuardGreaterEq { left, right, state } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), + &Insn::GuardGreaterEq { left, right, state, .. } => gen_guard_greater_eq(jit, asm, opnd!(left), opnd!(right), &function.frame_state(state)), Insn::PatchPoint { invariant, state } => no_output!(gen_patch_point(jit, asm, invariant, &function.frame_state(*state))), Insn::CCall { cfunc, recv, args, name, return_type: _, elidable: _ } => gen_ccall(asm, *cfunc, *name, opnd!(recv), opnds!(args)), // Give up CCallWithFrame for 7+ args since asm.ccall() supports at most 6 args (recv + args). diff --git a/zjit/src/cruby_methods.rs b/zjit/src/cruby_methods.rs index 8596472f5a1443..aeca0e36bd7783 100644 --- a/zjit/src/cruby_methods.rs +++ b/zjit/src/cruby_methods.rs @@ -336,7 +336,8 @@ fn inline_array_aref(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In let length = fun.push_insn(block, hir::Insn::ArrayLength { array: recv }); let index = fun.push_insn(block, hir::Insn::GuardLess { left: index, right: length, state }); let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); - let index = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: index, right: zero, state }); + use crate::hir::SideExitReason; + let index = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: index, right: zero, reason: SideExitReason::GuardGreaterEq, state }); let result = fun.push_insn(block, hir::Insn::ArrayAref { array: recv, index }); return Some(result); } @@ -359,7 +360,8 @@ fn inline_array_aset(fun: &mut hir::Function, block: hir::BlockId, recv: hir::In let length = fun.push_insn(block, hir::Insn::ArrayLength { array: recv }); let index = fun.push_insn(block, hir::Insn::GuardLess { left: index, right: length, state }); let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); - let index = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: index, right: zero, state }); + use crate::hir::SideExitReason; + let index = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: index, right: zero, reason: SideExitReason::GuardGreaterEq, state }); let _ = fun.push_insn(block, hir::Insn::ArrayAset { array: recv, index, val }); fun.push_insn(block, hir::Insn::WriteBarrier { recv, val }); @@ -451,7 +453,8 @@ fn inline_string_getbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir // This is unlike most other guards. let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, state }); let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); - let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, state }); + use crate::hir::SideExitReason; + let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, reason: SideExitReason::GuardGreaterEq, state }); let result = fun.push_insn(block, hir::Insn::StringGetbyte { string: recv, index: unboxed_index }); return Some(result); } @@ -473,7 +476,8 @@ fn inline_string_setbyte(fun: &mut hir::Function, block: hir::BlockId, recv: hir }); let unboxed_index = fun.push_insn(block, hir::Insn::GuardLess { left: unboxed_index, right: len, state }); let zero = fun.push_insn(block, hir::Insn::Const { val: hir::Const::CInt64(0) }); - let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, state }); + use crate::hir::SideExitReason; + let _ = fun.push_insn(block, hir::Insn::GuardGreaterEq { left: unboxed_index, right: zero, reason: SideExitReason::GuardGreaterEq, state }); // We know that all String are HeapObject, so no need to insert a GuardType(HeapObject). fun.guard_not_frozen(block, recv, state); let _ = fun.push_insn(block, hir::Insn::StringSetbyteFixnum { string: recv, index, value }); diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 65f7c3396b8528..63d68d9a44b0b5 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -1031,7 +1031,7 @@ pub enum Insn { /// Side-exit if (val & mask) != 0 GuardNoBitsSet { val: InsnId, mask: Const, mask_name: Option, reason: SideExitReason, state: InsnId }, /// Side-exit if left is not greater than or equal to right (both operands are C long). - GuardGreaterEq { left: InsnId, right: InsnId, state: InsnId }, + GuardGreaterEq { left: InsnId, right: InsnId, reason: SideExitReason, state: InsnId }, /// Side-exit if left is not less than right (both operands are C long). GuardLess { left: InsnId, right: InsnId, state: InsnId }, @@ -2240,7 +2240,7 @@ impl Function { &GuardBitEquals { val, expected, reason, state } => GuardBitEquals { val: find!(val), expected, reason, state }, &GuardAnyBitSet { val, mask, mask_name, reason, state } => GuardAnyBitSet { val: find!(val), mask, mask_name, reason, state }, &GuardNoBitsSet { val, mask, mask_name, reason, state } => GuardNoBitsSet { val: find!(val), mask, mask_name, reason, state }, - &GuardGreaterEq { left, right, state } => GuardGreaterEq { left: find!(left), right: find!(right), state }, + &GuardGreaterEq { left, right, reason, state } => GuardGreaterEq { left: find!(left), right: find!(right), reason, state }, &GuardLess { left, right, state } => GuardLess { left: find!(left), right: find!(right), state }, &IsBlockGiven { lep } => IsBlockGiven { lep: find!(lep) }, &GetBlockParam { level, ep_offset, state } => GetBlockParam { level, ep_offset, state: find!(state) }, @@ -4775,7 +4775,7 @@ impl Function { worklist.push_back(val); worklist.push_back(state); } - &Insn::GuardGreaterEq { left, right, state } => { + &Insn::GuardGreaterEq { left, right, state, .. } => { worklist.push_back(left); worklist.push_back(right); worklist.push_back(state); From 3d00e34ab1d533820d2cd8bc361549b336767c9c Mon Sep 17 00:00:00 2001 From: Max Bernstein Date: Thu, 5 Feb 2026 13:53:08 -0500 Subject: [PATCH 7/7] ZJIT: Check for size >= expected in expandarray Turns out `a, b = [1, 2, 3, 4]` is perfectly valid and just assigns `a = 1` and `b = 2` (and drops the rest). optcarrot relies on this. Allow it. --- zjit/src/hir.rs | 3 ++- zjit/src/hir/tests.rs | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs index 63d68d9a44b0b5..7812c6058e3cb2 100644 --- a/zjit/src/hir.rs +++ b/zjit/src/hir.rs @@ -7460,7 +7460,8 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result { let val = state.stack_pop()?; let array = fun.push_insn(block, Insn::GuardType { val, guard_type: types::ArrayExact, state: exit_id, }); let length = fun.push_insn(block, Insn::ArrayLength { array }); - fun.push_insn(block, Insn::GuardBitEquals { val: length, expected: Const::CInt64(num as i64), reason: SideExitReason::ExpandArray, state: exit_id }); + let expected = fun.push_insn(block, Insn::Const { val: Const::CInt64(num as i64) }); + fun.push_insn(block, Insn::GuardGreaterEq { left: length, right: expected, reason: SideExitReason::ExpandArray, state: exit_id }); for i in (0..num).rev() { // We do not emit a length guard here because in-bounds is already // ensured by the expandarray length check above. diff --git a/zjit/src/hir/tests.rs b/zjit/src/hir/tests.rs index d8975f677f7182..ef1d9597ddacce 100644 --- a/zjit/src/hir/tests.rs +++ b/zjit/src/hir/tests.rs @@ -3880,11 +3880,12 @@ pub mod hir_build_tests { bb2(v12:BasicObject, v13:BasicObject, v14:NilClass, v15:NilClass): v21:ArrayExact = GuardType v13, ArrayExact v22:CInt64 = ArrayLength v21 - v23:CInt64[2] = GuardBitEquals v22, CInt64(2) - v24:CInt64[1] = Const CInt64(1) - v25:BasicObject = ArrayAref v21, v24 - v26:CInt64[0] = Const CInt64(0) - v27:BasicObject = ArrayAref v21, v26 + v23:CInt64[2] = Const CInt64(2) + v24:CInt64 = GuardGreaterEq v22, v23 + v25:CInt64[1] = Const CInt64(1) + v26:BasicObject = ArrayAref v21, v25 + v27:CInt64[0] = Const CInt64(0) + v28:BasicObject = ArrayAref v21, v27 PatchPoint NoEPEscape(test) CheckInterrupts Return v13