123456789_123456789_123456789_123456789_123456789_

Module: RubyVM::ZJIT

Relationships & Source Files
Defined in: zjit.rb

Overview

This module allows for introspection of ZJIT, CRuby’s just-in-time compiler. Everything in the module is highly implementation specific and the API might be less stable compared to the standard library.

This module may not exist if ZJIT does not support the particular platform for which CRuby is built.

Class Attribute Summary

Class Method Summary

Class Attribute Details

.enabled?Boolean (readonly)

Check if ZJIT is enabled

[ GitHub ]

  
# File 'zjit.rb', line 21

def enabled?
  Primitive.cexpr! 'RBOOL(rb_zjit_enabled_p)'
end

.stats_enabled?Boolean (readonly)

Check if –zjit-stats is used

[ GitHub ]

  
# File 'zjit.rb', line 138

def stats_enabled?
  Primitive.rb_zjit_stats_enabled_p
end

.trace_exit_locations_enabled?Boolean (readonly)

Check if –zjit-trace-exits is used

[ GitHub ]

  
# File 'zjit.rb', line 26

def trace_exit_locations_enabled?
  Primitive.rb_zjit_trace_exit_locations_enabled_p
end

Class Method Details

.assert_compiles

This method is for internal use only.

Assert that any future ZJIT compilation will return a function pointer

[ GitHub ]

  
# File 'zjit.rb', line 211

def assert_compiles # :nodoc:
  Primitive.rb_zjit_assert_compiles
end

.dump_exit_locations(filename)

::Marshal dumps exit locations to the given filename.

Usage:

In a script call:

RubyVM::ZJIT.dump_exit_locations("my_file.dump")

Then run the file with the following options:

ruby --zjit --zjit-stats --zjit-trace-exits test.rb

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

[ GitHub ]

  
# File 'zjit.rb', line 126

def dump_exit_locations(filename)
  unless trace_exit_locations_enabled?
    raise ArgumentError, "--zjit-trace-exits must be enabled to use dump_exit_locations."
  end

  File.open(filename, "wb") do |file|
    Marshal.dump(RubyVM::ZJIT.exit_locations, file)
    file.size
  end
end

.dump_locations (private)

This method is for internal use only.
[ GitHub ]

  
# File 'zjit.rb', line 279

def dump_locations # :nodoc:
  return unless trace_exit_locations_enabled?

  filename = "zjit_exits_#{Process.pid}.dump"
  n_bytes = dump_exit_locations(filename)

  $stderr.puts("#{n_bytes} bytes written to #{filename}.")
end

.exit_locations

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

[ GitHub ]

  
# File 'zjit.rb', line 34

def exit_locations
  return unless trace_exit_locations_enabled?

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

  # Use nonexistent.def as a dummy file name.
  frame_template = { samples: 0, total_samples: 0, edges: {}, name: name, file: "nonexistent.def", line: nil, lines: {} }

  # Loop through all possible instructions and setup the frame hash.
  RubyVM::INSTRUCTION_NAMES.each_with_index do |name, frame_id|
    frames[frame_id] = frame_template.dup.tap { |h| h[:name] = name }
  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

  # These values are mandatory to include for stackprof, but we don't use them.
  results[:missed_samples] = 0
  results[:gc_samples] = 0

  results[:frames].reject! { |k, v| v[:samples] == 0 }

  results
end

.number_with_delimiter(number) (private)

This method is for internal use only.
[ GitHub ]

  
# File 'zjit.rb', line 267

def number_with_delimiter(number)
  s = number.to_s
  i = s.index('.') || s.size
  s.insert(i -= 3, ',') while i > 3
  s
end

.reset_stats!

Discard statistics collected for –zjit-stats.

[ GitHub ]

  
# File 'zjit.rb', line 148

def reset_stats!
  Primitive.rb_zjit_reset_stats_bang
end

.stats(target_key = nil)

Return ZJIT statistics as a ::Hash

[ GitHub ]

  
# File 'zjit.rb', line 143

def stats(target_key = nil)
  Primitive.rb_zjit_stats(target_key)
end

.stats_string

Get the summary of ZJIT statistics as a ::String

[ GitHub ]

  
# File 'zjit.rb', line 153

def stats_string
  buf = +"***ZJIT: Printing ZJIT statistics on exit***\n"
  stats = self.stats

  # Show counters independent from exit_* or dynamic_send_*
  print_counters_with_prefix(prefix: 'not_inlined_cfuncs_', prompt: 'not inlined C methods', buf:, stats:, limit: 20)

  # Show fallback counters, ordered by the typical amount of fallbacks for the prefix at the time
  print_counters_with_prefix(prefix: 'unspecialized_def_type_', prompt: 'not optimized method types', buf:, stats:, limit: 20)
  print_counters_with_prefix(prefix: 'not_optimized_yarv_insn_', prompt: 'not optimized instructions', buf:, stats:, limit: 20)
  print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback reasons', buf:, stats:, limit: 20)

  # Show exit counters, ordered by the typical amount of exits for the prefix at the time
  print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20)
  print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20)
  print_counters_with_prefix(prefix: 'exit_', prompt: 'side exit reasons', buf:, stats:, limit: 20)

  # Show no-prefix counters, having the most important stat `ratio_in_zjit` at the end
  print_counters([
    :send_count,
    :dynamic_send_count,
    :optimized_send_count,
    :iseq_optimized_send_count,
    :inline_cfunc_optimized_send_count,
    :non_variadic_cfunc_optimized_send_count,
    :variadic_cfunc_optimized_send_count,
  ], buf:, stats:, right_align: true, base: :send_count)
  print_counters([
    :dynamic_getivar_count,
    :dynamic_setivar_count,

    :compiled_iseq_count,
    :failed_iseq_count,

    :compile_time_ns,
    :profile_time_ns,
    :gc_time_ns,
    :invalidation_time_ns,

    :vm_write_pc_count,
    :vm_write_sp_count,
    :vm_write_locals_count,
    :vm_write_stack_count,
    :vm_write_to_parent_iseq_local_count,
    :vm_read_from_parent_iseq_local_count,

    :code_region_bytes,
    :side_exit_count,
    :total_insn_count,
    :vm_insn_count,
    :zjit_insn_count,
    :ratio_in_zjit,
  ], buf:, stats:)

  buf
end