Skip to content

Commit 3b6fd0d

Browse files
committed
ZJIT: Add stats for cfuncs that are not optimized
1 parent baec95c commit 3b6fd0d

4 files changed

Lines changed: 82 additions & 18 deletions

File tree

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
@@ -419,6 +419,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
419419
Insn::GetSpecialSymbol { symbol_type, state: _ } => gen_getspecial_symbol(asm, *symbol_type),
420420
Insn::GetSpecialNumber { nth, state } => gen_getspecial_number(asm, *nth, &function.frame_state(*state)),
421421
&Insn::IncrCounter(counter) => no_output!(gen_incr_counter(asm, counter)),
422+
Insn::CountUnoptimizedCFunc { signature, counter_ptr } => no_output!(gen_count_unoptimized_cfunc(asm, signature, *counter_ptr)),
422423
Insn::ObjToString { val, cd, state, .. } => gen_objtostring(jit, asm, opnd!(val), *cd, &function.frame_state(*state)),
423424
&Insn::CheckInterrupts { state } => no_output!(gen_check_interrupts(jit, asm, &function.frame_state(state))),
424425
&Insn::HashDup { val, state } => { gen_hash_dup(asm, opnd!(val), &function.frame_state(state)) },
@@ -1528,6 +1529,17 @@ fn gen_guard_bit_equals(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd,
15281529
val
15291530
}
15301531

