summaryrefslogtreecommitdiff
path: root/yjit.rb
blob: 1272e1747bda8b5caa0fd415a7c19a6737ddae35 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
module YJIT
  def self.disasm(iseq)
    iseq = RubyVM::InstructionSequence.of(iseq)

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

    str = ""

    cs = YJIT::Disasm.new

    str << iseq.disasm
    str << "\n"

    # Sort the blocks by increasing addresses
    blocks.sort_by(&:address).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"

      cs.disasm(block.code, block.address).each do |i|
        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.reduce(0, :+)
    str << "\n"
    str << "Total code size: #{total_bytes} bytes"
    str << "\n"

    str
  end if defined?(Disasm)

  # 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_stat_counters
  end

  # Discard statistics collected for --yjit-stats.
  def self.reset_stats!
    # defined in yjit_iface.c
    Primitive.reset_stats_bang
  end

  class << self
    private

    # Format and print out counters
    def _print_stats
      counters = runtime_stats

      return unless counters

      $stderr.puts("***YJIT: Printing runtime counters from yjit.rb***")

      $stderr.puts "Number of Bindings Allocated: %d\n" % counters[:binding_allocation_count]
      $stderr.puts "Number of locals modified through binding: %d\n" % counters[:local_variable_set_count]

      print_counters(counters, prefix: 'oswb_', prompt: 'opt_send_without_block exit reasons: ')
      print_counters(counters, prefix: 'leave_', prompt: 'leave exit reasons: ')
      print_counters(counters, prefix: 'getivar_', prompt: 'getinstancevariable exit reasons:')
      print_counters(counters, prefix: 'oaref_', prompt: 'opt_aref exit reasons: ')
    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