Skip to main content

aligned_vmem/
lazy_reservation.rs

1use crate::error::VmemError;
2use crate::page_size::page_size;
3use crate::Reservation;
4
5use super::lazy_commit_is_honored::lazy_commit_is_honored;
6
7/// A [`Reservation`] that also tracks how much of itself is committed.
8///
9/// The tracked counterpart to the raw commit primitives. A plain `Reservation`
10/// describes GEOMETRY — where the span is, how long, how aligned — but virtual
11/// memory also has STATE per page (reserved / committed), and a bare
12/// `Reservation` holds none of it. Every caller of the lazy path therefore had
13/// to invent the same bookkeeping. This type holds it instead.
14///
15/// # What is tracked, and what deliberately is not
16///
17/// Exactly one number: a **watermark**. `[0, committed_len())` is committed;
18/// `[committed_len(), len())` is not. Arbitrary committed/uncommitted HOLES are
19/// not representable, and that is a drawn boundary rather than an oversight —
20/// the dominant lazy-reservation shape is grow-only, a watermark covers it in
21/// one `usize`, and anything more general is a per-page bitmap, which is an
22/// allocator's job and not this crate's. A caller that genuinely needs holes
23/// already has its own metadata plane and should take
24/// [`into_reservation`](Self::into_reservation) and drive the raw primitives.
25///
26/// # Why the mutating methods take `&mut self`
27///
28/// Not bookkeeping hygiene — this is the crate finally stating a requirement it
29/// always had. A watermark is inherently racy: two threads committing
30/// concurrently must serialise, and under the raw primitives nothing ever said
31/// so. `&mut self` makes the compiler ask for the synchronisation that was
32/// always necessary, instead of leaving it to be discovered in production.
33/// (After task #1113, the raw `Reservation` also follows this rule.)
34///
35/// # The watermark is a guarantee, not a prohibition
36///
37/// `committed_len()` is what this crate GUARANTEES writable. Where decommit is
38/// advisory rather than reclaiming (see
39/// [`Reservation::decommit_reclaims_and_zeroes`]) memory past the watermark may
40/// still be resident and writable after
41/// [`shrink_committed`](Self::shrink_committed). Relying on that is exactly the
42/// non-portable assumption this type exists to prevent — treat the watermark as
43/// the contract.
44///
45/// # It can still be bypassed, and that is honest
46///
47/// [`as_ptr`](Self::as_ptr) hands out the raw pointer; it must, or you could not
48/// write to the memory. Passing that pointer to the raw commit primitives
49/// changes OS state behind the watermark's back and the watermark goes stale.
50/// No API over raw memory can prevent that. What changes is the DEFAULT: the
51/// tracked path is what you get without asking, and the bypass now requires
52/// deliberately reaching for a differently-named function.
53///
54/// After task #1104 there are exactly two doors out, and both are
55/// deliberate: the raw pointer above — every USE of which is already
56/// `unsafe` — and [`into_reservation`](Self::into_reservation), which
57/// consumes this handle, so the watermark cannot outlive its own
58/// tracking. A borrowed `&Reservation` was removed at task #1104; after
59/// task #1113 the OS-state mutators on `Reservation` take `&mut self`,
60/// so even a leaked `&Reservation` can no longer mutate OS state — the
61/// seal is structural. The read-only queries callers actually need —
62/// [`len`](Self::len), [`as_ptr`](Self::as_ptr), [`align`](Self::align) —
63/// are proxied directly on this type; anything else lives behind
64/// [`into_reservation`](Self::into_reservation) on purpose.
65///
66/// A `LazyReservation` is never huge-page backed — the lazy constructors always
67/// request ordinary pages — so there is no `is_huge()` here. It would be a
68/// constant `false` dressed up as a question.
69#[cfg(feature = "lazy-commit")]
70#[cfg_attr(docsrs, doc(cfg(feature = "lazy-commit")))]
71pub struct LazyReservation {
72    inner: Reservation,
73    /// Invariant: `committed <= inner.len()`, and `committed` is always a
74    /// multiple of the runtime `page_size()`. Both mutators round to page
75    /// granularity, and `inner.len()` is itself a page multiple because
76    /// `validate_initial_commit` rejects anything else.
77    committed: usize,
78}
79
80/// Hand-written for the same reason [`Reservation`'s](Reservation) is: the
81/// type's whole point is one piece of diagnostically decisive state — the
82/// watermark — and it must be visible in a panic message. Field selection
83/// follows the same principle as `Reservation`'s impl: print only what is
84/// already publicly observable (`committed_len()`, and the inner reservation,
85/// whose own `Debug` prints exactly its public observables). The inner
86/// reservation is rendered, not borrowed out — formatting goes through
87/// `Debug`, so no `&Reservation` escapes and the H1 sealing (task #1104)
88/// is unaffected.
89#[cfg(feature = "lazy-commit")]
90impl core::fmt::Debug for LazyReservation {
91    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
92        f.debug_struct("LazyReservation")
93            .field("committed_len", &self.committed_len())
94            .field("inner", &self.inner)
95            .finish()
96    }
97}
98
99#[cfg(feature = "lazy-commit")]
100impl LazyReservation {
101    /// Wrap a freshly-created lazy reservation, recording what the backend
102    /// ACTUALLY committed rather than what was requested.
103    ///
104    /// The two differ on every platform except real Windows — see
105    /// [`lazy_commit_is_honored`]. Deriving the watermark from that one query,
106    /// rather than from the caller's `initial_commit`, is what makes the
107    /// watermark and the query incapable of disagreeing.
108    pub(crate) fn new(inner: Reservation, initial_commit: usize) -> Self {
109        let committed = if lazy_commit_is_honored() {
110            initial_commit
111        } else {
112            inner.len()
113        };
114        Self { inner, committed }
115    }
116
117    /// Bytes from the base that are committed and writable.
118    ///
119    /// Where [`lazy_commit_is_honored`] is `false` this equals
120    /// [`len`](Self::len) from the moment of creation.
121    #[must_use]
122    pub const fn committed_len(&self) -> usize {
123        self.committed
124    }
125
126    /// Ensure at least `len` bytes from the base are committed.
127    ///
128    /// **Idempotent and monotone** — the point of the whole type. Call it before
129    /// every write without remembering what you already committed: a call
130    /// asking for no more than the current watermark issues no syscall and
131    /// returns `Ok(())`.
132    ///
133    /// `len` need NOT be page-aligned; it is rounded UP to the runtime page size
134    /// internally, so asking for one byte past the watermark commits the page
135    /// containing it.
136    ///
137    /// # Errors
138    ///
139    /// [`VmemError::invalid_argument`] if `len > len()`; otherwise the OS cause
140    /// when the commit genuinely fails (commit-charge exhaustion / OOM).
141    ///
142    /// **On failure the watermark is left unchanged**, so the handle still
143    /// describes exactly what is committed and a retry is safe.
144    pub fn ensure_committed(&mut self, len: usize) -> Result<(), VmemError> {
145        if len <= self.committed {
146            return Ok(());
147        }
148        if len > self.inner.len() {
149            return Err(VmemError::invalid_argument());
150        }
151        // `len <= inner.len()` and `inner.len()` is a page multiple, so rounding
152        // up cannot escape the span.
153        let new_end = len.next_multiple_of(page_size());
154        debug_assert!(new_end <= self.inner.len());
155        self.inner.try_commit_range(self.committed, new_end)?;
156        self.committed = new_end;
157        Ok(())
158    }
159
160    /// Lower the watermark to `len`, asking the OS to release
161    /// `[new watermark, old watermark)`.
162    ///
163    /// `len` is rounded UP to the runtime page size, so a page containing bytes
164    /// you asked to KEEP is never released. Asking for at least the current
165    /// watermark is a no-op.
166    ///
167    /// What the OS does with the released range is platform-dependent — see
168    /// [`Reservation::decommit`]'s platform matrix. The watermark drops
169    /// regardless, because it is this crate's guarantee and not a claim about
170    /// residency.
171    pub fn shrink_committed(&mut self, len: usize) {
172        if len >= self.committed {
173            return;
174        }
175        let new_end = len.next_multiple_of(page_size());
176        if new_end >= self.committed {
177            return;
178        }
179        self.inner.decommit(new_end, self.committed);
180        self.committed = new_end;
181    }
182
183    /// Give up tracking and take the plain [`Reservation`].
184    ///
185    /// The explicit door out, for a caller keeping its own commit state. The
186    /// motivating case is an allocator whose watermark must live in its own
187    /// metadata, reachable from a bare pointer on a hot path where no handle is
188    /// in scope. After this call the crate tracks nothing and the raw
189    /// primitives are yours to drive.
190    #[must_use]
191    pub fn into_reservation(self) -> Reservation {
192        self.inner
193    }
194
195    /// Base pointer of the usable span. See [`Reservation::as_ptr`].
196    #[must_use]
197    pub fn as_ptr(&self) -> *mut u8 {
198        self.inner.as_ptr()
199    }
200
201    /// Usable length of the span — committed and uncommitted together.
202    #[must_use]
203    pub const fn len(&self) -> usize {
204        self.inner.len()
205    }
206
207    /// Whether the usable span is empty. Always `false` for a reservation this
208    /// crate produced (a zero-size request is rejected); present because clippy
209    /// asks for it alongside `len`.
210    #[must_use]
211    pub const fn is_empty(&self) -> bool {
212        self.inner.len() == 0
213    }
214
215    /// Alignment the span was reserved with. See [`Reservation::align`].
216    #[must_use]
217    pub const fn align(&self) -> usize {
218        self.inner.align()
219    }
220}