use inkwell::module::Module;
use inkwell::values::FunctionValue;
use std::sync::{Mutex, OnceLock};
use std::time::Instant;
static LOWERED: Mutex<Vec<(String, f64, usize)>> = Mutex::new(Vec::new());
fn instr_count(f: FunctionValue<'_>) -> usize {
f.get_basic_blocks()
.iter()
.map(|b| b.get_instructions().count())
.sum()
}
pub fn record(t: Option<Instant>, name: &str, f: FunctionValue<'_>) {
if let Some(t) = t {
let secs = t.elapsed().as_secs_f64();
if let Ok(mut v) = LOWERED.lock() {
v.push((name.to_string(), secs, instr_count(f)));
}
}
}
pub fn report_module(module: &Module<'_>) {
if !enabled() {
return;
}
let mut sizes: Vec<(String, usize, usize)> = module
.get_functions()
.filter(|f| f.count_basic_blocks() > 0)
.map(|f| {
(
f.get_name().to_string_lossy().into_owned(),
instr_count(f),
f.count_basic_blocks() as usize,
)
})
.collect();
let total: usize = sizes.iter().map(|s| s.1).sum();
eprintln!(
"[timing] module: {} defined functions, {} IR instructions",
sizes.len(),
total
);
sizes.sort_by_key(|s| std::cmp::Reverse(s.1));
for (n, i, b) in sizes.iter().take(25) {
eprintln!("[timing] ir-size {i:>9} instrs {b:>8} blocks {n}");
}
let mut by_leaf: std::collections::HashMap<String, (usize, usize)> =
std::collections::HashMap::new();
let mut by_depth: std::collections::BTreeMap<usize, (usize, usize)> =
std::collections::BTreeMap::new();
for (n, i, _) in &sizes {
let parts: Vec<&str> = n.split("__").collect();
let leaf = parts[parts.len().saturating_sub(2)..].join("__");
let e = by_leaf.entry(leaf).or_default();
e.0 += 1;
e.1 += i;
let d = by_depth
.entry(parts.iter().filter(|p| p.starts_with("fork")).count())
.or_default();
d.0 += 1;
d.1 += i;
}
for (d, (c, i)) in &by_depth {
eprintln!("[timing] fork-depth {d:>2}: {c:>7} fns {i:>10} instrs");
}
let mut leaves: Vec<_> = by_leaf.into_iter().collect();
leaves.sort_by_key(|l| std::cmp::Reverse(l.1.1));
for (n, (c, i)) in leaves.iter().take(15) {
eprintln!("[timing] leaf-group {c:>6} fns {i:>10} instrs {n}");
}
if let Ok(mut v) = LOWERED.lock() {
let sum: f64 = v.iter().map(|x| x.1).sum();
eprintln!("[timing] lowering: {} recorded bodies, {sum:.1} s", v.len());
v.sort_by(|a, b| b.1.total_cmp(&a.1));
for (n, s, i) in v.iter().take(25) {
eprintln!("[timing] lower {s:>8.2} s {i:>9} instrs {n}");
}
v.clear();
}
}
pub const TIMING_ENV: &str = "SV_SIM_TIMING";
pub fn enabled() -> bool {
static ON: OnceLock<bool> = OnceLock::new();
*ON.get_or_init(|| std::env::var_os(TIMING_ENV).is_some())
}
fn epoch() -> Instant {
static T0: OnceLock<Instant> = OnceLock::new();
*T0.get_or_init(|| {
let unix = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0.0, |d| d.as_secs_f64());
eprintln!("[timing] epoch (unix) {unix:.2}");
Instant::now()
})
}
pub fn end_if_over(t: Option<Instant>, min_secs: f64, label: &str, detail: &str) {
if let Some(start) = t
&& start.elapsed().as_secs_f64() >= min_secs
{
end(t, label, detail);
}
}
pub fn start() -> Option<Instant> {
if enabled() {
epoch();
Some(Instant::now())
} else {
None
}
}
pub fn end(t: Option<Instant>, label: &str, detail: &str) {
if let Some(t) = t {
eprintln!(
"[timing] {label}: {:.2} s (t={:.1}) {detail} [rss {} MB, peak {} MB]",
t.elapsed().as_secs_f64(),
epoch().elapsed().as_secs_f64(),
proc_status_mb("VmRSS:"),
proc_status_mb("VmHWM:"),
);
}
}
fn proc_status_mb(key: &str) -> u64 {
std::fs::read_to_string("/proc/self/status")
.ok()
.and_then(|t| {
t.lines()
.find_map(|l| l.strip_prefix(key))
.and_then(|r| r.split_whitespace().next())
.and_then(|n| n.parse::<u64>().ok())
})
.map_or(0, |kb| kb / 1024)
}