Skip to content

Commit d82e9f9

Browse files
authored
ZJIT: Support invokeblock insn (ruby#14496)
ZJIT: Support invokeblock insn Basically like other send-ish insns, except that it doesn't pop the receiver.
1 parent 3364d79 commit d82e9f9

File tree

3 files changed

+135
-4
lines changed

3 files changed

+135
-4
lines changed

test/ruby/test_zjit.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2584,6 +2584,46 @@ def test(x)
25842584
}, insns: [:opt_case_dispatch]
25852585
end
25862586

2587+
def test_invokeblock
2588+
assert_compiles '42', %q{
2589+
def test
2590+
yield
2591+
end
2592+
test { 42 }
2593+
}, insns: [:invokeblock]
2594+
end
2595+
2596+
def test_invokeblock_with_args
2597+
assert_compiles '3', %q{
2598+
def test(x, y)
2599+
yield x, y
2600+
end
2601+
test(1, 2) { |a, b| a + b }
2602+
}, insns: [:invokeblock]
2603+
end
2604+
2605+
def test_invokeblock_no_block_given
2606+
assert_compiles ':error', %q{
2607+
def test
2608+
yield rescue :error
2609+
end
2610+
test
2611+
}, insns: [:invokeblock]
2612+
end
2613+
2614+
def test_invokeblock_multiple_yields
2615+
assert_compiles "[1, 2, 3]", %q{
2616+
results = []
2617+
def test
2618+
yield 1
2619+
yield 2
2620+
yield 3
2621+
end
2622+
test { |x| results << x }
2623+
results
2624+
}, insns: [:invokeblock]
2625+
end
2626+
25872627
private
25882628

25892629
# Assert that every method call in `test_script` can be compiled by ZJIT

zjit/src/codegen.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
369369
gen_send_without_block(jit, asm, *cd, &function.frame_state(*state)),
370370
Insn::SendWithoutBlockDirect { cme, iseq, self_val, args, state, .. } => gen_send_without_block_direct(cb, jit, asm, *cme, *iseq, opnd!(self_val), opnds!(args), &function.frame_state(*state)),
371371
&Insn::InvokeSuper { cd, blockiseq, state, .. } => gen_invokesuper(jit, asm, cd, blockiseq, &function.frame_state(state)),
372+
Insn::InvokeBlock { cd, state, .. } => gen_invoke_block(jit, asm, *cd, &function.frame_state(*state)),
372373
// Ensure we have enough room fit ec, self, and arguments
373374
// TODO remove this check when we have stack args (we can use Time.new to test it)
374375
Insn::InvokeBuiltin { bf, state, .. } if bf.argc + 2 > (C_ARG_OPNDS.len() as i32) => return Err(*state),
@@ -1093,6 +1094,31 @@ fn gen_send_without_block_direct(
10931094
ret
10941095
}
10951096

