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