123456789_123456789_123456789_123456789_123456789_

Module: RubyVM::YJIT

Relationships & Source Files
Namespace Children
Classes:
Defined in: yjit.rb,
yjit_iface.c

Overview

This module allows for introspection of YJIT, CRuby’s experimental 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.

Class Attribute Summary

Class Method Summary

Class Attribute Details

.enabled?Boolean (readonly)

[ GitHub ]

  
# File 'yjit.rb', line 151

def self.enabled?
  Primitive.cexpr! 'rb_yjit_enabled_p() ? Qtrue : Qfalse'
end

.stats_enabled?Boolean (readonly)

[ GitHub ]

  
# File 'yjit.rb', line 147

def self.stats_enabled?
  Primitive.yjit_stats_enabled_p
end

Class Method Details

._print_stats (private)

Format and print out counters

[ GitHub ]

  
# File 'yjit.rb', line 168

def _print_stats
  stats = runtime_stats
  return unless stats

  $stderr.puts("***YJIT: Printing YJIT statistics on exit***")

  print_counters(stats, prefix: 'send_', prompt: 'method call exit reasons: ')
  print_counters(stats, prefix: 'invokesuper_', prompt: 'invokesuper exit reasons: ')
  print_counters(stats, prefix: 'leave_', prompt: 'leave exit reasons: ')
  print_counters(stats, prefix: 'gbpp_', prompt: 'getblockparamproxy exit reasons: ')
  print_counters(stats, prefix: 'getivar_', prompt: 'getinstancevariable exit reasons:')
  print_counters(stats, prefix: 'setivar_', prompt: 'setinstancevariable exit reasons:')
  print_counters(stats, prefix: 'oaref_', prompt: 'opt_aref exit reasons: ')
  print_counters(stats, prefix: 'expandarray_', prompt: 'expandarray exit reasons: ')
  print_counters(stats, prefix: 'opt_getinlinecache_', prompt: 'opt_getinlinecache exit reasons: ')
  print_counters(stats, prefix: 'invalidate_', prompt: 'invalidation reasons: ')

  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

  # 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

  # Number of failed compiler invocations
  compilation_failure = stats[:compilation_failure]

  $stderr.puts "bindings_allocations:  " + ("%10d" % stats[:binding_allocations])
  $stderr.puts "bindings_set:          " + ("%10d" % stats[:binding_set])
  $stderr.puts "compilation_failure:   " + ("%10d" % compilation_failure) if compilation_failure != 0
  $stderr.puts "compiled_iseq_count:   " + ("%10d" % stats[:compiled_iseq_count])
  $stderr.puts "compiled_block_count:  " + ("%10d" % stats[:compiled_block_count])
  $stderr.puts "invalidation_count:    " + ("%10d" % stats[:invalidation_count])
  $stderr.puts "constant_state_bumps:  " + ("%10d" % stats[:constant_state_bumps])
  $stderr.puts "inline_code_size:      " + ("%10d" % stats[:inline_code_size])
  $stderr.puts "outlined_code_size:    " + ("%10d" % stats[:outlined_code_size])

  $stderr.puts "total_exit_count:      " + ("%10d" % total_exits)
  $stderr.puts "total_insns_count:     " + ("%10d" % total_insns_count)
  $stderr.puts "vm_insns_count:        " + ("%10d" % stats[:vm_insns_count])
  $stderr.puts "yjit_insns_count:      " + ("%10d" % stats[:exec_instruction])
  $stderr.puts "ratio_in_yjit:         " + ("%9.1f" % yjit_ratio_pct) + "%"
  $stderr.puts "avg_len_in_yjit:       " + ("%10.1f" % avg_len_in_yjit)

  print_sorted_exit_counts(stats, prefix: "exit_")
end

.blocks_for(rb_iseq) (mod_func)

[ GitHub ]

  
# File 'yjit_iface.c', line 507

static VALUE
yjit_blocks_for(VALUE mod, VALUE rb_iseq)
{
    if (CLASS_OF(rb_iseq) != rb_cISeq) {
        return rb_ary_new();
    }

    const rb_iseq_t *iseq = rb_iseqw_to_iseq(rb_iseq);

    VALUE all_versions = rb_ary_new();
    rb_darray_for(iseq->body->yjit_blocks, version_array_idx) {
        rb_yjit_block_array_t versions = rb_darray_get(iseq->body->yjit_blocks, version_array_idx);

        rb_darray_for(versions, block_idx) {
            block_t *block = rb_darray_get(versions, block_idx);

            // FIXME: The object craeted here can outlive the block itself
            VALUE rb_block = TypedData_Wrap_Struct(cYjitBlock, &yjit_block_type, block);
            rb_ary_push(all_versions, rb_block);
        }
    }

    return all_versions;
}

.comments_for(start_address, end_address)

[ GitHub ]

  
# File 'yjit.rb', line 65

def self.comments_for(start_address, end_address)
  Primitive.comments_for(start_address, end_address)
end

.disasm(iseq, tty: $stdout && $stdout.tty?)

