ZJIT: Split SendDirect argument planning from HIR emission (#18288)

* ZJIT: Split SendDirect argument planning from HIR emission

SendDirect argument setup mixes validation with fallback accounting and HIR
emission. This makes it difficult to evaluate argument shapes before choosing
a runtime path.

```
Before:

can_direct_send
└─ eligibility checks + fallback accounting

prepare_direct_send_args
└─ argument setup checks + HIR emission

After:

plan_send_direct_args
└─ eligibility and argument setup checks → SendDirectPlan

emit_send_direct_args
└─ SendDirectPlan → HIR
```

Build a side-effect-free plan first, then emit HIR from the validated plan.
Record fallback state only when planning fails and the original Send is kept.

* ZJIT: Avoid cloning SendDirect arguments

Move keyword and rest arguments between owned vectors while building a
SendDirectPlan. This removes recursive SendDirectArg clones during keyword
reordering and rest packing.

Co-authored-by: Max Bernstein <401167+tekknolagi@users.noreply.github.com>

* ZJIT: Refine SendDirect argument setup naming

Rename plan_send_direct_args to build_send_direct_args and
SendDirectPlan to SendDirectCall to clarify the build and emit phases.

Co-authored-by: Takashi Kokubun <3138447+k0kubun@users.noreply.github.com>

---------

Co-authored-by: Max Bernstein <401167+tekknolagi@users.noreply.github.com>
Co-authored-by: Takashi Kokubun <3138447+k0kubun@users.noreply.github.com>
This commit is contained in:
authored and GitHub committed 2026-08-13 16:24:23 -07:00
1 parent 2423f69df4
commit a9c656ebd2
5 files changed
+342 -234
Notes: git 2026-08-13 23:24:55 +00:00
Merged-By: k0kubun <takashikkbn@gmail.com>

No files matched your search

+1
View File
@@ -105,6 +105,7 @@ class << RubyVM::ZJIT
print_counters_with_prefix(prefix: 'unspecialized_super_def_type_', prompt: 'not optimized method types for super', buf:, stats:, limit: 20)
print_counters_with_prefix(prefix: 'uncategorized_fallback_yarv_insn_', prompt: 'instructions with uncategorized fallback reason', buf:, stats:, limit: 20)
print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'send fallback reasons', buf:, stats:, limit: 20)
print_counters_with_prefix(prefix: 'send_direct_fallback_context_', prompt: 'SendDirect fallback contexts', buf:, stats:, limit: 10)
print_counters_with_prefix(prefix: 'setivar_fallback_', prompt: 'setivar fallback reasons', buf:, stats:, limit: 5)
print_counters_with_prefix(prefix: 'getivar_fallback_', prompt: 'getivar fallback reasons', buf:, stats:, limit: 5)
print_counters_with_prefix(prefix: 'definedivar_fallback_', prompt: 'definedivar fallback reasons', buf:, stats:, limit: 5)
+187 -127
View File
@@ -797,8 +797,6 @@ pub enum SendFallbackReason {
SuperNotOptimizedMethodType(MethodType),
/// The `super` call is polymorpic.
SuperPolymorphic,
/// The `super` target call uses a complex argument pattern that the optimizer does not support.
SuperTargetComplexArgsPass,
/// The `invokeblock` instruction is not yet optimized in `type_specialize`.
InvokeBlockNotSpecialized,
/// The runtime block handler at a polymorphic `invokeblock` site did not match any
@@ -853,7 +851,6 @@ impl Display for SendFallbackReason {
SuperNotOptimizedMethodType(method_type) => write!(f, "super: unsupported target method type {:?}", method_type),
SuperPolymorphic => write!(f, "super: polymorphic call site"),
SuperTargetNotFound => write!(f, "super: profiled target method cannot be found"),
SuperTargetComplexArgsPass => write!(f, "super: complex argument passing to `super` target call"),
InvokeBlockNotSpecialized => write!(f, "InvokeBlock: not yet specialized"),
InvokeBlockPolymorphicMiss => write!(f, "InvokeBlock: polymorphic dispatch miss"),
SendForwardNotSpecialized => write!(f, "SendForward: not yet specialized"),
@@ -2601,13 +2598,10 @@ pub enum ValidationError {
MiscValidationError(InsnId, String),
}
/// Check if we can do a direct send to the given iseq with the given args.
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 {
let mut can_send = true;
let mut count_failure = |counter| {
can_send = false;
function.count(block, counter);
};
/// Check if we can emit SendDirect to the given ISEQ with the given arguments.
fn can_direct_send(iseq: *const rb_iseq_t, ci: *const rb_callinfo, args: &[InsnId], has_block: bool) -> Result<(), SendDirectFailure> {
let mut complex_arg_counters = vec![];
let mut count_failure = |counter| complex_arg_counters.push(counter);
let params = unsafe { iseq.params() };
let callee_has_block_param = 0 != params.flags.has_block();
@@ -2629,9 +2623,11 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq
if caller_passes_block && 0 == params.flags.use_block()
{ count_failure(complex_arg_pass_does_not_use_block) }
if !can_send {
function.set_dynamic_send_reason(send_insn, ComplexArgPass);
return false;
if !complex_arg_counters.is_empty() {
return Err(SendDirectFailure::with_counters(
ComplexArgPass,
complex_arg_counters,
));
}
let lead_num = params.lead_num;
@@ -2646,8 +2642,7 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq
let caller_positional = match args.len().checked_sub(caller_kw_count) {
Some(count) => count,
None => {
function.set_dynamic_send_reason(send_insn, ArgcParamMismatch);
return false;
return Err(SendDirectFailure::new(ArgcParamMismatch));
}
};
@@ -2659,8 +2654,7 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq
let caller_positional_i32 = match c_int::try_from(effective_positional) {
Ok(argc) => argc,
Err(_) => {
function.set_dynamic_send_reason(send_insn, ArgcParamMismatch);
return false;
return Err(SendDirectFailure::new(ArgcParamMismatch));
}
};
let min_positional = lead_num + post_num;
@@ -2670,8 +2664,7 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq
(min_positional..=min_positional + opt_num).contains(&caller_positional_i32)
};
if !positional_ok {
function.set_dynamic_send_reason(send_insn, ArgcParamMismatch);
return false
return Err(SendDirectFailure::new(ArgcParamMismatch));
}
// Plain keyword-to-positional-hash is safe to synthesize below. Keep VM
@@ -2680,9 +2673,10 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq
if keywords_as_positional_hash
&& (params.flags.accepts_no_kwarg() != 0 || params.flags.ruby2_keywords() != 0)
{
function.count(block, complex_arg_pass_keyword_to_positional_hash);
function.set_dynamic_send_reason(send_insn, ComplexArgPass);
return false;
return Err(SendDirectFailure::with_counters(
ComplexArgPass,
vec![complex_arg_pass_keyword_to_positional_hash],
));
}
// After keyword-to-positional-hash, SendDirect receives no keyword slots;
@@ -2693,8 +2687,7 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq
.map(|argc| (kw_req_num..=kw_total_num).contains(argc))
.unwrap_or(false);
if !keyword_ok {
function.set_dynamic_send_reason(send_insn, ArgcParamMismatch);
return false
return Err(SendDirectFailure::new(ArgcParamMismatch));
}
// asm.ccall() doesn't support 6+ args. Compute the final argc after keyword setup
@@ -2716,17 +2709,15 @@ fn can_direct_send(function: &mut Function, block: BlockId, iseq: *const rb_iseq
// TODO: Support passing arguments on the stack in C calls
if c_argc > C_ARG_OPNDS.len() {
function.set_dynamic_send_reason(send_insn, TooManyArgsForLir);
return false;
return Err(SendDirectFailure::new(TooManyArgsForLir));
}
// IseqCall stores the JIT entry index and argc as u16.
if u16::try_from(send_argc).is_err() {
function.set_dynamic_send_reason(send_insn, TooManyArgsForLir);
return false;
return Err(SendDirectFailure::new(TooManyArgsForLir));
}
can_send
Ok(())
}
/// Policy that controls how optimization passes generate code.
@@ -2837,6 +2828,69 @@ struct SendDirectArgs {
jit_entry_idx: u16,
}
/// One SendDirect argument before its HIR value is materialized.
enum SendDirectArg {
/// A HIR value already present in the original Send argument vector.
Existing(InsnId),
/// A Ruby value to materialize as a Const instruction on the selected path.
Constant(VALUE),
/// Explicit caller keywords to materialize as one positional Hash.
KeywordHash(Vec<SendDirectArg>),
/// Values to materialize as the Array passed to a callee rest parameter.
RestArray(Vec<SendDirectArg>),
}
/// Side-effect-free SendDirect argument setup before HIR emission.
struct SendDirectCall {
/// Argument slots in the shape expected by SendDirect.
args: Vec<SendDirectArg>,
/// Optional keyword slots omitted by the caller.
kw_bits: u32,
/// Optional positional entry selected from the caller's argument count.
jit_entry_idx: u16,
}
/// Why a SendDirect call could not be built, including feature counters that
/// should only be recorded when the caller keeps the dynamic fallback.
struct SendDirectFailure {
reason: SendFallbackReason,
counters: Vec<Counter>,
}
/// Call context in which SendDirect argument planning failed.
enum SendDirectFallbackContext {
Send,
Super,
}
impl SendDirectFailure {
fn new(reason: SendFallbackReason) -> Self {
Self { reason, counters: vec![] }
}
fn with_counters(reason: SendFallbackReason, counters: Vec<Counter>) -> Self {
Self { reason, counters }
}
fn record(
&self,
function: &mut Function,
block: BlockId,
send_insn: InsnId,
context: SendDirectFallbackContext,
) {
for &counter in &self.counters {
function.count(block, counter);
}
let context_counter = match context {
SendDirectFallbackContext::Send => Counter::send_direct_fallback_context_send,
SendDirectFallbackContext::Super => Counter::send_direct_fallback_context_super,
};
function.count(block, context_counter);
function.set_dynamic_send_reason(send_insn, self.reason);
}
}
unsafe extern "C" {
fn rb_simple_iseq_p(iseq: IseqPtr) -> bool;
}
@@ -3665,35 +3719,67 @@ impl Function {
}
}
/// Prepare arguments for a direct send, handling keyword argument reordering,
/// default synthesis, and rest parameter packing.
/// Returns the arguments to use for the SendDirect instruction,
/// or Err with the fallback reason if direct send isn't possible.
fn prepare_direct_send_args(
&mut self,
block: BlockId,
args: &[InsnId],
ci: *const rb_callinfo,
iseq: IseqPtr,
state: InsnId,
) -> Result<SendDirectArgs, SendFallbackReason> {
let (processed_args, caller_argc, kw_bits) = self.setup_keyword_arguments(block, args, ci, iseq, state)?;
let (processed_args, jit_entry_idx) = self.setup_rest_parameter(block, processed_args, iseq, state)?;
/// Validate and normalize SendDirect arguments without emitting HIR.
fn build_send_direct_args(&self, args: &[InsnId], ci: *const rb_callinfo, iseq: IseqPtr, has_block: bool) -> Result<SendDirectCall, SendDirectFailure> {
can_direct_send(iseq, ci, args, has_block)?;
let args = args.iter().copied().map(SendDirectArg::Existing).collect();
let (args, kw_bits) = Self::plan_send_direct_keyword_arguments(args, ci, iseq)
.map_err(SendDirectFailure::new)?;
let (args, jit_entry_idx) = Self::plan_send_direct_rest_parameter(args, iseq)
.map_err(SendDirectFailure::new)?;
// If args were reordered or synthesized, create a new snapshot with the updated stack
let send_state = if processed_args != args {
let new_state = self.frame_state(state).with_replaced_args(&processed_args, caller_argc);
Ok(SendDirectCall {
args,
kw_bits,
jit_entry_idx,
})
}
/// Materialize a validated SendDirect call in the selected runtime path.
fn emit_send_direct_args(&mut self, block: BlockId, call: SendDirectCall, original_args: &[InsnId], state: InsnId) -> SendDirectArgs {
let args: Vec<_> = call
.args
.into_iter()
.map(|arg| self.emit_send_direct_arg(block, arg, state))
.collect();
// If args were reordered or synthesized, create a new snapshot with the updated stack.
let send_state = if args != original_args {
let new_state = self.frame_state(state).with_replaced_args(&args, original_args.len());
self.push_insn(block, Insn::Snapshot { state: Box::new(new_state) })
} else {
state
};
Ok(SendDirectArgs {
SendDirectArgs {
state: send_state,
args: processed_args,
kw_bits,
jit_entry_idx,
})
args,
kw_bits: call.kw_bits,
jit_entry_idx: call.jit_entry_idx,
}
}
fn emit_send_direct_arg(&mut self, block: BlockId, arg: SendDirectArg, state: InsnId) -> InsnId {
match arg {
SendDirectArg::Existing(value) => value,
SendDirectArg::Constant(value) => {
self.push_insn(block, Insn::Const { val: Const::Value(value) })
}
SendDirectArg::KeywordHash(elements) => {
let elements = elements
.into_iter()
.map(|arg| self.emit_send_direct_arg(block, arg, state))
.collect();
self.push_insn(block, Insn::NewHash { elements, state })
}
SendDirectArg::RestArray(elements) => {
let elements = elements
.into_iter()
.map(|arg| self.emit_send_direct_arg(block, arg, state))
.collect();
self.push_insn(block, Insn::NewArray { elements, state })
}
}
}
/// Reorder keyword arguments to match the callee's expected order, and synthesize
@@ -3702,24 +3788,20 @@ impl Function {
/// The output always contains all of the callee's keyword arguments (required + optional),
/// so the returned vec may be larger than the input args.
///
/// Returns Ok with (processed_args, caller_argc, kw_bits) if successful, or Err with the fallback reason if not.
/// - caller_argc: number of arguments the caller actually pushed (for stack calculations)
/// Returns Ok with (processed_args, kw_bits) if successful, or Err with the fallback reason if not.
/// - kw_bits: bitmask indicating which optional keywords were NOT provided by the caller
/// (used by checkkeyword to determine if non-constant defaults need evaluation)
fn setup_keyword_arguments(
&mut self,
block: BlockId,
args: &[InsnId],
fn plan_send_direct_keyword_arguments(
args: Vec<SendDirectArg>,
ci: *const rb_callinfo,
iseq: IseqPtr,
state: InsnId,
) -> Result<(Vec<InsnId>, usize, u32), SendFallbackReason> {
) -> Result<(Vec<SendDirectArg>, u32), SendFallbackReason> {
let kwarg = unsafe { rb_vm_ci_kwarg(ci) };
let callee_keyword = unsafe { rb_get_iseq_body_param_keyword(iseq) };
if callee_keyword.is_null() {
if kwarg.is_null() {
// Neither caller nor callee have keywords - nothing to do
return Ok((args.to_vec(), args.len(), 0));
return Ok((args, 0));
}
let params = unsafe { iseq.params() };
@@ -3741,18 +3823,17 @@ impl Function {
// become one final positional Hash before regular parameter setup.
let caller_kw_count = unsafe { get_cikw_keyword_len(kwarg) } as usize;
let kw_args_start = args.len() - caller_kw_count;
let mut processed_args = args;
let keyword_values = processed_args.split_off(kw_args_start);
let mut elements = Vec::with_capacity(caller_kw_count * 2);
for i in 0..caller_kw_count {
for (i, value) in keyword_values.into_iter().enumerate() {
let keyword = unsafe { get_cikw_keywords_idx(kwarg, i as i32) };
let key = self.push_insn(block, Insn::Const { val: Const::Value(keyword) });
elements.push(key);
elements.push(args[kw_args_start + i]);
elements.push(SendDirectArg::Constant(keyword));
elements.push(value);
}
let hash = self.push_insn(block, Insn::NewHash { elements, state });
let mut processed_args = args[..kw_args_start].to_vec();
processed_args.push(hash);
return Ok((processed_args, args.len(), 0));
processed_args.push(SendDirectArg::KeywordHash(elements));
return Ok((processed_args, 0));
}
// kwarg may be null if caller passes no keywords but callee has optional keywords
@@ -3805,10 +3886,16 @@ impl Function {
}
}
// Move caller keyword values out of the positional prefix. Wrap them in
// Option so reordering can take each value without cloning it.
let mut processed_args = args;
let keyword_values = processed_args.split_off(kw_args_start);
let mut keyword_values: Vec<_> = keyword_values.into_iter().map(Some).collect();
// Reorder keyword arguments to match callee expectation.
// Track which optional keywords were not provided via kw_bits.
let mut kw_bits: u32 = 0;
let mut reordered_kw_args: Vec<InsnId> = Vec::with_capacity(callee_kw_count);
let mut reordered_kw_args = Vec::with_capacity(callee_kw_count);
for i in 0..callee_kw_count {
let expected_id = unsafe { *callee_kw_table.add(i) };
@@ -3816,7 +3903,7 @@ impl Function {
let mut found = false;
for (j, &caller_id) in caller_kw_order.iter().enumerate() {
if caller_id == expected_id {
reordered_kw_args.push(args[kw_args_start + j]);
reordered_kw_args.push(keyword_values[j].take().unwrap());
found = true;
break;
}
@@ -3838,22 +3925,17 @@ impl Function {
// Push Qnil as a placeholder; the callee's checkkeyword will detect this
// and branch to evaluate the default expression.
kw_bits |= 1 << default_idx;
let nil_insn = self.push_insn(block, Insn::Const { val: Const::Value(Qnil) });
reordered_kw_args.push(nil_insn);
reordered_kw_args.push(SendDirectArg::Constant(Qnil));
} else {
// Constant default value - use it directly
let const_insn = self.push_insn(block, Insn::Const { val: Const::Value(default_value) });
reordered_kw_args.push(const_insn);
reordered_kw_args.push(SendDirectArg::Constant(default_value));
}
}
}
// Replace the keyword arguments with the reordered ones.
// Keep track of the original caller argc for stack calculations.
let caller_argc = args.len();
let mut processed_args = args[..kw_args_start].to_vec();
processed_args.extend(reordered_kw_args);
Ok((processed_args, caller_argc, kw_bits))
Ok((processed_args, kw_bits))
}
/// Compute the positional optional entry index and pack positional arguments
@@ -3863,20 +3945,17 @@ impl Function {
/// the callee's *rest array before entering the method body.
///
/// The input args must already have keyword arguments normalized to the callee's
/// keyword table order by setup_keyword_arguments. This function only reshapes
/// keyword table order by plan_send_direct_keyword_arguments. This function only reshapes
/// the positional section before those keyword slots.
///
/// Returns Ok with (processed_args, jit_entry_idx) if successful, or Err with
/// the fallback reason if direct send isn't possible.
/// - processed_args: arguments to use for SendDirect after optional rest packing
/// - jit_entry_idx: number of positional optional parameters provided by the caller
fn setup_rest_parameter(
&mut self,
block: BlockId,
args: Vec<InsnId>,
fn plan_send_direct_rest_parameter(
args: Vec<SendDirectArg>,
iseq: IseqPtr,
state: InsnId,
) -> Result<(Vec<InsnId>, u16), SendFallbackReason> {
) -> Result<(Vec<SendDirectArg>, u16), SendFallbackReason> {
let params = unsafe { iseq.params() };
let lead_num = params.lead_num as usize;
let opt_num = params.opt_num as usize;
@@ -3905,18 +3984,11 @@ impl Function {
// [lead, filled opts, rest array, post, kw...].
let rest_start = lead_num + passed_opt_num;
let rest_end = positional_argc - post_num;
let (prefix, rest_and_suffix) = args.split_at(rest_start);
let (rest_elements, suffix) = rest_and_suffix.split_at(rest_end - rest_start);
let rest = self.push_insn(block, Insn::NewArray {
elements: rest_elements.to_vec(),
state,
});
let mut packed_args = Vec::with_capacity(prefix.len() + 1 + suffix.len());
packed_args.extend_from_slice(prefix);
packed_args.push(rest);
packed_args.extend_from_slice(suffix);
let mut packed_args = args;
let mut rest_elements = packed_args.split_off(rest_start);
let suffix = rest_elements.split_off(rest_end - rest_start);
packed_args.push(SendDirectArg::RestArray(rest_elements));
packed_args.extend(suffix);
Ok((packed_args, jit_entry_idx))
}
@@ -4429,13 +4501,8 @@ impl Function {
// Only specialize positional-positional calls
// TODO(max): Handle other kinds of parameter passing
let iseq = unsafe { get_def_iseq_ptr((*cme).def) };
if !can_direct_send(self, block, iseq, ci, insn_id, args.as_slice(), has_block) {
self.push_insn_id(block, insn_id); continue;
}
// Check if the args are compatible before emitting any assmptions
let Ok(SendDirectArgs { state: send_state, args: send_args, kw_bits, jit_entry_idx }) = self.prepare_direct_send_args(block, &args, ci, iseq, send_frame_state)
.inspect_err(|&reason| self.set_dynamic_send_reason(insn_id, reason)) else {
let Ok(call) = self.build_send_direct_args(&args, ci, iseq, has_block)
.inspect_err(|failure| failure.record(self, block, insn_id, SendDirectFallbackContext::Send)) else {
self.push_insn_id(block, insn_id); continue;
};
@@ -4454,6 +4521,8 @@ impl Function {
self.insn_types[recv.to_usize()] = self.infer_type(recv);
}
let SendDirectArgs { state: send_state, args: send_args, kw_bits, jit_entry_idx } =
self.emit_send_direct_args(block, call, &args, send_frame_state);
let replacement = self.try_inline_send_direct(block, Insn::SendDirect(Box::new(SendDirectData { recv, cd, cme, iseq, args: send_args, kw_bits, jit_entry_idx, state: send_state, block: send_block })));
self.make_equal_to(insn_id, replacement);
} else if !has_block && def_type == VM_METHOD_TYPE_BMETHOD {
@@ -4469,13 +4538,8 @@ impl Function {
let capture = unsafe { proc_block.as_.captured.as_ref() };
let iseq = unsafe { *capture.code.iseq.as_ref() };
if !can_direct_send(self, block, iseq, ci, insn_id, args.as_slice(), has_block) {
self.push_insn_id(block, insn_id); continue;
}
// Check if the args are compatible before emitting any assmptions
let Ok(SendDirectArgs { state: send_state, args: send_args, kw_bits, jit_entry_idx }) = self.prepare_direct_send_args(block, &args, ci, iseq, send_frame_state)
.inspect_err(|&reason| self.set_dynamic_send_reason(insn_id, reason)) else {
let Ok(call) = self.build_send_direct_args(&args, ci, iseq, has_block)
.inspect_err(|failure| failure.record(self, block, insn_id, SendDirectFallbackContext::Send)) else {
self.push_insn_id(block, insn_id); continue;
};
@@ -4497,6 +4561,8 @@ impl Function {
recv = self.guard_type_recompile(block, recv, Type::from_profiled_type(profiled_type), state, Recompile);
}
let SendDirectArgs { state: send_state, args: send_args, kw_bits, jit_entry_idx } =
self.emit_send_direct_args(block, call, &args, send_frame_state);
let replacement = self.try_inline_send_direct(block, Insn::SendDirect(Box::new(SendDirectData { recv, cd, cme, iseq, args: send_args, kw_bits, jit_entry_idx, state: send_state, block: None })));
self.make_equal_to(insn_id, replacement);
} else if !has_block && def_type == VM_METHOD_TYPE_IVAR && args.is_empty() {
@@ -5025,21 +5091,16 @@ impl Function {
// Check if the super method's parameters support direct send.
// If not, we can't do direct dispatch.
let super_iseq = unsafe { get_def_iseq_ptr((*super_cme).def) };
// TODO: pass Option<blockiseq> to can_direct_send when we start specializing `super { ... }`.
if !can_direct_send(self, block, super_iseq, ci, insn_id, args.as_slice(), false) {
self.push_insn_id(block, insn_id);
self.set_dynamic_send_reason(insn_id, SuperTargetComplexArgsPass);
continue;
}
// Check if the args are compatible before emitting any assmptions
let Ok(SendDirectArgs { state: send_state, args: send_args, kw_bits, jit_entry_idx }) = self.prepare_direct_send_args(block, &args, ci, super_iseq, state)
.inspect_err(|&reason| self.set_dynamic_send_reason(insn_id, reason)) else {
// TODO: pass Option<blockiseq> to build_send_direct_args when we start specializing `super { ... }`.
let Ok(call) = self.build_send_direct_args(&args, ci, super_iseq, false)
.inspect_err(|failure| failure.record(self, block, insn_id, SendDirectFallbackContext::Super)) else {
self.push_insn_id(block, insn_id); continue;
};
emit_super_call_guards(self, block, super_cme, current_cme, mid, state, frame_state_iseq);
let SendDirectArgs { state: send_state, args: send_args, kw_bits, jit_entry_idx } =
self.emit_send_direct_args(block, call, &args, state);
// Use SendDirect with the super method's CME and ISEQ.
let replacement = self.try_inline_send_direct(block, Insn::SendDirect(Box::new(SendDirectData {
recv,
@@ -5206,8 +5267,8 @@ impl Function {
// Inline callees with required, optional, post-required positional, keyword, and
// block parameters, including callees that dispatch to a passed block with `yield`.
// Double-splat (kwrest) and forwardable params stay out of the general
// inliner for now. Rest params are already normalized to a packed Array by
// prepare_direct_send_args, so the inliner can map that Array to the rest local.
// inliner for now. SendDirect argument emission normalizes rest params to a
// packed Array, so the inliner can map that Array to the rest local.
let params = unsafe { callee_iseq.params() };
if params.flags.forwardable() != 0
|| params.flags.has_kwrest() != 0
@@ -5369,9 +5430,8 @@ impl Function {
// begins when `lead_num + k + post_num + kw_num` arguments are
// passed: it runs the default-init code for the remaining
// `opt_num - k` optionals (if any) before falling through into the
// post-default body. SendDirect emission already guarantees
// `prepare_direct_send_args` records the matching `jit_entry_idx`
// when it packs the caller args, so do not recover the optional
// post-default body. SendDirect argument planning records the matching
// `jit_entry_idx` before packing the caller args, so do not recover the optional
// positional count from args.len() here.
let callee_params = unsafe { iseq.params() };
let lead_num = callee_params.lead_num as usize;
@@ -5465,7 +5525,7 @@ impl Function {
// trailing args, but their position in the local table leaves a gap
// of (opt_num - passed_opt_num) above the args' compact layout, so we
// shift the arg index down by that amount. Keyword args follow this
// same arithmetic because `prepare_direct_send_args` already
// same arithmetic because SendDirect argument planning already
// reordered them into callee table order with defaults filled in.
// * the hidden kw_bits storage local (when the callee has keywords) is
// aliased to the SendDirect's compile-time kw_bits value as a fixnum
+146 -102
View File
@@ -4627,13 +4627,13 @@ mod hir_opt_tests {
v11:Fixnum[1] = Const Value(1)
v13:Fixnum[2] = Const Value(2)
v15:Fixnum[3] = Const Value(3)
v23:ArrayExact = NewArray v11, v13, v15
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v26:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
PushInlineFrame :foo, v26 (0x1038), num_args=1
v24:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v25:ArrayExact = NewArray v11, v13, v15
PushInlineFrame :foo, v24 (0x1038), num_args=1
PatchPoint NoSingletonClass(Array@0x1060)
PatchPoint MethodRedefined(Array@0x1060, length@0x1068, cme:0x1070)
v49:CInt64 = ArrayLength v23
v49:CInt64 = ArrayLength v25
v50:Fixnum = BoxFixnum v49
CheckInterrupts
PopInlineFrame
@@ -4667,13 +4667,13 @@ mod hir_opt_tests {
v19:Fixnum[5] = Const Value(5)
v21:Fixnum[6] = Const Value(6)
v23:Fixnum[7] = Const Value(7)
v31:ArrayExact = NewArray v11, v13, v15, v17, v19, v21, v23
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v34:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
PushInlineFrame :foo, v34 (0x1038), num_args=1
v32:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v33:ArrayExact = NewArray v11, v13, v15, v17, v19, v21, v23
PushInlineFrame :foo, v32 (0x1038), num_args=1
PatchPoint NoSingletonClass(Array@0x1060)
PatchPoint MethodRedefined(Array@0x1060, length@0x1068, cme:0x1070)
v57:CInt64 = ArrayLength v31
v57:CInt64 = ArrayLength v33
v58:Fixnum = BoxFixnum v57
CheckInterrupts
PopInlineFrame
@@ -4703,13 +4703,13 @@ mod hir_opt_tests {
v11:Fixnum[1] = Const Value(1)
v13:Fixnum[2] = Const Value(2)
v15:Fixnum[3] = Const Value(3)
v23:ArrayExact = NewArray v11, v13, v15
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v26:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
PushInlineFrame :foo, v26 (0x1038), num_args=1
v24:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v25:ArrayExact = NewArray v11, v13, v15
PushInlineFrame :foo, v24 (0x1038), num_args=1
PatchPoint NoSingletonClass(Array@0x1060)
PatchPoint MethodRedefined(Array@0x1060, length@0x1068, cme:0x1070)
v55:CInt64 = ArrayLength v23
v55:CInt64 = ArrayLength v25
v56:Fixnum = BoxFixnum v55
v38:CPtr = GetEP 0
v39:CInt64 = LoadField v38, :VM_ENV_DATA_INDEX_SPECVAL@0x1098
@@ -4744,11 +4744,11 @@ mod hir_opt_tests {
v11:Fixnum[1] = Const Value(1)
v13:Fixnum[2] = Const Value(2)
v15:Fixnum[3] = Const Value(3)
v23:ArrayExact = NewArray v11, v13, v15
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v26:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v24:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v25:ArrayExact = NewArray v11, v13, v15
v57:NilClass = Const Value(nil)
PushInlineFrame :foo, v26 (0x1038), num_args=1
PushInlineFrame :foo, v24 (0x1038), num_args=1
v37:CPtr = GetEP 0
v38:CUInt64 = LoadField v37, :VM_ENV_DATA_INDEX_FLAGS@0x1060
v39:CBool = IsBlockParamModified v38
@@ -4764,7 +4764,7 @@ mod hir_opt_tests {
bb8(v35:BasicObject, v36:BasicObject):
PatchPoint NoSingletonClass(Array@0x1070)
PatchPoint MethodRedefined(Array@0x1070, length@0x1078, cme:0x1080)
v66:CInt64 = ArrayLength v23
v66:CInt64 = ArrayLength v25
v67:Fixnum = BoxFixnum v66
v52:BasicObject = Send v35, :call, v67 # SendFallbackReason: Send: unsupported optimized method type BlockCall
CheckInterrupts
@@ -4796,13 +4796,13 @@ mod hir_opt_tests {
v13:Fixnum[2] = Const Value(2)
v15:Fixnum[3] = Const Value(3)
v17:Fixnum[4] = Const Value(4)
v25:ArrayExact = NewArray v13, v15
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v28:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
PushInlineFrame :foo, v28 (0x1038), num_args=3
v26:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v27:ArrayExact = NewArray v13, v15
PushInlineFrame :foo, v26 (0x1038), num_args=3
PatchPoint NoSingletonClass(Array@0x1060)
PatchPoint MethodRedefined(Array@0x1060, length@0x1068, cme:0x1070)
v61:CInt64 = ArrayLength v25
v61:CInt64 = ArrayLength v27
v62:Fixnum = BoxFixnum v61
PatchPoint MethodRedefined(Integer@0x1098, +@0x10a0, cme:0x10a8)
v66:Fixnum = FixnumAdd v62, v11
@@ -4835,14 +4835,14 @@ mod hir_opt_tests {
v11:Fixnum[1] = Const Value(1)
v13:Fixnum[2] = Const Value(2)
v15:Fixnum[40] = Const Value(40)
v23:ArrayExact = NewArray v11, v13
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v26:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v24:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v25:ArrayExact = NewArray v11, v13
v47:Fixnum[0] = Const Value(0)
PushInlineFrame :foo, v26 (0x1038), num_args=2
PushInlineFrame :foo, v24 (0x1038), num_args=2
PatchPoint NoSingletonClass(Array@0x1060)
PatchPoint MethodRedefined(Array@0x1060, length@0x1068, cme:0x1070)
v56:CInt64 = ArrayLength v23
v56:CInt64 = ArrayLength v25
v57:Fixnum = BoxFixnum v56
PatchPoint MethodRedefined(Integer@0x1098, +@0x10a0, cme:0x10a8)
v61:Fixnum = FixnumAdd v57, v15
@@ -4873,18 +4873,18 @@ mod hir_opt_tests {
bb3(v6:BasicObject):
v11:Fixnum[1] = Const Value(1)
v13:Fixnum[2] = Const Value(2)
v21:Fixnum[40] = Const Value(40)
v22:ArrayExact = NewArray v11, v13
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v25:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v22:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v23:ArrayExact = NewArray v11, v13
v24:Fixnum[40] = Const Value(40)
v46:Fixnum[0] = Const Value(0)
PushInlineFrame :foo, v25 (0x1038), num_args=2
PushInlineFrame :foo, v22 (0x1038), num_args=2
PatchPoint NoSingletonClass(Array@0x1060)
PatchPoint MethodRedefined(Array@0x1060, length@0x1068, cme:0x1070)
v55:CInt64 = ArrayLength v22
v55:CInt64 = ArrayLength v23
v56:Fixnum = BoxFixnum v55
PatchPoint MethodRedefined(Integer@0x1098, +@0x10a0, cme:0x10a8)
v60:Fixnum = FixnumAdd v56, v21
v60:Fixnum = FixnumAdd v56, v24
CheckInterrupts
PopInlineFrame
Return v60
@@ -4914,11 +4914,11 @@ mod hir_opt_tests {
v13:Fixnum[20] = Const Value(20)
v15:Fixnum[30] = Const Value(30)
v17:Fixnum[40] = Const Value(40)
v25:ArrayExact = NewArray v15, v17
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v28:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
PushInlineFrame :foo, v28 (0x1038), num_args=3
v41:ArrayExact = NewArray v11, v13, v25
v26:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v27:ArrayExact = NewArray v15, v17
PushInlineFrame :foo, v26 (0x1038), num_args=3
v41:ArrayExact = NewArray v11, v13, v27
CheckInterrupts
PopInlineFrame
Return v41
@@ -5044,9 +5044,9 @@ mod hir_opt_tests {
v13:Fixnum[1] = Const Value(1)
v15:Fixnum[2] = Const Value(2)
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v25:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v24:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v44:Fixnum[0] = Const Value(0)
PushInlineFrame :foo, v25 (0x1038), num_args=3
PushInlineFrame :foo, v24 (0x1038), num_args=3
v39:ArrayExact = NewArray v13, v15, v11
CheckInterrupts
PopInlineFrame
@@ -5077,9 +5077,9 @@ mod hir_opt_tests {
v13:Fixnum[2] = Const Value(2)
v15:Fixnum[1] = Const Value(1)
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v25:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v24:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v44:Fixnum[0] = Const Value(0)
PushInlineFrame :foo, v25 (0x1038), num_args=3
PushInlineFrame :foo, v24 (0x1038), num_args=3
v39:ArrayExact = NewArray v11, v15, v13
CheckInterrupts
PopInlineFrame
@@ -5183,13 +5183,13 @@ mod hir_opt_tests {
bb3(v6:BasicObject):
v11:Fixnum[1] = Const Value(1)
v13:Fixnum[3] = Const Value(3)
v34:Fixnum[4] = Const Value(4)
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v37:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v35:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v36:Fixnum[4] = Const Value(4)
v74:Fixnum[0] = Const Value(0)
PushInlineFrame :foo, v37 (0x1038), num_args=3
PushInlineFrame :foo, v35 (0x1038), num_args=3
v52:Fixnum[2] = Const Value(2)
v68:ArrayExact = NewArray v11, v52, v13, v34
v68:ArrayExact = NewArray v11, v52, v13, v36
CheckInterrupts
PopInlineFrame
v18:Fixnum[1] = Const Value(1)
@@ -5197,7 +5197,7 @@ mod hir_opt_tests {
v22:Fixnum[40] = Const Value(40)
v24:Fixnum[30] = Const Value(30)
v98:Fixnum[0] = Const Value(0)
PushInlineFrame :foo, v37 (0x1038), num_args=4
PushInlineFrame :foo, v35 (0x1038), num_args=4
v93:ArrayExact = NewArray v18, v20, v24, v22
PopInlineFrame
v28:ArrayExact = NewArray v68, v93
@@ -5272,11 +5272,11 @@ mod hir_opt_tests {
Jump bb3(v4)
bb3(v6:BasicObject):
v11:Fixnum[1] = Const Value(1)
v19:StaticSymbol[:k] = Const Value(VALUE(0x1000))
v20:HashExact = NewHash v19: v11
PatchPoint MethodRedefined(Object@0x1008, foo@0x1010, cme:0x1018)
v23:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile
PushInlineFrame :foo, v23 (0x1040), num_args=1
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v21:StaticSymbol[:k] = Const Value(VALUE(0x1038))
v22:HashExact = NewHash v21: v11
PushInlineFrame :foo, v20 (0x1040), num_args=1
PatchPoint NoSingletonClass(Hash@0x1068)
PatchPoint MethodRedefined(Hash@0x1068, class@0x1070, cme:0x1078)
v44:ClassSubclass[Hash@0x1068] = Const Value(VALUE(0x1068))
@@ -5307,13 +5307,13 @@ mod hir_opt_tests {
bb3(v6:BasicObject):
v11:Fixnum[1] = Const Value(1)
v13:Fixnum[2] = Const Value(2)
v21:StaticSymbol[:k] = Const Value(VALUE(0x1000))
v22:StaticSymbol[:v] = Const Value(VALUE(0x1008))
v23:HashExact = NewHash v21: v11, v22: v13
PatchPoint MethodRedefined(Object@0x1010, foo@0x1018, cme:0x1020)
v26:ObjectSubclass[class_exact*:Object@VALUE(0x1010)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1010)] recompile
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v22:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v23:StaticSymbol[:k] = Const Value(VALUE(0x1038))
v24:StaticSymbol[:v] = Const Value(VALUE(0x1040))
v25:HashExact = NewHash v23: v11, v24: v13
CheckInterrupts
Return v23
Return v25
");
}
@@ -5338,12 +5338,12 @@ mod hir_opt_tests {
bb3(v6:BasicObject):
v11:Fixnum[1] = Const Value(1)
v13:Fixnum[2] = Const Value(2)
v21:StaticSymbol[:k] = Const Value(VALUE(0x1000))
v22:HashExact = NewHash v21: v13
PatchPoint MethodRedefined(Object@0x1008, foo@0x1010, cme:0x1018)
v25:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile
PushInlineFrame :foo, v25 (0x1040), num_args=2
v36:ArrayExact = NewArray v11, v22
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v22:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v23:StaticSymbol[:k] = Const Value(VALUE(0x1038))
v24:HashExact = NewHash v23: v13
PushInlineFrame :foo, v22 (0x1040), num_args=2
v36:ArrayExact = NewArray v11, v24
CheckInterrupts
PopInlineFrame
Return v36
@@ -5370,13 +5370,13 @@ mod hir_opt_tests {
Jump bb3(v4)
bb3(v6:BasicObject):
v11:Fixnum[1] = Const Value(1)
v19:StaticSymbol[:k] = Const Value(VALUE(0x1000))
v20:HashExact = NewHash v19: v11
PatchPoint MethodRedefined(Object@0x1008, foo@0x1010, cme:0x1018)
v23:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile
PushInlineFrame :foo, v23 (0x1040), num_args=1
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v21:StaticSymbol[:k] = Const Value(VALUE(0x1038))
v22:HashExact = NewHash v21: v11
PushInlineFrame :foo, v20 (0x1040), num_args=1
v31:Fixnum[2] = Const Value(2)
v42:ArrayExact = NewArray v20, v31
v42:ArrayExact = NewArray v22, v31
CheckInterrupts
PopInlineFrame
Return v42
@@ -5443,6 +5443,7 @@ mod hir_opt_tests {
v14:Fixnum[1] = Const Value(1)
IncrCounter zjit_insn_count
IncrCounter complex_arg_pass_keyword_to_positional_hash
IncrCounter send_direct_fallback_context_send
v17:BasicObject = Send v7, :foo, v14 # SendFallbackReason: Complex argument passing
IncrCounter zjit_insn_count
CheckInterrupts
@@ -5476,6 +5477,7 @@ mod hir_opt_tests {
v14:Fixnum[1] = Const Value(1)
IncrCounter zjit_insn_count
IncrCounter complex_arg_pass_keyword_to_positional_hash
IncrCounter send_direct_fallback_context_send
v17:BasicObject = Send v7, :foo, v14 # SendFallbackReason: Complex argument passing
IncrCounter zjit_insn_count
CheckInterrupts
@@ -5504,13 +5506,13 @@ mod hir_opt_tests {
bb3(v6:BasicObject):
v11:Fixnum[1] = Const Value(1)
v13:Fixnum[3] = Const Value(3)
v21:StaticSymbol[:k] = Const Value(VALUE(0x1000))
v22:HashExact = NewHash v21: v13
v23:ArrayExact = NewArray
PatchPoint MethodRedefined(Object@0x1008, foo@0x1010, cme:0x1018)
v26:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile
PushInlineFrame :foo, v26 (0x1040), num_args=3
v39:ArrayExact = NewArray v11, v22, v23
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v22:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v23:StaticSymbol[:k] = Const Value(VALUE(0x1038))
v24:HashExact = NewHash v23: v13
v25:ArrayExact = NewArray
PushInlineFrame :foo, v22 (0x1040), num_args=3
v39:ArrayExact = NewArray v11, v24, v25
CheckInterrupts
PopInlineFrame
Return v39
@@ -5537,15 +5539,15 @@ mod hir_opt_tests {
Jump bb3(v4)
bb3(v6:BasicObject):
v11:Fixnum[1] = Const Value(1)
v19:StaticSymbol[:k] = Const Value(VALUE(0x1000))
v20:HashExact = NewHash v19: v11
v21:ArrayExact = NewArray v20
PatchPoint MethodRedefined(Object@0x1008, foo@0x1010, cme:0x1018)
v24:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile
PushInlineFrame :foo, v24 (0x1040), num_args=1
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v21:StaticSymbol[:k] = Const Value(VALUE(0x1038))
v22:HashExact = NewHash v21: v11
v23:ArrayExact = NewArray v22
PushInlineFrame :foo, v20 (0x1040), num_args=1
CheckInterrupts
PopInlineFrame
Return v21
Return v23
");
}
@@ -5571,13 +5573,13 @@ mod hir_opt_tests {
v11:Fixnum[1] = Const Value(1)
v13:Fixnum[2] = Const Value(2)
v15:Fixnum[3] = Const Value(3)
v23:StaticSymbol[:k] = Const Value(VALUE(0x1000))
v24:HashExact = NewHash v23: v15
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v24:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v25:ArrayExact = NewArray v13
PatchPoint MethodRedefined(Object@0x1008, foo@0x1010, cme:0x1018)
v28:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile
PushInlineFrame :foo, v28 (0x1040), num_args=3
v41:ArrayExact = NewArray v11, v25, v24
v26:StaticSymbol[:k] = Const Value(VALUE(0x1038))
v27:HashExact = NewHash v26: v15
PushInlineFrame :foo, v24 (0x1040), num_args=3
v41:ArrayExact = NewArray v11, v25, v27
CheckInterrupts
PopInlineFrame
Return v41
@@ -5693,6 +5695,7 @@ mod hir_opt_tests {
v14:Fixnum[1] = Const Value(1)
IncrCounter zjit_insn_count
IncrCounter complex_arg_pass_param_kwrest
IncrCounter send_direct_fallback_context_send
v17:BasicObject = Send v7, :foo, v14 # SendFallbackReason: Complex argument passing
IncrCounter zjit_insn_count
CheckInterrupts
@@ -5773,11 +5776,11 @@ mod hir_opt_tests {
v4:BasicObject = LoadArg :self@0
Jump bb3(v4)
bb3(v6:BasicObject):
v17:Fixnum[1] = Const Value(1)
PatchPoint MethodRedefined(Object@0x1000, foo@0x1008, cme:0x1010)
v20:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v18:ObjectSubclass[class_exact*:Object@VALUE(0x1000)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1000)] recompile
v19:Fixnum[1] = Const Value(1)
v38:Fixnum[0] = Const Value(0)
PushInlineFrame :foo, v20 (0x1038), num_args=1
PushInlineFrame :foo, v18 (0x1038), num_args=1
v30:Fixnum[1] = Const Value(1)
PatchPoint MethodRedefined(Integer@0x1060, +@0x1068, cme:0x1070)
v47:Fixnum[2] = Const Value(2)
@@ -5845,6 +5848,7 @@ mod hir_opt_tests {
IncrCounter zjit_insn_count
IncrCounter zjit_insn_count
IncrCounter complex_arg_pass_param_kwrest
IncrCounter send_direct_fallback_context_send
v14:BasicObject = Send v7, :foo # SendFallbackReason: Complex argument passing
IncrCounter zjit_insn_count
CheckInterrupts
@@ -6175,9 +6179,9 @@ mod hir_opt_tests {
v14:NilClass = Const Value(nil)
PatchPoint MethodRedefined(Hash@0x1008, new@0x1009, cme:0x1010)
v44:HashExact = ObjectAllocClass Hash:VALUE(0x1008)
v45:Fixnum[0] = Const Value(0)
PatchPoint NoSingletonClass(Hash@0x1008)
PatchPoint MethodRedefined(Hash@0x1008, initialize@0x1038, cme:0x1040)
v48:Fixnum[0] = Const Value(0)
v97:Fixnum[0] = Const Value(0)
v98:NilClass = Const Value(nil)
PushInlineFrame :initialize, v44 (0x1068), num_args=1
@@ -6193,7 +6197,7 @@ mod hir_opt_tests {
v88:BasicObject = GetBlockParam :block, l0, EP@4
Jump bb13(v88)
bb13(v81:BasicObject):
v91:BasicObject = InvokeBuiltin rb_hash_init, v44, v45, v64, v64, v81
v91:BasicObject = InvokeBuiltin rb_hash_init, v44, v48, v64, v64, v81
CheckInterrupts
PopInlineFrame
Return v44
@@ -16391,7 +16395,7 @@ mod hir_opt_tests {
def call
puts [], [], [], [] # fill VM stack with junk
read_nil_local(true, 1, 1) # expected direct send
read_nil_local(true, 1, 1) # expected SendDirect
end
call # profile
@@ -17802,6 +17806,46 @@ mod hir_opt_tests {
");
}
#[test]
fn test_invokesuper_with_missing_keyword_tracks_super_target_context() {
enable_zjit_stats();
eval("
class A
def foo(k:) = k
end
class B < A
def foo
super()
end
end
begin; B.new.foo; rescue ArgumentError; end
begin; B.new.foo; rescue ArgumentError; end
");
assert_snapshot!(hir_string_proc("B.new.method(:foo)"), @"
fn foo@<compiled>:8:
bb1():
EntryPoint interpreter
v1:HeapBasicObject = LoadSelf
Jump bb3(v1)
bb2():
EntryPoint JIT(0)
v4:HeapBasicObject = LoadArg :self@0
IncrCounterPtr
Jump bb3(v4)
bb3(v7:HeapBasicObject):
IncrCounter zjit_insn_count
IncrCounter zjit_insn_count
IncrCounter send_direct_fallback_context_super
v14:BasicObject = InvokeSuper v7, 0x1000 # SendFallbackReason: Argument count does not match parameter count
IncrCounter zjit_insn_count
CheckInterrupts
Return v14
");
}
#[test]
fn test_infer_truthiness_from_branch() {
eval("
@@ -21102,14 +21146,14 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :n@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
v22:Fixnum[10] = Const Value(10)
PatchPoint MethodRedefined(Object@0x1008, add_optkw@0x1010, cme:0x1018)
v25:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v9, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile
v23:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v9, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile
v24:Fixnum[10] = Const Value(10)
v43:Fixnum[0] = Const Value(0)
PushInlineFrame :add_optkw, v25 (0x1040), num_args=2
PushInlineFrame :add_optkw, v23 (0x1040), num_args=2
PatchPoint MethodRedefined(Integer@0x1068, +@0x1070, cme:0x1078)
v50:Fixnum = GuardType v10, Fixnum recompile
v51:Fixnum = FixnumAdd v50, v22
v51:Fixnum = FixnumAdd v50, v24
CheckInterrupts
PopInlineFrame
Return v51
@@ -21158,9 +21202,9 @@ mod hir_opt_tests {
v16:Fixnum[3] = Const Value(3)
v18:Fixnum[2] = Const Value(2)
PatchPoint MethodRedefined(Object@0x1008, add_kws@0x1010, cme:0x1018)
v28:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v9, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile
v27:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v9, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile
v61:Fixnum[0] = Const Value(0)
PushInlineFrame :add_kws, v28 (0x1040), num_args=3
PushInlineFrame :add_kws, v27 (0x1040), num_args=3
v40:Fixnum[100] = Const Value(100)
PatchPoint MethodRedefined(Integer@0x1068, *@0x1070, cme:0x1078)
v68:Fixnum = GuardType v10, Fixnum recompile
@@ -21214,14 +21258,14 @@ mod hir_opt_tests {
v7:BasicObject = LoadArg :n@1
Jump bb3(v6, v7)
bb3(v9:BasicObject, v10:BasicObject):
v22:NilClass = Const Value(nil)
PatchPoint MethodRedefined(Object@0x1008, add_optkw_dyn@0x1010, cme:0x1018)
v25:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v9, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile
v23:ObjectSubclass[class_exact*:Object@VALUE(0x1008)] = GuardType v9, ObjectSubclass[class_exact*:Object@VALUE(0x1008)] recompile
v24:NilClass = Const Value(nil)
v63:Fixnum[1] = Const Value(1)
PushInlineFrame :add_optkw_dyn, v25 (0x1040), num_args=2
PushInlineFrame :add_optkw_dyn, v23 (0x1040), num_args=2
v34:BoolExact = FixnumBitCheck v63, 0
v36:CBool = Test v34
CondBranch v36, bb6(v22), bb7()
CondBranch v36, bb6(v24), bb7()
bb7():
v42:Fixnum[2] = Const Value(2)
PatchPoint MethodRedefined(Integer@0x1068, *@0x1070, cme:0x1078)
+3 -3
View File
@@ -206,12 +206,12 @@ mod snapshot_tests {
v13:Fixnum[1] = Const Value(1)
v15:Fixnum[2] = Const Value(2)
v16:Any = Snapshot FrameState { pc: 0x1008, stack: [v6, v11, v13, v15], locals: [] }
v23:Any = Snapshot FrameState { pc: 0x1008, stack: [v6, v13, v15, v11], locals: [] }
PatchPoint MethodRedefined(Object@0x1010, foo@0x1018, cme:0x1020)
v25:ObjectSubclass[class_exact*:Object@VALUE(0x1010)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1010)] recompile
v24:ObjectSubclass[class_exact*:Object@VALUE(0x1010)] = GuardType v6, ObjectSubclass[class_exact*:Object@VALUE(0x1010)] recompile
v25:Any = Snapshot FrameState { pc: 0x1008, stack: [v24, v13, v15, v11], locals: [] }
v44:Fixnum[0] = Const Value(0)
v27:Any = Snapshot FrameState { pc: 0x1008, stack: [], locals: [] }
PushInlineFrame :foo, v25 (0x1048), num_args=3
PushInlineFrame :foo, v24 (0x1048), num_args=3
v38:Any = Snapshot FrameState { pc: 0x1070, stack: [v13, v15, v11], locals: [a=v13, b=v15, c=v11, ID(0)=v44], caller: v27 }
v39:ArrayExact = NewArray v13, v15, v11
v40:Any = Snapshot FrameState { pc: 0x1078, stack: [v39], locals: [a=v13, b=v15, c=v11, ID(0)=v44], caller: v27 }
+5 -2
View File
@@ -292,7 +292,6 @@ make_counters! {
send_fallback_super_not_optimized_method_type,
send_fallback_super_polymorphic,
send_fallback_super_target_not_found,
send_fallback_super_target_complex_args_pass,
send_fallback_cannot_send_direct,
send_fallback_invokeblock_not_specialized,
send_fallback_invokeblock_polymorphic_miss,
@@ -445,6 +444,11 @@ make_counters! {
caller_splat_profile_megamorphic,
caller_splat_profile_skewed_megamorphic,
// Contexts in which SendDirect argument planning failed. These are kept
// outside dynamic_send because the detailed fallback reason is also counted.
send_direct_fallback_context_send,
send_direct_fallback_context_super,
// Writes to the VM frame
vm_write_jit_frame_count,
vm_write_sp_count,
@@ -708,7 +712,6 @@ pub fn send_fallback_counter(reason: crate::hir::SendFallbackReason) -> Counter
SuperNotOptimizedMethodType(_) => send_fallback_super_not_optimized_method_type,
SuperPolymorphic => send_fallback_super_polymorphic,
SuperTargetNotFound => send_fallback_super_target_not_found,
SuperTargetComplexArgsPass => send_fallback_super_target_complex_args_pass,
InvokeBlockNotSpecialized => send_fallback_invokeblock_not_specialized,
InvokeBlockPolymorphicMiss => send_fallback_invokeblock_polymorphic_miss,
SendForwardNotSpecialized => send_fallback_sendforward_not_specialized,