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
- .enabled? ⇒ Boolean readonly
- .stats_enabled? ⇒ Boolean readonly
Class Method Summary
- .comments_for(start_address, end_address)
- .disasm(iseq, tty: $stdout && $stdout.tty?)
- .disasm_block(cs, block, highlight)
- .graphviz_for(iseq)
-
.reset_stats!
Discard statistics collected for –yjit-stats.
-
.runtime_stats
Return a hash for statistics generated for the –yjit-stats command line option.
- .simulate_oom!
- .blocks_for(rb_iseq) mod_func
-
._print_stats
private
Format and print out counters.
- .print_counters(counters, prefix:, prompt:) private
- .print_sorted_exit_counts(stats, prefix:, how_many: 20, left_pad: 4) private
- .total_exit_count(stats, prefix: "exit_") private
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
# 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
.print_counters(counters, prefix:, prompt:) (private)
[ GitHub ]# File 'yjit.rb', line 257
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
.print_sorted_exit_counts(stats, prefix:, how_many: 20, left_pad: 4) (private)
[ GitHub ]# File 'yjit.rb', line 222
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
.reset_stats!
Discard statistics collected for –yjit-stats.
# 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.
# 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