summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMax Bernstein <rubybugs@bernsteinbear.com>2025-11-26 15:36:00 -0800
committerGitHub <noreply@github.com>2025-11-26 15:36:00 -0800
commitdb94a79da432bcdb9d48517733f11ccf03c7cd5d (patch)
tree3972c411032211b42a96dbae2e7ccb23af264716
parent52426a22de8ce5e2f02ce4169a1b6944e2165b46 (diff)
ZJIT: Count fallback reasons for set/get/definedivar (#15324)
lobsters: ``` Top-4 setivar fallback reasons (100.0% of total 7,789,008): shape_transition: 6,074,085 (78.0%) not_monomorphic: 1,484,013 (19.1%) not_t_object: 172,629 ( 2.2%) too_complex: 58,281 ( 0.7%) Top-3 getivar fallback reasons (100.0% of total 9,348,832): not_t_object: 4,658,833 (49.8%) not_monomorphic: 4,542,316 (48.6%) too_complex: 147,683 ( 1.6%) Top-3 definedivar fallback reasons (100.0% of total 366,383): not_monomorphic: 361,389 (98.6%) too_complex: 3,062 ( 0.8%) not_t_object: 1,932 ( 0.5%) ``` railsbench: ``` Top-3 setivar fallback reasons (100.0% of total 15,119,057): shape_transition: 13,760,763 (91.0%) not_monomorphic: 982,368 ( 6.5%) not_t_object: 375,926 ( 2.5%) Top-2 getivar fallback reasons (100.0% of total 14,438,747): not_t_object: 7,643,870 (52.9%) not_monomorphic: 6,794,877 (47.1%) Top-2 definedivar fallback reasons (100.0% of total 209,613): not_monomorphic: 209,526 (100.0%) not_t_object: 87 ( 0.0%) ``` shipit: ``` Top-3 setivar fallback reasons (100.0% of total 14,516,254): shape_transition: 8,613,512 (59.3%) not_monomorphic: 5,761,398 (39.7%) not_t_object: 141,344 ( 1.0%) Top-2 getivar fallback reasons (100.0% of total 21,016,444): not_monomorphic: 11,313,482 (53.8%) not_t_object: 9,702,962 (46.2%) Top-2 definedivar fallback reasons (100.0% of total 290,382): not_monomorphic: 287,755 (99.1%) not_t_object: 2,627 ( 0.9%) ```
-rw-r--r--zjit.rb9
-rw-r--r--zjit/src/codegen.rs2
-rw-r--r--zjit/src/hir.rs14
-rw-r--r--zjit/src/hir/opt_tests.rs15
-rw-r--r--zjit/src/stats.rs99
5 files changed, 128 insertions, 11 deletions
diff --git a/zjit.rb b/zjit.rb
index fc306c19a4..d128adead6 100644
--- a/zjit.rb
+++ b/zjit.rb
@@ -183,6 +183,9 @@ class << RubyVM::ZJIT
print_counters_with_prefix(prefix: 'unspecialized_send_without_block_def_type_', prompt: 'not optimized method types for send_without_block', 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: '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)
print_counters_with_prefix(prefix: 'invokeblock_handler_', prompt: 'invokeblock handler', buf:, stats:, limit: 10)
# Show most popular unsupported call features. Because each call can
@@ -201,6 +204,9 @@ class << RubyVM::ZJIT
:send_count,
:dynamic_send_count,
:optimized_send_count,
+ :dynamic_setivar_count,
+ :dynamic_getivar_count,
+ :dynamic_definedivar_count,
:iseq_optimized_send_count,
:inline_cfunc_optimized_send_count,
:inline_iseq_optimized_send_count,
@@ -208,9 +214,6 @@ class << RubyVM::ZJIT
:variadic_cfunc_optimized_send_count,
], buf:, stats:, right_align: true, base: :send_count)
print_counters([
- :dynamic_getivar_count,
- :dynamic_setivar_count,
-
:compiled_iseq_count,
:failed_iseq_count,
diff --git a/zjit/src/codegen.rs b/zjit/src/codegen.rs
index 3300219ccd..66436b2374 100644
--- a/zjit/src/codegen.rs
+++ b/zjit/src/codegen.rs
@@ -892,7 +892,6 @@ fn gen_ccall_variadic(
/// Emit an uncached instance variable lookup
fn gen_getivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic: *const iseq_inline_iv_cache_entry) -> Opnd {
- gen_incr_counter(asm, Counter::dynamic_getivar_count);
if ic.is_null() {
asm_ccall!(asm, rb_ivar_get, recv, id.0.into())
} else {
@@ -903,7 +902,6 @@ fn gen_getivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic:
/// Emit an uncached instance variable store
fn gen_setivar(jit: &mut JITState, asm: &mut Assembler, recv: Opnd, id: ID, ic: *const iseq_inline_iv_cache_entry, val: Opnd, state: &FrameState) {
- gen_incr_counter(asm, Counter::dynamic_setivar_count);
// Setting an ivar can raise FrozenError, so we need proper frame state for exception handling.
gen_prepare_non_leaf_call(jit, asm, state);
if ic.is_null() {
diff --git a/zjit/src/hir.rs b/zjit/src/hir.rs
index b7149ad14c..fbc9d80700 100644
--- a/zjit/src/hir.rs
+++ b/zjit/src/hir.rs
@@ -2806,19 +2806,23 @@ impl Function {
let frame_state = self.frame_state(state);
let Some(recv_type) = self.profiled_type_of_at(self_val, frame_state.insn_idx) else {
// No (monomorphic/skewed polymorphic) profile info
+ self.push_insn(block, Insn::IncrCounter(Counter::getivar_fallback_not_monomorphic));
self.push_insn_id(block, insn_id); continue;
};
if recv_type.flags().is_immediate() {
// Instance variable lookups on immediate values are always nil
+ self.push_insn(block, Insn::IncrCounter(Counter::getivar_fallback_immediate));
self.push_insn_id(block, insn_id); continue;
}
assert!(recv_type.shape().is_valid());
if !recv_type.flags().is_t_object() {
// Check if the receiver is a T_OBJECT
+ self.push_insn(block, Insn::IncrCounter(Counter::getivar_fallback_not_t_object));
self.push_insn_id(block, insn_id); continue;
}
if recv_type.shape().is_too_complex() {
// too-complex shapes can't use index access
+ self.push_insn(block, Insn::IncrCounter(Counter::getivar_fallback_too_complex));
self.push_insn_id(block, insn_id); continue;
}
let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state });
@@ -2845,19 +2849,23 @@ impl Function {
let frame_state = self.frame_state(state);
let Some(recv_type) = self.profiled_type_of_at(self_val, frame_state.insn_idx) else {
// No (monomorphic/skewed polymorphic) profile info
+ self.push_insn(block, Insn::IncrCounter(Counter::definedivar_fallback_not_monomorphic));
self.push_insn_id(block, insn_id); continue;
};
if recv_type.flags().is_immediate() {
// Instance variable lookups on immediate values are always nil
+ self.push_insn(block, Insn::IncrCounter(Counter::definedivar_fallback_immediate));
self.push_insn_id(block, insn_id); continue;
}
assert!(recv_type.shape().is_valid());
if !recv_type.flags().is_t_object() {
// Check if the receiver is a T_OBJECT
+ self.push_insn(block, Insn::IncrCounter(Counter::definedivar_fallback_not_t_object));
self.push_insn_id(block, insn_id); continue;
}
if recv_type.shape().is_too_complex() {
// too-complex shapes can't use index access
+ self.push_insn(block, Insn::IncrCounter(Counter::definedivar_fallback_too_complex));
self.push_insn_id(block, insn_id); continue;
}
let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state });
@@ -2877,28 +2885,34 @@ impl Function {
let frame_state = self.frame_state(state);
let Some(recv_type) = self.profiled_type_of_at(self_val, frame_state.insn_idx) else {
// No (monomorphic/skewed polymorphic) profile info
+ self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_not_monomorphic));
self.push_insn_id(block, insn_id); continue;
};
if recv_type.flags().is_immediate() {
// Instance variable lookups on immediate values are always nil
+ self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_immediate));
self.push_insn_id(block, insn_id); continue;
}
assert!(recv_type.shape().is_valid());
if !recv_type.flags().is_t_object() {
// Check if the receiver is a T_OBJECT
+ self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_not_t_object));
self.push_insn_id(block, insn_id); continue;
}
if recv_type.shape().is_too_complex() {
// too-complex shapes can't use index access
+ self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_too_complex));
self.push_insn_id(block, insn_id); continue;
}
if recv_type.shape().is_frozen() {
// Can't set ivars on frozen objects
+ self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_frozen));
self.push_insn_id(block, insn_id); continue;
}
let mut ivar_index: u16 = 0;
if !unsafe { rb_shape_get_iv_index(recv_type.shape().0, id, &mut ivar_index) } {
// TODO(max): Shape transition
+ self.push_insn(block, Insn::IncrCounter(Counter::setivar_fallback_shape_transition));
self.push_insn_id(block, insn_id); continue;
}
let self_val = self.push_insn(block, Insn::GuardType { val: self_val, guard_type: types::HeapBasicObject, state });
diff --git a/zjit/src/hir/opt_tests.rs b/zjit/src/hir/opt_tests.rs
index c460942fc1..b32da5a9eb 100644
--- a/zjit/src/hir/opt_tests.rs
+++ b/zjit/src/hir/opt_tests.rs
@@ -3343,6 +3343,7 @@ mod hir_opt_tests {
Jump bb2(v4)
bb2(v6:BasicObject):
PatchPoint SingleRactorMode
+ IncrCounter getivar_fallback_not_monomorphic
v11:BasicObject = GetIvar v6, :@foo
CheckInterrupts
Return v11
@@ -3366,6 +3367,7 @@ mod hir_opt_tests {
bb2(v6:BasicObject):
v10:Fixnum[1] = Const Value(1)
PatchPoint SingleRactorMode
+ IncrCounter setivar_fallback_not_monomorphic
SetIvar v6, :@foo, v10
CheckInterrupts
Return v10
@@ -3442,6 +3444,7 @@ mod hir_opt_tests {
EntryPoint JIT(0)
Jump bb2(v4)
bb2(v6:BasicObject):
+ IncrCounter definedivar_fallback_not_t_object
v10:StringExact|NilClass = DefinedIvar v6, :@a
CheckInterrupts
Return v10
@@ -3473,6 +3476,7 @@ mod hir_opt_tests {
EntryPoint JIT(0)
Jump bb2(v4)
bb2(v6:BasicObject):
+ IncrCounter definedivar_fallback_not_monomorphic
v10:StringExact|NilClass = DefinedIvar v6, :@a
CheckInterrupts
Return v10
@@ -3525,6 +3529,7 @@ mod hir_opt_tests {
bb2(v6:BasicObject):
v10:Fixnum[5] = Const Value(5)
PatchPoint SingleRactorMode
+ IncrCounter setivar_fallback_shape_transition
SetIvar v6, :@foo, v10
CheckInterrupts
Return v10
@@ -3554,6 +3559,7 @@ mod hir_opt_tests {
bb2(v6:BasicObject):
v10:Fixnum[5] = Const Value(5)
PatchPoint SingleRactorMode
+ IncrCounter setivar_fallback_not_t_object
SetIvar v6, :@a, v10
CheckInterrupts
Return v10
@@ -3587,6 +3593,7 @@ mod hir_opt_tests {
bb2(v6:BasicObject):
v10:Fixnum[5] = Const Value(5)
PatchPoint SingleRactorMode
+ IncrCounter setivar_fallback_not_monomorphic
SetIvar v6, :@a, v10
CheckInterrupts
Return v10
@@ -3618,6 +3625,7 @@ mod hir_opt_tests {
bb2(v6:BasicObject):
v10:Fixnum[5] = Const Value(5)
PatchPoint SingleRactorMode
+ IncrCounter setivar_fallback_shape_transition
SetIvar v6, :@a, v10
CheckInterrupts
Return v10
@@ -5527,6 +5535,7 @@ mod hir_opt_tests {
v16:Fixnum[5] = Const Value(5)
PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010)
v26:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C]
+ IncrCounter setivar_fallback_shape_transition
SetIvar v26, :@foo, v16
CheckInterrupts
Return v16
@@ -5558,6 +5567,7 @@ mod hir_opt_tests {
v16:Fixnum[5] = Const Value(5)
PatchPoint MethodRedefined(C@0x1000, foo=@0x1008, cme:0x1010)
v26:HeapObject[class_exact:C] = GuardType v9, HeapObject[class_exact:C]
+ IncrCounter setivar_fallback_shape_transition
SetIvar v26, :@foo, v16
CheckInterrupts
Return v16
@@ -8609,17 +8619,18 @@ mod hir_opt_tests {
SetLocal l0, EP@3, v27
v39:BasicObject = GetLocal l0, EP@3
PatchPoint SingleRactorMode
+ IncrCounter setivar_fallback_shape_transition
SetIvar v14, :@formatted, v39
v45:Class[VMFrozenCore] = Const Value(VALUE(0x1000))
PatchPoint MethodRedefined(Class@0x1008, lambda@0x1010, cme:0x1018)
PatchPoint NoSingletonClass(Class@0x1008)
- v59:BasicObject = CCallWithFrame RubyVM::FrozenCore.lambda@0x1040, v45, block=0x1048
+ v60:BasicObject = CCallWithFrame RubyVM::FrozenCore.lambda@0x1040, v45, block=0x1048
v48:BasicObject = GetLocal l0, EP@6
v49:BasicObject = GetLocal l0, EP@5
v50:BasicObject = GetLocal l0, EP@4
v51:BasicObject = GetLocal l0, EP@3
CheckInterrupts
- Return v59
+ Return v60
");
}
}
diff --git a/zjit/src/stats.rs b/zjit/src/stats.rs
index 1277db5b7e..890d92bc56 100644
--- a/zjit/src/stats.rs
+++ b/zjit/src/stats.rs
@@ -24,6 +24,15 @@ macro_rules! make_counters {
optimized_send {
$($optimized_send_counter_name:ident,)+
}
+ dynamic_setivar {
+ $($dynamic_setivar_counter_name:ident,)+
+ }
+ dynamic_getivar {
+ $($dynamic_getivar_counter_name:ident,)+
+ }
+ dynamic_definedivar {
+ $($dynamic_definedivar_counter_name:ident,)+
+ }
$($counter_name:ident,)+
) => {
/// Struct containing the counter values
@@ -33,6 +42,9 @@ macro_rules! make_counters {
$(pub $exit_counter_name: u64,)+
$(pub $dynamic_send_counter_name: u64,)+
$(pub $optimized_send_counter_name: u64,)+
+ $(pub $dynamic_setivar_counter_name: u64,)+
+ $(pub $dynamic_getivar_counter_name: u64,)+
+ $(pub $dynamic_definedivar_counter_name: u64,)+
$(pub $counter_name: u64,)+
}
@@ -44,6 +56,9 @@ macro_rules! make_counters {
$($exit_counter_name,)+
$($dynamic_send_counter_name,)+
$($optimized_send_counter_name,)+
+ $($dynamic_setivar_counter_name,)+
+ $($dynamic_getivar_counter_name,)+
+ $($dynamic_definedivar_counter_name,)+
$($counter_name,)+
}
@@ -54,6 +69,9 @@ macro_rules! make_counters {
$( Counter::$exit_counter_name => stringify!($exit_counter_name), )+
$( Counter::$dynamic_send_counter_name => stringify!($dynamic_send_counter_name), )+
$( Counter::$optimized_send_counter_name => stringify!($optimized_send_counter_name), )+
+ $( Counter::$dynamic_setivar_counter_name => stringify!($dynamic_setivar_counter_name), )+
+ $( Counter::$dynamic_getivar_counter_name => stringify!($dynamic_getivar_counter_name), )+
+ $( Counter::$dynamic_definedivar_counter_name => stringify!($dynamic_definedivar_counter_name), )+
$( Counter::$counter_name => stringify!($counter_name), )+
}
}
@@ -64,6 +82,9 @@ macro_rules! make_counters {
$( stringify!($exit_counter_name) => Some(Counter::$exit_counter_name), )+
$( stringify!($dynamic_send_counter_name) => Some(Counter::$dynamic_send_counter_name), )+
$( stringify!($optimized_send_counter_name) => Some(Counter::$optimized_send_counter_name), )+
+ $( stringify!($dynamic_setivar_counter_name) => Some(Counter::$dynamic_setivar_counter_name), )+
+ $( stringify!($dynamic_getivar_counter_name) => Some(Counter::$dynamic_getivar_counter_name), )+
+ $( stringify!($dynamic_definedivar_counter_name) => Some(Counter::$dynamic_definedivar_counter_name), )+
$( stringify!($counter_name) => Some(Counter::$counter_name), )+
_ => None,
}
@@ -77,6 +98,9 @@ macro_rules! make_counters {
$( Counter::$default_counter_name => std::ptr::addr_of_mut!(counters.$default_counter_name), )+
$( Counter::$exit_counter_name => std::ptr::addr_of_mut!(counters.$exit_counter_name), )+
$( Counter::$dynamic_send_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_send_counter_name), )+
+ $( Counter::$dynamic_setivar_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_setivar_counter_name), )+
+ $( Counter::$dynamic_getivar_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_getivar_counter_name), )+
+ $( Counter::$dynamic_definedivar_counter_name => std::ptr::addr_of_mut!(counters.$dynamic_definedivar_counter_name), )+
$( Counter::$optimized_send_counter_name => std::ptr::addr_of_mut!(counters.$optimized_send_counter_name), )+
$( Counter::$counter_name => std::ptr::addr_of_mut!(counters.$counter_name), )+
}
@@ -103,6 +127,21 @@ macro_rules! make_counters {
$( Counter::$optimized_send_counter_name, )+
];
+ /// List of other counters that are summed as dynamic_setivar_count.
+ pub const DYNAMIC_SETIVAR_COUNTERS: &'static [Counter] = &[
+ $( Counter::$dynamic_setivar_counter_name, )+
+ ];
+
+ /// List of other counters that are summed as dynamic_getivar_count.
+ pub const DYNAMIC_GETIVAR_COUNTERS: &'static [Counter] = &[
+ $( Counter::$dynamic_getivar_counter_name, )+
+ ];
+
+ /// List of other counters that are summed as dynamic_definedivar_count.
+ pub const DYNAMIC_DEFINEDIVAR_COUNTERS: &'static [Counter] = &[
+ $( Counter::$dynamic_definedivar_counter_name, )+
+ ];
+
/// List of other counters that are available only for --zjit-stats.
pub const OTHER_COUNTERS: &'static [Counter] = &[
$( Counter::$counter_name, )+
@@ -207,6 +246,35 @@ make_counters! {
variadic_cfunc_optimized_send_count,
}
+ // Ivar fallback counters that are summed as dynamic_setivar_count
+ dynamic_setivar {
+ // setivar_fallback_: Fallback reasons for dynamic setivar instructions
+ setivar_fallback_not_monomorphic,
+ setivar_fallback_immediate,
+ setivar_fallback_not_t_object,
+ setivar_fallback_too_complex,
+ setivar_fallback_frozen,
+ setivar_fallback_shape_transition,
+ }
+
+ // Ivar fallback counters that are summed as dynamic_getivar_count
+ dynamic_getivar {
+ // getivar_fallback_: Fallback reasons for dynamic getivar instructions
+ getivar_fallback_not_monomorphic,
+ getivar_fallback_immediate,
+ getivar_fallback_not_t_object,
+ getivar_fallback_too_complex,
+ }
+
+ // Ivar fallback counters that are summed as dynamic_definedivar_count
+ dynamic_definedivar {
+ // definedivar_fallback_: Fallback reasons for dynamic definedivar instructions
+ definedivar_fallback_not_monomorphic,
+ definedivar_fallback_immediate,
+ definedivar_fallback_not_t_object,
+ definedivar_fallback_too_complex,
+ }
+
// compile_error_: Compile error reasons
compile_error_iseq_stack_too_large,
compile_error_exception_handler,
@@ -236,10 +304,6 @@ make_counters! {
// The number of times YARV instructions are executed on JIT code
zjit_insn_count,
- // The number of times we do a dynamic ivar lookup from JIT code
- dynamic_getivar_count,
- dynamic_setivar_count,
-
// Method call def_type related to send without block fallback to dynamic dispatch
unspecialized_send_without_block_def_type_iseq,
unspecialized_send_without_block_def_type_cfunc,
@@ -657,6 +721,33 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) ->
set_stat_usize!(hash, "optimized_send_count", optimized_send_count);
set_stat_usize!(hash, "send_count", dynamic_send_count + optimized_send_count);
+ // Set send fallback counters for each setivar fallback reason
+ let mut dynamic_setivar_count = 0;
+ for &counter in DYNAMIC_SETIVAR_COUNTERS {
+ let count = unsafe { *counter_ptr(counter) };
+ dynamic_setivar_count += count;
+ set_stat_usize!(hash, &counter.name(), count);
+ }
+ set_stat_usize!(hash, "dynamic_setivar_count", dynamic_setivar_count);
+
+ // Set send fallback counters for each getivar fallback reason
+ let mut dynamic_getivar_count = 0;
+ for &counter in DYNAMIC_GETIVAR_COUNTERS {
+ let count = unsafe { *counter_ptr(counter) };
+ dynamic_getivar_count += count;
+ set_stat_usize!(hash, &counter.name(), count);
+ }
+ set_stat_usize!(hash, "dynamic_getivar_count", dynamic_getivar_count);
+
+ // Set send fallback counters for each definedivar fallback reason
+ let mut dynamic_definedivar_count = 0;
+ for &counter in DYNAMIC_DEFINEDIVAR_COUNTERS {
+ let count = unsafe { *counter_ptr(counter) };
+ dynamic_definedivar_count += count;
+ set_stat_usize!(hash, &counter.name(), count);
+ }
+ set_stat_usize!(hash, "dynamic_definedivar_count", dynamic_definedivar_count);
+
// Set send fallback counters for Uncategorized
let send_fallback_counters = ZJITState::get_send_fallback_counters();
for (op_idx, count) in send_fallback_counters.iter().enumerate().take(VM_INSTRUCTION_SIZE as usize) {