ruby-changes:68874
From: Alan <ko1@a...>
Date: Thu, 21 Oct 2021 08:15:00 +0900 (JST)
Subject: [ruby-changes:68874] 927ead9f75 (master): YJIT: unify exits. Patch iseqs only when necessary
https://git.ruby-lang.org/ruby.git/commit/?id=927ead9f75 From 927ead9f7510ff536eea2010e007a92a2bba61c8 Mon Sep 17 00:00:00 2001 From: Alan Wu <XrXr@u...> Date: Wed, 17 Mar 2021 11:54:49 -0400 Subject: YJIT: unify exits. Patch iseqs only when necessary * YJIT: unify exits. Patch iseqs only when necessary This fixes the gotcha that returning YJIT_CANT_COPMILE for an instruction at entry position leading to infinite loop. Also, iseq patching is only done only when necessary, which should make most exits faster. * Now that exits are the same, return YJIT_CANT_COMPILE --- yjit_codegen.c | 80 ++++++++++++++++++++++++++-------------------------------- 1 file changed, 36 insertions(+), 44 deletions(-) diff --git a/yjit_codegen.c b/yjit_codegen.c index 5081e2c19f..02410c04ea 100644 --- a/yjit_codegen.c +++ b/yjit_codegen.c @@ -126,15 +126,32 @@ yjit_load_regs(codeblock_t* cb) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L126 pop(cb, REG_CFP); } -/** -Generate an inline exit to return to the interpreter -*/ -static void -yjit_gen_exit(jitstate_t* jit, ctx_t* ctx, codeblock_t* cb, VALUE* exit_pc) +// Generate an inline exit to return to the interpreter +static uint8_t * +yjit_gen_exit(jitstate_t *jit, ctx_t *ctx, codeblock_t *cb) { + uint8_t *code_ptr = cb_get_ptr(ocb, ocb->write_pos); + + VALUE *exit_pc = jit->pc; + + // YJIT only ever patches the first instruction in an iseq + if (jit->insn_idx == 0) { + // Table mapping opcodes to interpreter handlers + const void *const *handler_table = rb_vm_get_insns_address_table(); + + // Write back the old instruction at the exit PC + // Otherwise the interpreter may jump right back to the + // JITted code we're trying to exit + int exit_opcode = opcode_at_pc(jit->iseq, exit_pc); + void* handler_addr = (void*)handler_table[exit_opcode]; + mov(cb, REG0, const_ptr_opnd(exit_pc)); + mov(cb, REG1, const_ptr_opnd(handler_addr)); + mov(cb, mem_opnd(64, REG0, 0), REG1); + } + + // Generate the code to exit to the interpreters // Write the adjusted SP back into the CFP - if (ctx->sp_offset != 0) - { + if (ctx->sp_offset != 0) { x86opnd_t stack_pointer = ctx_sp_opnd(ctx, 0); lea(cb, REG_SP, stack_pointer); mov(cb, member_opnd(REG_CFP, rb_control_frame_t, sp), REG_SP); @@ -143,7 +160,7 @@ yjit_gen_exit(jitstate_t* jit, ctx_t* ctx, codeblock_t* cb, VALUE* exit_pc) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L160 // Update the CFP on the EC mov(cb, member_opnd(REG_EC, rb_execution_context_t, cfp), REG_CFP); - // Directly return the next PC, which is a constant + // Put PC into the return register, which the post call bytes dispatches to mov(cb, RAX, const_ptr_opnd(exit_pc)); mov(cb, member_opnd(REG_CFP, rb_control_frame_t, pc), RAX); @@ -155,38 +172,17 @@ yjit_gen_exit(jitstate_t* jit, ctx_t* ctx, codeblock_t* cb, VALUE* exit_pc) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L172 } #endif - // Write the post call bytes cb_write_post_call_bytes(cb); + + return code_ptr; } -/** -Generate an out-of-line exit to return to the interpreter -*/ + +// A shorthand for generating an exit in the outline block static uint8_t * -yjit_side_exit(jitstate_t* jit, ctx_t* ctx) +yjit_side_exit(jitstate_t *jit, ctx_t *ctx) { - uint8_t* code_ptr = cb_get_ptr(ocb, ocb->write_pos); - - // Table mapping opcodes to interpreter handlers - const void * const *handler_table = rb_vm_get_insns_address_table(); - - // FIXME: rewriting the old instruction is only necessary if we're - // exiting right at an interpreter entry point - - // Write back the old instruction at the exit PC - // Otherwise the interpreter may jump right back to the - // JITted code we're trying to exit - VALUE* exit_pc = iseq_pc_at_idx(jit->iseq, jit->insn_idx); - int exit_opcode = opcode_at_pc(jit->iseq, exit_pc); - void* handler_addr = (void*)handler_table[exit_opcode]; - mov(ocb, RAX, const_ptr_opnd(exit_pc)); - mov(ocb, RCX, const_ptr_opnd(handler_addr)); - mov(ocb, mem_opnd(64, RAX, 0), RCX); - - // Generate the code to exit to the interpreters - yjit_gen_exit(jit, ctx, ocb, exit_pc); - - return code_ptr; + return yjit_gen_exit(jit, ctx, ocb); } #if RUBY_DEBUG @@ -309,7 +305,7 @@ yjit_gen_block(ctx_t* ctx, block_t* block, rb_execution_context_t* ec) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L305 if (!rb_st_lookup(gen_fns, opcode, (st_data_t*)&gen_fn)) { // If we reach an unknown instruction, // exit to the interpreter and stop compiling - yjit_gen_exit(&jit, ctx, cb, jit.pc); + yjit_gen_exit(&jit, ctx, cb); break; } @@ -330,7 +326,7 @@ yjit_gen_block(ctx_t* ctx, block_t* block, rb_execution_context_t* ec) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L326 // If we can't compile this instruction // exit to the interpreter and stop compiling if (status == YJIT_CANT_COMPILE) { - yjit_gen_exit(&jit, ctx, cb, jit.pc); + yjit_gen_exit(&jit, ctx, cb); break; } @@ -542,7 +538,7 @@ gen_setlocal_wc0(jitstate_t* jit, ctx_t* ctx) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L538 test(cb, flags_opnd, imm_opnd(VM_ENV_FLAG_WB_REQUIRED)); // Create a size-exit to fall back to the interpreter - uint8_t* side_exit = yjit_side_exit(jit, ctx); + uint8_t *side_exit = yjit_side_exit(jit, ctx); // if (flags & VM_ENV_FLAG_WB_REQUIRED) != 0 jnz_ptr(cb, side_exit); @@ -688,8 +684,7 @@ gen_getinstancevariable(jitstate_t* jit, ctx_t* ctx) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L684 // Eventually, we can encode whether an object is T_OBJECT or not // inside object shapes. if (rb_get_alloc_func(self_klass) != rb_class_allocate_instance) { - jmp_ptr(cb, side_exit); - return YJIT_END_BLOCK; + return YJIT_CANT_COMPILE; } RUBY_ASSERT(BUILTIN_TYPE(self_val) == T_OBJECT); // because we checked the allocator @@ -787,10 +782,7 @@ gen_getinstancevariable(jitstate_t* jit, ctx_t* ctx) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L782 return YJIT_END_BLOCK; } - // Take side exit because YJIT_CANT_COMPILE can exit to a JIT entry point and - // form an infinite loop when chain_depth > 0. - jmp_ptr(cb, side_exit); - return YJIT_END_BLOCK; + return YJIT_CANT_COMPILE; } static codegen_status_t -- cgit v1.2.1 -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/