@@ -531,6 +531,7 @@ pub enum SideExitReason {
531531 UnhandledYARVInsn(u32),
532532 UnhandledCallType(CallType),
533533 UnhandledBlockArg,
534+ BlockArgNotNil,
534535 TooManyKeywordParameters,
535536 TooManyArgsForLir,
536537 FixnumAddOverflow,
@@ -694,6 +695,8 @@ pub enum SendFallbackReason {
694695 SendCfuncArrayVariadic,
695696 SendNotOptimizedMethodType(MethodType),
696697 SendNotOptimizedNeedPermission,
698+ /// The block argument is not nil, so we can't optimize to SendWithoutBlockDirect
699+ SendBlockArgNotNil,
697700 CCallWithFrameTooManyArgs,
698701 ObjToStringNotString,
699702 TooManyArgsForLir,
@@ -768,6 +771,7 @@ impl Display for SendFallbackReason {
768771 SendCfuncVariadic => write!(f, "Send: C function is variadic"),
769772 SendCfuncArrayVariadic => write!(f, "Send: C function expects array variadic"),
770773 SendNotOptimizedMethodType(method_type) => write!(f, "Send: unsupported method type {:?}", method_type),
774+ SendBlockArgNotNil => write!(f, "Send: block argument is not nil"),
771775 CCallWithFrameTooManyArgs => write!(f, "CCallWithFrame: too many arguments"),
772776 ObjToStringNotString => write!(f, "ObjToString: result is not a string"),
773777 TooManyArgsForLir => write!(f, "Too many arguments for LIR"),
@@ -2479,7 +2483,7 @@ pub enum ValidationError {
24792483}
24802484
24812485/// Check if we can do a direct send to the given iseq with the given args.
2482- fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq_t, ci: *const rb_callinfo, send_insn: InsnId, args: &[InsnId], has_block: bool) -> bool {
2486+ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq_t, ci: *const rb_callinfo, send_insn: InsnId, args: &[InsnId], has_block: bool, caller_passes_block_arg: bool ) -> bool {
24832487 let mut can_send = true;
24842488 let mut count_failure = |counter| {
24852489 can_send = false;
@@ -2488,8 +2492,6 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq
24882492 let params = unsafe { iseq.params() };
24892493
24902494 let callee_has_block_param = 0 != params.flags.has_block();
2491- let caller_passes_block_arg = (unsafe { rb_vm_ci_flag(ci) } & VM_CALL_ARGS_BLOCKARG) != 0;
2492-
24932495 use Counter::*;
24942496 if 0 != params.flags.has_rest() { count_failure(complex_arg_pass_param_rest) }
24952497 if 0 != params.flags.forwardable() { count_failure(complex_arg_pass_param_forwardable) }
@@ -3703,14 +3705,17 @@ impl Function {
37033705 /// Try trivially inlining the method. If we can't, emit a SendDirect instruction instead and
37043706 /// leave it to the general-purpose inliner to handle.
37053707 fn try_inline_send_direct(&mut self, block: BlockId, insn: Insn) -> InsnId {
3706- let Insn::SendDirect { recv, iseq, cd, ref args, state, .. } = insn else {
3708+ let Insn::SendDirect { recv, iseq, cd, ref args, state, block: send_block, .. } = insn else {
37073709 panic!("try_inline_send_direct called with non-SendDirect instruction");
37083710 };
37093711 // The trivial inliner runs first to handle simple cases (constant returns,
37103712 // parameter returns, etc.) without frame push/pop overhead. The general
37113713 // inliner then handles more complex methods that require full inlining.
37123714 let call_info = unsafe { (*cd).ci };
3713- let ci_flags = unsafe { vm_ci_flag(call_info) };
3715+ let mut ci_flags = unsafe { vm_ci_flag(call_info) };
3716+ if send_block.is_none() {
3717+ ci_flags &= !VM_CALL_ARGS_BLOCKARG;
3718+ }
37143719 // .send call is not currently supported for builtins
37153720 if ci_flags & VM_CALL_OPT_SEND != 0 {
37163721 return self.push_insn(block, insn);
@@ -3822,9 +3827,32 @@ impl Function {
38223827 def_type = unsafe { get_cme_def_type(cme) };
38233828 }
38243829
3830+ // Check if we can optimize `foo(&block)` where block is nil to a send without block
3831+ let mut send_block = send_block;
3832+ let mut args = args;
3833+ let mut stripped_nil_block = false;
3834+ if send_block == Some(BlockHandler::BlockArg) && def_type == VM_METHOD_TYPE_ISEQ {
3835+ // The block arg is the last element in args
3836+ if let Some(&block_arg) = args.last() {
3837+ let block_arg_type = self.type_of(block_arg);
3838+ if block_arg_type.is_subtype(types::NilClass) {
3839+ // Block arg is known to be nil - strip it and treat as no block
3840+ args = args[..args.len() - 1].to_vec();
3841+ send_block = None;
3842+ stripped_nil_block = true;
3843+ } else {
3844+ // Can't prove block arg is nil
3845+ self.set_dynamic_send_reason(insn_id, SendBlockArgNotNil);
3846+ self.push_insn_id(block, insn_id); continue;
3847+ }
3848+ }
3849+ }
3850+
38253851 // If the call site info indicates that the `Function` has overly complex arguments, then do not optimize into a `SendDirect`.
38263852 // Optimized methods(`VM_METHOD_TYPE_OPTIMIZED`) handle their own argument constraints (e.g., kw_splat for Proc call).
3827- if def_type != VM_METHOD_TYPE_OPTIMIZED && unspecializable_call_type(flags) {
3853+ // Mask out ARGS_BLOCKARG only if we've already handled the nil block arg case above.
3854+ let flags_for_check = if stripped_nil_block { flags & !VM_CALL_ARGS_BLOCKARG } else { flags };
3855+ if def_type != VM_METHOD_TYPE_OPTIMIZED && unspecializable_call_type(flags_for_check) {
38283856 self.count_complex_call_features(block, flags);
38293857 self.set_dynamic_send_reason(insn_id, ComplexArgPass);
38303858 self.push_insn_id(block, insn_id); continue;
@@ -3835,7 +3863,7 @@ impl Function {
38353863 // Only specialize positional-positional calls
38363864 // TODO(max): Handle other kinds of parameter passing
38373865 let iseq = unsafe { get_def_iseq_ptr((*cme).def) };
3838- if !can_direct_send(self, block, iseq, ci, insn_id, args.as_slice(), has_block ) {
3866+ if !can_direct_send(self, block, iseq, ci, insn_id, args.as_slice(), send_block.is_some(), send_block == Some(BlockHandler::BlockArg) ) {
38393867 self.push_insn_id(block, insn_id); continue;
38403868 }
38413869
@@ -3874,7 +3902,7 @@ impl Function {
38743902 let capture = unsafe { proc_block.as_.captured.as_ref() };
38753903 let iseq = unsafe { *capture.code.iseq.as_ref() };
38763904
3877- if !can_direct_send(self, block, iseq, ci, insn_id, args.as_slice(), has_block) {
3905+ if !can_direct_send(self, block, iseq, ci, insn_id, args.as_slice(), has_block, false ) {
38783906 self.push_insn_id(block, insn_id); continue;
38793907 }
38803908
@@ -4216,7 +4244,7 @@ impl Function {
42164244 // If not, we can't do direct dispatch.
42174245 let super_iseq = unsafe { get_def_iseq_ptr((*super_cme).def) };
42184246 // TODO: pass Option<blockiseq> to can_direct_send when we start specializing `super { ... }`.
4219- if !can_direct_send(self, block, super_iseq, ci, insn_id, args.as_slice(), false) {
4247+ if !can_direct_send(self, block, super_iseq, ci, insn_id, args.as_slice(), false, false ) {
42204248 self.push_insn_id(block, insn_id);
42214249 self.set_dynamic_send_reason(insn_id, SuperTargetComplexArgsPass);
42224250 continue;
0 commit comments