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}