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