impl VM {
fn communication_replay_enabled(&self) -> bool {
!matches!(
self.config.communication_replay_mode,
CommunicationReplayMode::Off
)
}
#[must_use]
pub fn new(config: VMConfig) -> Self {
Self::new_with_models(config)
}
fn bind_default_handlers_for_session(&mut self, sid: SessionId) {
self.sessions.set_default_handler_for_session(
sid,
crate::session::DEFAULT_HANDLER_ID.to_string(),
);
}
fn ensure_session_capacity(&self) -> Result<(), VMError> {
if self.sessions.active_count() >= self.config.max_sessions {
return Err(VMError::TooManySessions {
max: self.config.max_sessions,
});
}
Ok(())
}
fn sync_communication_consumption_mode(&mut self) {
self.communication_consumption
.set_mode(self.config.communication_replay_mode);
}
fn allocate_send_sequence(&mut self, edge: &Edge) -> u64 {
if !self.communication_replay_enabled() {
return 0;
}
self.sync_communication_consumption_mode();
self.communication_consumption.allocate_send_sequence(edge)
}
fn consume_receive_identity(
&mut self,
identity: CommunicationIdentity,
) -> Result<CommunicationConsumeResult, CommunicationReplayError> {
if !self.communication_replay_enabled() {
return Ok(CommunicationConsumeResult {
mode: CommunicationReplayMode::Off,
pre_root: self.communication_consumption.state().root(),
post_root: self.communication_consumption.state().root(),
consumed_nullifier: None,
});
}
self.sync_communication_consumption_mode();
let result = self.communication_consumption.consume_receive(&identity)?;
self.communication_consumption_artifacts
.push(CommunicationConsumptionArtifact {
tick: self.clock.tick,
identity,
mode: result.mode,
pre_root: result.pre_root,
post_root: result.post_root,
});
Ok(result)
}
fn open_choreography_session(
&mut self,
image: &CodeImage,
) -> Result<(SessionId, Vec<String>), VMError> {
image.validate_runtime_shape().map_err(|reason| VMError::InvalidCodeImage { reason })?;
let roles = image.roles();
let sid = self.sessions.next_session_id();
self.sessions.open_with_sid(
sid,
roles.clone(),
&self.config.buffer_config,
&image.local_types,
);
self.next_session_id = self.sessions.next_session_id();
self.bind_default_handlers_for_session(sid);
self.monitor.set_kind(sid, SessionKind::Peer);
self.resource_states
.entry(sid)
.or_default();
self.apply_open_delta(sid)
.map_err(VMError::PersistenceError)?;
self.obs_trace.push(ObsEvent::Opened {
tick: self.clock.tick,
session: sid,
roles: roles.clone(),
});
Ok((sid, roles))
}
fn spawn_coroutine_for_role(
&mut self,
image: &CodeImage,
sid: SessionId,
role: &str,
) -> Result<(), VMError> {
if self.coroutines.len() >= self.config.max_coroutines {
return Err(VMError::TooManyCoroutines {
max: self.config.max_coroutines,
});
}
let program = image.programs.get(role).cloned().unwrap_or_default();
let program_id = self.programs.len();
self.programs.push(program.clone());
if self.code.is_none() {
self.code = Some(program);
}
let coro_id = self.next_coro_id;
self.next_coro_id += 1;
let endpoint = Endpoint {
sid,
role: role.to_string(),
};
let mut coro = Coroutine::new(
coro_id,
program_id,
sid,
role.to_string(),
self.config.num_registers,
self.config.initial_cost_budget,
);
coro.owned_endpoints.push(endpoint.clone());
if !coro.regs.is_empty() {
coro.regs[0] = Value::Endpoint(endpoint);
}
self.sched.add_ready(coro_id);
self.coroutines.push(coro);
Ok(())
}
fn spawn_session_coroutines(
&mut self,
image: &CodeImage,
sid: SessionId,
roles: &[String],
) -> Result<(), VMError> {
for role in roles {
self.spawn_coroutine_for_role(image, sid, role)?;
}
Ok(())
}
pub fn load_choreography(&mut self, image: &CodeImage) -> Result<SessionId, VMError> {
self.ensure_session_capacity()?;
let (sid, roles) = self.open_choreography_session(image)?;
self.spawn_session_coroutines(image, sid, &roles)?;
Ok(sid)
}
#[allow(clippy::too_many_lines)]
pub(crate) fn kernel_step_round(
&mut self,
handler: &dyn EffectHandler,
n: usize,
) -> Result<StepResult, VMError> {
#[cfg(debug_assertions)]
debug_assert!(self.wf_vm_state().is_ok());
if n == 0 {
return Err(VMError::InvalidConcurrency { n });
}
self.last_sched_step = None;
self.clock.advance();
if self.all_done() {
return Ok(StepResult::AllDone);
}
self.ingest_topology_events(handler)?;
self.prune_expired_timeouts();
self.try_unblock_receivers();
let paused_roles = &self.paused_roles;
let crashed_sites = &self.crashed_sites;
let timed_out_sites = &self.timed_out_sites;
let coroutines = &self.coroutines;
let has_eligible = self.sched.any_ready(|id| {
coroutines
.get(id)
.map(|c| {
!paused_roles.contains(&c.role)
&& !crashed_sites.contains(&c.role)
&& !timed_out_sites.contains_key(&c.role)
})
.unwrap_or(false)
});
if !has_eligible {
return Ok(StepResult::Stuck);
}
let Some(coro_id) = VMKernel::select_ready_eligible(
&mut self.sched,
|id| {
coroutines
.get(id)
.map(|c| !c.progress_tokens.is_empty())
.unwrap_or(false)
},
|id| {
coroutines
.get(id)
.map(|c| {
!paused_roles.contains(&c.role)
&& !crashed_sites.contains(&c.role)
&& !timed_out_sites.contains_key(&c.role)
})
.unwrap_or(false)
},
) else {
return Ok(StepResult::Stuck);
};
let result = self.exec_instr(coro_id, handler);
match result {
Ok(ExecOutcome::Continue) => {
self.last_sched_step = Some(SchedStepDebug {
selected_coro: coro_id,
exec_status: SchedExecStatus::Continue,
});
self.sched.reschedule(coro_id);
}
Ok(ExecOutcome::Blocked(reason)) => {
let yielded = matches!(reason, BlockReason::Spawn);
self.last_sched_step = Some(SchedStepDebug {
selected_coro: coro_id,
exec_status: if yielded {
SchedExecStatus::Yielded
} else {
SchedExecStatus::Blocked
},
});
if yielded {
self.sched.reschedule(coro_id);
} else {
self.sched.mark_blocked(coro_id, reason);
}
}
Ok(ExecOutcome::Halted) => {
self.last_sched_step = Some(SchedStepDebug {
selected_coro: coro_id,
exec_status: SchedExecStatus::Halted,
});
self.sched.mark_done(coro_id);
self.obs_trace.push(ObsEvent::Halted {
tick: self.clock.tick,
coro_id,
});
}
Err(fault) => {
self.last_sched_step = Some(SchedStepDebug {
selected_coro: coro_id,
exec_status: SchedExecStatus::Faulted,
});
self.obs_trace.push(ObsEvent::Faulted {
tick: self.clock.tick,
coro_id,
fault: fault.clone(),
});
let Some(idx) = self.coro_index(coro_id) else {
return Err(VMError::Fault { coro_id, fault });
};
self.coroutines[idx].status = CoroStatus::Faulted(fault.clone());
self.sched.mark_done(coro_id);
return Err(VMError::Fault { coro_id, fault });
}
}
if self.all_done() {
#[cfg(debug_assertions)]
debug_assert!(self.wf_vm_state().is_ok());
Ok(StepResult::AllDone)
} else {
#[cfg(debug_assertions)]
debug_assert!(self.wf_vm_state().is_ok());
Ok(StepResult::Continue)
}
}
pub fn step(&mut self, handler: &dyn EffectHandler) -> Result<StepResult, VMError> {
self.step_round(handler, 1)
}
pub fn step_round(
&mut self,
handler: &dyn EffectHandler,
n: usize,
) -> Result<StepResult, VMError> {
VMKernel::step_round(self, handler, n)
}
pub fn run_concurrent(
&mut self,
handler: &dyn EffectHandler,
max_rounds: usize,
concurrency: usize,
) -> Result<RunStatus, VMError> {
VMKernel::run_concurrent(self, handler, max_rounds, concurrency)
}
pub fn run(
&mut self,
handler: &dyn EffectHandler,
max_steps: usize,
) -> Result<RunStatus, VMError> {
VMKernel::run(self, handler, max_steps)
}
pub fn run_replay(
&mut self,
fallback: &dyn EffectHandler,
replay_trace: &[EffectTraceEntry],
max_steps: usize,
) -> Result<RunStatus, VMError> {
self.run_replay_shared(
fallback,
Arc::<[EffectTraceEntry]>::from(replay_trace),
max_steps,
)
}
pub fn run_replay_shared(
&mut self,
fallback: &dyn EffectHandler,
replay_trace: Arc<[EffectTraceEntry]>,
max_steps: usize,
) -> Result<RunStatus, VMError> {
let replay = ReplayEffectHandler::with_fallback(replay_trace, fallback);
self.run(&replay, max_steps)
}
pub fn run_concurrent_replay(
&mut self,
fallback: &dyn EffectHandler,
replay_trace: &[EffectTraceEntry],
max_rounds: usize,
concurrency: usize,
) -> Result<RunStatus, VMError> {
self.run_concurrent_replay_shared(
fallback,
Arc::<[EffectTraceEntry]>::from(replay_trace),
max_rounds,
concurrency,
)
}
pub fn run_concurrent_replay_shared(
&mut self,
fallback: &dyn EffectHandler,
replay_trace: Arc<[EffectTraceEntry]>,
max_rounds: usize,
concurrency: usize,
) -> Result<RunStatus, VMError> {
let replay = ReplayEffectHandler::with_fallback(replay_trace, fallback);
self.run_concurrent(&replay, max_rounds, concurrency)
}
#[must_use]
pub fn trace(&self) -> &[ObsEvent] {
&self.obs_trace
}
#[must_use]
pub fn obs_trace(&self) -> &[ObsEvent] {
&self.obs_trace
}
#[must_use]
pub fn role_symbol_count(&self) -> usize {
self.role_symbols.len()
}
#[must_use]
pub fn label_symbol_count(&self) -> usize {
self.label_symbols.len()
}
#[must_use]
pub fn config(&self) -> &VMConfig {
&self.config
}
#[must_use]
pub fn last_sched_step(&self) -> Option<&SchedStepDebug> {
self.last_sched_step.as_ref()
}
#[must_use]
pub fn scheduler_step_count(&self) -> usize {
self.sched.step_count()
}
#[must_use]
pub fn coroutine_count(&self) -> usize {
self.coroutines.len()
}
#[must_use]
pub fn next_session_id(&self) -> SessionId {
self.sessions.next_session_id()
}
#[must_use]
pub fn session_count(&self) -> usize {
self.sessions.active_count()
}
#[must_use]
pub fn output_condition_checks(&self) -> &[OutputConditionCheck] {
&self.output_condition_checks
}
#[must_use]
pub fn effect_trace(&self) -> &[EffectTraceEntry] {
&self.effect_trace
}
#[must_use]
pub fn communication_replay_root(&self) -> crate::verification::Hash {
self.communication_consumption.state().root()
}
#[must_use]
pub fn communication_consumption_artifacts(&self) -> &[CommunicationConsumptionArtifact] {
&self.communication_consumption_artifacts
}
#[must_use]
pub fn canonical_replay_fragment(&self) -> CanonicalReplayFragmentV1 {
let partitioned_edges = self.partitioned_edges.iter().cloned().collect();
let corrupted_edges = self
.corrupted_edges
.iter()
.map(|(edge, corruption)| (edge.clone(), *corruption))
.collect();
let timed_out_sites = self
.timed_out_sites
.iter()
.map(|(site, until_tick)| (site.clone(), *until_tick))
.collect();
canonical_replay_fragment_v1(
&self.obs_trace,
&self.effect_trace,
self.crashed_sites.iter().cloned().collect(),
partitioned_edges,
corrupted_edges,
timed_out_sites,
self.config.effect_determinism_tier,
self.config.communication_replay_mode,
Some(self.communication_consumption.state().root()),
self.communication_consumption_artifacts.clone(),
)
}
#[must_use]
pub fn crashed_sites(&self) -> &BTreeSet<SiteId> {
&self.crashed_sites
}
#[must_use]
pub fn partitioned_edges(&self) -> &BTreeSet<(SiteId, SiteId)> {
&self.partitioned_edges
}
#[must_use]
pub fn corrupted_edges(&self) -> &BTreeMap<(SiteId, SiteId), CorruptionType> {
&self.corrupted_edges
}
#[must_use]
pub fn timed_out_sites(&self) -> &BTreeMap<SiteId, u64> {
&self.timed_out_sites
}
}