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.
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; }
# 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
# 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
# File yjit.rb, line 150 def self.enabled? Primitive.cexpr! 'rb_yjit_enabled_p() ? Qtrue : Qfalse' end
# 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
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
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
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
# File yjit.rb, line 256 def print_counters(counters, prefix:, prompt:) $stderr.puts(prompt) counters = counters.filter { |key, _| key.start_with?(prefix) } counters.filter! { |_, value| value != 0 } counters.transform_keys! { |key| key.to_s.delete_prefix(prefix) } if counters.empty? $stderr.puts(" (all relevant counters are zero)") return end counters = counters.to_a counters.sort_by! { |(_, counter_value)| counter_value } longest_name_length = counters.max_by { |(name, _)| name.length }.first.length total = counters.sum { |(_, counter_value)| counter_value } counters.reverse_each do |(name, value)| percentage = value.fdiv(total) * 100 $stderr.printf(" %*s %10d (%4.1f%%)\n", longest_name_length, name, value, percentage); end end
# File yjit.rb, line 221 def print_sorted_exit_counts(stats, prefix:, how_many: 20, left_pad: 4) exits = [] stats.each do |k, v| if k.start_with?(prefix) exits.push [k.to_s.delete_prefix(prefix), v] end end exits = exits.sort_by { |name, count| -count }[0...how_many] total_exits = total_exit_count(stats) top_n_total = exits.map { |name, count| count }.sum top_n_exit_pct = 100.0 * top_n_total / total_exits $stderr.puts "Top-#{how_many} most frequent exit ops (#{"%.1f" % top_n_exit_pct}% of exits):" longest_insn_name_len = exits.map { |name, count| name.length }.max exits.each do |name, count| padding = longest_insn_name_len + left_pad padded_name = "%#{padding}s" % name padded_count = "%10d" % count percent = 100.0 * count / total_exits formatted_percent = "%.1f" % percent $stderr.puts("#{padded_name}: #{padded_count} (#{formatted_percent}%)" ) end end