pub struct LinkedListOwner<M: AnyFrameMeta + Repr<MetaSlotSmall>> {
pub list: Seq<LinkOwner>,
pub list_id: u64,
pub _marker: PhantomData<M>,
}Fields§
§list: Seq<LinkOwner>§list_id: u64§_marker: PhantomData<M>Implementations§
Source§impl<M: AnyFrameMeta + Repr<MetaSlotSmall>> LinkedListOwner<M>
impl<M: AnyFrameMeta + Repr<MetaSlotSmall>> LinkedListOwner<M>
Sourcepub open spec fn inv_at(self, i: int) -> bool
pub open spec fn inv_at(self, i: int) -> bool
{
&&& self.list[i].inv()
&&& self.list[i].in_list == self.list_id
}Per-link structural invariant: the link’s own inv() holds and its
in_list tag matches the list’s list_id. The per-link metadata facts
(perm wf/is_init/pointer wiring) are tracked via relate_region_at
against the global MetaRegionOwners, NOT through this predicate.
Sourcepub open spec fn slot_index_at(self, i: int) -> usize
pub open spec fn slot_index_at(self, i: int) -> usize
{ frame_to_index(meta_to_frame_spec(self.list[i].paddr)) }The region slot index keyed by the i-th link’s meta-slot address.
Sourcepub open spec fn meta_perm_of(
self,
regions: MetaRegionOwners,
i: int,
) -> PointsTo<MetaSlot, Metadata<Link<M>>>
pub open spec fn meta_perm_of( self, regions: MetaRegionOwners, i: int, ) -> PointsTo<MetaSlot, Metadata<Link<M>>>
{
let idx = self.slot_index_at(i);
vstd_extra::cast_ptr::PointsTo::new_spec(
regions.slots[idx],
regions.slot_owners[idx].inner_perms,
)
}The typed permission for the i-th link, reconstructed from the region:
the outer pointer-perm regions.slots[idx] paired with the inner perms
regions.slot_owners[idx].inner_perms.
Sourcepub open spec fn relate_region_at(self, regions: MetaRegionOwners, i: int) -> bool
pub open spec fn relate_region_at(self, regions: MetaRegionOwners, i: int) -> bool
{
let idx = self.slot_index_at(i);
let perm = self.meta_perm_of(regions, i);
&&& regions.slots.contains_key(idx)
&&& regions.slot_owners.contains_key(idx)
&&& perm.addr() == self.list[i].paddr
&&& perm.points_to.addr() == self.list[i].paddr
&&& perm.inner_perms.ref_count.value() == REF_COUNT_UNIQUE
&&& perm.wf(&perm.inner_perms)
&&& perm.addr() % META_SLOT_SIZE == 0
&&& FRAME_METADATA_RANGE.start <= perm.addr()
< FRAME_METADATA_RANGE.start + MAX_NR_PAGES * META_SLOT_SIZE
&&& perm.is_init()
&&& perm.value().metadata.wf(self.list[i])
&&& i == 0 <==> perm.value().metadata.prev is None
&&& i == self.list.len() - 1 <==> perm.value().metadata.next is None
&&& 0 < i
==> {
&&& perm.value().metadata.prev is Some
&&& perm.value().metadata.prev.unwrap().addr()
== self.meta_perm_of(regions, i - 1).addr()
&&& perm.value().metadata.prev.unwrap().ptr
== self.meta_perm_of(regions, i - 1).points_to.pptr()
}
&&& i < self.list.len() - 1
==> {
&&& perm.value().metadata.next is Some
&&& perm.value().metadata.next.unwrap().addr()
== self.meta_perm_of(regions, i + 1).addr()
&&& perm.value().metadata.next.unwrap().ptr
== self.meta_perm_of(regions, i + 1).points_to.pptr()
}
&&& self.list[i].inv()
&&& self.list[i].in_list == self.list_id
}The per-link invariant expressed over the region permission
(meta_perm_of) rather than the list’s owned perms[i]. This is the
inv_at analog that connects each list element to its region slot, so
accessors can reason about the link’s metadata without bringing the
list’s perms[i] into scope (which would conflict — two permissions at
the same address).
Sourcepub open spec fn relate_region(self, regions: MetaRegionOwners) -> bool
pub open spec fn relate_region(self, regions: MetaRegionOwners) -> bool
{
&&& forall |i: int| 0 <= i < self.list.len() ==> self.relate_region_at(regions, i)
&&& forall |i: int, j: int| {
0 <= i < self.list.len() && 0 <= j < self.list.len() && i != j
==> self.slot_index_at(i) != self.slot_index_at(j)
}
&&& self.list.len() > 0 ==> self.list_id != 0
}The list-wide region relation: every link satisfies relate_region_at,
and distinct list positions map to distinct region slot indices (so a
frame appears at most once — required by the borrow model, where link
edits mutate regions.slots[slot_index_at(i)] and must not alias).
Sourcepub proof fn length_le_max_meta_slots(self, regions: MetaRegionOwners)
pub proof fn length_le_max_meta_slots(self, regions: MetaRegionOwners)
self.relate_region(regions),regions.inv(),ensuresself.list.len() <= max_meta_slots(),Pigeonhole bound: the list is no longer than the number of meta slots.
Each link occupies a region slot (relate_region_at ⟹
slots.contains_key(slot_index_at(i)), and regions.inv() ⟹
slot_index_at(i) < max_meta_slots()), and distinct positions occupy
distinct slots (relate_region’s injectivity). So the positions inject
into [0, max_meta_slots()) and the length is capped by it.
Sourcepub proof fn length_lt_usize_max(self, regions: MetaRegionOwners)
pub proof fn length_lt_usize_max(self, regions: MetaRegionOwners)
self.relate_region(regions),regions.inv(),ensuresself.list.len() < usize::MAX,The list counter can never saturate: its length is capped by
max_meta_slots() (see Self::length_le_max_meta_slots), which is far
below usize::MAX. Lets insert_before discharge the size + 1
overflow check without a caller-supplied non-fullness precondition.
Sourcepub proof fn relate_region_at_facts(self, regions: MetaRegionOwners, i: int)
pub proof fn relate_region_at_facts(self, regions: MetaRegionOwners, i: int)
self.relate_region_at(regions, i),ensures({
let idx = self.slot_index_at(i);
let perm = self.meta_perm_of(regions, i);
&&& regions.slots.contains_key(idx)
&&& regions.slot_owners.contains_key(idx)
&&& perm.addr() == self.list[i].paddr
&&& perm.points_to.addr() == self.list[i].paddr
&&& perm.inner_perms.ref_count.value() == REF_COUNT_UNIQUE
&&& perm.wf(&perm.inner_perms)
&&& perm.addr() % META_SLOT_SIZE == 0
&&& FRAME_METADATA_RANGE.start <= perm.addr()
< FRAME_METADATA_RANGE.start + MAX_NR_PAGES * META_SLOT_SIZE
&&& perm.is_init()
&&& perm.value().metadata.wf(self.list[i])
&&& (i == 0 <==> perm.value().metadata.prev is None)
&&& (i == self.list.len() - 1 <==> perm.value().metadata.next is None)
&&& (0 < i
==> {
&&& perm.value().metadata.prev is Some
&&& perm.value().metadata.prev.unwrap().addr()
== self.meta_perm_of(regions, i - 1).addr()
&&& perm.value().metadata.prev.unwrap().ptr
== self.meta_perm_of(regions, i - 1).points_to.pptr()
})
&&& (i < self.list.len() - 1
==> {
&&& perm.value().metadata.next is Some
&&& perm.value().metadata.next.unwrap().addr()
== self.meta_perm_of(regions, i + 1).addr()
&&& perm.value().metadata.next.unwrap().ptr
== self.meta_perm_of(regions, i + 1).points_to.pptr()
})
&&& self.list[i].inv()
&&& self.list[i].in_list == self.list_id
}),Unfolds the opaque relate_region_at ONCE and exposes its clauses.
relate_region_at is opaque to avoid meta_perm_of quantifier
explosion at use sites; this lemma localizes the reveal so callers get
the facts at a single index without re-exploding the SMT context.
Sourcepub proof fn relate_region_at_from_clauses(self, regions: MetaRegionOwners, i: int)
pub proof fn relate_region_at_from_clauses(self, regions: MetaRegionOwners, i: int)
({
let idx = self.slot_index_at(i);
let perm = self.meta_perm_of(regions, i);
&&& regions.slots.contains_key(idx)
&&& regions.slot_owners.contains_key(idx)
&&& perm.addr() == self.list[i].paddr
&&& perm.points_to.addr() == self.list[i].paddr
&&& perm.inner_perms.ref_count.value() == REF_COUNT_UNIQUE
&&& perm.wf(&perm.inner_perms)
&&& perm.addr() % META_SLOT_SIZE == 0
&&& FRAME_METADATA_RANGE.start <= perm.addr()
< FRAME_METADATA_RANGE.start + MAX_NR_PAGES * META_SLOT_SIZE
&&& perm.is_init()
&&& perm.value().metadata.wf(self.list[i])
&&& (i == 0 <==> perm.value().metadata.prev is None)
&&& (i == self.list.len() - 1 <==> perm.value().metadata.next is None)
&&& (0 < i
==> {
&&& perm.value().metadata.prev is Some
&&& perm.value().metadata.prev.unwrap().addr()
== self.meta_perm_of(regions, i - 1).addr()
&&& perm.value().metadata.prev.unwrap().ptr
== self.meta_perm_of(regions, i - 1).points_to.pptr()
})
&&& (i < self.list.len() - 1
==> {
&&& perm.value().metadata.next is Some
&&& perm.value().metadata.next.unwrap().addr()
== self.meta_perm_of(regions, i + 1).addr()
&&& perm.value().metadata.next.unwrap().ptr
== self.meta_perm_of(regions, i + 1).points_to.pptr()
})
&&& self.list[i].inv()
&&& self.list[i].in_list == self.list_id
}),ensuresself.relate_region_at(regions, i),Constructor (inverse of [relate_region_at_facts]): establishes the
opaque relate_region_at from its unfolded clauses. Used by the pop/
insert “surgery” proofs, which assemble each clause for the new list and
then fold them back into the opaque predicate.
Sourcepub proof fn relate_region_preserved_external_change(
self,
regions1: MetaRegionOwners,
regions2: MetaRegionOwners,
)
pub proof fn relate_region_preserved_external_change( self, regions1: MetaRegionOwners, regions2: MetaRegionOwners, )
self.relate_region(regions1),regions2.slots == regions1.slots,forall |i: int| {
0 <= i < self.list.len()
==> {
let idx = self.slot_index_at(i);
&&& regions2.slot_owners.contains_key(idx)
&&& regions2.slot_owners[idx] == regions1.slot_owners[idx]
}
},ensuresself.relate_region(regions2),relate_region is preserved under a region change that doesn’t touch
any of the list’s slots. Used by LinkedList::drop’s loop body: after
take_current pops position 0, the popped slot is dropped via
frame.drop, which only modifies regions.slot_owners[cur_idx] and
leaves regions.slots fully untouched. Since the cursor’s remaining
list never contains cur_idx (distinctness on the original list),
relate_region carries through.
Sourcepub proof fn pop_preserves_relate_region(
old: LinkedListOwner<M>,
r0: MetaRegionOwners,
new: LinkedListOwner<M>,
fr: MetaRegionOwners,
n: int,
)
pub proof fn pop_preserves_relate_region( old: LinkedListOwner<M>, r0: MetaRegionOwners, new: LinkedListOwner<M>, fr: MetaRegionOwners, n: int, )
0 <= n < old.list.len(),old.relate_region(r0),new.list == old.list.remove(n),new.list_id == old.list_id,forall |p: int| {
(0 <= p < old.list.len() && p != n)
==> ({
let i = old.slot_index_at(p);
let fp = vstd_extra::cast_ptr::PointsTo::<
MetaSlot,
Metadata<Link<M>>,
>::new_spec(fr.slots[i], fr.slot_owners[i].inner_perms);
&&& fr.slots.contains_key(i)
&&& fr.slot_owners.contains_key(i)
&&& fp.addr() == old.list[p].paddr
&&& fp.points_to.addr() == old.list[p].paddr
&&& fp.points_to.pptr() == r0.slots[i].pptr()
&&& fp.inner_perms.ref_count.value() == REF_COUNT_UNIQUE
&&& fp.wf(&fp.inner_perms)
&&& fp.addr() % META_SLOT_SIZE == 0
&&& FRAME_METADATA_RANGE.start <= fp.addr()
< FRAME_METADATA_RANGE.start + MAX_NR_PAGES * META_SLOT_SIZE
&&& fp.is_init()
&&& (p == n - 1
==> fp.value().metadata.next
== old.meta_perm_of(r0, n).value().metadata.next)
&&& (p != n - 1
==> fp.value().metadata.next
== old.meta_perm_of(r0, p).value().metadata.next)
&&& (p == n + 1
==> fp.value().metadata.prev
== old.meta_perm_of(r0, n).value().metadata.prev)
&&& (p != n + 1
==> fp.value().metadata.prev
== old.meta_perm_of(r0, p).value().metadata.prev)
})
},ensuresnew.relate_region(fr),The list-rewiring “surgery” for popping the element at index n: given
the entry invariant old.relate_region(r0) and a characterization of the
post-pop region fr (every surviving slot keeps its local facts and
pointers, except the two neighbors whose next/prev were rewired to
bridge the gap), the shrunk list new satisfies relate_region(fr).
New position k maps to old position p = (k < n ? k : k+1); the
neighbor of k maps to p ± 1 except across the cut (new position n-1
reaches old n+1, new position n reaches old n-1), which is exactly
where the body rewired the link pointers.
Sourcepub proof fn insert_preserves_relate_region(
old: LinkedListOwner<M>,
r0: MetaRegionOwners,
new: LinkedListOwner<M>,
fr: MetaRegionOwners,
n: int,
link: LinkOwner,
)
pub proof fn insert_preserves_relate_region( old: LinkedListOwner<M>, r0: MetaRegionOwners, new: LinkedListOwner<M>, fr: MetaRegionOwners, n: int, link: LinkOwner, )
0 <= n <= old.list.len(),old.relate_region(r0),new.list == old.list.insert(n, link),new.list_id != 0,old.list.len() > 0 ==> new.list_id == old.list_id,link.in_list == new.list_id,forall |p: int| {
(0 <= p < old.list.len()) ==> old.slot_index_at(p) != new.slot_index_at(n)
},({
let ins = new.slot_index_at(n);
let fpn = vstd_extra::cast_ptr::PointsTo::<
MetaSlot,
Metadata<Link<M>>,
>::new_spec(fr.slots[ins], fr.slot_owners[ins].inner_perms);
&&& fr.slots.contains_key(ins)
&&& fr.slot_owners.contains_key(ins)
&&& fpn.addr() == link.paddr
&&& fpn.points_to.addr() == link.paddr
&&& fpn.inner_perms.ref_count.value() == REF_COUNT_UNIQUE
&&& fpn.wf(&fpn.inner_perms)
&&& fpn.addr() % META_SLOT_SIZE == 0
&&& FRAME_METADATA_RANGE.start <= fpn.addr()
< FRAME_METADATA_RANGE.start + MAX_NR_PAGES * META_SLOT_SIZE
&&& fpn.is_init()
&&& (n == 0 <==> fpn.value().metadata.prev is None)
&&& (n == old.list.len() <==> fpn.value().metadata.next is None)
&&& (n > 0
==> {
&&& fpn.value().metadata.prev is Some
&&& fpn.value().metadata.prev.unwrap().addr() == old.list[n - 1].paddr
&&& fpn.value().metadata.prev.unwrap().ptr
== r0.slots[old.slot_index_at(n - 1)].pptr()
})
&&& (n < old.list.len()
==> {
&&& fpn.value().metadata.next is Some
&&& fpn.value().metadata.next.unwrap().addr() == old.list[n].paddr
&&& fpn.value().metadata.next.unwrap().ptr
== r0.slots[old.slot_index_at(n)].pptr()
})
}),forall |p: int| {
(0 <= p < old.list.len())
==> ({
let i = old.slot_index_at(p);
let ins = new.slot_index_at(n);
let fp = vstd_extra::cast_ptr::PointsTo::<
MetaSlot,
Metadata<Link<M>>,
>::new_spec(fr.slots[i], fr.slot_owners[i].inner_perms);
&&& fr.slots.contains_key(i)
&&& fr.slot_owners.contains_key(i)
&&& fp.addr() == old.list[p].paddr
&&& fp.points_to.addr() == old.list[p].paddr
&&& fp.points_to.pptr() == r0.slots[i].pptr()
&&& fp.inner_perms.ref_count.value() == REF_COUNT_UNIQUE
&&& fp.wf(&fp.inner_perms)
&&& fp.addr() % META_SLOT_SIZE == 0
&&& FRAME_METADATA_RANGE.start <= fp.addr()
< FRAME_METADATA_RANGE.start + MAX_NR_PAGES * META_SLOT_SIZE
&&& fp.is_init()
&&& (p == n - 1
==> {
&&& fp.value().metadata.next is Some
&&& fp.value().metadata.next.unwrap().addr() == link.paddr
&&& fp.value().metadata.next.unwrap().ptr == fr.slots[ins].pptr()
})
&&& (p != n - 1
==> fp.value().metadata.next
== old.meta_perm_of(r0, p).value().metadata.next)
&&& (p == n
==> {
&&& fp.value().metadata.prev is Some
&&& fp.value().metadata.prev.unwrap().addr() == link.paddr
&&& fp.value().metadata.prev.unwrap().ptr == fr.slots[ins].pptr()
})
&&& (p != n
==> fp.value().metadata.prev
== old.meta_perm_of(r0, p).value().metadata.prev)
})
},ensuresnew.relate_region(fr),The list-rewiring “surgery” for inserting link before index n
(0 <= n <= old.list.len()): given the entry relate_region and a
per-slot characterization of the post-insert region fr, the longer list
new = old.list.insert(n, link) satisfies relate_region(fr).
New position k maps to old position k (k<n), is the inserted link
(k==n), or maps to old k-1 (k>n). The inserted link sits at slot
ins = new.slot_index_at(n); its prev/next point to old n-1/n
(or None at the ends), and old n-1’s next / old n’s prev are
rewired to point at the inserted link. Mirror of
[pop_preserves_relate_region].
Sourcepub open spec fn view_helper(owners: Seq<LinkOwner>) -> Seq<LinkModel>
pub open spec fn view_helper(owners: Seq<LinkOwner>) -> Seq<LinkModel>
{
if owners.len() == 0 {
Seq::<LinkModel>::empty()
} else {
seq![owners[0].view()].add(Self::view_helper(owners.remove(0)))
}
}Sourcepub proof fn view_preserves_len(owners: Seq<LinkOwner>)
pub proof fn view_preserves_len(owners: Seq<LinkOwner>)
Self::view_helper(owners).len() == owners.len(),Sourcepub proof fn view_helper_index(owners: Seq<LinkOwner>, i: int)
pub proof fn view_helper_index(owners: Seq<LinkOwner>, i: int)
0 <= i < owners.len(),ensuresSelf::view_helper(owners)[i] == owners[i].view(),Proves that view_helper preserves indexing: view_helper(s)[i] == s[i].view()
Sourcepub proof fn view_helper_remove(owners: Seq<LinkOwner>, i: int)
pub proof fn view_helper_remove(owners: Seq<LinkOwner>, i: int)
0 <= i < owners.len(),ensuresSelf::view_helper(owners.remove(i)) =~= Self::view_helper(owners).remove(i),Proves that view_helper commutes with remove: view_helper(s.remove(i)) =~= view_helper(s).remove(i)
Sourcepub proof fn view_helper_insert(owners: Seq<LinkOwner>, i: int, v: LinkOwner)
pub proof fn view_helper_insert(owners: Seq<LinkOwner>, i: int, v: LinkOwner)
0 <= i <= owners.len(),ensuresSelf::view_helper(owners.insert(i, v)) =~= Self::view_helper(owners).insert(i, v.view()),Proves that view_helper commutes with insert: view_helper(s.insert(i, v)) =~= view_helper(s).insert(i, v.view())
Source§impl<M: AnyFrameMeta + Repr<MetaSlotSmall>> LinkedListOwner<M>
impl<M: AnyFrameMeta + Repr<MetaSlotSmall>> LinkedListOwner<M>
Sourcepub proof fn tracked_take(tracked owner: &mut Self) -> tracked res : Self
pub proof fn tracked_take(tracked owner: &mut Self) -> tracked res : Self
res == *old(owner),final(owner).list =~= Seq::<LinkOwner>::empty(),final(owner).inv(),Take ownership of *owner by swapping it with a fresh empty
LinkedListOwner. The resulting “leftover” *owner has an empty
list, so its inv() holds vacuously. Used by drop-style call sites
that need to feed an owned LinkedListOwner to a downstream API while
themselves only having a &mut to it.
Sourcepub proof fn tracked_destroy_empty(tracked self)
pub proof fn tracked_destroy_empty(tracked self)
self.list =~= Seq::<LinkOwner>::empty(),Discard a logically-empty LinkedListOwner. Sound because such an
owner has an empty list and claims no external permissions (the
borrow model parks all permissions in MetaRegionOwners).