Expand description
Deep embedding of the VmSpace and VmReader/VmWriter API.
VmStore is the abstract state of a caller of these APIs: it holds
the MetaRegionOwners plus a registry of every owner object the
caller currently has access to.
Op is an ADT enumerating the public exec API. step is the
single proof-mode dispatcher; it requires s.inv() and the
per-op precondition op_pre (which says the ids referenced in
op resolve to existing entries with the right cross-store
relationships). op_pre contains all preconditions necessary
to dispatch each operation, which makes it the cornerstone of soundness.
See its documentation for analysis.
§Module layout
vm_space: ops on thecrate::mm::vm_space::VmSpacetype (new, drop).cursor: ops onCursor/CursorMut(open, drop,query,find_next,jump,map,unmap,protect_next).io: ops onVmReader/VmWriter(creation, drop, the user-space and kernel-space IO methods).trace: explicit-induction theorems overSeq<Op>.
§Soundness boundary: _embedded axioms
Each axiom named <exec_function_path>_embedded mirrors the
ensures clause of one public exec function. Naming is the only
mechanism keeping the axiom in sync with its exec counterpart;
reviewers touching either side should grep for the partner.
§Roadmap — DONE / open work
All five originally-deferred items have landed. Shape B for
segments is fully active: Op::SegmentFromUnused /
Op::SegmentDrop are in the dispatch, [accounting_inv] has the
generalised rc == H + P + cover_count equation, and
[structural_inv] carries raw_count == segment_cover_count +
segment-covered ⟹ Frame-usage + segment range well-formedness.
-
Strengthen
crate::specs::mm::frame::meta_owners::MetaSlotOwner::inv’s SHARED branch — DONE. The branch (0 < rc <= REF_COUNT_MAX) now carriesinner_perms.storage.is_init()andinner_perms.in_list.value() == 0. Therc == 1 ⟹ ...guards onstorage/in_listin [crate::mm::frame::Frame::drop_requires] were dropped. -
Frame::wf(state)— DONE at both layers.- Embedding layer:
lemma_frame_drop_pre_derivablederives all offrame::drop_pre’s residuals (rc not in sentinels,rc <= REF_COUNT_MAX,storage.is_init,in_list == 0,rc == 1 ⟹ paths empty) plus therc == 1 ⟹ handle_count == 1clause froms.inv()+ theFrameEntry’s registration + the segment-cover hypothesis.op_pre[FrameDrop]andstep_frame_dropshrink to id-existence + segment-cover only. - Exec layer:
crate::mm::frame::Frame::inv_with_regionspackages the per-handle cross-object validity (slot/pointer identity + SHARED rc bounds —> 0 ∧ ≠ UNUSED ∧ ≠ UNIQUE ∧ ≤ MAX).Frame::drop_requiresis refactored to readself.inv_with_regions(s) ∧ raw_count == 0 ∧ rc == 1 ⟹ paths empty, which keeps the drop-specific bits explicit while consolidating the static “this Frame is valid against this state” conjuncts. clone_requiresnot refactored: would cascade intoPageTableConfig::clone_requires_concrete(a trait method with multiple implementors); left explicit to keep the change local.- Preservation of
inv_with_regions(FUTURE).Frame::inv_with_regions’s preservation across drops of other handles at the same slot is currently informal (claimed in the docstring; no machine-checked proof). To prove it, everyFrame<M>needs a tracked ghost “reference-count share” certificate that proves “I contribute 1 to my slot’src,” combined with an aggregate invariant onMetaSlotOwnersayingheld_shares == rc.value(). Recommended primitive: [vstd_extra::resource::ghost_resource::tokens::Token] (alias forCountGhost<(), TOTAL>) withTOTAL = REF_COUNT_MAX. The resource framework providessplit/combine/agree/boundedpre-proven; the Frame side adds aTracked<Token<MAX>>field and theMetaSlotOwnerside adds aCountGhostResource<(), MAX>aggregate of remaining shares with the linking invariantrc.value() + remaining == MAX. Cursor map / unmap axioms gain share-juggling clauses (paths_in_pt += 1↔ split off 1 share). The math is proven by vstd_extra; the integration is a multi-day refactor with cascading effects on everyMetaRegionOwnersconsumer. The embedding’shandle_countalready provides the equivalent property at the abstract level, so this is only needed if downstream code outside the embedding’s tracking needsFrame::inv_with_regionspreservation proofs.
- Embedding layer:
3a. Op::Map consumes a FrameId — DONE. Op::Map { c, fid, prop } extracts the matching FrameEntry (so H at the
mapped slot decrements by 1, paired with the cursor axiom’s
paths_in_pt += 1 at the same slot). Combined with the
cursor_mut_map_embedded axiom’s per-slot ensures (rc / usage
/ storage preserved at target, changed-slots ⟹ PT-node ⟹
usage != Frame), [accounting_inv]’s Frame-scoped equation
rc == H + P chains.
3b. Op::Query clone modeling — DONE. The cursor_query_embedded
axiom now returns Option<Paddr>: Some(paddr) when query
resolved a tracked leaf and clone_item bumped rc at that
slot; None otherwise (out-of-range / no leaf / MMIO leaf).
[step_query] consumes the paddr to register a fresh
FrameEntry so H at the cloned slot grows in lockstep with
rc, keeping accounting_inv’s rc == H + P chained.
-
Strengthen
cursor_mut_unmap_embedded— DONE. The axiom now mirrors exec: universal preservation ofraw_count/in_list/usage/self_addr/vtable_ptr; storage preserved at slots ending non-UNUSED; rc doesn’t bump to UNIQUE; at Frame slots the “non-mapping count”rc - paths.lenis invariant with both monotonically non- increasing, and postrc != 0(Frame teardown collapsesrc==0toREF_COUNT_UNUSEDatomically); MMIO slots are untouched (preserving theMetaSlotOwner::invMMIO exception that allows non-emptypaths_in_ptat UNUSED). [step_unmap] discharges accounting via case-splits on Frame / non-Frame / MMIO. -
Shape-B segments — base + split landed; the rest is documented with status per op.
from_unused/drop— DONE. Allocate a segment over a contiguous range of UNUSED frames, and release the segment’s forgotten references with per-frame teardown.split— DONE. Partition the segment at a page-aligned offset;regionsis unchanged because per-paddrcover_countis invariant under the partition.lemma_segment_cover_splitproves the per-paddr invariance.clone— NOT modeled, pending exec fix. Today execSegment::clonebumpsrcbut notraw_countand doesn’t produce a newSegmentOwner, so the clone is un-droppable from verified code. Planned fix (separate session): generaliseFrame::from_rawtoraw_count >= 1+ decrement; weakenSegmentOwner::relate_regionstoraw_count >= 1; replaceRCClone for Segmentwith an inherentclonethat returns(Self, Tracked<SegmentOwner<M>>)and per-framefrom_in_use + ManuallyDrop::new. Once it ships, the embedding addsOp::SegmentClone { sid }that inserts a freshSegmentEntrymirroringsid’s range.next— DONE. The conversion bridge between segment-held forgotten references and user-heldFrame<M>handles. Per-paddr at the popped slot:raw_count -= 1,cover_count -= 1,H += 1,rcunchanged. The accounting equationrc == H + P + cover_countchains in lockstep because H and cover decrement/increment together; structuralraw_count == cover_countchains vialemma_segment_cover_shrink_front.slice— NOT modeled, deferred for the same reason asclone(and probably HARDER, despite initial appearances). Both APIs produce an un-droppable handle (noSegmentOwnerreturned) and don’t bumpraw_count. A faithful fix requires generalisingFrame::from_raw’s precondition fromraw_count <= 1toraw_count >= 1(with decrement-not- zero body) so that sliced/cloned segments — which produce multiple forgotten refs at the same slot — can each be drop-reclaimed. An attempted exec fix in this session revealed unanticipated cascade:borrow/borrow_paddrbridge throughlemma_from_raw_manuallydrop_generalwhich assumes the single-forgotten-ref case, and the PT-nodechild_perms_embeddinginvariant carriesraw_count <= 1without supplying a>= 1companion. Tightening one half breaks the other. Properly resolving this requires either: (a) per-handle ghost certificates (theFrameCertroute documented in the Item 2 future-work note above), or (b) splittingfrom_rawinto two variants — one for the single-forgotten case (existing) and one for multi-forgotten (new) — and threading the discriminator throughSegmentOwnersodropcan pick the right one. Either path is half-day to multi-day work.sliceandcloneshould be tackled together when that engineering effort is funded; the embedding side is ready to receive acover_count + 1/raw_count + 1Op as soon as the exec ships the API.into_raw/from_raw—pub(crate)only in exec, so the embedding can ignore them.
Modules§
- cursor
- Embedding of
Cursor/CursorMutoperations: open, drop, navigation (query/find_next/jump), and mutation (map/unmap/protect_next). - frame
- Embedding of
Framelifecycle operations: allocate (from_unused), acquire-by-paddr (from_in_use), and drop. - io
- Embedding of
VmReader/VmWriteroperations. - segment
- Embedding of
Segmentlifecycle operations: contiguous-range allocate (segment_from_unused_embedded) and drop (segment_drop_embedded). - trace
- Lazy trace generation:
rungenerates a trace on the fly by repeatedly stepping with arbitrary-but-valid Ops. - vm_
space - Embedding of
VmSpace-level operations: creation and drop.
Structs§
- Cursor
Entry - Per-cursor entry in the store.
- Frame
Entry - Per-Frame entry in the store. Represents one outstanding handle to
the slot at
paddr— i.e., one unit of refcount inregions.slot_owners[frame_to_index(paddr)]. - Segment
Entry - Per-Segment entry in the store. Represents one outstanding
Segment<M>covering the contiguous physical rangerange. - VmIo
Entry - Per-VmIo entry in the store.
- VmStore
- Resource store: the abstract state visible to a caller of the VmSpace + VmReader/VmWriter API.
Enums§
- Cursor
Kind - Whether a cursor is a read-only
Cursoror a mutableCursorMut. - Op
- Public exec API of
ostd::mm::vm_spaceandostd::mm::io, lifted to data. - VmIo
Kind - Whether a
VmIoOwnerbacks aVmReaderor aVmWriter.
Functions§
- axiom_
cursor_ entry_ new - axiom_
frame_ entry_ new - axiom_
fresh_ cursor_ id_ not_ in_ dom - axiom_
fresh_ frame_ id_ not_ in_ dom - axiom_
fresh_ segment_ id_ not_ in_ dom - axiom_
fresh_ vm_ io_ id_ not_ in_ dom - axiom_
fresh_ vm_ space_ id_ not_ in_ dom - axiom_
segment_ entry_ new - axiom_
vm_ io_ entry_ new - fresh_
cursor_ id - fresh_
frame_ id - fresh_
segment_ id - fresh_
vm_ io_ id - fresh_
vm_ space_ id - handle_
count - lemma_
frame_ drop_ pre_ derivable - lemma_
handle_ count_ insert_ fresh - lemma_
handle_ count_ remove - lemma_
segment_ cover_ contains - lemma_
segment_ cover_ insert_ inside - lemma_
segment_ cover_ insert_ outside - lemma_
segment_ cover_ remove_ inside - lemma_
segment_ cover_ remove_ outside - lemma_
segment_ cover_ shrink_ front - lemma_
segment_ cover_ split - lemma_
segment_ cover_ witness - op_pre
- segment_
cover_ count - step
Type Aliases§
- Cursor
Id - Logical identifier for a
CursorOwnerin the store. - FrameId
- Logical identifier for a held
crate::mm::frame::Framehandle in the store. - Segment
Id - Logical identifier for a held
crate::mm::frame::Segmenthandle in the store. - VmIoId
- Logical identifier for a
VmIoOwnerin the store. - VmSpace
Id - Logical identifier for a
VmSpaceOwnerin the store.