1097+
/// Compile for invokeblock
1098+
fn gen_invoke_block(
1099+
jit: &mut JITState,
1100+
asm: &mut Assembler,
1101+
cd: *const rb_call_data,
1102+
state: &FrameState,
1103+
) -> lir::Opnd {
1104+
gen_incr_counter(asm, Counter::dynamic_send_count);
1105+
1106+
// Save PC and SP, spill locals and stack
1107+
gen_prepare_call_with_gc(asm, state);
1108+
gen_save_sp(asm, state.stack().len());
1109+
gen_spill_locals(jit, asm, state);
1110+
gen_spill_stack(jit, asm, state);
1111+
1112+
asm_comment!(asm, "call invokeblock");
1113+
unsafe extern "C" {
1114+
fn rb_vm_invokeblock(ec: EcPtr, cfp: CfpPtr, cd: VALUE) -> VALUE;
1115+
}
1116+
asm.ccall(
1117+
rb_vm_invokeblock as *const u8,
1118+
vec![EC, CFP, (cd as usize).into()],
1119+
)
1120+
}
1121+
10961122
/// Compile a dynamic dispatch for `super`
10971123
fn gen_invokesuper(
10981124
jit: &mut JITState,

zjit/src/hir.rs

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,7 @@ pub enum Insn {
585585
SendWithoutBlock { self_val: InsnId, cd: *const rb_call_data, args: Vec<InsnId>, state: InsnId },
586586
Send { self_val: InsnId, cd: *const rb_call_data, blockiseq: IseqPtr, args: Vec<InsnId>, state: InsnId },
587587
InvokeSuper { self_val: InsnId, cd: *const rb_call_data, blockiseq: IseqPtr, args: Vec<InsnId>, state: InsnId },
588+
InvokeBlock { cd: *const rb_call_data, args: Vec<InsnId>, state: InsnId },
588589

589590
/// Optimized ISEQ call
590591
SendWithoutBlockDirect {
@@ -845,6 +846,13 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
845846
}
846847
Ok(())
847848
}
849+
Insn::InvokeBlock { args, .. } => {
850+
write!(f, "InvokeBlock")?;
851+
for arg in args {
852+
write!(f, ", {arg}")?;
853+
}
854+
Ok(())
855+
}
848856
Insn::InvokeBuiltin { bf, args, .. } => {
849857
write!(f, "InvokeBuiltin {}", unsafe { CStr::from_ptr(bf.name) }.to_str().unwrap())?;
850858
for arg in args {
@@ -1349,6 +1357,11 @@ impl Function {
13491357
args: find_vec!(args),
13501358
state,
13511359
},
1360+
&InvokeBlock { cd, ref args, state } => InvokeBlock {
1361+
cd,
1362+
args: find_vec!(args),
1363+
state,
1364+
},
13521365
&InvokeBuiltin { bf, ref args, state, return_type } => InvokeBuiltin { bf, args: find_vec!(args), state, return_type },
13531366
&ArrayDup { val, state } => ArrayDup { val: find!(val), state },
13541367
&HashDup { val, state } => HashDup { val: find!(val), state },
@@ -1463,6 +1476,7 @@ impl Function {
14631476
Insn::SendWithoutBlockDirect { .. } => types::BasicObject,
14641477
Insn::Send { .. } => types::BasicObject,
14651478
Insn::InvokeSuper { .. } => types::BasicObject,
1479+
Insn::InvokeBlock { .. } => types::BasicObject,
14661480
Insn::InvokeBuiltin { return_type, .. } => return_type.unwrap_or(types::BasicObject),
14671481
Insn::Defined { pushval, .. } => Type::from_value(*pushval).union(types::NilClass),
14681482
Insn::DefinedIvar { .. } => types::BasicObject,
@@ -2276,7 +2290,8 @@ impl Function {
22762290
worklist.extend(args);
22772291
worklist.push_back(state);
22782292
}
2279-
&Insn::InvokeBuiltin { ref args, state, .. } => {
2293+
&Insn::InvokeBuiltin { ref args, state, .. }
2294+
| &Insn::InvokeBlock { ref args, state, .. } => {
22802295
worklist.extend(args);
22812296
worklist.push_back(state)
22822297
}
@@ -3651,6 +3666,21 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
36513666
}
36523667
}
36533668
}
3669+
YARVINSN_invokeblock => {
3670+
let cd: *const rb_call_data = get_arg(pc, 0).as_ptr();
3671+
let call_info = unsafe { rb_get_call_data_ci(cd) };
3672+
if let Err(call_type) = unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) {
3673+
// Unknown call type; side-exit into the interpreter
3674+
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
3675+
fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) });
3676+
break; // End the block
3677+
}
3678+
let argc = unsafe { vm_ci_argc((*cd).ci) };
3679+
let args = state.stack_pop_n(argc as usize)?;
3680+
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
3681+
let result = fun.push_insn(block, Insn::InvokeBlock { cd, args, state: exit_id });
3682+
state.stack_push(result);
3683+
}
36543684
YARVINSN_getglobal => {
36553685
let id = ID(get_arg(pc, 0).as_u64());
36563686
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
@@ -5835,11 +5865,14 @@ mod tests {
58355865
v19:CBool = Test v16
58365866
IfFalse v19, bb1(v0, v1, v2, v3, v4, v10)
58375867
PatchPoint NoEPEscape(open)
5838-
SideExit UnhandledYARVInsn(invokeblock)
5839-
bb1(v27:BasicObject, v28:BasicObject, v29:BasicObject, v30:BasicObject, v31:BasicObject, v32:BasicObject):
5868+
v26:BasicObject = InvokeBlock, v10
5869+
v30:BasicObject = InvokeBuiltin dir_s_close, v0, v10
5870+
CheckInterrupts
5871+
Return v26
5872+
bb1(v36:BasicObject, v37:BasicObject, v38:BasicObject, v39:BasicObject, v40:BasicObject, v41:BasicObject):
58405873
PatchPoint NoEPEscape(open)
58415874
CheckInterrupts
5842-
Return v32
5875+
Return v41
58435876
");
58445877
}
58455878

@@ -6028,6 +6061,38 @@ mod tests {
60286061
Throw TAG_BREAK, v6
60296062
");
60306063
}
6064+
6065+
#[test]
6066+
fn test_invokeblock() {
6067+
eval(r#"
6068+
def test
6069+
yield
6070+
end
6071+
"#);
6072+
assert_snapshot!(hir_string("test"), @r"
6073+
fn test@<compiled>:3:
6074+
bb0(v0:BasicObject):
6075+
v5:BasicObject = InvokeBlock
6076+
CheckInterrupts
6077+
Return v5
6078+
");
6079+
}
6080+
6081+
#[test]
6082+
fn test_invokeblock_with_args() {
6083+
eval(r#"
6084+
def test(x, y)
6085+
yield x, y
6086+
end
6087+
"#);
6088+
assert_snapshot!(hir_string("test"), @r"
6089+
fn test@<compiled>:3:
6090+
bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject):
6091+
v7:BasicObject = InvokeBlock, v1, v2
6092+
CheckInterrupts
6093+
Return v7
6094+
");
6095+
}
60316096
}
60326097

60336098
#[cfg(test)]

0 commit comments

Comments
 (0)