Skip to content

Commit 4f5dc86

Browse files
luke-gruberluke-gru
authored andcommitted
YJIT: implement better getblockparamproxy dispatch
``` Results — YJIT enabled: block.call vs yield, before/after ┌──────────────────────┬─────────────────────┬────────────────────┬────────────┬ │ operation │ before (2a23415) │ after (dd4711e727) │ speedup │ ├──────────────────────┼─────────────────────┼────────────────────┼────────────┼ │ yield (control) │ 58.57M i/s │ 58.56M i/s │ — (stable) │ ├──────────────────────┼─────────────────────┼────────────────────┼────────────┼ │ call (0 args) │ 32.95M i/s │ 57.37M i/s │ 1.74× │ ├──────────────────────┼─────────────────────┼────────────────────┼────────────┼ │ yield(1,2) (control) │ 47.25M i/s │ 51.64M i/s │ — │ ├──────────────────────┼─────────────────────┼────────────────────┼────────────┼ │ call(1,2) (2 args) │ 31.25M i/s │ 51.62M i/s │ 1.65× │ └──────────────────────┴─────────────────────┴────────────────────┴────────────┴ ```
1 parent 7ea07dd commit 4f5dc86

2 files changed

Lines changed: 111 additions & 9 deletions

File tree

yjit/src/codegen.rs

Lines changed: 106 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7486,7 +7486,7 @@ fn gen_send_bmethod(
74867486
}
74877487

74887488
let frame_type = VM_FRAME_MAGIC_BLOCK | VM_FRAME_FLAG_BMETHOD | VM_FRAME_FLAG_LAMBDA;
7489-
perf_call! { gen_send_iseq(jit, asm, iseq, ci, frame_type, Some(capture.ep), cme, block, flags, argc, None) }
7489+
perf_call! { gen_send_iseq(jit, asm, iseq, ci, frame_type, Some(capture.ep), cme, block, flags, argc, None, false) }
74907490
}
74917491