1532+
/// Generate code that records unoptimized C functions if --zjit-stats is enabled
1533+
fn gen_count_unoptimized_cfunc(asm: &mut Assembler, signature: &str, counter_ptr: *mut u64) {
1534+
if get_option!(stats) {
1535+
unsafe extern "C" {
1536+
fn rb_zjit_count_unoptimized_cfunc(counter_ptr: *mut u64);
1537+
}
1538+
asm_comment!(asm, "count unoptimized cfunc: {}", signature);
1539+
asm_ccall!(asm, rb_zjit_count_unoptimized_cfunc, Opnd::const_ptr(counter_ptr as *const u8));
1540+
}
1541+
}
1542+
15311543
/// Generate code that increments a counter if --zjit-stats
15321544
fn gen_incr_counter(asm: &mut Assembler, counter: Counter) {
15331545
if get_option!(stats) {

zjit/src/hir.rs

Lines changed: 45 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use std::{
1414
use crate::hir_type::{Type, types};
1515
use crate::bitset::BitSet;
1616
use crate::profile::{TypeDistributionSummary, ProfiledType};
17-
use crate::stats::Counter;
17+
use crate::stats::{Counter, get_or_create_unoptimized_cfunc_counter_ptr};
1818

1919
/// An index of an [`Insn`] in a [`Function`]. This is a popular
2020
/// type since this effectively acts as a pointer to an [`Insn`].
@@ -711,6 +711,9 @@ pub enum Insn {
711711
/// Increment a counter in ZJIT stats
712712
IncrCounter(Counter),
713713

714+
/// Increment a counter in ZJIT stats for the given unoptimized C function
715+
CountUnoptimizedCFunc { signature: String, counter_ptr: *mut u64 },
716+
714717
/// Equivalent of RUBY_VM_CHECK_INTS. Automatically inserted by the compiler before jumps and
715718
/// return instructions.
716719
CheckInterrupts { state: InsnId },
@@ -724,7 +727,7 @@ impl Insn {
724727
| Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::EntryPoint { .. } | Insn::Return { .. }
725728
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. }
726729
| Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. }
727-
| Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_)
730+
| Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) | Insn::CountUnoptimizedCFunc { .. }
728731
| Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } => false,
729732
_ => true,
730733
}
@@ -978,6 +981,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
978981
}
979982
Ok(())
980983
},
984+
Insn::CountUnoptimizedCFunc { signature, .. } => write!(f, "CountUnoptimizedCFunc {}", signature),
981985
Insn::Snapshot { state } => write!(f, "Snapshot {}", state.print(self.ptr_map)),
982986
Insn::Defined { op_type, v, .. } => {
983987
// op_type (enum defined_type) printing logic from iseq.c.
@@ -1385,6 +1389,7 @@ impl Function {
13851389
| EntryPoint {..}
13861390
| LoadPC
13871391
| LoadSelf
1392+
| CountUnoptimizedCFunc {..}
13881393
| IncrCounter(_)) => result.clone(),
13891394
&Snapshot { state: FrameState { iseq, insn_idx, pc, ref stack, ref locals } } =>
13901395
Snapshot {
@@ -1534,7 +1539,7 @@ impl Function {
15341539
| Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::Throw { .. }
15351540
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. }
15361541
| Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } | Insn::IncrCounter(_)
1537-
| Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } =>
1542+
| Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::CountUnoptimizedCFunc { .. } =>
15381543
panic!("Cannot infer type of instruction with no output: {}", self.insns[insn.0]),
15391544
Insn::Const { val: Const::Value(val) } => Type::from_value(*val),
15401545
Insn::Const { val: Const::CBool(val) } => Type::from_cbool(*val),
@@ -2154,9 +2159,9 @@ impl Function {
21542159
self_type: Type,
21552160
send: Insn,
21562161
send_insn_id: InsnId,
2157-
) -> Result<(), ()> {
2162+
) -> Result<(), Option<*const rb_callable_method_entry_struct>> {
21582163
let Insn::SendWithoutBlock { mut recv, cd, mut args, state, .. } = send else {
2159-
return Err(());
2164+
return Err(None);
21602165
};
21612166

21622167
let call_info = unsafe { (*cd).ci };
@@ -2168,20 +2173,20 @@ impl Function {
21682173
(class, None)
21692174
} else {
21702175
let iseq_insn_idx = fun.frame_state(state).insn_idx;
2171-
let Some(recv_type) = fun.profiled_type_of_at(recv, iseq_insn_idx) else { return Err(()) };
2176+
let Some(recv_type) = fun.profiled_type_of_at(recv, iseq_insn_idx) else { return Err(None) };
21722177
(recv_type.class(), Some(recv_type))
21732178
};
21742179

21752180
// Do method lookup
2176-
let method = unsafe { rb_callable_method_entry(recv_class, method_id) };
2181+
let method: *const rb_callable_method_entry_struct = unsafe { rb_callable_method_entry(recv_class, method_id) };
21772182
if method.is_null() {
2178-
return Err(());
2183+
return Err(None);
21792184
}
21802185

21812186
// Filter for C methods
21822187
let def_type = unsafe { get_cme_def_type(method) };
21832188
if def_type != VM_METHOD_TYPE_CFUNC {
2184-
return Err(());
2189+
return Err(None);
21852190
}
21862191

21872192
// Find the `argc` (arity) of the C method, which describes the parameters it expects
@@ -2193,15 +2198,15 @@ impl Function {
21932198
//
21942199
// Bail on argc mismatch
21952200
if argc != cfunc_argc as u32 {
2196-
return Err(());
2201+
return Err(Some(method));
21972202
}
21982203

21992204
// Filter for a leaf and GC free function
22002205
use crate::cruby_methods::FnProperties;
22012206
let Some(FnProperties { leaf: true, no_gc: true, return_type, elidable }) =
22022207
ZJITState::get_method_annotations().get_cfunc_properties(method)
22032208
else {
2204-
return Err(());
2209+
return Err(Some(method));
22052210
};
22062211

22072212
let ci_flags = unsafe { vm_ci_flag(call_info) };
@@ -2218,13 +2223,13 @@ impl Function {
22182223
cfunc_args.append(&mut args);
22192224
let ccall = fun.push_insn(block, Insn::CCall { cfun, args: cfunc_args, name: method_id, return_type, elidable });
22202225
fun.make_equal_to(send_insn_id, ccall);
2221-
return Ok(());
2226+
return Ok(())
22222227
}
22232228
}
22242229
// Variadic method
22252230
-1 => {
22262231
if unsafe { rb_zjit_method_tracing_currently_enabled() } {
2227-
return Err(());
2232+
return Err(None);
22282233
}
22292234
// The method gets a pointer to the first argument
22302235
// func(int argc, VALUE *argv, VALUE recv)
@@ -2256,8 +2261,9 @@ impl Function {
22562261
});
22572262

22582263
fun.make_equal_to(send_insn_id, ccall);
2259-
return Ok(());
2264+
return Ok(())
22602265
}
2266+
22612267
// Fall through for complex cases (splat, kwargs, etc.)
22622268
}
22632269
-2 => {
@@ -2267,7 +2273,7 @@ impl Function {
22672273
_ => unreachable!("unknown cfunc kind: argc={argc}")
22682274
}
22692275

2270-
Err(())
2276+
Err(Some(method))
22712277
}
22722278

