Skip to content

Commit 39d8df2

Browse files
committed
WIP
1 parent 928fea3 commit 39d8df2

4 files changed

Lines changed: 132 additions & 1 deletion

File tree

test/ruby/test_zjit.rb

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2569,6 +2569,46 @@ def test(x)
25692569
}, insns: [:opt_case_dispatch]
25702570
end
25712571

2572+
def test_invokeblock
2573+
assert_compiles '42', %q{
2574+
def test
2575+
yield
2576+
end
2577+
test { 42 }
2578+
}, insns: [:invokeblock]
2579+
end
2580+
2581+
def test_invokeblock_with_args
2582+
assert_compiles '3', %q{
2583+
def test(x, y)
2584+
yield x, y
2585+
end
2586+
test(1, 2) { |a, b| a + b }
2587+
}, insns: [:invokeblock]
2588+
end
2589+
2590+
def test_invokeblock_no_block_given
2591+
assert_compiles ':error', %q{
2592+
def test
2593+
yield rescue :error
2594+
end
2595+
test
2596+
}, insns: [:invokeblock]
2597+
end
2598+
2599+
def test_invokeblock_multiple_yields
2600+
assert_compiles "[1, 2, 3]", %q{
2601+
results = []
2602+
def test
2603+
yield 1
2604+
yield 2
2605+
yield 3
2606+
end
2607+
test { |x| results << x }
2608+
results
2609+
}, insns: [:invokeblock]
2610+
end
2611+
25722612
private
25732613

25742614
# 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::invokeblock_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: 63 additions & 1 deletion
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
}
@@ -3665,6 +3680,21 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
36653680
}
36663681
}
36673682
}
3683+
YARVINSN_invokeblock => {
3684+
let cd: *const rb_call_data = get_arg(pc, 0).as_ptr();
3685+
let call_info = unsafe { rb_get_call_data_ci(cd) };
3686+
if let Err(call_type) = unknown_call_type(unsafe { rb_vm_ci_flag(call_info) }) {
3687+
// Unknown call type; side-exit into the interpreter
3688+
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
3689+
fun.push_insn(block, Insn::SideExit { state: exit_id, reason: SideExitReason::UnhandledCallType(call_type) });
3690+
break; // End the block
3691+
}
3692+
let argc = unsafe { vm_ci_argc((*cd).ci) };
3693+
let args = state.stack_pop_n(argc as usize)?;
3694+
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
3695+
let result = fun.push_insn(block, Insn::InvokeBlock { cd, args, state: exit_id });
3696+
state.stack_push(result);
3697+
}
36683698
YARVINSN_getglobal => {
36693699
let id = ID(get_arg(pc, 0).as_u64());
36703700
let exit_id = fun.push_insn(block, Insn::Snapshot { state: exit_state });
@@ -6028,6 +6058,38 @@ mod tests {
60286058
Throw TAG_BREAK, v6
60296059
");
60306060
}
6061+
6062+
#[test]
6063+
fn test_invokeblock() {
6064+
eval(r#"
6065+
def test
6066+
yield
6067+
end
6068+
"#);
6069+
assert_snapshot!(hir_string("test"), @r"
6070+
fn test@<compiled>:3:
6071+
bb0(v0:BasicObject):
6072+
v5:BasicObject = InvokeBlock
6073+
CheckInterrupts
6074+
Return v5
6075+
");
6076+
}
6077+
6078+
#[test]
6079+
fn test_invokeblock_with_args() {
6080+
eval(r#"
6081+
def test(x, y)
6082+
yield x, y
6083+
end
6084+
"#);
6085+
assert_snapshot!(hir_string("test"), @r"
6086+
fn test@<compiled>:3:
6087+
bb0(v0:BasicObject, v1:BasicObject, v2:BasicObject):
6088+
v7:BasicObject = InvokeBlock, v1, v2
6089+
CheckInterrupts
6090+
Return v7
6091+
");
6092+
}
60316093
}
60326094

60336095
#[cfg(test)]

zjit/src/stats.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,9 @@ make_counters! {
137137

138138
// The number of times we do a dynamic dispatch from JIT code
139139
dynamic_send_count,
140+
141+
// The number of times we do invoke a block from JIT code
142+
invokeblock_count,
140143
}
141144

142145
/// Increase a counter by a specified amount

0 commit comments

Comments
 (0)