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