diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 0f67c6ebbb..cf79ccd101 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -190,6 +190,28 @@ assert_normal_exit %q{ end } +# Test getinstancevariable and inline caches +assert_equal '6', %q{ + class Foo + def initialize + @x1 = 1 + @x2 = 1 + @x2 = 1 + @x3 = 1 + @x4 = 3 + end + + def bar + x = 1 + @x4 + @x4 + end + end + + f = Foo.new + f.bar + f.bar +} + # Test that getinstancevariable codegen checks for extended table size assert_equal "nil\n", %q{ class A diff --git a/ujit_codegen.c b/ujit_codegen.c index 0b2c52a844..86e1242f21 100644 --- a/ujit_codegen.c +++ b/ujit_codegen.c @@ -82,17 +82,17 @@ jit_mov_gc_ptr(jitstate_t* jit, codeblock_t* cb, x86opnd_t reg, VALUE ptr) // Check if we are compiling the instruction at the stub PC // Meaning we are compiling the instruction that is next to execute static bool -jit_at_current_insn(jitstate_t* jit, ctx_t* ctx) +jit_at_current_insn(jitstate_t* jit) { - const VALUE* stub_pc = jit->ec->cfp->pc; - return (stub_pc == jit->pc); + const VALUE* ec_pc = jit->ec->cfp->pc; + return (ec_pc == jit->pc); } // Peek at the topmost value on the Ruby stack static VALUE jit_peek_at_stack(jitstate_t* jit, ctx_t* ctx) { - RUBY_ASSERT(jit_at_current_insn(jit, ctx)); + RUBY_ASSERT(jit_at_current_insn(jit)); VALUE* sp = jit->ec->cfp->sp + ctx->sp_offset; @@ -317,7 +317,13 @@ ujit_gen_block(ctx_t* ctx, block_t* block, rb_execution_context_t* ec) // Call the code generation function bool continue_generating = p_desc->gen_fn(&jit, ctx); - if (!continue_generating) { + // For now, reset the chain depth after each instruction + ctx->chain_depth = 0; + + // If we can't compile this instruction + // exit to the interpreter and stop compiling + if (status == UJIT_CANT_COMPILE) { + ujit_gen_exit(&jit, ctx, cb, jit.pc); break; } @@ -572,33 +578,21 @@ gen_getinstancevariable(jitstate_t* jit, ctx_t* ctx) return UJIT_CANT_COMPILE; } + // Defer compilation so we can peek at the topmost object + if (!jit_at_current_insn(jit)) + { + defer_compilation(jit->block, jit->insn_idx, ctx); + return UJIT_END_BLOCK; + } + + // Peek at the topmost value on the stack at compilation time + VALUE top_val = jit_peek_at_stack(jit, ctx); + + // TODO: play with deferred compilation and sidechains! :) - /* - num_versions = count_block_versions(this_instruction); - - if (num_versions > N) - return JIT_CANT_COMPILE; - - - if (defer_compilation(this_instruction, ctx)) - return JIT_END_BLOCK; - - - VALUE top_val = jit_peek_at_stack(); - - - - - class = get_ruby_class(top_val); - - - - guard_object_class(class, current_instr); - */ - @@ -1452,7 +1446,6 @@ gen_oswb_iseq(jitstate_t* jit, ctx_t* ctx, struct rb_call_data * cd, const rb_ca cmp(cb, klass_opnd, REG1); jne_ptr(cb, COUNTED_EXIT(side_exit, oswb_se_cc_klass_differ)); - if (METHOD_ENTRY_VISI(cme) == METHOD_VISI_PROTECTED) { // Generate ancestry guard for protected callee. jit_protected_guard(jit, cb, cme, side_exit); diff --git a/ujit_core.c b/ujit_core.c index f2215cc9bf..c1d2bfb650 100644 --- a/ujit_core.c +++ b/ujit_core.c @@ -113,6 +113,15 @@ Returns INT_MAX if incompatible */ int ctx_diff(const ctx_t* src, const ctx_t* dst) { + // Can only lookup the first version in the chain + if (dst->chain_depth != 0) + return INT_MAX; + + // Blocks with depth > 0 always produce new versions + // Sidechains cannot overlap + if (src->chain_depth != 0) + return INT_MAX; + if (dst->stack_size != src->stack_size) return INT_MAX; @@ -353,6 +362,7 @@ uint8_t* branch_stub_hit(uint32_t branch_idx, uint32_t target_idx, rb_execution_ //fprintf(stderr, "\nstub hit, branch idx: %d, target idx: %d\n", branch_idx, target_idx); //fprintf(stderr, "blockid.iseq=%p, blockid.idx=%d\n", target.iseq, target.idx); + //fprintf(stderr, "chain_depth=%d\n", target_ctx->chain_depth); // Update the PC in the current CFP, because it // may be out of sync in JITted code @@ -376,7 +386,7 @@ uint8_t* branch_stub_hit(uint32_t branch_idx, uint32_t target_idx, rb_execution_ generic_ctx.sp_offset = target_ctx->sp_offset; if (get_num_versions(target) >= MAX_VERSIONS - 1) { - fprintf(stderr, "version limit hit in branch_stub_hit\n"); + //fprintf(stderr, "version limit hit in branch_stub_hit\n"); target_ctx = &generic_ctx; } @@ -542,7 +552,7 @@ void gen_direct_jump( generic_ctx.sp_offset = ctx->sp_offset; if (get_num_versions(target0) >= MAX_VERSIONS - 1) { - fprintf(stderr, "version limit hit in gen_direct_jump\n"); + //fprintf(stderr, "version limit hit in gen_direct_jump\n"); ctx = &generic_ctx; } @@ -588,42 +598,50 @@ void gen_direct_jump( // Create a stub to force the code up to this point to be executed void defer_compilation( block_t* block, - ctx_t* cur_ctx, - uint32_t insn_idx + uint32_t insn_idx, + ctx_t* cur_ctx ) { + //fprintf(stderr, "defer compilation at (%p, %d) depth=%d\n", block->blockid.iseq, insn_idx, cur_ctx->chain_depth); + if (cur_ctx->chain_depth != 0) { + rb_backtrace(); + exit(1); + } + ctx_t next_ctx = *cur_ctx; + if (next_ctx.chain_depth >= UINT8_MAX) { + rb_bug("max block version chain depth reached"); + } + next_ctx.chain_depth += 1; - - - - - - - - /* RUBY_ASSERT(num_branches < MAX_BRANCHES); uint32_t branch_idx = num_branches++; + // Get the branch targets or stubs + blockid_t target0 = (blockid_t){ block->blockid.iseq, insn_idx }; + uint8_t* dst_addr0 = get_branch_target(target0, &next_ctx, branch_idx, 0); + + // Call the branch generation function + uint32_t start_pos = cb->write_pos; + gen_jump_branch(cb, dst_addr0, NULL, SHAPE_DEFAULT); + uint32_t end_pos = cb->write_pos; + // Register this branch entry branch_t branch_entry = { start_pos, end_pos, - *ctx, + *cur_ctx, { target0, BLOCKID_NULL }, - { *ctx, *ctx }, + { next_ctx, next_ctx }, { dst_addr0, NULL }, gen_jump_branch, - branch_shape + SHAPE_DEFAULT }; branch_entries[branch_idx] = branch_entry; - */ - - } // Remove all references to a block then free it. diff --git a/ujit_core.h b/ujit_core.h index 92d2dd594f..cf82dfe5af 100644 --- a/ujit_core.h +++ b/ujit_core.h @@ -20,12 +20,18 @@ // Maximum number of temp value types we keep track of #define MAX_TEMP_TYPES 8 +// Default versioning context (no type information) +#define DEFAULT_CTX ( (ctx_t){ 0 } ) + /** Code generation context Contains information we can use to optimize code */ typedef struct CtxStruct { + // Depth of this block in the sidechain (eg: inline-cache chain) + int8_t chain_depth; + // Temporary variable types we keep track of // Values are `ruby_value_type` // T_NONE==0 is the unknown type @@ -58,12 +64,12 @@ typedef struct BlockId static const blockid_t BLOCKID_NULL = { 0, 0 }; /// Branch code shape enumeration -enum uint8_t +typedef enum : int8_t { SHAPE_NEXT0, // Target 0 is next SHAPE_NEXT1, // Target 1 is next SHAPE_DEFAULT // Neither target is next -}; +} branch_shape_t; // Branch code generation function signature typedef void (*branchgen_fn)(codeblock_t* cb, uint8_t* target0, uint8_t* target1, uint8_t shape); @@ -92,7 +98,7 @@ typedef struct BranchEntry branchgen_fn gen_fn; // Shape of the branch - uint8_t shape; + branch_shape_t shape; } branch_t; @@ -159,6 +165,12 @@ void gen_direct_jump( blockid_t target0 ); +void defer_compilation( + block_t* block, + uint32_t insn_idx, + ctx_t* cur_ctx +); + void invalidate_block_version(block_t* block); void ujit_init_core(void); diff --git a/version.c b/version.c index 9c1aaff32d..7a5a2cb229 100644 --- a/version.c +++ b/version.c @@ -126,7 +126,7 @@ ruby_show_version(void) } if (rb_ujit_enabled_p()) { - fputs("uJIT is enabled\n", stdout); + fputs("YJIT is enabled\n", stdout); } #ifdef RUBY_LAST_COMMIT_TITLE fputs("last_commit=" RUBY_LAST_COMMIT_TITLE, stdout);