Skip to content

Commit 980ebb0

Browse files
authored
ZJIT: Add compilation failure stats (ruby#14310)
1 parent 221c76b commit 980ebb0

4 files changed

Lines changed: 126 additions & 72 deletions

File tree

zjit.rb

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,19 +39,31 @@ def stats(key = nil)
3939

4040
# Get the summary of ZJIT statistics as a String
4141
def stats_string
42-
buf = +''
42+
buf = +"***ZJIT: Printing ZJIT statistics on exit***\n"
4343
stats = self.stats
4444

45-
[
45+
print_counters_with_prefix(prefix: 'failed_', prompt: 'compilation failure reasons', buf:, stats:)
46+
print_counters([
47+
:compiled_iseq_count,
48+
:compilation_failure,
49+
4650
:compile_time_ns,
4751
:profile_time_ns,
4852
:gc_time_ns,
4953
:invalidation_time_ns,
54+
5055
:total_insns_count,
5156
:vm_insns_count,
5257
:zjit_insns_count,
5358
:ratio_in_zjit,
54-
].each do |key|
59+
], buf:, stats:)
60+
61+
buf
62+
end
63+
64+
def print_counters(keys, buf:, stats:)
65+
left_pad = keys.map(&:size).max + 1
66+
keys.each do |key|
5567
# Some stats like vm_insns_count and ratio_in_zjit are not supported on the release build
5668
next unless stats.key?(key)
5769
value = stats[key]
@@ -66,9 +78,16 @@ def stats_string
6678
value = number_with_delimiter(value)
6779
end
6880

69-
buf << "#{'%-18s' % "#{key}:"} #{value}\n"
81+
buf << "#{"%-#{left_pad}s" % "#{key}:"} #{value}\n"
82+
end
83+
end
84+
85+
def print_counters_with_prefix(buf:, stats:, prefix:, prompt:)
86+
keys = stats.keys.select { |key| key.start_with?(prefix) && stats[key] > 0 }
87+
unless keys.empty?
88+
buf << "#{prompt}:\n"
89+
print_counters(keys, buf:, stats:)
7090
end
71-
buf
7291
end
7392

7493
# Assert that any future ZJIT compilation will return a function pointer

zjit/src/codegen.rs

Lines changed: 58 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@ use std::slice;
66
use crate::asm::Label;
77
use crate::backend::current::{Reg, ALLOC_REGS};
88
use crate::invariants::{track_bop_assumption, track_cme_assumption, track_single_ractor_assumption, track_stable_constant_names_assumption};
9-
use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqStatus};
9+
use crate::gc::{append_gc_offsets, get_or_create_iseq_payload, get_or_create_iseq_payload_ptr, IseqPayload, IseqStatus};
1010
use crate::state::ZJITState;
11+
use crate::stats::incr_counter;
1112
use crate::stats::{counter_ptr, with_time_stat, Counter, Counter::compile_time_ns};
1213
use crate::{asm::CodeBlock, cruby::*, options::debug, virtualmem::CodePtr};
1314
use crate::backend::lir::{self, asm_comment, asm_ccall, Assembler, Opnd, Target, CFP, C_ARG_OPNDS, C_RET_OPND, EC, NATIVE_STACK_PTR, NATIVE_BASE_PTR, SCRATCH_OPND, SP};
@@ -112,11 +113,14 @@ fn gen_iseq_entry_point(iseq: IseqPtr) -> *const u8 {
112113
/// Compile an entry point for a given ISEQ
113114
fn gen_iseq_entry_point_body(cb: &mut CodeBlock, iseq: IseqPtr) -> Option<CodePtr> {
114115
// Compile ISEQ into High-level IR
115-
let function = compile_iseq(iseq)?;
116+
let Some(function) = compile_iseq(iseq) else {
117+
incr_counter!(compilation_failure);
118+
return None;
119+
};
116120

117121
// Compile the High-level IR
118-
let Some((start_ptr, gc_offsets, jit)) = gen_function(cb, iseq, &function) else {
119-
debug!("Failed to compile iseq: gen_function failed: {}", iseq_get_location(iseq, 0));
122+
let Some(start_ptr) = gen_iseq(cb, iseq, Some(&function)) else {
123+
debug!("Failed to compile iseq: gen_iseq failed: {}", iseq_get_location(iseq, 0));
120124
return None;
121125
};
122126

@@ -126,17 +130,6 @@ fn gen_iseq_entry_point_body(cb: &mut CodeBlock, iseq: IseqPtr) -> Option<CodePt
126130
return None;
127131
};
128132

129-
// Stub callee ISEQs for JIT-to-JIT calls
130-
for iseq_call in jit.iseq_calls.iter() {
131-
gen_iseq_call(cb, iseq, iseq_call)?;
132-
}
133-
134-
// Remember the block address to reuse it later
135-
let payload = get_or_create_iseq_payload(iseq);
136-
payload.status = IseqStatus::Compiled(start_ptr);
137-
payload.iseq_calls.extend(jit.iseq_calls);
138-
append_gc_offsets(iseq, &gc_offsets);
139-
140133
// Return a JIT code address
141134
Some(entry_ptr)
142135
}
@@ -194,53 +187,64 @@ fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function, function_pt
194187
println!("LIR:\nJIT entry for {}:\n{:?}", iseq_name(iseq), asm);
195188
}
196189

