Skip to content

Commit c12e6d5

Browse files
committed
ZJIT: Add stats for cfuncs that are not optimized
1 parent 922f0ef commit c12e6d5

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
@@ -420,6 +420,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
420420
Insn::GetSpecialSymbol { symbol_type, state: _ } => gen_getspecial_symbol(asm, *symbol_type),
421421
Insn::GetSpecialNumber { nth, state } => gen_getspecial_number(asm, *nth, &function.frame_state(*state)),
422422
&Insn::IncrCounter(counter) => no_output!(gen_incr_counter(asm, counter)),
423+
Insn::CountUnoptimizedCFunc { signature, counter_ptr } => no_output!(gen_count_unoptimized_cfunc(asm, signature, *counter_ptr)),
423424
Insn::ObjToString { val, cd, state, .. } => gen_objtostring(jit, asm, opnd!(val), *cd, &function.frame_state(*state)),
424425
&Insn::CheckInterrupts { state } => no_output!(gen_check_interrupts(jit, asm, &function.frame_state(state))),
425426
&Insn::HashDup { val, state } => { gen_hash_dup(asm, opnd!(val), &function.frame_state(state)) },
@@ -1584,6 +1585,17 @@ fn gen_guard_bit_equals(jit: &mut JITState, asm: &mut Assembler, val: lir::Opnd,
15841585
val
15851586
}
15861587

