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 {
138 forall|n: usize| n < self.num_nodes
139 && (#[trigger] self.nstate@[n as int] == StepGraphNodeState::Running
140 || self.nstate@[n as int] == StepGraphNodeState::Complete)
141 ==> Self::predecessors_complete_in(self.edges@, self.nstate@, n)
142 }
143
144 pub open spec fn inv(&self) -> bool {
146 self.type_invariant() && self.eligibility_closed()
147 }
148
149 #[expect(clippy::ptr_arg, reason = "Verus sequence-view contracts are stated over Vec for StepGraph edges")]
150 #[expect(clippy::indexing_slicing, reason = "Verus proves the predecessor cursor remains in bounds")]
151 #[expect(clippy::arithmetic_side_effects, reason = "Verus proves the predecessor cursor increment remains in bounds")]
152 fn has_predecessor_exec(edges: &Vec<(usize, usize)>, node: usize) -> (b: bool)
153 ensures b == Self::has_predecessor_in(edges@, node),
154 {
155 let mut i = 0;
156 while i < edges.len()
157 invariant
158 i <= edges.len(),
159 forall|k: int| 0 <= k < i ==> edges@[k].1 != node,
160 decreases edges.len() - i,
161 {
162 if edges[i].1 == node {
163 assert(Self::has_predecessor_in(edges@, node));
164 return true;
165 }
166 i += 1;
167 }
168 false
169 }
170
171 #[expect(clippy::indexing_slicing, reason = "the type invariant and loop invariant bound edge and predecessor-state indices")]
172 #[expect(clippy::arithmetic_side_effects, reason = "Verus proves the predecessor-completion cursor increment remains in bounds")]
173 fn predecessors_complete_exec(&self, node: usize) -> (b: bool)
174 requires
175 self.type_invariant(),
176 node < self.num_nodes,
177 ensures b == Self::predecessors_complete_in(self.edges@, self.nstate@, node),
178 {
179 let mut i = 0;
180 while i < self.edges.len()
181 invariant
182 i <= self.edges.len(),
183 self.type_invariant(),
184 forall|k: int| 0 <= k < i && self.edges@[k].1 == node
185 ==> self.nstate@[self.edges@[k].0 as int]
186 == StepGraphNodeState::Complete,
187 decreases self.edges.len() - i,
188 {
189 if self.edges[i].1 == node
190 && !matches!(
191 self.nstate[self.edges[i].0],
192 StepGraphNodeState::Complete
193 )
194 {
195 assert(!Self::predecessors_complete_in(self.edges@, self.nstate@, node));
196 return false;
197 }
198 i += 1;
199 }
200 true
201 }
202
203 #[expect(clippy::arithmetic_side_effects, reason = "Verus proves the state-construction cursor remains within the node bound")]
204 pub fn new(num_nodes: usize, edges: Vec<(usize, usize)>) -> (s: StepGraph)
206 requires
207 Self::edges_valid(edges@, num_nodes),
208 Self::edges_distinct(edges@),
209 ensures
210 s.num_nodes == num_nodes,
211 s.edges@ == edges@,
212 s.nstate@.len() == num_nodes,
213 forall|n: usize| n < num_nodes ==>
214 #[trigger] s.nstate@[n as int]
215 == if Self::has_predecessor_in(edges@, n) {
216 StepGraphNodeState::NotReady
217 } else {
218 StepGraphNodeState::Ready
219 },
220 s.inv(),
221 s.no_run_before_predecessors(),
222 {
223 let mut nstate = Vec::new();
224 let mut n = 0;
225 while n < num_nodes
226 invariant
227 n <= num_nodes,
228 nstate@.len() == n,
229 forall|k: usize| k < n ==>
230 #[trigger] nstate@[k as int]
231 == if Self::has_predecessor_in(edges@, k) {
232 StepGraphNodeState::NotReady
233 } else {
234 StepGraphNodeState::Ready
235 },
236 decreases num_nodes - n,
237 {
238 if Self::has_predecessor_exec(&edges, n) {
239 nstate.push(StepGraphNodeState::NotReady);
240 } else {
241 nstate.push(StepGraphNodeState::Ready);
242 }
243 n += 1;
244 }
245 let s = StepGraph { num_nodes, edges, nstate };
246 assert(s.eligibility_closed()) by {
247 assert forall|node: usize| node < s.num_nodes
248 && #[trigger] s.nstate@[node as int] != StepGraphNodeState::NotReady
249 implies Self::predecessors_complete_in(s.edges@, s.nstate@, node) by {
250 assert(!Self::has_predecessor_in(s.edges@, node));
251 }
252 }
253 s
254 }
255
256 #[expect(clippy::indexing_slicing, reason = "the action guard and Verus invariant bound the node-state index")]
257 pub fn become_ready(&mut self, node: usize) -> (accepted: bool)
259 requires old(self).inv(),
260 ensures
261 final(self).num_nodes == old(self).num_nodes,
262 final(self).edges@ == old(self).edges@,
263 become_ready_action(
264 old(self).nstate@,
265 final(self).nstate@,
266 node as int,
267 Self::has_predecessor_in(old(self).edges@, node)
268 && Self::predecessors_complete_in(
269 old(self).edges@,
270 old(self).nstate@,
271 node,
272 ),
273 accepted,
274 ),
275 states_monotone(old(self).nstate@, final(self).nstate@),
276 final(self).inv(),
277 final(self).no_run_before_predecessors(),
278 {
279 if node < self.num_nodes
280 && matches!(self.nstate[node], StepGraphNodeState::NotReady)
281 && Self::has_predecessor_exec(&self.edges, node)
282 && self.predecessors_complete_exec(node)
283 {
284 let ghost old_states = self.nstate@;
285 self.nstate.set(node, StepGraphNodeState::Ready);
286 assert(self.eligibility_closed()) by {
287 assert forall|m: usize| m < self.num_nodes
288 && #[trigger] self.nstate@[m as int] != StepGraphNodeState::NotReady
289 implies Self::predecessors_complete_in(self.edges@, self.nstate@, m) by {
290 if m == node {
291 assert forall|e: int| 0 <= e < self.edges@.len()
292 && self.edges@[e].1 == m
293 implies #[trigger] self.nstate@[self.edges@[e].0 as int]
294 == StepGraphNodeState::Complete by {
295 let p = self.edges@[e].0;
296 assert(old_states[p as int] == StepGraphNodeState::Complete);
297 assert(p != node);
298 }
299 } else {
300 assert(Self::predecessors_complete_in(self.edges@, old_states, m));
301 assert forall|e: int| 0 <= e < self.edges@.len()
302 && self.edges@[e].1 == m
303 implies #[trigger] self.nstate@[self.edges@[e].0 as int]
304 == StepGraphNodeState::Complete by {
305 let p = self.edges@[e].0;
306 assert(old_states[p as int] == StepGraphNodeState::Complete);
307 assert(p != node);
308 }
309 }
310 }
311 }
312 true
313 } else {
314 false
315 }
316 }
317
318 #[expect(clippy::indexing_slicing, reason = "the action guard and Verus invariant bound the node-state index")]
319 pub fn start_running(&mut self, node: usize) -> (accepted: bool)
321 requires old(self).inv(),
322 ensures
323 final(self).num_nodes == old(self).num_nodes,
324 final(self).edges@ == old(self).edges@,
325 start_running_action(
326 old(self).nstate@,
327 final(self).nstate@,
328 node as int,
329 true,
330 accepted,
331 ),
332 states_monotone(old(self).nstate@, final(self).nstate@),
333 final(self).inv(),
334 final(self).no_run_before_predecessors(),
335 {
336 if node < self.num_nodes && matches!(self.nstate[node], StepGraphNodeState::Ready) {
337 let ghost old_states = self.nstate@;
338 self.nstate.set(node, StepGraphNodeState::Running);
339 assert(self.eligibility_closed()) by {
340 assert forall|m: usize| m < self.num_nodes
341 && #[trigger] self.nstate@[m as int] != StepGraphNodeState::NotReady
342 implies Self::predecessors_complete_in(self.edges@, self.nstate@, m) by {
343 assert(Self::predecessors_complete_in(self.edges@, old_states, m));
344 assert forall|e: int| 0 <= e < self.edges@.len()
345 && self.edges@[e].1 == m
346 implies #[trigger] self.nstate@[self.edges@[e].0 as int]
347 == StepGraphNodeState::Complete by {
348 let p = self.edges@[e].0;
349 assert(old_states[p as int] == StepGraphNodeState::Complete);
350 assert(p != node);
351 }
352 }
353 }
354 true
355 } else {
356 false
357 }
358 }
359
360 #[expect(clippy::indexing_slicing, reason = "the action guard and Verus invariant bound the node-state index")]
361 pub fn complete_node(&mut self, node: usize) -> (accepted: bool)
363 requires old(self).inv(),
364 ensures
365 final(self).num_nodes == old(self).num_nodes,
366 final(self).edges@ == old(self).edges@,
367 complete_node_action(
368 old(self).nstate@,
369 final(self).nstate@,
370 node as int,
371 true,
372 accepted,
373 ),
374 states_monotone(old(self).nstate@, final(self).nstate@),
375 final(self).inv(),
376 final(self).no_run_before_predecessors(),
377 {
378 if node < self.num_nodes && matches!(self.nstate[node], StepGraphNodeState::Running) {
379 let ghost old_states = self.nstate@;
380 self.nstate.set(node, StepGraphNodeState::Complete);
381 assert(self.eligibility_closed()) by {
382 assert forall|m: usize| m < self.num_nodes
383 && #[trigger] self.nstate@[m as int] != StepGraphNodeState::NotReady
384 implies Self::predecessors_complete_in(self.edges@, self.nstate@, m) by {
385 assert(Self::predecessors_complete_in(self.edges@, old_states, m));
386 assert forall|e: int| 0 <= e < self.edges@.len()
387 && self.edges@[e].1 == m
388 implies #[trigger] self.nstate@[self.edges@[e].0 as int]
389 == StepGraphNodeState::Complete by {
390 let p = self.edges@[e].0;
391 if p != node {
392 assert(self.nstate@[p as int] == old_states[p as int]);
393 }
394 }
395 }
396 }
397 true
398 } else {
399 false
400 }
401 }
402
403 #[expect(clippy::indexing_slicing, reason = "Verus proves the completion cursor remains in bounds")]
404 #[expect(clippy::arithmetic_side_effects, reason = "Verus proves the completion cursor increment remains in bounds")]
405 pub fn done_stuttering(&mut self) -> (enabled: bool)
407 requires old(self).inv(),
408 ensures
409 enabled == (forall|i: int| 0 <= i < old(self).nstate@.len()
410 ==> #[trigger] old(self).nstate@[i] == StepGraphNodeState::Complete),
411 final(self).num_nodes == old(self).num_nodes,
412 final(self).edges@ == old(self).edges@,
413 final(self).nstate@ == old(self).nstate@,
414 final(self).inv(),
415 {
416 let mut i = 0;
417 while i < self.nstate.len()
418 invariant
419 i <= self.nstate.len(),
420 self.inv(),
421 forall|k: int| 0 <= k < i ==>
422 self.nstate@[k] == StepGraphNodeState::Complete,
423 decreases self.nstate.len() - i,
424 {
425 if !matches!(self.nstate[i], StepGraphNodeState::Complete) {
426 return false;
427 }
428 i += 1;
429 }
430 true
431 }
432}
433
434}