Skip to main content

logicaffeine_compile/vm/
tier_trace.rs

1//! `LOGOS_TIER_TRACE` — one line per execution-tier hot-swap (HOTSWAP §P13).
2//!
3//! When the env var is set (and non-empty, non-`"0"`), the VM emits a line to stderr
4//! every time a function body is swapped to a hotter tier: baseline bytecode → warm
5//! bytecode (Axis-1) → native via forge (Axis-2) → native via AOT cdylib (Axis-3).
6//! This is the "we must be able to know and understand" observability hook — it makes
7//! the otherwise-invisible tier ladder legible without a debugger. The formatting is a
8//! pure function so it can be asserted directly.
9
10use std::sync::atomic::{AtomicU8, Ordering};
11
12/// Tri-state cache of the env check (0 = unread, 1 = on, 2 = off) so the hot path
13/// reads an atomic instead of the environment on every transition.
14static ENABLED: AtomicU8 = AtomicU8::new(0);
15
16/// Whether `LOGOS_TIER_TRACE` requests the trace (set, non-empty, not `"0"`).
17pub fn trace_enabled() -> bool {
18    match ENABLED.load(Ordering::Relaxed) {
19        1 => true,
20        2 => false,
21        _ => {
22            let on = std::env::var("LOGOS_TIER_TRACE")
23                .map(|v| !v.is_empty() && v != "0")
24                .unwrap_or(false);
25            ENABLED.store(if on { 1 } else { 2 }, Ordering::Relaxed);
26            on
27        }
28    }
29}
30
31/// The execution tier a function body runs at, coldest to hottest.
32#[derive(Clone, Copy, Debug, PartialEq, Eq)]
33pub enum ExecTier {
34    /// The baseline bytecode in `program.code`.
35    Bytecode,
36    /// A re-optimized body in the warm side-table (Axis-1, browser-portable).
37    Warm,
38    /// A copy-and-patch native body installed by forge (Axis-2).
39    NativeForge,
40    /// A native body loaded from an AOT-compiled cdylib (Axis-3).
41    NativeAot,
42}
43
44impl ExecTier {
45    pub fn label(self) -> &'static str {
46        match self {
47            ExecTier::Bytecode => "bytecode",
48            ExecTier::Warm => "warm",
49            ExecTier::NativeForge => "native(forge)",
50            ExecTier::NativeAot => "native(aot)",
51        }
52    }
53}
54
55/// Format one tier-transition line. Pure — the unit under test.
56pub fn format_transition(fi: usize, name: &str, to: ExecTier) -> String {
57    if name.is_empty() {
58        format!("[tier] fn#{fi} -> {}", to.label())
59    } else {
60        format!("[tier] fn#{fi} '{name}' -> {}", to.label())
61    }
62}
63
64/// Emit a transition line iff the trace is enabled.
65pub fn trace_transition(fi: usize, name: &str, to: ExecTier) {
66    if trace_enabled() {
67        eprintln!("{}", format_transition(fi, name, to));
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74
75    #[test]
76    fn transition_lines_format_with_and_without_a_name() {
77        assert_eq!(
78            format_transition(3, "fib", ExecTier::Warm),
79            "[tier] fn#3 'fib' -> warm"
80        );
81        assert_eq!(
82            format_transition(7, "quicksort", ExecTier::NativeForge),
83            "[tier] fn#7 'quicksort' -> native(forge)"
84        );
85        assert_eq!(
86            format_transition(0, "", ExecTier::NativeAot),
87            "[tier] fn#0 -> native(aot)"
88        );
89        assert_eq!(
90            format_transition(1, "main", ExecTier::Bytecode),
91            "[tier] fn#1 'main' -> bytecode"
92        );
93    }
94}