1588+
/// Generate code that records unoptimized C functions if --zjit-stats is enabled
1589+
fn gen_count_unoptimized_cfunc(asm: &mut Assembler, signature: &str, counter_ptr: *mut u64) {
1590+
if get_option!(stats) {
1591+
unsafe extern "C" {
1592+
fn rb_zjit_count_unoptimized_cfunc(counter_ptr: *mut u64);
1593+
}
1594+
asm_comment!(asm, "count unoptimized cfunc: {}", signature);
1595+
asm_ccall!(asm, rb_zjit_count_unoptimized_cfunc, Opnd::const_ptr(counter_ptr as *const u8));
1596+
}
1597+
}
1598+
15871599
/// Generate code that increments a counter if --zjit-stats
15881600
fn gen_incr_counter(asm: &mut Assembler, counter: Counter) {
15891601
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`].
@@ -707,6 +707,9 @@ pub enum Insn {
707707
/// Increment a counter in ZJIT stats
708708
IncrCounter(Counter),
709709

710+
/// Increment a counter in ZJIT stats for the given unoptimized C function
711+
CountUnoptimizedCFunc { signature: String, counter_ptr: *mut u64 },
712+
710713
/// Equivalent of RUBY_VM_CHECK_INTS. Automatically inserted by the compiler before jumps and
711714
/// return instructions.
712715
CheckInterrupts { state: InsnId },
@@ -720,7 +723,7 @@ impl Insn {
720723
| Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::EntryPoint { .. } | Insn::Return { .. }
721724
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. }
722725
| Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetGlobal { .. }
723-
| Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_)
726+
| Insn::SetLocal { .. } | Insn::Throw { .. } | Insn::IncrCounter(_) | Insn::CountUnoptimizedCFunc { .. }
724727
| Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } => false,
725728
_ => true,
726729
}
@@ -972,6 +975,7 @@ impl<'a> std::fmt::Display for InsnPrinter<'a> {
972975
}
973976
Ok(())
974977
},
978+
Insn::CountUnoptimizedCFunc { signature, .. } => write!(f, "CountUnoptimizedCFunc {}", signature),
975979
Insn::Snapshot { state } => write!(f, "Snapshot {}", state.print(self.ptr_map)),
976980
Insn::Defined { op_type, v, .. } => {
977981
// op_type (enum defined_type) printing logic from iseq.c.
@@ -1376,6 +1380,7 @@ impl Function {
13761380
| SideExit {..}
13771381
| EntryPoint {..}
13781382
| LoadPC
1383+
| CountUnoptimizedCFunc {..}
13791384
| IncrCounter(_)) => result.clone(),
13801385
&Snapshot { state: FrameState { iseq, insn_idx, pc, ref stack, ref locals } } =>
13811386
Snapshot {
@@ -1525,7 +1530,7 @@ impl Function {
15251530
| Insn::IfTrue { .. } | Insn::IfFalse { .. } | Insn::Return { .. } | Insn::Throw { .. }
15261531
| Insn::PatchPoint { .. } | Insn::SetIvar { .. } | Insn::ArrayExtend { .. }
15271532
| Insn::ArrayPush { .. } | Insn::SideExit { .. } | Insn::SetLocal { .. } | Insn::IncrCounter(_)
1528-
| Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } =>
1533+
| Insn::CheckInterrupts { .. } | Insn::GuardBlockParamProxy { .. } | Insn::CountUnoptimizedCFunc { .. } =>
15291534
panic!("Cannot infer type of instruction with no output: {}", self.insns[insn.0]),
15301535
Insn::Const { val: Const::Value(val) } => Type::from_value(*val),
15311536
Insn::Const { val: Const::CBool(val) } => Type::from_cbool(*val),
@@ -2149,9 +2154,9 @@ impl Function {
21492154
self_type: Type,
21502155
send: Insn,
21512156
send_insn_id: InsnId,
2152-
) -> Result<(), ()> {
2157+
) -> Result<(), Option<*const rb_callable_method_entry_struct>> {
21532158
let Insn::SendWithoutBlock { mut recv, cd, mut args, state, .. } = send else {
2154-
return Err(());
2159+
return Err(None);
21552160
};
21562161

21572162
let call_info = unsafe { (*cd).ci };
@@ -2163,20 +2168,20 @@ impl Function {
21632168
(class, None)
21642169
} else {
21652170
let iseq_insn_idx = fun.frame_state(state).insn_idx;
2166-
let Some(recv_type) = fun.profiled_type_of_at(recv, iseq_insn_idx) else { return Err(()) };
2171+
let Some(recv_type) = fun.profiled_type_of_at(recv, iseq_insn_idx) else { return Err(None) };
21672172
(recv_type.class(), Some(recv_type))
21682173
};
21692174

21702175
// Do method lookup
2171-
let method = unsafe { rb_callable_method_entry(recv_class, method_id) };
2176+
let method: *const rb_callable_method_entry_struct = unsafe { rb_callable_method_entry(recv_class, method_id) };
21722177
if method.is_null() {
2173-
return Err(());
2178+
return Err(None);
21742179
}
21752180

21762181
// Filter for C methods
21772182
let def_type = unsafe { get_cme_def_type(method) };
21782183
if def_type != VM_METHOD_TYPE_CFUNC {
2179-
return Err(());
2184+
return Err(None);
21802185
}
21812186

21822187
// Find the `argc` (arity) of the C method, which describes the parameters it expects
@@ -2188,15 +2193,15 @@ impl Function {
21882193
//
21892194
// Bail on argc mismatch
21902195
if argc != cfunc_argc as u32 {
2191-
return Err(());
2196+
return Err(Some(method));
21922197
}
21932198

21942199
// Filter for a leaf and GC free function
21952200
use crate::cruby_methods::FnProperties;
21962201
let Some(FnProperties { leaf: true, no_gc: true, return_type, elidable }) =
21972202
ZJITState::get_method_annotations().get_cfunc_properties(method)
21982203
else {
2199-
return Err(());
2204+
return Err(Some(method));
22002205
};
22012206

22022207
let ci_flags = unsafe { vm_ci_flag(call_info) };
@@ -2213,13 +2218,13 @@ impl Function {
22132218
cfunc_args.append(&mut args);
22142219
let ccall = fun.push_insn(block, Insn::CCall { cfun, args: cfunc_args, name: method_id, return_type, elidable });
22152220
fun.make_equal_to(send_insn_id, ccall);
2216-
return Ok(());
2221+
return Ok(())
22172222
}
22182223
}
22192224
// Variadic method
22202225
-1 => {
22212226
if unsafe { rb_zjit_method_tracing_currently_enabled() } {
2222-
return Err(());
2227+
return Err(None);
22232228
}
22242229
// The method gets a pointer to the first argument
22252230
// func(int argc, VALUE *argv, VALUE recv)
@@ -2251,8 +2256,9 @@ impl Function {
22512256
});
22522257

22532258
fun.make_equal_to(send_insn_id, ccall);
2254-
return Ok(());
2259+
return Ok(())
22552260
}
2261+
22562262
// Fall through for complex cases (splat, kwargs, etc.)
22572263
}
22582264
-2 => {
@@ -2262,7 +2268,7 @@ impl Function {
22622268
_ => unreachable!("unknown cfunc kind: argc={argc}")
22632269
}
22642270

2265-
Err(())
2271+
Err(Some(method))
22662272
}
22672273

22682274
for block in self.rpo() {
@@ -2271,8 +2277,19 @@ impl Function {
22712277
for insn_id in old_insns {
22722278
if let send @ Insn::SendWithoutBlock { recv, .. } = self.find(insn_id) {
22732279
let recv_type = self.type_of(recv);
2274-
if reduce_to_ccall(self, block, recv_type, send, insn_id).is_ok() {
2275-
continue;
2280+
match reduce_to_ccall(self, block, recv_type, send, insn_id) {
2281+
Ok(()) => continue,
2282+
Err(Some(cme)) => {
2283+
let owner = unsafe { (*cme).owner };
2284+
let called_id = unsafe { (*cme).called_id };
2285+
let class_name = get_class_name(owner);
2286+
let method_name = called_id.contents_lossy();
2287+
let signature = format!("{}#{}", class_name, method_name);
2288+
let counter_ptr = get_or_create_unoptimized_cfunc_counter_ptr(signature.clone());
2289+
2290+
self.push_insn(block, Insn::CountUnoptimizedCFunc { signature, counter_ptr });
2291+
}
2292+
_ => {}
22762293
}
22772294
}
22782295
self.push_insn_id(block, insn_id);
@@ -2417,7 +2434,8 @@ impl Function {
24172434
| &Insn::LoadPC { .. }
24182435
| &Insn::GetLocal { .. }
24192436
| &Insn::PutSpecialObject { .. }
2420-
| &Insn::IncrCounter(_) =>
2437+
| &Insn::IncrCounter(_)
2438+
| &Insn::CountUnoptimizedCFunc { .. } =>
24212439
{}
24222440
&Insn::PatchPoint { state, .. }
24232441
| &Insn::CheckInterrupts { state }
@@ -9484,6 +9502,7 @@ mod opt_tests {
94849502
bb2(v6:BasicObject):
94859503
v10:Fixnum[1] = Const Value(1)
94869504
v11:Fixnum[0] = Const Value(0)
9505+
CountUnoptimizedCFunc Kernel#itself
94879506
v13:BasicObject = SendWithoutBlock v10, :itself, v11
94889507
CheckInterrupts
94899508
Return v13
@@ -10322,6 +10341,7 @@ mod opt_tests {
1032210341
Jump bb2(v4)
1032310342
bb2(v6:BasicObject):
1032410343
v11:HashExact = NewHash
10344+
CountUnoptimizedCFunc Kernel#dup
1032510345
v13:BasicObject = SendWithoutBlock v11, :dup
1032610346
v15:BasicObject = SendWithoutBlock v13, :freeze
1032710347
CheckInterrupts
@@ -10345,6 +10365,7 @@ mod opt_tests {
1034510365
bb2(v6:BasicObject):
1034610366
v11:HashExact = NewHash
1034710367
v12:NilClass = Const Value(nil)
10368+
CountUnoptimizedCFunc Hash#freeze
1034810369
v14:BasicObject = SendWithoutBlock v11, :freeze, v12
1034910370
CheckInterrupts
1035010371
Return v14
@@ -10409,6 +10430,7 @@ mod opt_tests {
1040910430
Jump bb2(v4)
1041010431
bb2(v6:BasicObject):
1041110432
v11:ArrayExact = NewArray
10433+
CountUnoptimizedCFunc Kernel#dup
1041210434
v13:BasicObject = SendWithoutBlock v11, :dup
1041310435
v15:BasicObject = SendWithoutBlock v13, :freeze
1041410436
CheckInterrupts
@@ -10432,6 +10454,7 @@ mod opt_tests {
1043210454
bb2(v6:BasicObject):
1043310455
v11:ArrayExact = NewArray
1043410456
v12:NilClass = Const Value(nil)
10457+
CountUnoptimizedCFunc Array#freeze
1043510458
v14:BasicObject = SendWithoutBlock v11, :freeze, v12
1043610459
CheckInterrupts
1043710460
Return v14
@@ -10497,6 +10520,7 @@ mod opt_tests {
1049710520
bb2(v6:BasicObject):
1049810521
v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
1049910522
v12:StringExact = StringCopy v10
10523+
CountUnoptimizedCFunc String#dup
1050010524
v14:BasicObject = SendWithoutBlock v12, :dup
1050110525
v16:BasicObject = SendWithoutBlock v14, :freeze
1050210526
CheckInterrupts
@@ -10521,6 +10545,7 @@ mod opt_tests {
1052110545
v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
1052210546
v12:StringExact = StringCopy v10
1052310547
v13:NilClass = Const Value(nil)
10548+
CountUnoptimizedCFunc String#freeze
1052410549
v15:BasicObject = SendWithoutBlock v12, :freeze, v13
1052510550
CheckInterrupts
1052610551
Return v15
@@ -10586,6 +10611,7 @@ mod opt_tests {
1058610611
bb2(v6:BasicObject):
1058710612
v10:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
1058810613
v12:StringExact = StringCopy v10
10614+
CountUnoptimizedCFunc String#dup
1058910615
v14:BasicObject = SendWithoutBlock v12, :dup
1059010616
v16:BasicObject = SendWithoutBlock v14, :-@
1059110617
CheckInterrupts
@@ -10715,6 +10741,7 @@ mod opt_tests {
1071510741
bb2(v8:BasicObject, v9:BasicObject):
1071610742
v13:StringExact[VALUE(0x1000)] = Const Value(VALUE(0x1000))
1071710743
v25:BasicObject = GuardTypeNot v9, String
10744+
CountUnoptimizedCFunc Array#to_s
1071810745
v26:BasicObject = SendWithoutBlock v9, :to_s
1071910746
v17:String = AnyToString v9, str: v26
1072010747
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)