mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
MicroJIT: avoid having to invalidate running output code
This commit is contained in:
parent
9ce9f613b0
commit
188c54428c
3 changed files with 59 additions and 19 deletions
27
test/ruby/test_microjit.rb
Normal file
27
test/ruby/test_microjit.rb
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
require 'test/unit'
|
||||||
|
|
||||||
|
class TestMicroJIT < Test::Unit::TestCase
|
||||||
|
# MicroJIT's code invalidation mechanism can't invalidate
|
||||||
|
# code that is executing. Test that we don't try to do that.
|
||||||
|
def test_code_invalidation
|
||||||
|
klass = Class.new do
|
||||||
|
def alias_then_hash(klass, method_to_redefine)
|
||||||
|
klass.alias_method(method_to_redefine, :itself)
|
||||||
|
hash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
instance = klass.new
|
||||||
|
i = 0
|
||||||
|
while i < 12
|
||||||
|
if i < 11
|
||||||
|
instance.alias_then_hash(klass, :bar)
|
||||||
|
else
|
||||||
|
ret = instance.alias_then_hash(klass, :hash)
|
||||||
|
assert(instance.equal?(ret))
|
||||||
|
end
|
||||||
|
i += 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -25,6 +25,8 @@
|
||||||
#define UJIT_CHECK_MODE 0
|
#define UJIT_CHECK_MODE 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// >= 1: print when output code invalidation happens
|
||||||
|
// >= 2: dump list of instructions when regions compile
|
||||||
#ifndef UJIT_DUMP_MODE
|
#ifndef UJIT_DUMP_MODE
|
||||||
#define UJIT_DUMP_MODE 0
|
#define UJIT_DUMP_MODE 0
|
||||||
#endif
|
#endif
|
||||||
|
@ -358,22 +360,20 @@ Compile a sequence of bytecode instructions starting at `insn_idx`.
|
||||||
Return the index to the first instruction not compiled in the sequence
|
Return the index to the first instruction not compiled in the sequence
|
||||||
through `next_ujit_idx`. Return `NULL` in case compilation fails.
|
through `next_ujit_idx`. Return `NULL` in case compilation fails.
|
||||||
*/
|
*/
|
||||||
uint8_t *
|
static uint8_t *
|
||||||
ujit_compile_insn(const rb_iseq_t *iseq, unsigned int insn_idx, unsigned int *next_ujit_idx)
|
ujit_compile_insn(const rb_iseq_t *iseq, unsigned int insn_idx, unsigned int *next_ujit_idx)
|
||||||
{
|
{
|
||||||
assert (cb != NULL);
|
assert (cb != NULL);
|
||||||
|
unsigned first_insn_idx = insn_idx;
|
||||||
VALUE *encoded = iseq->body->iseq_encoded;
|
VALUE *encoded = iseq->body->iseq_encoded;
|
||||||
|
|
||||||
// NOTE: if we are ever deployed in production, we
|
// NOTE: if we are ever deployed in production, we
|
||||||
// should probably just log an error and return NULL here,
|
// should probably just log an error and return NULL here,
|
||||||
// so we can fail more gracefully
|
// so we can fail more gracefully
|
||||||
if (cb->write_pos + 1024 >= cb->mem_size)
|
if (cb->write_pos + 1024 >= cb->mem_size) {
|
||||||
{
|
|
||||||
rb_bug("out of executable memory");
|
rb_bug("out of executable memory");
|
||||||
}
|
}
|
||||||
if (ocb->write_pos + 1024 >= ocb->mem_size)
|
if (ocb->write_pos + 1024 >= ocb->mem_size) {
|
||||||
{
|
|
||||||
rb_bug("out of executable memory (outlined block)");
|
rb_bug("out of executable memory (outlined block)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -396,9 +396,8 @@ ujit_compile_insn(const rb_iseq_t *iseq, unsigned int insn_idx, unsigned int *ne
|
||||||
ctx.replacement_idx = insn_idx;
|
ctx.replacement_idx = insn_idx;
|
||||||
|
|
||||||
// For each instruction to compile
|
// For each instruction to compile
|
||||||
size_t num_instrs;
|
unsigned num_instrs;
|
||||||
for (num_instrs = 0;; ++num_instrs)
|
for (num_instrs = 0;; ++num_instrs) {
|
||||||
{
|
|
||||||
// Set the current PC
|
// Set the current PC
|
||||||
ctx.pc = &encoded[insn_idx];
|
ctx.pc = &encoded[insn_idx];
|
||||||
|
|
||||||
|
@ -407,16 +406,14 @@ ujit_compile_insn(const rb_iseq_t *iseq, unsigned int insn_idx, unsigned int *ne
|
||||||
|
|
||||||
// Lookup the codegen function for this instruction
|
// Lookup the codegen function for this instruction
|
||||||
st_data_t st_gen_fn;
|
st_data_t st_gen_fn;
|
||||||
if (!rb_st_lookup(gen_fns, opcode, &st_gen_fn))
|
if (!rb_st_lookup(gen_fns, opcode, &st_gen_fn)) {
|
||||||
{
|
|
||||||
//print_int(cb, imm_opnd(num_instrs));
|
//print_int(cb, imm_opnd(num_instrs));
|
||||||
//print_str(cb, insn_name(opcode));
|
//print_str(cb, insn_name(opcode));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the pre call bytes before the first instruction
|
// Write the pre call bytes before the first instruction
|
||||||
if (num_instrs == 0)
|
if (num_instrs == 0) {
|
||||||
{
|
|
||||||
ujit_gen_entry(cb);
|
ujit_gen_entry(cb);
|
||||||
|
|
||||||
// Load the current SP from the CFP into REG_SP
|
// Load the current SP from the CFP into REG_SP
|
||||||
|
@ -425,29 +422,46 @@ ujit_compile_insn(const rb_iseq_t *iseq, unsigned int insn_idx, unsigned int *ne
|
||||||
|
|
||||||
// Call the code generation function
|
// Call the code generation function
|
||||||
codegen_fn gen_fn = (codegen_fn)st_gen_fn;
|
codegen_fn gen_fn = (codegen_fn)st_gen_fn;
|
||||||
if (!gen_fn(cb, ocb, &ctx))
|
if (!gen_fn(cb, ocb, &ctx)) {
|
||||||
{
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move to the next instruction
|
// Move to the next instruction
|
||||||
insn_idx += insn_len(opcode);
|
insn_idx += insn_len(opcode);
|
||||||
|
|
||||||
|
// Ensure we only have one send per region. Our code invalidation mechanism can't
|
||||||
|
// invalidate running code and one send could invalidate the other if we had
|
||||||
|
// multiple in the same region.
|
||||||
|
if (opcode == BIN(opt_send_without_block)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Let the caller know how many instructions ujit compiled
|
// Let the caller know how many instructions ujit compiled
|
||||||
*next_ujit_idx = insn_idx;
|
*next_ujit_idx = insn_idx;
|
||||||
|
|
||||||
// If no instructions were compiled
|
// If no instructions were compiled
|
||||||
if (num_instrs == 0)
|
if (num_instrs == 0) {
|
||||||
{
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate code to exit to the interpreter
|
// Generate code to exit to the interpreter
|
||||||
ujit_gen_exit(cb, &ctx, ctx.pc);
|
ujit_gen_exit(cb, &ctx, &encoded[*next_ujit_idx]);
|
||||||
|
|
||||||
addr2insn_bookkeeping(code_ptr, first_opcode);
|
addr2insn_bookkeeping(code_ptr, first_opcode);
|
||||||
|
|
||||||
|
if (UJIT_DUMP_MODE >= 2) {
|
||||||
|
// Dump list of compiled instrutions
|
||||||
|
fprintf(stderr, "Compiled the following for iseq=%p:\n", (void *)iseq);
|
||||||
|
VALUE *pc = &encoded[first_insn_idx];
|
||||||
|
VALUE *end_pc = &encoded[*next_ujit_idx];
|
||||||
|
while (pc < end_pc) {
|
||||||
|
int opcode = opcode_at_pc(iseq, pc);
|
||||||
|
fprintf(stderr, " %04td %s\n", pc - encoded, insn_name(opcode));
|
||||||
|
pc += insn_len(opcode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return code_ptr;
|
return code_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,6 @@ bool rb_ujit_enabled_p(void)
|
||||||
|
|
||||||
void rb_ujit_method_lookup_change(VALUE cme_or_cc);
|
void rb_ujit_method_lookup_change(VALUE cme_or_cc);
|
||||||
void rb_ujit_init(void);
|
void rb_ujit_init(void);
|
||||||
uint8_t *ujit_compile_insn(const rb_iseq_t *iseq, unsigned int insn_idx, unsigned int *next_ujit_idx);
|
|
||||||
void rb_ujit_compile_iseq(const rb_iseq_t *iseq);
|
void rb_ujit_compile_iseq(const rb_iseq_t *iseq);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Add table
Reference in a new issue