Skip to content

Commit fc6d031

Browse files
committed
ZJIT: Add stats for cfuncs that are not optimized
1 parent 19109b4 commit fc6d031

6 files changed

Lines changed: 96 additions & 22 deletions

File tree

zjit.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@ rb_zjit_class_has_default_allocator(VALUE klass)
211211
return alloc == rb_class_allocate_instance;
212212
}
213213

214+
void rb_zjit_count_unoptimized_cfunc(const struct rb_callable_method_entry_struct *cme);
215+
214216
// Primitives used by zjit.rb. Don't put other functions below, which wouldn't use them.
215217
VALUE rb_zjit_assert_compiles(rb_execution_context_t *ec, VALUE self);
216218
VALUE rb_zjit_stats(rb_execution_context_t *ec, VALUE self, VALUE target_key);

zjit.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ void rb_zjit_iseq_mark(void *payload);
2424
void rb_zjit_iseq_update_references(void *payload);
2525
void rb_zjit_before_ractor_spawn(void);
2626
void rb_zjit_tracing_invalidate_all(void);
27+
void rb_zjit_count_unoptimized_cfunc(const struct rb_callable_method_entry_struct *cme);
2728
#else
2829
#define rb_zjit_enabled_p false
2930
static inline void rb_zjit_compile_iseq(const rb_iseq_t *iseq, bool jit_exception) {}
@@ -33,6 +34,7 @@ static inline void rb_zjit_bop_redefined(int redefined_flag, enum ruby_basic_ope
3334
static inline void rb_zjit_cme_invalidate(const rb_callable_method_entry_t *cme) {}
3435
static inline void rb_zjit_invalidate_no_ep_escape(const rb_iseq_t *iseq) {}
3536
static inline void rb_zjit_constant_state_changed(ID id) {}
37+
static inline void rb_zjit_count_unoptimized_cfunc(const struct rb_callable_method_entry_struct *cme) {}
3638
static inline void rb_zjit_before_ractor_spawn(void) {}
3739
static inline void rb_zjit_tracing_invalidate_all(void) {}
3840
#endif // #if USE_ZJIT

zjit.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def stats_string
4343
print_counters_with_prefix(prefix: 'dynamic_send_type_', prompt: 'dynamic send types', buf:, stats:, limit: 20)
4444
print_counters_with_prefix(prefix: 'unspecialized_def_type_', prompt: 'send fallback unspecialized def_types', buf:, stats:, limit: 20)
4545
print_counters_with_prefix(prefix: 'send_fallback_', prompt: 'dynamic send types', buf:, stats:, limit: 20)
46+
print_counters_with_prefix(prefix: 'not_optimized_cfuncs', prompt: 'Unoptimized C functions', buf:, stats:, limit: 20)
4647

4748
# Show exit counters, ordered by the typical amount of exits for the prefix at the time
4849
print_counters_with_prefix(prefix: 'unhandled_yarv_insn_', prompt: 'unhandled YARV insns', buf:, stats:, limit: 20)

zjit/src/codegen.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
415415
Insn::GetSpecialSymbol { symbol_type, state: _ } => gen_getspecial_symbol(asm, *symbol_type),
416416
Insn::GetSpecialNumber { nth, state } => gen_getspecial_number(asm, *nth, &function.frame_state(*state)),
417417
&Insn::IncrCounter(counter) => no_output!(gen_incr_counter(asm, counter)),
418+
&Insn::CountUnoptimizedCFunc { cme } => no_output!(gen_count_unoptimized_cfunc(asm, cme)),
418419
Insn::ObjToString { val, cd, state, .. } => gen_objtostring(jit, asm, opnd!(val), *cd, &function.frame_state(*state)),
419420
&Insn::CheckInterrupts { state } => no_output!(gen_check_interrupts(jit, asm, &function.frame_state(state))),
420421
&Insn::HashDup { val, state } => { gen_hash_dup(asm, opnd!(val), &function.frame_state(state)) },
@@ -1575,6 +1576,17 @@ fn gen_guard_bit_equals(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd,
15751576
val
15761577
}
15771578

1579+
/// Generate code that records unoptimized C functions if --zjit-stats is enabled
1580+
fn gen_count_unoptimized_cfunc(asm: &mut Assembler, cme: *const rb_callable_method_entry_struct) {
1581+
if get_option!(stats) {
1582+
unsafe extern "C" {
1583+
fn rb_zjit_count_unoptimized_cfunc(cme: *const rb_callable_method_entry_struct);
1584+
}
1585+
asm_comment!(asm, "count unoptimized cfunc");
1586+
asm_ccall!(asm, rb_zjit_count_unoptimized_cfunc, Opnd::const_ptr(cme));
1587+
}
1588+
}
1589+
15781590
/// Generate code that increments a counter if --zjit-stats
15791591
fn gen_incr_counter(asm: &mut Assembler, counter: Counter) {
15801592
if get_option!(stats) {

zjit/src/hir.rs

Lines changed: 47 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -697,6 +697,9 @@ pub enum Insn {
697697
/// Increment a counter in ZJIT stats
698698
IncrCounter(Counter),
699699

700+
/// Increment a counter in ZJIT stats for the given C function
701+
CountUnoptimizedCFunc { cme: *const rb_callable_method_entry_struct },
702+
700703
/// Equivalent of RUBY_VM_CHECK_INTS. Automatically inserted by the compiler before jumps and
701704
/// return instructions.
702705
CheckInterrupts { state: InsnId },
@@ -710,7 +713,7 @@ impl Insn {
710713
| Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. }
711714
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. }
712715
| Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. }
713-
| Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_)
716+
| Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) | Insn::CountUnoptimizedCFunc { .. }
714717
| Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } => false,
715718
_ => true,
716719
}
@@ -956,6 +959,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
956959
}
957960
Ok(())
958961
},
962+
Insn::CountUnoptimizedCFunc { .. } => write!(f, "CountUnoptimizedCFunc"),
959963
Insn::Snapshot { state } => write!(f, "Snapshot {}", state.print(self.ptr_map)),
960964
Insn::Defined { op_type, v, .. } => {
961965
// op_type (enum defined_type) printing logic from iseq.c.
@@ -1347,6 +1351,7 @@ impl Function {
13471351
| GetGlobal {..}
13481352
| GetLocal {..}
13491353
| SideExit {..}
1354+
| CountUnoptimizedCFunc {..}
13501355
| IncrCounter(_)) => result.clone(),
13511356
&Snapshot { state: FrameState { iseq, insn_idx, pc, ref stack, ref locals } } =>
13521357
Snapshot {
@@ -1495,7 +1500,7 @@ impl Function {
14951500
| Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::Throw { .. }
14961501
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. }
14971502
| Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } | Insn::IncrCounter(_)
1498-
| Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } =>
1503+
| Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::CountUnoptimizedCFunc { .. } =>
14991504
panic!("Cannot infer type of instruction with no output: {}", self.insns[insn.0]),
15001505
Insn::Const { val: Const::Value(val) } => Type::from_value(*val),
15011506
Insn::Const { val: Const::CBool(val) } => Type::from_cbool(*val),
@@ -2083,9 +2088,9 @@ impl Function {
20832088
self_type: Type,
20842089
send: Insn,
20852090
send_insn_id: InsnId,
2086-
) -> Result<(), ()> {
2091+
) -> Result<(), Option<*const rb_callable_method_entry_struct>> {
20872092
let Insn::SendWithoutBlock { mut recv, cd, mut args, state, .. } = send else {
2088-
return Err(());
2093+
return Err(None);
20892094
};
20902095

20912096
let call_info = unsafe { (*cd).ci };
@@ -2097,20 +2102,20 @@ impl Function {
20972102
(class, None)
20982103
} else {
20992104
let iseq_insn_idx = fun.frame_state(state).insn_idx;
2100-
let Some(recv_type) = fun.profiled_type_of_at(recv, iseq_insn_idx) else { return Err(()) };
2105+
let Some(recv_type) = fun.profiled_type_of_at(recv, iseq_insn_idx) else { return Err(None) };
21012106
(recv_type.class(), Some(recv_type))
21022107
};
21032108

21042109
// Do method lookup
2105-
let method = unsafe { rb_callable_method_entry(recv_class, method_id) };
2110+
let method: *const rb_callable_method_entry_struct = unsafe { rb_callable_method_entry(recv_class, method_id) };
21062111
if method.is_null() {
2107-
return Err(());
2112+
return Err(None);
21082113
}
21092114

21102115
// Filter for C methods
21112116
let def_type = unsafe { get_cme_def_type(method) };
21122117
if def_type != VM_METHOD_TYPE_CFUNC {
2113-
return Err(());
2118+
return Err(None);
21142119
}
21152120

21162121
// Find the `argc` (arity) of the C method, which describes the parameters it expects
@@ -2122,15 +2127,18 @@ impl Function {
21222127
//
21232128
// Bail on argc mismatch
21242129
if argc != cfunc_argc as u32 {
2125-
return Err(());
2130+
return Err(Some(method));
21262131
}
21272132

21282133
// Filter for a leaf and GC free function
21292134
use crate::cruby_methods::FnProperties;
2130-
let Some(FnProperties { leaf: true, no_gc: true, return_type, elidable }) =
2131-
ZJITState::get_method_annotations().get_cfunc_properties(method)
2132-
else {
2133-
return Err(());
2135+
let cfunc_properties = ZJITState::get_method_annotations().get_cfunc_properties(method);
2136+
2137+
let (return_type, elidable) = match cfunc_properties {
2138+
Some(FnProperties { leaf: true, no_gc: true, return_type, elidable }) => {
2139+
(return_type, elidable)
2140+
}
2141+
_ => return Err(Some(method))
21342142
};
21352143

21362144
let ci_flags = unsafe { vm_ci_flag(call_info) };
@@ -2147,13 +2155,15 @@ impl Function {
21472155
cfunc_args.append(&mut args);
21482156
let ccall = fun.push_insn(block, Insn::CCall { cfun, args: cfunc_args, name: method_id, return_type, elidable });
21492157
fun.make_equal_to(send_insn_id, ccall);
2150-
return Ok(());
2158+
Ok(())
2159+
} else {
2160+
Err(Some(method))
21512161
}
21522162
}
21532163
// Variadic method
21542164
-1 => {
21552165
if unsafe { rb_zjit_method_tracing_currently_enabled() } {
2156-
return Err(());
2166+
return Err(None);
21572167
}
21582168
// The method gets a pointer to the first argument
21592169
// func(int argc, VALUE *argv, VALUE recv)
@@ -2185,18 +2195,19 @@ impl Function {
21852195
});
21862196

21872197
fun.make_equal_to(send_insn_id, ccall);
2188-
return Ok(());
2198+
Ok(())
2199+
} else {
2200+
// Fall through for complex cases (splat, kwargs, etc.)
2201+
Err(Some(method))
21892202
}
2190-
// Fall through for complex cases (splat, kwargs, etc.)
21912203
}
21922204
-2 => {
21932205
// (self, args_ruby_array) parameter form
21942206
// Falling through for now
2207+
Err(Some(method))
21952208
}
21962209
_ => unreachable!("unknown cfunc kind: argc={argc}")
21972210
}
2198-
2199-
Err(())
22002211
}
22012212

