diff options
| author | Max Bernstein <tekknolagi@gmail.com> | 2026-04-08 13:34:54 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-04-08 13:34:54 -0400 |
| commit | 6d6f927dd85c78b215f17365e3e282d7b9ab9a19 (patch) | |
| tree | 4509a1844feadf1dbddafe19a32615ed47c1031e | |
| parent | d3f7d2c7bdc27c8168b2f4bddb11065f8fa43aef (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.rs | 326 | ||||
| -rw-r--r-- | zjit/src/hir/opt_tests.rs | 4 |
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 "); |
