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
Free and recompile all existing JIT code
# File ruby_3_3_0_preview2/yjit.rb, line 218 def self.code_gc Primitive.rb_yjit_code_gc end
Produce disassembly for an iseq
# File ruby_3_3_0_preview2/yjit.rb, line 195 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
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 ruby_3_3_0_preview2/yjit.rb, line 143 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
Check if YJIT
is enabled
# File ruby_3_3_0_preview2/yjit.rb, line 13 def self.enabled? Primitive.cexpr! 'RBOOL(rb_yjit_enabled_p())' end
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 ruby_3_3_0_preview2/yjit.rb, line 41 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] i += 1 # consume the stack length sample_count = raw_samples[i + 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] += sample_count end frame_info = frames[frame_id] frame_info[:total_samples] += sample_count frame_info[:lines][line_samples[idx]] ||= [0, 0] frame_info[:lines][line_samples[idx]][0] += sample_count prev_frame_id = frame_id end i += stack_length # consume the stack top_frame_id = prev_frame_id top_frame_line = 1 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
Produce a list of instructions compiled by YJIT
for an iseq
# File ruby_3_3_0_preview2/yjit.rb, line 209 def self.insns_compiled(iseq) return nil unless self.enabled? # If a method or proc is passed in, get its iseq iseq = RubyVM::InstructionSequence.of(iseq) Primitive.rb_yjit_insns_compiled(iseq) end
Discard statistics collected for –yjit-stats.
# File ruby_3_3_0_preview2/yjit.rb, line 28 def self.reset_stats! Primitive.rb_yjit_reset_stats_bang end
Resume YJIT
compilation after paused on startup with –yjit-pause
# File ruby_3_3_0_preview2/yjit.rb, line 33 def self.resume Primitive.rb_yjit_resume end
Return a hash for statistics generated for the –yjit-stats command line option. Return nil when option is not passed or unavailable.
# File ruby_3_3_0_preview2/yjit.rb, line 153 def self.runtime_stats(context: false) stats = Primitive.rb_yjit_get_stats(context) 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[:yjit_insns_count] - side_exits # Average length of instruction sequences executed by YJIT avg_len_in_yjit = total_exits > 0 ? retired_in_yjit.to_f / total_exits : 0 # 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 # 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
Check if –yjit-stats is used.
# File ruby_3_3_0_preview2/yjit.rb, line 18 def self.stats_enabled? Primitive.rb_yjit_stats_enabled_p end
Format and print out counters as a String
. This returns a non-empty content only when –yjit-stats is enabled.
# File ruby_3_3_0_preview2/yjit.rb, line 186 def self.stats_string # Lazily require StringIO to avoid breaking miniruby require 'stringio' strio = StringIO.new _print_stats(out: strio) strio.string end
Check if rb_yjit_trace_exit_locations_enabled_p is enabled.
# File ruby_3_3_0_preview2/yjit.rb, line 23 def self.trace_exit_locations_enabled? Primitive.rb_yjit_trace_exit_locations_enabled_p end
Private Class Methods
Format large numbers with comma separators for readability
# File ruby_3_3_0_preview2/yjit.rb, line 408 def format_number(pad, number) integer, decimal = number.to_s.split(".") d_groups = integer.chars.to_a.reverse.each_slice(3) with_commas = d_groups.map(&:join).join(',').reverse formatted = [with_commas, decimal].compact.join(".") formatted.rjust(pad, ' ') end
Format a number along with a percentage over a total value
# File ruby_3_3_0_preview2/yjit.rb, line 417 def format_number_pct(pad, number, total) padded_count = format_number(pad, number) percentage = number.fdiv(total) * 100 formatted_pct = "%4.1f%%" % percentage "#{padded_count} (#{formatted_pct})" end