22022213
for block in self.rpo() {
@@ -2205,8 +2216,12 @@ impl Function {
22052216
for insn_id in old_insns {
22062217
if let send @ Insn::SendWithoutBlock { recv, .. } = self.find(insn_id) {
22072218
let recv_type = self.type_of(recv);
2208-
if reduce_to_ccall(self, block, recv_type, send, insn_id).is_ok() {
2209-
continue;
2219+
match reduce_to_ccall(self, block, recv_type, send, insn_id) {
2220+
Ok(()) => continue,
2221+
Err(Some(cme)) => {
2222+
self.push_insn(block, Insn::CountUnoptimizedCFunc { cme });
2223+
}
2224+
_ => {}
22102225
}
22112226
}
22122227
self.push_insn_id(block, insn_id);
@@ -2349,7 +2364,8 @@ impl Function {
23492364
| &Insn::Param { .. }
23502365
| &Insn::GetLocal { .. }
23512366
| &Insn::PutSpecialObject { .. }
2352-
| &Insn::IncrCounter(_) =>
2367+
| &Insn::IncrCounter(_)
2368+
| &Insn::CountUnoptimizedCFunc { .. } =>
23532369
{}
23542370
&Insn::PatchPoint { state, .. }
23552371
| &Insn::CheckInterrupts { state }
@@ -8072,6 +8088,7 @@ mod opt_tests {
80728088
bb0(v0:BasicObject):
80738089
v4:Fixnum[1] = Const Value(1)
80748090
v5:Fixnum[0] = Const Value(0)
8091+
CountUnoptimizedCFunc
80758092
v7:BasicObject = SendWithoutBlock v4, :itself, v5
80768093
CheckInterrupts
80778094
Return v7
@@ -8709,6 +8726,7 @@ mod opt_tests {
87098726
fn test@<compiled>:2:
87108727
bb0(v0:BasicObject):
87118728
v5:HashExact = NewHash
8729+
CountUnoptimizedCFunc
87128730
v7:BasicObject = SendWithoutBlock v5, :dup
87138731
v9:BasicObject = SendWithoutBlock v7, :freeze
87148732
CheckInterrupts
@@ -8726,6 +8744,7 @@ mod opt_tests {
87268744
bb0(v0:BasicObject):
87278745
v5:HashExact = NewHash
87288746
v6:NilClass = Const Value(nil)
8747+
CountUnoptimizedCFunc
87298748
v8:BasicObject = SendWithoutBlock v5, :freeze, v6
87308749
CheckInterrupts
87318750
Return v8
@@ -8772,6 +8791,7 @@ mod opt_tests {
87728791
fn test@<compiled>:2:
87738792
bb0(v0:BasicObject):
87748793
v5:ArrayExact = NewArray
8794+
CountUnoptimizedCFunc
87758795
v7:BasicObject = SendWithoutBlock v5, :dup
87768796
v9:BasicObject = SendWithoutBlock v7, :freeze
87778797
CheckInterrupts
@@ -8789,6 +8809,7 @@ mod opt_tests {
87898809
bb0(v0:BasicObject):
87908810
v5:ArrayExact = NewArray
87918811
v6:NilClass = Const Value(nil)
8812+
CountUnoptimizedCFunc
87928813
v8:BasicObject = SendWithoutBlock v5, :freeze, v6
87938814
CheckInterrupts
87948815
Return v8
@@ -8836,6 +8857,7 @@ mod opt_tests {
88368857
bb0(v0:BasicObject):
88378858
v4:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
88388859
v6:StringExact = StringCopy v4
8860+
CountUnoptimizedCFunc
88398861
v8:BasicObject = SendWithoutBlock v6, :dup
88408862
v10:BasicObject = SendWithoutBlock v8, :freeze
88418863
CheckInterrupts
@@ -8854,6 +8876,7 @@ mod opt_tests {
88548876
v4:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
88558877
v6:StringExact = StringCopy v4
88568878
v7:NilClass = Const Value(nil)
8879+
CountUnoptimizedCFunc
88578880
v9:BasicObject = SendWithoutBlock v6, :freeze, v7
88588881
CheckInterrupts
88598882
Return v9
@@ -8901,6 +8924,7 @@ mod opt_tests {
89018924
bb0(v0:BasicObject):
89028925
v4:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
89038926
v6:StringExact = StringCopy v4
8927+
CountUnoptimizedCFunc
89048928
v8:BasicObject = SendWithoutBlock v6, :dup
89058929
v10:BasicObject = SendWithoutBlock v8, :-@
89068930
CheckInterrupts
@@ -9000,6 +9024,7 @@ mod opt_tests {
90009024
bb0(v0:BasicObject, v1:BasicObject):
90019025
v5:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
90029026
v17:BasicObject = GuardTypeNot v1, String
9027+
CountUnoptimizedCFunc
90039028
v18:BasicObject = SendWithoutBlock v1, :to_s
90049029
v9:String = AnyToString v1, str: v18
90059030
v11:StringExact = StringConcat v5, v9

zjit/src/stats.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
33
use std::time::Instant;
44
use std::sync::atomic::Ordering;
5+
use std::sync::{LazyLock, Mutex};
6+
use std::collections::HashMap;
57

68
#[cfg(feature = "stats_allocator")]
79
#[path = "../../jit/src/lib.rs"]
@@ -183,6 +185,29 @@ pub(crate) use incr_counter;
183185
/// The number of side exits from each YARV instruction
184186
pub type ExitCounters = [u64; VM_INSTRUCTION_SIZE as usize];
185187

188+
static SEND_FALLBACK_REASONS: LazyLock<Mutex<HashMap<String, u64>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
189+
190+
#[unsafe(no_mangle)]
191+
pub extern "C" fn rb_zjit_count_unoptimized_cfunc(cme: *const rb_callable_method_entry_struct) {
192+
unsafe extern "C" {
193+
fn rb_class2name(klass: VALUE) -> *const std::ffi::c_char;
194+
fn rb_id2name(id: ID) -> *const std::ffi::c_char;
195+
}
196+
197+
// Get class name and method name directly from cme
198+
let defined_class = unsafe { (*cme).defined_class };
199+
let called_id = unsafe { (*cme).called_id };
200+
201+
// Convert to C strings and then to Rust strings
202+
let class_str = unsafe { cstr_to_rust_string(rb_class2name(defined_class)) }.unwrap_or_else(|| "UnknownOwner".to_string());
203+
let method_str = unsafe { cstr_to_rust_string(rb_id2name(called_id)) }.unwrap_or_else(|| "UnknownMethod".to_string());
204+
205+
let reason_str = format!("{}#{}", class_str, method_str);
206+
207+
let mut map = SEND_FALLBACK_REASONS.lock().unwrap();
208+
*map.entry(reason_str).or_insert(0) += 1;
209+
}
210+
186211
/// Return a raw pointer to the exit counter for a given YARV opcode
187212
pub fn exit_counter_ptr_for_opcode(opcode: u32) -> *mut u64 {
188213
let exit_counters = ZJITState::get_exit_counters();
@@ -370,6 +395,13 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) ->
370395
set_stat_f64!(hash, "ratio_in_zjit", 100.0 * zjit_insn_count as f64 / total_insn_count as f64);
371396
}
372397

398+
// Set send fallback reasons
399+
let fallback_reasons = SEND_FALLBACK_REASONS.lock().unwrap();
400+
for (reason, count) in fallback_reasons.iter() {
401+
let key_string = "not_optimized_cfuncs".to_owned() + reason;
402+
set_stat_usize!(hash, &key_string, *count);
403+
}
404+
373405
hash
374406
}
375407

0 commit comments

Comments
 (0)