Skip to content

Commit 555c8b2

Browse files
committed
ZJIT: Optimize variadic cfunc Send calls into CCallVariadic
1 parent ab3b79e commit 555c8b2

4 files changed

Lines changed: 111 additions & 33 deletions

File tree

test/ruby/test_zjit.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,21 @@ def test = [1, 2].map(&:to_s)
525525
}
526526
end
527527

528+
def test_send_variadic_with_block
529+
assert_compiles '[[1, "a"], [2, "b"], [3, "c"]]', %q{
530+
A = [1, 2, 3]
531+
B = ["a", "b", "c"]
532+
533+
def test
534+
result = []
535+
A.zip(B) { |x, y| result << [x, y] }
536+
result
537+
end
538+
539+
test; test
540+
}, call_threshold: 2
541+
end
542+
528543
def test_send_splat
529544
assert_runs '[1, 2]', %q{
530545
def test(a, b) = [a, b]

zjit/src/codegen.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,8 +431,8 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
431431
gen_send_without_block(jit, asm, *cd, &function.frame_state(*state), SendFallbackReason::CCallWithFrameTooManyArgs),
432432
Insn::CCallWithFrame { cfunc, name, args, cme, state, blockiseq, .. } =>
433433
gen_ccall_with_frame(jit, asm, *cfunc, *name, opnds!(args), *cme, *blockiseq, &function.frame_state(*state)),
434-
Insn::CCallVariadic { cfunc, recv, args, name, cme, state, return_type: _, elidable: _ } => {
435-
gen_ccall_variadic(jit, asm, *cfunc, *name, opnd!(recv), opnds!(args), *cme, &function.frame_state(*state))
434+
Insn::CCallVariadic { cfunc, recv, args, name, cme, state, blockiseq, .. } => {
435+
gen_ccall_variadic(jit, asm, *cfunc, *name, opnd!(recv), opnds!(args), *cme, *blockiseq, &function.frame_state(*state))
436436
}
437437
Insn::GetIvar { self_val, id, ic, state: _ } => gen_getivar(jit, asm, opnd!(self_val), *id, *ic),
438438
Insn::SetGlobal { id, val, state } => no_output!(gen_setglobal(jit, asm, *id, opnd!(val), &function.frame_state(*state))),
@@ -845,6 +845,7 @@ fn gen_ccall_variadic(
845845
recv: Opnd,
846846
args: Vec<Opnd>,
847847
cme: *const rb_callable_method_entry_t,
848+
blockiseq: Option<IseqPtr>,
848849
state: &FrameState,
849850
) -> lir::Opnd {
850851
gen_incr_counter(asm, Counter::variadic_cfunc_optimized_send_count);
@@ -854,12 +855,23 @@ fn gen_ccall_variadic(
854855
let stack_growth = state.stack_size();
855856
gen_stack_overflow_check(jit, asm, state, stack_growth);
856857

858+
let block_handler_specval = if let Some(block_iseq) = blockiseq {
859+
// Change cfp->block_code in the current frame. See vm_caller_setup_arg_block().
860+
// VM_CFP_TO_CAPTURED_BLOCK then turns &cfp->self into a block handler.
861+
// rb_captured_block->code.iseq aliases with cfp->block_code.
862+
asm.store(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_BLOCK_CODE), VALUE::from(block_iseq).into());
863+
let cfp_self_addr = asm.lea(Opnd::mem(64, CFP, RUBY_OFFSET_CFP_SELF));
864+
asm.or(cfp_self_addr, Opnd::Imm(1))
865+
} else {
866+
VM_BLOCK_HANDLER_NONE.into()
867+
};
868+
857869
gen_push_frame(asm, args.len(), state, ControlFrame {
858870
recv,
859871
iseq: None,
860872
cme,
861873
frame_type: VM_FRAME_MAGIC_CFUNC | VM_FRAME_FLAG_CFRAME | VM_ENV_FLAG_LOCAL,
862-
specval: VM_BLOCK_HANDLER_NONE.into(),
874+
specval: block_handler_specval,
863875
pc: PC_POISON,
864876
});
865877

zjit/src/hir.rs

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,7 @@ pub enum Insn {
804804
state: InsnId,
805805
return_type: Type,
806806
elidable: bool,
807+
blockiseq: Option<IseqPtr>,
807808
},
808809

809810
/// Un-optimized fallback implementation (dynamic dispatch) for send-ish instructions
@@ -1920,8 +1921,8 @@ impl Function {
19201921
elidable,
19211922
blockiseq,
19221923
},
1923-
&CCallVariadic { cfunc, recv, ref args, cme, name, state, return_type, elidable } => CCallVariadic {
1924-
cfunc, recv: find!(recv), args: find_vec!(args), cme, name, state, return_type, elidable
1924+
&CCallVariadic { cfunc, recv, ref args, cme, name, state, return_type, elidable, blockiseq } => CCallVariadic {
1925+
cfunc, recv: find!(recv), args: find_vec!(args), cme, name, state, return_type, elidable, blockiseq
19251926
},
19261927
&Defined { op_type, obj, pushval, v, state } => Defined { op_type, obj, pushval, v: find!(v), state: find!(state) },
19271928
&DefinedIvar { self_val, pushval, id, state } => DefinedIvar { self_val: find!(self_val), pushval, id, state },
@@ -2975,9 +2976,23 @@ impl Function {
29752976
return Err(());
29762977
}
29772978

2978-
// Find the `argc` (arity) of the C method, which describes the parameters it expects
2979+
let ci_flags = unsafe { vm_ci_flag(call_info) };
2980+
2981+
// When seeing &block argument, fall back to dynamic dispatch for now
2982+
// TODO: Support block forwarding
2983+
if unspecializable_call_type(ci_flags) {
2984+
fun.count_complex_call_features(block, ci_flags);
2985+
fun.set_dynamic_send_reason(send_insn_id, ComplexArgPass);
2986+
return Err(());
2987+
}
2988+
2989+
let blockiseq = if blockiseq.is_null() { None } else { Some(blockiseq) };
2990+
29792991
let cfunc = unsafe { get_cme_def_body_cfunc(cme) };
2992+
// Find the `argc` (arity) of the C method, which describes the parameters it expects
29802993
let cfunc_argc = unsafe { get_mct_argc(cfunc) };
2994+
let cfunc_ptr = unsafe { get_mct_func(cfunc) }.cast();
2995+
29812996
match cfunc_argc {
29822997
0.. => {
29832998
// (self, arg0, arg1, ..., argc) form
@@ -2987,16 +3002,6 @@ impl Function {
29873002
return Err(());
29883003
}
29893004

2990-
let ci_flags = unsafe { vm_ci_flag(call_info) };
2991-
2992-
// When seeing &block argument, fall back to dynamic dispatch for now
2993-
// TODO: Support block forwarding
2994-
if unspecializable_call_type(ci_flags) {
2995-
fun.count_complex_call_features(block, ci_flags);
2996-
fun.set_dynamic_send_reason(send_insn_id, ComplexArgPass);
2997-
return Err(());
2998-
}
2999-
30003005
// Commit to the replacement. Put PatchPoint.
30013006
fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, cme, state);
30023007
if recv_class.instance_can_have_singleton_class() {
@@ -3009,17 +3014,14 @@ impl Function {
30093014
fun.insn_types[recv.0] = fun.infer_type(recv);
30103015
}
30113016

3012-
let blockiseq = if blockiseq.is_null() { None } else { Some(blockiseq) };
3013-
30143017
// Emit a call
3015-
let cfunc = unsafe { get_mct_func(cfunc) }.cast();
30163018
let mut cfunc_args = vec![recv];
30173019
cfunc_args.append(&mut args);
30183020

30193021
let name = rust_str_to_id(&qualified_method_name(unsafe { (*cme).owner }, unsafe { (*cme).called_id }));
30203022
let ccall = fun.push_insn(block, Insn::CCallWithFrame {
30213023
cd,
3022-
cfunc,
3024+
cfunc: cfunc_ptr,
30233025
args: cfunc_args,
30243026
cme,
30253027
name,
@@ -3033,9 +3035,37 @@ impl Function {
30333035
}
30343036
// Variadic method
30353037
-1 => {
3038+
// The method gets a pointer to the first argument
30363039
// func(int argc, VALUE *argv, VALUE recv)
3037-
fun.set_dynamic_send_reason(send_insn_id, SendCfuncVariadic);
3038-
Err(())
3040+
fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, cme, state);
3041+
3042+
if recv_class.instance_can_have_singleton_class() {
3043+
fun.push_insn(block, Insn::PatchPoint { invariant: Invariant::NoSingletonClass { klass: recv_class }, state });
3044+
}
3045+
if let Some(profiled_type) = profiled_type {
3046+
// Guard receiver class
3047+
recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state });
3048+
fun.insn_types[recv.0] = fun.infer_type(recv);
3049+
}
3050+
3051+
if get_option!(stats) {
3052+
count_not_inlined_cfunc(fun, block, cme);
3053+
}
3054+
3055+
let ccall = fun.push_insn(block, Insn::CCallVariadic {
3056+
cfunc: cfunc_ptr,
3057+
recv,
3058+
args,
3059+
cme,
3060+
name: method_id,
3061+
state,
3062+
return_type: types::BasicObject,
3063+
elidable: false,
3064+
blockiseq
3065+
});
3066+
3067+
fun.make_equal_to(send_insn_id, ccall);
3068+
Ok(())
30393069
}
30403070
-2 => {
30413071
// (self, args_ruby_array)
@@ -3238,6 +3268,7 @@ impl Function {
32383268
state,
32393269
return_type,
32403270
elidable,
3271+
blockiseq: None,
32413272
});
32423273

32433274
fun.make_equal_to(send_insn_id, ccall);

zjit/src/hir/opt_tests.rs

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5294,26 +5294,46 @@ mod hir_opt_tests {
52945294
}
52955295

52965296
#[test]
5297-
fn test_do_not_optimize_send_variadic_with_block() {
5297+
fn test_optimize_send_variadic_with_block() {
52985298
eval(r#"
5299-
def test = [1, 2, 3].index { |x| x == 2 }
5299+
A = [1, 2, 3]
5300+
B = ["a", "b", "c"]
5301+
5302+
def test
5303+
result = []
5304+
A.zip(B) { |x, y| result << [x, y] }
5305+
result
5306+
end
5307+
53005308
test; test
53015309
"#);
53025310
assert_snapshot!(hir_string("test"), @r"
5303-
fn test@<compiled>:2:
5311+
fn test@<compiled>:6:
53045312
bb0():
53055313
EntryPoint interpreter
53065314
v1:BasicObject = LoadSelf
5307-
Jump bb2(v1)
5308-
bb1(v4:BasicObject):
5315+
v2:NilClass = Const Value(nil)
5316+
Jump bb2(v1, v2)
5317+
bb1(v5:BasicObject):
53095318
EntryPoint JIT(0)
5310-
Jump bb2(v4)
5311-
bb2(v6:BasicObject):
5312-
v10:ArrayExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
5313-
v11:ArrayExact = ArrayDup v10
5314-
v13:BasicObject = Send v11, 0x1008, :index
5319+
v6:NilClass = Const Value(nil)
5320+
Jump bb2(v5, v6)
5321+
bb2(v8:BasicObject, v9:NilClass):
5322+
v13:ArrayExact = NewArray
5323+
SetLocal l0, EP@3, v13
5324+
PatchPoint SingleRactorMode
5325+
PatchPoint StableConstantNames(0x1000, A)
5326+
v36:ArrayExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
5327+
PatchPoint SingleRactorMode
5328+
PatchPoint StableConstantNames(0x1010, B)
5329+
v39:ArrayExact[VALUE(0x1018)] = Const Value(VALUE(0x1018))
5330+
PatchPoint MethodRedefined(Array@0x1020, zip@0x1028, cme:0x1030)
5331+
PatchPoint NoSingletonClass(Array@0x1020)
5332+
v43:BasicObject = CCallVariadic zip@0x1058, v36, v39
5333+
v25:BasicObject = GetLocal l0, EP@3
5334+
v29:BasicObject = GetLocal l0, EP@3
53155335
CheckInterrupts
5316-
Return v13
5336+
Return v29
53175337
");
53185338
}
53195339

0 commit comments

Comments
 (0)