use crate::Engine;
use std::sync::atomic::Ordering;
pub fn spec_trace_on() -> bool {
spec_trace_level() >= 1
}
pub fn spec_trace_level() -> u8 {
use std::sync::OnceLock;
static L: OnceLock<u8> = OnceLock::new();
*L.get_or_init(|| {
spec_trace_level_from(
std::env::var("MEMRA_SPEC_TRACE").ok().as_deref(),
std::env::var("MEMRA_GLM5_SPEC_TRACE").ok().as_deref(),
)
})
}
fn parse_level(v: Option<&str>) -> Option<u8> {
match v {
Some("1") => Some(1),
Some("2") => Some(2),
_ => None,
}
}
fn spec_trace_level_from(general: Option<&str>, glm5_alias: Option<&str>) -> u8 {
let g = parse_level(general);
let a = parse_level(glm5_alias);
if let (Some(gv), Some(av)) = (g, a)
&& gv != av
{
eprintln!(
"[spec-trace] MEMRA_SPEC_TRACE={gv} overrides MEMRA_GLM5_SPEC_TRACE={av} \
(the general flag wins; unset one to silence this)"
);
}
g.or(a).unwrap_or(0)
}
pub(crate) static V_KDA_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub(crate) static V_KDA_SCAN_NS: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
pub(crate) static V_MLA_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
pub(crate) static V_FFN_NS: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
#[derive(Default)]
pub(crate) struct SpecPhaseNs {
pub(crate) draft: u64,
pub(crate) verify: u64,
pub(crate) accept: u64,
pub(crate) roll: u64,
pub(crate) maint: u64,
pub(crate) rounds: u64,
}
impl SpecPhaseNs {
pub(crate) fn clock(e: &Engine, eh: &Engine) -> std::time::Instant {
let _ = e.stream().synchronize();
if !std::ptr::eq(e, eh) {
let _ = eh.stream().synchronize();
}
std::time::Instant::now()
}
pub(crate) fn emit(&self, tag: &str, tag_v: &str, k: usize) {
if self.rounds == 0 {
return;
}
let ms = |ns: u64| ns as f64 / 1e6;
let per = |ns: u64| ns as f64 / 1e6 / self.rounds as f64;
let total = self.draft + self.verify + self.accept + self.roll + self.maint;
eprintln!(
"[{tag}] rounds={} k={k} total={:.2}ms | draft={:.2} verify={:.2} \
accept={:.2} roll={:.2} maint={:.2} | per-round ms: draft={:.3} verify={:.3} \
accept={:.3} roll={:.3} maint={:.3} total={:.3}",
self.rounds,
ms(total),
ms(self.draft),
ms(self.verify),
ms(self.accept),
ms(self.roll),
ms(self.maint),
per(self.draft),
per(self.verify),
per(self.accept),
per(self.roll),
per(self.maint),
per(total),
);
if spec_trace_level() >= 2 {
let vkda = V_KDA_NS.swap(0, Ordering::Relaxed);
let scan = V_KDA_SCAN_NS.swap(0, Ordering::Relaxed);
let vmla = V_MLA_NS.swap(0, Ordering::Relaxed);
let vffn = V_FFN_NS.swap(0, Ordering::Relaxed);
let vrest = self.verify.saturating_sub(vkda + vmla);
eprintln!(
"[{tag_v}] rounds={} k={k} | per-round ms: vkda={:.3} (scan={:.3}) \
vmla={:.3} vrest={:.3} (vffn={:.3})",
self.rounds,
per(vkda),
per(scan),
per(vmla),
per(vrest),
per(vffn),
);
}
}
}
#[cfg(test)]
mod tests {
use super::{parse_level, spec_trace_level_from};
#[test]
fn level_resolution_honors_both_names_general_wins() {
assert_eq!(spec_trace_level_from(None, None), 0);
assert_eq!(spec_trace_level_from(Some("x"), None), 0);
assert_eq!(spec_trace_level_from(Some("1"), None), 1);
assert_eq!(spec_trace_level_from(Some("2"), None), 2);
assert_eq!(spec_trace_level_from(None, Some("1")), 1);
assert_eq!(spec_trace_level_from(None, Some("2")), 2);
assert_eq!(spec_trace_level_from(Some("2"), Some("2")), 2);
assert_eq!(spec_trace_level_from(Some("1"), Some("2")), 1);
assert_eq!(spec_trace_level_from(Some("2"), Some("1")), 2);
assert_eq!(spec_trace_level_from(Some("x"), Some("1")), 1);
assert_eq!(parse_level(Some("0")), None);
}
}