🔍

No recent searches

# module ZJIT

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.

### Public Class Methods

dump_exit_locations (filename)
Source

    # File zjit.rb, line 140
    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

`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.

enable ()
Source

    # File zjit.rb, line 31
    def enable
      return false if enabled?

      if Primitive.cexpr! 'RBOOL(rb_yjit_enabled_p)'
        warn("Only one JIT can be enabled at the same time.")
        return false
      end

      Primitive.rb_zjit_enable
    end

Enable ZJIT compilation.

enabled? ()
Source

    # File zjit.rb, line 26
    def enabled?
      Primitive.cexpr! 'RBOOL(rb_zjit_enabled_p)'
    end

Check if ZJIT is enabled

exit_locations ()
Source

    # File zjit.rb, line 51
    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
    end

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.

reset_stats! ()
Source

    # File zjit.rb, line 162
    def reset_stats!
      Primitive.rb_zjit_reset_stats_bang
    end

Discard statistics collected for ‘–zjit-stats`.

stats (target_key = nil)
Source

    # File zjit.rb, line 157
    def stats(target_key = nil)
      Primitive.rb_zjit_stats(target_key)
    end

Return ZJIT statistics as a `Hash`

stats_enabled? ()
Source

    # File zjit.rb, line 152
    def stats_enabled?
      Primitive.rb_zjit_stats_enabled_p
    end

Check if ‘–zjit-stats` is used

stats_string ()
Source

    # File zjit.rb, line 167
    def stats_string
      return unless stats = self.stats
      buf = +"***ZJIT: Printing ZJIT statistics on exit***\n"

      if stats[:guard_type_count]&.nonzero?
        stats[:guard_type_exit_ratio] = stats[:exit_guard_type_failure].to_f / stats[:guard_type_count] * 100
      end
      if stats[:guard_shape_count]&.nonzero?
        stats[:guard_shape_exit_ratio] = stats[:exit_guard_shape_failure].to_f / stats[:guard_shape_count] * 100
      end

      # 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)
      print_counters_with_prefix(prefix: 'ccall_', prompt: 'calls to C functions from JIT code', buf:, stats:, limit: 20)
      # Don't show not_annotated_cfuncs right now because it mostly duplicates not_inlined_cfuncs
      # print_counters_with_prefix(prefix: 'not_annotated_cfuncs_', prompt: 'not annotated 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_send_def_type_', prompt: 'not optimized method types for send', buf:, stats:, limit: 20)
      print_counters_with_prefix(prefix: 'unspecialized_send_without_block_def_type_', prompt: 'not optimized method types for send_without_block', buf:, stats:, limit: 20)
      print_counters_with_prefix(prefix: 'uncategorized_fallback_yarv_insn_', prompt: 'instructions with uncategorized fallback reason', buf:, stats:, limit: 20)
      print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback reasons', buf:, stats:, limit: 20)
      print_counters_with_prefix(prefix: 'setivar_fallback_', prompt: 'setivar fallback reasons', buf:, stats:, limit: 5)
      print_counters_with_prefix(prefix: 'getivar_fallback_', prompt: 'getivar fallback reasons', buf:, stats:, limit: 5)
      print_counters_with_prefix(prefix: 'definedivar_fallback_', prompt: 'definedivar fallback reasons', buf:, stats:, limit: 5)
      print_counters_with_prefix(prefix: 'invokeblock_handler_', prompt: 'invokeblock handler', buf:, stats:, limit: 10)

      # Show most popular unsupported call features. Because each call can
      # use multiple complex features, a decrease in this number does not
      # necessarily mean an increase in number of optimized calls.
      print_counters_with_prefix(prefix: 'complex_arg_pass_', prompt: 'popular complex argument-parameter features not optimized', buf:, stats:, limit: 10)

      # Show exit counters, ordered by the typical amount of exits for the prefix at the time
      print_counters_with_prefix(prefix: 'compile_error_', prompt: 'compile error reasons', buf:, stats:, limit: 20)
      print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20)
      print_counters_with_prefix(prefix: 'unhandled_hir_insn_', prompt: 'unhandled HIR insns', 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,
        :dynamic_setivar_count,
        :dynamic_getivar_count,
        :dynamic_definedivar_count,
        :iseq_optimized_send_count,
        :inline_cfunc_optimized_send_count,
        :inline_iseq_optimized_send_count,
        :non_variadic_cfunc_optimized_send_count,
        :variadic_cfunc_optimized_send_count,
      ], buf:, stats:, right_align: true, base: :send_count)
      print_counters([
        :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,

        :guard_type_count,
        :guard_type_exit_ratio,
        :guard_shape_count,
        :guard_shape_exit_ratio,

        :code_region_bytes,
        :zjit_alloc_bytes,
        :total_mem_bytes,

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

      buf
    end

Get the summary of ZJIT statistics as a `String`

trace_exit_locations_enabled? ()
Source

    # File zjit.rb, line 43
    def trace_exit_locations_enabled?
      Primitive.rb_zjit_trace_exit_locations_enabled_p
    end

Check if ‘–zjit-trace-exits` is used