197-
let result = asm.compile(cb).map(|(start_ptr, _)| start_ptr);
198-
if let Some(start_addr) = result {
199-
if get_option!(perf) {
200-
let start_ptr = start_addr.raw_ptr(cb) as usize;
201-
let end_ptr = cb.get_write_ptr().raw_ptr(cb) as usize;
202-
let code_size = end_ptr - start_ptr;
203-
let iseq_name = iseq_get_location(iseq, 0);
204-
register_with_perf(format!("entry for {iseq_name}"), start_ptr, code_size);
205-
}
190+
let (code_ptr, gc_offsets) = asm.compile(cb)?;
191+
assert!(gc_offsets.is_empty());
192+
if get_option!(perf) {
193+
let start_ptr = code_ptr.raw_ptr(cb) as usize;
194+
let end_ptr = cb.get_write_ptr().raw_ptr(cb) as usize;
195+
let code_size = end_ptr - start_ptr;
196+
let iseq_name = iseq_get_location(iseq, 0);
197+
register_with_perf(format!("entry for {iseq_name}"), start_ptr, code_size);
206198
}
207-
result
199+
Some(code_ptr)
208200
}
209201

210-
/// Compile an ISEQ into machine code
211-
fn gen_iseq(cb: &mut CodeBlock, iseq: IseqPtr) -> Option<(CodePtr, Vec<Rc<RefCell<IseqCall>>>)> {
202+
/// Compile an ISEQ into machine code if not compiled yet
203+
fn gen_iseq(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>) -> Option<CodePtr> {
212204
// Return an existing pointer if it's already compiled
213205
let payload = get_or_create_iseq_payload(iseq);
214206
match payload.status {
215-
IseqStatus::Compiled(start_ptr) => return Some((start_ptr, vec![])),
207+
IseqStatus::Compiled(start_ptr) => return Some(start_ptr),
216208
IseqStatus::CantCompile => return None,
217209
IseqStatus::NotCompiled => {},
218210
}
219211

220-
// Convert ISEQ into High-level IR and optimize HIR
221-
let function = match compile_iseq(iseq) {
212+
// Compile the ISEQ
213+
let code_ptr = gen_iseq_body(cb, iseq, function, payload);
214+
if let Some(start_ptr) = code_ptr {
215+
payload.status = IseqStatus::Compiled(start_ptr);
216+
incr_counter!(compiled_iseq_count);
217+
} else {
218+
payload.status = IseqStatus::CantCompile;
219+
incr_counter!(compilation_failure);
220+
}
221+
code_ptr
222+
}
223+
224+
/// Compile an ISEQ into machine code
225+
fn gen_iseq_body(cb: &mut CodeBlock, iseq: IseqPtr, function: Option<&Function>, payload: &mut IseqPayload) -> Option<CodePtr> {
226+
// Convert ISEQ into optimized High-level IR if not given
227+
let function = match function {
222228
Some(function) => function,
223-
None => {
224-
payload.status = IseqStatus::CantCompile;
225-
return None;
226-
}
229+
None => &compile_iseq(iseq)?,
227230
};
228231

229232
// Compile the High-level IR
230-
let result = gen_function(cb, iseq, &function);
231-
if let Some((start_ptr, gc_offsets, jit)) = result {
232-
payload.status = IseqStatus::Compiled(start_ptr);
233-
payload.iseq_calls.extend(jit.iseq_calls.clone());
234-
append_gc_offsets(iseq, &gc_offsets);
235-
Some((start_ptr, jit.iseq_calls))
236-
} else {
237-
payload.status = IseqStatus::CantCompile;
238-
None
233+
let (start_ptr, gc_offsets, iseq_calls) = gen_function(cb, iseq, function)?;
234+
235+
// Stub callee ISEQs for JIT-to-JIT calls
236+
for iseq_call in iseq_calls.iter() {
237+
gen_iseq_call(cb, iseq, iseq_call)?;
239238
}
239+
240+
// Prepare for GC
241+
payload.iseq_calls.extend(iseq_calls.clone());
242+
append_gc_offsets(iseq, &gc_offsets);
243+
Some(start_ptr)
240244
}
241245

242246
/// Compile a function
243-
fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Option<(CodePtr, Vec<CodePtr>, JITState)> {
247+
fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Option<(CodePtr, Vec<CodePtr>, Vec<Rc<RefCell<IseqCall>>>)> {
244248
let c_stack_slots = max_num_params(function).saturating_sub(ALLOC_REGS.len());
245249
let mut jit = JITState::new(iseq, function.num_insns(), function.num_blocks(), c_stack_slots);
246250
let mut asm = Assembler::new();
@@ -279,6 +283,7 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Optio
279283
let insn = function.find(insn_id);
280284
if gen_insn(cb, &mut jit, &mut asm, function, insn_id, &insn).is_none() {
281285
debug!("Failed to compile insn: {insn_id} {insn}");
286+
incr_counter!(failed_gen_insn);
282287
return None;
283288
}
284289
}
@@ -291,8 +296,8 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Optio
291296
}
292297

293298
// Generate code if everything can be compiled
294-
let result = asm.compile(cb).map(|(start_ptr, gc_offsets)| (start_ptr, gc_offsets, jit));
295-
if let Some((start_ptr, _, _)) = result {
299+
let result = asm.compile(cb);
300+
if let Some((start_ptr, _)) = result {
296301
if get_option!(perf) {
297302
let start_usize = start_ptr.raw_ptr(cb) as usize;
298303
let end_usize = cb.get_write_ptr().raw_ptr(cb) as usize;
@@ -304,8 +309,10 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Optio
304309
let iseq_name = iseq_get_location(iseq, 0);
305310
ZJITState::log_compile(iseq_name);
306311
}
312+
} else {
313+
incr_counter!(failed_asm_compile);
307314
}
308-
result
315+
result.map(|(start_ptr, gc_offsets)| (start_ptr, gc_offsets, jit.iseq_calls))
309316
}
310317

311318
/// Compile an instruction
@@ -410,6 +417,7 @@ fn gen_insn(cb: &mut CodeBlock, jit: &mut JITState, asm: &mut Assembler, functio
410417
| Insn::Const { .. }
411418
=> {
412419
debug!("ZJIT: gen_function: unexpected insn {insn}");
420+
incr_counter!(failed_gen_insn_unexpected);
413421
return None;
414422
}
415423
};
@@ -1372,6 +1380,7 @@ fn compile_iseq(iseq: IseqPtr) -> Option<Function> {
13721380
Err(err) => {
13731381
let name = crate::cruby::iseq_get_location(iseq, 0);
13741382
debug!("ZJIT: iseq_to_hir: {err:?}: {name}");
1383+
incr_counter!(failed_hir_compile);
13751384
return None;
13761385
}
13771386
};
@@ -1382,6 +1391,7 @@ fn compile_iseq(iseq: IseqPtr) -> Option<Function> {
13821391
#[cfg(debug_assertions)]
13831392
if let Err(err) = function.validate() {
13841393
debug!("ZJIT: compile_iseq: {err:?}");
1394+
incr_counter!(failed_hir_optimize);
13851395
return None;
13861396
}
13871397
Some(function)
@@ -1516,16 +1526,11 @@ c_callable! {
15161526
/// Compile an ISEQ for a function stub
15171527
fn function_stub_hit_body(cb: &mut CodeBlock, iseq_call: &Rc<RefCell<IseqCall>>) -> Option<CodePtr> {
15181528
// Compile the stubbed ISEQ
1519-
let Some((code_ptr, iseq_calls)) = gen_iseq(cb, iseq_call.borrow().iseq) else {
1529+
let Some(code_ptr) = gen_iseq(cb, iseq_call.borrow().iseq, None) else {
15201530
debug!("Failed to compile iseq: gen_iseq failed: {}", iseq_get_location(iseq_call.borrow().iseq, 0));
15211531
return None;
15221532
};
15231533

1524-
// Stub callee ISEQs for JIT-to-JIT calls
1525-
for callee_iseq_call in iseq_calls.iter() {
1526-
gen_iseq_call(cb, iseq_call.borrow().iseq, callee_iseq_call)?;
1527-
}
1528-
15291534
// Update the stub to call the code pointer
15301535
let code_addr = code_ptr.raw_ptr(cb);
15311536
let iseq = iseq_call.borrow().iseq;

zjit/src/hir.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44
#![allow(non_upper_case_globals)]
55

66
use crate::{
7-
cast::IntoUsize, cruby::*, gc::{get_or_create_iseq_payload, IseqPayload}, options::{get_option, DumpHIR}, state::ZJITState, stats::Counter
7+
cast::IntoUsize, cruby::*, gc::{get_or_create_iseq_payload, IseqPayload}, options::{get_option, DumpHIR}, state::ZJITState
88
};
99
use std::{
1010
cell::RefCell, collections::{HashMap, HashSet, VecDeque}, ffi::{c_int, c_void, CStr}, fmt::Display, mem::{align_of, size_of}, ptr, slice::Iter
1111
};
1212
use crate::hir_type::{Type, types};
1313
use crate::bitset::BitSet;
1414
use crate::profile::{TypeDistributionSummary, ProfiledType};
15+
use crate::stats::{incr_counter, Counter};
1516

1617
/// An index of an [`Insn`] in a [`Function`]. This is a popular
1718
/// type since this effectively acts as a pointer to an [`Insn`].
@@ -3494,6 +3495,7 @@ pub fn iseq_to_hir(iseq: *const rb_iseq_t) -> Result<Function, ParseError> {
34943495

34953496
fun.profiles = Some(profiles);
34963497
if let Err(e) = fun.validate() {
3498+
incr_counter!(failed_hir_compile_validate);
34973499
return Err(ParseError::Validation(e));
34983500
}
34993501
Ok(fun)

0 commit comments

Comments
 (0)