Skip to main content

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}