hopper_runtime/segment_lease.rs
1//! RAII-leased typed segment guards.
2//!
3//! [`SegmentBorrowRegistry`]
4//! records live byte-range borrows. [`SegmentLease`] owns one registry entry
5//! and removes it on drop. [`SegRef`] and [`SegRefMut`] pair that lease with an
6//! account-data guard, allowing sequential access after the previous guard is
7//! dropped while rejecting incompatible live ranges.
8//!
9//! For example, both mutations below are sequential because the first guard is
10//! dropped before the second is acquired:
11//!
12//! ```ignore
13//! { let mut b = ctx.segment_mut::<WireU64>(0, BAL)?; *b += amount; }
14//! { let mut b = ctx.segment_mut::<WireU64>(0, BAL)?; *b += more; }
15//! ```
16//!
17//! ## Representation
18//!
19//! `SegmentLease` stores a raw pointer to the registry plus a
20//! `PhantomData<&'a mut SegmentBorrowRegistry>`. A raw pointer avoids extending
21//! a Rust `&mut` borrow of the entire context through the returned segment
22//! guard. The lifetime marker ties the lease to the registry borrow, and `Drop`
23//! performs an exact entry release without allocation.
24//!
25//! ## Why a wrapper, not a field on `Ref`/`RefMut`
26//!
27//! The canonical `hopper_runtime::Ref` / `RefMut` are kept flat on
28//! Solana (`{ptr, state_ptr}` = 2 words, see `borrow.rs`). Adding a
29//! registry pointer to them would expand the representation
30//! for all access paths, including the whole-account `load()` path that
31//! doesn't touch the segment registry. Keeping the lease as a separate
32//! wrapper leaves `load()` at two words while segment access carries the
33//! additional lease pointer.
34
35use core::marker::PhantomData;
36use core::ops::{Deref, DerefMut};
37
38use crate::borrow::{Ref, RefMut};
39use crate::segment_borrow::{SegmentBorrow, SegmentBorrowRegistry};
40
41// ══════════════════════════════════════════════════════════════════════
42// SegmentLease
43// ══════════════════════════════════════════════════════════════════════
44
45/// RAII lease on one registered entry in a
46/// [`SegmentBorrowRegistry`].
47///
48/// On drop, the lease removes the registered entry via exact match.
49/// It is returned wrapped inside [`SegRef`] / [`SegRefMut`]; callers
50/// should not construct a `SegmentLease` directly.
51///
52/// # Safety invariants
53///
54/// The raw pointer is valid for `'a` because the lease was created
55/// from a `&'a mut SegmentBorrowRegistry`. No other code writes to the
56/// registry while a lease exists *from the caller's perspective*,
57/// because the enclosing `SegRef<T>` / `SegRefMut<T>` owns the lease.
58/// Drop runs exactly once.
59pub struct SegmentLease<'a> {
60 registry: *mut SegmentBorrowRegistry,
61 borrow: SegmentBorrow,
62 _lt: PhantomData<&'a mut SegmentBorrowRegistry>,
63}
64
65impl<'a> SegmentLease<'a> {
66 /// Construct a lease from a live `&mut SegmentBorrowRegistry` and
67 /// the borrow that was just registered.
68 ///
69 /// # Safety
70 ///
71 /// The caller must ensure `borrow` was registered in `registry`
72 /// immediately before this call, and no path other than dropping
73 /// the returned lease will remove the entry.
74 ///
75 /// `pub` but `#[doc(hidden)]` so cross-crate Hopper code
76 /// (`hopper-core`'s `Frame`, macro-generated accessors) can build
77 /// leases without rebuilding the primitive; end users of Hopper
78 /// should reach for `AccountView::segment_ref` / `segment_mut`
79 /// instead, which wrap this constructor safely.
80 #[doc(hidden)]
81 #[inline(always)]
82 pub unsafe fn new(registry: &'a mut SegmentBorrowRegistry, borrow: SegmentBorrow) -> Self {
83 Self {
84 registry: registry as *mut _,
85 borrow,
86 _lt: PhantomData,
87 }
88 }
89
90 /// Construct a lease from a raw registry pointer.
91 ///
92 /// Used by batch APIs (`AccountView::split_segments_mut`) that
93 /// register several disjoint borrows against one
94 /// `&'a mut SegmentBorrowRegistry` and then hand back several
95 /// coexisting guards. Each guard needs its own lease, but only one
96 /// `&mut` exists. The batch helper takes the registry's raw pointer
97 /// once and binds every lease's lifetime to that single `&'a mut`.
98 ///
99 /// # Safety
100 ///
101 /// `registry` must point to a `SegmentBorrowRegistry` borrowed
102 /// mutably for `'a` (the caller holds the `&'a mut`), `borrow` must
103 /// have been registered in it immediately before, and no path other
104 /// than dropping the returned lease may remove that entry.
105 #[doc(hidden)]
106 #[inline(always)]
107 pub unsafe fn from_raw(registry: *mut SegmentBorrowRegistry, borrow: SegmentBorrow) -> Self {
108 Self {
109 registry,
110 borrow,
111 _lt: PhantomData,
112 }
113 }
114
115 /// The borrow entry this lease owns, for diagnostics.
116 ///
117 /// Inherent diagnostic accessor returning the owned `SegmentBorrow` record,
118 /// not `core::borrow::Borrow` (whose blanket reflexive impl has a different
119 /// shape); the name reads naturally at call sites.
120 #[allow(clippy::should_implement_trait)]
121 #[inline(always)]
122 pub fn borrow(&self) -> &SegmentBorrow {
123 &self.borrow
124 }
125}
126
127impl<'a> Drop for SegmentLease<'a> {
128 #[inline(always)]
129 fn drop(&mut self) {
130 // SAFETY: `_lt` pins `'a` to the registry borrow. The pointer remains
131 // valid for the full lifetime of `self`, and exact release removes only
132 // this lease's registered entry.
133 unsafe {
134 (*self.registry).release(&self.borrow);
135 }
136 }
137}
138
139impl<'a> core::fmt::Debug for SegmentLease<'a> {
140 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
141 f.debug_struct("SegmentLease")
142 .field("borrow", &self.borrow)
143 .finish_non_exhaustive()
144 }
145}
146
147// ══════════════════════════════════════════════════════════════════════
148// SegRef / SegRefMut
149// ══════════════════════════════════════════════════════════════════════
150
151/// Shared typed segment guard: a [`Ref<T>`](crate::borrow::Ref) paired
152/// with a [`SegmentLease`] that releases the registry entry on drop.
153///
154/// `SegRef<T>` derefs to `T`, so call sites written against the
155/// previous `Ref<T>`-returning signatures compile unchanged in the
156/// vast majority of cases (pattern bindings that explicitly named
157/// `Ref<'_, T>` need the one-word substitution to `SegRef<'_, T>`).
158pub struct SegRef<'a, T: ?Sized> {
159 inner: Ref<'a, T>,
160 lease: SegmentLease<'a>,
161}
162
163impl<'a, T: ?Sized> SegRef<'a, T> {
164 /// Assemble a `SegRef` from a pre-built inner guard and lease.
165 ///
166 /// Doc-hidden public constructor for cross-crate use (Frame,
167 /// generated accessors). Prefer `AccountView::segment_ref` /
168 /// `Context::segment_ref` / `Frame::segment_ref` in user code.
169 #[doc(hidden)]
170 #[inline(always)]
171 pub fn new(inner: Ref<'a, T>, lease: SegmentLease<'a>) -> Self {
172 Self { inner, lease }
173 }
174
175 /// Consume the guard and return the underlying pointer.
176 ///
177 /// The lease and account-level borrow are still released on drop
178 /// of the returned components; this escape hatch is provided for
179 /// rare generic plumbing.
180 #[inline(always)]
181 pub fn into_parts(self) -> (Ref<'a, T>, SegmentLease<'a>) {
182 (self.inner, self.lease)
183 }
184
185 /// Raw `*const T` of the borrowed data.
186 #[inline(always)]
187 pub fn as_ptr(&self) -> *const T {
188 self.inner.as_ptr()
189 }
190
191 /// Access the underlying `Ref<T>` without dropping the lease.
192 #[inline(always)]
193 pub fn inner(&self) -> &Ref<'a, T> {
194 &self.inner
195 }
196}
197
198impl<T: ?Sized> Deref for SegRef<'_, T> {
199 type Target = T;
200 #[inline(always)]
201 fn deref(&self) -> &T {
202 &self.inner
203 }
204}
205
206impl<T: ?Sized> core::fmt::Debug for SegRef<'_, T> {
207 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
208 f.debug_struct("SegRef")
209 .field("lease", &self.lease)
210 .finish_non_exhaustive()
211 }
212}
213
214/// Exclusive typed segment guard.
215///
216/// Mirror of [`SegRef`] for the mutable path. Derefs mutably to `T`.
217pub struct SegRefMut<'a, T: ?Sized> {
218 inner: RefMut<'a, T>,
219 lease: SegmentLease<'a>,
220}
221
222impl<'a, T: ?Sized> SegRefMut<'a, T> {
223 /// Assemble a `SegRefMut` from a pre-built inner guard and lease.
224 ///
225 /// Doc-hidden public constructor, see [`SegRef::new`].
226 #[doc(hidden)]
227 #[inline(always)]
228 pub fn new(inner: RefMut<'a, T>, lease: SegmentLease<'a>) -> Self {
229 Self { inner, lease }
230 }
231
232 /// Consume the guard and return its parts.
233 #[inline(always)]
234 pub fn into_parts(self) -> (RefMut<'a, T>, SegmentLease<'a>) {
235 (self.inner, self.lease)
236 }
237
238 #[inline(always)]
239 pub fn as_ptr(&self) -> *const T {
240 self.inner.as_ptr()
241 }
242
243 #[inline(always)]
244 pub fn as_mut_ptr(&mut self) -> *mut T {
245 self.inner.as_mut_ptr()
246 }
247}
248
249impl<T: ?Sized> Deref for SegRefMut<'_, T> {
250 type Target = T;
251 #[inline(always)]
252 fn deref(&self) -> &T {
253 &self.inner
254 }
255}
256
257impl<T: ?Sized> DerefMut for SegRefMut<'_, T> {
258 #[inline(always)]
259 fn deref_mut(&mut self) -> &mut T {
260 &mut self.inner
261 }
262}
263
264impl<T: ?Sized> core::fmt::Debug for SegRefMut<'_, T> {
265 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
266 f.debug_struct("SegRefMut")
267 .field("lease", &self.lease)
268 .finish_non_exhaustive()
269 }
270}
271
272// ══════════════════════════════════════════════════════════════════════
273// SegmentsMut, simultaneous disjoint mutable segment access
274// ══════════════════════════════════════════════════════════════════════
275
276/// A guard over **several disjoint typed sub-ranges** of one account,
277/// returned by [`AccountView::split_segments_mut`](crate::AccountView::split_segments_mut).
278///
279/// It holds a single exclusive byte borrow of the account plus `N`
280/// registry leases (one per range) that proved pairwise disjointness at
281/// construction and release on drop. Because the ranges are disjoint and
282/// all sit inside the one borrow, [`all_mut`](Self::all_mut) can hand out
283/// `N` independent `&mut T` simultaneously. This is the generalized
284/// `split_at_mut` for account fields that ordinary `segment_mut` cannot
285/// express.
286///
287/// Construction must go through the account's checked split API. Raw offsets
288/// cannot be supplied to a safe constructor:
289///
290/// ```compile_fail,E0624
291/// use hopper_runtime::{RefMut, SegmentLease, SegmentsMut};
292/// fn unchecked<'a>(data: RefMut<'a, [u8]>, leases: [SegmentLease<'a>; 2]) {
293/// let _ = SegmentsMut::<[u8; 8], 2>::new(data, [0, 0], leases);
294/// }
295/// ```
296pub struct SegmentsMut<'a, T, const N: usize> {
297 data: RefMut<'a, [u8]>,
298 offsets: [usize; N],
299 // Leases live for the guard; dropping them releases the registry
300 // entries. Order of field drops doesn't matter (independent ranges).
301 _leases: [SegmentLease<'a>; N],
302 _t: PhantomData<fn() -> T>,
303}
304
305impl<'a, T: crate::Pod, const N: usize> SegmentsMut<'a, T, N> {
306 /// Assemble the guard. Doc-hidden; built by `split_segments_mut`.
307 #[doc(hidden)]
308 #[inline(always)]
309 pub(crate) fn new(
310 data: RefMut<'a, [u8]>,
311 offsets: [usize; N],
312 leases: [SegmentLease<'a>; N],
313 ) -> Self {
314 Self {
315 data,
316 offsets,
317 _leases: leases,
318 _t: PhantomData,
319 }
320 }
321
322 /// Number of disjoint segments held.
323 #[inline(always)]
324 pub const fn len(&self) -> usize {
325 N
326 }
327
328 /// Whether this split contains no ranges (`N == 0`).
329 #[inline(always)]
330 pub const fn is_empty(&self) -> bool {
331 N == 0
332 }
333
334 /// Mutably access one segment by batch index.
335 #[inline(always)]
336 pub fn get_mut(&mut self, i: usize) -> Option<&mut T> {
337 let off = *self.offsets.get(i)?;
338 let base = self.data.as_bytes_mut_ptr();
339 // SAFETY: `off` was bounds- and size-validated for `T` at
340 // construction; the byte borrow backing `base` is exclusive and
341 // live for `&mut self`.
342 Some(unsafe { &mut *(base.add(off) as *mut T) })
343 }
344
345 /// Borrow **all** segments mutably at once as `[&mut T; N]`.
346 ///
347 /// Sound because the offsets are pairwise disjoint (proven by the
348 /// registry at construction) and every range lies inside the single
349 /// exclusive byte borrow, so the references never alias.
350 #[inline(always)]
351 pub fn all_mut(&mut self) -> [&mut T; N] {
352 let base = self.data.as_bytes_mut_ptr();
353 let offsets = self.offsets;
354 // Manual MaybeUninit fill (avoids `core::array::from_fn`, keeping
355 // codegen on the conservative SBPF version for broad deployability).
356 // SAFETY: array of `MaybeUninit` is valid uninitialized.
357 let mut out: [core::mem::MaybeUninit<&mut T>; N] =
358 unsafe { core::mem::MaybeUninit::uninit().assume_init() };
359 let mut i = 0;
360 while i < N {
361 // SAFETY: ranges are disjoint and validated; `base` is a live
362 // exclusive byte borrow, so each typed pointer is unique and
363 // non-overlapping.
364 let r: &mut T = unsafe { &mut *(base.add(offsets[i]) as *mut T) };
365 out[i] = core::mem::MaybeUninit::new(r);
366 i += 1;
367 }
368 // SAFETY: all N slots initialized above.
369 unsafe {
370 let init = core::ptr::read(&out as *const _ as *const [&mut T; N]);
371 // The `MaybeUninit` array does not drop its contents; the forget
372 // documents that ownership moved into `init` via the read above.
373 #[allow(clippy::forget_non_drop)]
374 core::mem::forget(out);
375 init
376 }
377 }
378}
379
380impl<'a, T, const N: usize> core::fmt::Debug for SegmentsMut<'a, T, N> {
381 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
382 f.debug_struct("SegmentsMut")
383 .field("offsets", &self.offsets)
384 .finish_non_exhaustive()
385 }
386}