module RubyVM::YJIT - Documentation for Ruby 3.2

# module RubyVM::YJIT

This module allows for introspection of `YJIT`, CRuby’s in-process just-in-time compiler. This module exists only to help develop `YJIT`, as such, everything in the module is highly implementation specific and comes with no API stability guarantee whatsoever.

This module may not exist if `YJIT` does not support the particular platform for which CRuby is built. There is also no API stability guarantee as to in what situations this module is defined.

### Public Class Methods

code_gc() click to toggle source

Free and recompile all existing JIT code

    # File yjit.rb, line 209
    def self.code_gc
      Primitive.rb_yjit_code_gc
    end

disasm(iseq) click to toggle source

Produce disassembly for an iseq

    # File yjit.rb, line 183
    def self.disasm(iseq)
      # If a method or proc is passed in, get its iseq
      iseq = RubyVM::InstructionSequence.of(iseq)

      if self.enabled?
        # Produce the disassembly string
        # Include the YARV iseq disasm in the string for additional context
        iseq.disasm + "\n" + Primitive.rb_yjit_disasm_iseq(iseq)
      else
        iseq.disasm
      end
    end

dump_exit_locations(filename) click to toggle source

`Marshal` dumps exit locations to the given filename.

Usage:

If ‘–yjit-exit-locations` is passed, a file named “yjit_exit_locations.dump” will automatically be generated.

If you want to collect traces manually, call ‘dump_exit_locations` directly.

Note that calling this in a script will generate stats after the dump is created, so the stats data may include exits from the dump itself.

In a script call:

    at_exit do
      RubyVM::YJIT.dump_exit_locations("my_file.dump")
    end

Then run the file with the following options:

    ruby --yjit --yjit-trace-exits test.rb

Once the code is done running, use Stackprof to read the dump file. See Stackprof documentation for options.

    # File yjit.rb, line 138
    def self.dump_exit_locations(filename)
      unless trace_exit_locations_enabled?
        raise ArgumentError, "--yjit-trace-exits must be enabled to use dump_exit_locations."
      end

      File.binwrite(filename, Marshal.dump(RubyVM::YJIT.exit_locations))
    end

enabled?() click to toggle source

Check if `YJIT` is enabled

    # File yjit.rb, line 13
    def self.enabled?
      Primitive.cexpr! 'RBOOL(rb_yjit_enabled_p())'
    end

exit_locations() click to toggle source

If –yjit-trace-exits is enabled parse the hashes from Primitive.rb_yjit_get_exit_locations into a format readable by Stackprof. This will allow us to find the exact location of a side exit in `YJIT` based on the instruction that is exiting.

    # File yjit.rb, line 36
    def self.exit_locations
      return unless trace_exit_locations_enabled?

      results = Primitive.rb_yjit_get_exit_locations
      raw_samples = results[:raw].dup
      line_samples = results[:lines].dup
      frames = results[:frames].dup
      samples_count = 0

      # Loop through the instructions and set the frame hash with the data.
      # We use nonexistent.def for the file name, otherwise insns.def will be displayed
      # and that information isn't useful in this context.
      RubyVM::INSTRUCTION_NAMES.each_with_index do |name, frame_id|
        frame_hash = { samples: 0, total_samples: 0, edges: {}, name: name, file: "nonexistent.def", line: nil, lines: {} }
        results[:frames][frame_id] = frame_hash
        frames[frame_id] = frame_hash
      end

      # Loop through the raw_samples and build the hashes for StackProf.
      # The loop is based off an example in the StackProf documentation and therefore
      # this functionality can only work with that library.
      #
      # Raw Samples:
      # [ length, frame1, frame2, frameN, ..., instruction, count
      #
      # Line Samples
      # [ length, line_1, line_2, line_n, ..., dummy value, count
      i = 0
      while i < raw_samples.length
        stack_length = raw_samples[i] + 1
        i += 1 # consume the stack length

        prev_frame_id = nil
        stack_length.times do |idx|
          idx += i
          frame_id = raw_samples[idx]

          if prev_frame_id
            prev_frame = frames[prev_frame_id]
            prev_frame[:edges][frame_id] ||= 0
            prev_frame[:edges][frame_id] += 1
          end

          frame_info = frames[frame_id]
          frame_info[:total_samples] += 1

          frame_info[:lines][line_samples[idx]] ||= [0, 0]
          frame_info[:lines][line_samples[idx]][0] += 1

          prev_frame_id = frame_id
        end

        i += stack_length # consume the stack

        top_frame_id = prev_frame_id
        top_frame_line = 1

        sample_count = raw_samples[i]

        frames[top_frame_id][:samples] += sample_count
        frames[top_frame_id][:lines] ||= {}
        frames[top_frame_id][:lines][top_frame_line] ||= [0, 0]
        frames[top_frame_id][:lines][top_frame_line][1] += sample_count

        samples_count += sample_count
        i += 1
      end

      results[:samples] = samples_count
      # Set missed_samples and gc_samples to 0 as their values
      # don't matter to us in this context.
      results[:missed_samples] = 0
      results[:gc_samples] = 0
      results
    end

insns_compiled(iseq) click to toggle source

Produce a list of instructions compiled by `YJIT` for an iseq

    # File yjit.rb, line 197
    def self.insns_compiled(iseq)
      # If a method or proc is passed in, get its iseq
      iseq = RubyVM::InstructionSequence.of(iseq)

      if self.enabled?
        Primitive.rb_yjit_insns_compiled(iseq)
      else
        Qnil
      end
    end

reset_stats!() click to toggle source

Discard statistics collected for –yjit-stats.

    # File yjit.rb, line 28
    def self.reset_stats!
      Primitive.rb_yjit_reset_stats_bang
    end

runtime_stats() click to toggle source

Return a hash for statistics generated for the –yjit-stats command line option. Return nil when option is not passed or unavailable.

    # File yjit.rb, line 148
    def self.runtime_stats
      stats = Primitive.rb_yjit_get_stats
      return stats if stats.nil?

      stats[:object_shape_count] = Primitive.object_shape_count
      return stats unless Primitive.rb_yjit_stats_enabled_p

      side_exits = total_exit_count(stats)
      total_exits = side_exits + stats[:leave_interp_return]

      # Number of instructions that finish executing in YJIT.
      # See :count-placement: about the subtraction.
      retired_in_yjit = stats[:exec_instruction] - side_exits

      # Average length of instruction sequences executed by YJIT
      avg_len_in_yjit = retired_in_yjit.to_f / total_exits

      # This only available on yjit stats builds
      if stats.key?(:vm_insns_count)
        # Proportion of instructions that retire in YJIT
        total_insns_count = retired_in_yjit + stats[:vm_insns_count]
        yjit_ratio_pct = 100.0 * retired_in_yjit.to_f / total_insns_count
        stats[:total_insns_count] = total_insns_count
        stats[:ratio_in_yjit] = yjit_ratio_pct
      end

      # Make those stats available in RubyVM::YJIT.runtime_stats as well
      stats[:side_exit_count]  = side_exits
      stats[:total_exit_count] = total_exits
      stats[:avg_len_in_yjit]  = avg_len_in_yjit

      stats
    end

stats_enabled?() click to toggle source

Check if –yjit-stats is used.

    # File yjit.rb, line 18
    def self.stats_enabled?
      Primitive.rb_yjit_stats_enabled_p
    end

trace_exit_locations_enabled?() click to toggle source

Check if rb_yjit_trace_exit_locations_enabled_p is enabled.

    # File yjit.rb, line 23
    def self.trace_exit_locations_enabled?
      Primitive.rb_yjit_trace_exit_locations_enabled_p
    end