diff --git a/test/ruby/test_microjit.rb b/test/ruby/test_microjit.rb new file mode 100644 index 0000000000..f0c876b70f --- /dev/null +++ b/test/ruby/test_microjit.rb @@ -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 diff --git a/ujit_compile.c b/ujit_compile.c index df9b9e3d0a..7a8e809879 100644 --- a/ujit_compile.c +++ b/ujit_compile.c @@ -25,6 +25,8 @@ #define UJIT_CHECK_MODE 0 #endif +// >= 1: print when output code invalidation happens +// >= 2: dump list of instructions when regions compile #ifndef UJIT_DUMP_MODE #define UJIT_DUMP_MODE 0 #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 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) { assert (cb != NULL); - + unsigned first_insn_idx = insn_idx; VALUE *encoded = iseq->body->iseq_encoded; // NOTE: if we are ever deployed in production, we // should probably just log an error and return NULL here, // 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"); } - if (ocb->write_pos + 1024 >= ocb->mem_size) - { + if (ocb->write_pos + 1024 >= ocb->mem_size) { 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; // For each instruction to compile - size_t num_instrs; - for (num_instrs = 0;; ++num_instrs) - { + unsigned num_instrs; + for (num_instrs = 0;; ++num_instrs) { // Set the current PC 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 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_str(cb, insn_name(opcode)); break; } // Write the pre call bytes before the first instruction - if (num_instrs == 0) - { + if (num_instrs == 0) { ujit_gen_entry(cb); // 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 codegen_fn gen_fn = (codegen_fn)st_gen_fn; - if (!gen_fn(cb, ocb, &ctx)) - { + if (!gen_fn(cb, ocb, &ctx)) { break; } // Move to the next instruction 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 *next_ujit_idx = insn_idx; // If no instructions were compiled - if (num_instrs == 0) - { + if (num_instrs == 0) { return NULL; } // 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); + 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; } diff --git a/ujit_compile.h b/ujit_compile.h index 4b2031d1a8..15078386ff 100644 --- a/ujit_compile.h +++ b/ujit_compile.h @@ -25,7 +25,6 @@ bool rb_ujit_enabled_p(void) void rb_ujit_method_lookup_change(VALUE cme_or_cc); 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); #endif