diff --git a/benchmark/vm_send_cfunc.yml b/benchmark/vm_send_cfunc.yml new file mode 100644 index 0000000000..b114ac317d --- /dev/null +++ b/benchmark/vm_send_cfunc.yml @@ -0,0 +1,3 @@ +benchmark: + vm_send_cfunc: self.class +loop_count: 100000000 diff --git a/debug_counter.h b/debug_counter.h index 62b3cea909..4e4c993aa2 100644 --- a/debug_counter.h +++ b/debug_counter.h @@ -66,6 +66,7 @@ RB_DEBUG_COUNTER(ccf_iseq_opt) /* has_opt == TRUE (has optional parameters), but RB_DEBUG_COUNTER(ccf_iseq_kw1) /* vm_call_iseq_setup_kwparm_kwarg() */ RB_DEBUG_COUNTER(ccf_iseq_kw2) /* vm_call_iseq_setup_kwparm_nokwarg() */ RB_DEBUG_COUNTER(ccf_cfunc) +RB_DEBUG_COUNTER(ccf_cfunc_with_frame) RB_DEBUG_COUNTER(ccf_ivar) /* attr_reader */ RB_DEBUG_COUNTER(ccf_attrset) /* attr_writer */ RB_DEBUG_COUNTER(ccf_method_missing) diff --git a/tool/ruby_vm/views/_mjit_compile_send.erb b/tool/ruby_vm/views/_mjit_compile_send.erb index 255648bc2d..2b1ecdb2f3 100644 --- a/tool/ruby_vm/views/_mjit_compile_send.erb +++ b/tool/ruby_vm/views/_mjit_compile_send.erb @@ -18,9 +18,12 @@ % # compiler: Inline send insn where some supported fastpath is used. const rb_iseq_t *iseq = NULL; const CALL_INFO ci = cd->ci; + int kw_splat = IS_ARGS_KW_SPLAT(ci) > 0; + extern bool rb_splat_or_kwargs_p(const struct rb_callinfo *restrict ci); if (!status->compile_info->disable_send_cache && has_valid_method_type(captured_cc) && ( -% # `CC_SET_FASTPATH(cc, vm_call_cfunc, TRUE)` in `vm_call_method_each_type` - vm_cc_cme(captured_cc)->def->type == VM_METHOD_TYPE_CFUNC +% # `CC_SET_FASTPATH(cd->cc, vm_call_cfunc_with_frame, ...)` in `vm_call_cfunc` + (vm_cc_cme(captured_cc)->def->type == VM_METHOD_TYPE_CFUNC + && !rb_splat_or_kwargs_p(ci) && !kw_splat) % # `CC_SET_FASTPATH(cc, vm_call_iseq_setup_func(...), vm_call_iseq_optimizable_p(...))` in `vm_callee_setup_arg`, % # and support only non-VM_CALL_TAILCALL path inside it || (vm_cc_cme(captured_cc)->def->type == VM_METHOD_TYPE_ISEQ @@ -61,14 +64,14 @@ % else fprintf(f, " calling.block_handler = VM_BLOCK_HANDLER_NONE;\n"); % end - fprintf(f, " calling.kw_splat = %d;\n", IS_ARGS_KW_SPLAT(ci) > 0); + fprintf(f, " calling.kw_splat = %d;\n", kw_splat); fprintf(f, " calling.recv = stack[%d];\n", b->stack_size + sp_inc - 1); fprintf(f, " calling.argc = %d;\n", vm_ci_argc(ci)); if (vm_cc_cme(captured_cc)->def->type == VM_METHOD_TYPE_CFUNC) { % # TODO: optimize this more fprintf(f, " CALL_DATA cd = (CALL_DATA)0x%"PRIxVALUE";\n", operands[0]); - fprintf(f, " val = vm_call_cfunc(ec, reg_cfp, &calling, cd);\n"); + fprintf(f, " val = vm_call_cfunc_with_frame(ec, reg_cfp, &calling, cd);\n"); } else { // VM_METHOD_TYPE_ISEQ % # fastpath_applied_iseq_p checks rb_simple_iseq_p, which ensures has_opt == FALSE diff --git a/vm_insnhelper.c b/vm_insnhelper.c index 21b5e93ca3..a4edd9a797 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -1964,6 +1964,13 @@ rb_iseq_only_kwparam_p(const rb_iseq_t *iseq) iseq->body->param.flags.has_block == FALSE; } +// If true, cc->call needs to include `CALLER_SETUP_ARG` (i.e. can't be skipped in fastpath) +MJIT_STATIC bool +rb_splat_or_kwargs_p(const struct rb_callinfo *restrict ci) +{ + return IS_ARGS_SPLAT(ci) || IS_ARGS_KW_OR_KW_SPLAT(ci); +} + static inline void CALLER_SETUP_ARG(struct rb_control_frame_struct *restrict cfp, @@ -2508,6 +2515,7 @@ vm_method_cfunc_entry(const rb_callable_method_entry_t *me) static VALUE vm_call_cfunc_with_frame(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb_calling_info *calling, struct rb_call_data *cd) { + RB_DEBUG_COUNTER_INC(ccf_cfunc_with_frame); const struct rb_callinfo *ci = cd->ci; const struct rb_callcache *cc = cd->cc; VALUE val; @@ -2555,6 +2563,7 @@ vm_call_cfunc(rb_execution_context_t *ec, rb_control_frame_t *reg_cfp, struct rb CALLER_SETUP_ARG(reg_cfp, calling, ci); CALLER_REMOVE_EMPTY_KW_SPLAT(reg_cfp, calling, ci); + CC_SET_FASTPATH(cd->cc, vm_call_cfunc_with_frame, !rb_splat_or_kwargs_p(ci) && !calling->kw_splat); return vm_call_cfunc_with_frame(ec, reg_cfp, calling, cd); }