Permalink
Cannot retrieve contributors at this time
256 lines (211 sloc)
9.06 KB
module YJIT | |
if defined?(Disasm) | |
def self.disasm(iseq, tty: $stdout && $stdout.tty?) | |
iseq = RubyVM::InstructionSequence.of(iseq) | |
blocks = YJIT.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 = YJIT::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 | |
def self.comments_for(start_address, end_address) | |
Primitive.comments_for(start_address, end_address) | |
end | |
def self.graphviz_for(iseq) | |
iseq = RubyVM::InstructionSequence.of(iseq) | |
cs = YJIT::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 | |
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 | |
end | |
# Return a hash for statistics generated for the --yjit-stats command line option. | |
# Return nil when option is not passed or unavailable. | |
def self.runtime_stats | |
# defined in yjit_iface.c | |
Primitive.get_yjit_stats | |
end | |
# Discard statistics collected for --yjit-stats. | |
def self.reset_stats! | |
# defined in yjit_iface.c | |
Primitive.reset_stats_bang | |
end | |
def self.stats_enabled? | |
Primitive.cexpr! 'rb_yjit_opts.gen_stats ? Qtrue : Qfalse' | |
end | |
def self.enabled? | |
Primitive.cexpr! 'rb_yjit_enabled_p() ? Qtrue : Qfalse' | |
end | |
class << self | |
private | |
# Format and print out counters | |
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 | |
$stderr.puts "bindings_allocations: " + ("%10d" % stats[:binding_allocations]) | |
$stderr.puts "bindings_set: " + ("%10d" % stats[:binding_set]) | |
$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 | |
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 | |
def total_exit_count(stats, prefix: "exit_") | |
total = 0 | |
stats.each do |k,v| | |
total += v if k.start_with?(prefix) | |
end | |
total | |
end | |
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 | |
end | |
end |