/// Debug metadata captured while compiling a JIT module.
///|
/// A resolved wasm frame in a native trap report.
pub(all) struct WasmTrapFrame {
pc : Int64
func_idx : Int
func_name : String
wasm_offset : Int64
}
///|
/// Structured trap report for native integrations.
pub(all) struct TrapReport {
trap_kind : String
message : String
signal : Int
signal_name : String
pc : Int64
lr : Int64
fp : Int64
frame_lr : Int64
fault_addr : Int64
brk_imm : Int
wasm_func_idx : Int?
wasm_func_name : String?
wasm_offset : Int64?
wasm_frames : Array[WasmTrapFrame]
host_frames : Array[String]
dump_path : String?
}
///|
/// Per-function compilation dumps captured for dump-on-trap.
pub struct JITFunctionDebug {
ir : String
machv_before_regalloc : String
machv_after_regalloc : String
machine_code : String
}
///|
pub fn JITFunctionDebug::JITFunctionDebug(
ir : String,
machv_before_regalloc : String,
machv_after_regalloc : String,
machine_code : String,
) -> JITFunctionDebug {
{ ir, machv_before_regalloc, machv_after_regalloc, machine_code }
}
///|
/// Optional in-memory database of per-function debug dumps.
pub struct JITDebugDB {
entries : Map[Int, JITFunctionDebug]
}
///|
pub fn JITDebugDB::JITDebugDB() -> JITDebugDB {
{ entries: Map([]) }
}
///|
pub fn JITDebugDB::set(
self : JITDebugDB,
func_idx : Int,
debug : JITFunctionDebug,
) -> Unit {
self.entries.set(func_idx, debug)
}
///|
pub fn JITDebugDB::get(self : JITDebugDB, func_idx : Int) -> JITFunctionDebug? {
self.entries.get(func_idx)
}
///|
pub fn format_u64_hex(v : Int64) -> String {
let u = v.reinterpret_as_uint64()
let b7 = (u >> 56).reinterpret_as_int64().to_int() & 0xFF
let b6 = (u >> 48).reinterpret_as_int64().to_int() & 0xFF
let b5 = (u >> 40).reinterpret_as_int64().to_int() & 0xFF
let b4 = (u >> 32).reinterpret_as_int64().to_int() & 0xFF
let b3 = (u >> 24).reinterpret_as_int64().to_int() & 0xFF
let b2 = (u >> 16).reinterpret_as_int64().to_int() & 0xFF
let b1 = (u >> 8).reinterpret_as_int64().to_int() & 0xFF
let b0 = u.reinterpret_as_int64().to_int() & 0xFF
"0x" +
@types.to_hex_byte(b7) +
@types.to_hex_byte(b6) +
@types.to_hex_byte(b5) +
@types.to_hex_byte(b4) +
@types.to_hex_byte(b3) +
@types.to_hex_byte(b2) +
@types.to_hex_byte(b1) +
@types.to_hex_byte(b0)
}
///|
pub fn sanitize_debug_filename(s : String) -> String {
let mut out = ""
for c in s {
let ok = (c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '0' && c <= '9') ||
c == '_' ||
c == '-' ||
c == '.'
out = out + (if ok { c.to_string() } else { "_" })
}
if out.is_empty() {
"unknown"
} else {
out
}
}
///|
pub fn format_trap_report_message(report : TrapReport) -> String {
let fields : Array[String] = []
if report.signal != 0 {
fields.push("sig=\{report.signal_name}")
}
if report.pc != 0L {
fields.push("pc=\{format_u64_hex(report.pc)}")
}
if report.lr != 0L {
fields.push("lr=\{format_u64_hex(report.lr)}")
}
if report.frame_lr != 0L {
fields.push("frame_lr=\{format_u64_hex(report.frame_lr)}")
}
if report.fault_addr != 0L {
fields.push("addr=\{format_u64_hex(report.fault_addr)}")
}
if report.brk_imm >= 0 {
fields.push("brk_imm=\{report.brk_imm}")
}
if report.wasm_func_idx is Some(func_idx) {
match (report.wasm_func_name, report.wasm_offset) {
(Some(name), Some(offset)) =>
fields.push(
"wasm=\{func_idx} '\{name}'+0x\{@types.int_to_hex(offset.to_int())}",
)
(Some(name), None) => fields.push("wasm=\{func_idx} '\{name}'")
(None, Some(offset)) =>
fields.push("wasm=\{func_idx} +0x\{@types.int_to_hex(offset.to_int())}")
(None, None) => fields.push("wasm=\{func_idx}")
}
}
if report.wasm_frames.length() > 1 {
let caller = report.wasm_frames[1]
fields.push(
"caller=\{caller.func_idx} '\{caller.func_name}'+0x\{@types.int_to_hex(caller.wasm_offset.to_int())}",
)
}
if report.dump_path is Some(path) {
fields.push("dump=\{path}")
}
if fields.is_empty() {
report.message
} else {
report.message + " (" + fields.join(", ") + ")"
}
}