summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Bernstein <tekknolagi@gmail.com>2026-04-08 13:34:54 -0400
committerGitHub <noreply@github.com>2026-04-08 13:34:54 -0400
commit6d6f927dd85c78b215f17365e3e282d7b9ab9a19 (patch)
tree4509a1844feadf1dbddafe19a32615ed47c1031e
parentd3f7d2c7bdc27c8168b2f4bddb11065f8fa43aef (diff)
ZJIT: Merge reduce_send_without_block_to_ccall and reduce_send_to_ccall (#16675)
Thread through special handling of the potential block argument.
-rw-r--r--zjit/src/hir.rs326
-rw-r--r--zjit/src/hir/opt_tests.rs4
2 files changed, 81 insertions, 249 deletions
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index 02b99a6d78..9a09e8789c 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -4799,14 +4799,28 @@ impl Function {
}
let blockiseq = match send_block {
- None | Some(BlockHandler::BlockArg) => unreachable!("went to reduce_send_without_block_to_ccall"),
+ Some(BlockHandler::BlockArg) => unreachable!("unsupported &block should have been filtered out"),
Some(BlockHandler::BlockIseq(blockiseq)) => Some(blockiseq),
+ None => None,
};
let cfunc = unsafe { get_cme_def_body_cfunc(cme) };
// Find the `argc` (arity) of the C method, which describes the parameters it expects
let cfunc_argc = unsafe { get_mct_argc(cfunc) };
let cfunc_ptr = unsafe { get_mct_func(cfunc) }.cast();
+ let name = unsafe { (*cme).called_id };
+
+ // Look up annotations
+ let props = ZJITState::get_method_annotations().get_cfunc_properties(cme);
+ if props.is_none() && get_option!(stats) {
+ fun.count_not_annotated_cfunc(block, cme);
+ }
+ let props = props.unwrap_or_default();
+ let return_type = props.return_type;
+ let elidable = match blockiseq {
+ Some(_) => false, // Don't consider cfuncs with block arguments as elidable for now
+ None => props.elidable,
+ };
match cfunc_argc {
0.. => {
@@ -4832,20 +4846,48 @@ impl Function {
fun.insn_types[recv.0] = fun.infer_type(recv);
}
- // Emit a call
- let cfunc = unsafe { get_mct_func(cfunc) }.cast();
+ // Try inlining the cfunc into HIR. Only inline if we don't have a block argument
+ if blockiseq.is_none() {
+ let tmp_block = fun.new_block(u32::MAX);
+ if let Some(replacement) = (props.inline)(fun, tmp_block, recv, &args, state) {
+ // Copy contents of tmp_block to block
+ assert_ne!(block, tmp_block);
+ let insns = std::mem::take(&mut fun.blocks[tmp_block.0].insns);
+ fun.blocks[block.0].insns.extend(insns);
+ fun.count(block, Counter::inline_cfunc_optimized_send_count);
+ fun.make_equal_to(send_insn_id, replacement);
+ if fun.type_of(replacement).bit_equal(types::Any) {
+ // Not set yet; infer type
+ fun.insn_types[replacement.0] = fun.infer_type(replacement);
+ }
+ fun.remove_block(tmp_block);
+ return Ok(());
+ }
+
+ // Only allow leaf calls if we don't have a block argument
+ if props.leaf && props.no_gc {
+ fun.count(block, Counter::inline_cfunc_optimized_send_count);
+ let owner = unsafe { (*cme).owner };
+ let ccall = fun.push_insn(block, Insn::CCall { cfunc: cfunc_ptr, recv, args, name, owner, return_type, elidable });
+ fun.make_equal_to(send_insn_id, ccall);
+ return Ok(());
+ }
+ }
- let name = unsafe { (*cme).called_id };
+ // Emit a call
+ if get_option!(stats) {
+ fun.count_not_inlined_cfunc(block, cme);
+ }
let ccall = fun.push_insn(block, Insn::CCallWithFrame {
cd,
- cfunc,
+ cfunc: cfunc_ptr,
recv,
args,
cme,
name,
state,
- return_type: types::BasicObject,
- elidable: false,
+ return_type,
+ elidable,
block: blockiseq.map(BlockHandler::BlockIseq),
});
fun.make_equal_to(send_insn_id, ccall);
@@ -4870,215 +4912,8 @@ impl Function {
fun.insn_types[recv.0] = fun.infer_type(recv);
}
- if get_option!(stats) {
- fun.count_not_inlined_cfunc(block, cme);
- }
-
- let ccall = fun.push_insn(block, Insn::CCallVariadic {
- cfunc: cfunc_ptr,
- recv,
- args,
- cme,
- name: method_id,
- state,
- return_type: types::BasicObject,
- elidable: false,
- block: blockiseq.map(BlockHandler::BlockIseq),
- });
-
- fun.make_equal_to(send_insn_id, ccall);
- Ok(())
- }
- -2 => {
- // (self, args_ruby_array)
- fun.set_dynamic_send_reason(send_insn_id, SendCfuncArrayVariadic);
- Err(())
- }
- _ => unreachable!("unknown cfunc kind: argc={argc}")
- }
- }
-
- // Try to reduce a Send insn with blockiseq: None to a CCall/CCallWithFrame
- fn reduce_send_without_block_to_ccall(
- fun: &mut Function,
- block: BlockId,
- self_type: Type,
- send: Insn,
- send_insn_id: InsnId,
- ) -> Result<(), ()> {
- let Insn::Send { mut recv, cd, args, state, .. } = send else {
- return Err(());
- };
-
- let call_info = unsafe { (*cd).ci };
- let argc = unsafe { vm_ci_argc(call_info) };
- let method_id = unsafe { rb_vm_ci_mid(call_info) };
-
- // If we have info about the class of the receiver
- let iseq_insn_idx = fun.frame_state(state).insn_idx;
- let (recv_class, profiled_type) = match fun.resolve_receiver_type(recv, self_type, iseq_insn_idx) {
- ReceiverTypeResolution::StaticallyKnown { class } => (class, None),
- ReceiverTypeResolution::Monomorphic { profiled_type }
- | ReceiverTypeResolution::SkewedPolymorphic { profiled_type } => (profiled_type.class(), Some(profiled_type)),
- ReceiverTypeResolution::SkewedMegamorphic { .. } | ReceiverTypeResolution::Polymorphic | ReceiverTypeResolution::Megamorphic | ReceiverTypeResolution::NoProfile => return Err(()),
- };
-
- // Do method lookup
- let mut cme: *const rb_callable_method_entry_struct = unsafe { rb_callable_method_entry(recv_class, method_id) };
- if cme.is_null() {
- fun.set_dynamic_send_reason(send_insn_id, SendWithoutBlockNotOptimizedMethodType(MethodType::Null));
- return Err(());
- }
-
- // Filter for C methods
- let mut def_type = unsafe { get_cme_def_type(cme) };
- while def_type == VM_METHOD_TYPE_ALIAS {
- cme = unsafe { rb_aliased_callable_method_entry(cme) };
- def_type = unsafe { get_cme_def_type(cme) };
- }
- if def_type != VM_METHOD_TYPE_CFUNC {
- return Err(());
- }
-
- let ci_flags = unsafe { vm_ci_flag(call_info) };
- let visibility = unsafe { METHOD_ENTRY_VISI(cme) };
- match (visibility, ci_flags & VM_CALL_FCALL != 0) {
- (METHOD_VISI_PUBLIC, _) => {}
- (METHOD_VISI_PRIVATE, true) => {}
- (METHOD_VISI_PROTECTED, true) => {}
- _ => {
- fun.set_dynamic_send_reason(send_insn_id, SendWithoutBlockNotOptimizedNeedPermission);
- return Err(());
- }
- }
-
- // Find the `argc` (arity) of the C method, which describes the parameters it expects
- let cfunc = unsafe { get_cme_def_body_cfunc(cme) };
- let cfunc_argc = unsafe { get_mct_argc(cfunc) };
- match cfunc_argc {
- 0.. => {
- // (self, arg0, arg1, ..., argc) form
- //
- // Bail on argc mismatch
- if argc != cfunc_argc as u32 {
- return Err(());
- }
-
- // Filter for simple call sites (i.e. no splats etc.)
- if ci_flags & VM_CALL_ARGS_SIMPLE == 0 {
- // Only count features NOT already counted in type_specialize.
- if !unspecializable_call_type(ci_flags) {
- fun.count_complex_call_features(block, ci_flags);
- }
- fun.set_dynamic_send_reason(send_insn_id, ComplexArgPass);
- return Err(());
- }
-
- // Check singleton class assumption first, before emitting other patchpoints
- if !fun.assume_no_singleton_classes(block, recv_class, state) {
- fun.set_dynamic_send_reason(send_insn_id, SingletonClassSeen);
- return Err(());
- }
-
- // Commit to the replacement. Put PatchPoint.
- fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, cme, state);
-
- let props = ZJITState::get_method_annotations().get_cfunc_properties(cme);
- if props.is_none() && get_option!(stats) {
- fun.count_not_annotated_cfunc(block, cme);
- }
- let props = props.unwrap_or_default();
-
- if let Some(profiled_type) = profiled_type {
- // Guard receiver class
- recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state });
- fun.insn_types[recv.0] = fun.infer_type(recv);
- }
-
- // Try inlining the cfunc into HIR
- let tmp_block = fun.new_block(u32::MAX);
- if let Some(replacement) = (props.inline)(fun, tmp_block, recv, &args, state) {
- // Copy contents of tmp_block to block
- assert_ne!(block, tmp_block);
- let insns = std::mem::take(&mut fun.blocks[tmp_block.0].insns);
- fun.blocks[block.0].insns.extend(insns);
- fun.count(block, Counter::inline_cfunc_optimized_send_count);
- fun.make_equal_to(send_insn_id, replacement);
- if fun.type_of(replacement).bit_equal(types::Any) {
- // Not set yet; infer type
- fun.insn_types[replacement.0] = fun.infer_type(replacement);
- }
- fun.remove_block(tmp_block);
- return Ok(());
- }
-
- // No inlining; emit a call
- let cfunc = unsafe { get_mct_func(cfunc) }.cast();
- let name = unsafe { (*cme).called_id };
- let owner = unsafe { (*cme).owner };
- let return_type = props.return_type;
- let elidable = props.elidable;
- // Filter for a leaf and GC free function
- if props.leaf && props.no_gc {
- fun.count(block, Counter::inline_cfunc_optimized_send_count);
- let ccall = fun.push_insn(block, Insn::CCall { cfunc, recv, args, name, owner, return_type, elidable });
- fun.make_equal_to(send_insn_id, ccall);
- } else {
- if get_option!(stats) {
- fun.count_not_inlined_cfunc(block, cme);
- }
- let ccall = fun.push_insn(block, Insn::CCallWithFrame {
- cd,
- cfunc,
- recv,
- args,
- cme,
- name,
- state,
- return_type,
- elidable,
- block: None,
- });
- fun.make_equal_to(send_insn_id, ccall);
- }
-
- return Ok(());
- }
- // Variadic method
- -1 => {
- // The method gets a pointer to the first argument
- // func(int argc, VALUE *argv, VALUE recv)
- let ci_flags = unsafe { vm_ci_flag(call_info) };
- if ci_flags & VM_CALL_ARGS_SIMPLE == 0 {
- // Only count features NOT already counted in type_specialize.
- if !unspecializable_call_type(ci_flags) {
- fun.count_complex_call_features(block, ci_flags);
- }
- fun.set_dynamic_send_reason(send_insn_id, ComplexArgPass);
- return Err(());
- } else {
- // Check singleton class assumption first, before emitting other patchpoints
- if !fun.assume_no_singleton_classes(block, recv_class, state) {
- fun.set_dynamic_send_reason(send_insn_id, SingletonClassSeen);
- return Err(());
- }
-
- fun.gen_patch_points_for_optimized_ccall(block, recv_class, method_id, cme, state);
-
- if let Some(profiled_type) = profiled_type {
- // Guard receiver class
- recv = fun.push_insn(block, Insn::GuardType { val: recv, guard_type: Type::from_profiled_type(profiled_type), state });
- fun.insn_types[recv.0] = fun.infer_type(recv);
- }
-
- let cfunc = unsafe { get_mct_func(cfunc) }.cast();
- let props = ZJITState::get_method_annotations().get_cfunc_properties(cme);
- if props.is_none() && get_option!(stats) {
- fun.count_not_annotated_cfunc(block, cme);
- }
- let props = props.unwrap_or_default();
-
- // Try inlining the cfunc into HIR
+ // Try inlining the cfunc into HIR. Only inline if we don't have a block argument
+ if blockiseq.is_none() {
let tmp_block = fun.new_block(u32::MAX);
if let Some(replacement) = (props.inline)(fun, tmp_block, recv, &args, state) {
// Copy contents of tmp_block to block
@@ -5095,40 +4930,43 @@ impl Function {
return Ok(());
}
- // No inlining; emit a call
- if get_option!(stats) {
- fun.count_not_inlined_cfunc(block, cme);
+ // Only allow leaf calls if we don't have a block argument
+ if props.leaf && props.no_gc {
+ fun.count(block, Counter::inline_cfunc_optimized_send_count);
+ let owner = unsafe { (*cme).owner };
+ let ccall = fun.push_insn(block, Insn::CCall { cfunc: cfunc_ptr, recv, args, name, owner, return_type, elidable });
+ fun.make_equal_to(send_insn_id, ccall);
+ return Ok(());
}
- let return_type = props.return_type;
- let elidable = props.elidable;
- let name = unsafe { (*cme).called_id };
- let ccall = fun.push_insn(block, Insn::CCallVariadic {
- cfunc,
- recv,
- args,
- cme,
- name,
- state,
- return_type,
- elidable,
- block: None,
- });
+ }
- fun.make_equal_to(send_insn_id, ccall);
- return Ok(())
+ // No inlining; emit a call
+ if get_option!(stats) {
+ fun.count_not_inlined_cfunc(block, cme);
}
- // Fall through for complex cases (splat, kwargs, etc.)
+ let ccall = fun.push_insn(block, Insn::CCallVariadic {
+ cfunc: cfunc_ptr,
+ recv,
+ args,
+ cme,
+ name: method_id,
+ state,
+ return_type,
+ elidable,
+ block: blockiseq.map(BlockHandler::BlockIseq),
+ });
+
+ fun.make_equal_to(send_insn_id, ccall);
+ Ok(())
}
-2 => {
- // (self, args_ruby_array) parameter form
- // Falling through for now
- fun.set_dynamic_send_reason(send_insn_id, SendWithoutBlockCfuncArrayVariadic);
+ // (self, args_ruby_array)
+ fun.set_dynamic_send_reason(send_insn_id, SendCfuncArrayVariadic);
+ Err(())
}
_ => unreachable!("unknown cfunc kind: argc={argc}")
}
-
- Err(())
}
for block in self.rpo() {
@@ -5137,12 +4975,6 @@ impl Function {
for insn_id in old_insns {
let send = self.find(insn_id);
match send {
- send @ Insn::Send { recv, block: None, .. } => {
- let recv_type = self.type_of(recv);
- if reduce_send_without_block_to_ccall(self, block, recv_type, send, insn_id).is_ok() {
- continue;
- }
- }
send @ Insn::Send { recv, .. } => {
let recv_type = self.type_of(recv);
if reduce_send_to_ccall(self, block, recv_type, send, insn_id).is_ok() {
diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs
index 43e35352b2..39357c8683 100644
--- a/zjit/src/hir/opt_tests.rs
+++ b/zjit/src/hir/opt_tests.rs
@@ -1512,7 +1512,7 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
- v11:BasicObject = Send v6, :foo # SendFallbackReason: SendWithoutBlock: unsupported method type Null
+ v11:BasicObject = Send v6, :foo # SendFallbackReason: Send: unsupported method type Null
CheckInterrupts
Return v11
");
@@ -13911,7 +13911,7 @@ mod hir_opt_tests {
PatchPoint SingleRactorMode
PatchPoint StableConstantNames(0x1000, Obj)
v21:BasicObjectExact[VALUE(0x1008)] = Const Value(VALUE(0x1008))
- v12:BasicObject = Send v21, :initialize # SendFallbackReason: SendWithoutBlock: method private or protected and no FCALL
+ v12:BasicObject = Send v21, :initialize # SendFallbackReason: Send: method private or protected and no FCALL
CheckInterrupts
Return v12
");