Skip to content

Commit 30b1368

Browse files
authored
ZJIT: Create perf map files for profilers (ruby#13941)
This lets us ZJIT compiled functions show up in the profiles of, say, perf, or samply. Fix Shopify#634
1 parent 86320a5 commit 30b1368

File tree

2 files changed

+42
-2
lines changed

2 files changed

+42
-2
lines changed

zjit/src/codegen.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,20 @@ fn gen_iseq_entry_point_body(cb: &mut CodeBlock, iseq: IseqPtr) -> *const u8 {
154154
start_ptr.map(|start_ptr| start_ptr.raw_ptr(cb)).unwrap_or(std::ptr::null())
155155
}
156156

157+
/// Write an entry to the perf map in /tmp
158+
fn register_with_perf(iseq_name: String, start_ptr: usize, code_size: usize) {
159+
use std::io::Write;
160+
let perf_map = format!("/tmp/perf-{}.map", std::process::id());
161+
let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(&perf_map) else {
162+
debug!("Failed to open perf map file: {perf_map}");
163+
return;
164+
};
165+
let Ok(_) = writeln!(file, "{:#x} {:#x} zjit::{}", start_ptr, code_size, iseq_name) else {
166+
debug!("Failed to write {iseq_name} to perf map file: {perf_map}");
167+
return;
168+
};
169+
}
170+
157171
/// Compile a JIT entry
158172
fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function, function_ptr: CodePtr, c_stack_bytes: usize) -> Option<CodePtr> {
159173
// Set up registers for CFP, EC, SP, and basic block arguments
@@ -172,7 +186,17 @@ fn gen_entry(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function, function_pt
172186
asm.frame_teardown();
173187
asm.cret(C_RET_OPND);
174188

175-
asm.compile(cb).map(|(start_ptr, _)| start_ptr)
189+
let result = asm.compile(cb).map(|(start_ptr, _)| start_ptr);
190+
if let Some(start_addr) = result {
191+
if get_option!(perf) {
192+
let start_ptr = start_addr.raw_ptr(cb) as usize;
193+
let end_ptr = cb.get_write_ptr().raw_ptr(cb) as usize;
194+
let code_size = end_ptr - start_ptr;
195+
let iseq_name = iseq_get_location(iseq, 0);
196+
register_with_perf(format!("entry for {iseq_name}"), start_ptr, code_size);
197+
}
198+
}
199+
result
176200
}
177201

178202
/// Compile an ISEQ into machine code
@@ -255,7 +279,17 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, function: &Function) -> Optio
255279
}
256280

257281
// Generate code if everything can be compiled
258-
asm.compile(cb).map(|(start_ptr, gc_offsets)| (start_ptr, gc_offsets, jit))
282+
let result = asm.compile(cb).map(|(start_ptr, gc_offsets)| (start_ptr, gc_offsets, jit));
283+
if let Some((start_ptr, _, _)) = result {
284+
if get_option!(perf) {
285+
let start_usize = start_ptr.raw_ptr(cb) as usize;
286+
let end_usize = cb.get_write_ptr().raw_ptr(cb) as usize;
287+
let code_size = end_usize - start_usize;
288+
let iseq_name = iseq_get_location(iseq, 0);
289+
register_with_perf(iseq_name, start_usize, code_size);
290+
}
291+
}
292+
result
259293
}
260294

261295
/// Compile an instruction

zjit/src/options.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ pub struct Options {
3333

3434
/// Dump all compiled machine code.
3535
pub dump_disasm: bool,
36+
37+
/// Dump code map to /tmp for performance profilers.
38+
pub perf: bool,
3639
}
3740

3841
/// Return an Options with default values
@@ -44,6 +47,7 @@ pub fn init_options() -> Options {
4447
dump_hir_opt: None,
4548
dump_lir: false,
4649
dump_disasm: false,
50+
perf: false,
4751
}
4852
}
4953

@@ -143,6 +147,8 @@ fn parse_option(options: &mut Options, str_ptr: *const std::os::raw::c_char) ->
143147

144148
("dump-disasm", "") => options.dump_disasm = true,
145149

150+
("perf", "") => options.perf = true,
151+
146152
_ => return None, // Option name not recognized
147153
}
148154

0 commit comments

Comments
 (0)