metrale_circuit/state_ops.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-09-30: The state programs of a circuit (M5, LIFECYCLE-DESIGN.md section 3.5): the small
4//! programs that move recurrent state between its places around a speculative verify, and reset
5//! a slot, as nodes over the circuit's declared states. Legacy runs them as host code that sizes
6//! each copy itself (`verify_a_ssm.rs`, `async_chkpt.rs`, `ssm_pool_slots.rs`), with the
7//! GatedDeltaNet conv formula, which is 0 bytes for a Mamba2 layer; here every node is sized from
8//! its state's declaration, so a Mamba2 layer's copies are its own.
9//!
10//! Owner: metrale-circuit.
11//! Invariants:
12//! - The programs are generic over recurrent state kinds: every recurrent state of the target
13//! (`Section::Main`) gets one node in each program, in state order.
14//! - A node's bytes are one unit of its state ([`StatePlan`] at one slot), so a program's bytes
15//! are what one sequence's copies move.
16
17use std::collections::BTreeMap;
18
19use crate::ir::{Circuit, Section};
20use crate::state::{Holding, StateDtype, StateError, StateInputs, StateKind, StatePlan};
21
22/// 2026-09-30: Where one sequence's unit of a recurrent state lives.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub enum StatePlace {
25 /// 2026-09-30: The slot's live state.
26 Live,
27 /// 2026-09-30: The pre-verify checkpoint.
28 Checkpoint,
29 /// 2026-09-30: The verify intermediate after the last accepted row.
30 AcceptedStep,
31}
32
33/// 2026-09-30: What a state-program node does.
34#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
35pub enum StateOp {
36 /// 2026-09-30: Copy one unit from `from` to `to`.
37 Copy { from: StatePlace, to: StatePlace },
38 /// 2026-09-30: Zero the live unit.
39 Zero,
40}
41
42/// 2026-09-30: The state programs.
43#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
44pub enum StateProgramId {
45 /// 2026-09-30: Before a verify: live to checkpoint (`checkpoint_ssm_states_dispatch`).
46 VerifyCheckpoint,
47 /// 2026-09-30: A verify that accepted nothing: checkpoint to live (`rollback_ssm_states`).
48 VerifyRollback,
49 /// 2026-09-30: A verify that accepted a prefix: the accepted row's intermediate to live
50 /// (`commit_accepted_prefix_dispatch`).
51 CommitAccepted,
52 /// 2026-09-30: A slot handed to a new sequence: zero its live state (`zero_slot`).
53 SlotZero,
54}
55
56impl StateProgramId {
57 /// 2026-09-30: Every program, in this order.
58 pub const ALL: [StateProgramId; 4] = [
59 Self::VerifyCheckpoint,
60 Self::VerifyRollback,
61 Self::CommitAccepted,
62 Self::SlotZero,
63 ];
64
65 /// 2026-09-30: The op each of its nodes runs.
66 pub fn op(self) -> StateOp {
67 match self {
68 Self::VerifyCheckpoint => StateOp::Copy {
69 from: StatePlace::Live,
70 to: StatePlace::Checkpoint,
71 },
72 Self::VerifyRollback => StateOp::Copy {
73 from: StatePlace::Checkpoint,
74 to: StatePlace::Live,
75 },
76 Self::CommitAccepted => StateOp::Copy {
77 from: StatePlace::AcceptedStep,
78 to: StatePlace::Live,
79 },
80 Self::SlotZero => StateOp::Zero,
81 }
82 }
83
84 /// 2026-09-30: The spelling used in node ids.
85 pub fn name(self) -> &'static str {
86 match self {
87 Self::VerifyCheckpoint => "verify_checkpoint",
88 Self::VerifyRollback => "verify_rollback",
89 Self::CommitAccepted => "commit_accepted",
90 Self::SlotZero => "slot_zero",
91 }
92 }
93}
94
95/// 2026-09-30: One node of a state program.
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub struct StateOpNode {
98 /// 2026-09-30: `<program>.<state id>` (`verify_rollback.l4.mamba.conv`).
99 pub id: String,
100 /// 2026-09-30: Index into [`Circuit::states`].
101 pub state: usize,
102 /// 2026-09-30: What it does.
103 pub op: StateOp,
104}
105
106/// 2026-09-30: One state program.
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct StateProgram {
109 pub id: StateProgramId,
110 pub nodes: Vec<StateOpNode>,
111}
112
113impl StateProgram {
114 /// 2026-09-30: Bytes one sequence's run of the program moves (or zeroes): one unit of each
115 /// node's state, its keyed format from `formats`.
116 pub fn bytes(
117 &self,
118 circuit: &Circuit,
119 formats: &BTreeMap<String, StateDtype>,
120 ) -> Result<u64, StateError> {
121 let decls: Vec<_> = self
122 .nodes
123 .iter()
124 .map(|n| circuit.states[n.state].clone())
125 .collect();
126 let plan = StatePlan::new(
127 &decls,
128 &StateInputs {
129 formats: formats.clone(),
130 slots: 1,
131 verify: None,
132 kv: None,
133 draft_kv: None,
134 },
135 )?;
136 Ok(plan.bytes_where(|t| t.holding == Holding::Live))
137 }
138}
139
140/// 2026-09-30: Every state program of `circuit`, one node per recurrent target state.
141pub fn state_programs(circuit: &Circuit) -> Vec<StateProgram> {
142 StateProgramId::ALL
143 .into_iter()
144 .map(|id| StateProgram {
145 id,
146 nodes: circuit
147 .states
148 .iter()
149 .enumerate()
150 .filter(|(_, s)| s.kind == StateKind::Recurrent && s.section == Section::Main)
151 .map(|(i, s)| StateOpNode {
152 id: format!("{}.{}", id.name(), s.id),
153 state: i,
154 op: id.op(),
155 })
156 .collect(),
157 })
158 .collect()
159}
160
161#[cfg(test)]
162#[path = "state_ops_tests.rs"]
163mod state_ops_tests;