1use crate::execution_api::StepState as StepGraphNodeState;
4use vstd::prelude::*;
5
6verus! {
7
8pub open spec fn state_rank(state: StepGraphNodeState) -> int {
10 match state {
11 StepGraphNodeState::NotReady => 0,
12 StepGraphNodeState::Ready => 1,
13 StepGraphNodeState::Running => 2,
14 StepGraphNodeState::Complete => 3,
15 }
16}
17
18pub open spec fn states_monotone(
20 before: Seq<StepGraphNodeState>,
21 after: Seq<StepGraphNodeState>,
22) -> bool {
23 &&& after.len() == before.len()
24 &&& forall|i: int| 0 <= i < before.len() ==>
25 state_rank(after[i]) >= state_rank(before[i])
26}
27
28pub open spec fn become_ready_action(
30 before: Seq<StepGraphNodeState>,
31 after: Seq<StepGraphNodeState>,
32 node: int,
33 eligible: bool,
34 accepted: bool,
35) -> bool {
36 let enabled = 0 <= node < before.len()
37 && before[node] == StepGraphNodeState::NotReady
38 && eligible;
39 &&& accepted == enabled
40 &&& after == if accepted {
41 before.update(node, StepGraphNodeState::Ready)
42 } else {
43 before
44 }
45}
46
47pub open spec fn start_running_action(
49 before: Seq<StepGraphNodeState>,
50 after: Seq<StepGraphNodeState>,
51 node: int,
52 selected: bool,
53 accepted: bool,
54) -> bool {
55 let enabled = 0 <= node < before.len()
56 && selected
57 && before[node] == StepGraphNodeState::Ready;
58 &&& accepted == enabled
59 &&& after == if accepted {
60 before.update(node, StepGraphNodeState::Running)
61 } else {
62 before
63 }
64}
65
66pub open spec fn complete_node_action(
68 before: Seq<StepGraphNodeState>,
69 after: Seq<StepGraphNodeState>,
70 node: int,
71 selected: bool,
72 accepted: bool,
73) -> bool {
74 let enabled = 0 <= node < before.len()
75 && selected
76 && before[node] == StepGraphNodeState::Running;
77 &&& accepted == enabled
78 &&& after == if accepted {
79 before.update(node, StepGraphNodeState::Complete)
80 } else {
81 before
82 }
83}
84
85pub struct StepGraph {
87 pub num_nodes: usize,
89 pub edges: Vec<(usize, usize)>,
91 pub nstate: Vec<StepGraphNodeState>,
93}
94
95impl StepGraph {
96 pub open spec fn edges_valid(edges: Seq<(usize, usize)>, num_nodes: usize) -> bool {
98 forall|i: int| 0 <= i < edges.len() ==>
99 #[trigger] edges[i].0 < num_nodes && edges[i].1 < num_nodes
100 }
101
102 pub open spec fn edges_distinct(edges: Seq<(usize, usize)>) -> bool {
104 forall|i: int, j: int|
105 0 <= i < edges.len() && 0 <= j < edges.len() && i != j
106 ==> #[trigger] edges[i] != #[trigger] edges[j]
107 }
108
109 pub open spec fn has_predecessor_in(edges: Seq<(usize, usize)>, node: usize) -> bool {
111 exists|i: int| 0 <= i < edges.len() && edges[i].1 == node
112 }
113
114 pub open spec fn predecessors_complete_in(
116 edges: Seq<(usize, usize)>, states: Seq<StepGraphNodeState>, node: usize,
117 ) -> bool {
118 forall|i: int| 0 <= i < edges.len() && edges[i].1 == node
119 ==> #[trigger] states[edges[i].0 as int] == StepGraphNodeState::Complete
120 }
121
122 pub open spec fn type_invariant(&self) -> bool {
124 &&& self.nstate@.len() == self.num_nodes
125 &&& Self::edges_valid(self.edges@, self.num_nodes)
126 &&& Self::edges_distinct(self.edges@)
127 }
128
129 pub open spec fn eligibility_closed(&self) -> bool {
131 forall|n: usize| n < self.num_nodes
132 && #[trigger] self.nstate@[n as int] != StepGraphNodeState::NotReady
133 ==> Self::predecessors_complete_in(self.edges@, self.nstate@, n)
134 }
135
136 pub open spec fn no_run_before_predecessors(&self) -> bool {
139 forall|n: usize| n < self.num_nodes
140 && (#[trigger] self.nstate@[n as int] == StepGraphNodeState::Running
141 || self.nstate@[n as int] == StepGraphNodeState::Complete)
142 ==> Self::predecessors_complete_in(self.edges@, self.nstate@, n)
143 }
144
145 pub open spec fn inv(&self) -> bool {
147 self.type_invariant() && self.eligibility_closed()
148 }
149
150 #[expect(clippy::ptr_arg, reason = "Verus sequence-view contracts are stated over Vec for StepGraph edges")]
151 #[expect(clippy::indexing_slicing, reason = "Verus proves the predecessor cursor remains in bounds")]
152 #[expect(clippy::arithmetic_side_effects, reason = "Verus proves the predecessor cursor increment remains in bounds")]
153 fn has_predecessor_exec(edges: &Vec<(usize, usize)>, node: usize) -> (b: bool)
154 ensures b == Self::has_predecessor_in(edges@, node),
155 {
156 let mut i = 0;
157 while i < edges.len()
158 invariant
159 i <= edges.len(),
160 forall|k: int| 0 <= k < i ==> edges@[k].1 != node,
161 decreases edges.len() - i,
162 {
163 if edges[i].1 == node {
164 assert(Self::has_predecessor_in(edges@, node));
165 return true;
166 }
167 i += 1;
168 }
169 false
170 }
171
172 #[expect(clippy::indexing_slicing, reason = "the type invariant and loop invariant bound edge and predecessor-state indices")]
173 #[expect(clippy::arithmetic_side_effects, reason = "Verus proves the predecessor-completion cursor increment remains in bounds")]
174 fn predecessors_complete_exec(&self, node: usize) -> (b: bool)
175 requires
176 self.type_invariant(),
177 node < self.num_nodes,
178 ensures b == Self::predecessors_complete_in(self.edges@, self.nstate@, node),
179 {
180 let mut i = 0;
181 while i < self.edges.len()
182 invariant
183 i <= self.edges.len(),
184 self.type_invariant(),
185 forall|k: int| 0 <= k < i && self.edges@[k].1 == node
186 ==> self.nstate@[self.edges@[k].0 as int]
187 == StepGraphNodeState::Complete,
188 decreases self.edges.len() - i,
189 {
190 if self.edges[i].1 == node
191 && !matches!(
192 self.nstate[self.edges[i].0],
193 StepGraphNodeState::Complete
194 )
195 {
196 assert(!Self::predecessors_complete_in(self.edges@, self.nstate@, node));
197 return false;
198 }
199 i += 1;
200 }
201 true
202 }
203
204 #[expect(clippy::arithmetic_side_effects, reason = "Verus proves the state-construction cursor remains within the node bound")]
205 pub fn new(num_nodes: usize, edges: Vec<(usize, usize)>) -> (s: StepGraph)
207 requires
208 Self::edges_valid(edges@, num_nodes),
209 Self::edges_distinct(edges@),
210 ensures
211 s.num_nodes == num_nodes,
212 s.edges@ == edges@,
213 s.nstate@.len() == num_nodes,
214 forall|n: usize| n < num_nodes ==>
215 #[trigger] s.nstate@[n as int]
216 == if Self::has_predecessor_in(edges@, n) {
217 StepGraphNodeState::NotReady
218 } else {
219 StepGraphNodeState::Ready
220 },
221 s.inv(),
222 s.no_run_before_predecessors(),
223 {
224 let mut nstate = Vec::new();
225 let mut n = 0;
226 while n < num_nodes
227 invariant
228 n <= num_nodes,
229 nstate@.len() == n,
230 forall|k: usize| k < n ==>
231 #[trigger] nstate@[k as int]
232 == if Self::has_predecessor_in(edges@, k) {
233 StepGraphNodeState::NotReady
234 } else {
235 StepGraphNodeState::Ready
236 },
237 decreases num_nodes - n,
238 {
239 if Self::has_predecessor_exec(&edges, n) {
240 nstate.push(StepGraphNodeState::NotReady);
241 } else {
242 nstate.push(StepGraphNodeState::Ready);
243 }
244 n += 1;
245 }
246 let s = StepGraph { num_nodes, edges, nstate };
247 assert(s.eligibility_closed()) by {
248 assert forall|node: usize| node < s.num_nodes
249 && #[trigger] s.nstate@[node as int] != StepGraphNodeState::NotReady
250 implies Self::predecessors_complete_in(s.edges@, s.nstate@, node) by {
251 assert(!Self::has_predecessor_in(s.edges@, node));
252 }
253 }
254 s
255 }
256
257 #[expect(clippy::indexing_slicing, reason = "the action guard and Verus invariant bound the node-state index")]
258 pub fn become_ready(&mut self, node: usize) -> (accepted: bool)
260 requires old(self).inv(),
261 ensures
262 final(self).num_nodes == old(self).num_nodes,
263 final(self).edges@ == old(self).edges@,
264 become_ready_action(
265 old(self).nstate@,
266 final(self).nstate@,
267 node as int,
268 Self::has_predecessor_in(old(self).edges@, node)
269 && Self::predecessors_complete_in(
270 old(self).edges@,
271 old(self).nstate@,
272 node,
273 ),
274 accepted,
275 ),
276 states_monotone(old(self).nstate@, final(self).nstate@),
277 final(self).inv(),
278 final(self).no_run_before_predecessors(),
279 {
280 if node < self.num_nodes
281 && matches!(self.nstate[node], StepGraphNodeState::NotReady)
282 && Self::has_predecessor_exec(&self.edges, node)
283 && self.predecessors_complete_exec(node)
284 {
285 let ghost old_states = self.nstate@;
286 self.nstate.set(node, StepGraphNodeState::Ready);
287 assert(self.eligibility_closed()) by {
288 assert forall|m: usize| m < self.num_nodes
289 && #[trigger] self.nstate@[m as int] != StepGraphNodeState::NotReady
290 implies Self::predecessors_complete_in(self.edges@, self.nstate@, m) by {
291 if m == node {
292 assert forall|e: int| 0 <= e < self.edges@.len()
293 && self.edges@[e].1 == m
294 implies #[trigger] self.nstate@[self.edges@[e].0 as int]
295 == StepGraphNodeState::Complete by {
296 let p = self.edges@[e].0;
297 assert(old_states[p as int] == StepGraphNodeState::Complete);
298 assert(p != node);
299 }
300 } else {
301 assert(Self::predecessors_complete_in(self.edges@, old_states, m));
302 assert forall|e: int| 0 <= e < self.edges@.len()
303 && self.edges@[e].1 == m
304 implies #[trigger] self.nstate@[self.edges@[e].0 as int]
305 == StepGraphNodeState::Complete by {
306 let p = self.edges@[e].0;
307 assert(old_states[p as int] == StepGraphNodeState::Complete);
308 assert(p != node);
309 }
310 }
311 }
312 }
313 true
314 } else {
315 false
316 }
317 }
318
319 #[expect(clippy::indexing_slicing, reason = "the action guard and Verus invariant bound the node-state index")]
320 pub fn start_running(&mut self, node: usize) -> (accepted: bool)
322 requires old(self).inv(),
323 ensures
324 final(self).num_nodes == old(self).num_nodes,
325 final(self).edges@ == old(self).edges@,
326 start_running_action(
327 old(self).nstate@,
328 final(self).nstate@,
329 node as int,
330 true,
331 accepted,
332 ),
333 states_monotone(old(self).nstate@, final(self).nstate@),
334 final(self).inv(),
335 final(self).no_run_before_predecessors(),
336 {
337 if node < self.num_nodes && matches!(self.nstate[node], StepGraphNodeState::Ready) {
338 let ghost old_states = self.nstate@;
339 self.nstate.set(node, StepGraphNodeState::Running);
340 assert(self.eligibility_closed()) by {
341 assert forall|m: usize| m < self.num_nodes
342 && #[trigger] self.nstate@[m as int] != StepGraphNodeState::NotReady
343 implies Self::predecessors_complete_in(self.edges@, self.nstate@, m) by {
344 assert(Self::predecessors_complete_in(self.edges@, old_states, m));
345 assert forall|e: int| 0 <= e < self.edges@.len()
346 && self.edges@[e].1 == m
347 implies #[trigger] self.nstate@[self.edges@[e].0 as int]
348 == StepGraphNodeState::Complete by {
349 let p = self.edges@[e].0;
350 assert(old_states[p as int] == StepGraphNodeState::Complete);
351 assert(p != node);
352 }
353 }
354 }
355 true
356 } else {
357 false
358 }
359 }
360
361 #[expect(clippy::indexing_slicing, reason = "the action guard and Verus invariant bound the node-state index")]
362 pub fn complete_node(&mut self, node: usize) -> (accepted: bool)
364 requires old(self).inv(),
365 ensures
366 final(self).num_nodes == old(self).num_nodes,
367 final(self).edges@ == old(self).edges@,
368 complete_node_action(
369 old(self).nstate@,
370 final(self).nstate@,
371 node as int,
372 true,
373 accepted,
374 ),
375 states_monotone(old(self).nstate@, final(self).nstate@),
376 final(self).inv(),
377 final(self).no_run_before_predecessors(),
378 {
379 if node < self.num_nodes && matches!(self.nstate[node], StepGraphNodeState::Running) {
380 let ghost old_states = self.nstate@;
381 self.nstate.set(node, StepGraphNodeState::Complete);
382 assert(self.eligibility_closed()) by {
383 assert forall|m: usize| m < self.num_nodes
384 && #[trigger] self.nstate@[m as int] != StepGraphNodeState::NotReady
385 implies Self::predecessors_complete_in(self.edges@, self.nstate@, m) by {
386 assert(Self::predecessors_complete_in(self.edges@, old_states, m));
387 assert forall|e: int| 0 <= e < self.edges@.len()
388 && self.edges@[e].1 == m
389 implies #[trigger] self.nstate@[self.edges@[e].0 as int]
390 == StepGraphNodeState::Complete by {
391 let p = self.edges@[e].0;
392 if p != node {
393 assert(self.nstate@[p as int] == old_states[p as int]);
394 }
395 }
396 }
397 }
398 true
399 } else {
400 false
401 }
402 }
403
404 #[expect(clippy::indexing_slicing, reason = "Verus proves the completion cursor remains in bounds")]
405 #[expect(clippy::arithmetic_side_effects, reason = "Verus proves the completion cursor increment remains in bounds")]
406 pub fn done_stuttering(&mut self) -> (enabled: bool)
408 requires old(self).inv(),
409 ensures
410 enabled == (forall|i: int| 0 <= i < old(self).nstate@.len()
411 ==> #[trigger] old(self).nstate@[i] == StepGraphNodeState::Complete),
412 final(self).num_nodes == old(self).num_nodes,
413 final(self).edges@ == old(self).edges@,
414 final(self).nstate@ == old(self).nstate@,
415 final(self).inv(),
416 {
417 let mut i = 0;
418 while i < self.nstate.len()
419 invariant
420 i <= self.nstate.len(),
421 self.inv(),
422 forall|k: int| 0 <= k < i ==>
423 self.nstate@[k] == StepGraphNodeState::Complete,
424 decreases self.nstate.len() - i,
425 {
426 if !matches!(self.nstate[i], StepGraphNodeState::Complete) {
427 return false;
428 }
429 i += 1;
430 }
431 true
432 }
433}
434
435}