Skip to main content

polydat_core/kernel/
opt.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! `KernelOptLevel` โ€” session-wide optimization knob for op-template
5//! kernel synthesis.
6//!
7//! The closure-binding economy (Rule 5) allocates a magic extern
8//! (`body` / `count` / `ok`) only when the result source references
9//! it (subcontext_construction.md ยง3.2), so a kernel has no slot for
10//! an unreferenced one and a host write to that name finds none.
11//!
12//! That's fine in production: the workload didn't ask for the value,
13//! we don't pay to track it. It's actively harmful for step-debug /
14//! cycle-replay / "show me what the adapter actually wrote this
15//! cycle even though nothing read it" inspection.
16//!
17//! `Diagnostic` mode allocates all three magic externs whether or not
18//! the source references them. The writes land, `wires.get` answers,
19//! the step-debugger sees the real values. Compute path unchanged โ€”
20//! the extra slots have no eval cone hanging off them.
21//!
22//! The knob threads through [`crate::kernel::subcontext::CompileOptions`]
23//! and is consulted by `SubcontextBuilder::add_result_bindings`. The
24//! polydat binary does not expose it; a host may map a CLI flag onto it.
25
26/// Optimization level for op-template kernel synthesis.
27///
28/// `Release` is the production default โ€” closure-binding economy
29/// elides magic-extern slots nothing references. `Diagnostic` keeps
30/// every magic-extern slot allocated so step-debug / cycle-replay
31/// introspection can see the values the runtime would otherwise
32/// drop.
33///
34/// Naming: matches the rustc convention (`opt-level=0..3`) but
35/// collapsed to two semantically-distinct positions; there's no
36/// useful middle ground between "DCE on" and "keep everything for
37/// inspection."
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
39pub enum KernelOptLevel {
40    /// Production default. Closure-binding economy elides slots
41    /// for unreferenced magic externs; an elided name has no slot
42    /// to write.
43    #[default]
44    Release,
45    /// Step-debug / cycle-replay mode. Force-allocate every magic
46    /// extern (`body` / `count` / `ok`) regardless of reference.
47    /// Runtime writes always land; `wires.get` always answers.
48    Diagnostic,
49}
50
51impl KernelOptLevel {
52    /// True when slot allocation should ignore the "is this name
53    /// referenced?" check and force-allocate every candidate slot.
54    pub fn keep_unreferenced_slots(self) -> bool {
55        matches!(self, Self::Diagnostic)
56    }
57
58    /// Parse from a CLI-style string. Returns `Err(input)` on an
59    /// unrecognised value so the caller can format its own
60    /// diagnostic.
61    pub fn parse(s: &str) -> Result<Self, &str> {
62        match s {
63            "release" => Ok(Self::Release),
64            "diagnostic" => Ok(Self::Diagnostic),
65            _ => Err(s),
66        }
67    }
68
69    /// Canonical CLI spelling for this level. Inverse of
70    /// [`Self::parse`].
71    pub fn as_str(self) -> &'static str {
72        match self {
73            Self::Release => "release",
74            Self::Diagnostic => "diagnostic",
75        }
76    }
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82
83    #[test]
84    fn default_is_release() {
85        assert_eq!(KernelOptLevel::default(), KernelOptLevel::Release);
86    }
87
88    #[test]
89    fn keep_unreferenced_slots_release() {
90        assert!(!KernelOptLevel::Release.keep_unreferenced_slots());
91    }
92
93    #[test]
94    fn keep_unreferenced_slots_diagnostic() {
95        assert!(KernelOptLevel::Diagnostic.keep_unreferenced_slots());
96    }
97
98    #[test]
99    fn parse_roundtrip() {
100        for lvl in [KernelOptLevel::Release, KernelOptLevel::Diagnostic] {
101            assert_eq!(KernelOptLevel::parse(lvl.as_str()), Ok(lvl));
102        }
103    }
104
105    #[test]
106    fn parse_unknown_returns_err() {
107        assert_eq!(KernelOptLevel::parse("none"), Err("none"));
108        assert_eq!(KernelOptLevel::parse(""), Err(""));
109    }
110}