In Files

  • yjit.rb
  • ast.c

Files

Class/Module Index [+]

Quicksearch

RubyVM::YJIT

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.

Public Class Methods

blocks_for(p1) click to toggle source

Get a list of the YJIT blocks associated with `rb_iseq`

 
               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) click to toggle source
 
               # File yjit.rb, line 64
def self.comments_for(start_address, end_address)
  Primitive.comments_for(start_address, end_address)
end
            
disasm(iseq, tty: $stdout && $stdout.tty?) click to toggle source
 
               # File yjit.rb, line 12
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) click to toggle source
 
               # File yjit.rb, line 112
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
            
enabled?() click to toggle source
 
               # File yjit.rb, line 150
def self.enabled?
  Primitive.cexpr! 'rb_yjit_enabled_p() ? Qtrue : Qfalse'
end
            
graphviz_for(iseq) click to toggle source
 
               # File yjit.rb, line 68
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!() click to toggle source

Discard statistics collected for –yjit-stats.

 
               # File yjit.rb, line 141
def self.reset_stats!
  # defined in yjit_iface.c
  Primitive.reset_stats_bang
end
            
runtime_stats() click to toggle source

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

 
               # File yjit.rb, line 135
def self.runtime_stats
  # defined in yjit_iface.c
  Primitive.get_yjit_stats
end
            
simulate_oom!() click to toggle source
 
               # File yjit.rb, line 154
def self.simulate_oom!
  Primitive.simulate_oom_bang
end
            
stats_enabled?() click to toggle source
 
               # File yjit.rb, line 146
def self.stats_enabled?
  Primitive.yjit_stats_enabled_p
end
            

Private Class Methods

_print_stats() click to toggle source

Format and print out counters

 
               # File yjit.rb, line 167
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
            
total_exit_count(stats, prefix: "exit_") click to toggle source
 
               # File yjit.rb, line 248
def total_exit_count(stats, prefix: "exit_")
  total = 0
  stats.each do |k,v|
    total += v if k.start_with?(prefix)
  end
  total
end