74927492
/// The kind of a value an ISEQ returns
@@ -7582,6 +7582,10 @@ fn gen_send_iseq(
75827582
flags: u32,
75837583
argc: i32,
75847584
captured_opnd: Option<Opnd>,
7585+
// The block param proxy receiver is on the stack below the args. self/EP come from
7586+
// the captured block (as for invokeblock); the proxy is shifted off after the
7587+
// overflow check so the rest of the frame setup is a plain invokeblock.
7588+
proxy_recv: bool,
75857589
) -> Option<CodegenStatus> {
75867590
// Argument count. We will change this as we gather values from
75877591
// sources to satisfy the callee's parameters. To help make sense
@@ -7747,6 +7751,13 @@ fn gen_send_iseq(
77477751
&& !get_iseq_flags_ambiguous_param0(iseq)
77487752
};
77497753
if block_arg0_splat {
7754+
// The block param proxy removes its receiver from the stack only after the
7755+
// overflow check (see proxy_recv below). arg0 auto-splat needs a side exit past
7756+
// that point, which can't reconstruct the proxy at the opt_send_without_block PC.
7757+
if proxy_recv {
7758+
gen_counter_incr(jit, asm, Counter::send_bpp_arg0_splat);
7759+
return None;
7760+
}
77507761
// If block_arg0_splat, we still need side exits after splat, but
77517762
// the splat modifies the stack which breaks side exits. So bail out.
77527763
if splat_call {
@@ -7876,7 +7887,7 @@ fn gen_send_iseq(
78767887
}
78777888
IseqReturn::Value(value) => {
78787889
// Pop receiver and arguments
7879-
asm.stack_pop(argc as usize + if captured_opnd.is_some() { 0 } else { 1 });
7890+
asm.stack_pop(argc as usize + if captured_opnd.is_some() && !proxy_recv { 0 } else { 1 });
78807891

78817892
// Push the return value
78827893
let stack_ret = asm.stack_push(Type::from(value));
@@ -7903,6 +7914,11 @@ fn gen_send_iseq(
79037914
asm.cmp(CFP, stack_limit);
79047915
asm.jbe(Target::side_exit(Counter::guard_send_se_cf_overflow));
79057916

7917+
// Remove the block param proxy receiver from the stack, mirroring vm_invoke_block_opt_call
7918+
if proxy_recv {
7919+
handle_opt_send_shift_stack(asm, argc);
7920+
}
7921+
79067922
if iseq_has_rest && splat_call {
79077923
// Insert length guard for a call to copy_splat_args_for_rest_callee()
79087924
// that will come later. We will have made changes to
@@ -9155,9 +9171,11 @@ fn gen_send_general(
91559171

91569172
// Don't compile calls through singleton classes to avoid retaining the receiver.
91579173
// Make an exception for class methods since classes tend to be retained anyways.
9158-
// Also compile calls on top_self to help tests.
9174+
// Also compile calls on top_self to help tests. The block param proxy is an
9175+
// immortal global root, so retaining it is a non-issue.
91599176
if VALUE(0) != unsafe { FL_TEST(comptime_recv_klass, VALUE(RUBY_FL_SINGLETON as usize)) }
91609177
&& comptime_recv != unsafe { rb_vm_top_self() }
9178+
&& comptime_recv != unsafe { rb_block_param_proxy }
91619179
&& !unsafe { RB_TYPE_P(comptime_recv, RUBY_T_CLASS) }
91629180
&& !unsafe { RB_TYPE_P(comptime_recv, RUBY_T_MODULE) } {
91639181
gen_counter_incr(jit, asm, Counter::send_singleton_class);
@@ -9244,7 +9262,7 @@ fn gen_send_general(
92449262
VM_METHOD_TYPE_ISEQ => {
92459263
let iseq = unsafe { get_def_iseq_ptr((*cme).def) };
92469264
let frame_type = VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL;
9247-
return perf_call! { gen_send_iseq(jit, asm, iseq, ci, frame_type, None, cme, block, flags, argc, None) };
9265+
return perf_call! { gen_send_iseq(jit, asm, iseq, ci, frame_type, None, cme, block, flags, argc, None, false) };
92489266
}
92499267
VM_METHOD_TYPE_CFUNC => {
92509268
return perf_call! { gen_send_cfunc(
@@ -9489,8 +9507,7 @@ fn gen_send_general(
94899507
return jump_to_next_insn(jit, asm);
94909508
}
94919509
OPTIMIZED_METHOD_TYPE_BLOCK_CALL => {
9492-
gen_counter_incr(jit, asm, Counter::send_optimized_method_block_call);
9493-
return None;
9510+
return gen_send_block_param_proxy(jit, asm, ci, block, flags, argc);
94949511
}
94959512
OPTIMIZED_METHOD_TYPE_STRUCT_AREF => {
94969513
if flags & VM_CALL_ARGS_SPLAT != 0 {
@@ -9770,7 +9787,7 @@ fn gen_invokeblock_specialized(
97709787
Counter::guard_invokeblock_iseq_block_changed,
97719788
);
97729789

9773-
perf_call! { gen_send_iseq(jit, asm, comptime_iseq, ci, VM_FRAME_MAGIC_BLOCK, None, 0 as _, None, flags, argc, Some(captured_opnd)) }
9790+
perf_call! { gen_send_iseq(jit, asm, comptime_iseq, ci, VM_FRAME_MAGIC_BLOCK, None, 0 as _, None, flags, argc, Some(captured_opnd), false) }
97749791
} else if comptime_handler.0 & 0x3 == 0x3 { // VM_BH_IFUNC_P
97759792
// We aren't handling CALLER_SETUP_ARG and CALLER_REMOVE_EMPTY_KW_SPLAT yet.
97769793
if flags & VM_CALL_ARGS_SPLAT != 0 {
@@ -9831,6 +9848,87 @@ fn gen_invokeblock_specialized(
98319848
}
98329849
}
98339850

9851+
// blk.call where blk is the block param proxy. Inline the block invocation the way invokeblock does
9852+
// instead of dispatching through the proxy's singleton method. The receiver's singleton class was already
9853+
// guarded by the caller, which is enough to prove the receiver is the (unique) block param proxy.
9854+
fn gen_send_block_param_proxy(
9855+
jit: &mut JITState,
9856+
asm: &mut Assembler,
9857+
ci: *const rb_callinfo,
9858+
block: Option<BlockHandler>,
9859+
flags: u32,
9860+
argc: i32,
9861+
) -> Option<CodegenStatus> {
9862+
// Anything fancier than a plain call falls back to dynamic dispatch, which
9863+
// materializes a Proc and dispatches normally (also handling redefinition).
9864+
if block.is_some() {
9865+
gen_counter_incr(jit, asm, Counter::send_bpp_not_simple);
9866+
return None;
9867+
}
9868+
if flags & (VM_CALL_ARGS_SPLAT | VM_CALL_KWARG | VM_CALL_KW_SPLAT | VM_CALL_ARGS_BLOCKARG | VM_CALL_OPT_SEND) != 0 {
9869+
gen_counter_incr(jit, asm, Counter::send_bpp_not_simple);
9870+
return None;
9871+
}
9872+
9873+
// Fall back to dynamic dispatch if this callsite is megamorphic
9874+
if asm.ctx.get_chain_depth() >= SEND_MAX_DEPTH {
9875+
gen_counter_incr(jit, asm, Counter::send_bpp_megamorphic);
9876+
return None;
9877+
}
9878+
9879+
// The block handler lives in the local EP: the same source both the interpreter's
9880+
// vm_call_opt_block_call and the getblockparamproxy instruction read from.
9881+
let cfp = jit.get_cfp();
9882+
let lep = unsafe { rb_vm_ep_local_ep(get_cfp_ep(cfp)) };
9883+
let comptime_handler = unsafe { *lep.offset(VM_ENV_DATA_INDEX_SPECVAL as isize) };
9884+
9885+
// Only specialize an ISEQ block; other block handler types fall back to dynamic dispatch.
9886+
if comptime_handler.0 & 0x3 != 0x1 { // VM_BH_ISEQ_BLOCK_P
9887+
gen_counter_incr(jit, asm, Counter::send_bpp_not_iseq_block);
9888+
return None;
9889+
}
9890+
9891+
if !assume_bop_not_redefined(jit, asm, PROC_REDEFINED_OP_FLAG, BOP_CALL) {
9892+
return None;
9893+
}
9894+
9895+
gen_counter_incr(jit, asm, Counter::send_bpp_dispatch);
9896+
9897+
asm_comment!(asm, "get local EP");
9898+
let ep_opnd = gen_get_lep(jit, asm);
9899+
let block_handler_opnd = asm.load(
9900+
Opnd::mem(64, ep_opnd, SIZEOF_VALUE_I32 * VM_ENV_DATA_INDEX_SPECVAL)
9901+
);
9902+
9903+
asm_comment!(asm, "guard block_handler type");
9904+
let tag_opnd = asm.and(block_handler_opnd, 0x3.into());
9905+
asm.cmp(tag_opnd, 0x1.into()); // VM_BH_ISEQ_BLOCK_P
9906+
jit_chain_guard(
9907+
JCC_JNE,
9908+
jit,
9909+
asm,
9910+
SEND_MAX_DEPTH,
9911+
Counter::guard_invokeblock_tag_changed,
9912+
);
9913+
9914+
let comptime_captured = unsafe { ((comptime_handler.0 & !0x3) as *const rb_captured_block).as_ref().unwrap() };
9915+
let comptime_iseq = unsafe { *comptime_captured.code.iseq.as_ref() };
9916+
9917+
asm_comment!(asm, "guard known ISEQ");
9918+
let captured_opnd = asm.and(block_handler_opnd, Opnd::Imm(!0x3));
9919+
let iseq_opnd = asm.load(Opnd::mem(64, captured_opnd, SIZEOF_VALUE_I32 * 2));
9920+
asm.cmp(iseq_opnd, VALUE::from(comptime_iseq).into());
9921+
jit_chain_guard(
9922+
JCC_JNE,
9923+
jit,
9924+
asm,
9925+
SEND_MAX_DEPTH,
9926+
Counter::guard_invokeblock_iseq_block_changed,
9927+
);
9928+
9929+
perf_call! { gen_send_iseq(jit, asm, comptime_iseq, ci, VM_FRAME_MAGIC_BLOCK, None, 0 as _, None, flags, argc, Some(captured_opnd), true) }
9930+
}
9931+
98349932
fn gen_invokesuper(
98359933
jit: &mut JITState,
98369934
asm: &mut Assembler,
@@ -10000,7 +10098,7 @@ fn gen_invokesuper_specialized(
1000010098
VM_METHOD_TYPE_ISEQ => {
1000110099
let iseq = unsafe { get_def_iseq_ptr((*cme).def) };
1000210100
let frame_type = VM_FRAME_MAGIC_METHOD | VM_ENV_FLAG_LOCAL;
10003-
perf_call! { gen_send_iseq(jit, asm, iseq, ci, frame_type, None, cme, Some(block), ci_flags, argc, None) }
10101+
perf_call! { gen_send_iseq(jit, asm, iseq, ci, frame_type, None, cme, Some(block), ci_flags, argc, None, false) }
1000410102
}
1000510103
VM_METHOD_TYPE_CFUNC => {
1000610104
perf_call! { gen_send_cfunc(jit, asm, ci, cme, Some(block), None, ci_flags, argc) }

yjit/src/stats.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,11 @@ make_counters! {
332332
send_ivar_set_method,
333333
send_zsuper_method,
334334
send_undef_method,
335-
send_optimized_method_block_call,
335+
send_bpp_dispatch,
336+
send_bpp_not_simple,
337+
send_bpp_megamorphic,
338+
send_bpp_not_iseq_block,
339+
send_bpp_arg0_splat,
336340
send_call_block,
337341
send_call_kwarg,
338342
send_call_multi_ractor,

0 commit comments

Comments
 (0)