Skip to main content

ostd/specs/mm/embedding/
list_store.rs

1//! Self-contained one-step-soundness harness for the frame `LinkedList`
2//! ([`crate::mm::frame::LinkedList`]) — the embedding's companion to
3//! [`super::VmStore`], specialised to linked-list operations.
4//!
5//! # Why a separate, generic store
6//!
7//! Unlike `VmStore` (which fixes concrete configs such as
8//! `UserPtConfig`), `ListStore<M>` is *generic* over the link metadata
9//! `M`. The kernel's `LinkedList<M>` is a generic library with no
10//! canonical concrete instantiation in ostd, and `LinkedListOwner<M>`
11//! cannot be type-erased to `dyn`: its per-link permission is the
12//! associated type `<M as Repr<MetaSlotSmall>>::Perm`, embedded in
13//! `LinkInnerPerms<M>`, so the trait is not object-safe (cf.
14//! `Frame<dyn AnyFrameMeta>`, which works only because it exposes no
15//! associated type post-erasure).
16//!
17//! # State
18//!
19//! - `regions`: the shared metadata-region ownership.
20//! - `lists`: held [`LinkedListOwner`]s. Each link is a forgotten
21//!   `UniqueFrame<Link<M>>` (its drop-obligation was consumed by
22//!   `into_raw` on push); the owner's [`LinkedListOwner::relate_region`]
23//!   ties every link to its UNIQUE region slot and pins the
24//!   `next`/`prev` pointer wiring.
25//! - `loose`: held-but-unlisted [`UniqueFrameOwner`]s — live
26//!   `UniqueFrame<Link<M>>` handles (drop-obligation present) eligible
27//!   to be pushed. `push` moves one from `loose` into a list; `pop`
28//!   moves a list's end link out into `loose`.
29//!
30//! # Roadmap
31//!
32//! Landed: the store + invariant, the front/back
33//! allocate-build-teardown suite (`new`, `push_front` / `pop_front`,
34//! `push_back` / `pop_back`), the general cursor surgery —
35//! `insert_before` / `take_current` at an *arbitrary* index
36//! ([`ListStore::step_insert_before_at`] / [`ListStore::step_take_at`]),
37//! which subsume the front/back ops — the read-only accessors
38//! ([`ListStore::step_size`] / [`ListStore::step_is_empty`]), and the
39//! full **persistent cursor** lifecycle: a cursor checks its list out of
40//! `lists` into `cursors` ([`ListStore::step_cursor_front_mut`] /
41//! `step_cursor_back_mut` / `step_cursor_mut_at`), walks it
42//! (`step_move_next` / `step_move_prev` / `step_current_meta`), mutates
43//! through it (`step_cursor_insert_before` / `step_cursor_take_current`),
44//! and checks it back in on drop ([`ListStore::step_cursor_drop`]).
45use vstd::prelude::*;
46use vstd_extra::{cast_ptr::Repr, ownership::*, set_extra::lemma_finite_int_set_has_unused};
47
48use crate::specs::{
49    arch::valid_frame_paddr,
50    mm::frame::{
51        linked_list::linked_list_owners::{
52            CursorOwner, LinkInnerPerms, LinkOwner, LinkedListOwner, MetaSlotSmall,
53        },
54        mapping::{frame_to_index, meta_to_index},
55        meta_owners::{MetadataPerm, PageUsage},
56        meta_region_owners::MetaRegionOwners,
57        unique::UniqueFrameOwner,
58    },
59};
60
61use crate::mm::{
62    Paddr,
63    frame::{
64        AnyFrameMeta, Link,
65        meta::{REF_COUNT_UNIQUE, REF_COUNT_UNUSED},
66    },
67};
68
69verus! {
70
71/// Logical identifier for a held [`LinkedListOwner`] in the store.
72pub type ListId = int;
73
74/// Logical identifier for a loose (held-but-unlisted)
75/// `UniqueFrame<Link<M>>` in the store.
76pub type LooseId = int;
77
78/// Logical identifier for a live [`CursorOwner`] in the store. A cursor
79/// is keyed by the *home* [`ListId`] whose list it checked out, so a
80/// list is cursored iff its id is in `cursors` (and then absent from
81/// `lists`).
82pub type CursorId = ListId;
83
84/// The membership registry relating one (held or checked-out) list `lo`
85/// to the physical `in_list` tags in `regions`:
86///   - **forward**: every link's region slot carries `lo.list_id` (the
87///     exec `insert_before` stamps it via `store(lazy_get_id())`);
88///   - **reverse** (only for a real, non-zero id): every region slot
89///     carrying that id is one of `lo`'s links — the global
90///     `in_list`-uniqueness the id allocator guarantees (a freshly
91///     minted id is system-wide unused; ids are never reused).
92/// Together they make `in_list == list_id` an *exact* membership test,
93/// which is exactly what [`crate::mm::frame::LinkedList::contains`]
94/// computes.
95pub open spec fn list_registry_ok<M: AnyFrameMeta + Repr<MetaSlotSmall>>(
96    regions: MetaRegionOwners,
97    lo: LinkedListOwner<M>,
98) -> bool {
99    &&& forall|i: int|
100        #![trigger meta_to_index(lo.list[i].paddr)]
101        0 <= i < lo.list.len() ==> regions.slot_owners[meta_to_index(
102            lo.list[i].paddr,
103        )].in_list_perm.value() == lo.list_id
104    &&& lo.list_id != 0 ==> forall|idx: int|
105        #![trigger regions.slot_owners[idx]]
106        regions.contains(idx) && regions.slot_owners[idx].in_list_perm.value() == lo.list_id
107            ==> exists|i: int|
108            0 <= i < lo.list.len() && #[trigger] meta_to_index(lo.list[i].paddr) == idx
109}
110
111/// One-step-soundness store for the frame `LinkedList`. Holds the shared
112/// `regions`, the set of held lists, the pool of loose
113/// (push-eligible) `UniqueFrame<Link<M>>` handles, and the live cursors.
114///
115/// A cursor *checks out* its list: a live `CursorMut` borrows the
116/// `LinkedList` exclusively, so while a cursor exists its
117/// `LinkedListOwner` lives inside the [`CursorOwner`] (`cursors`) rather
118/// than in `lists`. Dropping the cursor returns the list to `lists`.
119pub tracked struct ListStore<M: AnyFrameMeta + Repr<MetaSlotSmall>> {
120    pub regions: MetaRegionOwners,
121    pub lists: Map<ListId, LinkedListOwner<M>>,
122    pub loose: Map<LooseId, UniqueFrameOwner<Link<M>>>,
123    pub cursors: Map<CursorId, CursorOwner<M>>,
124}
125
126impl<M: AnyFrameMeta + Repr<MetaSlotSmall>> ListStore<M> {
127    /// The store's top-level invariant.
128    pub open spec fn inv(self) -> bool {
129        &&& self.regions.inv()
130        // Each held list is well-formed and every link relates to its
131        // UNIQUE region slot (incl. the `next`/`prev` pointer wiring).
132        &&& forall|id: ListId| #[trigger]
133            self.lists.dom().contains(id) ==> {
134                &&& self.lists[id].inv()
135                &&& self.lists[id].relate_region(self.regions)
136            }
137        &&& forall|lid: LooseId| #[trigger]
138            self.loose.dom().contains(lid) ==> {
139                &&& self.loose[lid].inv()
140                &&& self.loose[lid].global_inv(self.regions)
141                &&& self.loose[lid].frame_link_inv(self.regions)
142                &&& self.regions.slot_owners[self.loose[lid].slot_index].in_list_perm.value() == 0
143            }
144            // Distinct lists carry distinct *nonzero* ids.
145        &&& forall|id1: ListId, id2: ListId|
146            #![trigger self.lists.dom().contains(id1), self.lists.dom().contains(id2)]
147            self.lists.dom().contains(id1) && self.lists.dom().contains(id2)
148                && self.lists[id1].list_id == self.lists[id2].list_id && self.lists[id1].list_id
149                != 0 ==> id1
150                == id2
151            // Distinct loose handles occupy distinct slots (a UNIQUE frame
152            // is held in at most one place).
153        &&& forall|lid1: LooseId, lid2: LooseId|
154            #![trigger self.loose.dom().contains(lid1), self.loose.dom().contains(lid2)]
155            self.loose.dom().contains(lid1) && self.loose.dom().contains(lid2)
156                && self.loose[lid1].slot_index == self.loose[lid2].slot_index ==> lid1
157                == lid2
158            // A cursored list is *checked out*: it lives in `cursors`
159            // (keyed by its home id), never simultaneously in `lists`. This
160            // is the borrow — a live `CursorMut` holds the list exclusively.
161        &&& self.lists.dom().disjoint(
162            self.cursors.dom(),
163        )
164        // Each live cursor's checked-out list is well-formed and every
165        // link relates to its UNIQUE region slot.
166        &&& forall|cid: CursorId| #[trigger]
167            self.cursors.dom().contains(cid) ==> {
168                &&& self.cursors[cid].list_own.inv()
169                &&& self.cursors[cid].wf_with_region(self.regions)
170            }
171            // A cursor's list shares no nonzero id with any held list —
172            // cross list/cursor slot disjointness, mirroring lists×lists.
173        &&& forall|id: ListId, cid: CursorId|
174            #![trigger self.lists.dom().contains(id), self.cursors.dom().contains(cid)]
175            self.lists.dom().contains(id) && self.cursors.dom().contains(cid)
176                && self.lists[id].list_id == self.cursors[cid].list_own.list_id
177                && self.lists[id].list_id != 0
178                ==> false
179            // Distinct cursors carry distinct *nonzero* list ids.
180        &&& forall|cid1: CursorId, cid2: CursorId|
181            #![trigger self.cursors.dom().contains(cid1), self.cursors.dom().contains(cid2)]
182            self.cursors.dom().contains(cid1) && self.cursors.dom().contains(cid2)
183                && self.cursors[cid1].list_own.list_id == self.cursors[cid2].list_own.list_id
184                && self.cursors[cid1].list_own.list_id != 0 ==> cid1
185                == cid2
186            // Membership registry: each held list's id tags exactly its own
187            // links in the region.
188        &&& forall|id: ListId| #[trigger]
189            self.lists.dom().contains(id) ==> list_registry_ok(
190                self.regions,
191                self.lists[id],
192            )
193        // Same registry for each checked-out cursor's list.
194        &&& forall|cid: CursorId| #[trigger]
195            self.cursors.dom().contains(cid) ==> list_registry_ok(
196                self.regions,
197                self.cursors[cid].list_own,
198            )
199    }
200}
201
202// =============================================================================
203// Fresh-id helpers + tracked constructors
204// =============================================================================
205/// Tracked constructor for a fresh *empty* list owner.
206pub proof fn tracked_empty_list_owner<M: AnyFrameMeta + Repr<MetaSlotSmall>>() -> (tracked res:
207    LinkedListOwner<M>)
208    ensures
209        res.list =~= Seq::<LinkOwner>::empty(),
210        res.repr_perms =~= Seq::<LinkInnerPerms<M>>::empty(),
211        res.metadata_perms =~= Seq::<MetadataPerm>::empty(),
212        res.list_id == 0,
213{
214    let tracked list = Seq::<LinkOwner>::tracked_empty();
215    let tracked repr_perms = Seq::<LinkInnerPerms<M>>::tracked_empty();
216    let tracked metadata_perms = Seq::<MetadataPerm>::tracked_empty();
217    let tracked res = LinkedListOwner::<M> {
218        list,
219        repr_perms,
220        metadata_perms,
221        list_id: 0,
222        _marker: core::marker::PhantomData,
223    };
224    res
225}
226
227/// Fresh-id helper for the list id space.
228pub open spec fn fresh_list_id<M: AnyFrameMeta + Repr<MetaSlotSmall>>(
229    lists: Map<ListId, LinkedListOwner<M>>,
230    cursors: Map<CursorId, CursorOwner<M>>,
231) -> ListId {
232    choose|id: ListId| !lists.dom().contains(id) && !cursors.dom().contains(id)
233}
234
235pub proof fn lemma_fresh_list_id_not_in_dom<M: AnyFrameMeta + Repr<MetaSlotSmall>>(
236    lists: Map<ListId, LinkedListOwner<M>>,
237    cursors: Map<CursorId, CursorOwner<M>>,
238)
239    ensures
240        !lists.dom().contains(fresh_list_id(lists, cursors)) && !cursors.dom().contains(
241            fresh_list_id(lists, cursors),
242        ),
243{
244    lemma_finite_int_set_has_unused(lists.dom() + cursors.dom());
245}
246
247/// Trusted reflection of [`crate::mm::frame::LinkedList::push_front`].
248pub proof fn push_front_embedded<M: AnyFrameMeta + Repr<MetaSlotSmall>>(
249    tracked regions: &mut MetaRegionOwners,
250    tracked owner: &mut LinkedListOwner<M>,
251    tracked frame_own: &mut UniqueFrameOwner<Link<M>>,
252    used_ids: Set<u64>,
253)
254    requires
255        old(regions).inv(),
256        old(owner).inv(),
257        old(owner).relate_region(*old(regions)),
258        old(frame_own).inv(),
259        old(frame_own).global_inv(*old(regions)),
260        old(frame_own).frame_link_inv(*old(regions)),
261        old(regions).slot_owners[old(frame_own).slot_index].in_list_perm.value() == 0,
262    ensures
263        final(regions).inv(),
264        final(owner).inv(),
265        final(owner).relate_region(*final(regions)),
266        final(owner).list == old(owner).list.insert(0, final(frame_own).meta_own),
267        old(owner).list_id != 0 ==> final(owner).list_id == old(owner).list_id,
268        final(owner).list_id != 0,
269        old(owner).list_id == 0 ==> !used_ids.contains(final(owner).list_id),
270        final(frame_own).meta_own.paddr == old(frame_own).meta_own.paddr,
271        final(frame_own).meta_own.in_list == final(owner).list_id,
272        forall|k: int|
273            #![trigger final(regions).slots[k]]
274            #![trigger final(regions).slot_owners[k]]
275            k != old(frame_own).slot_index && (old(owner).list.len() > 0 ==> k != meta_to_index(
276                old(owner).list[0].paddr,
277            )) ==> final(regions).slots[k] == old(regions).slots[k] && final(regions).slot_owners[k]
278                == old(regions).slot_owners[k],
279        forall|l: LinkedListOwner<M>|
280            #![trigger l.relate_region(*old(regions))]
281            l.inv() && l.relate_region(*old(regions)) && l.list_id != final(owner).list_id
282                ==> l.relate_region(*final(regions)),
283        list_registry_ok(*final(regions), *final(owner)),
284        forall|l: LinkedListOwner<M>|
285            #![trigger l.relate_region(*old(regions))]
286            l.inv() && l.relate_region(*old(regions)) && list_registry_ok(*old(regions), l)
287                && l.list_id != final(owner).list_id ==> list_registry_ok(*final(regions), l),
288        forall|fo: UniqueFrameOwner<Link<M>>|
289            #![trigger fo.global_inv(*old(regions))]
290            fo.global_inv(*old(regions)) && fo.frame_link_inv(*old(regions)) && old(
291                regions,
292            ).slot_owners[fo.slot_index].in_list_perm.value() == 0 && fo.slot_index != old(
293                frame_own,
294            ).slot_index ==> fo.global_inv(*final(regions)) && fo.frame_link_inv(*final(regions))
295                && final(regions).slot_owners[fo.slot_index].in_list_perm.value() == 0,
296{
297    axiom_insert_before_at_embedded(regions, owner, frame_own, 0, used_ids);
298}
299
300/// Fresh-id helper for the loose-frame id space.
301pub open spec fn fresh_loose_id<M: AnyFrameMeta + Repr<MetaSlotSmall>>(
302    m: Map<LooseId, UniqueFrameOwner<Link<M>>>,
303) -> LooseId {
304    choose|id: LooseId| !m.dom().contains(id)
305}
306
307/// Checked front specialization of [`axiom_take_at_embedded`], reflecting
308/// [`crate::mm::frame::LinkedList::pop_front`].
309pub proof fn tracked_pop_front_embedded<M: AnyFrameMeta + Repr<MetaSlotSmall>>(
310    tracked regions: &mut MetaRegionOwners,
311    tracked owner: &mut LinkedListOwner<M>,
312) -> (tracked frame_own: UniqueFrameOwner<Link<M>>)
313    requires
314        old(regions).inv(),
315        old(owner).inv(),
316        old(owner).relate_region(*old(regions)),
317        old(owner).list.len() > 0,
318    ensures
319        final(regions).inv(),
320        final(owner).inv(),
321        final(owner).relate_region(*final(regions)),
322        final(owner).list == old(owner).list.remove(0),
323        final(owner).list_id == old(owner).list_id,
324        // The popped frame is a valid loose handle at the old front slot.
325        frame_own.inv(),
326        frame_own.global_inv(*final(regions)),
327        frame_own.frame_link_inv(*final(regions)),
328        frame_own.slot_index == meta_to_index(old(owner).list[0].paddr),
329        final(regions).slot_owners[frame_own.slot_index].in_list_perm.value() == 0,
330        forall|j: int|
331            #![trigger final(regions).slots[j]]
332            #![trigger final(regions).slot_owners[j]]
333            j != meta_to_index(old(owner).list[0].paddr) && (old(owner).list.len() > 1 ==> j
334                != meta_to_index(old(owner).list[1].paddr)) ==> final(regions).slots[j] == old(
335                regions,
336            ).slots[j] && final(regions).slot_owners[j] == old(regions).slot_owners[j],
337        // Other lists preserved.
338        forall|l: LinkedListOwner<M>|
339            #![trigger l.relate_region(*old(regions))]
340            l.inv() && l.relate_region(*old(regions)) && l.list_id != final(owner).list_id
341                ==> l.relate_region(*final(regions)),
342        list_registry_ok(*final(regions), *final(owner)),
343        forall|l: LinkedListOwner<M>|
344            #![trigger l.relate_region(*old(regions))]
345            l.inv() && l.relate_region(*old(regions)) && list_registry_ok(*old(regions), l)
346                && l.list_id != final(owner).list_id ==> list_registry_ok(*final(regions), l),
347        forall|fo: UniqueFrameOwner<Link<M>>|
348            #![trigger fo.global_inv(*old(regions))]
349            fo.global_inv(*old(regions)) && fo.frame_link_inv(*old(regions)) && old(
350                regions,
351            ).slot_owners[fo.slot_index].in_list_perm.value() == 0 ==> fo.global_inv(
352                *final(regions),
353            ) && fo.frame_link_inv(*final(regions))
354                && final(regions).slot_owners[fo.slot_index].in_list_perm.value() == 0
355                && fo.slot_index != meta_to_index(old(owner).list[0].paddr),
356{
357    let tracked frame_own = axiom_take_at_embedded(regions, owner, 0);
358    frame_own
359}
360
361/// Checked back specialization of [`axiom_insert_before_at_embedded`], reflecting the
362/// [`crate::mm::frame::LinkedList::push_back`].
363pub proof fn lemma_push_back_embedded<M: AnyFrameMeta + Repr<MetaSlotSmall>>(
364    tracked regions: &mut MetaRegionOwners,
365    tracked owner: &mut LinkedListOwner<M>,
366    tracked frame_own: &mut UniqueFrameOwner<Link<M>>,
367    used_ids: Set<u64>,
368)
369    requires
370        old(regions).inv(),
371        old(owner).inv(),
372        old(owner).relate_region(*old(regions)),
373        old(frame_own).inv(),
374        old(frame_own).global_inv(*old(regions)),
375        old(frame_own).frame_link_inv(*old(regions)),
376        old(regions).slot_owners[old(frame_own).slot_index].in_list_perm.value() == 0,
377    ensures
378        final(regions).inv(),
379        final(owner).inv(),
380        final(owner).relate_region(*final(regions)),
381        old(owner).list.len() > 0 ==> final(owner).list == old(owner).list.insert(
382            old(owner).list.len() - 1,
383            final(frame_own).meta_own,
384        ),
385        old(owner).list.len() == 0 ==> final(owner).list == old(owner).list.insert(
386            0,
387            final(frame_own).meta_own,
388        ),
389        old(owner).list_id != 0 ==> final(owner).list_id == old(owner).list_id,
390        final(owner).list_id != 0,
391        old(owner).list_id == 0 ==> !used_ids.contains(final(owner).list_id),
392        final(frame_own).meta_own.paddr == old(frame_own).meta_own.paddr,
393        final(frame_own).meta_own.in_list == final(owner).list_id,
394        forall|k: int|
395            #![trigger final(regions).slots[k]]
396            #![trigger final(regions).slot_owners[k]]
397            k != old(frame_own).slot_index && (old(owner).list.len() > 1 ==> k != meta_to_index(
398                old(owner).list[old(owner).list.len() - 2].paddr,
399            )) && (old(owner).list.len() > 0 ==> k != meta_to_index(
400                old(owner).list[old(owner).list.len() - 1].paddr,
401            )) ==> final(regions).slots[k] == old(regions).slots[k] && final(regions).slot_owners[k]
402                == old(regions).slot_owners[k],
403        forall|l: LinkedListOwner<M>|
404            #![trigger l.relate_region(*old(regions))]
405            l.inv() && l.relate_region(*old(regions)) && l.list_id != final(owner).list_id
406                ==> l.relate_region(*final(regions)),
407        list_registry_ok(*final(regions), *final(owner)),
408        forall|l: LinkedListOwner<M>|
409            #![trigger l.relate_region(*old(regions))]
410            l.inv() && l.relate_region(*old(regions)) && list_registry_ok(*old(regions), l)
411                && l.list_id != final(owner).list_id ==> list_registry_ok(*final(regions), l),
412        forall|fo: UniqueFrameOwner<Link<M>>|
413            #![trigger fo.global_inv(*old(regions))]
414            fo.global_inv(*old(regions)) && fo.frame_link_inv(*old(regions)) && old(
415                regions,
416            ).slot_owners[fo.slot_index].in_list_perm.value() == 0 && fo.slot_index != old(
417                frame_own,
418            ).slot_index ==> fo.global_inv(*final(regions)) && fo.frame_link_inv(*final(regions))
419                && final(regions).slot_owners[fo.slot_index].in_list_perm.value() == 0,
420{
421    let ghost n = if owner.list.len() > 0 {
422        owner.list.len() - 1
423    } else {
424        0
425    };
426    axiom_insert_before_at_embedded(regions, owner, frame_own, n, used_ids);
427}
428
429/// Checked back specialization of [`axiom_take_at_embedded`], reflecting the
430/// [`crate::mm::frame::LinkedList::pop_back`].
431pub proof fn tracked_pop_back_embedded<M: AnyFrameMeta + Repr<MetaSlotSmall>>(
432    tracked regions: &mut MetaRegionOwners,
433    tracked owner: &mut LinkedListOwner<M>,
434) -> (tracked frame_own: UniqueFrameOwner<Link<M>>)
435    requires
436        old(regions).inv(),
437        old(owner).inv(),
438        old(owner).relate_region(*old(regions)),
439        old(owner).list.len() > 0,
440    ensures
441        final(regions).inv(),
442        final(owner).inv(),
443        final(owner).relate_region(*final(regions)),
444        final(owner).list == old(owner).list.remove(old(owner).list.len() - 1),
445        final(owner).list_id == old(owner).list_id,
446        frame_own.inv(),
447        frame_own.global_inv(*final(regions)),
448        frame_own.frame_link_inv(*final(regions)),
449        frame_own.slot_index == meta_to_index(old(owner).list[old(owner).list.len() - 1].paddr),
450        final(regions).slot_owners[frame_own.slot_index].in_list_perm.value() == 0,
451        forall|j: int|
452            #![trigger final(regions).slots[j]]
453            #![trigger final(regions).slot_owners[j]]
454            j != meta_to_index(old(owner).list[old(owner).list.len() - 1].paddr) && (old(
455                owner,
456            ).list.len() > 1 ==> j != meta_to_index(
457                old(owner).list[old(owner).list.len() - 2].paddr,
458            )) ==> final(regions).slots[j] == old(regions).slots[j] && final(regions).slot_owners[j]
459                == old(regions).slot_owners[j],
460        forall|l: LinkedListOwner<M>|
461            #![trigger l.relate_region(*old(regions))]
462            l.inv() && l.relate_region(*old(regions)) && l.list_id != final(owner).list_id
463                ==> l.relate_region(*final(regions)),
464        list_registry_ok(*final(regions), *final(owner)),
465        forall|l: LinkedListOwner<M>|
466            #![trigger l.relate_region(*old(regions))]
467            l.inv() && l.relate_region(*old(regions)) && list_registry_ok(*old(regions), l)
468                && l.list_id != final(owner).list_id ==> list_registry_ok(*final(regions), l),
469        forall|fo: UniqueFrameOwner<Link<M>>|
470            #![trigger fo.global_inv(*old(regions))]
471            fo.global_inv(*old(regions)) && fo.frame_link_inv(*old(regions)) && old(
472                regions,
473            ).slot_owners[fo.slot_index].in_list_perm.value() == 0 ==> fo.global_inv(
474                *final(regions),
475            ) && fo.frame_link_inv(*final(regions))
476                && final(regions).slot_owners[fo.slot_index].in_list_perm.value() == 0
477                && fo.slot_index != meta_to_index(old(owner).list[old(owner).list.len() - 1].paddr),
478{
479    let ghost n = owner.list.len() - 1;
480    let tracked frame_own = axiom_take_at_embedded(regions, owner, n);
481    frame_own
482}
483
484/// Trusted reflection of [`crate::mm::frame::CursorMut::insert_before`].
485pub axiom fn axiom_insert_before_at_embedded<M: AnyFrameMeta + Repr<MetaSlotSmall>>(
486    tracked regions: &mut MetaRegionOwners,
487    tracked owner: &mut LinkedListOwner<M>,
488    tracked frame_own: &mut UniqueFrameOwner<Link<M>>,
489    n: int,
490    used_ids: Set<u64>,
491)
492    requires
493        old(regions).inv(),
494        old(owner).inv(),
495        old(owner).relate_region(*old(regions)),
496        old(frame_own).inv(),
497        old(frame_own).global_inv(*old(regions)),
498        old(frame_own).frame_link_inv(*old(regions)),
499        old(regions).slot_owners[old(frame_own).slot_index].in_list_perm.value() == 0,
500        0 <= n <= old(owner).list.len(),
501    ensures
502        final(regions).inv(),
503        final(owner).inv(),
504        final(owner).relate_region(*final(regions)),
505        final(owner).list == old(owner).list.insert(n, final(frame_own).meta_own),
506        old(owner).list_id != 0 ==> final(owner).list_id == old(owner).list_id,
507        final(owner).list_id != 0,
508        old(owner).list_id == 0 ==> !used_ids.contains(final(owner).list_id),
509        final(frame_own).meta_own.paddr == old(frame_own).meta_own.paddr,
510        final(frame_own).meta_own.in_list == final(owner).list_id,
511        forall|k: int|
512            #![trigger final(regions).slots[k]]
513            #![trigger final(regions).slot_owners[k]]
514            k != old(frame_own).slot_index && (n > 0 ==> k != meta_to_index(
515                old(owner).list[n - 1].paddr,
516            )) && (n < old(owner).list.len() ==> k != meta_to_index(old(owner).list[n].paddr))
517                ==> final(regions).slots[k] == old(regions).slots[k]
518                && final(regions).slot_owners[k] == old(regions).slot_owners[k],
519        forall|l: LinkedListOwner<M>|
520            #![trigger l.relate_region(*old(regions))]
521            l.inv() && l.relate_region(*old(regions)) && l.list_id != final(owner).list_id
522                ==> l.relate_region(*final(regions)),
523        list_registry_ok(*final(regions), *final(owner)),
524        forall|l: LinkedListOwner<M>|
525            #![trigger l.relate_region(*old(regions))]
526            l.inv() && l.relate_region(*old(regions)) && list_registry_ok(*old(regions), l)
527                && l.list_id != final(owner).list_id ==> list_registry_ok(*final(regions), l),
528        forall|fo: UniqueFrameOwner<Link<M>>|
529            #![trigger fo.global_inv(*old(regions))]
530            fo.global_inv(*old(regions)) && fo.frame_link_inv(*old(regions)) && old(
531                regions,
532            ).slot_owners[fo.slot_index].in_list_perm.value() == 0 && fo.slot_index != old(
533                frame_own,
534            ).slot_index ==> fo.global_inv(*final(regions)) && fo.frame_link_inv(*final(regions))
535                && final(regions).slot_owners[fo.slot_index].in_list_perm.value() == 0,
536;
537
538/// Trusted reflection of [`crate::mm::frame::CursorMut::take_current`].
539pub axiom fn axiom_take_at_embedded<M: AnyFrameMeta + Repr<MetaSlotSmall>>(
540    tracked regions: &mut MetaRegionOwners,
541    tracked owner: &mut LinkedListOwner<M>,
542    n: int,
543) -> (tracked frame_own: UniqueFrameOwner<Link<M>>)
544    requires
545        old(regions).inv(),
546        old(owner).inv(),
547        old(owner).relate_region(*old(regions)),
548        0 <= n < old(owner).list.len(),
549    ensures
550        final(regions).inv(),
551        final(owner).inv(),
552        final(owner).relate_region(*final(regions)),
553        final(owner).list == old(owner).list.remove(n),
554        final(owner).list_id == old(owner).list_id,
555        frame_own.inv(),
556        frame_own.global_inv(*final(regions)),
557        frame_own.frame_link_inv(*final(regions)),
558        frame_own.slot_index == meta_to_index(old(owner).list[n].paddr),
559        final(regions).slot_owners[frame_own.slot_index].in_list_perm.value() == 0,
560        forall|j: int|
561            #![trigger final(regions).slots[j]]
562            #![trigger final(regions).slot_owners[j]]
563            j != meta_to_index(old(owner).list[n].paddr) && (n > 0 ==> j != meta_to_index(
564                old(owner).list[n - 1].paddr,
565            )) && (n < old(owner).list.len() - 1 ==> j != meta_to_index(
566                old(owner).list[n + 1].paddr,
567            )) ==> final(regions).slots[j] == old(regions).slots[j] && final(regions).slot_owners[j]
568                == old(regions).slot_owners[j],
569        forall|l: LinkedListOwner<M>|
570            #![trigger l.relate_region(*old(regions))]
571            l.inv() && l.relate_region(*old(regions)) && l.list_id != final(owner).list_id
572                ==> l.relate_region(*final(regions)),
573        list_registry_ok(*final(regions), *final(owner)),
574        forall|l: LinkedListOwner<M>|
575            #![trigger l.relate_region(*old(regions))]
576            l.inv() && l.relate_region(*old(regions)) && list_registry_ok(*old(regions), l)
577                && l.list_id != final(owner).list_id ==> list_registry_ok(*final(regions), l),
578        forall|fo: UniqueFrameOwner<Link<M>>|
579            #![trigger fo.global_inv(*old(regions))]
580            fo.global_inv(*old(regions)) && fo.frame_link_inv(*old(regions)) && old(
581                regions,
582            ).slot_owners[fo.slot_index].in_list_perm.value() == 0 ==> fo.global_inv(
583                *final(regions),
584            ) && fo.frame_link_inv(*final(regions))
585                && final(regions).slot_owners[fo.slot_index].in_list_perm.value() == 0
586                && fo.slot_index != meta_to_index(old(owner).list[n].paddr),
587;
588
589/// Trusted reflection of the [`crate::mm::frame::LinkedList`]'s `Drop`.
590pub axiom fn axiom_list_drop_embedded<M: AnyFrameMeta + Repr<MetaSlotSmall>>(
591    tracked regions: &mut MetaRegionOwners,
592    tracked owner: LinkedListOwner<M>,
593)
594    requires
595        old(regions).inv(),
596        owner.inv(),
597        owner.relate_region(*old(regions)),
598        forall|i: int|
599            #![trigger meta_to_index(owner.list[i].paddr)]
600            0 <= i < owner.list.len() ==> old(regions).slot_owners[meta_to_index(
601                owner.list[i].paddr,
602            )].paths_in_pt.is_empty(),
603    ensures
604        final(regions).inv(),
605        final(regions).slots.dom() =~= old(regions).slots.dom(),
606        // An empty list frees nothing — the region is untouched.
607        owner.list.len() == 0 ==> *final(regions) == *old(regions),
608        // Each former link is freed: its slot is UNUSED with `in_list` 0.
609        forall|i: int|
610            #![trigger meta_to_index(owner.list[i].paddr)]
611            0 <= i < owner.list.len() ==> {
612                let idx = meta_to_index(owner.list[i].paddr);
613                &&& final(regions).ref_count(idx) == REF_COUNT_UNUSED
614                &&& final(regions).slot_owners[idx].in_list_perm.value() == 0
615            },
616        // Every slot outside the dropped list is fully preserved.
617        forall|idx: int|
618            #![trigger final(regions).slot_owners[idx]]
619            (forall|i: int|
620                0 <= i < owner.list.len() ==> idx != #[trigger] meta_to_index(owner.list[i].paddr))
621                ==> final(regions).slot_owners[idx] == old(regions).slot_owners[idx]
622                && final(regions).slots[idx] == old(regions).slots[idx],
623        // Other lists / cursors keep their `relate_region` and registry.
624        forall|l: LinkedListOwner<M>|
625            #![trigger l.relate_region(*old(regions))]
626            l.inv() && l.relate_region(*old(regions)) && l.list_id != owner.list_id
627                ==> l.relate_region(*final(regions)),
628        forall|l: LinkedListOwner<M>|
629            #![trigger l.relate_region(*old(regions))]
630            l.inv() && l.relate_region(*old(regions)) && list_registry_ok(*old(regions), l)
631                && l.list_id != owner.list_id ==> list_registry_ok(*final(regions), l),
632        // Other loose frames (at `in_list == 0` slots disjoint from the
633        // dropped list's) are untouched.
634        forall|fo: UniqueFrameOwner<Link<M>>|
635            #![trigger fo.global_inv(*old(regions))]
636            fo.global_inv(*old(regions)) && fo.frame_link_inv(*old(regions)) && old(
637                regions,
638            ).slot_owners[fo.slot_index].in_list_perm.value() == 0 ==> fo.global_inv(
639                *final(regions),
640            ) && fo.frame_link_inv(*final(regions))
641                && final(regions).slot_owners[fo.slot_index].in_list_perm.value() == 0,
642;
643
644// =============================================================================
645// Operations
646// =============================================================================
647impl<M: AnyFrameMeta + Repr<MetaSlotSmall>> ListStore<M> {
648    /// `LinkedList::size`: the number of links in list `id`. A read-only
649    /// query — the store is unchanged.
650    pub proof fn step_size(tracked &self, id: ListId) -> (res: nat)
651        requires
652            self.inv(),
653            self.lists.dom().contains(id),
654        ensures
655            res == self.lists[id].list.len(),
656    {
657        self.lists[id].list.len()
658    }
659
660    /// `LinkedList::is_empty`: whether list `id` has no links. Read-only.
661    pub proof fn step_is_empty(tracked &self, id: ListId) -> (res: bool)
662        requires
663            self.inv(),
664            self.lists.dom().contains(id),
665        ensures
666            res <==> self.lists[id].list.len() == 0,
667    {
668        self.lists[id].list.len() == 0
669    }
670
671    /// `LinkedList::contains`: whether `frame` is a link of list `id`.
672    pub proof fn step_contains(tracked &self, id: ListId, frame: Paddr) -> (res: bool)
673        requires
674            self.inv(),
675            self.lists.dom().contains(id),
676        ensures
677            res <==> (valid_frame_paddr(frame) && exists|i: int|
678                0 <= i < self.lists[id].list.len() && #[trigger] meta_to_index(
679                    self.lists[id].list[i].paddr,
680                ) == frame_to_index(frame)),
681    {
682        let idx = frame_to_index(frame);
683        if valid_frame_paddr(frame) {
684            self.regions.lemma_contains_valid_frame_paddr(frame);
685            if self.lists[id].list_id != 0 {
686                let res = self.regions.slot_owners[idx].in_list_perm.value()
687                    == self.lists[id].list_id;
688                res
689            } else {
690                false
691            }
692        } else {
693            false
694        }
695    }
696
697    /// `LinkedList::new`: register a fresh *empty* list.
698    pub proof fn step_list_new(tracked &mut self) -> (res: ListId)
699        requires
700            old(self).inv(),
701        ensures
702            final(self).inv(),
703            final(self).regions == old(self).regions,
704            final(self).loose == old(self).loose,
705            !old(self).lists.dom().contains(res),
706            final(self).lists == old(self).lists.insert(res, final(self).lists[res]),
707            final(self).lists[res].list.len() == 0,
708    {
709        let ghost id = fresh_list_id(self.lists, self.cursors);
710        lemma_fresh_list_id_not_in_dom(self.lists, self.cursors);
711        let tracked empty = tracked_empty_list_owner::<M>();
712        self.lists.tracked_insert(id, empty);
713        assert(self.lists[id].relate_region(self.regions)) by {
714            reveal(LinkedListOwner::relate_region);
715        };
716        id
717    }
718
719    /// Drop of `LinkedList` `id`. Faithful to the verified destructor.
720    pub proof fn step_list_drop(tracked &mut self, id: ListId)
721        requires
722            old(self).inv(),
723            old(self).lists.dom().contains(id),
724        ensures
725            final(self).inv(),
726            !final(self).lists.dom().contains(id),
727            final(self).loose == old(self).loose,
728            final(self).cursors == old(self).cursors,
729    {
730        self.lists[id].lemma_relate_region_shape(self.regions);
731        assert forall|i: int|
732            #![trigger meta_to_index(self.lists[id].list[i].paddr)]
733            0 <= i < self.lists[id].list.len() implies self.regions.slot_owners[meta_to_index(
734            self.lists[id].list[i].paddr,
735        )].paths_in_pt.is_empty() by {
736            let idx = meta_to_index(self.lists[id].list[i].paddr);
737            let _ = self.lists[id].list[i];
738            self.lists[id].lemma_relate_region_at_index(self.regions, i);
739            self.lists[id].lemma_relate_region_at_facts(self.regions, i);
740            assert(self.regions.contains(idx));
741            assert(self.regions.ref_count(idx) == REF_COUNT_UNIQUE);
742            assert(self.regions.slot_owners[idx].usage is Frame);
743        };
744
745        let tracked owner = self.lists.tracked_remove(id);
746        axiom_list_drop_embedded(&mut self.regions, owner);
747    }
748
749    /// `LinkedList::push_front`: move the loose handle `lid` to the front
750    /// of list `id`. The frame is forgotten into the list (its
751    /// drop-obligation consumed); the `loose` entry is removed.
752    pub proof fn step_push_front(tracked &mut self, id: ListId, lid: LooseId)
753        requires
754            old(self).inv(),
755            old(self).lists.dom().contains(id),
756            old(self).loose.dom().contains(lid),
757        ensures
758            final(self).inv(),
759    {
760        let ghost old_self = *self;
761        // The other lists' ids — the lazily-minted id must avoid these.
762        let ghost used = Set::<u64>::full().unwrap().filter(
763            |x: u64|
764                (exists|i: ListId| #[trigger]
765                    old_self.lists.dom().contains(i) && i != id && old_self.lists[i].list_id == x)
766                    || (exists|cid: CursorId| #[trigger]
767                    old_self.cursors.dom().contains(cid) && old_self.cursors[cid].list_own.list_id
768                        == x),
769        );
770
771        let tracked mut owner = self.lists.tracked_remove(id);
772        let tracked mut frame_own = self.loose.tracked_remove(lid);
773        push_front_embedded(&mut self.regions, &mut owner, &mut frame_own, used);
774        self.lists.tracked_insert(id, owner);
775    }
776
777    /// `LinkedList::pop_front`: pop the front link of list `id` back into
778    /// the loose pool as a fresh `UniqueFrame<Link<M>>`. Requires the
779    /// list be non-empty. Returns the fresh loose id.
780    pub proof fn step_pop_front(tracked &mut self, id: ListId) -> (res: Option<LooseId>)
781        requires
782            old(self).inv(),
783            old(self).lists.dom().contains(id),
784        ensures
785            final(self).inv(),
786            old(self).lists[id].list.len() == 0 ==> res is None && *final(self) == *old(self),
787            old(self).lists[id].list.len() > 0 ==> res is Some,
788    {
789        if self.lists[id].list.len() == 0 {
790            Option::None
791        } else {
792            let tracked mut owner = self.lists.tracked_remove(id);
793            let tracked frame_own = tracked_pop_front_embedded(&mut self.regions, &mut owner);
794            self.lists.tracked_insert(id, owner);
795            let ghost new_loose = fresh_loose_id(self.loose);
796
797            self.loose.tracked_insert(new_loose, frame_own);
798
799            Option::Some(new_loose)
800        }
801    }
802
803    /// `LinkedList::push_back`: move the loose handle `lid` to the back
804    /// of list `id`. Same global effect as [`Self::step_push_front`] —
805    /// only the link's position within the list differs.
806    pub proof fn step_push_back(tracked &mut self, id: ListId, lid: LooseId)
807        requires
808            old(self).inv(),
809            old(self).lists.dom().contains(id),
810            old(self).loose.dom().contains(lid),
811        ensures
812            final(self).inv(),
813    {
814        let ghost old_self = *self;
815        let ghost used = Set::<u64>::full().unwrap().filter(
816            |x: u64|
817                (exists|i: ListId| #[trigger]
818                    old_self.lists.dom().contains(i) && i != id && old_self.lists[i].list_id == x)
819                    || (exists|cid: CursorId| #[trigger]
820                    old_self.cursors.dom().contains(cid) && old_self.cursors[cid].list_own.list_id
821                        == x),
822        );
823
824        let tracked mut owner = self.lists.tracked_remove(id);
825        let tracked mut frame_own = self.loose.tracked_remove(lid);
826        lemma_push_back_embedded(&mut self.regions, &mut owner, &mut frame_own, used);
827
828        self.lists.tracked_insert(id, owner);
829    }
830
831    /// `LinkedList::pop_back`: pop the back link of list `id` back into
832    /// the loose pool. Same global effect as [`Self::step_pop_front`] —
833    /// only which link is removed differs.
834    pub proof fn step_pop_back(tracked &mut self, id: ListId) -> (res: Option<LooseId>)
835        requires
836            old(self).inv(),
837            old(self).lists.dom().contains(id),
838        ensures
839            final(self).inv(),
840            old(self).lists[id].list.len() == 0 ==> res is None && *final(self) == *old(self),
841            old(self).lists[id].list.len() > 0 ==> res is Some,
842    {
843        if self.lists[id].list.len() == 0 {
844            Option::None
845        } else {
846            let tracked mut owner = self.lists.tracked_remove(id);
847            let tracked frame_own = tracked_pop_back_embedded(&mut self.regions, &mut owner);
848            self.lists.tracked_insert(id, owner);
849            let ghost new_loose = fresh_loose_id(self.loose);
850
851            self.loose.tracked_insert(new_loose, frame_own);
852
853            Option::Some(new_loose)
854        }
855    }
856
857    /// The general form of [`Self::step_push_front`] /
858    /// [`Self::step_push_back`]; same global effect.
859    pub proof fn step_insert_before_at(tracked &mut self, id: ListId, n: int, lid: LooseId)
860        requires
861            old(self).inv(),
862            old(self).lists.dom().contains(id),
863            old(self).loose.dom().contains(lid),
864            0 <= n <= old(self).lists[id].list.len(),
865        ensures
866            final(self).inv(),
867    {
868        let ghost old_self = *self;
869        let ghost used = Set::<u64>::full().unwrap().filter(
870            |x: u64|
871                (exists|i: ListId| #[trigger]
872                    old_self.lists.dom().contains(i) && i != id && old_self.lists[i].list_id == x)
873                    || (exists|cid: CursorId| #[trigger]
874                    old_self.cursors.dom().contains(cid) && old_self.cursors[cid].list_own.list_id
875                        == x),
876        );
877
878        let tracked mut owner = self.lists.tracked_remove(id);
879        let tracked mut frame_own = self.loose.tracked_remove(lid);
880        axiom_insert_before_at_embedded(&mut self.regions, &mut owner, &mut frame_own, n, used);
881
882        self.lists.tracked_insert(id, owner);
883    }
884
885    /// Cursor `take_current` at an arbitrary position `n`: pop the link
886    /// at index `n` (`0 <= n < len`) of list `id` back into the loose
887    /// pool. The general form of [`Self::step_pop_front`] /
888    /// [`Self::step_pop_back`]; same global effect.
889    pub proof fn step_take_at(tracked &mut self, id: ListId, n: int) -> (res: Option<LooseId>)
890        requires
891            old(self).inv(),
892            old(self).lists.dom().contains(id),
893        ensures
894            final(self).inv(),
895            !(0 <= n < old(self).lists[id].list.len()) ==> res is None && *final(self) == *old(
896                self,
897            ),
898            0 <= n < old(self).lists[id].list.len() ==> res is Some,
899    {
900        if !(0 <= n < self.lists[id].list.len()) {
901            // Exec take-at-position returns `None` when `n` is out of
902            // range; the store is unchanged.
903            Option::None
904        } else {
905            let tracked mut owner = self.lists.tracked_remove(id);
906            let tracked frame_own = axiom_take_at_embedded(&mut self.regions, &mut owner, n);
907            self.lists.tracked_insert(id, owner);
908            let ghost new_loose = fresh_loose_id(self.loose);
909
910            self.loose.tracked_insert(new_loose, frame_own);
911
912            Option::Some(new_loose)
913        }
914    }
915
916    // -------------------------------------------------------------------
917    // Persistent cursor lifecycle
918    // -------------------------------------------------------------------
919    /// `LinkedList::cursor_front_mut`: check list `id` out into a cursor
920    /// positioned at the front (index 0). The list leaves `lists` and
921    /// enters `cursors` under the same id (its borrow).
922    pub proof fn step_cursor_front_mut(tracked &mut self, id: ListId)
923        requires
924            old(self).inv(),
925            old(self).lists.dom().contains(id),
926        ensures
927            final(self).inv(),
928            !final(self).lists.dom().contains(id),
929            final(self).cursors.dom().contains(id),
930            final(self).cursors[id] == CursorOwner::front_owner(old(self).lists[id]),
931    {
932        let tracked owner = self.lists.tracked_remove(id);
933        let tracked cur = CursorOwner::tracked_front_owner(owner);
934        self.cursors.tracked_insert(id, cur);
935    }
936
937    /// `LinkedList::cursor_back_mut`: check list `id` out into a cursor
938    /// at the back (the last element, or the ghost slot when empty).
939    pub proof fn step_cursor_back_mut(tracked &mut self, id: ListId)
940        requires
941            old(self).inv(),
942            old(self).lists.dom().contains(id),
943        ensures
944            final(self).inv(),
945            !final(self).lists.dom().contains(id),
946            final(self).cursors.dom().contains(id),
947            final(self).cursors[id] == CursorOwner::back_owner(old(self).lists[id]),
948    {
949        let tracked owner = self.lists.tracked_remove(id);
950        let tracked cur = CursorOwner::tracked_back_owner(owner);
951        self.cursors.tracked_insert(id, cur);
952    }
953
954    /// `LinkedList::cursor_mut_at`.
955    pub proof fn step_cursor_mut_at(tracked &mut self, id: ListId, frame: Paddr) -> (res: bool)
956        requires
957            old(self).inv(),
958            old(self).lists.dom().contains(id),
959        ensures
960            final(self).inv(),
961            // `res` is exactly list membership of `frame`.
962            res == (exists|i: int|
963                0 <= i < old(self).lists[id].list.len() && #[trigger] meta_to_index(
964                    old(self).lists[id].list[i].paddr,
965                ) == frame_to_index(frame)),
966            // On a hit: the list is checked out into a cursor positioned
967            // at the matching link.
968            res ==> !final(self).lists.dom().contains(id) && final(self).cursors.dom().contains(id)
969                && exists|i: int|
970                0 <= i < old(self).lists[id].list.len() && #[trigger] meta_to_index(
971                    old(self).lists[id].list[i].paddr,
972                ) == #[trigger] frame_to_index(frame) && final(self).cursors[id]
973                    == CursorOwner::cursor_mut_at_owner(old(self).lists[id], i),
974            // On a miss: no checkout, the store is unchanged.
975            !res ==> *final(self) == *old(self),
976    {
977        if exists|i: int|
978            0 <= i < self.lists[id].list.len() && #[trigger] meta_to_index(
979                self.lists[id].list[i].paddr,
980            ) == frame_to_index(frame) {
981            let ghost index = choose|i: int|
982                0 <= i < self.lists[id].list.len() && #[trigger] meta_to_index(
983                    self.lists[id].list[i].paddr,
984                ) == frame_to_index(frame);
985            let tracked owner = self.lists.tracked_remove(id);
986            let tracked cur = CursorOwner::tracked_cursor_mut_at_owner(owner, index);
987            self.cursors.tracked_insert(id, cur);
988            true
989        } else {
990            false
991        }
992    }
993
994    /// `CursorMut::move_next`.
995    pub proof fn step_move_next(tracked &mut self, id: CursorId)
996        requires
997            old(self).inv(),
998            old(self).cursors.dom().contains(id),
999        ensures
1000            final(self).inv(),
1001            final(self).regions == old(self).regions,
1002            final(self).cursors[id] == old(self).cursors[id].move_next_owner_spec(),
1003    {
1004        let tracked cur = self.cursors.tracked_remove(id);
1005        let ghost ni = cur.move_next_owner_spec().index;
1006        let tracked CursorOwner { list_own, index: _ } = cur;
1007        let tracked cur2 = CursorOwner::tracked_cursor_mut_at_owner(list_own, ni);
1008        self.cursors.tracked_insert(id, cur2);
1009    }
1010
1011    /// `CursorMut::move_prev`.
1012    pub proof fn step_move_prev(tracked &mut self, id: CursorId)
1013        requires
1014            old(self).inv(),
1015            old(self).cursors.dom().contains(id),
1016        ensures
1017            final(self).inv(),
1018            final(self).regions == old(self).regions,
1019            final(self).cursors[id] == old(self).cursors[id].move_prev_owner_spec(),
1020    {
1021        let tracked cur = self.cursors.tracked_remove(id);
1022        let ghost ni = cur.move_prev_owner_spec().index;
1023        let tracked CursorOwner { list_own, index: _ } = cur;
1024        let tracked cur2 = CursorOwner::tracked_cursor_mut_at_owner(list_own, ni);
1025        self.cursors.tracked_insert(id, cur2);
1026    }
1027
1028    /// `CursorMut::current_meta`.
1029    pub proof fn step_current_meta(tracked &self, id: CursorId) -> (res: Option<LinkOwner>)
1030        requires
1031            self.inv(),
1032            self.cursors.dom().contains(id),
1033        ensures
1034            res == self.cursors[id].current(),
1035            res.is_some() <==> 0 <= self.cursors[id].index < self.cursors[id].length(),
1036    {
1037        self.cursors[id].current()
1038    }
1039
1040    /// `CursorMut::as_list`: borrow the cursor's checked-out list for
1041    /// reading. A pure no-op.
1042    pub proof fn step_as_list(tracked &self, id: CursorId) -> (res: Seq<LinkOwner>)
1043        requires
1044            self.inv(),
1045            self.cursors.dom().contains(id),
1046        ensures
1047            res == self.cursors[id].list_own.list,
1048            res.len() == self.cursors[id].length(),
1049    {
1050        self.cursors[id].list_own.list
1051    }
1052
1053    /// Drop of a `CursorMut`: check the cursor's list back into `lists`
1054    /// under its home id, ending the borrow. Inverse of
1055    /// [`Self::step_cursor_front_mut`] et al.
1056    pub proof fn step_cursor_drop(tracked &mut self, id: CursorId)
1057        requires
1058            old(self).inv(),
1059            old(self).cursors.dom().contains(id),
1060        ensures
1061            final(self).inv(),
1062            !final(self).cursors.dom().contains(id),
1063            final(self).lists.dom().contains(id),
1064            final(self).lists[id] == old(self).cursors[id].list_own,
1065    {
1066        let tracked cur = self.cursors.tracked_remove(id);
1067        let tracked CursorOwner { list_own, index: _ } = cur;
1068        self.lists.tracked_insert(id, list_own);
1069    }
1070
1071    /// `CursorMut::insert_before`.
1072    pub proof fn step_cursor_insert_before(tracked &mut self, id: CursorId, lid: LooseId)
1073        requires
1074            old(self).inv(),
1075            old(self).cursors.dom().contains(id),
1076            old(self).loose.dom().contains(lid),
1077        ensures
1078            final(self).inv(),
1079    {
1080        let ghost old_self = *self;
1081        let ghost n = self.cursors[id].index;
1082        // Avoid every other list's *and* every other cursor's id.
1083        let ghost used = Set::<u64>::full().unwrap().filter(
1084            |x: u64|
1085                (exists|i: ListId| #[trigger]
1086                    old_self.lists.dom().contains(i) && old_self.lists[i].list_id == x) || (exists|
1087                    cid: CursorId,
1088                | #[trigger]
1089                    old_self.cursors.dom().contains(cid) && cid != id
1090                        && old_self.cursors[cid].list_own.list_id == x),
1091        );
1092
1093        let tracked cur = self.cursors.tracked_remove(id);
1094        let tracked CursorOwner { list_own: mut owner, index: _ } = cur;
1095        let tracked mut frame_own = self.loose.tracked_remove(lid);
1096        axiom_insert_before_at_embedded(&mut self.regions, &mut owner, &mut frame_own, n, used);
1097        let tracked cur2 = CursorOwner::tracked_cursor_mut_at_owner(owner, n + 1);
1098        self.cursors.tracked_insert(id, cur2);
1099    }
1100
1101    /// `CursorMut::take_current`: through the checked-out cursor `id`,
1102    pub proof fn step_cursor_take_current(tracked &mut self, id: CursorId) -> (res: Option<LooseId>)
1103        requires
1104            old(self).inv(),
1105            old(self).cursors.dom().contains(id),
1106        ensures
1107            final(self).inv(),
1108            !(0 <= old(self).cursors[id].index < old(self).cursors[id].length()) ==> res is None
1109                && *final(self) == *old(self),
1110            0 <= old(self).cursors[id].index < old(self).cursors[id].length() ==> res is Some,
1111    {
1112        if !(0 <= self.cursors[id].index < self.cursors[id].length()) {
1113            // Exec `CursorMut::take_current` returns `None` when the
1114            // cursor is not on an element; the store is unchanged.
1115            Option::None
1116        } else {
1117            let ghost n = self.cursors[id].index;
1118
1119            let tracked cur = self.cursors.tracked_remove(id);
1120            let tracked CursorOwner { list_own: mut owner, index: _ } = cur;
1121            let tracked frame_own = axiom_take_at_embedded(&mut self.regions, &mut owner, n);
1122            let tracked cur2 = CursorOwner::tracked_cursor_mut_at_owner(owner, n);
1123            self.cursors.tracked_insert(id, cur2);
1124            let ghost new_loose = fresh_loose_id(self.loose);
1125
1126            self.loose.tracked_insert(new_loose, frame_own);
1127
1128            Option::Some(new_loose)
1129        }
1130    }
1131}
1132
1133} // verus!