@@ -6,6 +6,7 @@ use std::cell::{Cell, RefCell};
66use std:: rc:: Rc ;
77use std:: ffi:: { c_int, c_long, c_void} ;
88use std:: slice;
9+ use std:: sync:: OnceLock ;
910
1011use crate :: backend:: current:: ALLOC_REGS ;
1112use crate :: invariants:: {
@@ -25,6 +26,30 @@ use crate::hir::{Const, FrameState, Function, Insn, InsnId, SendFallbackReason};
2526use crate :: hir_type:: { types, Type } ;
2627use crate :: options:: get_option;
2728use crate :: cast:: IntoUsize ;
29+ use crate :: jitdump:: { JitdumpWriter , DebugEntry } ;
30+
31+ /// Global jitdump writer, initialized on first use when --zjit-perf is set.
32+ static JITDUMP : OnceLock < JitdumpWriter > = OnceLock :: new ( ) ;
33+
34+ /// Path to the single HIR source file for all methods.
35+ /// Written to /tmp/zjit-hir-{pid}.hir
36+ static HIR_FILE_PATH : OnceLock < String > = OnceLock :: new ( ) ;
37+
38+ fn get_jitdump ( ) -> Option < & ' static JitdumpWriter > {
39+ if !get_option ! ( perf) {
40+ return None ;
41+ }
42+ Some ( JITDUMP . get_or_init ( || {
43+ JitdumpWriter :: open ( ) . expect ( "Failed to open jitdump file" )
44+ } ) )
45+ }
46+
47+ fn get_hir_file_path ( ) -> & ' static str {
48+ HIR_FILE_PATH . get_or_init ( || {
49+ let pid = std:: process:: id ( ) ;
50+ format ! ( "/tmp/zjit-hir-{pid}.hir" )
51+ } )
52+ }
2853
2954/// At the moment, we support recompiling each ISEQ only once.
3055pub const MAX_ISEQ_VERSIONS : usize = 2 ;
@@ -163,6 +188,134 @@ pub fn gen_iseq_call(cb: &mut CodeBlock, iseq_call: &IseqCallRef) -> Result<(),
163188 Ok ( ( ) )
164189}
165190
191+ /// Emit jitdump records with HIR source-level debug info for a compiled function.
192+ fn emit_jitdump_for_function (
193+ cb : & CodeBlock ,
194+ function : & Function ,
195+ iseq_name : & str ,
196+ start_ptr : CodePtr ,
197+ code_size : usize ,
198+ pos_markers : & [ ( CodePtr , InsnId ) ] ,
199+ ) {
200+ let Some ( jitdump) = get_jitdump ( ) else { return } ;
201+
202+ let start_addr = start_ptr. raw_addr ( cb) as u64 ;
203+
204+ // Read the generated code bytes for the CODE_LOAD record
205+ let code_bytes = unsafe { std:: slice:: from_raw_parts ( start_ptr. raw_ptr ( cb) , code_size) } ;
206+ let func_name = format ! ( "zjit::{iseq_name}" ) ;
207+ if let Err ( e) = jitdump. write_code_load ( & func_name, start_addr, & code_bytes) {
208+ debug ! ( "Failed to write jitdump code load: {e}" ) ;
209+ return ;
210+ }
211+
212+ // Build HIR text and line mapping for this function.
213+ // We write HIR as text to a single shared file, appending each method.
214+ // The line numbers in the debug entries reference lines in this file.
215+ let hir_file_path = get_hir_file_path ( ) ;
216+ let ( hir_text, insn_id_to_line) = format_hir_for_jitdump ( function) ;
217+
218+ // Append HIR text to the shared file and get the starting line offset
219+ let line_offset = append_hir_to_file ( hir_file_path, & hir_text) ;
220+
221+ // Build debug entries: map each pos_marker's code offset to the HIR line
222+ let mut debug_entries: Vec < DebugEntry > = Vec :: new ( ) ;
223+ for & ( code_ptr, insn_id) in pos_markers {
224+ if let Some ( & line) = insn_id_to_line. get ( & insn_id) {
225+ debug_entries. push ( DebugEntry {
226+ code_addr : code_ptr. raw_addr ( cb) as u64 - start_addr,
227+ line : line_offset + line,
228+ filename : hir_file_path,
229+ } ) ;
230+ }
231+ }
232+
233+ if let Err ( e) = jitdump. write_debug_info ( start_addr, & debug_entries) {
234+ debug ! ( "Failed to write jitdump debug info: {e}" ) ;
235+ }
236+ }
237+
238+ /// Format a function's HIR as text for the jitdump source file.
239+ /// Returns (text, map from InsnId to 1-based line number within the text).
240+ fn format_hir_for_jitdump ( function : & Function ) -> ( String , std:: collections:: HashMap < InsnId , u32 > ) {
241+ use std:: fmt:: Write ;
242+ use crate :: hir:: PtrPrintMap ;
243+ let mut text = String :: new ( ) ;
244+ let mut insn_id_to_line: std:: collections:: HashMap < InsnId , u32 > = std:: collections:: HashMap :: new ( ) ;
245+ let mut line: u32 = 1 ;
246+
247+ let iseq = function. iseq ( ) ;
248+ let iseq_name = if iseq. is_null ( ) {
249+ String :: from ( "<manual>" )
250+ } else {
251+ iseq_get_location ( iseq, 0 )
252+ } ;
253+ writeln ! ( text, "fn {iseq_name}:" ) . unwrap ( ) ;
254+ line += 1 ;
255+
256+ let ptr_map = PtrPrintMap :: identity ( ) ;
257+
258+ for block_id in function. rpo ( ) {
259+ let block = function. block ( block_id) ;
260+ write ! ( text, "{block_id}(" ) . unwrap ( ) ;
261+ let mut sep = "" ;
262+ for & param in block. params ( ) {
263+ let insn_type = function. type_of ( param) ;
264+ if insn_type. is_subtype ( types:: Empty ) {
265+ write ! ( text, "{sep}{param}" ) . unwrap ( ) ;
266+ } else {
267+ write ! ( text, "{sep}{param}:{}" , insn_type. print( & ptr_map) ) . unwrap ( ) ;
268+ }
269+ sep = ", " ;
270+ }
271+ writeln ! ( text, "):" ) . unwrap ( ) ;
272+ line += 1 ;
273+
274+ for & insn_id in block. insns ( ) {
275+ let insn = function. find ( insn_id) ;
276+ if matches ! ( insn, Insn :: Snapshot { .. } ) {
277+ continue ;
278+ }
279+
280+ insn_id_to_line. insert ( insn_id, line) ;
281+
282+ write ! ( text, " " ) . unwrap ( ) ;
283+ if insn. has_output ( ) {
284+ let insn_type = function. type_of ( insn_id) ;
285+ if insn_type. is_subtype ( types:: Empty ) {
286+ write ! ( text, "{insn_id} = " ) . unwrap ( ) ;
287+ } else {
288+ write ! ( text, "{insn_id}:{} = " , insn_type. print( & ptr_map) ) . unwrap ( ) ;
289+ }
290+ }
291+ writeln ! ( text, "{}" , insn. print( & ptr_map, Some ( iseq) ) ) . unwrap ( ) ;
292+ line += 1 ;
293+ }
294+ }
295+
296+ ( text, insn_id_to_line)
297+ }
298+
299+ /// Append HIR text to the shared file and return the 1-based line offset
300+ /// (i.e., how many lines were in the file before this append).
301+ fn append_hir_to_file ( path : & str , text : & str ) -> u32 {
302+ use std:: io:: { Write , BufRead } ;
303+
304+ // Count existing lines
305+ let existing_lines = if let Ok ( file) = std:: fs:: File :: open ( path) {
306+ std:: io:: BufReader :: new ( file) . lines ( ) . count ( ) as u32
307+ } else {
308+ 0
309+ } ;
310+
311+ // Append the new text
312+ if let Ok ( mut file) = std:: fs:: OpenOptions :: new ( ) . create ( true ) . append ( true ) . open ( path) {
313+ let _ = file. write_all ( text. as_bytes ( ) ) ;
314+ }
315+
316+ existing_lines
317+ }
318+
166319/// Write an entry to the perf map in /tmp
167320fn register_with_perf ( iseq_name : String , start_ptr : usize , code_size : usize ) {
168321 use std:: io:: Write ;
@@ -272,6 +425,9 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
272425 let mut jit = JITState :: new ( iseq, version, function. num_insns ( ) , function. num_blocks ( ) ) ;
273426 let mut asm = Assembler :: new_with_stack_slots ( num_spilled_params) ;
274427
428+ // Collect (CodePtr, InsnId) pairs for jitdump debug info
429+ let hir_pos_markers: Rc < RefCell < Vec < ( CodePtr , InsnId ) > > > = Rc :: new ( RefCell :: new ( Vec :: new ( ) ) ) ;
430+
275431 // Mapping from HIR block IDs to LIR block IDs.
276432 // This is is a one-to-one mapping from HIR to LIR blocks used for finding
277433 // jump targets in LIR (LIR should always jump to the head of an HIR block)
@@ -330,6 +486,15 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
330486 // Compile all instructions
331487 for & insn_id in block. insns ( ) {
332488 let insn = function. find ( insn_id) ;
489+
490+ // Record code position for each non-snapshot HIR instruction (for jitdump)
491+ if get_option ! ( perf) && !matches ! ( insn, Insn :: Snapshot { .. } ) {
492+ let markers = Rc :: clone ( & hir_pos_markers) ;
493+ asm. pos_marker ( move |code_ptr, _cb| {
494+ markers. borrow_mut ( ) . push ( ( code_ptr, insn_id) ) ;
495+ } ) ;
496+ }
497+
333498 match insn {
334499 Insn :: IfFalse { val, target } => {
335500 let val_opnd = jit. get_opnd ( val) ;
@@ -411,15 +576,17 @@ fn gen_function(cb: &mut CodeBlock, iseq: IseqPtr, version: IseqVersionRef, func
411576 // Generate code if everything can be compiled
412577 let result = asm. compile ( cb) ;
413578 if let Ok ( ( start_ptr, _) ) = result {
579+ let iseq_name = iseq_get_location ( iseq, 0 ) ;
414580 if get_option ! ( perf) {
415581 let start_usize = start_ptr. raw_addr ( cb) ;
416582 let end_usize = cb. get_write_ptr ( ) . raw_addr ( cb) ;
417583 let code_size = end_usize - start_usize;
418- let iseq_name = iseq_get_location ( iseq, 0 ) ;
419- register_with_perf ( iseq_name, start_usize, code_size) ;
584+ register_with_perf ( iseq_name. clone ( ) , start_usize, code_size) ;
585+
586+ // Emit jitdump records with HIR debug info
587+ emit_jitdump_for_function ( cb, function, & iseq_name, start_ptr, code_size, & hir_pos_markers. borrow ( ) ) ;
420588 }
421589 if ZJITState :: should_log_compiled_iseqs ( ) {
422- let iseq_name = iseq_get_location ( iseq, 0 ) ;
423590 ZJITState :: log_compile ( iseq_name) ;
424591 }
425592 }
0 commit comments