1use crate::modalities::fork_join::ForkJoin as ForkJoinCarrier;
4use crate::modalities::sequential::Sequential as SequentialCarrier;
5use crate::modalities::step_graph::StepGraph as StepGraphCarrier;
6use crate::modalities::stream_graph::StreamGraph as StreamGraphCarrier;
7use vstd::prelude::*;
8
9verus! {
10
11#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13#[non_exhaustive]
14pub enum SequentialBuildError {
15 NoSteps,
17 EmptyValueDomain,
19 InitialValueOutOfRange,
21}
22
23pub struct Sequential {
25 inner: SequentialCarrier,
26}
27
28impl Sequential {
29 #[verifier::type_invariant]
30 closed spec fn well_formed(&self) -> bool { self.inner.inv() }
31
32 pub fn new(steps: usize, value_domain_size: u64, initial_value: u64)
34 -> (result: Result<Self, SequentialBuildError>) {
35 if steps == 0 { return Err(SequentialBuildError::NoSteps); }
36 if value_domain_size == 0 { return Err(SequentialBuildError::EmptyValueDomain); }
37 if initial_value >= value_domain_size {
38 return Err(SequentialBuildError::InitialValueOutOfRange);
39 }
40 Ok(Self { inner: SequentialCarrier::new(steps, value_domain_size, initial_value) })
41 }
42
43 pub fn steps(&self) -> usize { self.inner.steps }
45
46 pub fn completed(&self) -> usize { self.inner.pc }
48
49 pub fn value(&self) -> u64 { self.inner.value }
51
52 pub fn is_active(&self) -> bool { self.inner.active }
54
55 pub fn is_done(&self) -> bool { self.inner.pc == self.inner.steps && !self.inner.active }
57
58 #[expect(clippy::indexing_slicing, reason = "the branch proves the history index is in bounds")]
60 pub fn history(&self, index: usize) -> Option<u64> {
61 if index < self.inner.history.len() { Some(self.inner.history[index]) } else { None }
62 }
63
64 #[must_use]
66 pub fn begin_step(&mut self) -> (accepted: bool) {
67 proof { use_type_invariant(&*self); }
68 let mut carrier = sequential_sentinel();
69 core::mem::swap(&mut self.inner, &mut carrier);
70 let accepted = carrier.begin_step();
71 core::mem::swap(&mut self.inner, &mut carrier);
72 accepted
73 }
74
75 #[must_use]
77 pub fn complete_step(&mut self, next_value: u64) -> (accepted: bool) {
78 proof { use_type_invariant(&*self); }
79 let mut carrier = sequential_sentinel();
80 core::mem::swap(&mut self.inner, &mut carrier);
81 let accepted = carrier.complete_step(next_value);
82 core::mem::swap(&mut self.inner, &mut carrier);
83 accepted
84 }
85}
86
87#[derive(Clone, Copy, Debug, Eq, PartialEq)]
89#[non_exhaustive]
90pub enum ForkJoinBuildError {
91 EmptyValueDomain,
93 InitialValueOutOfRange,
95}
96
97#[derive(Clone, Copy, Debug, Eq, PartialEq)]
99pub enum WorkerState {
100 Ready,
102 Running,
104 Complete,
106}
107
108#[derive(Clone, Copy, Debug, Eq, PartialEq)]
110pub enum ForkJoinPhase {
111 Fork,
113 Join,
115 Done,
117}
118
119pub struct ForkJoin {
121 inner: ForkJoinCarrier,
122}
123
124impl ForkJoin {
125 #[verifier::type_invariant]
126 closed spec fn well_formed(&self) -> bool { self.inner.inv() }
127
128 pub fn new(workers: usize, value_domain_size: u64, initial_value: u64)
130 -> (result: Result<Self, ForkJoinBuildError>) {
131 if value_domain_size == 0 { return Err(ForkJoinBuildError::EmptyValueDomain); }
132 if initial_value >= value_domain_size {
133 return Err(ForkJoinBuildError::InitialValueOutOfRange);
134 }
135 Ok(Self { inner: ForkJoinCarrier::new(workers, value_domain_size, initial_value) })
136 }
137
138 pub fn len(&self) -> usize { self.inner.wstate.len() }
140
141 pub fn is_empty(&self) -> bool { self.inner.wstate.is_empty() }
143
144 pub fn phase(&self) -> ForkJoinPhase {
146 if self.inner.phase == 0 { ForkJoinPhase::Fork }
147 else if self.inner.phase == 1 { ForkJoinPhase::Join }
148 else { ForkJoinPhase::Done }
149 }
150
151 pub fn output_ready(&self) -> bool { self.inner.output_ready }
153
154 #[expect(clippy::indexing_slicing, reason = "the branch proves the worker index is in bounds")]
156 pub fn worker_state(&self, worker: usize) -> Option<WorkerState> {
157 if worker >= self.inner.wstate.len() { return None; }
158 Some(if self.inner.wstate[worker] == 0 { WorkerState::Ready }
159 else if self.inner.wstate[worker] == 1 { WorkerState::Running }
160 else { WorkerState::Complete })
161 }
162
163 #[expect(clippy::indexing_slicing, reason = "the branch proves the worker index is in bounds")]
165 pub fn worker_value(&self, worker: usize) -> Option<u64> {
166 if worker < self.inner.wvalue.len() { Some(self.inner.wvalue[worker]) } else { None }
167 }
168
169 #[expect(clippy::indexing_slicing, reason = "the branch proves the output index is in bounds")]
171 pub fn output(&self, worker: usize) -> Option<u64> {
172 if self.inner.output_ready && worker < self.inner.output_snapshot.len() {
173 Some(self.inner.output_snapshot[worker])
174 } else { None }
175 }
176
177 #[must_use]
179 pub fn start_worker(&mut self, worker: usize) -> (accepted: bool) {
180 proof { use_type_invariant(&*self); }
181 let mut carrier = fork_join_sentinel();
182 core::mem::swap(&mut self.inner, &mut carrier);
183 let accepted = carrier.start_worker(worker);
184 core::mem::swap(&mut self.inner, &mut carrier);
185 accepted
186 }
187
188 #[must_use]
190 pub fn complete_worker(&mut self, worker: usize, value: u64) -> (accepted: bool) {
191 proof { use_type_invariant(&*self); }
192 let mut carrier = fork_join_sentinel();
193 core::mem::swap(&mut self.inner, &mut carrier);
194 let accepted = carrier.complete_worker(worker, value);
195 core::mem::swap(&mut self.inner, &mut carrier);
196 accepted
197 }
198
199 #[must_use]
201 pub fn barrier(&mut self) -> (accepted: bool) {
202 proof { use_type_invariant(&*self); }
203 let mut carrier = fork_join_sentinel();
204 core::mem::swap(&mut self.inner, &mut carrier);
205 let accepted = carrier.barrier();
206 core::mem::swap(&mut self.inner, &mut carrier);
207 accepted
208 }
209
210 #[must_use]
212 pub fn produce_output(&mut self) -> (accepted: bool) {
213 proof { use_type_invariant(&*self); }
214 let mut carrier = fork_join_sentinel();
215 core::mem::swap(&mut self.inner, &mut carrier);
216 let accepted = carrier.produce_output();
217 core::mem::swap(&mut self.inner, &mut carrier);
218 accepted
219 }
220}
221
222#[derive(Clone, Copy, Debug, Eq, PartialEq)]
224#[non_exhaustive]
225pub enum StepGraphBuildError {
226 EdgeEndpointOutOfRange,
228 DuplicateEdge,
230}
231
232#[derive(Clone, Copy, Debug, Eq, PartialEq)]
234pub enum StepState {
235 NotReady,
237 Ready,
239 Running,
241 Complete,
243}
244
245pub struct StepGraph {
247 inner: StepGraphCarrier,
248}
249
250impl StepGraph {
251 #[verifier::type_invariant]
252 closed spec fn well_formed(&self) -> bool { self.inner.inv() }
253
254 pub fn new(num_nodes: usize, edges: Vec<(usize, usize)>)
256 -> (result: Result<Self, StepGraphBuildError>) {
257 if !step_edges_valid(&edges, num_nodes) {
258 return Err(StepGraphBuildError::EdgeEndpointOutOfRange);
259 }
260 if !step_edges_distinct(&edges) { return Err(StepGraphBuildError::DuplicateEdge); }
261 Ok(Self { inner: StepGraphCarrier::new(num_nodes, edges) })
262 }
263
264 pub fn len(&self) -> usize { self.inner.num_nodes }
266
267 pub fn is_empty(&self) -> bool { self.inner.num_nodes == 0 }
269
270 pub fn edge_count(&self) -> usize { self.inner.edges.len() }
272
273 #[expect(clippy::indexing_slicing, reason = "the branch proves the edge index is in bounds")]
275 pub fn edge(&self, index: usize) -> Option<(usize, usize)> {
276 if index < self.inner.edges.len() { Some(self.inner.edges[index]) } else { None }
277 }
278
279 #[expect(clippy::indexing_slicing, reason = "the branch proves the node index is in bounds")]
281 pub fn state(&self, node: usize) -> Option<StepState> {
282 if node >= self.inner.nstate.len() { return None; }
283 Some(if self.inner.nstate[node] == 0 { StepState::NotReady }
284 else if self.inner.nstate[node] == 1 { StepState::Ready }
285 else if self.inner.nstate[node] == 2 { StepState::Running }
286 else { StepState::Complete })
287 }
288
289 #[must_use]
291 pub fn become_ready(&mut self, node: usize) -> (accepted: bool) {
292 proof { use_type_invariant(&*self); }
293 let mut carrier = step_graph_sentinel();
294 core::mem::swap(&mut self.inner, &mut carrier);
295 let accepted = carrier.become_ready(node);
296 core::mem::swap(&mut self.inner, &mut carrier);
297 accepted
298 }
299
300 #[must_use]
302 pub fn start(&mut self, node: usize) -> (accepted: bool) {
303 proof { use_type_invariant(&*self); }
304 let mut carrier = step_graph_sentinel();
305 core::mem::swap(&mut self.inner, &mut carrier);
306 let accepted = carrier.start_running(node);
307 core::mem::swap(&mut self.inner, &mut carrier);
308 accepted
309 }
310
311 #[must_use]
313 pub fn complete(&mut self, node: usize) -> (accepted: bool) {
314 proof { use_type_invariant(&*self); }
315 let mut carrier = step_graph_sentinel();
316 core::mem::swap(&mut self.inner, &mut carrier);
317 let accepted = carrier.complete_node(node);
318 core::mem::swap(&mut self.inner, &mut carrier);
319 accepted
320 }
321
322 #[expect(clippy::indexing_slicing, reason = "the loop proves the state index is in bounds")]
324 #[expect(clippy::arithmetic_side_effects, reason = "the loop proves the cursor remains within the vector")]
325 pub fn is_done(&self) -> bool {
326 let mut index = 0;
327 while index < self.inner.nstate.len()
328 invariant index <= self.inner.nstate.len(),
329 decreases self.inner.nstate.len() - index,
330 {
331 if self.inner.nstate[index] != 3 { return false; }
332 index = index + 1;
333 }
334 true
335 }
336}
337
338#[derive(Clone, Copy, Debug, Eq, PartialEq)]
340#[non_exhaustive]
341pub enum StreamGraphBuildError {
342 UnsupportedChainLength,
344 ZeroCapacity,
346 EmptyRecordDomain,
348}
349
350pub struct StreamGraph {
352 inner: StreamGraphCarrier,
353}
354
355impl StreamGraph {
356 #[verifier::type_invariant]
357 closed spec fn well_formed(&self) -> bool { self.inner.inv() }
358
359 pub fn new(chain_length: usize, capacity: usize, max_inputs: usize, record_domain_size: u64)
361 -> (result: Result<Self, StreamGraphBuildError>) {
362 if chain_length != 3 && chain_length != 4 {
363 return Err(StreamGraphBuildError::UnsupportedChainLength);
364 }
365 if capacity == 0 { return Err(StreamGraphBuildError::ZeroCapacity); }
366 if record_domain_size == 0 { return Err(StreamGraphBuildError::EmptyRecordDomain); }
367 Ok(Self { inner: StreamGraphCarrier::new(
368 chain_length, capacity, max_inputs, record_domain_size,
369 ) })
370 }
371
372 pub fn chain_length(&self) -> usize { self.inner.chain_length }
374
375 pub fn capacity(&self) -> usize { self.inner.capacity }
377
378 pub fn ingested(&self) -> usize { self.inner.ingested }
380
381 pub fn emitted(&self) -> usize { self.inner.emitted }
383
384 pub fn first_queue_len(&self) -> usize { self.inner.q1.len() }
386
387 pub fn second_queue_len(&self) -> usize { self.inner.q2.len() }
389
390 pub fn third_queue_len(&self) -> usize { self.inner.q3.len() }
392
393 #[must_use]
395 pub fn ingest(&mut self, value: u64) -> (accepted: bool) {
396 proof { use_type_invariant(&*self); }
397 let mut carrier = stream_graph_sentinel();
398 core::mem::swap(&mut self.inner, &mut carrier);
399 let accepted = carrier.source_ingest(value);
400 core::mem::swap(&mut self.inner, &mut carrier);
401 accepted
402 }
403
404 #[must_use]
406 pub fn advance_first(&mut self) -> (accepted: bool) {
407 proof { use_type_invariant(&*self); }
408 let mut carrier = stream_graph_sentinel();
409 core::mem::swap(&mut self.inner, &mut carrier);
410 let accepted = carrier.middle2_fire();
411 core::mem::swap(&mut self.inner, &mut carrier);
412 accepted
413 }
414
415 #[must_use]
417 pub fn advance_second(&mut self) -> (accepted: bool) {
418 proof { use_type_invariant(&*self); }
419 let mut carrier = stream_graph_sentinel();
420 core::mem::swap(&mut self.inner, &mut carrier);
421 let accepted = carrier.middle3_fire();
422 core::mem::swap(&mut self.inner, &mut carrier);
423 accepted
424 }
425
426 #[expect(clippy::indexing_slicing, reason = "the queue guards prove the sink head is present")]
428 pub fn consume(&mut self) -> (value: Option<u64>) {
429 proof { use_type_invariant(&*self); }
430 let value = if self.inner.chain_length == 3 {
431 if self.inner.q2.is_empty() { return None; }
432 self.inner.q2[0]
433 } else {
434 if self.inner.q3.is_empty() { return None; }
435 self.inner.q3[0]
436 };
437 let mut carrier = stream_graph_sentinel();
438 core::mem::swap(&mut self.inner, &mut carrier);
439 let accepted = carrier.sink_consume();
440 if !accepted {
441 core::mem::swap(&mut self.inner, &mut carrier);
442 return None;
443 }
444 core::mem::swap(&mut self.inner, &mut carrier);
445 Some(value)
446 }
447
448 pub fn is_done(&self) -> bool {
450 self.inner.ingested == self.inner.max_inputs
451 && self.inner.q1.is_empty()
452 && self.inner.q2.is_empty()
453 && self.inner.q3.is_empty()
454 }
455}
456
457#[expect(clippy::indexing_slicing, reason = "the loop proves the edge index is in bounds")]
458#[expect(clippy::arithmetic_side_effects, reason = "the loop proves the cursor remains within the vector")]
459#[expect(clippy::ptr_arg, reason = "Verus sequence-view contracts are stated over Vec in this checked boundary")]
460fn step_edges_valid(edges: &Vec<(usize, usize)>, num_nodes: usize) -> (valid: bool)
461 ensures valid == StepGraphCarrier::edges_valid(edges@, num_nodes),
462{
463 let mut index = 0;
464 while index < edges.len()
465 invariant
466 index <= edges.len(),
467 forall|i: int| 0 <= i < index ==>
468 #[trigger] edges@[i].0 < num_nodes && edges@[i].1 < num_nodes,
469 decreases edges.len() - index,
470 {
471 if edges[index].0 >= num_nodes || edges[index].1 >= num_nodes {
472 assert(!StepGraphCarrier::edges_valid(edges@, num_nodes));
473 return false;
474 }
475 index = index + 1;
476 }
477 true
478}
479
480#[expect(clippy::indexing_slicing, reason = "the nested loops prove both edge indices are in bounds")]
481#[expect(clippy::arithmetic_side_effects, reason = "the loops prove both cursors remain within the vector")]
482#[expect(clippy::ptr_arg, reason = "Verus sequence-view contracts are stated over Vec in this checked boundary")]
483fn step_edges_distinct(edges: &Vec<(usize, usize)>) -> (distinct: bool)
484 ensures distinct == StepGraphCarrier::edges_distinct(edges@),
485{
486 let mut left = 0;
487 while left < edges.len()
488 invariant
489 left <= edges.len(),
490 forall|i: int, j: int| 0 <= i < left && 0 <= j < edges.len() && i != j
491 ==> #[trigger] edges@[i] != #[trigger] edges@[j],
492 decreases edges.len() - left,
493 {
494 let mut right = left + 1;
495 while right < edges.len()
496 invariant
497 left < edges.len(),
498 left + 1 <= right <= edges.len(),
499 forall|j: int| left < j < right ==> edges@[left as int] != edges@[j],
500 decreases edges.len() - right,
501 {
502 if edges[left].0 == edges[right].0 && edges[left].1 == edges[right].1 {
503 assert(!StepGraphCarrier::edges_distinct(edges@));
504 return false;
505 }
506 right = right + 1;
507 }
508 left = left + 1;
509 }
510 true
511}
512
513fn sequential_sentinel() -> (carrier: SequentialCarrier)
514 ensures carrier.inv(),
515{ SequentialCarrier::new(1, 1, 0) }
516
517fn fork_join_sentinel() -> (carrier: ForkJoinCarrier)
518 ensures carrier.inv(),
519{ ForkJoinCarrier::new(0, 1, 0) }
520
521fn step_graph_sentinel() -> (carrier: StepGraphCarrier)
522 ensures carrier.inv(),
523{
524 let edges: Vec<(usize, usize)> = Vec::new();
525 StepGraphCarrier::new(0, edges)
526}
527
528fn stream_graph_sentinel() -> (carrier: StreamGraphCarrier)
529 ensures carrier.inv(),
530{ StreamGraphCarrier::new(3, 1, 0, 1) }
531
532}
533
534macro_rules! impl_error {
535 ($type:ty, { $($variant:path => $message:literal),+ $(,)? }) => {
536 impl core::fmt::Display for $type {
537 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
538 formatter.write_str(match self { $($variant => $message),+ })
539 }
540 }
541 impl std::error::Error for $type {}
542 };
543}
544
545macro_rules! impl_observational_debug {
546 ($type:ty, $name:literal, $($field:literal => $method:ident),+ $(,)?) => {
547 impl core::fmt::Debug for $type {
548 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
549 let mut state = formatter.debug_struct($name);
550 $(state.field($field, &self.$method());)+
551 state.finish()
552 }
553 }
554 };
555}
556
557impl_observational_debug!(Sequential, "Sequential",
558 "steps" => steps,
559 "completed" => completed,
560 "value" => value,
561 "active" => is_active,
562 "done" => is_done,
563);
564impl_observational_debug!(ForkJoin, "ForkJoin",
565 "len" => len,
566 "phase" => phase,
567 "output_ready" => output_ready,
568);
569impl_observational_debug!(StepGraph, "StepGraph",
570 "len" => len,
571 "edge_count" => edge_count,
572 "done" => is_done,
573);
574impl_observational_debug!(StreamGraph, "StreamGraph",
575 "chain_length" => chain_length,
576 "capacity" => capacity,
577 "ingested" => ingested,
578 "emitted" => emitted,
579 "first_queue_len" => first_queue_len,
580 "second_queue_len" => second_queue_len,
581 "third_queue_len" => third_queue_len,
582 "done" => is_done,
583);
584
585impl_error!(SequentialBuildError, {
586 Self::NoSteps => "sequential execution requires at least one step",
587 Self::EmptyValueDomain => "sequential value domain is empty",
588 Self::InitialValueOutOfRange => "initial sequential value is outside its domain",
589});
590impl_error!(ForkJoinBuildError, {
591 Self::EmptyValueDomain => "fork-join value domain is empty",
592 Self::InitialValueOutOfRange => "initial worker value is outside its domain",
593});
594impl_error!(StepGraphBuildError, {
595 Self::EdgeEndpointOutOfRange => "step-graph edge endpoint is outside the node universe",
596 Self::DuplicateEdge => "step graph contains a duplicate edge",
597});
598impl_error!(StreamGraphBuildError, {
599 Self::UnsupportedChainLength => "stream graph supports only three- or four-stage chains",
600 Self::ZeroCapacity => "stream-graph queue capacity must be positive",
601 Self::EmptyRecordDomain => "stream-graph record domain is empty",
602});