Skip to content

Commit e74f5f7

Browse files
authored
ZJIT: Compile sendforward with dynamic dispatch (ruby#14501)
* ZJIT: Compile sendforward with dynamic dispatch * Reload locals only if it has blockiseq * Add a test case of ... with other args
1 parent 7ff036d commit e74f5f7

4 files changed

Lines changed: 102 additions & 12 deletions

File tree

test/ruby/test_zjit.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ def test(...) = 1
478478
end
479479

480480
def test_sendforward
481-
assert_runs '[1, 2]', %q{
481+
assert_compiles '[1, 2]', %q{
482482
def callee(a, b) = [a, b]
483483
def test(...) = callee(...)
484484
test(1, 2)

zjit/src/codegen.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
365365
Insn::IfTrue { val, target } => no_output!(gen_if_true(jit, asm, opnd!(val), target)),
366366
Insn::IfFalse { val, target } => no_output!(gen_if_false(jit, asm, opnd!(val), target)),
367367
&Insn::Send { cd, blockiseq, state, .. } => gen_send(jit, asm, cd, blockiseq, &function.frame_state(state)),
368+
&Insn::SendForward { cd, blockiseq, state, .. } => gen_send_forward(jit, asm, cd, blockiseq, &function.frame_state(state)),
368369
Insn::SendWithoutBlock { cd, state, def_type, .. } => gen_send_without_block(jit, asm, *cd, *def_type, &function.frame_state(*state)),
369370
// Give up SendWithoutBlockDirect for 6+ args since asm.ccall() doesn't support it.
370371
Insn::SendWithoutBlockDirect { cd, state, args, .. } if args.len() + 1 > C_ARG_OPNDS.len() => // +1 for self
@@ -1041,6 +1042,29 @@ fn gen_send(
10411042
)
10421043
}
10431044

1045+
/// Compile a dynamic dispatch with `...`
1046+
fn gen_send_forward(
1047+
jit: &mut JITState,
1048+
asm: &mut Assembler,
1049+
cd: *const rb_call_data,
1050+
blockiseq: IseqPtr,
1051+
state: &FrameState,
1052+
) -> lir::Opnd {
1053+
gen_incr_counter(asm, Counter::dynamic_send_count);
1054+
gen_incr_counter(asm, Counter::dynamic_send_type_send_forward);
1055+
1056+
gen_prepare_non_leaf_call(jit, asm, state);
1057+
1058+
asm_comment!(asm, "call #{} with dynamic dispatch", ruby_call_method_name(cd));
1059+
unsafe extern "C" {
1060+
fn rb_vm_sendforward(ec: EcPtr, cfp: CfpPtr, cd: VALUE, blockiseq: IseqPtr) -> VALUE;
1061+
}
1062+
asm.ccall(
1063+
rb_vm_sendforward as *const u8,
1064+
vec![EC, CFP, (cd as usize).into(), VALUE(blockiseq as usize).into()],
1065+
)
1066+
}
1067+
10441068
/// Compile a dynamic dispatch without block
10451069
fn gen_send_without_block(
10461070
jit: &mut JITState,

zjit/src/hir.rs

Lines changed: 76 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,7 @@ pub enum Insn {
642642
state: InsnId,
643643
},
644644
Send { recv: InsnId, cd: *const rb_call_data, blockiseq: IseqPtr, args: Vec<InsnId>, state: InsnId },
645+
SendForward { recv: InsnId, cd: *const rb_call_data, blockiseq: IseqPtr, args: Vec<InsnId>, state: InsnId },
645646
InvokeSuper { recv: InsnId, cd: *const rb_call_data, blockiseq: IseqPtr, args: Vec<InsnId>, state: InsnId },
646647
InvokeBlock { cd: *const rb_call_data, args: Vec<InsnId>, state: InsnId },
647648

@@ -902,6 +903,13 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
902903
}
903904
Ok(())
904905
}
906+
Insn::SendForward { cd, args, blockiseq, .. } => {
907+
write!(f, "SendForward {:p}, :{}", self.ptr_map.map_ptr(blockiseq), ruby_call_method_name(*cd))?;
908+
for arg in args {
909+
write!(f, ", {arg}")?;
910+
}
911+
Ok(())
912+
}
905913
Insn::InvokeSuper { recv, blockiseq, args, .. } => {
906914
write!(f, "InvokeSuper {recv}, {:p}", self.ptr_map.map_ptr(blockiseq))?;
907915
for arg in args {
@@ -1422,6 +1430,13 @@ impl Function {
14221430
args: find_vec!(args),
14231431
state,
14241432
},
1433+
&SendForward { recv, cd, blockiseq, ref args, state } => SendForward {
1434+
recv: find!(recv),
1435+
cd,
1436+
blockiseq,
1437+
args: find_vec!(args),
1438+
state,
1439+
},
14251440
&InvokeSuper { recv, cd, blockiseq, ref args, state } => InvokeSuper {
14261441
recv: find!(recv),
14271442
cd,
@@ -1552,6 +1567,7 @@ impl Function {
15521567
Insn::SendWithoutBlock { .. } => types::BasicObject,
15531568
Insn::SendWithoutBlockDirect { .. } => types::BasicObject,
15541569
Insn::Send { .. } => types::BasicObject,
1570+
Insn::SendForward { .. } => types::BasicObject,
15551571
Insn::InvokeSuper { .. } => types::BasicObject,
15561572
Insn::InvokeBlock { .. } => types::BasicObject,
15571573
Insn::InvokeBuiltin { return_type, .. } => return_type.unwrap_or(types::BasicObject),
@@ -2430,6 +2446,7 @@ impl Function {
24302446
worklist.push_back(state);
24312447
}
24322448
&Insn::Send { recv, ref args, state, .. }
2449+
| &Insn::SendForward { recv, ref args, state, .. }
24332450
| &Insn::SendWithoutBlock { recv, ref args, state, .. }
24342451
| &Insn::CCallVariadic { recv, ref args, state, .. }
24352452
| &Insn::SendWithoutBlockDirect { recv, ref args, state, .. }
@@ -3774,13 +3791,44 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
37743791
let send = fun.push_insn(block, Insn::Send { recv, cd, blockiseq, args, state: exit_id });
37753792
state.stack_push(send);
37763793

3777-
// Reload locals that may have been modified by the blockiseq.
3778-
// TODO: Avoid reloading locals that are not referenced by the blockiseq
3779-
// or not used after this. Max thinks we could eventually DCE them.
3780-
for local_idx in 0..state.locals.len() {
3781-
let ep_offset = local_idx_to_ep_offset(iseq, local_idx) as u32;
3782-
let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0 });
3783-
state.setlocal(ep_offset, val);
3794+
if !blockiseq.is_null() {
3795+
// Reload locals that may have been modified by the blockiseq.
3796+
// TODO: Avoid reloading locals that are not referenced by the blockiseq
3797+
// or not used after this. Max thinks we could eventually DCE them.
3798+
for local_idx in 0..state.locals.len() {
3799+
let ep_offset = local_idx_to_ep_offset(iseq, local_idx) as u32;
3800+
let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0 });
3801+
state.setlocal(ep_offset, val);
3802+
}
3803+
}
3804+
}
3805+
YARVINSN_sendforward => {
3806+
let cd: *const rb_call_data = get_arg(pc, 0).as_ptr();
3807+
let blockiseq: IseqPtr = get_arg(pc, 1).as_iseq();
3808+
let call_info = unsafe { rb_get_call_data_ci(cd) };
3809+
let flags = unsafe { rb_vm_ci_flag(call_info) };
3810+
let forwarding = (flags & VM_CALL_FORWARDING) != 0;
3811+
if let Err(call_type) = unhandled_call_type(flags) {
3812+
// Can't handle the call type; side-exit into the interpreter
3813+
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
3814+
fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) });
3815+
break; // End the block
3816+
}
3817+
let argc = unsafe { vm_ci_argc((*cd).ci) };
3818+
3819+
let args = state.stack_pop_n(argc as usize + usize::from(forwarding))?;
3820+
let recv = state.stack_pop()?;
3821+
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
3822+
let send_forward = fun.push_insn(block, Insn::SendForward { recv, cd, blockiseq, args, state: exit_id });
3823+
state.stack_push(send_forward);
3824+
3825+
if !blockiseq.is_null() {
3826+
// Reload locals that may have been modified by the blockiseq.
3827+
for local_idx in 0..state.locals.len() {
3828+
let ep_offset = local_idx_to_ep_offset(iseq, local_idx) as u32;
3829+
let val = fun.push_insn(block, Insn::GetLocal { ep_offset, level: 0 });
3830+
state.setlocal(ep_offset, val);
3831+
}
37843832
}
37853833
}
37863834
YARVINSN_invokesuper => {
@@ -5315,7 +5363,6 @@ mod tests {
53155363
fn test@<compiled>:2:
53165364
bb0(v0:BasicObject, v1:BasicObject):
53175365
v6:BasicObject = Send v0, 0x1000, :foo, v1
5318-
v7:BasicObject = GetLocal l0, EP@3
53195366
CheckInterrupts
53205367
Return v6
53215368
");
@@ -5457,14 +5504,33 @@ mod tests {
54575504
}
54585505

54595506
#[test]
5460-
fn test_cant_compile_forwarding() {
5507+
fn test_compile_forwarding() {
54615508
eval("
54625509
def test(...) = foo(...)
54635510
");
54645511
assert_snapshot!(hir_string("test"), @r"
54655512
fn test@<compiled>:2:
54665513
bb0(v0:BasicObject, v1:BasicObject):
5467-
SideExit UnhandledYARVInsn(sendforward)
5514+
v6:BasicObject = SendForward 0x1000, :foo, v1
5515+
CheckInterrupts
5516+
Return v6
5517+
");
5518+
}
5519+
5520+
#[test]
5521+
fn test_compile_triple_dots_with_positional_args() {
5522+
eval("
5523+
def test(a, ...) = foo(a, ...)
5524+
");
5525+
assert_snapshot!(hir_string("test"), @r"
5526+
fn test@<compiled>:2:
5527+
bb0(v0:BasicObject, v1:BasicObject, v2:ArrayExact, v3:BasicObject, v4:BasicObject):
5528+
v5:NilClass = Const Value(nil)
5529+
v10:ArrayExact = ToArray v2
5530+
PatchPoint NoEPEscape(test)
5531+
GuardBlockParamProxy l0
5532+
v15:BasicObject[BlockParamProxy] = Const Value(VALUE(0x1000))
5533+
SideExit UnhandledYARVInsn(splatkw)
54685534
");
54695535
}
54705536

@@ -8274,7 +8340,6 @@ mod opt_tests {
82748340
GuardBlockParamProxy l0
82758341
v7:BasicObject[BlockParamProxy] = Const Value(VALUE(0x1000))
82768342
v9:BasicObject = Send v0, 0x1008, :tap, v7
8277-
v10:BasicObject = GetLocal l0, EP@3
82788343
CheckInterrupts
82798344
Return v9
82808345
");

zjit/src/stats.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ make_counters! {
133133
dynamic_send_count,
134134
dynamic_send_type_send_without_block,
135135
dynamic_send_type_send,
136+
dynamic_send_type_send_forward,
136137
dynamic_send_type_invokeblock,
137138
dynamic_send_type_invokesuper,
138139

0 commit comments

Comments
 (0)