telltale_vm/vm/runtime_exec/
core.rs1#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
3pub struct VmMemoryUsage {
4 pub session_store: SessionStoreMemoryUsage,
6 pub coroutine_records: usize,
8 pub terminal_coroutines: usize,
10 pub program_count: usize,
12 pub program_instruction_count: usize,
14 pub obs_events: usize,
16 pub effect_trace_entries: usize,
18 pub delegation_audits: usize,
20 pub authority_audits: usize,
22 pub communication_artifacts: usize,
24 pub output_condition_checks: usize,
26 pub retained_bytes: VmRetainedBytes,
28}
29#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
31pub struct VmRetainedBytes {
32 pub session_store: usize,
34 pub coroutines: usize,
36 pub programs: usize,
38 pub resource_states: usize,
40 pub traces: usize,
42 pub replay: usize,
44 pub output_condition_checks: usize,
46 pub scheduler_and_control: usize,
48 pub symbols: usize,
50 pub guard_layer: usize,
52 pub monitor: usize,
54 pub arena: usize,
56 pub total: usize,
58}
59
60fn vm_serialized_bytes<T: Serialize>(value: &T) -> usize {
61 crate::serialization::binary_size(value)
62}
63
64impl VM {
65 fn communication_replay_enabled(&self) -> bool {
66 !matches!(
67 self.config.communication_replay_mode,
68 CommunicationReplayMode::Off
69 )
70 }
71
72 fn intern_load_plan_symbols(&mut self, plan: &crate::session::SessionOpenPlan, sid: SessionId) {
73 for role in plan.roles() {
74 let _: StringId = self.role_symbols.intern(role);
75 }
76 let _: StringId = self
77 .handler_symbols
78 .intern(crate::session::DEFAULT_HANDLER_ID);
79 let edge_handlers: Vec<_> = self
80 .sessions
81 .get(sid)
82 .map(|session| session.edge_handlers.keys().cloned().collect())
83 .unwrap_or_default();
84 for edge in edge_handlers {
85 let _: EdgeId = self.intern_edge(&edge);
86 }
87 }
88
89 #[must_use]
91 pub fn new(config: VMConfig) -> Self {
92 Self::new_with_models(config)
93 }
94
95 fn bind_default_handlers_for_session(&mut self, sid: SessionId) {
96 self.sessions
97 .set_default_handler_for_session(sid, crate::session::DEFAULT_HANDLER_ID.to_string());
98 self.handler_symbols
99 .intern(crate::session::DEFAULT_HANDLER_ID);
100 }
101
102 fn ensure_session_capacity(&self) -> Result<(), VMError> {
103 if self.sessions.active_count() >= self.config.max_sessions {
104 return Err(VMError::TooManySessions {
105 max: self.config.max_sessions,
106 });
107 }
108 Ok(())
109 }
110
111 fn coroutine_runtime_eligible(&self, coro_id: usize) -> bool {
112 let Some(idx) = self.coro_index(coro_id) else {
113 return false;
114 };
115 let role = &self.coroutines[idx].role;
116 !(self.paused_coro_ids.contains(&coro_id)
117 || self.paused_roles.contains(role)
118 || self.crashed_sites.contains(role)
119 || self.timed_out_coro_ids.contains(&coro_id)
120 || self.timed_out_sites.contains_key(role))
121 }
122
123 fn mark_eligibility_dirty(&mut self) {
124 self.eligibility_dirty = true;
125 }
126
127 fn sync_ready_eligibility_for(&mut self, coro_id: usize) {
128 let eligible = self.sched.is_ready(coro_id) && self.coroutine_runtime_eligible(coro_id);
129 let eligibility = if eligible {
130 crate::scheduler::ReadyEligibility::Eligible
131 } else {
132 crate::scheduler::ReadyEligibility::Ineligible
133 };
134 self.sched.set_ready_eligibility(coro_id, eligibility);
135 #[cfg(debug_assertions)]
136 {
137 if eligible {
138 self.eligible_ready.insert(coro_id);
139 } else {
140 self.eligible_ready.remove(&coro_id);
141 }
142 }
143 }
144
145 fn refresh_ready_eligibility(&mut self) {
146 self.sched.clear_ready_eligibility();
147 #[cfg(debug_assertions)]
148 self.eligible_ready.clear();
149 for coro_id in self.sched.ready_set_snapshot() {
150 let eligible = self.coroutine_runtime_eligible(coro_id);
151 let eligibility = if eligible {
152 crate::scheduler::ReadyEligibility::Eligible
153 } else {
154 crate::scheduler::ReadyEligibility::Ineligible
155 };
156 self.sched.set_ready_eligibility(coro_id, eligibility);
157 #[cfg(debug_assertions)]
158 if eligible {
159 self.eligible_ready.insert(coro_id);
160 }
161 }
162 self.eligibility_dirty = false;
163 }
164
165 fn ensure_ready_eligibility(&mut self) {
166 if self.eligibility_dirty {
167 self.refresh_ready_eligibility();
168 }
169 }
170
171 #[cfg(debug_assertions)]
172 fn debug_assert_ready_eligibility_consistent(&self) {
173 for coro_id in &self.eligible_ready {
174 debug_assert!(self.sched.is_ready(*coro_id));
175 debug_assert!(self.coroutine_runtime_eligible(*coro_id));
176 }
177 }
178
179 fn sync_communication_consumption_mode(&mut self) {
180 self.communication_consumption
181 .set_mode(self.config.communication_replay_mode);
182 }
183
184 fn allocate_send_sequence(&mut self, edge: &Edge) -> u64 {
185 if !self.communication_replay_enabled() {
186 return 0;
188 }
189 self.sync_communication_consumption_mode();
190 self.communication_consumption.allocate_send_sequence(edge)
191 }
192
193 fn consume_receive_identity(
194 &mut self,
195 identity: CommunicationIdentity,
196 ) -> Result<CommunicationConsumeResult, CommunicationReplayError> {
197 if !self.communication_replay_enabled() {
198 return Ok(CommunicationConsumeResult {
200 mode: CommunicationReplayMode::Off,
201 pre_root: self.communication_consumption.root(),
202 post_root: self.communication_consumption.root(),
203 consumed_nullifier: None,
204 });
205 }
206 self.sync_communication_consumption_mode();
207 let result = self.communication_consumption.consume_receive(&identity)?;
208 self.communication_consumption_artifacts.push(
209 CommunicationConsumptionArtifact {
210 tick: self.clock.tick,
211 identity,
212 mode: result.mode,
213 pre_root: result.pre_root,
214 post_root: result.post_root,
215 },
216 &self.config.observability_retention,
217 );
218 Ok(result)
219 }
220
221 fn session_open_plan(&mut self, image: &CodeImage) -> &crate::session::SessionOpenPlan {
222 let key = format!("{image:p}");
223 self.session_open_plans.entry(key).or_insert_with(|| {
224 crate::session::SessionOpenPlan::new(&image.roles(), &image.local_types)
225 })
226 }
227
228 fn open_choreography_session(
229 &mut self,
230 plan: &crate::session::SessionOpenPlan,
231 ) -> (SessionId, Vec<String>) {
232 let sid = self.sessions.next_session_id();
233 let roles = plan.roles().to_vec();
234 self.sessions
235 .open_with_sid_from_plan(sid, plan, &self.config.buffer_config);
236 (sid, roles)
237 }
238
239 fn finalize_open_choreography_session(
240 &mut self,
241 sid: SessionId,
242 roles: &[String],
243 plan: &crate::session::SessionOpenPlan,
244 ) -> Result<(), VMError> {
245 self.next_session_id = self.sessions.next_session_id();
246 self.bind_default_handlers_for_session(sid);
247 self.intern_load_plan_symbols(plan, sid);
248 self.monitor.set_kind(sid, SessionKind::Peer);
249 self.resource_states.entry(sid).or_default();
250 self.apply_open_delta(sid)
251 .map_err(VMError::PersistenceError)?;
252 self.obs_trace.push(
253 ObsEvent::Opened {
254 tick: self.clock.tick,
255 session: sid,
256 roles: roles.to_vec(),
257 },
258 &self.config.observability_retention,
259 );
260 Ok(())
261 }
262
263 fn spawn_coroutine_for_role(
264 &mut self,
265 image: &CodeImage,
266 sid: SessionId,
267 role: &str,
268 ) -> Result<(), VMError> {
269 if self.coroutines.len() >= self.config.max_coroutines {
270 return Err(VMError::TooManyCoroutines {
271 max: self.config.max_coroutines,
272 });
273 }
274
275 let program_id = self
276 .programs
277 .intern(image.programs.get(role).cloned().unwrap_or_default());
278 if self.code.is_none() {
279 let program = self
280 .programs
281 .get(program_id)
282 .expect("interned program must exist")
283 .clone();
284 self.code = Some(program);
285 }
286
287 let coro_id = self.next_coro_id;
288 self.next_coro_id += 1;
289
290 let endpoint = Endpoint {
291 sid,
292 role: role.to_string(),
293 };
294 self.role_coroutines
295 .entry(role.to_string())
296 .or_default()
297 .push(coro_id);
298 if self.paused_roles.contains(role) {
299 self.paused_coro_ids.insert(coro_id);
300 }
301 if self.timed_out_sites.contains_key(role) {
302 self.timed_out_coro_ids.insert(coro_id);
303 }
304 let mut coro = Coroutine::new(
305 coro_id,
306 program_id,
307 sid,
308 role.to_string(),
309 self.config.num_registers,
310 self.config.initial_cost_budget,
311 );
312 coro.owned_endpoints.push(endpoint.clone());
313 if !coro.regs.is_empty() {
314 coro.regs[0] = Value::Endpoint(endpoint);
315 }
316 self.sched.add_ready(coro_id);
317 self.coroutines.push(coro);
318 self.coro_slots.insert(coro_id, self.coroutines.len() - 1);
319 self.sync_ready_eligibility_for(coro_id);
320 Ok(())
321 }
322
323 fn spawn_session_coroutines(
324 &mut self,
325 image: &CodeImage,
326 sid: SessionId,
327 roles: &[String],
328 ) -> Result<(), VMError> {
329 for role in roles {
330 self.spawn_coroutine_for_role(image, sid, role)?;
331 }
332 Ok(())
333 }
334
335 #[doc(hidden)]
345 pub fn load_choreography(&mut self, image: &CodeImage) -> Result<SessionId, VMError> {
346 self.ensure_session_capacity()?;
347 image
348 .validate_runtime_shape()
349 .map_err(|reason| VMError::InvalidCodeImage { reason })?;
350 let plan = self.session_open_plan(image).clone();
351 let (sid, roles) = self.open_choreography_session(&plan);
352 self.finalize_open_choreography_session(sid, &roles, &plan)?;
353 self.programs.reserve(image.programs.len());
354 self.coroutines.reserve(roles.len());
355 self.spawn_session_coroutines(image, sid, &roles)?;
356 Ok(sid)
357 }
358}