22732279
for block in self.rpo() {
@@ -2276,8 +2282,19 @@ impl Function {
22762282
for insn_id in old_insns {
22772283
if let send @ Insn::SendWithoutBlock { recv, .. } = self.find(insn_id) {
22782284
let recv_type = self.type_of(recv);
2279-
if reduce_to_ccall(self, block, recv_type, send, insn_id).is_ok() {
2280-
continue;
2285+
match reduce_to_ccall(self, block, recv_type, send, insn_id) {
2286+
Ok(()) => continue,
2287+
Err(Some(cme)) => {
2288+
let owner = unsafe { (*cme).owner };
2289+
let called_id = unsafe { (*cme).called_id };
2290+
let class_name = get_class_name(owner);
2291+
let method_name = called_id.contents_lossy();
2292+
let signature = format!("{}#{}", class_name, method_name);
2293+
let counter_ptr = get_or_create_unoptimized_cfunc_counter_ptr(signature.clone());
2294+
2295+
self.push_insn(block, Insn::CountUnoptimizedCFunc { signature, counter_ptr });
2296+
}
2297+
_ => {}
22812298
}
22822299
}
22832300
self.push_insn_id(block, insn_id);
@@ -2423,7 +2440,8 @@ impl Function {
24232440
| &Insn::LoadSelf
24242441
| &Insn::GetLocal { .. }
24252442
| &Insn::PutSpecialObject { .. }
2426-
| &Insn::IncrCounter(_) =>
2443+
| &Insn::IncrCounter(_)
2444+
| &Insn::CountUnoptimizedCFunc { .. } =>
24272445
{}
24282446
&Insn::PatchPoint { state, .. }
24292447
| &Insn::CheckInterrupts { state }
@@ -9841,6 +9859,7 @@ mod opt_tests {
98419859
bb2(v6:BasicObject):
98429860
v10:Fixnum[1] = Const Value(1)
98439861
v11:Fixnum[0] = Const Value(0)
9862+
CountUnoptimizedCFunc Kernel#itself
98449863
v13:BasicObject = SendWithoutBlock v10, :itself, v11
98459864
CheckInterrupts
98469865
Return v13
@@ -10720,6 +10739,7 @@ mod opt_tests {
1072010739
Jump bb2(v4)
1072110740
bb2(v6:BasicObject):
1072210741
v11:HashExact = NewHash
10742+
CountUnoptimizedCFunc Kernel#dup
1072310743
v13:BasicObject = SendWithoutBlock v11, :dup
1072410744
v15:BasicObject = SendWithoutBlock v13, :freeze
1072510745
CheckInterrupts
@@ -10744,6 +10764,7 @@ mod opt_tests {
1074410764
bb2(v6:BasicObject):
1074510765
v11:HashExact = NewHash
1074610766
v12:NilClass = Const Value(nil)
10767+
CountUnoptimizedCFunc Hash#freeze
1074710768
v14:BasicObject = SendWithoutBlock v11, :freeze, v12
1074810769
CheckInterrupts
1074910770
Return v14
@@ -10811,6 +10832,7 @@ mod opt_tests {
1081110832
Jump bb2(v4)
1081210833
bb2(v6:BasicObject):
1081310834
v11:ArrayExact = NewArray
10835+
CountUnoptimizedCFunc Kernel#dup
1081410836
v13:BasicObject = SendWithoutBlock v11, :dup
1081510837
v15:BasicObject = SendWithoutBlock v13, :freeze
1081610838
CheckInterrupts
@@ -10835,6 +10857,7 @@ mod opt_tests {
1083510857
bb2(v6:BasicObject):
1083610858
v11:ArrayExact = NewArray
1083710859
v12:NilClass = Const Value(nil)
10860+
CountUnoptimizedCFunc Array#freeze
1083810861
v14:BasicObject = SendWithoutBlock v11, :freeze, v12
1083910862
CheckInterrupts
1084010863
Return v14
@@ -10903,6 +10926,7 @@ mod opt_tests {
1090310926
bb2(v6:BasicObject):
1090410927
v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
1090510928
v12:StringExact = StringCopy v10
10929+
CountUnoptimizedCFunc String#dup
1090610930
v14:BasicObject = SendWithoutBlock v12, :dup
1090710931
v16:BasicObject = SendWithoutBlock v14, :freeze
1090810932
CheckInterrupts
@@ -10928,6 +10952,7 @@ mod opt_tests {
1092810952
v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
1092910953
v12:StringExact = StringCopy v10
1093010954
v13:NilClass = Const Value(nil)
10955+
CountUnoptimizedCFunc String#freeze
1093110956
v15:BasicObject = SendWithoutBlock v12, :freeze, v13
1093210957
CheckInterrupts
1093310958
Return v15
@@ -10996,6 +11021,7 @@ mod opt_tests {
1099611021
bb2(v6:BasicObject):
1099711022
v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
1099811023
v12:StringExact = StringCopy v10
11024+
CountUnoptimizedCFunc String#dup
1099911025
v14:BasicObject = SendWithoutBlock v12, :dup
1100011026
v16:BasicObject = SendWithoutBlock v14, :-@
1100111027
CheckInterrupts
@@ -11133,6 +11159,7 @@ mod opt_tests {
1113311159
bb2(v8:BasicObject, v9:BasicObject):
1113411160
v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
1113511161
v25:BasicObject = GuardTypeNot v9, String
11162+
CountUnoptimizedCFunc Array#to_s
1113611163
v26:BasicObject = SendWithoutBlock v9, :to_s
1113711164
v17:String = AnyToString v9, str: v26
1113811165
v19:StringExact = StringConcat v13, v17

zjit/src/stats.rs

Lines changed: 24 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"]
@@ -188,6 +190,21 @@ pub(crate) use incr_counter;
188190
/// The number of side exits from each YARV instruction
189191
pub type ExitCounters = [u64; VM_INSTRUCTION_SIZE as usize];
190192

193+
/// Store signature ("Klass#method") to counter pointer mappings for unoptimized cfuncs
194+
pub static UNOPTIMIZED_CFUNC_COUNTER_POINTERS: LazyLock<Mutex<HashMap<String, Box<u64>>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
195+
196+
/// Get or create a counter pointer for the given signature
197+
pub fn get_or_create_unoptimized_cfunc_counter_ptr(signature: String) -> *mut u64 {
198+
let mut map = UNOPTIMIZED_CFUNC_COUNTER_POINTERS.lock().unwrap();
199+
let counter = map.entry(signature).or_insert_with(|| Box::new(0));
200+
&mut **counter as *mut u64
201+
}
202+
203+
#[unsafe(no_mangle)]
204+
pub extern "C" fn rb_zjit_count_unoptimized_cfunc(counter_ptr: *mut u64) {
205+
unsafe { *counter_ptr += 1 }
206+
}
207+
191208
/// Return a raw pointer to the exit counter for a given YARV opcode
192209
pub fn exit_counter_ptr_for_opcode(opcode: u32) -> *mut u64 {
193210
let exit_counters = ZJITState::get_exit_counters();
@@ -384,6 +401,13 @@ pub extern "C" fn rb_zjit_stats(_ec: EcPtr, _self: VALUE, target_key: VALUE) ->
384401
set_stat_f64!(hash, "ratio_in_zjit", 100.0 * zjit_insn_count as f64 / total_insn_count as f64);
385402
}
386403

404+
// Set unoptimized cfunc counters
405+
let unoptimized_cfuncs = UNOPTIMIZED_CFUNC_COUNTER_POINTERS.lock().unwrap();
406+
for (signature, counter) in unoptimized_cfuncs.iter() {
407+
let key_string = format!("not_optimized_cfuncs_{}", signature);
408+
set_stat_usize!(hash, &key_string, **counter);
409+
}
410+
387411
hash
388412
}
389413

0 commit comments

Comments
 (0)