ruby-changes:69018
From: Alan <ko1@a...>
Date: Thu, 21 Oct 2021 08:20:16 +0900 (JST)
Subject: [ruby-changes:69018] 0758115d11 (master): Implement send with blocks
https://git.ruby-lang.org/ruby.git/commit/?id=0758115d11 From 0758115d112a1ff452876d3689d20e84d5ff1e37 Mon Sep 17 00:00:00 2001 From: Alan Wu <XrXr@u...> Date: Tue, 4 May 2021 12:35:51 -0400 Subject: Implement send with blocks * Implement send with blocks Not that much extra work compared to `opt_send_without_block`. Moved the stack over flow check because it could've exited after changes are made to cfp. * rename oswb counters * Might as well implement sending block to cfuncs * Disable sending blocks to cfuncs for now * Reconstruct interpreter sp before calling into cfuncs In case the callee cfunc calls a method or delegates to a block. This also has the side benefit of letting call sites that sometimes are iseq calls and sometimes cfunc call share the same successor. * only sync with interpreter sp when passing a block Co-authored-by: Maxime Chevalier-Boisvert <maximechevalierb@g...> Co-authored-by: Aaron Patterson <aaron.patterson@s...> --- bootstraptest/test_yjit.rb | 24 ++++++ yjit.rb | 2 +- yjit_codegen.c | 199 +++++++++++++++++++++++++++++---------------- yjit_codegen.h | 3 + yjit_iface.c | 1 + yjit_iface.h | 52 ++++++------ 6 files changed, 184 insertions(+), 97 deletions(-) diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index 6ac31f8134..b8ae5a989c 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -799,3 +799,27 @@ assert_equal 'raised', %q{ https://github.com/ruby/ruby/blob/trunk/bootstraptest/test_yjit.rb#L799 :raised end } + +# test calling Ruby method with a block +assert_equal '[1, 2, 42]', %q{ +def thing(a, b) + [a, b, yield] +end + +def use + thing(1,2) { 42 } +end + +use +use +} + +# test calling C method with a block +assert_equal '[42, 42]', %q{ +def use(array, initial) + array.reduce(initial) { |a, b| a + b } +end + +use([], 0) +[use([2, 2], 38), use([14, 14, 14], 0)] +} diff --git a/yjit.rb b/yjit.rb index 8203051b81..cd3ad4d652 100644 --- a/yjit.rb +++ b/yjit.rb @@ -82,7 +82,7 @@ module YJIT https://github.com/ruby/ruby/blob/trunk/yjit.rb#L82 $stderr.puts("Number of bindings allocated: %d\n" % counters[:binding_allocations]) $stderr.puts("Number of locals modified through binding: %d\n" % counters[:binding_set]) - print_counters(counters, prefix: 'oswb_', prompt: 'opt_send_without_block exit reasons: ') + print_counters(counters, prefix: 'send_', prompt: 'method call exit reasons: ') print_counters(counters, prefix: 'leave_', prompt: 'leave exit reasons: ') print_counters(counters, prefix: 'getivar_', prompt: 'getinstancevariable exit reasons:') print_counters(counters, prefix: 'setivar_', prompt: 'setinstancevariable exit reasons:') diff --git a/yjit_codegen.c b/yjit_codegen.c index 62be5e3d2e..dfcf198978 100644 --- a/yjit_codegen.c +++ b/yjit_codegen.c @@ -46,7 +46,7 @@ jit_print_loc(jitstate_t* jit, const char* msg) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L46 static int jit_get_opcode(jitstate_t* jit) { - return yjit_opcode_at_pc(jit->iseq, jit->pc); + return jit->opcode; } // Get the index of the next instruction @@ -362,11 +362,9 @@ yjit_gen_block(block_t *block, rb_execution_context_t *ec) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L362 // Initialize a JIT state object jitstate_t jit = { - block, - iseq, - 0, - 0, - ec + .block = block, + .iseq = iseq, + .ec = ec }; // Mark the start position of the block @@ -389,6 +387,7 @@ yjit_gen_block(block_t *block, rb_execution_context_t *ec) https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L387 // Set the current instruction jit.insn_idx = insn_idx; jit.pc = pc; + jit.opcode = opcode; // Lookup the codegen function for this instruction codegen_fn gen_fn = gen_fns[opcode]; @@ -831,7 +830,7 @@ bool rb_iv_index_tbl_lookup(struct st_table *iv_index_tbl, ID id, struct rb_iv_i https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L830 enum { GETIVAR_MAX_DEPTH = 10, // up to 5 different classes, and embedded or not for each OPT_AREF_MAX_CHAIN_DEPTH = 2, // hashes and arrays - OSWB_MAX_DEPTH = 5, // up to 5 different classes + SEND_MAX_DEPTH = 5, // up to 5 different classes }; // Codegen for setting an instance variable. @@ -1664,29 +1663,29 @@ jit_protected_callee_ancestry_guard(jitstate_t *jit, codeblock_t *cb, const rb_c https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L1663 call_ptr(cb, REG0, (void *)&rb_obj_is_kind_of); yjit_load_regs(cb); test(cb, RAX, RAX); - jz_ptr(cb, COUNTED_EXIT(side_exit, oswb_se_protected_check_failed)); + jz_ptr(cb, COUNTED_EXIT(side_exit, send_se_protected_check_failed)); } static codegen_status_t -gen_oswb_cfunc(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const rb_callable_method_entry_t *cme, int32_t argc) +gen_send_cfunc(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const rb_callable_method_entry_t *cme, rb_iseq_t *block, const int32_t argc) { const rb_method_cfunc_t *cfunc = UNALIGNED_MEMBER_PTR(cme->def, body.cfunc); // If the function expects a Ruby array of arguments if (cfunc->argc < 0 && cfunc->argc != -1) { - GEN_COUNTER_INC(cb, oswb_cfunc_ruby_array_varg); + GEN_COUNTER_INC(cb, send_cfunc_ruby_array_varg); return YJIT_CANT_COMPILE; } // If the argument count doesn't match if (cfunc->argc >= 0 && cfunc->argc != argc) { - GEN_COUNTER_INC(cb, oswb_cfunc_argc_mismatch); + GEN_COUNTER_INC(cb, send_cfunc_argc_mismatch); return YJIT_CANT_COMPILE; } // Don't JIT functions that need C stack arguments for now if (argc + 1 > NUM_C_ARG_REGS) { - GEN_COUNTER_INC(cb, oswb_cfunc_toomany_args); + GEN_COUNTER_INC(cb, send_cfunc_toomany_args); return YJIT_CANT_COMPILE; } @@ -1699,42 +1698,62 @@ gen_oswb_cfunc(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L1698 //print_str(cb, "recv"); //print_ptr(cb, recv); + // If this function needs a Ruby stack frame + const bool push_frame = cfunc_needs_frame(cfunc); + // Create a size-exit to fall back to the interpreter uint8_t *side_exit = yjit_side_exit(jit, ctx); // Check for interrupts yjit_check_ints(cb, side_exit); + if (push_frame) { + // Stack overflow check + // #define CHECK_VM_STACK_OVERFLOW0(cfp, sp, margin) + // REG_CFP <= REG_SP + 4 * sizeof(VALUE) + sizeof(rb_control_frame_t) + lea(cb, REG0, ctx_sp_opnd(ctx, sizeof(VALUE) * 4 + sizeof(rb_control_frame_t))); + cmp(cb, REG_CFP, REG0); + jle_ptr(cb, COUNTED_EXIT(side_exit, send_se_cf_overflow)); + } + // Points to the receiver operand on the stack x86opnd_t recv = ctx_stack_opnd(ctx, argc); // Store incremented PC into current control frame in case callee raises. - mov(cb, REG0, const_ptr_opnd(jit->pc + insn_len(BIN(opt_send_without_block)))); + mov(cb, REG0, const_ptr_opnd(jit->pc + insn_len(jit->opcode))); mov(cb, mem_opnd(64, REG_CFP, offsetof(rb_control_frame_t, pc)), REG0); - // If this function needs a Ruby stack frame - if (cfunc_needs_frame(cfunc)) { - // Stack overflow check - // #define CHECK_VM_STACK_OVERFLOW0(cfp, sp, margin) - // REG_CFP <= REG_SP + 4 * sizeof(VALUE) + sizeof(rb_control_frame_t) - lea(cb, REG0, ctx_sp_opnd(ctx, sizeof(VALUE) * 4 + sizeof(rb_control_frame_t))); - cmp(cb, REG_CFP, REG0); - jle_ptr(cb, COUNTED_EXIT(side_exit, oswb_se_cf_overflow)); + if (push_frame) { + if (block) { + // Change cfp->block_code in the current frame. See vm_caller_setup_arg_block(). + // VM_CFP_TO_CAPTURED_BLCOK does &cfp->self, rb_captured_block->code.iseq aliases + // with cfp->block_code. + jit_mov_gc_ptr(jit, cb, REG0, (VALUE)block); + mov(cb, member_opnd(REG_CFP, rb_control_frame_t, block_code), REG0); + } // Increment the stack pointer by 3 (in the callee) // sp += 3 lea(cb, REG0, ctx_sp_opnd(ctx, sizeof(VALUE) * 3)); + // Write method entry at sp[-3] + // sp[-3] = me; // Put compile time cme into REG1. It's assumed to be valid because we are notified when // any cme we depend on become outdated. See rb_yjit_method_lookup_change(). jit_mov_gc_ptr(jit, cb, REG1, (VALUE)cme); - // Write method entry at sp[-3] - // sp[-3] = me; mov(cb, mem_opnd(64, REG0, 8 * -3), REG1); // Write block handler at sp[-2] // sp[-2] = block_handler; - mov(cb, mem_opnd(64, REG0, 8 * -2), imm_opnd(VM_BLOCK_HANDLER_NONE)); + if (block) { + // reg1 = VM_BH_FROM_ISEQ_BLOCK(VM_CFP_TO_CAPTURED_BLOCK(reg_cfp)); + lea(cb, REG1, member_opnd(REG_CFP, rb_control_frame_t, self)); + or(cb, REG1, imm_opnd(1)); + mov(cb, mem_opnd(64, REG0, 8 * -2), REG1); + } + else { + mov(cb, mem_opnd(64, REG0, 8 * -2), imm_opnd(VM_BLOCK_HANDLER_NONE)); + } // Write env flags at sp[-1] // sp[-1] = frame_type; @@ -1776,22 +1795,33 @@ gen_oswb_cfunc(jitstate_t *jit, ctx_t *ctx, const struct rb_callinfo *ci, const https://github.com/ruby/ruby/blob/trunk/yjit_codegen.c#L1795 yjit_save_regs(cb); // Call check_cfunc_dispatch - mov(cb, RDI, recv); - jit_mov_gc_ptr(jit, cb, RSI, (VALUE)ci); - mov(cb, RDX, const_ptr_opnd((void *)cfunc->func)); - jit_mov_gc_ptr(jit, cb, RCX, (VALUE)cme); + mov(cb, C_ARG_REGS[0], recv); + jit_mov_gc_ptr(jit, cb, C_ARG_REGS[1], (VALUE)ci); + mov(cb, C_ARG_REGS[2 (... truncated) -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/