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