Skip to main content

ostd/specs/mm/
virt_mem.rs

1//! Virtual-memory specification model used by [`VmSpace`] proofs.
2//!
3//! This module defines:
4//! - [`VirtPtr`], a specification-level virtual pointer over a bounded virtual range;
5//! - [`FrameContents`], a physical-frame content model with alignment and range invariants;
6//! - [`MemView`], a projection of virtual-to-physical mappings and frame contents that supports
7//!   translation, read/write reasoning, borrowing, splitting, and joining.
8//!
9//! The model is intentionally verification-oriented (Verus specs/proofs) and is used by
10//! [`crate::mm::vm_space`] to state and prove reader/writer permission isolation as well
11//! as correctness of low-level virtual memory operations that are used by them.
12//!
13//! [`VmSpace`]: crate::mm::vm_space::VmSpace
14use vstd::pervasive::arbitrary;
15use vstd::prelude::*;
16use vstd_extra::prelude::*;
17
18use vstd::raw_ptr;
19use vstd::set;
20
21use core::marker::PhantomData;
22use core::ops::Range;
23
24use crate::Pod;
25use crate::mm::{Paddr, PodOnce, Vaddr};
26use crate::specs::arch::MAX_PADDR;
27use crate::specs::mm::page_table::Mapping;
28use ostd_pod::{decode_pod, lemma_decode_pod_inverse, pod_bytes};
29
30verus! {
31
32/// Specification-level virtual pointer with an associated half-open range.
33///
34/// [`VirtPtr`] models a C-like pointer used by verification code:
35/// - `vaddr` is the current cursor position;
36/// - `range` is the allocation-like bounds `[start, end)`.
37///
38/// Most operations require `is_valid()` (i.e., the current pointer is within
39/// range and not one-past-end), while offset operations additionally require
40/// the computed address to stay inside the same range.
41pub struct VirtPtr {
42    /// Current virtual address represented by this pointer value.
43    pub vaddr: Vaddr,
44    /// Logical bounds of the pointer's valid object/range.
45    pub range: Ghost<Range<Vaddr>>,
46}
47
48/// Byte contents of one physical frame range tracked in a [`MemView`].
49///
50/// This is the physical-memory side of the model: each frame base `Paddr` maps
51/// to a [`FrameContents`] value whose metadata (`size`, `range`) constrains how
52/// `contents` can be interpreted.
53pub ghost struct FrameContents {
54    /// Per-byte initialization/value state for this frame.
55    pub contents: Seq<raw_ptr::MemContents<u8>>,
56    /// Frame size in bytes.
57    pub size: usize,
58    /// Physical range covered by this frame, modeled as `[start, end)`.
59    pub range: Range<Paddr>,
60}
61
62impl Inv for FrameContents {
63    /// Well-formedness invariant for [`FrameContents`].
64    ///
65    /// # Invariant
66    /// - `contents.len() == size`.
67    /// - `size == range.end - range.start`.
68    /// - `range.start` and `range.end` are aligned to `size`.
69    /// - `range` is ordered and remains below [`MAX_PADDR`].
70    open spec fn inv(self) -> bool {
71        &&& self.contents.len() == self.size@
72        &&& self.size@ == self.range.end - self.range.start
73        &&& self.range.start % self.size == 0
74        &&& self.range.end % self.size == 0
75        &&& self.range.start <= self.range.end < MAX_PADDR
76    }
77}
78
79/// A local virtual-memory view used in proofs.
80///
81/// A [`MemView`] pairs:
82/// - [`Self::mappings`]: a set of [`Mapping`] entries used by [`Self::addr_transl`];
83/// - [`Self::memory`]: frame bytes keyed by physical frame base (`Paddr`) via [`FrameContents`].
84///
85/// In practice this view is obtained from [`GlobalMemView`] using
86/// [`GlobalMemView::take_view`], then consumed by APIs such as [`VirtPtr::read`],
87/// [`VirtPtr::write`], and higher-level ownership proofs in
88/// [`VmSpaceOwner`].
89///
90/// [`VmSpaceOwner`]: crate::mm::vm_space::vm_space_specs::VmSpaceOwner
91pub tracked struct MemView {
92    /// Virtual-to-physical mapping set used for address translation.
93    pub mappings: Set<Mapping>,
94    /// Physical frame contents for mapped pages referenced by [`Self::mappings`].
95    pub memory: Map<Paddr, FrameContents>,
96}
97
98impl MemView {
99    /// Translates a virtual address to `(frame_base_pa, frame_offset)`.
100    ///
101    /// Returns [`Option::None`] if no mapping in [`Self::mappings`] covers `va`.
102    pub open spec fn addr_transl(self, va: usize) -> Option<(usize, usize)> {
103        let mappings = self.mappings.filter(|m: Mapping| m.va_range.start <= va < m.va_range.end);
104        if 0 < mappings.len() {
105            let m = mappings.choose();  // In a well-formed PT there will only be one, but if malformed this is non-deterministic!
106            let off = va - m.va_range.start;
107            Some((m.pa_range.start, off as usize))
108        } else {
109            None
110        }
111    }
112
113    /// Specification read through virtual translation.
114    ///
115    /// Equivalent to resolving via [`Self::addr_transl`] and reading from [`Self::memory`].
116    pub open spec fn read(self, va: usize) -> raw_ptr::MemContents<u8> {
117        let (pa, off) = self.addr_transl(va)->0;
118        self.memory[pa].contents[off as int]
119    }
120
121    /// Specification write through virtual translation.
122    ///
123    /// Returns a new [`MemView`] with one byte updated, preserving all mappings.
124    pub open spec fn write(self, va: usize, x: u8) -> Self {
125        let (pa, off) = self.addr_transl(va)->0;
126        MemView {
127            memory: self.memory.insert(
128                pa,
129                FrameContents {
130                    contents: self.memory[pa].contents.update(
131                        off as int,
132                        raw_ptr::MemContents::Init(x),
133                    ),
134                    ..self.memory[pa]
135                },
136            ),
137            ..self
138        }
139    }
140
141    /// Spec for writing `bytes.len()` bytes starting at `va`.
142    ///
143    /// `bytes[0]` is written to `va`, `bytes[1]` to `va + 1`, etc.
144    pub open spec fn write_bytes(self, va: usize, bytes: Seq<u8>) -> Self
145        decreases bytes.len(),
146    {
147        if bytes.len() == 0 {
148            self
149        } else {
150            self.write(va, bytes[0]).write_bytes((va + 1) as usize, bytes.drop_first())
151        }
152    }
153
154    /// Spec for reading `len` bytes starting at `va`.
155    ///
156    /// The byte at `va + i` is `self.read(va + i).value()`. Callers should only
157    /// rely on the result when every byte in the range is initialized; otherwise
158    /// the returned sequence contains arbitrary values where memory is `Uninit`.
159    pub open spec fn read_bytes(self, va: usize, len: usize) -> Seq<u8>
160        decreases len,
161    {
162        if len == 0 {
163            Seq::empty()
164        } else {
165            seq![self.read(va).value()].add(self.read_bytes((va + 1) as usize, (len - 1) as usize))
166        }
167    }
168
169    /// Lemma: [`Self::write_bytes`] preserves [`Self::mappings`].
170    pub proof fn lemma_write_bytes_mappings(self, va: usize, bytes: Seq<u8>)
171        ensures
172            self.write_bytes(va, bytes).mappings == self.mappings,
173        decreases bytes.len(),
174    {
175        if bytes.len() > 0 {
176            self.write(va, bytes[0]).lemma_write_bytes_mappings(
177                (va + 1) as usize,
178                bytes.drop_first(),
179            );
180        }
181    }
182
183    /// Lemma: [`Self::write_bytes`] preserves [`Self::addr_transl`] at every address.
184    pub proof fn lemma_write_bytes_addr_transl(self, va: usize, bytes: Seq<u8>, query: usize)
185        ensures
186            self.write_bytes(va, bytes).addr_transl(query) == self.addr_transl(query),
187        decreases bytes.len(),
188    {
189        if bytes.len() > 0 {
190            self.write(va, bytes[0]).lemma_write_bytes_addr_transl(
191                (va + 1) as usize,
192                bytes.drop_first(),
193                query,
194            );
195        }
196    }
197
198    /// Lemma: [`Self::write_bytes`] only grows [`Self::memory`]'s domain.
199    pub proof fn lemma_write_bytes_memory_dom_grows(self, va: usize, bytes: Seq<u8>)
200        ensures
201            self.memory.dom().subset_of(self.write_bytes(va, bytes).memory.dom()),
202        decreases bytes.len(),
203    {
204        if bytes.len() > 0 {
205            self.write(va, bytes[0]).lemma_write_bytes_memory_dom_grows(
206                (va + 1) as usize,
207                bytes.drop_first(),
208            );
209        }
210    }
211
212    /// Whether two virtual addresses denote equal byte contents in this view.
213    pub open spec fn eq_at(self, va1: usize, va2: usize) -> bool {
214        let (pa1, off1) = self.addr_transl(va1)->0;
215        let (pa2, off2) = self.addr_transl(va2)->0;
216        self.memory[pa1].contents[off1 as int] == self.memory[pa2].contents[off2 as int]
217    }
218
219    /// Whether `va` is translated and mapped to frame base `pa`.
220    pub open spec fn is_mapped(self, va: usize, pa: usize) -> bool {
221        self.addr_transl(va) is Some && self.addr_transl(va)->Some_0.0 == pa
222    }
223
224    /// Specification for borrowing a sub-view covering `[vaddr, vaddr + len)`.
225    ///
226    /// The result keeps overlapping mappings and restricts memory to physical
227    /// frames reachable from that virtual range.
228    pub open spec fn borrow_at(&self, vaddr: usize, len: usize) -> MemView {
229        let range_end = vaddr + len;
230
231        let valid_pas = self.memory.dom().filter(
232            |pa: usize|
233                exists|va: usize| vaddr <= va < range_end && #[trigger] self.is_mapped(va, pa),
234        );
235
236        MemView {
237            mappings: self.mappings.filter(
238                |m: Mapping| m.va_range.start < range_end && m.va_range.end > vaddr,
239            ),
240            memory: self.memory.restrict(valid_pas),
241        }
242    }
243
244    /// Whether mappings in this view are pairwise VA-disjoint.
245    pub open spec fn mappings_are_disjoint(self) -> bool {
246        forall|m1: Mapping, m2: Mapping|
247            #![trigger self.mappings.contains(m1), self.mappings.contains(m2)]
248            self.mappings.contains(m1) && self.mappings.contains(m2) && m1 != m2 ==> {
249                m1.va_range.end <= m2.va_range.start || m2.va_range.end <= m1.va_range.start
250            }
251    }
252
253    /// Specification for splitting this view at `split_end = vaddr + len`.
254    ///
255    /// Returns `(left, right)` where:
256    /// - `left` covers `[vaddr, split_end)`, i.e. all mappings overlapping that range.
257    /// - `right` covers addresses `>= split_end`.
258    pub open spec fn split(self, vaddr: usize, len: usize) -> (MemView, MemView) {
259        let split_end = vaddr + len;
260
261        // The left part.
262        let left_mappings = self.mappings.filter(
263            |m: Mapping| m.va_range.start < split_end && m.va_range.end > vaddr,
264        );
265        let right_mappings = self.mappings.filter(|m: Mapping| m.va_range.end > split_end);
266
267        let left_pas = self.memory.dom().filter(
268            |pa: usize| exists|va: usize| vaddr <= va < split_end && self.is_mapped(va, pa),
269        );
270        let right_pas = self.memory.dom().filter(
271            |pa: usize| exists|va: usize| va >= split_end && self.is_mapped(va, pa),
272        );
273
274        (
275            MemView { mappings: left_mappings, memory: self.memory.restrict(left_pas) },
276            MemView { mappings: right_mappings, memory: self.memory.restrict(right_pas) },
277        )
278    }
279
280    /// Tracked wrapper of [`Self::borrow_at`].
281    ///
282    /// # Verified Properties
283    ///
284    /// ## Postconditions
285    /// - `r == self.borrow_at(vaddr, len)`.
286    #[verifier::external_body]
287    pub proof fn tracked_borrow_at(tracked &self, vaddr: usize, len: usize) -> (tracked r: MemView)
288        ensures
289            r == self.borrow_at(vaddr, len),
290    {
291        unimplemented!()
292    }
293
294    /// Tracked wrapper of [`Self::split`].
295    ///
296    /// # Verified Properties
297    ///
298    /// ## Postconditions
299    /// - `r == self.split(vaddr, len)`.
300    #[verifier::external_body]
301    pub proof fn tracked_split(tracked self, vaddr: usize, len: usize) -> (tracked r: (Self, Self))
302        ensures
303            r == self.split(vaddr, len),
304    {
305        unimplemented!()
306    }
307
308    /// Lemma: [`Self::split`] preserves translation semantics on each side.
309    ///
310    /// # Verified Properties
311    ///
312    /// ## Preconditions
313    /// - `original.split(vaddr, len) == (left, right)`.
314    ///
315    /// ## Postconditions
316    /// - `right.memory.dom().subset_of(original.memory.dom())`.
317    /// - For any `va` in `[vaddr, vaddr + len)`,
318    ///   `original.addr_transl(va) == left.addr_transl(va)`.
319    /// - For any `va >= vaddr + len`,
320    ///   `original.addr_transl(va) == right.addr_transl(va)`.
321    pub proof fn lemma_split_preserves_transl(
322        original: MemView,
323        vaddr: usize,
324        len: usize,
325        left: MemView,
326        right: MemView,
327    )
328        requires
329            original.split(vaddr, len) == (left, right),
330        ensures
331            right.memory.dom().subset_of(original.memory.dom()),
332            forall|va: usize|
333                vaddr <= va < vaddr + len ==> {
334                    #[trigger] original.addr_transl(va) == left.addr_transl(va)
335                },
336            forall|va: usize|
337                va >= vaddr + len ==> {
338                    #[trigger] original.addr_transl(va) == right.addr_transl(va)
339                },
340    {
341        assert forall|va: usize| vaddr <= va < vaddr + len implies original.addr_transl(va)
342            == left.addr_transl(va) by {
343            assert(left.mappings == original.mappings.filter(
344                |m: Mapping| m.va_range.start < vaddr + len && m.va_range.end > vaddr,
345            ));
346            let o_mappings = original.mappings.filter(
347                |m: Mapping| m.va_range.start <= va < m.va_range.end,
348            );
349            let l_mappings = left.mappings.filter(
350                |m: Mapping| m.va_range.start <= va < m.va_range.end,
351            );
352
353            assert forall|m: Mapping| #[trigger] o_mappings.contains(m) implies l_mappings.contains(
354                m,
355            ) by {
356                assert(left.mappings.contains(m));
357            };
358            assert(o_mappings == l_mappings);
359        }
360
361        assert forall|va: usize| va >= vaddr + len implies original.addr_transl(va)
362            == right.addr_transl(va) by {
363            let split_end = vaddr + len;
364
365            let o_mappings = original.mappings.filter(
366                |m: Mapping| m.va_range.start <= va < m.va_range.end,
367            );
368            let r_mappings = right.mappings.filter(
369                |m: Mapping| m.va_range.start <= va < m.va_range.end,
370            );
371
372            assert forall|m: Mapping| o_mappings.contains(m) implies r_mappings.contains(m) by {
373                assert(right.mappings.contains(m));
374                assert(r_mappings.contains(m));
375            }
376
377            assert(o_mappings == r_mappings);
378        }
379    }
380
381    /// Specification for merging two views.
382    ///
383    /// Mappings are unioned, and memory conflicts are resolved by
384    /// [`vstd::map::Map::union_prefer_right`] with `other` taking precedence.
385    pub open spec fn join(self, other: MemView) -> MemView {
386        MemView {
387            mappings: self.mappings.union(other.mappings),
388            memory: self.memory.union_prefer_right(other.memory),
389        }
390    }
391
392    /// Tracked wrapper of [`Self::join`].
393    ///
394    /// # Verified Properties
395    ///
396    /// ## Preconditions
397    /// - `old(self).mappings.disjoint(other.mappings)`.
398    ///
399    /// ## Postconditions
400    /// - `*self == old(self).join(other)`.
401    #[verifier::external_body]
402    pub proof fn tracked_join(tracked &mut self, tracked other: Self)
403        requires
404            old(self).mappings.disjoint(other.mappings),
405        ensures
406            *final(self) == old(self).join(other),
407    {
408        unimplemented!()
409    }
410
411    /// Lemma: splitting and then joining reconstructs the original view.
412    ///
413    /// # Verified Properties
414    ///
415    /// ## Preconditions
416    /// - `this.split(vaddr, len) == (lhs, rhs)`.
417    /// - Every mapping in `this` starts at or after `vaddr`.
418    /// - Every physical frame tracked by `this.memory` is reachable from some
419    ///   virtual address at or after `vaddr`.
420    ///
421    /// ## Postconditions
422    /// - `lhs.join(rhs)` has the same mappings and memory contents as `this`.
423    pub proof fn lemma_split_join_identity(
424        this: MemView,
425        lhs: MemView,
426        rhs: MemView,
427        vaddr: usize,
428        len: usize,
429    )
430        requires
431            this.split(vaddr, len) == (lhs, rhs),
432            forall|m: Mapping| #[trigger]
433                this.mappings.contains(m) ==> vaddr <= m.va_range.start < m.va_range.end,
434            forall|pa: Paddr| #[trigger]
435                this.memory.contains_key(pa) ==> exists|va: usize|
436                    vaddr <= va && #[trigger] this.is_mapped(va, pa),
437        ensures
438            this.mappings == lhs.join(rhs).mappings,
439            this.memory == lhs.join(rhs).memory,
440    {
441    }
442
443    /// Lemma: byte-level reads agree on the right half of a split.
444    ///
445    /// For any `va >= vaddr + len` whose translated frame is present in
446    /// `original.memory`, the stored frame contents at `va` match the
447    /// original view. This is the byte-level analogue of
448    /// [`Self::lemma_split_preserves_transl`] and supports propagating
449    /// the read view across [`VmIoOwner::advance`] calls (used by loops
450    /// over `read_once`).
451    pub proof fn lemma_split_preserves_read(
452        original: MemView,
453        vaddr: usize,
454        len: usize,
455        left: MemView,
456        right: MemView,
457    )
458        requires
459            original.split(vaddr, len) == (left, right),
460        ensures
461            forall|va: usize|
462                #![trigger original.read(va)]
463                va >= vaddr + len && original.addr_transl(va) is Some
464                    && original.memory.contains_key((original.addr_transl(va)->0).0)
465                    ==> original.read(va) == right.read(va),
466    {
467        Self::lemma_split_preserves_transl(original, vaddr, len, left, right);
468        let split_end = vaddr + len;
469        let right_pas = original.memory.dom().filter(
470            |pa: usize| exists|va: usize| va >= split_end && original.is_mapped(va, pa),
471        );
472        assert forall|va: usize|
473            va >= split_end && original.addr_transl(va) is Some && original.memory.contains_key(
474                original.addr_transl(va).unwrap().0,
475            ) implies #[trigger] original.read(va) == right.read(va) by {
476            assert(original.addr_transl(va) == right.addr_transl(va));
477            let pa = original.addr_transl(va).unwrap().0;
478            assert(original.is_mapped(va, pa));
479            assert(right_pas.contains(pa));
480            assert(right.memory[pa] == original.memory[pa]);
481        }
482    }
483
484    /// Lemma: pointwise [`Self::read`] equality implies [`Self::read_bytes`] equality.
485    pub proof fn lemma_read_bytes_eq_pointwise(a: MemView, b: MemView, va: usize, n: usize)
486        requires
487            forall|i: usize| va <= i < va + n ==> a.read(i) == b.read(i),
488            va + n <= usize::MAX,
489        ensures
490            a.read_bytes(va, n) == b.read_bytes(va, n),
491        decreases n,
492    {
493        if n == 0 {
494            return;
495        }
496        Self::lemma_read_bytes_eq_pointwise(a, b, (va + 1) as usize, (n - 1) as usize);
497    }
498}
499
500impl Inv for VirtPtr {
501    open spec fn inv(self) -> bool {
502        &&& self.range@.start <= self.vaddr <= self.range@.end
503        &&& self.range@.end >= self.range@.start
504    }
505}
506
507impl Clone for VirtPtr {
508    fn clone(&self) -> (res: Self)
509        ensures
510            res == self,
511    {
512        Self { vaddr: self.vaddr, range: Ghost(self.range@) }
513    }
514}
515
516impl Copy for VirtPtr {
517
518}
519
520impl VirtPtr {
521    /// Creates a pointer at `vaddr` with logical range `[vaddr, vaddr + len)`.
522    #[vstd::contrib::auto_spec]
523    pub fn new(vaddr: Vaddr, len: usize) -> Self {
524        Self { vaddr, range: Ghost(Range { start: vaddr, end: (vaddr + len) as usize }) }
525    }
526
527    /// Whether the pointer has consistent bounds.
528    pub open spec fn is_defined(self) -> bool {
529        self.range@.start <= self.vaddr <= self.range@.end
530    }
531
532    /// Whether dereferencing the current pointer position is allowed.
533    ///
534    /// This excludes the one-past-end position (`vaddr == range.end`).
535    pub open spec fn is_valid(self) -> bool {
536        &&& self.is_defined()
537        &&& self.vaddr < self.range@.end
538    }
539
540    /// Reads one byte from `self.vaddr` in the provided memory view.
541    ///
542    /// # Verified Properties
543    ///
544    /// ## Preconditions
545    /// - `mem.addr_transl(self.vaddr) is Some`.
546    /// - Target byte is initialized:
547    ///   `mem.memory[mem.addr_transl(self.vaddr).unwrap().0].contents[mem.addr_transl(self.vaddr).unwrap().1 as int] is Init`.
548    /// - `self.is_valid()`.
549    ///
550    /// ## Postconditions
551    /// - Returns `mem.read(self.vaddr).value()`.
552    #[verifier::external_body]
553    pub fn read(self, Tracked(mem): Tracked<&MemView>) -> u8
554        requires
555            mem.addr_transl(self.vaddr) is Some,
556            mem.memory[(mem.addr_transl(self.vaddr)->0).0].contents[(mem.addr_transl(
557                self.vaddr,
558            )->0).1 as int] is Init,
559            self.is_valid(),
560        returns
561            mem.read(self.vaddr).value(),
562    {
563        unimplemented!()
564    }
565
566    /// Writes one byte to `self.vaddr` in the provided memory view.
567    ///
568    /// # Verified Properties
569    ///
570    /// ## Preconditions
571    /// - `old(mem).addr_transl(self.vaddr) is Some`.
572    /// - `self.is_valid()`.
573    ///
574    /// ## Postconditions
575    /// - `*mem == old(mem).write(self.vaddr, x)`.
576    #[verifier::external_body]
577    pub fn write(self, Tracked(mem): Tracked<&mut MemView>, x: u8)
578        requires
579            old(mem).addr_transl(self.vaddr) is Some,
580            self.is_valid(),
581        ensures
582            *final(mem) == old(mem).write(self.vaddr, x),
583    {
584        unimplemented!()
585    }
586
587    /// Reads a [`PodOnce`] value using one volatile memory load from a verified memory view.
588    ///
589    /// # Verified Properties
590    ///
591    /// ## Preconditions
592    /// - `self.inv()`.
593    /// - The readable range `[self.vaddr, self.vaddr + size_of::<T>())` must fit in `self.range@`.
594    /// - `self.vaddr` must satisfy the alignment requirement of `T`.
595    /// - Every byte in that range must translate in `mem` and be initialized.
596    ///
597    /// ## Safety
598    /// - This function is trusted because the underlying volatile typed read relies on raw-pointer
599    ///   operations that Verus does not yet model directly.
600    #[verifier::external_body]
601    pub fn read_volatile<T: PodOnce>(self, Tracked(mem): Tracked<&MemView>) -> (val: T)
602        requires
603            self.inv(),
604            core::mem::size_of::<T>() <= self.range@.end - self.vaddr,
605            self.vaddr % core::mem::align_of::<T>() == 0,
606            forall|i: usize|
607                #![trigger mem.addr_transl(i)]
608                self.vaddr <= i < self.vaddr + core::mem::size_of::<T>() ==> {
609                    &&& mem.addr_transl(i) is Some
610                    &&& mem.memory.contains_key((mem.addr_transl(i)->0).0)
611                    &&& mem.memory[(mem.addr_transl(i)->0).0].contents[(mem.addr_transl(
612                        i,
613                    )->0).1 as int] is Init
614                },
615        ensures
616            pod_bytes(val) == mem.read_bytes(self.vaddr, core::mem::size_of::<T>()),
617    {
618        let pnt = self.vaddr as *const T;
619        unsafe { pnt.read_volatile() }
620    }
621
622    /// Writes a [`Pod`] value of size `size_of::<T>()` bytes using a volatile memory store
623    /// to a verified memory view.
624    ///
625    /// For `T: PodOnce` the underlying store is non-tearing (single instruction). For
626    /// arbitrary `T: Pod` the store may tear across multiple instructions but completes
627    /// before this function returns.
628    ///
629    /// # Verified Properties
630    ///
631    /// ## Preconditions
632    /// - `self.inv()`.
633    /// - The writable range `[self.vaddr, self.vaddr + size_of::<T>())` must fit in `self.range@`.
634    /// - `self.vaddr` must satisfy the alignment requirement of `T`.
635    /// - Every byte in that range must translate in `mem`.
636    ///
637    /// ## Postconditions
638    /// - The final memory equals `old(mem).write_bytes(self.vaddr, pod_bytes(val))`.
639    /// - `mem.mappings` is unchanged (derivable from `write_bytes`, but stated
640    ///   directly for caller convenience).
641    /// - `mem.memory.dom()` can only grow (same).
642    /// - `addr_transl` is preserved for every virtual address (same).
643    ///
644    /// ## Safety
645    /// - This function is trusted because the underlying volatile typed write relies on raw-pointer
646    ///   operations that Verus does not yet model directly.
647    #[verifier::external_body]
648    pub fn write_volatile<T: Pod>(self, Tracked(mem): Tracked<&mut MemView>, val: T)
649        requires
650            self.inv(),
651            core::mem::size_of::<T>() <= self.range@.end - self.vaddr,
652            self.vaddr % core::mem::align_of::<T>() == 0,
653            forall|i: usize|
654                #![trigger old(mem).addr_transl(i)]
655                self.vaddr <= i < self.vaddr + core::mem::size_of::<T>() ==> {
656                    &&& old(mem).addr_transl(i) is Some
657                },
658        ensures
659            *final(mem) == old(mem).write_bytes(self.vaddr, pod_bytes(val)),
660            final(mem).mappings == old(mem).mappings,
661            old(mem).memory.dom().subset_of(final(mem).memory.dom()),
662            forall|va: usize|
663                #![trigger final(mem).addr_transl(va)]
664                old(mem).addr_transl(va) == final(mem).addr_transl(va),
665    {
666        let pnt = self.vaddr as *mut T;
667        unsafe { pnt.write_volatile(val) }
668    }
669
670    pub open spec fn add_spec(self, n: usize) -> Self {
671        VirtPtr { vaddr: (self.vaddr + n) as usize, range: self.range }
672    }
673
674    /// Advances the pointer by `n` bytes.
675    ///
676    /// This operation only updates the cursor; it does not perform memory access.
677    ///
678    /// # Verified Properties
679    ///
680    /// ## Preconditions
681    /// - `0 <= old(self).vaddr + n < usize::MAX`.
682    ///
683    /// ## Postconditions
684    /// - `*self == old(self).add_spec(n)`.
685    pub fn add(&mut self, n: usize)
686        requires
687    // Option 1: strict C standard compliance
688    // old(self).range@.start <= self.vaddr + n <= old(self).range@.end,
689    // Option 2: just make sure it doesn't overflow
690
691            0 <= old(self).vaddr + n < usize::MAX,
692        ensures
693            *final(self) == old(self).add_spec(
694                n,
695            ),
696    // If we take option 1, we can also ensure:
697    // self.is_defined()
698
699    {
700        self.vaddr = self.vaddr + n
701    }
702
703    pub open spec fn sub_spec(self, n: usize) -> Self {
704        VirtPtr { vaddr: (self.vaddr - n) as usize, range: self.range }
705    }
706
707    /// Returns a new pointer whose address is `self.vaddr` rewound by `n` bytes.
708    ///
709    /// Mirrors the shape of [`<*const T>::sub`] / [`<*mut T>::sub`] in the raw-pointer
710    /// API: takes `self` by value and returns the new pointer.
711    ///
712    /// # Verified Properties
713    ///
714    /// ## Preconditions
715    /// - `n <= self.vaddr` (no underflow).
716    ///
717    /// ## Postconditions
718    /// - `res == self.sub_spec(n)`.
719    pub fn sub(self, n: usize) -> (res: Self)
720        requires
721            n <= self.vaddr,
722        returns
723            self.sub_spec(n),
724    {
725        VirtPtr { vaddr: self.vaddr - n, range: self.range }
726    }
727
728    pub open spec fn wrapping_add_spec(self, n: usize) -> Self {
729        VirtPtr { vaddr: self.vaddr.wrapping_add(n), range: self.range }
730    }
731
732    /// Returns a new pointer whose address is `self.vaddr` advanced by `n`
733    /// bytes using wrapping arithmetic.
734    pub fn wrapping_add(self, n: usize) -> (res: Self)
735        returns
736            self.wrapping_add_spec(n),
737    {
738        VirtPtr { vaddr: self.vaddr.wrapping_add(n), range: self.range }
739    }
740
741    /// Reinterprets `self.vaddr` as a typed raw pointer to `T`.
742    ///
743    /// This is purely a type cast; no memory access is performed and no
744    /// alignment is checked. The returned pointer carries no provenance — it
745    /// is suitable for runtime checks (e.g., `is_aligned`) and for unsafe
746    /// volatile reads/writes whose preconditions are independently verified.
747    #[verifier::external_body]
748    pub fn cast<T>(self) -> (res: *mut T)
749        ensures
750            res as usize == self.vaddr,
751    {
752        self.vaddr as *mut T
753    }
754
755    pub open spec fn read_offset_spec(self, mem: MemView, n: usize) -> u8 {
756        mem.read((self.vaddr + n) as usize).value()
757    }
758
759    /// Reads from `self.vaddr + n` without mutating `self`.
760    ///
761    /// Implemented by creating a temporary pointer and calling [`Self::read`].
762    /// When `self.vaddr == self.range.start`, this behaves like array indexing.
763    ///
764    /// # Verified Properties
765    ///
766    /// ## Preconditions
767    /// - `self.vaddr + n < usize::MAX`.
768    /// - `self.range@.start <= self.vaddr + n < self.range@.end`.
769    /// - `mem.addr_transl((self.vaddr + n) as usize) is Some`.
770    /// - Target byte at offset is initialized.
771    ///
772    /// ## Postconditions
773    /// - Returns `self.read_offset_spec(*mem, n)`.
774    pub fn read_offset(&self, Tracked(mem): Tracked<&MemView>, n: usize) -> u8
775        requires
776            self.vaddr + n < usize::MAX,
777            self.range@.start <= self.vaddr + n < self.range@.end,
778            mem.addr_transl((self.vaddr + n) as usize) is Some,
779            mem.memory[(mem.addr_transl((self.vaddr + n) as usize)->0).0].contents[(mem.addr_transl(
780                (self.vaddr + n) as usize,
781            )->0).1 as int] is Init,
782        returns
783            self.read_offset_spec(*mem, n),
784    {
785        let mut tmp = self.clone();
786        tmp.add(n);
787        tmp.read(Tracked(mem))
788    }
789
790    pub open spec fn write_offset_spec(self, mem: MemView, n: usize, x: u8) -> MemView {
791        mem.write((self.vaddr + n) as usize, x)
792    }
793
794    /// Writes to `self.vaddr + n` without mutating `self`.
795    ///
796    /// # Verified Properties
797    ///
798    /// ## Preconditions
799    /// - `self.inv()`.
800    /// - `self.range@.start <= self.vaddr + n < self.range@.end`.
801    /// - `old(mem).addr_transl((self.vaddr + n) as usize) is Some`.
802    pub fn write_offset(&self, Tracked(mem): Tracked<&mut MemView>, n: usize, x: u8)
803        requires
804            self.inv(),
805            self.vaddr + n < usize::MAX,
806            self.range@.start <= self.vaddr + n < self.range@.end,
807            old(mem).addr_transl((self.vaddr + n) as usize) is Some,
808        ensures
809            *final(mem) == self.write_offset_spec(*old(mem), n, x),
810    {
811        let mut tmp = self.clone();
812        tmp.add(n);
813        tmp.write(Tracked(mem), x)
814    }
815
816    pub open spec fn copy_offset_spec(
817        src: Self,
818        dst: Self,
819        mem_src: MemView,
820        mem_dst: MemView,
821        n: usize,
822    ) -> MemView {
823        let x = src.read_offset_spec(mem_src, n);
824        dst.write_offset_spec(mem_dst, n, x)
825    }
826
827    /// Copies one byte from `src + n` to `dst + n`.
828    ///
829    /// Source and destination are reasoned about via distinct memory views.
830    ///
831    /// # Verified Properties
832    ///
833    /// ## Preconditions
834    /// - `src.inv()` and `dst.inv()`.
835    /// - Source offset and destination offset are both in-range.
836    /// - Source offset is mapped and initialized in `mem_src`.
837    /// - Destination offset is mapped in `old(mem_dst)`.
838    ///
839    /// ## Postconditions
840    /// - `*mem_dst == Self::copy_offset_spec(*src, *dst, *mem_src, *old(mem_dst), n)`.
841    /// - `mem_dst.mappings == old(mem_dst).mappings`.
842    /// - `mem_dst.memory.dom() == old(mem_dst).memory.dom()`.
843    pub fn copy_offset(
844        src: &Self,
845        dst: &Self,
846        Tracked(mem_src): Tracked<&MemView>,
847        Tracked(mem_dst): Tracked<&mut MemView>,
848        n: usize,
849    )
850        requires
851            src.inv(),
852            dst.inv(),
853            src.vaddr + n < usize::MAX,
854            dst.vaddr + n < usize::MAX,
855            src.range@.start <= src.vaddr + n < src.range@.end,
856            mem_src.addr_transl((src.vaddr + n) as usize) is Some,
857            mem_src.memory.contains_key((mem_src.addr_transl((src.vaddr + n) as usize)->0).0),
858            mem_src.memory[(mem_src.addr_transl((src.vaddr + n) as usize)->0).0].contents[(
859            mem_src.addr_transl((src.vaddr + n) as usize)->0).1 as int] is Init,
860            dst.range@.start <= dst.vaddr + n < dst.range@.end,
861            old(mem_dst).addr_transl((dst.vaddr + n) as usize) is Some,
862        ensures
863            *final(mem_dst) == Self::copy_offset_spec(*src, *dst, *mem_src, *old(mem_dst), n),
864            final(mem_dst).mappings == old(mem_dst).mappings,
865            old(mem_dst).memory.dom().subset_of(final(mem_dst).memory.dom()),
866    {
867        let x = src.read_offset(Tracked(mem_src), n);
868        dst.write_offset(Tracked(mem_dst), n, x)
869    }
870
871    pub open spec fn memcpy_spec(
872        src: Self,
873        dst: Self,
874        mem_src: MemView,
875        mem_dst: MemView,
876        n: usize,
877    ) -> MemView
878        decreases n,
879    {
880        if n == 0 {
881            mem_dst
882        } else {
883            let mem_dst_1 = Self::copy_offset_spec(src, dst, mem_src, mem_dst, (n - 1) as usize);
884
885            Self::memcpy_spec(src, dst, mem_src, mem_dst_1, (n - 1) as usize)
886        }
887    }
888
889    /// Copies `n` bytes from `src` to `dst` in the given memory view.
890    ///
891    /// `mem_src` and `mem_dst` are independent tracked memory views — the verifier
892    /// enforces this by virtue of the `&MemView` / `&mut MemView` ownership pattern,
893    /// so no vaddr-level non-overlap precondition is needed. (`mem_src` is never
894    /// mutated during the copy; each byte is read from its original state.)
895    ///
896    /// # Verified Properties
897    ///
898    /// ## Preconditions
899    /// - `src.inv()` and `dst.inv()`.
900    /// - Source slice `[src.vaddr, src.vaddr + n)` is in-range and initialized in `mem_src`.
901    /// - Destination slice `[dst.vaddr, dst.vaddr + n)` is in-range and mapped in `old(mem_dst)`.
902    ///
903    /// ## Postconditions
904    /// - `*mem_dst == Self::memcpy_spec(*src, *dst, *mem_src, *old(mem_dst), n)`.
905    /// - `mem_dst.mappings == old(mem_dst).mappings`.
906    /// - `mem_dst.memory.dom() == old(mem_dst).memory.dom()`.
907    /// - Destination slice remains mapped in `mem_dst`.
908    pub fn copy_nonoverlapping(
909        src: &Self,
910        dst: &Self,
911        Tracked(mem_src): Tracked<&MemView>,
912        Tracked(mem_dst): Tracked<&mut MemView>,
913        n: usize,
914    )
915        requires
916            src.inv(),
917            dst.inv(),
918            src.range@.start <= src.vaddr,
919            src.vaddr + n <= src.range@.end,
920            forall|i: usize|
921                #![trigger mem_src.addr_transl(i)]
922                src.vaddr <= i < src.vaddr + n ==> {
923                    &&& mem_src.addr_transl(i) is Some
924                    &&& mem_src.memory.contains_key((mem_src.addr_transl(i)->0).0)
925                    &&& mem_src.memory[(mem_src.addr_transl(i)->0).0].contents[(mem_src.addr_transl(
926                        i,
927                    )->0).1 as int] is Init
928                },
929            dst.range@.start <= dst.vaddr,
930            dst.vaddr + n <= dst.range@.end,
931            forall|i: usize|
932                dst.vaddr <= i < dst.vaddr + n ==> {
933                    &&& old(mem_dst).addr_transl(i) is Some
934                },
935        ensures
936            *final(mem_dst) == Self::memcpy_spec(*src, *dst, *mem_src, *old(mem_dst), n),
937            final(mem_dst).mappings == old(mem_dst).mappings,
938            // dom() can only grow (writes may insert new pa keys).
939            old(mem_dst).memory.dom().subset_of(final(mem_dst).memory.dom()),
940            forall|i: usize|
941                #![trigger final(mem_dst).addr_transl(i)]
942                dst.vaddr <= i < dst.vaddr + n ==> {
943                    &&& final(mem_dst).addr_transl(i) is Some
944                },
945        decreases n,
946    {
947        if n == 0 {
948            return;
949        } else {
950            let ghost mem0 = *mem_dst;
951
952            Self::copy_offset(src, dst, Tracked(mem_src), Tracked(mem_dst), n - 1);
953
954            proof {
955                assert(forall|i: usize|
956                    dst.vaddr <= i < dst.vaddr + n - 1 ==> mem_dst.addr_transl(i)
957                        == #[trigger] mem0.addr_transl(i));
958                assert forall|i: usize|
959                    dst.vaddr <= i < dst.vaddr + n - 1 implies mem_dst.addr_transl(i) is Some by {
960                    assert(mem_dst.addr_transl(i) == mem0.addr_transl(i));
961                }
962            }
963
964            Self::copy_nonoverlapping(src, dst, Tracked(mem_src), Tracked(mem_dst), n - 1);
965        }
966    }
967
968    /// Builds a valid [`VirtPtr`] from a concrete virtual-address range.
969    ///
970    /// # Verified Properties
971    ///
972    /// ## Preconditions
973    /// - `len <= usize::MAX - vaddr`.
974    ///
975    /// ## Postconditions
976    /// - `r.range@.start == vaddr`.
977    /// - `r.range@.end == (vaddr + len) as usize`.
978    /// - When `len > 0`, the result is valid for dereferencing.
979    pub fn from_vaddr(vaddr: usize, len: usize) -> (r: Self)
980        requires
981            len <= usize::MAX - vaddr,
982        ensures
983            r.inv(),
984            r.vaddr == vaddr,
985            r.range@.start == vaddr,
986            r.range@.end == (vaddr + len) as usize,
987            len > 0 ==> r.is_valid(),
988    {
989        Self { vaddr, range: Ghost(Range { start: vaddr, end: (vaddr + len) as usize }) }
990    }
991
992    /// Executable helper to split the [`VirtPtr`] into two contiguous ranges.
993    ///
994    /// This updates ghost ranges to match a split operation over the same region.
995    ///
996    /// # Verified Properties
997    ///
998    /// ## Preconditions
999    /// - `self.is_valid()`.
1000    /// - `0 <= n <= self.range@.end - self.range@.start`.
1001    /// - `self.vaddr == self.range@.start`.
1002    ///
1003    /// ## Postconditions
1004    /// - Left pointer covers `[self.range@.start, self.range@.start + n)`.
1005    /// - Right pointer covers `[self.range@.start + n, self.range@.end)`.
1006    #[verus_spec(r =>
1007        requires
1008            self.is_valid(),
1009            0 <= n <= self.range@.end - self.range@.start,
1010            self.vaddr == self.range@.start,
1011        ensures
1012            r.0.range@.start == self.range@.start,
1013            r.0.range@.end == self.range@.start + n,
1014            r.0.vaddr == self.range@.start,
1015            r.1.range@.start == self.range@.start + n,
1016            r.1.range@.end == self.range@.end,
1017            r.1.vaddr == self.range@.start + n,
1018    )]
1019    pub fn split(self, n: usize) -> (Self, Self) {
1020        let left = VirtPtr {
1021            vaddr: self.vaddr,
1022            range: Ghost(Range { start: self.vaddr, end: (self.vaddr + n) as usize }),
1023        };
1024
1025        let right = VirtPtr {
1026            vaddr: self.vaddr + n,
1027            range: Ghost(Range { start: (self.vaddr + n) as usize, end: self.range@.end }),
1028        };
1029
1030        (left, right)
1031    }
1032
1033    pub fn addr(&self) -> Vaddr
1034        returns
1035            self.vaddr,
1036    {
1037        self.vaddr
1038    }
1039}
1040
1041/// A [`GlobalMemView`] is a more abstract view of memory that elides most of the details. The API specifications
1042/// of higher-level objects like [`VmSpaceOwner`](crate::specs::mm::vm_space::VmSpaceOwner) use this view.
1043pub tracked struct GlobalMemView {
1044    pub pt_mappings: Set<Mapping>,
1045    pub tlb_mappings: Set<Mapping>,
1046    pub unmapped_pas: Set<Paddr>,
1047    pub memory: Map<Paddr, FrameContents>,
1048}
1049
1050impl Inv for GlobalMemView {
1051    open spec fn inv(self) -> bool {
1052        &&& forall|m: Mapping|
1053            #![auto]
1054            self.tlb_mappings.contains(m) ==> {
1055                &&& m.inv()
1056                &&& forall|pa: Paddr|
1057                    m.pa_range.start <= pa < m.pa_range.end ==> {
1058                        &&& self.memory.dom().contains(pa)
1059                    }
1060                &&& self.memory.contains_key(m.pa_range.start)
1061                &&& self.memory[m.pa_range.start].size == m.page_size
1062                &&& self.memory[m.pa_range.start].inv()
1063            }
1064        &&& forall|m: Mapping|
1065            forall|n: Mapping|
1066                #![auto]
1067                self.tlb_mappings.contains(m) ==> self.tlb_mappings.contains(n) ==> m != n
1068                    ==> #[trigger] m.va_range.end <= n.va_range.start || n.va_range.end
1069                    <= m.va_range.start
1070        &&& self.all_pas_accounted_for()
1071        &&& self.pas_uniquely_mapped()
1072        &&& self.unmapped_correct()
1073    }
1074}
1075
1076impl GlobalMemView {
1077    pub open spec fn addr_transl(self, va: usize) -> Option<(usize, usize)> {
1078        let mappings = self.tlb_mappings.filter(
1079            |m: Mapping| m.va_range.start <= va < m.va_range.end,
1080        );
1081        if 0 < mappings.len() {
1082            let m = mappings.choose();  // In a well-formed TLB there will only be one, but if malformed this is non-deterministic!
1083            let off = va - m.va_range.start;
1084            Some((m.pa_range.start, off as usize))
1085        } else {
1086            None
1087        }
1088    }
1089
1090    pub open spec fn is_mapped(self, pa: usize) -> bool {
1091        exists|m: Mapping| self.tlb_mappings.contains(m) && m.pa_range.start <= pa < m.pa_range.end
1092    }
1093
1094    pub open spec fn all_pas_accounted_for(self) -> bool {
1095        forall|pa: Paddr|
1096            0 <= pa < MAX_PADDR ==> #[trigger] self.is_mapped(pa)
1097                || #[trigger] self.unmapped_pas.contains(pa)
1098    }
1099
1100    pub open spec fn pas_uniquely_mapped(self) -> bool {
1101        forall|m1: Mapping, m2: Mapping|
1102            #![trigger self.tlb_mappings.contains(m1), self.tlb_mappings.contains(m2)]
1103            self.tlb_mappings.contains(m1) && self.tlb_mappings.contains(m2) && m1 != m2
1104                ==> m1.pa_range.end <= m2.pa_range.start || m2.pa_range.end <= m1.pa_range.start
1105    }
1106
1107    pub open spec fn unmapped_correct(self) -> bool {
1108        forall|pa: Paddr| #![auto] self.is_mapped(pa) <==> !self.unmapped_pas.contains(pa)
1109    }
1110
1111    pub open spec fn take_view(self, vaddr: usize, len: usize) -> (Self, MemView) {
1112        let range_end = vaddr + len;
1113
1114        let leave_mappings: Set<Mapping> = self.tlb_mappings.filter(
1115            |m: Mapping| m.va_range.end <= vaddr || m.va_range.start > range_end,
1116        );
1117
1118        let take_mappings = self.tlb_mappings.filter(
1119            |m: Mapping| m.va_range.start < range_end && m.va_range.end > vaddr,
1120        );
1121
1122        let leave_pas = self.memory.dom().filter(
1123            |pa: usize|
1124                exists|m: Mapping|
1125                    leave_mappings.contains(m) && m.pa_range.start <= pa < m.pa_range.end,
1126        );
1127        let take_pas = self.memory.dom().filter(
1128            |pa: usize|
1129                exists|m: Mapping|
1130                    take_mappings.contains(m) && m.pa_range.start <= pa < m.pa_range.end,
1131        );
1132
1133        (
1134            GlobalMemView {
1135                tlb_mappings: leave_mappings,
1136                memory: self.memory.restrict(leave_pas),
1137                ..self
1138            },
1139            MemView { mappings: take_mappings, memory: self.memory.restrict(take_pas) },
1140        )
1141    }
1142
1143    pub axiom fn tracked_take_view(tracked &mut self, vaddr: usize, len: usize) -> (tracked view:
1144        MemView)
1145        ensures
1146            *final(self) == old(self).take_view(vaddr, len).0,
1147            view == old(self).take_view(vaddr, len).1,
1148    ;
1149
1150    pub open spec fn return_view(self, view: MemView) -> Self {
1151        GlobalMemView {
1152            tlb_mappings: self.tlb_mappings.union(view.mappings),
1153            memory: self.memory.union_prefer_right(view.memory),
1154            ..self
1155        }
1156    }
1157
1158    pub axiom fn tracked_return_view(&mut self, view: MemView)
1159        ensures
1160            *final(self) == old(self).return_view(view),
1161    ;
1162
1163    pub open spec fn tlb_flush_vaddr(self, vaddr: Vaddr) -> Self {
1164        let tlb_mappings = self.tlb_mappings.filter(
1165            |m: Mapping| m.va_range.end <= vaddr || vaddr < m.va_range.start,
1166        );
1167        GlobalMemView { tlb_mappings, ..self }
1168    }
1169
1170    pub axiom fn tracked_tlb_flush_vaddr(&mut self, vaddr: Vaddr)
1171        requires
1172            old(self).inv(),
1173        ensures
1174            *final(self) == old(self).tlb_flush_vaddr(vaddr),
1175            final(self).inv(),
1176    ;
1177
1178    pub open spec fn tlb_soft_fault(self, vaddr: Vaddr) -> Self {
1179        let mapping = self.pt_mappings.filter(
1180            |m: Mapping| m.va_range.start <= vaddr < m.va_range.end,
1181        ).choose();
1182        GlobalMemView { tlb_mappings: self.tlb_mappings.insert(mapping), ..self }
1183    }
1184
1185    pub axiom fn tracked_tlb_soft_fault(tracked &mut self, vaddr: Vaddr)
1186        requires
1187            old(self).inv(),
1188            old(self).addr_transl(vaddr) is None,
1189        ensures
1190            *final(self) == old(self).tlb_soft_fault(vaddr),
1191            final(self).inv(),
1192    ;
1193
1194    pub open spec fn pt_map(self, m: Mapping) -> Self {
1195        let pt_mappings = self.pt_mappings.insert(m);
1196        let unmapped_pas = self.unmapped_pas.difference(
1197            Set::<usize>::range(m.pa_range.start, m.pa_range.end),
1198        );
1199        GlobalMemView { pt_mappings, unmapped_pas, ..self }
1200    }
1201
1202    pub axiom fn tracked_pt_map(&mut self, m: Mapping)
1203        requires
1204            forall|pa: Paddr|
1205                m.pa_range.start <= pa < m.pa_range.end ==> old(self).unmapped_pas.contains(pa),
1206            old(self).inv(),
1207        ensures
1208            *final(self) == old(self).pt_map(m),
1209    ;
1210
1211    pub open spec fn pt_unmap(self, m: Mapping) -> Self {
1212        let pt_mappings = self.pt_mappings.remove(m);
1213        let unmapped_pas = self.unmapped_pas.union(
1214            Set::<usize>::range(m.pa_range.start, m.pa_range.end),
1215        );
1216        GlobalMemView { pt_mappings, unmapped_pas, ..self }
1217    }
1218
1219    pub axiom fn tracked_pt_unmap(&mut self, m: Mapping)
1220        requires
1221            old(self).pt_mappings.contains(m),
1222            old(self).inv(),
1223        ensures
1224            *final(self) == old(self).pt_unmap(m),
1225            final(self).inv(),
1226    ;
1227
1228    pub proof fn lemma_va_mapping_unique(self, va: usize)
1229        requires
1230            self.inv(),
1231            // Some mapping must cover `va` for the filter to be non-empty.
1232            self.addr_transl(va) is Some,
1233        ensures
1234            self.tlb_mappings.filter(
1235                |m: Mapping| m.va_range.start <= va < m.va_range.end,
1236            ).is_singleton(),
1237    {
1238        let f = self.tlb_mappings.filter(|m: Mapping| m.va_range.start <= va < m.va_range.end);
1239        // addr_transl is Some iff the filter is non-empty.
1240        assert(f.len() > 0);
1241        // Pairwise disjointness on tlb_mappings (from inv) plus both
1242        // mappings covering va forces equality.
1243        assert forall|m: Mapping, n: Mapping| f.contains(m) && f.contains(n) implies m == n by {
1244            assert(self.tlb_mappings.contains(m));
1245            assert(self.tlb_mappings.contains(n));
1246            assert(m.va_range.start <= va < m.va_range.end);
1247            assert(n.va_range.start <= va < n.va_range.end);
1248            // If m != n, pairwise disjointness gives va-disjoint, contradicting va ∈ both.
1249        };
1250    }
1251}
1252
1253} // verus!