1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/yjit_iface.c

1113 lines
33 KiB
C
Raw Normal View History

#include "ruby/ruby.h"
#include "vm_core.h"
#include "insns.inc"
#include "internal.h"
#include "vm_sync.h"
#include "vm_callinfo.h"
#include "builtin.h"
2021-03-17 19:07:20 -04:00
#include "gc.h"
#include "internal/compile.h"
#include "internal/class.h"
#include "insns_info.inc"
#include "yjit.h"
#include "yjit_iface.h"
#include "yjit_codegen.h"
#include "yjit_core.h"
#include "darray.h"
2021-01-22 14:26:20 -05:00
#if HAVE_LIBCAPSTONE
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
#include <capstone/capstone.h>
static VALUE cYjitDisasm;
static VALUE cYjitDisasmInsn;
2021-01-22 14:26:20 -05:00
#endif
static VALUE mYjit;
static VALUE cYjitBlock;
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
#if YJIT_STATS
static VALUE cYjitCodeComment;
#endif
#if YJIT_STATS
extern const int rb_vm_max_insn_name_size;
static int64_t exit_op_count[VM_INSTRUCTION_SIZE] = { 0 };
struct rb_yjit_runtime_counters yjit_runtime_counters = { 0 };
#endif
2021-02-16 11:15:29 -05:00
// Machine code blocks (executable memory)
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
extern codeblock_t *cb;
extern codeblock_t *ocb;
2021-02-16 11:15:29 -05:00
// Hash table of encoded instructions
extern st_table *rb_encoded_insn_data;
struct rb_yjit_options rb_yjit_opts;
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
static const rb_data_type_t yjit_block_type = {
"YJIT/Block",
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
{0, 0, 0, },
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
};
// Get the PC for a given index in an iseq
VALUE *
yjit_iseq_pc_at_idx(const rb_iseq_t *iseq, uint32_t insn_idx)
{
RUBY_ASSERT(iseq != NULL);
RUBY_ASSERT(insn_idx < iseq->body->iseq_size);
VALUE *encoded = iseq->body->iseq_encoded;
VALUE *pc = &encoded[insn_idx];
return pc;
}
// Keep track of mapping from instructions to generated code
// See comment for rb_encoded_insn_data in iseq.c
void
map_addr2insn(void *code_ptr, int insn)
{
const void * const *table = rb_vm_get_insns_address_table();
const void * const translated_address = table[insn];
st_data_t encoded_insn_data;
if (st_lookup(rb_encoded_insn_data, (st_data_t)translated_address, &encoded_insn_data)) {
st_insert(rb_encoded_insn_data, (st_data_t)code_ptr, encoded_insn_data);
}
else {
rb_bug("yjit: failed to find info for original instruction while dealing with addr2insn");
}
}
// For debugging. Print the disassembly of an iseq.
void
yjit_print_iseq(const rb_iseq_t *iseq)
{
char *ptr;
long len;
VALUE disassembly = rb_iseq_disasm(iseq);
RSTRING_GETMEM(disassembly, ptr, len);
fprintf(stderr, "%.*s\n", (int)len, ptr);
}
int
yjit_opcode_at_pc(const rb_iseq_t *iseq, const VALUE *pc)
{
const VALUE at_pc = *pc;
if (FL_TEST_RAW((VALUE)iseq, ISEQ_TRANSLATED)) {
return rb_vm_insn_addr2opcode((const void *)at_pc);
}
else {
return (int)at_pc;
}
}
// Verify that calling with cd on receiver goes to callee
void
2021-03-22 21:04:00 -04:00
check_cfunc_dispatch(VALUE receiver, struct rb_callinfo *ci, void *callee, rb_callable_method_entry_t *compile_time_cme)
{
if (METHOD_ENTRY_INVALIDATED(compile_time_cme)) {
rb_bug("yjit: output code uses invalidated cme %p", (void *)compile_time_cme);
}
bool callee_correct = false;
2021-03-22 21:04:00 -04:00
const rb_callable_method_entry_t *cme = rb_callable_method_entry(CLASS_OF(receiver), vm_ci_mid(ci));
if (cme->def->type == VM_METHOD_TYPE_CFUNC) {
const rb_method_cfunc_t *cfunc = UNALIGNED_MEMBER_PTR(cme->def, body.cfunc);
if ((void *)cfunc->func == callee) {
callee_correct = true;
}
}
if (!callee_correct) {
2021-03-22 21:04:00 -04:00
rb_bug("yjit: output code calls wrong method");
}
}
MJIT_FUNC_EXPORTED VALUE rb_hash_has_key(VALUE hash, VALUE key);
// GC root for interacting with the GC
struct yjit_root_struct {
2021-02-16 16:50:09 -05:00
int unused; // empty structs are not legal in C99
};
2021-03-17 19:07:20 -04:00
// Hash table of BOP blocks
static st_table *blocks_assuming_bops;
bool
assume_bop_not_redefined(block_t *block, int redefined_flag, enum ruby_basic_operators bop)
{
if (BASIC_OP_UNREDEFINED_P(bop, redefined_flag)) {
if (blocks_assuming_bops) {
st_insert(blocks_assuming_bops, (st_data_t)block, 0);
}
2021-03-17 19:07:20 -04:00
return true;
}
else {
return false;
}
}
2021-03-17 19:07:20 -04:00
// Map klass => id_table[mid, set of blocks]
// While a block `b` is in the table, b->callee_cme == rb_callable_method_entry(klass, mid).
// See assume_method_lookup_stable()
static st_table *method_lookup_dependency;
2021-03-17 19:07:20 -04:00
// For adding to method_lookup_dependency data with st_update
struct lookup_dependency_insertion {
block_t *block;
ID mid;
};
// Map cme => set of blocks
// See assume_method_lookup_stable()
static st_table *cme_validity_dependency;
static int
2021-03-17 19:07:20 -04:00
add_cme_validity_dependency_i(st_data_t *key, st_data_t *value, st_data_t new_block, int existing)
{
2021-03-17 19:07:20 -04:00
st_table *block_set;
if (existing) {
2021-03-17 19:07:20 -04:00
block_set = (st_table *)*value;
}
2021-03-17 19:07:20 -04:00
else {
// Make the set and put it into cme_validity_dependency
block_set = st_init_numtable();
*value = (st_data_t)block_set;
}
2021-03-17 19:07:20 -04:00
// Put block into set
st_insert(block_set, new_block, 1);
return ST_CONTINUE;
}
2021-03-17 19:07:20 -04:00
static int
add_lookup_dependency_i(st_data_t *key, st_data_t *value, st_data_t data, int existing)
{
2021-03-17 19:07:20 -04:00
struct lookup_dependency_insertion *info = (void *)data;
// Find or make an id table
struct rb_id_table *id2blocks;
if (existing) {
id2blocks = (void *)*value;
}
else {
2021-03-17 19:07:20 -04:00
// Make an id table and put it into the st_table
id2blocks = rb_id_table_create(1);
*value = (st_data_t)id2blocks;
}
// Find or make a block set
st_table *block_set;
{
VALUE blocks;
if (rb_id_table_lookup(id2blocks, info->mid, &blocks)) {
// Take existing set
block_set = (st_table *)blocks;
}
else {
// Make new block set and put it into the id table
block_set = st_init_numtable();
rb_id_table_insert(id2blocks, info->mid, (VALUE)block_set);
}
}
2021-03-17 19:07:20 -04:00
st_insert(block_set, (st_data_t)info->block, 1);
return ST_CONTINUE;
}
// Remember that a block assumes that
// `rb_callable_method_entry(receiver_klass, cme->called_id) == cme` and that
// `cme` is valid.
2021-03-17 19:07:20 -04:00
// When either of these assumptions becomes invalid, rb_yjit_method_lookup_change() or
// rb_yjit_cme_invalidate() invalidates the block.
2021-03-22 20:12:34 -04:00
//
// @raise NoMemoryError
void
2021-03-17 19:07:20 -04:00
assume_method_lookup_stable(VALUE receiver_klass, const rb_callable_method_entry_t *cme, block_t *block)
{
2021-03-17 19:07:20 -04:00
RUBY_ASSERT(cme_validity_dependency);
RUBY_ASSERT(method_lookup_dependency);
RUBY_ASSERT(rb_callable_method_entry(receiver_klass, cme->called_id) == cme);
RUBY_ASSERT_ALWAYS(RB_TYPE_P(receiver_klass, T_CLASS) || RB_TYPE_P(receiver_klass, T_ICLASS));
2021-03-17 19:07:20 -04:00
RUBY_ASSERT_ALWAYS(!rb_objspace_garbage_object_p(receiver_klass));
cme_dependency_t cme_dep = { receiver_klass, (VALUE)cme };
rb_darray_append(&block->cme_dependencies, cme_dep);
2021-03-17 19:07:20 -04:00
st_update(cme_validity_dependency, (st_data_t)cme, add_cme_validity_dependency_i, (st_data_t)block);
struct lookup_dependency_insertion info = { block, cme->called_id };
st_update(method_lookup_dependency, (st_data_t)receiver_klass, add_lookup_dependency_i, (st_data_t)&info);
}
static st_table *blocks_assuming_single_ractor_mode;
// Can raise NoMemoryError.
RBIMPL_ATTR_NODISCARD()
bool
assume_single_ractor_mode(block_t *block) {
if (rb_multi_ractor_p()) return false;
st_insert(blocks_assuming_single_ractor_mode, (st_data_t)block, 1);
return true;
}
static st_table *blocks_assuming_stable_global_constant_state;
// Assume that the global constant state has not changed since call to this function.
// Can raise NoMemoryError.
void
assume_stable_global_constant_state(block_t *block) {
st_insert(blocks_assuming_stable_global_constant_state, (st_data_t)block, 1);
}
static int
mark_and_pin_keys_i(st_data_t k, st_data_t v, st_data_t ignore)
{
rb_gc_mark((VALUE)k);
return ST_CONTINUE;
}
// GC callback during mark phase
static void
yjit_root_mark(void *ptr)
{
if (method_lookup_dependency) {
2021-03-17 19:07:20 -04:00
// TODO: This is a leak. Unused blocks linger in the table forever, preventing the
// callee class they speculate on from being collected.
// We could do a bespoke weak reference scheme on classes similar to
// the interpreter's call cache. See finalizer for T_CLASS and cc_table_free().
st_foreach(method_lookup_dependency, mark_and_pin_keys_i, 0);
2021-03-17 19:07:20 -04:00
}
if (cme_validity_dependency) {
// Why not let the GC move the cme keys in this table?
// Because this is basically a compare_by_identity Hash.
// If a key moves, we would need to reinsert it into the table so it is rehashed.
// That is tricky to do, espcially as it could trigger allocation which could
// trigger GC. Not sure if it is okay to trigger GC while the GC is updating
// references.
st_foreach(cme_validity_dependency, mark_and_pin_keys_i, 0);
}
}
static void
yjit_root_free(void *ptr)
{
// Do nothing. The root lives as long as the process.
}
static size_t
yjit_root_memsize(const void *ptr)
{
// Count off-gc-heap allocation size of the dependency table
return st_memsize(method_lookup_dependency); // TODO: more accurate accounting
}
// GC callback during compaction
static void
yjit_root_update_references(void *ptr)
{
}
// Custom type for interacting with the GC
// TODO: make this write barrier protected
static const rb_data_type_t yjit_root_type = {
"yjit_root",
{yjit_root_mark, yjit_root_free, yjit_root_memsize, yjit_root_update_references},
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
};
2021-03-17 19:07:20 -04:00
static int
block_set_invalidate_i(st_data_t key, st_data_t v, st_data_t ignore)
{
block_t *version = (block_t *)key;
invalidate_block_version(version);
return ST_CONTINUE;
}
// Callback for when rb_callable_method_entry(klass, mid) is going to change.
// Invalidate blocks that assume stable method lookup of `mid` in `klass` when this happens.
void
2021-03-17 19:07:20 -04:00
rb_yjit_method_lookup_change(VALUE klass, ID mid)
{
2021-03-17 19:07:20 -04:00
if (!method_lookup_dependency) return;
2021-01-29 12:07:18 -05:00
RB_VM_LOCK_ENTER();
2021-03-17 19:07:20 -04:00
st_data_t image;
st_data_t key = (st_data_t)klass;
if (st_lookup(method_lookup_dependency, key, &image)) {
struct rb_id_table *id2blocks = (void *)image;
VALUE blocks;
// Invalidate all blocks in method_lookup_dependency[klass][mid]
if (rb_id_table_lookup(id2blocks, mid, &blocks)) {
rb_id_table_delete(id2blocks, mid);
2021-03-17 19:07:20 -04:00
st_table *block_set = (st_table *)blocks;
st_foreach(block_set, block_set_invalidate_i, 0);
2021-01-29 12:07:18 -05:00
2021-03-17 19:07:20 -04:00
st_free_table(block_set);
}
2021-03-17 19:07:20 -04:00
}
RB_VM_LOCK_LEAVE();
}
// Callback for when a cme becomes invalid.
// Invalidate all blocks that depend on cme being valid.
void
rb_yjit_cme_invalidate(VALUE cme)
{
if (!cme_validity_dependency) return;
2021-03-17 19:07:20 -04:00
RUBY_ASSERT(IMEMO_TYPE_P(cme, imemo_ment));
RB_VM_LOCK_ENTER();
// Delete the block set from the table
st_data_t cme_as_st_data = (st_data_t)cme;
st_data_t blocks;
if (st_delete(cme_validity_dependency, &cme_as_st_data, &blocks)) {
st_table *block_set = (st_table *)blocks;
// Invalidate each block
st_foreach(block_set, block_set_invalidate_i, 0);
st_free_table(block_set);
}
2021-01-29 12:07:18 -05:00
RB_VM_LOCK_LEAVE();
}
2021-03-17 19:07:20 -04:00
// For dealing with refinements
void
rb_yjit_invalidate_all_method_lookup_assumptions(void)
{
// TODO: implement
}
// Remove a block from the method lookup dependency table
static void
remove_method_lookup_dependency(block_t *block, VALUE receiver_klass, const rb_callable_method_entry_t *callee_cme)
{
RUBY_ASSERT(receiver_klass);
RUBY_ASSERT(callee_cme); // callee_cme should be set when receiver_klass is set
2021-03-17 19:07:20 -04:00
st_data_t image;
st_data_t key = (st_data_t)receiver_klass;
if (st_lookup(method_lookup_dependency, key, &image)) {
2021-03-17 19:07:20 -04:00
struct rb_id_table *id2blocks = (void *)image;
ID mid = callee_cme->called_id;
2021-03-17 19:07:20 -04:00
// Find block set
VALUE blocks;
if (rb_id_table_lookup(id2blocks, mid, &blocks)) {
st_table *block_set = (st_table *)blocks;
// Remove block from block set
st_data_t block_as_st_data = (st_data_t)block;
(void)st_delete(block_set, &block_as_st_data, NULL);
if (block_set->num_entries == 0) {
// Block set now empty. Remove from id table.
rb_id_table_delete(id2blocks, mid);
st_free_table(block_set);
}
}
}
}
// Remove a block from cme_validity_dependency
static void
remove_cme_validity_dependency(block_t *block, const rb_callable_method_entry_t *callee_cme)
2021-03-17 19:07:20 -04:00
{
RUBY_ASSERT(callee_cme);
2021-03-17 19:07:20 -04:00
st_data_t blocks;
if (st_lookup(cme_validity_dependency, (st_data_t)callee_cme, &blocks)) {
2021-03-17 19:07:20 -04:00
st_table *block_set = (st_table *)blocks;
2021-03-17 19:07:20 -04:00
st_data_t block_as_st_data = (st_data_t)block;
(void)st_delete(block_set, &block_as_st_data, NULL);
}
}
void
yjit_unlink_method_lookup_dependency(block_t *block)
{
cme_dependency_t *cme_dep;
rb_darray_foreach(block->cme_dependencies, cme_dependency_idx, cme_dep) {
remove_method_lookup_dependency(block, cme_dep->receiver_klass, (const rb_callable_method_entry_t *)cme_dep->callee_cme);
remove_cme_validity_dependency(block, (const rb_callable_method_entry_t *)cme_dep->callee_cme);
}
rb_darray_free(block->cme_dependencies);
}
void
yjit_block_assumptions_free(block_t *block)
{
st_data_t as_st_data = (st_data_t)block;
if (blocks_assuming_stable_global_constant_state) {
st_delete(blocks_assuming_stable_global_constant_state, &as_st_data, NULL);
}
if (blocks_assuming_single_ractor_mode) {
st_delete(blocks_assuming_single_ractor_mode, &as_st_data, NULL);
}
if (blocks_assuming_bops) {
st_delete(blocks_assuming_bops, &as_st_data, NULL);
}
}
2021-07-15 15:40:34 -04:00
typedef VALUE (*yjit_func_t)(rb_execution_context_t *, rb_control_frame_t *);
bool
rb_yjit_compile_iseq(const rb_iseq_t *iseq, rb_execution_context_t *ec)
{
#if (OPT_DIRECT_THREADED_CODE || OPT_CALL_THREADED_CODE) && JIT_ENABLED
bool success = true;
RB_VM_LOCK_ENTER();
// TODO: I think we need to stop all other ractors here
2020-12-10 00:06:10 -05:00
// Compile a block version starting at the first instruction
uint8_t* code_ptr = gen_entry_point(iseq, 0, ec);
if (code_ptr)
{
2021-07-15 15:40:34 -04:00
iseq->body->jit_func = (yjit_func_t)code_ptr;
}
else {
iseq->body->jit_func = 0;
success = false;
}
2020-12-10 00:06:10 -05:00
RB_VM_LOCK_LEAVE();
return success;
#else
return false;
#endif
}
struct yjit_block_itr {
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
const rb_iseq_t *iseq;
VALUE list;
};
/* Get a list of the YJIT blocks associated with `rb_iseq` */
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
static VALUE
yjit_blocks_for(VALUE mod, VALUE rb_iseq)
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
{
if (CLASS_OF(rb_iseq) != rb_cISeq) {
2021-02-04 12:29:36 -05:00
return rb_ary_new();
}
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
const rb_iseq_t *iseq = rb_iseqw_to_iseq(rb_iseq);
2021-03-04 12:05:18 -05:00
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);
}
}
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
return all_versions;
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
}
/* Get the address of the the code associated with a YJIT::Block */
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
static VALUE
block_address(VALUE self)
{
block_t * block;
TypedData_Get_Struct(self, block_t, &yjit_block_type, block);
uint8_t* code_addr = cb_get_ptr(cb, block->start_pos);
return LONG2NUM((intptr_t)code_addr);
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
}
/* Get the machine code for YJIT::Block as a binary string */
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
static VALUE
block_code(VALUE self)
{
block_t * block;
TypedData_Get_Struct(self, block_t, &yjit_block_type, block);
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
return (VALUE)rb_str_new(
(const char*)cb->mem_block + block->start_pos,
block->end_pos - block->start_pos
);
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
}
/* Get the start index in the Instruction Sequence that corresponds to this
* YJIT::Block */
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
static VALUE
iseq_start_index(VALUE self)
{
block_t * block;
TypedData_Get_Struct(self, block_t, &yjit_block_type, block);
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
return INT2NUM(block->blockid.idx);
}
/* Get the end index in the Instruction Sequence that corresponds to this
* YJIT::Block */
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
static VALUE
iseq_end_index(VALUE self)
{
block_t * block;
TypedData_Get_Struct(self, block_t, &yjit_block_type, block);
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
return INT2NUM(block->end_idx);
}
static int
block_invalidation_iterator(st_data_t key, st_data_t value, st_data_t data) {
block_t *block = (block_t *)key;
invalidate_block_version(block); // Thankfully, st_table supports deleteing while iterating
return ST_CONTINUE;
}
/* Called when a basic operation is redefined */
void
rb_yjit_bop_redefined(VALUE klass, const rb_method_entry_t *me, enum ruby_basic_operators bop)
{
if (blocks_assuming_bops) {
st_foreach(blocks_assuming_bops, block_invalidation_iterator, 0);
}
}
/* Called when the constant state changes */
void
rb_yjit_constant_state_changed(void)
{
if (blocks_assuming_stable_global_constant_state) {
st_foreach(blocks_assuming_stable_global_constant_state, block_invalidation_iterator, 0);
#if YJIT_STATS
yjit_runtime_counters.constant_state_bumps++;
#endif
}
}
// Callback from the opt_setinlinecache instruction in the interpreter
void
yjit_constant_ic_update(const rb_iseq_t *iseq, IC ic)
{
RB_VM_LOCK_ENTER();
rb_vm_barrier(); // Stop other ractors since we are going to patch machine code.
{
const struct rb_iseq_constant_body *const body = iseq->body;
VALUE *code = body->iseq_encoded;
// This should come from a running iseq, so direct threading translation
// should have been done
RUBY_ASSERT(FL_TEST((VALUE)iseq, ISEQ_TRANSLATED));
RUBY_ASSERT(ic->get_insn_idx < body->iseq_size);
RUBY_ASSERT(rb_vm_insn_addr2insn((const void *)code[ic->get_insn_idx]) == BIN(opt_getinlinecache));
// Find the matching opt_getinlinecache and invalidate all the blocks there
RUBY_ASSERT(insn_op_type(BIN(opt_getinlinecache), 1) == TS_IC);
if (ic == (IC)code[ic->get_insn_idx + 1 + 1]) {
rb_yjit_block_array_t getinlinecache_blocks = yjit_get_version_array(iseq, ic->get_insn_idx);
rb_darray_for(getinlinecache_blocks, i) {
block_t *block = rb_darray_get(getinlinecache_blocks, i);
invalidate_block_version(block);
}
}
else {
RUBY_ASSERT(false && "ic->get_insn_diex not set properly");
}
}
RB_VM_LOCK_LEAVE();
}
void
rb_yjit_before_ractor_spawn(void)
{
if (blocks_assuming_single_ractor_mode) {
st_foreach(blocks_assuming_single_ractor_mode, block_invalidation_iterator, 0);
}
}
2021-01-22 14:26:20 -05:00
#if HAVE_LIBCAPSTONE
static const rb_data_type_t yjit_disasm_type = {
"YJIT/Disasm",
2021-01-22 14:26:20 -05:00
{0, (void(*)(void *))cs_close, 0, },
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
};
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
static VALUE
yjit_disasm_init(VALUE klass)
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
{
csh * handle;
VALUE disasm = TypedData_Make_Struct(klass, csh, &yjit_disasm_type, handle);
if (cs_open(CS_ARCH_X86, CS_MODE_64, handle) != CS_ERR_OK) {
rb_raise(rb_eRuntimeError, "failed to make Capstone handle");
}
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
return disasm;
}
static VALUE
yjit_disasm(VALUE self, VALUE code, VALUE from)
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
{
size_t count;
csh * handle;
cs_insn *insns;
TypedData_Get_Struct(self, csh, &yjit_disasm_type, handle);
count = cs_disasm(*handle, (uint8_t*)StringValuePtr(code), RSTRING_LEN(code), NUM2ULL(from), 0, &insns);
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
VALUE insn_list = rb_ary_new_capa(count);
for (size_t i = 0; i < count; i++) {
VALUE vals = rb_ary_new_from_args(3, LONG2NUM(insns[i].address),
rb_str_new2(insns[i].mnemonic),
rb_str_new2(insns[i].op_str));
rb_ary_push(insn_list, rb_struct_alloc(cYjitDisasmInsn, vals));
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
}
cs_free(insns, count);
return insn_list;
}
2021-01-22 14:26:20 -05:00
#endif
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
static VALUE
at_exit_print_stats(RB_BLOCK_CALL_FUNC_ARGLIST(yieldarg, data))
{
// Defined in yjit.rb
rb_funcall(mYjit, rb_intern("_print_stats"), 0);
return Qnil;
}
// Primitive called in yjit.rb. Export all machine code comments as a Ruby array.
static VALUE
comments_for(rb_execution_context_t *ec, VALUE self, VALUE start_address, VALUE end_address)
{
VALUE comment_array = rb_ary_new();
#if RUBY_DEBUG
uint8_t *start = (void *)NUM2ULL(start_address);
uint8_t *end = (void *)NUM2ULL(end_address);
rb_darray_for(yjit_code_comments, i) {
struct yjit_comment comment = rb_darray_get(yjit_code_comments, i);
uint8_t *comment_pos = cb_get_ptr(cb, comment.offset);
if (comment_pos >= end) {
break;
}
if (comment_pos >= start) {
VALUE vals = rb_ary_new_from_args(
2,
LL2NUM((long long) comment_pos),
rb_str_new_cstr(comment.comment)
);
rb_ary_push(comment_array, rb_struct_alloc(cYjitCodeComment, vals));
}
}
#endif // if RUBY_DEBUG
return comment_array;
}
// Primitive called in yjit.rb. Export all YJIT statistics as a Ruby hash.
static VALUE
get_yjit_stats(rb_execution_context_t *ec, VALUE self)
{
// Return Qnil if YJIT isn't enabled
if (cb == NULL) {
return Qnil;
}
VALUE hash = rb_hash_new();
RB_VM_LOCK_ENTER();
{
VALUE key = ID2SYM(rb_intern("inline_code_size"));
VALUE value = LL2NUM((long long)cb->write_pos);
rb_hash_aset(hash, key, value);
key = ID2SYM(rb_intern("outlined_code_size"));
value = LL2NUM((long long)ocb->write_pos);
rb_hash_aset(hash, key, value);
}
#if YJIT_STATS
if (rb_yjit_opts.gen_stats) {
// Indicate that the complete set of stats is available
rb_hash_aset(hash, ID2SYM(rb_intern("all_stats")), Qtrue);
int64_t *counter_reader = (int64_t *)&yjit_runtime_counters;
int64_t *counter_reader_end = &yjit_runtime_counters.last_member;
// For each counter in yjit_counter_names, add that counter as
// a key/value pair.
// Iterate through comma separated counter name list
char *name_reader = yjit_counter_names;
char *counter_name_end = yjit_counter_names + sizeof(yjit_counter_names);
while (name_reader < counter_name_end && counter_reader < counter_reader_end) {
if (*name_reader == ',' || *name_reader == ' ') {
name_reader++;
continue;
}
// Compute length of counter name
int name_len;
char *name_end;
{
name_end = strchr(name_reader, ',');
if (name_end == NULL) break;
name_len = (int)(name_end - name_reader);
}
// Put counter into hash
VALUE key = ID2SYM(rb_intern2(name_reader, name_len));
VALUE value = LL2NUM((long long)*counter_reader);
rb_hash_aset(hash, key, value);
counter_reader++;
name_reader = name_end;
}
// For each entry in exit_op_count, add a stats entry with key "exit_INSTRUCTION_NAME"
// and the value is the count of side exits for that instruction.
char key_string[rb_vm_max_insn_name_size + 6]; // Leave room for "exit_" and a final NUL
for (int i = 0; i < VM_INSTRUCTION_SIZE; i++) {
const char *i_name = insn_name(i); // Look up Ruby's NUL-terminated insn name string
snprintf(key_string, rb_vm_max_insn_name_size + 6, "%s%s", "exit_", i_name);
VALUE key = ID2SYM(rb_intern(key_string));
VALUE value = LL2NUM((long long)exit_op_count[i]);
rb_hash_aset(hash, key, value);
}
}
#endif
RB_VM_LOCK_LEAVE();
return hash;
}
// Primitive called in yjit.rb. Zero out all the counters.
static VALUE
reset_stats_bang(rb_execution_context_t *ec, VALUE self)
{
#if YJIT_STATS
memset(&exit_op_count, 0, sizeof(exit_op_count));
memset(&yjit_runtime_counters, 0, sizeof(yjit_runtime_counters));
#endif // if YJIT_STATS
return Qnil;
}
#include "yjit.rbinc"
#if YJIT_STATS
void
rb_yjit_collect_vm_usage_insn(int insn)
{
yjit_runtime_counters.vm_insns_count++;
}
Collect statistics about binding allocations / local variable set This commit collects statistics about how many binding objects are allocated as well as the number of local variables set on bindings. Statistics are output along with other YJIT stats. Here is an example of the output: ``` ***YJIT: Printing runtime counters from yjit.rb*** Number of Bindings Allocated: 195 Number of locals modified through binding: 0 opt_send_without_block exit reasons: ivar_get_method 7515891 (40.4%) se_cc_klass_differ 3081330 (16.6%) iseq_argc_mismatch 1564578 ( 8.4%) se_receiver_not_heap 1557663 ( 8.4%) ic_empty 1407064 ( 7.6%) optimized_method 995823 ( 5.4%) iseq_not_simple 819413 ( 4.4%) alias_method 706972 ( 3.8%) bmethod 685253 ( 3.7%) callsite_not_simple 225983 ( 1.2%) kw_splat 25999 ( 0.1%) ivar_set_method 902 ( 0.0%) cfunc_toomany_args 394 ( 0.0%) refined_method 42 ( 0.0%) cfunc_ruby_array_varg 29 ( 0.0%) invalid_cme 4 ( 0.0%) leave exit reasons: se_finish_frame 4067107 (100.0%) se_interrupt 24 ( 0.0%) getinstancevariable exit reasons: undef 121177 (100.0%) idx_out_of_range 5 ( 0.0%) opt_aref exit reasons: (all relevant counters are zero) compiled_iseq_count: 3944 main_block_code_size: 1.1 MiB side_block_code_size: 0.6 MiB vm_insns_count: 1137268516 yjit_exec_insns_count: 414015644 ratio_in_yjit: 26.7% avg_len_in_yjit: 7.5 total_exit_count: 55491789 most frequent exit op: opt_send_without_block: 18587628 (33.5%) opt_getinlinecache: 11075822 (20.0%) send: 4949300 (8.9%) leave: 4067131 (7.3%) defined: 3975196 (7.2%) setinstancevariable: 3567315 (6.4%) invokesuper: 2982163 (5.4%) getblockparamproxy: 2168852 (3.9%) opt_nil_p: 2104524 (3.8%) opt_aref: 2013858 (3.6%) ``` Running RailsBench allocates 195 binding objects but doesn't set any local variables.
2021-04-07 15:51:50 -04:00
void
rb_yjit_collect_binding_alloc(void)
{
yjit_runtime_counters.binding_allocations++;
}
void
rb_yjit_collect_binding_set(void)
{
yjit_runtime_counters.binding_set++;
}
const VALUE *
rb_yjit_count_side_exit_op(const VALUE *exit_pc)
{
int insn = rb_vm_insn_addr2opcode((const void *)*exit_pc);
exit_op_count[insn]++;
return exit_pc; // This function must return exit_pc!
}
#endif
void
rb_yjit_iseq_mark(const struct rb_iseq_constant_body *body)
{
rb_darray_for(body->yjit_blocks, version_array_idx) {
rb_yjit_block_array_t version_array = rb_darray_get(body->yjit_blocks, version_array_idx);
2021-03-04 12:05:18 -05:00
rb_darray_for(version_array, block_idx) {
block_t *block = rb_darray_get(version_array, block_idx);
2021-03-04 12:05:18 -05:00
rb_gc_mark_movable((VALUE)block->blockid.iseq);
cme_dependency_t *cme_dep;
rb_darray_foreach(block->cme_dependencies, cme_dependency_idx, cme_dep) {
rb_gc_mark_movable(cme_dep->receiver_klass);
rb_gc_mark_movable(cme_dep->callee_cme);
}
// Mark outgoing branch entries
rb_darray_for(block->outgoing, branch_idx) {
branch_t* branch = rb_darray_get(block->outgoing, branch_idx);
for (int i = 0; i < 2; ++i) {
rb_gc_mark_movable((VALUE)branch->targets[i].iseq);
}
}
// Walk over references to objects in generated code.
uint32_t *offset_element;
rb_darray_foreach(block->gc_object_offsets, offset_idx, offset_element) {
uint32_t offset_to_value = *offset_element;
uint8_t *value_address = cb_get_ptr(cb, offset_to_value);
VALUE object;
memcpy(&object, value_address, SIZEOF_VALUE);
rb_gc_mark_movable(object);
}
2021-07-13 14:56:02 -04:00
// Mark the machine code page this block lives on
rb_gc_mark_movable(block->code_page);
}
}
}
void
rb_yjit_iseq_update_references(const struct rb_iseq_constant_body *body)
{
rb_darray_for(body->yjit_blocks, version_array_idx) {
rb_yjit_block_array_t version_array = rb_darray_get(body->yjit_blocks, version_array_idx);
rb_darray_for(version_array, block_idx) {
block_t *block = rb_darray_get(version_array, block_idx);
block->blockid.iseq = (const rb_iseq_t *)rb_gc_location((VALUE)block->blockid.iseq);
cme_dependency_t *cme_dep;
rb_darray_foreach(block->cme_dependencies, cme_dependency_idx, cme_dep) {
cme_dep->receiver_klass = rb_gc_location(cme_dep->receiver_klass);
cme_dep->callee_cme = rb_gc_location(cme_dep->callee_cme);
}
// Update outgoing branch entries
rb_darray_for(block->outgoing, branch_idx) {
branch_t* branch = rb_darray_get(block->outgoing, branch_idx);
for (int i = 0; i < 2; ++i) {
branch->targets[i].iseq = (const void *)rb_gc_location((VALUE)branch->targets[i].iseq);
}
}
// Walk over references to objects in generated code.
uint32_t *offset_element;
rb_darray_foreach(block->gc_object_offsets, offset_idx, offset_element) {
uint32_t offset_to_value = *offset_element;
uint8_t *value_address = cb_get_ptr(cb, offset_to_value);
VALUE object;
memcpy(&object, value_address, SIZEOF_VALUE);
VALUE possibly_moved = rb_gc_location(object);
// Only write when the VALUE moves, to be CoW friendly.
if (possibly_moved != object) {
memcpy(value_address, &possibly_moved, SIZEOF_VALUE);
}
}
2021-07-13 14:56:02 -04:00
// Update the machine code page this block lives on
block->code_page = rb_gc_location(block->code_page);
}
}
}
// Free the yjit resources associated with an iseq
void
rb_yjit_iseq_free(const struct rb_iseq_constant_body *body)
{
rb_darray_for(body->yjit_blocks, version_array_idx) {
rb_yjit_block_array_t version_array = rb_darray_get(body->yjit_blocks, version_array_idx);
rb_darray_for(version_array, block_idx) {
block_t *block = rb_darray_get(version_array, block_idx);
yjit_free_block(block);
}
rb_darray_free(version_array);
}
rb_darray_free(body->yjit_blocks);
}
2021-07-13 14:56:02 -04:00
static void
yjit_code_page_free(void *code_page)
{
free_code_page((code_page_t*)code_page);
}
// Custom type for interacting with the GC
static const rb_data_type_t yjit_code_page_type = {
"yjit_code_page",
{NULL, yjit_code_page_free, NULL, NULL},
0, 0, RUBY_TYPED_FREE_IMMEDIATELY
};
// Allocate a code page and wrap it into a Ruby object owned by the GC
2021-07-14 10:08:33 -04:00
VALUE rb_yjit_code_page_alloc(void)
2021-07-13 14:56:02 -04:00
{
code_page_t* code_page = alloc_code_page();
VALUE cp_obj = TypedData_Wrap_Struct(0, &yjit_code_page_type, code_page);
return cp_obj;
}
// Unwrap the Ruby object representing a code page
code_page_t *rb_yjit_code_page_unwrap(VALUE cp_obj)
{
code_page_t * code_page;
TypedData_Get_Struct(cp_obj, code_page_t, &yjit_code_page_type, code_page);
return code_page;
}
2021-03-17 19:07:20 -04:00
bool
rb_yjit_enabled_p(void)
2021-02-23 15:22:20 -05:00
{
return rb_yjit_opts.yjit_enabled;
2021-02-23 15:22:20 -05:00
}
2021-03-17 19:07:20 -04:00
unsigned
rb_yjit_call_threshold(void)
2021-02-23 15:22:20 -05:00
{
return rb_yjit_opts.call_threshold;
2021-02-23 15:22:20 -05:00
}
# define PTR2NUM(x) (LONG2NUM((long)(x)))
/**
* call-seq: block.id -> unique_id
*
* Returns a unique integer ID for the block. For example:
*
* blocks = blocks_for(iseq)
* blocks.group_by(&:id)
*/
static VALUE
block_id(VALUE self)
{
block_t * block;
TypedData_Get_Struct(self, block_t, &yjit_block_type, block);
return PTR2NUM(block);
}
/**
* call-seq: block.outgoing_ids -> list
*
* Returns a list of outgoing ids for the current block. This list can be used
* in conjunction with Block#id to construct a graph of block objects.
*/
static VALUE
outgoing_ids(VALUE self)
{
block_t * block;
TypedData_Get_Struct(self, block_t, &yjit_block_type, block);
VALUE ids = rb_ary_new();
rb_darray_for(block->outgoing, branch_idx) {
branch_t* out_branch = rb_darray_get(block->outgoing, branch_idx);
for (size_t succ_idx = 0; succ_idx < 2; succ_idx++) {
block_t* succ = out_branch->blocks[succ_idx];
if (succ == NULL)
continue;
rb_ary_push(ids, PTR2NUM(succ));
}
}
return ids;
}
// Can raise RuntimeError
void
rb_yjit_init(struct rb_yjit_options *options)
{
if (!PLATFORM_SUPPORTED_P || !JIT_ENABLED) {
return;
}
rb_yjit_opts = *options;
rb_yjit_opts.yjit_enabled = true;
rb_yjit_opts.gen_stats |= !!getenv("YJIT_STATS");
#if !YJIT_STATS
if(rb_yjit_opts.gen_stats) {
rb_warning("--yjit-stats requires that Ruby is compiled with CPPFLAGS='-DYJIT_STATS=1' or CPPFLAGS='-DRUBY_DEBUG=1'");
}
#endif
// Normalize command-line options to default values
if (rb_yjit_opts.exec_mem_size < 1) {
rb_yjit_opts.exec_mem_size = 256;
}
if (rb_yjit_opts.call_threshold < 1) {
rb_yjit_opts.call_threshold = YJIT_DEFAULT_CALL_THRESHOLD;
2021-02-23 15:22:20 -05:00
}
if (rb_yjit_opts.max_versions < 1) {
rb_yjit_opts.max_versions = 4;
}
blocks_assuming_stable_global_constant_state = st_init_numtable();
blocks_assuming_single_ractor_mode = st_init_numtable();
blocks_assuming_bops = st_init_numtable();
yjit_init_core();
yjit_init_codegen();
// YJIT Ruby module
mYjit = rb_define_module("YJIT");
rb_define_module_function(mYjit, "blocks_for", yjit_blocks_for, 1);
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
// YJIT::Block (block version, code block)
cYjitBlock = rb_define_class_under(mYjit, "Block", rb_cObject);
rb_define_method(cYjitBlock, "address", block_address, 0);
rb_define_method(cYjitBlock, "id", block_id, 0);
rb_define_method(cYjitBlock, "code", block_code, 0);
rb_define_method(cYjitBlock, "iseq_start_index", iseq_start_index, 0);
rb_define_method(cYjitBlock, "iseq_end_index", iseq_end_index, 0);
rb_define_method(cYjitBlock, "outgoing_ids", outgoing_ids, 0);
Expose methods for inspecting Micro JIT code blocks This commit adds a module `UJIT`. The module allows you to insert the initial Micro JIT instruction in to an arbitrary iseq like this: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump ``` After the initial jump is added, we can make Micro JIT do some work: ```ruby 100.times { foo(0) } ``` The `UJIT` module also exposes a method for finding all compiled blocks for a given iseq, like this: ```ruby blocks = UJIT.blocks_for(iseq) ``` We can sort the blocks by address and use the Crabstone gem (which is a wrapper around `capstone`) to disassemble the generated code. Here is the full code example: ```ruby def foo(x) if x < 1 "less than one" else "something else" end end iseq = RubyVM::InstructionSequence.of(method(:foo)) UJIT.insert(iseq) # Add initial jump 100.times { foo(0) } blocks = UJIT.blocks_for(iseq) # brew install capstone # gem install crabstone require "crabstone" cs = Crabstone::Disassembler.new(Crabstone::ARCH_X86, Crabstone::MODE_64) puts iseq.disasm blocks.sort_by(&:address).reverse.each do |block| puts "== ISEQ RANGE: #{block.iseq_start_index} -> #{block.iseq_end_index} ".ljust(80, "=") cs.disasm(block.code, 0).each do |i| printf( "\t0x%<address>x:\t%<instruction>s\t%<details>s\n", address: i.address, instruction: i.mnemonic, details: i.op_str ) end end ``` Here is the output: ``` $ ./ruby 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 "less than one" ( 3)[Li] 0009 leave ( 7)[Re] 0010 putstring "something else" ( 5)[Li] 0012 leave ( 7)[Re] == ISEQ RANGE: 7 -> 7 ========================================================== 0x0: movabs rax, 0x7fcd014cd518 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, 0x7fcd0180ac00 0x1d: test byte ptr [rax + 0x3e6], 1 0x24: jne 0x3ffe0da 0x2a: test byte ptr [rdx], 1 0x2d: je 0x3ffe0da 0x33: test byte ptr [rdx + 8], 1 0x37: je 0x3ffe0da 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: je 0x3ffe111 0x6c: jmp 0xffffffffffffffa3 ```
2021-01-20 13:50:13 -05:00
// YJIT disassembler interface
2021-01-22 14:26:20 -05:00
#if HAVE_LIBCAPSTONE
cYjitDisasm = rb_define_class_under(mYjit, "Disasm", rb_cObject);
rb_define_alloc_func(cYjitDisasm, yjit_disasm_init);
rb_define_method(cYjitDisasm, "disasm", yjit_disasm, 2);
cYjitDisasmInsn = rb_struct_define_under(cYjitDisasm, "Insn", "address", "mnemonic", "op_str", NULL);
#if RUBY_DEBUG
cYjitCodeComment = rb_struct_define_under(cYjitDisasm, "Comment", "address", "comment", NULL);
#endif
2021-01-22 14:26:20 -05:00
#endif
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
if (YJIT_STATS && rb_yjit_opts.gen_stats) {
// Setup at_exit callback for printing out counters
rb_block_call(rb_mKernel, rb_intern("at_exit"), 0, NULL, at_exit_print_stats, Qfalse);
}
2021-03-17 19:07:20 -04:00
// Make dependency tables
method_lookup_dependency = st_init_numtable();
2021-03-17 19:07:20 -04:00
cme_validity_dependency = st_init_numtable();
// Initialize the GC hooks
struct yjit_root_struct *root;
VALUE yjit_root = TypedData_Make_Struct(0, struct yjit_root_struct, &yjit_root_type, root);
rb_gc_register_mark_object(yjit_root);
}