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