Skip to main content

telltale_vm/vm/runtime_state/
program_store.rs

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