2021-12-11 18:45:07 -05:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
# 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
|
2021-10-25 10:45:22 -04:00
|
|
|
# API stability guarantee whatsoever.
|
|
|
|
#
|
2021-12-11 18:45:07 -05:00
|
|
|
# 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.
|
2021-10-27 16:10:25 -04:00
|
|
|
module RubyVM::YJIT
|
2021-04-07 15:27:05 -04:00
|
|
|
if defined?(Disasm)
|
|
|
|
def self.disasm(iseq, tty: $stdout && $stdout.tty?)
|
|
|
|
iseq = RubyVM::InstructionSequence.of(iseq)
|
2021-01-22 15:24:04 -05:00
|
|
|
|
2021-10-27 16:10:25 -04:00
|
|
|
blocks = blocks_for(iseq)
|
2021-04-07 15:27:05 -04:00
|
|
|
return if blocks.empty?
|
Directly link libcapstone for easier development
This lets us use libcapstone directly from miniruby so we don't need a
Ruby Gem to to dev work.
Example usage:
```ruby
def foo(x)
if x < 1
"wow"
else
"neat"
end
end
iseq = RubyVM::InstructionSequence.of(method(:foo))
puts UJIT.disasm(iseq)
100.times { foo 1 }
puts UJIT.disasm(iseq)
```
Then in the terminal
```
$ ./miniruby test.rb
== disasm: #<ISeq:foo@test.rb:1 (1,0)-(7,3)> (catch: FALSE)
local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] x@0<Arg>
0000 getlocal_WC_0 x@0 ( 2)[LiCa]
0002 putobject_INT2FIX_1_
0003 opt_lt <calldata!mid:<, argc:1, ARGS_SIMPLE>
0005 branchunless 10
0007 putstring "wow" ( 3)[Li]
0009 leave ( 7)[Re]
0010 putstring "neat" ( 5)[Li]
0012 leave ( 7)[Re]
== ISEQ RANGE: 10 -> 10 ========================================================
0x0: movabs rax, 0x7fe816e2d1a0
0xa: mov qword ptr [rdi], rax
0xd: mov r8, rax
0x10: mov r9, rax
0x13: mov r11, r12
0x16: jmp qword ptr [rax]
== ISEQ RANGE: 0 -> 7 ==========================================================
0x0: mov rax, qword ptr [rdi + 0x20]
0x4: mov rax, qword ptr [rax - 0x18]
0x8: mov qword ptr [rdx], rax
0xb: mov qword ptr [rdx + 8], 3
0x13: movabs rax, 0x7fe817808200
0x1d: test byte ptr [rax + 0x3e6], 1
0x24: jne 0x3ffff7b
0x2a: test byte ptr [rdx], 1
0x2d: je 0x3ffff7b
0x33: test byte ptr [rdx + 8], 1
0x37: je 0x3ffff7b
0x3d: mov rax, qword ptr [rdx]
0x40: cmp rax, qword ptr [rdx + 8]
0x44: movabs rax, 0
0x4e: movabs rcx, 0x14
0x58: cmovl rax, rcx
0x5c: mov qword ptr [rdx], rax
0x5f: test qword ptr [rdx], -9
0x66: jne 0x3ffffd5
```
Make sure to `brew install pkg-config capstone`
2021-01-22 13:43:26 -05:00
|
|
|
|
2021-06-14 21:02:32 -04:00
|
|
|
str = String.new
|
2021-04-07 15:27:05 -04:00
|
|
|
str << iseq.disasm
|
|
|
|
str << "\n"
|
Directly link libcapstone for easier development
This lets us use libcapstone directly from miniruby so we don't need a
Ruby Gem to to dev work.
Example usage:
```ruby
def foo(x)
if x < 1
"wow"
else
"neat"
end
end
iseq = RubyVM::InstructionSequence.of(method(:foo))
puts UJIT.disasm(iseq)
100.times { foo 1 }
puts UJIT.disasm(iseq)
```
Then in the terminal
```
$ ./miniruby test.rb
== disasm: #<ISeq:foo@test.rb:1 (1,0)-(7,3)> (catch: FALSE)
local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] x@0<Arg>
0000 getlocal_WC_0 x@0 ( 2)[LiCa]
0002 putobject_INT2FIX_1_
0003 opt_lt <calldata!mid:<, argc:1, ARGS_SIMPLE>
0005 branchunless 10
0007 putstring "wow" ( 3)[Li]
0009 leave ( 7)[Re]
0010 putstring "neat" ( 5)[Li]
0012 leave ( 7)[Re]
== ISEQ RANGE: 10 -> 10 ========================================================
0x0: movabs rax, 0x7fe816e2d1a0
0xa: mov qword ptr [rdi], rax
0xd: mov r8, rax
0x10: mov r9, rax
0x13: mov r11, r12
0x16: jmp qword ptr [rax]
== ISEQ RANGE: 0 -> 7 ==========================================================
0x0: mov rax, qword ptr [rdi + 0x20]
0x4: mov rax, qword ptr [rax - 0x18]
0x8: mov qword ptr [rdx], rax
0xb: mov qword ptr [rdx + 8], 3
0x13: movabs rax, 0x7fe817808200
0x1d: test byte ptr [rax + 0x3e6], 1
0x24: jne 0x3ffff7b
0x2a: test byte ptr [rdx], 1
0x2d: je 0x3ffff7b
0x33: test byte ptr [rdx + 8], 1
0x37: je 0x3ffff7b
0x3d: mov rax, qword ptr [rdx]
0x40: cmp rax, qword ptr [rdx + 8]
0x44: movabs rax, 0
0x4e: movabs rcx, 0x14
0x58: cmovl rax, rcx
0x5c: mov qword ptr [rdx], rax
0x5f: test qword ptr [rdx], -9
0x66: jne 0x3ffffd5
```
Make sure to `brew install pkg-config capstone`
2021-01-22 13:43:26 -05:00
|
|
|
|
2021-04-07 15:27:05 -04:00
|
|
|
# Sort the blocks by increasing addresses
|
|
|
|
sorted_blocks = blocks.sort_by(&:address)
|
|
|
|
|
|
|
|
highlight = ->(str) {
|
|
|
|
if tty
|
|
|
|
"\x1b[1m#{str}\x1b[0m"
|
|
|
|
else
|
|
|
|
str
|
|
|
|
end
|
|
|
|
}
|
|
|
|
|
2021-10-27 16:10:25 -04:00
|
|
|
cs = Disasm.new
|
2021-04-07 15:27:05 -04:00
|
|
|
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
|
2021-04-08 14:31:27 -04:00
|
|
|
str << " ; #{highlight.call(comment.comment)}\n"
|
2021-04-07 15:27:05 -04:00
|
|
|
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
|
Directly link libcapstone for easier development
This lets us use libcapstone directly from miniruby so we don't need a
Ruby Gem to to dev work.
Example usage:
```ruby
def foo(x)
if x < 1
"wow"
else
"neat"
end
end
iseq = RubyVM::InstructionSequence.of(method(:foo))
puts UJIT.disasm(iseq)
100.times { foo 1 }
puts UJIT.disasm(iseq)
```
Then in the terminal
```
$ ./miniruby test.rb
== disasm: #<ISeq:foo@test.rb:1 (1,0)-(7,3)> (catch: FALSE)
local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] x@0<Arg>
0000 getlocal_WC_0 x@0 ( 2)[LiCa]
0002 putobject_INT2FIX_1_
0003 opt_lt <calldata!mid:<, argc:1, ARGS_SIMPLE>
0005 branchunless 10
0007 putstring "wow" ( 3)[Li]
0009 leave ( 7)[Re]
0010 putstring "neat" ( 5)[Li]
0012 leave ( 7)[Re]
== ISEQ RANGE: 10 -> 10 ========================================================
0x0: movabs rax, 0x7fe816e2d1a0
0xa: mov qword ptr [rdi], rax
0xd: mov r8, rax
0x10: mov r9, rax
0x13: mov r11, r12
0x16: jmp qword ptr [rax]
== ISEQ RANGE: 0 -> 7 ==========================================================
0x0: mov rax, qword ptr [rdi + 0x20]
0x4: mov rax, qword ptr [rax - 0x18]
0x8: mov qword ptr [rdx], rax
0xb: mov qword ptr [rdx + 8], 3
0x13: movabs rax, 0x7fe817808200
0x1d: test byte ptr [rax + 0x3e6], 1
0x24: jne 0x3ffff7b
0x2a: test byte ptr [rdx], 1
0x2d: je 0x3ffff7b
0x33: test byte ptr [rdx + 8], 1
0x37: je 0x3ffff7b
0x3d: mov rax, qword ptr [rdx]
0x40: cmp rax, qword ptr [rdx + 8]
0x44: movabs rax, 0
0x4e: movabs rcx, 0x14
0x58: cmovl rax, rcx
0x5c: mov qword ptr [rdx], rax
0x5f: test qword ptr [rdx], -9
0x66: jne 0x3ffffd5
```
Make sure to `brew install pkg-config capstone`
2021-01-22 13:43:26 -05:00
|
|
|
|
2021-04-07 15:27:05 -04:00
|
|
|
block_sizes = blocks.map { |block| block.code.length }
|
|
|
|
total_bytes = block_sizes.sum
|
|
|
|
str << "\n"
|
|
|
|
str << "Total code size: #{total_bytes} bytes"
|
Directly link libcapstone for easier development
This lets us use libcapstone directly from miniruby so we don't need a
Ruby Gem to to dev work.
Example usage:
```ruby
def foo(x)
if x < 1
"wow"
else
"neat"
end
end
iseq = RubyVM::InstructionSequence.of(method(:foo))
puts UJIT.disasm(iseq)
100.times { foo 1 }
puts UJIT.disasm(iseq)
```
Then in the terminal
```
$ ./miniruby test.rb
== disasm: #<ISeq:foo@test.rb:1 (1,0)-(7,3)> (catch: FALSE)
local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] x@0<Arg>
0000 getlocal_WC_0 x@0 ( 2)[LiCa]
0002 putobject_INT2FIX_1_
0003 opt_lt <calldata!mid:<, argc:1, ARGS_SIMPLE>
0005 branchunless 10
0007 putstring "wow" ( 3)[Li]
0009 leave ( 7)[Re]
0010 putstring "neat" ( 5)[Li]
0012 leave ( 7)[Re]
== ISEQ RANGE: 10 -> 10 ========================================================
0x0: movabs rax, 0x7fe816e2d1a0
0xa: mov qword ptr [rdi], rax
0xd: mov r8, rax
0x10: mov r9, rax
0x13: mov r11, r12
0x16: jmp qword ptr [rax]
== ISEQ RANGE: 0 -> 7 ==========================================================
0x0: mov rax, qword ptr [rdi + 0x20]
0x4: mov rax, qword ptr [rax - 0x18]
0x8: mov qword ptr [rdx], rax
0xb: mov qword ptr [rdx + 8], 3
0x13: movabs rax, 0x7fe817808200
0x1d: test byte ptr [rax + 0x3e6], 1
0x24: jne 0x3ffff7b
0x2a: test byte ptr [rdx], 1
0x2d: je 0x3ffff7b
0x33: test byte ptr [rdx + 8], 1
0x37: je 0x3ffff7b
0x3d: mov rax, qword ptr [rdx]
0x40: cmp rax, qword ptr [rdx + 8]
0x44: movabs rax, 0
0x4e: movabs rcx, 0x14
0x58: cmovl rax, rcx
0x5c: mov qword ptr [rdx], rax
0x5f: test qword ptr [rdx], -9
0x66: jne 0x3ffffd5
```
Make sure to `brew install pkg-config capstone`
2021-01-22 13:43:26 -05:00
|
|
|
str << "\n"
|
|
|
|
|
2021-04-07 15:27:05 -04:00
|
|
|
str
|
Directly link libcapstone for easier development
This lets us use libcapstone directly from miniruby so we don't need a
Ruby Gem to to dev work.
Example usage:
```ruby
def foo(x)
if x < 1
"wow"
else
"neat"
end
end
iseq = RubyVM::InstructionSequence.of(method(:foo))
puts UJIT.disasm(iseq)
100.times { foo 1 }
puts UJIT.disasm(iseq)
```
Then in the terminal
```
$ ./miniruby test.rb
== disasm: #<ISeq:foo@test.rb:1 (1,0)-(7,3)> (catch: FALSE)
local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] x@0<Arg>
0000 getlocal_WC_0 x@0 ( 2)[LiCa]
0002 putobject_INT2FIX_1_
0003 opt_lt <calldata!mid:<, argc:1, ARGS_SIMPLE>
0005 branchunless 10
0007 putstring "wow" ( 3)[Li]
0009 leave ( 7)[Re]
0010 putstring "neat" ( 5)[Li]
0012 leave ( 7)[Re]
== ISEQ RANGE: 10 -> 10 ========================================================
0x0: movabs rax, 0x7fe816e2d1a0
0xa: mov qword ptr [rdi], rax
0xd: mov r8, rax
0x10: mov r9, rax
0x13: mov r11, r12
0x16: jmp qword ptr [rax]
== ISEQ RANGE: 0 -> 7 ==========================================================
0x0: mov rax, qword ptr [rdi + 0x20]
0x4: mov rax, qword ptr [rax - 0x18]
0x8: mov qword ptr [rdx], rax
0xb: mov qword ptr [rdx + 8], 3
0x13: movabs rax, 0x7fe817808200
0x1d: test byte ptr [rax + 0x3e6], 1
0x24: jne 0x3ffff7b
0x2a: test byte ptr [rdx], 1
0x2d: je 0x3ffff7b
0x33: test byte ptr [rdx + 8], 1
0x37: je 0x3ffff7b
0x3d: mov rax, qword ptr [rdx]
0x40: cmp rax, qword ptr [rdx + 8]
0x44: movabs rax, 0
0x4e: movabs rcx, 0x14
0x58: cmovl rax, rcx
0x5c: mov qword ptr [rdx], rax
0x5f: test qword ptr [rdx], -9
0x66: jne 0x3ffffd5
```
Make sure to `brew install pkg-config capstone`
2021-01-22 13:43:26 -05:00
|
|
|
end
|
2021-01-25 18:49:54 -05:00
|
|
|
|
2021-04-07 15:27:05 -04:00
|
|
|
def self.comments_for(start_address, end_address)
|
|
|
|
Primitive.comments_for(start_address, end_address)
|
|
|
|
end
|
2021-06-10 16:16:58 -04:00
|
|
|
|
|
|
|
def self.graphviz_for(iseq)
|
|
|
|
iseq = RubyVM::InstructionSequence.of(iseq)
|
2021-10-27 16:10:25 -04:00
|
|
|
cs = Disasm.new
|
2021-06-21 19:48:03 -04:00
|
|
|
|
|
|
|
highlight = ->(comment) { "<b>#{comment}</b>" }
|
|
|
|
linebreak = "<br align=\"left\"/>\n"
|
|
|
|
|
2021-07-15 14:56:17 -04:00
|
|
|
buff = +''
|
2021-06-21 19:48:03 -04:00
|
|
|
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"
|
2021-06-22 17:33:46 -04:00
|
|
|
buff << " edge [fontname=\"courier\" penwidth=3];\n"
|
2021-06-10 16:16:58 -04:00
|
|
|
blocks.each do |block|
|
2021-06-21 19:48:03 -04:00
|
|
|
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)
|
2021-06-22 17:33:46 -04:00
|
|
|
"b#{block.id} -> b#{id}[label=\"Fall\"];"
|
2021-06-21 19:48:03 -04:00
|
|
|
else
|
2021-06-22 17:33:46 -04:00
|
|
|
"b#{block.id} -> b#{id}[label=\"Jump\" style=dashed];"
|
2021-06-21 19:48:03 -04:00
|
|
|
end
|
|
|
|
}.join("\n")
|
|
|
|
buff << "\n"
|
2021-06-10 16:16:58 -04:00
|
|
|
end
|
2021-06-21 19:48:03 -04:00
|
|
|
buff << " }"
|
2021-06-10 16:16:58 -04:00
|
|
|
buff << "}"
|
|
|
|
buff
|
|
|
|
end
|
|
|
|
|
2021-06-21 19:48:03 -04:00
|
|
|
def self.disasm_block(cs, block, highlight)
|
2021-06-10 16:16:58 -04:00
|
|
|
comments = comments_for(block.address, block.address + block.code.length)
|
|
|
|
comment_idx = 0
|
2021-07-15 14:57:27 -04:00
|
|
|
str = +''
|
2021-06-10 16:16:58 -04:00
|
|
|
cs.disasm(block.code, block.address).each do |i|
|
|
|
|
while (comment = comments[comment_idx]) && comment.address <= i.address
|
2021-06-21 19:48:03 -04:00
|
|
|
str << " ; #{highlight.call(comment.comment)}\n"
|
2021-06-10 16:16:58 -04:00
|
|
|
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
|
2021-04-07 15:27:05 -04:00
|
|
|
end
|
2021-03-02 18:27:50 -05:00
|
|
|
|
2021-03-06 18:46:56 -05:00
|
|
|
# Return a hash for statistics generated for the --yjit-stats command line option.
|
2021-03-02 18:27:50 -05:00
|
|
|
# Return nil when option is not passed or unavailable.
|
|
|
|
def self.runtime_stats
|
2021-03-06 18:46:56 -05:00
|
|
|
# defined in yjit_iface.c
|
2021-06-29 10:37:12 -04:00
|
|
|
Primitive.get_yjit_stats
|
2021-03-02 18:27:50 -05:00
|
|
|
end
|
|
|
|
|
2021-03-06 18:46:56 -05:00
|
|
|
# Discard statistics collected for --yjit-stats.
|
2021-03-03 16:56:24 -05:00
|
|
|
def self.reset_stats!
|
2021-03-06 18:46:56 -05:00
|
|
|
# defined in yjit_iface.c
|
2021-03-03 16:56:24 -05:00
|
|
|
Primitive.reset_stats_bang
|
|
|
|
end
|
|
|
|
|
2021-08-26 10:11:15 -04:00
|
|
|
def self.stats_enabled?
|
2021-10-26 15:15:46 -04:00
|
|
|
Primitive.yjit_stats_enabled_p
|
2021-08-26 10:11:15 -04:00
|
|
|
end
|
|
|
|
|
2021-06-25 16:14:39 -04:00
|
|
|
def self.enabled?
|
2022-01-01 02:57:22 -05:00
|
|
|
Primitive.cexpr! 'RBOOL(rb_yjit_enabled_p())'
|
2021-06-25 16:14:39 -04:00
|
|
|
end
|
|
|
|
|
2021-11-04 12:30:30 -04:00
|
|
|
def self.simulate_oom!
|
|
|
|
Primitive.simulate_oom_bang
|
|
|
|
end
|
|
|
|
|
2021-10-26 15:15:46 -04:00
|
|
|
# Avoid calling a method here to not interfere with compilation tests
|
|
|
|
if Primitive.yjit_stats_enabled_p
|
|
|
|
at_exit { _print_stats }
|
|
|
|
end
|
|
|
|
|
2021-03-02 18:27:50 -05:00
|
|
|
class << self
|
|
|
|
private
|
|
|
|
|
|
|
|
# Format and print out counters
|
|
|
|
def _print_stats
|
2021-06-29 11:36:05 -04:00
|
|
|
stats = runtime_stats
|
|
|
|
return unless stats
|
2021-03-02 18:27:50 -05:00
|
|
|
|
2021-06-29 10:37:12 -04:00
|
|
|
$stderr.puts("***YJIT: Printing YJIT statistics on exit***")
|
2021-06-29 11:36:05 -04:00
|
|
|
|
|
|
|
print_counters(stats, prefix: 'send_', prompt: 'method call exit reasons: ')
|
2021-08-30 23:58:53 -04:00
|
|
|
print_counters(stats, prefix: 'invokesuper_', prompt: 'invokesuper exit reasons: ')
|
2021-06-29 11:36:05 -04:00
|
|
|
print_counters(stats, prefix: 'leave_', prompt: 'leave exit reasons: ')
|
2021-10-04 11:26:28 -04:00
|
|
|
print_counters(stats, prefix: 'gbpp_', prompt: 'getblockparamproxy exit reasons: ')
|
2021-06-29 11:36:05 -04:00
|
|
|
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: ')
|
2021-07-07 14:45:48 -04:00
|
|
|
print_counters(stats, prefix: 'expandarray_', prompt: 'expandarray exit reasons: ')
|
2021-09-21 18:16:23 -04:00
|
|
|
print_counters(stats, prefix: 'opt_getinlinecache_', prompt: 'opt_getinlinecache exit reasons: ')
|
2021-09-22 16:56:42 -04:00
|
|
|
print_counters(stats, prefix: 'invalidate_', prompt: 'invalidation reasons: ')
|
2021-06-29 11:36:05 -04:00
|
|
|
|
2021-09-10 11:04:14 -04:00
|
|
|
side_exits = total_exit_count(stats)
|
|
|
|
total_exits = side_exits + stats[:leave_interp_return]
|
2021-06-29 11:36:05 -04:00
|
|
|
|
2021-09-10 11:04:14 -04:00
|
|
|
# Number of instructions that finish executing in YJIT.
|
|
|
|
# See :count-placement: about the subtraction.
|
|
|
|
retired_in_yjit = stats[:exec_instruction] - side_exits
|
2021-06-29 11:36:05 -04:00
|
|
|
|
|
|
|
# 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
|
|
|
|
|
2021-11-19 23:44:13 -05:00
|
|
|
# Number of failed compiler invocations
|
|
|
|
compilation_failure = stats[:compilation_failure]
|
|
|
|
|
2021-09-16 12:39:53 -04:00
|
|
|
$stderr.puts "bindings_allocations: " + ("%10d" % stats[:binding_allocations])
|
|
|
|
$stderr.puts "bindings_set: " + ("%10d" % stats[:binding_set])
|
2021-11-19 23:44:13 -05:00
|
|
|
$stderr.puts "compilation_failure: " + ("%10d" % compilation_failure) if compilation_failure != 0
|
2021-06-29 11:36:05 -04:00
|
|
|
$stderr.puts "compiled_iseq_count: " + ("%10d" % stats[:compiled_iseq_count])
|
2021-09-15 13:59:50 -04:00
|
|
|
$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])
|
2021-06-29 11:36:05 -04:00
|
|
|
$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
|
2021-06-30 04:41:19 -04:00
|
|
|
top_n_exit_pct = 100.0 * top_n_total / total_exits
|
2021-06-29 11:36:05 -04:00
|
|
|
|
|
|
|
$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
|
2021-09-14 15:06:46 -04:00
|
|
|
$stderr.puts("#{padded_name}: #{padded_count} (#{formatted_percent}%)" )
|
2021-06-29 11:36:05 -04:00
|
|
|
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
|
2021-03-03 17:31:20 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def print_counters(counters, prefix:, prompt:)
|
|
|
|
$stderr.puts(prompt)
|
|
|
|
counters = counters.filter { |key, _| key.start_with?(prefix) }
|
2021-03-06 11:05:55 -05:00
|
|
|
counters.filter! { |_, value| value != 0 }
|
2021-03-03 17:31:20 -05:00
|
|
|
counters.transform_keys! { |key| key.to_s.delete_prefix(prefix) }
|
2021-03-02 18:27:50 -05:00
|
|
|
|
2021-03-06 11:05:55 -05:00
|
|
|
if counters.empty?
|
|
|
|
$stderr.puts(" (all relevant counters are zero)")
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2021-03-02 18:27:50 -05:00
|
|
|
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
|
Directly link libcapstone for easier development
This lets us use libcapstone directly from miniruby so we don't need a
Ruby Gem to to dev work.
Example usage:
```ruby
def foo(x)
if x < 1
"wow"
else
"neat"
end
end
iseq = RubyVM::InstructionSequence.of(method(:foo))
puts UJIT.disasm(iseq)
100.times { foo 1 }
puts UJIT.disasm(iseq)
```
Then in the terminal
```
$ ./miniruby test.rb
== disasm: #<ISeq:foo@test.rb:1 (1,0)-(7,3)> (catch: FALSE)
local table (size: 1, argc: 1 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 1] x@0<Arg>
0000 getlocal_WC_0 x@0 ( 2)[LiCa]
0002 putobject_INT2FIX_1_
0003 opt_lt <calldata!mid:<, argc:1, ARGS_SIMPLE>
0005 branchunless 10
0007 putstring "wow" ( 3)[Li]
0009 leave ( 7)[Re]
0010 putstring "neat" ( 5)[Li]
0012 leave ( 7)[Re]
== ISEQ RANGE: 10 -> 10 ========================================================
0x0: movabs rax, 0x7fe816e2d1a0
0xa: mov qword ptr [rdi], rax
0xd: mov r8, rax
0x10: mov r9, rax
0x13: mov r11, r12
0x16: jmp qword ptr [rax]
== ISEQ RANGE: 0 -> 7 ==========================================================
0x0: mov rax, qword ptr [rdi + 0x20]
0x4: mov rax, qword ptr [rax - 0x18]
0x8: mov qword ptr [rdx], rax
0xb: mov qword ptr [rdx + 8], 3
0x13: movabs rax, 0x7fe817808200
0x1d: test byte ptr [rax + 0x3e6], 1
0x24: jne 0x3ffff7b
0x2a: test byte ptr [rdx], 1
0x2d: je 0x3ffff7b
0x33: test byte ptr [rdx + 8], 1
0x37: je 0x3ffff7b
0x3d: mov rax, qword ptr [rdx]
0x40: cmp rax, qword ptr [rdx + 8]
0x44: movabs rax, 0
0x4e: movabs rcx, 0x14
0x58: cmovl rax, rcx
0x5c: mov qword ptr [rdx], rax
0x5f: test qword ptr [rdx], -9
0x66: jne 0x3ffffd5
```
Make sure to `brew install pkg-config capstone`
2021-01-22 13:43:26 -05:00
|
|
|
end
|