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.
# File tmp/rubies/ruby-3.2.0/yjit.rb, line 209
def self.code_gc
Primitive.rb_yjit_code_gc
end
Free and recompile all existing JIT code
# File tmp/rubies/ruby-3.2.0/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
Produce disassembly for an iseq
# File tmp/rubies/ruby-3.2.0/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
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 tmp/rubies/ruby-3.2.0/yjit.rb, line 13
def self.enabled?
Primitive.cexpr! 'RBOOL(rb_yjit_enabled_p())'
end
Check if YJIT
is enabled
# File tmp/rubies/ruby-3.2.0/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
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 tmp/rubies/ruby-3.2.0/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
Produce a list of instructions compiled by YJIT
for an iseq
# File tmp/rubies/ruby-3.2.0/yjit.rb, line 28
def self.reset_stats!
Primitive.rb_yjit_reset_stats_bang
end
Discard statistics collected for –yjit-stats.
# File tmp/rubies/ruby-3.2.0/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
Return a hash for statistics generated for the –yjit-stats command line option. Return nil when option is not passed or unavailable.
# File tmp/rubies/ruby-3.2.0/yjit.rb, line 18
def self.stats_enabled?
Primitive.rb_yjit_stats_enabled_p
end
Check if –yjit-stats is used.
# File tmp/rubies/ruby-3.2.0/yjit.rb, line 23
def self.trace_exit_locations_enabled?
Primitive.rb_yjit_trace_exit_locations_enabled_p
end
Check if rb_yjit_trace_exit_locations_enabled_p is enabled.