Skip to main content

telltale_machine/engine/runtime_state/
program_store.rs

1// Immutable program storage with deterministic interning.
2fn default_instruction_cost() -> usize {
3    1
4}
5
6fn default_initial_cost_budget() -> usize {
7    usize::MAX
8}
9
10fn default_config_schema_version() -> u32 {
11    1
12}
13
14fn default_max_payload_bytes() -> usize {
15    64 * 1024
16}
17
18/// Scope identifier type, aligned with Lean's scope representation.
19pub(crate) type ScopeId = usize;
20
21/// Lean-aligned immutable program representation.
22pub type Program = Box<[Instr]>;
23
24/// Immutable program storage with deterministic interning.
25#[derive(Debug, Clone, Default, Serialize, Deserialize)]
26pub struct ProgramStore {
27    programs: Vec<Program>,
28    #[serde(default)]
29    cache: BTreeMap<Vec<u8>, usize>,
30}
31
32impl ProgramStore {
33    /// Create an empty immutable program store.
34    #[must_use]
35    pub fn new() -> Self {
36        Self::default()
37    }
38
39    fn ensure_cache_initialized(&mut self) {
40        if self.cache.len() == self.programs.len() {
41            return;
42        }
43        self.cache.clear();
44        for (idx, program) in self.programs.iter().enumerate() {
45            self.cache.insert(Self::cache_key(program), idx);
46        }
47    }
48
49    fn cache_key(program: &[Instr]) -> Vec<u8> {
50        crate::serialization::binary_encode(program)
51            .expect("program serialization for cache key should succeed")
52    }
53
54    /// Reserve space for additional unique programs.
55    pub fn reserve(&mut self, additional: usize) {
56        self.programs.reserve(additional);
57    }
58
59    /// Intern a program and return its stable index.
60    pub fn intern(&mut self, program: Vec<Instr>) -> usize {
61        self.ensure_cache_initialized();
62        let key = Self::cache_key(&program);
63        if let Some(existing) = self.cache.get(&key) {
64            return *existing;
65        }
66        let program_id = self.programs.len();
67        self.programs.push(program.into_boxed_slice());
68        self.cache.insert(key, program_id);
69        program_id
70    }
71
72    /// Fetch an immutable program by id.
73    #[must_use]
74    pub fn get(&self, program_id: usize) -> Option<&Program> {
75        self.programs.get(program_id)
76    }
77
78    /// Number of unique programs retained by the store.
79    #[must_use]
80    pub fn len(&self) -> usize {
81        self.programs.len()
82    }
83
84    /// Whether the program store is empty.
85    #[must_use]
86    pub fn is_empty(&self) -> bool {
87        self.programs.is_empty()
88    }
89
90    /// Total instruction count across all unique programs.
91    #[must_use]
92    pub fn instruction_count(&self) -> usize {
93        self.programs.iter().map(|program| program.len()).sum()
94    }
95
96    #[cfg(test)]
97    fn replace_for_test(&mut self, program_id: usize, program: Vec<Instr>) {
98        self.ensure_cache_initialized();
99        if let Some(existing) = self.programs.get(program_id) {
100            let key = Self::cache_key(existing);
101            self.cache.remove(&key);
102        }
103        self.programs[program_id] = program.into_boxed_slice();
104        let new_key = Self::cache_key(&self.programs[program_id]);
105        self.cache.insert(new_key, program_id);
106    }
107}
108
109/// Branch list type used in local types.
110type BranchList = Vec<(
111    telltale_types::Label,
112    Option<telltale_types::ValType>,
113    LocalTypeR,
114)>;
115
116// RECURSION_SAFE: structural recursion over a finite runtime value tree.
117pub(crate) fn runtime_value_val_type(value: &Value) -> ValType {
118    match value {
119        Value::Unit => ValType::Unit,
120        Value::Nat(_) => ValType::Nat,
121        Value::Bool(_) => ValType::Bool,
122        Value::Str(_) => ValType::String,
123        Value::Prod(left, right) => ValType::Prod(
124            Box::new(runtime_value_val_type(left)),
125            Box::new(runtime_value_val_type(right)),
126        ),
127        Value::Endpoint(endpoint) => ValType::Chan {
128            sid: endpoint.sid,
129            role: endpoint.role.clone(),
130        },
131    }
132}
133
134// RECURSION_SAFE: structural recursion over a finite runtime value tree.
135pub(crate) fn runtime_value_wire_size_bytes(value: &Value) -> usize {
136    match value {
137        Value::Unit => 1,
138        Value::Nat(_) => 8,
139        Value::Bool(_) => 1,
140        Value::Str(text) => 8_usize.saturating_add(text.len()),
141        Value::Prod(left, right) => 1_usize
142            .saturating_add(runtime_value_wire_size_bytes(left))
143            .saturating_add(runtime_value_wire_size_bytes(right)),
144        Value::Endpoint(endpoint) => 8_usize
145            .saturating_add(8_usize)
146            .saturating_add(endpoint.role.len()),
147    }
148}
149
150// RECURSION_SAFE: structural recursion over finite value/type trees.
151pub(crate) fn runtime_value_matches_val_type(value: &Value, expected: &ValType) -> bool {
152    match (value, expected) {
153        (Value::Unit, ValType::Unit) => true,
154        (Value::Nat(_), ValType::Nat) => true,
155        (Value::Bool(_), ValType::Bool) => true,
156        (Value::Str(_), ValType::String) => true,
157        (Value::Prod(left, right), ValType::Prod(expected_left, expected_right)) => {
158            runtime_value_matches_val_type(left, expected_left)
159                && runtime_value_matches_val_type(right, expected_right)
160        }
161        (Value::Endpoint(endpoint), ValType::Chan { sid, role }) => {
162            endpoint.sid == *sid && endpoint.role == *role
163        }
164        _ => false,
165    }
166}
167
168/// Lean-aligned resource state with commitments and nullifiers.
169#[derive(Debug, Clone, Serialize, Deserialize, Default)]
170pub struct ResourceState {
171    commitments: BTreeSet<crate::verification::Commitment>,
172    nullifiers: BTreeSet<crate::verification::Nullifier>,
173}
174
175impl ResourceState {
176    /// Record a commitment for a value and return the commitment digest.
177    #[must_use]
178    pub fn commit(&mut self, value: &Value) -> crate::verification::Commitment {
179        let commitment = crate::verification::DefaultVerificationModel::commitment(value);
180        self.commitments.insert(commitment);
181        commitment
182    }
183
184    /// Consume a value by inserting its nullifier.
185    ///
186    /// # Errors
187    ///
188    /// Returns an error when the value has already been consumed.
189    pub fn consume(&mut self, value: &Value) -> Result<crate::verification::Nullifier, String> {
190        let nullifier = crate::verification::DefaultVerificationModel::nullifier(value);
191        if self.nullifiers.contains(&nullifier) {
192            return Err("resource already consumed".to_string());
193        }
194        self.nullifiers.insert(nullifier);
195        Ok(nullifier)
196    }
197
198    /// Check whether a value has not yet been consumed.
199    #[must_use]
200    pub fn verify_uncommitted(&self, value: &Value) -> bool {
201        let nullifier = crate::verification::DefaultVerificationModel::nullifier(value);
202        !self.nullifiers.contains(&nullifier)
203    }
204}