Skip to main content

automation_structures/modalities/
sequential.rs

1//! Total-order execution carrier.
2
3use vstd::prelude::*;
4
5verus! {
6
7/// Transition for beginning one sequential position.
8pub open spec fn begin_step_action(
9    before_position: nat,
10    after_position: nat,
11    steps: nat,
12    selected: bool,
13    accepted: bool,
14) -> bool {
15    let enabled = selected && before_position < steps;
16    &&& accepted == enabled
17    &&& after_position == before_position
18}
19
20/// Transition for completing one sequential position.
21pub open spec fn complete_step_action(
22    before_position: nat,
23    after_position: nat,
24    steps: nat,
25    selected: bool,
26    value_admitted: bool,
27    accepted: bool,
28) -> bool {
29    let enabled = selected
30        && value_admitted
31        && before_position < steps;
32    &&& accepted == enabled
33    &&& after_position == if accepted {
34        before_position + 1
35    } else {
36        before_position
37    }
38}
39
40/// Totally ordered execution owner.
41pub struct Sequential {
42    /// Total number of steps.
43    pub steps: usize,
44    /// Exclusive upper bound of carried values.
45    pub value_domain_size: u64,
46    /// Program counter identifying the next step.
47    pub pc: usize,
48    /// Current carried value.
49    pub value: u64,
50    /// Whether the current step is active.
51    pub active: bool,
52    /// Values committed by completed steps.
53    pub history: Vec<u64>,
54}
55
56impl Sequential {
57    /// Whether every retained value lies within the configured value domain.
58    pub open spec fn values_valid(&self) -> bool {
59        forall|i: int| 0 <= i < self.history@.len()
60            ==> #[trigger] self.history@[i] < self.value_domain_size
61    }
62
63    /// Whether cursor, activity, history, and values have consistent shape.
64    pub open spec fn type_invariant(&self) -> bool {
65        &&& self.steps > 0
66        &&& self.value_domain_size > 0
67        &&& self.pc <= self.steps
68        &&& self.value < self.value_domain_size
69        &&& self.values_valid()
70    }
71
72    /// Whether the history records one total execution order.
73    pub open spec fn total_order(&self) -> bool {
74        self.history@.len() == self.pc
75    }
76
77    /// Whether an active step always precedes terminal completion.
78    pub open spec fn active_before_done(&self) -> bool {
79        self.active ==> self.pc < self.steps
80    }
81
82    /// Whether all sequential-execution obligations hold.
83    pub open spec fn inv(&self) -> bool {
84        self.type_invariant() && self.total_order() && self.active_before_done()
85    }
86
87    /// Construct an inactive execution at the first step.
88    pub fn new(steps: usize, value_domain_size: u64, initial_value: u64) -> (s: Sequential)
89        requires
90            steps > 0,
91            value_domain_size > 0,
92            initial_value < value_domain_size,
93        ensures
94            s.steps == steps,
95            s.value_domain_size == value_domain_size,
96            s.pc == 0,
97            s.value == initial_value,
98            !s.active,
99            s.history@.len() == 0,
100            s.inv(),
101    {
102        Sequential {
103            steps,
104            value_domain_size,
105            pc: 0,
106            value: initial_value,
107            active: false,
108            history: Vec::new(),
109        }
110    }
111
112    /// `BeginStep`, with disabled calls exposed as a stuttering rejection.
113    pub fn begin_step(&mut self) -> (accepted: bool)
114        requires old(self).inv(),
115        ensures
116            accepted == (old(self).pc < old(self).steps && !old(self).active),
117            begin_step_action(
118                old(self).pc as nat,
119                final(self).pc as nat,
120                old(self).steps as nat,
121                !old(self).active,
122                accepted,
123            ),
124            crate::connectives::marker::set_if(
125                old(self).active,
126                final(self).active,
127                accepted,
128            ),
129            final(self).steps == old(self).steps,
130            final(self).value_domain_size == old(self).value_domain_size,
131            if accepted {
132                final(self).active
133                    && final(self).pc == old(self).pc
134                    && final(self).value == old(self).value
135                    && final(self).history@ == old(self).history@
136            } else {
137                final(self).pc == old(self).pc
138                    && final(self).value == old(self).value
139                    && final(self).active == old(self).active
140                    && final(self).history@ == old(self).history@
141            },
142            final(self).inv(),
143    {
144        if self.pc < self.steps && !self.active {
145            self.active = true;
146            true
147        } else {
148            false
149        }
150    }
151
152    /// `CompleteStep`, including its nondeterministic ValueDomain choice as an
153    /// explicit caller value. An out-of-domain choice is not an enabled TLA+
154    /// action and therefore stutters with `false`.
155    #[expect(clippy::arithmetic_side_effects, reason = "Verus proves pc remains within the step bound")]
156    pub fn complete_step(&mut self, next_value: u64) -> (accepted: bool)
157        requires old(self).inv(),
158        ensures
159            accepted == (old(self).active && next_value < old(self).value_domain_size),
160            complete_step_action(
161                old(self).pc as nat,
162                final(self).pc as nat,
163                old(self).steps as nat,
164                old(self).active,
165                next_value < old(self).value_domain_size,
166                accepted,
167            ),
168            crate::connectives::marker::clear_if(
169                old(self).active,
170                final(self).active,
171                accepted,
172            ),
173            final(self).steps == old(self).steps,
174            final(self).value_domain_size == old(self).value_domain_size,
175            if accepted {
176                !final(self).active
177                    && final(self).pc == old(self).pc + 1
178                    && final(self).value == next_value
179                    && final(self).history@ == old(self).history@.push(next_value)
180            } else {
181                final(self).pc == old(self).pc
182                    && final(self).value == old(self).value
183                    && final(self).active == old(self).active
184                    && final(self).history@ == old(self).history@
185            },
186            final(self).inv(),
187    {
188        if self.active && next_value < self.value_domain_size {
189            let ghost old_history = self.history@;
190            self.value = next_value;
191            self.history.push(next_value);
192            self.pc += 1;
193            self.active = false;
194            assert(self.values_valid()) by {
195                assert forall|i: int| 0 <= i < self.history@.len()
196                    implies #[trigger] self.history@[i] < self.value_domain_size by {
197                    if i < old_history.len() {
198                        assert(self.history@[i] == old_history[i]);
199                    } else {
200                        assert(i == old_history.len());
201                        assert(self.history@[i] == next_value);
202                    }
203                }
204            }
205            true
206        } else {
207            false
208        }
209    }
210
211    /// `DoneStuttering`: enabled exactly at the terminal inactive state and
212    /// never changes carrier state.
213    pub fn done_stuttering(&mut self) -> (enabled: bool)
214        requires old(self).inv(),
215        ensures
216            enabled == (old(self).pc == old(self).steps && !old(self).active),
217            final(self).steps == old(self).steps,
218            final(self).value_domain_size == old(self).value_domain_size,
219            final(self).pc == old(self).pc,
220            final(self).value == old(self).value,
221            final(self).active == old(self).active,
222            final(self).history@ == old(self).history@,
223            final(self).inv(),
224    {
225        self.pc == self.steps && !self.active
226    }
227}
228
229}