[ GitHub ]

  
# File 'yjit.rb', line 13

def self.disasm(iseq, tty: $stdout && $stdout.tty?)
  iseq = RubyVM::InstructionSequence.of(iseq)

  blocks = blocks_for(iseq)
  return if blocks.empty?

  str = String.new
  str << iseq.disasm
  str << "\n"

  # Sort the blocks by increasing addresses
  sorted_blocks = blocks.sort_by(&:address)

  highlight = ->(str) {
    if tty
      "\x1b[1m#{str}\x1b[0m"
    else
      str
    end
  }

  cs = Disasm.new
  sorted_blocks.each_with_index do |block, i|
    str << "== BLOCK #{i+1}/#{blocks.length}: #{block.code.length} BYTES, ISEQ RANGE [#{block.iseq_start_index},#{block.iseq_end_index}) ".ljust(80, "=")
    str << "\n"

    comments = comments_for(block.address, block.address + block.code.length)
    comment_idx = 0
    cs.disasm(block.code, block.address).each do |i|
      while (comment = comments[comment_idx]) && comment.address <= i.address
        str << "  ; #{highlight.call(comment.comment)}\n"
        comment_idx += 1
      end

      str << sprintf(
        "  %<address>08x:  %<instruction>s\t%<details>s\n",
        address: i.address,
        instruction: i.mnemonic,
        details: i.op_str
      )
    end
  end

  block_sizes = blocks.map { |block| block.code.length }
  total_bytes = block_sizes.sum
  str << "\n"
  str << "Total code size: #{total_bytes} bytes"
  str << "\n"

  str
end

.disasm_block(cs, block, highlight)

[ GitHub ]

  
# File 'yjit.rb', line 113

def self.disasm_block(cs, block, highlight)
  comments = comments_for(block.address, block.address + block.code.length)
  comment_idx = 0
  str = +''
  cs.disasm(block.code, block.address).each do |i|
    while (comment = comments[comment_idx]) && comment.address <= i.address
      str << "  ; #{highlight.call(comment.comment)}\n"
      comment_idx += 1
    end

    str << sprintf(
      "  %<address>08x:  %<instruction>s\t%<details>s\n",
      address: i.address,
      instruction: i.mnemonic,
      details: i.op_str
    )
  end
  str
end

.graphviz_for(iseq)

[ GitHub ]

  
# File 'yjit.rb', line 69

def self.graphviz_for(iseq)
  iseq = RubyVM::InstructionSequence.of(iseq)
  cs = Disasm.new

  highlight = ->(comment) { "<b>#{comment}</b>" }
  linebreak = "<br align=\"left\"/>\n"

  buff = +''
  blocks = blocks_for(iseq).sort_by(&:id)
  buff << "digraph g {\n"

  # Write the iseq info as a legend
  buff << "  legend [shape=record fontsize=\"30\" fillcolor=\"lightgrey\" style=\"filled\"];\n"
  buff << "  legend [label=\"{ Instruction Disassembly For: | {#{iseq.base_label}@#{iseq.absolute_path}:#{iseq.first_lineno}}}\"];\n"

  # Subgraph contains disassembly
  buff << "  subgraph disasm {\n"
  buff << "  node [shape=record fontname=\"courier\"];\n"
  buff << "  edge [fontname=\"courier\" penwidth=3];\n"
  blocks.each do |block|
    disasm = disasm_block(cs, block, highlight)

    # convert newlines to breaks that graphviz understands
    disasm.gsub!(/\n/, linebreak)

    # strip leading whitespace
    disasm.gsub!(/^\s+/, '')

    buff << "b#{block.id} [label=<#{disasm}>];\n"
    buff << block.outgoing_ids.map { |id|
      next_block = blocks.bsearch { |nb| id <=> nb.id }
      if next_block.address == (block.address + block.code.length)
        "b#{block.id} -> b#{id}[label=\"Fall\"];"
      else
        "b#{block.id} -> b#{id}[label=\"Jump\" style=dashed];"
      end
    }.join("\n")
    buff << "\n"
  end
  buff << "  }"
  buff << "}"
  buff
end

.reset_stats!

Discard statistics collected for –yjit-stats.

[ GitHub ]

  
# File 'yjit.rb', line 142

def self.reset_stats!
  # defined in yjit_iface.c
  Primitive.reset_stats_bang
end

.runtime_stats

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

[ GitHub ]

  
# File 'yjit.rb', line 136

def self.runtime_stats
  # defined in yjit_iface.c
  Primitive.get_yjit_stats
end

.simulate_oom!

[ GitHub ]

  
# File 'yjit.rb', line 155

def self.simulate_oom!
  Primitive.simulate_oom_bang
end

.total_exit_count(stats, prefix: "exit_") (private)

[ GitHub ]

  
# File 'yjit.rb', line 249

def total_exit_count(stats, prefix: "exit_")
  total = 0
  stats.each do |k,v|
    total += v if k.start_with?(prefix)
  end
  total
end