Skip to main content

hopper_native/
entrypoint.rs

1//! Program entrypoint ownership for Hopper Native.
2//!
3//! This file is the only raw program-entry boundary owner in Hopper Native.
4//! Loader input parsing lives in [`crate::raw_input`], while the public macros
5//! below own the raw `entrypoint(input: *mut u8)` boundary and delegate into
6//! Hopper callbacks.
7
8use core::mem::MaybeUninit;
9
10use crate::account_view::AccountView;
11use crate::address::Address;
12use crate::error::ProgramError;
13
14/// Convert a handler's `ProgramError` into the Solana runtime's u64 return code.
15///
16/// Outlined `#[cold] #[inline(never)]` so an entrypoint's success tail lowers to
17/// a bare `return SUCCESS` and the `ProgramError -> u64` mapping (the 25-arm
18/// `From<ProgramError> for u64` match) is never inlined into the hot frame,
19/// where it would add code size and stack traffic that every successful
20/// invocation pays for. This mirrors Pinocchio's cold error outline.
21///
22/// The conversion is exactly `Into::<u64>::into(e)`, byte-for-byte identical to
23/// the previous inline `error.into()`, so the runtime error codes are unchanged.
24#[cold]
25#[inline(never)]
26pub fn err_to_u64(e: ProgramError) -> u64 {
27    e.into()
28}
29
30/// Process the BPF entrypoint input.
31///
32/// This is the function called by the canonical Hopper Native entrypoint macro's
33/// generated entrypoint.
34///
35/// # Safety
36///
37/// `input` must be the raw pointer provided by the Solana runtime.
38#[inline(always)]
39pub unsafe fn process_entrypoint<const MAX: usize>(
40    input: *mut u8,
41    process_instruction: for<'info> fn(
42        &'info Address,
43        &'info [AccountView<'info>],
44        &'info [u8],
45    ) -> crate::ProgramResult,
46) -> u64 {
47    const UNINIT: MaybeUninit<AccountView<'static>> = MaybeUninit::uninit();
48    let mut accounts = [UNINIT; 254]; // MAX_TX_ACCOUNTS
49
50    let (program_id, count, instruction_data) =
51        // SAFETY: `input` is the loader's input buffer (this function's
52        // contract) and `accounts` has room for the 254 accounts the parser
53        // may write.
54        unsafe { crate::raw_input::deserialize_accounts::<254>(input, &mut accounts) };
55
56    // Respect MAX: only pass up to MAX accounts to the callback.
57    let effective_count = count.min(MAX);
58    // SAFETY: The parser initialized the first `count` slots and
59    // `effective_count <= count`; `MaybeUninit<AccountView>` has the layout
60    // of `AccountView`.
61    let account_slice = unsafe {
62        core::slice::from_raw_parts(accounts.as_ptr() as *const AccountView<'_>, effective_count)
63    };
64
65    match process_instruction(program_id, account_slice, instruction_data) {
66        Ok(()) => crate::SUCCESS,
67        Err(error) => err_to_u64(error),
68    }
69}
70
71/// Declare the canonical Hopper Native program entrypoint.
72///
73/// Generates the `extern "C" fn entrypoint` that the Solana runtime calls.
74/// `program_entrypoint!` remains available as a backward-compatible alias.
75///
76/// # Usage
77///
78/// ```ignore
79/// use hopper_native::hopper_program_entrypoint;
80///
81/// hopper_program_entrypoint!(process_instruction);
82///
83/// pub fn process_instruction(
84///     program_id: &Address,
85///     accounts: &[AccountView],
86///     instruction_data: &[u8],
87/// ) -> ProgramResult {
88///     Ok(())
89/// }
90/// ```
91#[macro_export]
92macro_rules! hopper_program_entrypoint {
93    ( $process_instruction:expr ) => {
94        $crate::hopper_program_entrypoint!($process_instruction, { $crate::MAX_TX_ACCOUNTS });
95    };
96    ( $process_instruction:expr, $maximum:expr ) => {
97        /// # Safety
98        ///
99        /// Called by the Solana runtime; `input` is a valid BPF input buffer.
100        #[no_mangle]
101        pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
102            const UNINIT: core::mem::MaybeUninit<$crate::AccountView<'static>> =
103                core::mem::MaybeUninit::<$crate::AccountView<'static>>::uninit();
104            let mut accounts = [UNINIT; $maximum];
105
106            // SAFETY: `input` is the loader's input buffer (the entrypoint's
107            // contract) and `accounts` has `$maximum` slots, the bound the
108            // parser is given.
109            let (program_id, count, instruction_data) = unsafe {
110                $crate::raw_input::deserialize_accounts::<$maximum>(input, &mut accounts)
111            };
112
113            match $process_instruction(
114                program_id,
115                // SAFETY: The parser initialized the first `count` slots;
116                // `MaybeUninit<AccountView>` has the layout of `AccountView`.
117                unsafe {
118                    core::slice::from_raw_parts(
119                        accounts.as_ptr() as *const $crate::AccountView<'_>,
120                        count,
121                    )
122                },
123                instruction_data,
124            ) {
125                Ok(()) => $crate::SUCCESS,
126                Err(error) => $crate::entrypoint::err_to_u64(error),
127            }
128        }
129    };
130}
131
132/// Backward-compatible alias for `hopper_program_entrypoint!`.
133#[macro_export]
134macro_rules! program_entrypoint {
135    ( $process_instruction:expr ) => {
136        $crate::hopper_program_entrypoint!($process_instruction);
137    };
138    ( $process_instruction:expr, $maximum:expr ) => {
139        $crate::hopper_program_entrypoint!($process_instruction, $maximum);
140    };
141}
142
143/// Declare a fast two-argument Hopper Native program entrypoint.
144///
145/// Uses the SVM's second entrypoint register (`r2`), which carries a
146/// direct pointer to instruction data under [SIMD-0321], letting the
147/// entrypoint skip locating the instruction tail. Measured honestly
148/// (2026-07-21, post the 2026-07-07 fused single-pass walk): the fused
149/// scanning entrypoint already hops records by their `data_len` headers
150/// without touching account data, so on programs whose accounts fit the
151/// declared maximum the r2 path is CU-neutral (+/- 2 CU in controlled
152/// A/Bs) and costs ~368 bytes for carrying both paths. The historical
153/// "~30-40 CU" figure described the pre-fusion two-pass scanner. The r2
154/// path earns its keep as the base of the SIMD-0449 O(1) account-pointer
155/// table, and for instructions whose transaction carries many more
156/// accounts than the program materializes.
157///
158/// # Feature gating (`simd-0321`)
159///
160/// SIMD-0321 is **activated on all three public clusters** (feature gate
161/// `5xXZc66h4UdB6Yq7FzdBxBiRAFMMScMLwHxk2QZDaNZL`; mainnet-beta at slot
162/// 410,400,000, 2026-04-01). Current agave sets `r2` unconditionally, so
163/// builds may enable the feature for any cluster target; on a runtime
164/// that ever leaves `r2` zero, the null-check below still falls back to
165/// the scanning parse.
166///
167/// - **Default (feature off):** this macro expands to the standard
168///   scanning entrypoint ([`hopper_program_entrypoint!`]). Identical
169///   semantics, sound on every cluster today, and source-compatible:
170///   when the gate activates, rebuild with the feature to claim the
171///   CU savings.
172/// - **`simd-0321` enabled:** the macro expands to the two-argument
173///   entrypoint. As defense in depth it null-checks `r2` and falls
174///   back to the scanning parse when the register is zero (current
175///   SBPF VMs zero-initialize unused argument registers), so a binary
176///   built with the feature degrades to the slow path instead of
177///   reading garbage if it lands on a cluster without the activation.
178///
179/// `hopper doctor` / `hopper deploy` can check the feature-gate account
180/// on the target cluster before a `simd-0321` build ships.
181///
182/// [SIMD-0321]: https://github.com/solana-foundation/solana-improvement-documents/blob/main/proposals/0321-vm-r2-instruction-data-pointer.md
183///
184/// # Usage
185///
186/// ```ignore
187/// use hopper_native::hopper_fast_entrypoint;
188///
189/// hopper_fast_entrypoint!(process_instruction, 3);
190///
191/// pub fn process_instruction(
192///     program_id: &Address,
193///     accounts: &[AccountView],
194///     instruction_data: &[u8],
195/// ) -> ProgramResult {
196///     Ok(())
197/// }
198/// ```
199#[cfg(feature = "simd-0321")]
200#[macro_export]
201macro_rules! hopper_fast_entrypoint {
202    ( $process_instruction:expr ) => {
203        $crate::hopper_fast_entrypoint!($process_instruction, { $crate::MAX_TX_ACCOUNTS });
204    };
205    ( $process_instruction:expr, $maximum:expr ) => {
206        /// # Safety
207        ///
208        /// Called by the Solana runtime; `input` is a valid BPF input buffer.
209        /// When SIMD-0321 is active, `ix_data` points to the instruction data
210        /// with its u64 length stored at offset -8; when it is not active the
211        /// register is zero and the scanning fallback below is taken.
212        #[no_mangle]
213        pub unsafe extern "C" fn entrypoint(input: *mut u8, ix_data: *const u8) -> u64 {
214            const UNINIT: core::mem::MaybeUninit<$crate::AccountView<'static>> =
215                core::mem::MaybeUninit::<$crate::AccountView<'static>>::uninit();
216            let mut accounts = [UNINIT; $maximum];
217
218            let (program_id, count, instruction_data) = if ix_data.is_null() {
219                // SIMD-0321 not active on this cluster: r2 is zero. Fall back
220                // to the full scanning parse so the program stays correct.
221                // SAFETY: `input` is the loader-provided input buffer; the
222                // scanning parser owns all bounds/duplicate-marker checks.
223                unsafe { $crate::raw_input::deserialize_accounts::<$maximum>(input, &mut accounts) }
224            } else {
225                // Instruction data length is the u64 immediately before the
226                // data pointer (per SIMD-0321's serialization contract).
227                // SAFETY: SIMD-0321 ix_data points at instruction-data bytes
228                // with u64 length prefix at `ix_data - 8`.
229                let ix_len =
230                    unsafe { core::ptr::read_unaligned(ix_data.sub(8) as *const u64) as usize };
231                let instruction_data: &'static [u8] =
232                    unsafe { core::slice::from_raw_parts(ix_data, ix_len) };
233
234                // SAFETY: program id trails the instruction data per the
235                // loader serialization layout; `Address` is a transparent
236                // `[u8; 32]`, so a reference into the buffer is valid at any
237                // offset and lives as long as the invocation.
238                let program_id: &'static $crate::Address =
239                    unsafe { &*(ix_data.add(ix_len) as *const $crate::Address) };
240
241                if $crate::raw_input::SIMD_0449_TABLE_ENABLED {
242                    // SIMD-0449 build: consume the runtime's appended
243                    // pre-deduplicated account-pointer table, O(1)
244                    // resolution plus one pointer copy per account. The
245                    // gate is a `const`, so the untaken branch folds
246                    // away entirely.
247                    // SAFETY: the `simd-0449` feature asserts the SIMD
248                    // is active on the target cluster (table present);
249                    // `instruction_data`/`program_id` were derived from
250                    // the SIMD-0321 r2 register above.
251                    unsafe {
252                        $crate::raw_input::deserialize_accounts_0449_into::<$maximum>(
253                            input,
254                            &mut accounts,
255                            instruction_data,
256                            program_id,
257                        )
258                    }
259                } else {
260                    // SAFETY: `input` is the loader input buffer; account-slot
261                    // framing is validated by `deserialize_accounts_fast`.
262                    unsafe {
263                        $crate::raw_input::deserialize_accounts_fast::<$maximum>(
264                            input,
265                            &mut accounts,
266                            instruction_data,
267                            program_id,
268                        )
269                    }
270                }
271            };
272
273            match $process_instruction(
274                program_id,
275                // SAFETY: the first `count` slots were initialized by the
276                // parser above; `AccountView` is repr(C) over the slot data.
277                unsafe {
278                    core::slice::from_raw_parts(
279                        accounts.as_ptr() as *const $crate::AccountView<'_>,
280                        count,
281                    )
282                },
283                instruction_data,
284            ) {
285                Ok(()) => $crate::SUCCESS,
286                Err(error) => $crate::entrypoint::err_to_u64(error),
287            }
288        }
289    };
290}
291
292/// Without the `simd-0321` feature the "fast" entrypoint is an alias for
293/// the standard scanning entrypoint. The SIMD-0321 gate is live on every
294/// public cluster (mainnet-beta 2026-04-01); the r2 form is sound to build
295/// and stays opt-in only because it measured CU-neutral against the fused
296/// scanning walk for ~368 bytes of extra `.text`. The two-argument r2 form
297/// also null-checks the register and falls back to scanning, so it is safe
298/// even where the gate is somehow inactive.
299#[cfg(not(feature = "simd-0321"))]
300#[macro_export]
301macro_rules! hopper_fast_entrypoint {
302    ( $process_instruction:expr ) => {
303        $crate::hopper_program_entrypoint!($process_instruction);
304    };
305    ( $process_instruction:expr, $maximum:expr ) => {
306        $crate::hopper_program_entrypoint!($process_instruction, $maximum);
307    };
308}
309
310/// Backward-compatible alias for `hopper_fast_entrypoint!`.
311#[macro_export]
312macro_rules! fast_entrypoint {
313    ( $process_instruction:expr ) => {
314        $crate::hopper_fast_entrypoint!($process_instruction);
315    };
316    ( $process_instruction:expr, $maximum:expr ) => {
317        $crate::hopper_fast_entrypoint!($process_instruction, $maximum);
318    };
319}
320
321/// Declare the canonical lazy program entrypoint that defers account parsing.
322#[macro_export]
323macro_rules! hopper_lazy_entrypoint {
324    ( $process:expr ) => {
325        /// # Safety
326        ///
327        /// Called by the Solana runtime; `input` is a valid BPF input buffer.
328        #[no_mangle]
329        pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 {
330            // SAFETY: `input` is the loader's input buffer (the entrypoint's
331            // contract), which is what `lazy_deserialize` requires.
332            let mut ctx = unsafe { $crate::lazy::lazy_deserialize(input) };
333            match $process(&mut ctx) {
334                Ok(()) => $crate::SUCCESS,
335                Err(error) => $crate::entrypoint::err_to_u64(error),
336            }
337        }
338    };
339}
340
341/// Backward-compatible alias for `hopper_lazy_entrypoint!`.
342#[macro_export]
343macro_rules! lazy_entrypoint {
344    ( $process:expr ) => {
345        $crate::hopper_lazy_entrypoint!($process);
346    };
347}
348
349/// Set up a no-op global allocator that aborts on allocation.
350///
351/// Useful for `no_std` programs that must not allocate. Any attempt to
352/// allocate immediately aborts the invocation through the SVM's abort syscall.
353/// No experimental inline assembly is required. Returning null would also be
354/// valid for `GlobalAlloc`; this allocator deliberately fails immediately.
355#[macro_export]
356macro_rules! no_allocator {
357    () => {
358        #[cfg(target_os = "solana")]
359        mod __hopper_allocator {
360            struct NoAlloc;
361
362            unsafe impl core::alloc::GlobalAlloc for NoAlloc {
363                unsafe fn alloc(&self, _layout: core::alloc::Layout) -> *mut u8 {
364                    // SAFETY: abort accepts no pointers and never returns.
365                    unsafe { $crate::syscalls::abort() }
366                }
367                unsafe fn dealloc(&self, _ptr: *mut u8, _layout: core::alloc::Layout) {}
368            }
369
370            #[global_allocator]
371            static ALLOCATOR: NoAlloc = NoAlloc;
372        }
373    };
374}
375
376/// Canonical Solana heap region start address (`0x3_0000_0000`).
377pub const HEAP_START_ADDRESS: usize = 0x3_0000_0000;
378
379/// Default Solana heap region length (32 KiB).
380pub const HEAP_LENGTH: usize = 32 * 1024;
381
382/// Bytes of the heap's BOTTOM reserved as Hopper runtime scratch, starting
383/// right after the [`BumpAllocator`] cursor word: the byte range
384/// `[HEAP_START + 8, HEAP_START + 8 + HEAP_RUNTIME_RESERVED)`.
385///
386/// Why this exists: deployed SBF programs cannot carry writable sections,
387/// the loader rejects `.bss`/`.data` outright (`WritableSectionNotSupported`),
388/// so a `static mut` is not merely costly, it makes the program FAIL TO
389/// LOAD. The only writable, per-invocation, zero-initialized memory a
390/// program owns is this VM heap region. Hopper's instruction-scoped
391/// runtime state (today: the lamport gate in
392/// `hopper_runtime::write_policy`) therefore lives at the heap bottom,
393/// which works precisely because the VM zeroes the region on every
394/// invocation and every such structure is valid all-zero.
395///
396/// The [`BumpAllocator`] treats this range as out of bounds (its floor sits
397/// above it), so `alloc` can never hand it out. Programs that install a
398/// custom allocator over the heap must honor the same reservation if they
399/// link any hopper-runtime feature that uses it.
400pub const HEAP_RUNTIME_RESERVED: usize = 20 * 1024;
401
402/// Bytes at the top of the reserved scratch that hold the per-invocation
403/// Rent cache (`hopper_runtime::rent::live_rent`): the rate, the threshold
404/// bits, and a loaded flag. All-zero is the empty cache, so the VM's
405/// zeroed heap needs no initialization, exactly like the gate store below
406/// it. The gate store and the touch log assert that they end before
407/// [`RENT_CACHE_HEAP_OFFSET`].
408pub const RENT_CACHE_BYTES: usize = 32;
409
410/// Heap offset of the Rent cache, relative to [`HEAP_START_ADDRESS`].
411pub const RENT_CACHE_HEAP_OFFSET: usize = HEAP_RUNTIME_RESERVED - RENT_CACHE_BYTES;
412
413/// The largest heap a transaction can request with
414/// `ComputeBudgetInstruction::RequestHeapFrame` (256 KiB).
415pub const MAX_HEAP_LENGTH: usize = 256 * 1024;
416
417/// A bump allocator over the SVM heap region.
418///
419/// Single pass and never frees, like the Solana SDK's and pinocchio's: the
420/// first word of the heap holds the cursor and `dealloc` does nothing. It
421/// is the right allocator for the cold paths of a program that wants
422/// `alloc` (a `Vec` while building a CPI) while the hot path allocates
423/// nothing. For programs that must never allocate, prefer [`no_allocator!`]
424/// so a stray allocation traps.
425///
426/// # It grows upward, so a requested heap frame is usable
427///
428/// The cursor starts just above Hopper's reserved scratch and moves up. A
429/// program that declares a larger heap with `default_allocator!(heap = N)`
430/// can use all of it when the transaction carries
431/// `RequestHeapFrame(N)`. When the transaction does not, the first 32 KiB
432/// still work exactly as before, because small allocations land at the
433/// bottom either way; only an allocation that reaches past the memory the
434/// VM mapped faults, and the VM, not the allocator, stops it. An allocator
435/// that grows downward from the top of a 256 KiB region would fault on its
436/// first allocation in every transaction that forgot the request.
437///
438/// # The last allocation resizes in place
439///
440/// `realloc` of the most recent allocation moves the cursor and copies
441/// nothing. A `Vec` that grows while nothing else allocates, the usual
442/// case in a handler, costs its final size and not the sum of every size
443/// it passed through.
444///
445/// # Checkpoints
446///
447/// [`mark`](Self::mark) and [`release_to`](Self::release_to) give a loop
448/// the heap back on every iteration, which a bump allocator otherwise
449/// cannot do.
450///
451/// Install it with `default_allocator!`.
452pub struct BumpAllocator {
453    /// Heap region start address.
454    pub start: usize,
455    /// Heap region length in bytes.
456    pub len: usize,
457}
458
459/// A position of the heap cursor, taken with [`BumpAllocator::mark`].
460#[derive(Clone, Copy, Debug, PartialEq, Eq)]
461pub struct HeapMark(usize);
462
463impl HeapMark {
464    /// The mark a host build hands out: there is no VM heap off chain.
465    #[cfg(not(target_os = "solana"))]
466    pub(crate) const fn host() -> Self {
467        Self(0)
468    }
469}
470
471impl BumpAllocator {
472    /// An allocator over the SVM heap, `len` bytes long. `len` is the heap
473    /// the program expects: 32 KiB by default, up to 256 KiB when its
474    /// transactions request a heap frame. Anything else is a compile
475    /// error in a `static`.
476    pub const fn new(len: usize) -> Self {
477        assert!(
478            len >= HEAP_LENGTH && len <= MAX_HEAP_LENGTH,
479            "the heap is between 32 KiB and 256 KiB"
480        );
481        assert!(
482            len.is_multiple_of(1024),
483            "a heap frame is a multiple of 1 KiB"
484        );
485        Self {
486            start: HEAP_START_ADDRESS,
487            len,
488        }
489    }
490
491    /// The lowest address the allocator hands out: above the cursor word
492    /// and the Hopper runtime scratch ([`HEAP_RUNTIME_RESERVED`]).
493    #[inline(always)]
494    const fn floor(&self) -> usize {
495        self.start + core::mem::size_of::<usize>() + HEAP_RUNTIME_RESERVED
496    }
497
498    #[inline(always)]
499    fn cursor(&self) -> usize {
500        // SAFETY: `start` is the heap's first word, which this allocator
501        // owns as its cursor; the VM zeroes the heap, and a program runs on
502        // one thread.
503        let pos = unsafe { *(self.start as *const usize) };
504        // Zero is the VM's fresh heap: nothing allocated yet.
505        if pos == 0 {
506            self.floor()
507        } else {
508            pos
509        }
510    }
511
512    #[inline(always)]
513    fn set_cursor(&self, pos: usize) {
514        // SAFETY: as in `cursor`; the word is written only here.
515        unsafe { *(self.start as *mut usize) = pos };
516    }
517
518    /// Bytes handed out so far, alignment padding included.
519    #[inline]
520    pub fn used(&self) -> usize {
521        self.cursor() - self.floor()
522    }
523
524    /// Bytes left before the declared end of the heap.
525    #[inline]
526    pub fn remaining(&self) -> usize {
527        (self.start + self.len).saturating_sub(self.cursor())
528    }
529
530    /// The current position of the heap, to return to with
531    /// [`release_to`](Self::release_to).
532    #[inline]
533    pub fn mark(&self) -> HeapMark {
534        HeapMark(self.cursor())
535    }
536
537    /// Give back every allocation made since `mark` was taken.
538    ///
539    /// # Safety
540    ///
541    /// Nothing allocated after `mark` may be used again: every `Box`,
542    /// `Vec`, and `String` created since must already be dropped or
543    /// forgotten. `mark` must come from this allocator in this invocation.
544    #[inline]
545    pub unsafe fn release_to(&self, mark: HeapMark) {
546        let pos = mark.0;
547        if pos >= self.floor() && pos <= self.cursor() {
548            self.set_cursor(pos);
549        }
550    }
551}
552
553// SAFETY: every pointer returned lies in `[floor, start + len)`, is aligned
554// as the layout asks, and is never returned twice while live: the cursor
555// only moves past what was handed out, except in `realloc` of the most
556// recent block (which stays where it is) and in `release_to` (whose caller
557// promises the released blocks are dead). A program runs on one thread, so
558// the cursor word is never accessed concurrently.
559unsafe impl core::alloc::GlobalAlloc for BumpAllocator {
560    // SAFETY: `GlobalAlloc::alloc` is `unsafe` by the trait's signature.
561    // The body is integer arithmetic on the cursor with every sum checked;
562    // it asks nothing of the caller beyond a valid `Layout`, whose
563    // alignment is a nonzero power of two by construction.
564    #[inline]
565    unsafe fn alloc(&self, layout: core::alloc::Layout) -> *mut u8 {
566        let mask = layout.align() - 1;
567        let start = match self.cursor().checked_add(mask) {
568            Some(bumped) => bumped & !mask,
569            None => return core::ptr::null_mut(),
570        };
571        let end = match start.checked_add(layout.size()) {
572            Some(end) => end,
573            None => return core::ptr::null_mut(),
574        };
575        if end > self.start + self.len {
576            return core::ptr::null_mut();
577        }
578        self.set_cursor(end);
579        start as *mut u8
580    }
581
582    // SAFETY: `GlobalAlloc::dealloc` is `unsafe` by the trait's signature.
583    // This body reads and writes nothing, so it holds for any pointer and
584    // layout: a bump allocator reclaims its memory when the instruction
585    // ends.
586    #[inline]
587    unsafe fn dealloc(&self, _ptr: *mut u8, _layout: core::alloc::Layout) {}
588
589    // SAFETY: `GlobalAlloc::realloc` is `unsafe` by the trait's signature.
590    // The caller vouches that `ptr` is a live block of this allocator with
591    // layout `layout`; the body moves the cursor or copies `layout.size()`
592    // bytes out of that block, and nothing else.
593    #[inline]
594    unsafe fn realloc(
595        &self,
596        ptr: *mut u8,
597        layout: core::alloc::Layout,
598        new_size: usize,
599    ) -> *mut u8 {
600        let block = ptr as usize;
601        // The most recent allocation ends at the cursor: it grows or
602        // shrinks where it is.
603        if block.checked_add(layout.size()) == Some(self.cursor()) {
604            return match block.checked_add(new_size) {
605                Some(end) if end <= self.start + self.len => {
606                    self.set_cursor(end);
607                    ptr
608                }
609                _ => core::ptr::null_mut(),
610            };
611        }
612        if new_size <= layout.size() {
613            // An older block that shrinks keeps its place.
614            return ptr;
615        }
616        // SAFETY: `new_size` is nonzero (it exceeds the old size) and the
617        // alignment is the old layout's, which the caller vouches for.
618        let new_layout =
619            unsafe { core::alloc::Layout::from_size_align_unchecked(new_size, layout.align()) };
620        // SAFETY: forwarded `GlobalAlloc` contract.
621        let new_ptr = unsafe { self.alloc(new_layout) };
622        if !new_ptr.is_null() {
623            // SAFETY: the old block is valid for `layout.size()` bytes, the
624            // new one for more, and a fresh block cannot overlap a live one.
625            unsafe { core::ptr::copy_nonoverlapping(ptr, new_ptr, layout.size()) };
626        }
627        new_ptr
628    }
629}
630
631/// Install the default bump allocator over the SVM heap region.
632///
633/// Opt-in counterpart to [`no_allocator!`]: use this when a program needs
634/// `alloc` (e.g. heap `Vec`/`String` on a cold path) while keeping the
635/// zero-copy hot path allocation-free. Never frees within an instruction;
636/// the whole heap is reclaimed when the instruction returns.
637///
638/// `default_allocator!(heap = 128 * 1024)` declares a larger heap, up to
639/// 256 KiB. The transactions that need it carry
640/// `ComputeBudgetInstruction::RequestHeapFrame` with the same size; the
641/// ones that allocate less than 32 KiB work without it.
642#[macro_export]
643macro_rules! default_allocator {
644    () => {
645        $crate::default_allocator!(heap = $crate::HEAP_LENGTH);
646    };
647    (heap = $len:expr) => {
648        #[cfg(target_os = "solana")]
649        #[global_allocator]
650        static ALLOCATOR: $crate::BumpAllocator = $crate::BumpAllocator::new($len);
651    };
652}
653
654/// Whether a panic reports where it happened (`panic-location`).
655pub const PANIC_REPORTS_LOCATION: bool = cfg!(feature = "panic-location");
656/// Whether a panic logs its message (`panic-message`).
657pub const PANIC_REPORTS_MESSAGE: bool = cfg!(feature = "panic-message");
658
659/// What the panic handler does, as a function so the features that shape
660/// it are this crate's and not the calling program's.
661///
662/// - By default: abort. Nothing is logged and no formatting code is
663///   linked, so a release build pays nothing for having a handler.
664/// - With `panic-location`: the runtime logs `panicked at file:line:col`
665///   (through `sol_panic_`) before the transaction fails. The file names
666///   are the only cost, a few hundred bytes of read-only data.
667/// - With `panic-message`: the panic's message is logged first, cut at
668///   the last whole character that fits 256 bytes. This links the
669///   formatting machinery, so it is for debugging builds.
670///
671/// Turn the features on through `hopper` (`features = ["panic-location"]`)
672/// while a program is being debugged, and off again for the build that
673/// ships.
674#[cfg(target_os = "solana")]
675#[inline(always)]
676pub fn report_panic(info: &core::panic::PanicInfo<'_>) -> ! {
677    #[cfg(feature = "panic-message")]
678    {
679        use core::fmt::Write;
680        let mut buf = [0u8; 256];
681        let mut writer = crate::log::StackWriter::new(&mut buf);
682        let _ = write!(writer, "{}", info.message());
683        crate::log::log(writer.as_str());
684    }
685    #[cfg(feature = "panic-location")]
686    if let Some(location) = info.location() {
687        let file = location.file();
688        // SAFETY: the pointer and the length describe one live `&str`;
689        // the syscall logs it with the line and column and never returns.
690        unsafe {
691            crate::syscalls::sol_panic_(
692                file.as_ptr(),
693                file.len() as u64,
694                location.line() as u64,
695                location.column() as u64,
696            )
697        }
698    }
699    let _ = info;
700    // SAFETY: abort accepts no pointers and never returns.
701    unsafe { crate::syscalls::abort() }
702}
703
704/// The `no_std` panic handler.
705///
706/// Aborts through the SVM's abort syscall, without experimental inline
707/// assembly or a compute-consuming spin loop; the runtime rolls the
708/// instruction back. With the `panic-location` feature it first reports
709/// the file, line, and column, and with `panic-message` the message. See
710/// [`report_panic`](crate::entrypoint::report_panic).
711#[macro_export]
712macro_rules! nostd_panic_handler {
713    () => {
714        #[cfg(target_os = "solana")]
715        #[panic_handler]
716        fn panic(info: &core::panic::PanicInfo) -> ! {
717            $crate::entrypoint::report_panic(info)
718        }
719    };
720}
721
722#[cfg(test)]
723mod entrypoint_tail_tests {
724    extern crate std;
725
726    use std::vec;
727    use std::vec::Vec;
728
729    use super::*;
730
731    /// Serialize a zero-account loader frame: `u64` account count (0), then the
732    /// `u64` ix-data length prefix, the ix-data bytes, and the 32-byte program
733    /// id. Returns an 8-aligned `u64` backing (matching `MM_INPUT_START`).
734    fn build_zero_account_frame(ix_data: &[u8], program_id: [u8; 32]) -> Vec<u64> {
735        let mut buf: Vec<u8> = Vec::new();
736        buf.extend_from_slice(&0u64.to_le_bytes()); // account_count = 0
737        buf.extend_from_slice(&(ix_data.len() as u64).to_le_bytes());
738        buf.extend_from_slice(ix_data);
739        buf.extend_from_slice(&program_id);
740        let mut words = vec![0u64; buf.len().div_ceil(8)];
741        // SAFETY: `words` has at least `buf.len()` bytes of capacity and the
742        // regions do not overlap.
743        unsafe {
744            core::ptr::copy_nonoverlapping(buf.as_ptr(), words.as_mut_ptr() as *mut u8, buf.len());
745        }
746        words
747    }
748
749    fn ok_handler<'a>(
750        _: &'a Address,
751        _: &'a [AccountView<'a>],
752        _: &'a [u8],
753    ) -> crate::ProgramResult {
754        Ok(())
755    }
756
757    fn custom_err_handler<'a>(
758        _: &'a Address,
759        _: &'a [AccountView<'a>],
760        _: &'a [u8],
761    ) -> crate::ProgramResult {
762        Err(ProgramError::Custom(4242))
763    }
764
765    fn builtin_err_handler<'a>(
766        _: &'a Address,
767        _: &'a [AccountView<'a>],
768        _: &'a [u8],
769    ) -> crate::ProgramResult {
770        Err(ProgramError::MissingRequiredSignature)
771    }
772
773    #[test]
774    fn ok_returns_bare_success_zero() {
775        let mut frame = build_zero_account_frame(&[1, 2, 3], [7u8; 32]);
776        // SAFETY: `frame` is a well-formed, 8-aligned zero-account loader frame.
777        let code = unsafe { process_entrypoint::<4>(frame.as_mut_ptr() as *mut u8, ok_handler) };
778        assert_eq!(code, 0);
779        assert_eq!(code, crate::SUCCESS);
780    }
781
782    #[test]
783    fn custom_err_maps_through_cold_outline_unchanged() {
784        let mut frame = build_zero_account_frame(&[], [0u8; 32]);
785        // SAFETY: well-formed, 8-aligned zero-account loader frame.
786        let code =
787            unsafe { process_entrypoint::<4>(frame.as_mut_ptr() as *mut u8, custom_err_handler) };
788        // Cold outline must equal the direct `From<ProgramError> for u64` mapping.
789        assert_eq!(code, u64::from(ProgramError::Custom(4242)));
790        assert_eq!(code, err_to_u64(ProgramError::Custom(4242)));
791        assert_eq!(code, 4242);
792    }
793
794    #[test]
795    fn builtin_err_maps_through_cold_outline_unchanged() {
796        let mut frame = build_zero_account_frame(&[], [0u8; 32]);
797        // SAFETY: well-formed, 8-aligned zero-account loader frame.
798        let code =
799            unsafe { process_entrypoint::<4>(frame.as_mut_ptr() as *mut u8, builtin_err_handler) };
800        assert_eq!(code, u64::from(ProgramError::MissingRequiredSignature));
801        assert_eq!(code, err_to_u64(ProgramError::MissingRequiredSignature));
802    }
803
804    /// The cold outline is a byte-for-byte alias of `From<ProgramError> for u64`
805    /// across the full variant space (custom-zero, custom, and builtins).
806    #[test]
807    fn err_to_u64_matches_from_impl_for_all_variants() {
808        let cases = [
809            ProgramError::Custom(0),
810            ProgramError::Custom(1),
811            ProgramError::Custom(u32::MAX),
812            ProgramError::InvalidArgument,
813            ProgramError::MissingRequiredSignature,
814            ProgramError::AccountBorrowFailed,
815            ProgramError::ArithmeticOverflow,
816            ProgramError::IncorrectAuthority,
817        ];
818        for e in cases {
819            assert_eq!(err_to_u64(e.clone()), u64::from(e));
820        }
821    }
822}
823
824#[cfg(test)]
825mod allocator_tests {
826    extern crate std;
827
828    use super::*;
829    use core::alloc::{GlobalAlloc, Layout};
830    use std::vec;
831    use std::vec::Vec;
832
833    /// A zeroed, 8-aligned region that stands in for the VM's heap, and an
834    /// allocator over it.
835    fn test_heap(len: usize) -> (Vec<u64>, BumpAllocator) {
836        let mut backing = vec![0u64; len / 8];
837        let allocator = BumpAllocator {
838            start: backing.as_mut_ptr() as usize,
839            len,
840        };
841        (backing, allocator)
842    }
843
844    const SCRATCH_END: usize = 8 + HEAP_RUNTIME_RESERVED;
845
846    fn layout(size: usize, align: usize) -> Layout {
847        Layout::from_size_align(size, align).unwrap()
848    }
849
850    #[test]
851    fn dealloc_gives_nothing_back_and_touches_nothing() {
852        let (backing, heap) = test_heap(HEAP_LENGTH);
853        // SAFETY: a test allocation from a private region.
854        let a = unsafe { heap.alloc(layout(64, 8)) };
855        let used = heap.used();
856        // SAFETY: `a` came from this allocator with this layout.
857        unsafe { heap.dealloc(a, layout(64, 8)) };
858        assert_eq!(heap.used(), used);
859        // SAFETY: as above.
860        let b = unsafe { heap.alloc(layout(64, 8)) };
861        assert_eq!(b as usize, a as usize + 64, "the freed block is not reused");
862        // Only the cursor word was ever written.
863        assert!(backing[1..].iter().all(|word| *word == 0));
864    }
865
866    #[test]
867    fn allocations_start_above_the_scratch_and_grow_upward() {
868        let (backing, heap) = test_heap(HEAP_LENGTH);
869        assert_eq!(heap.used(), 0);
870        assert_eq!(heap.remaining(), HEAP_LENGTH - SCRATCH_END);
871        // SAFETY: a test allocation from a private region, never freed.
872        let a = unsafe { heap.alloc(layout(10, 1)) } as usize;
873        // SAFETY: as above.
874        let b = unsafe { heap.alloc(layout(8, 8)) } as usize;
875        // SAFETY: as above.
876        let c = unsafe { heap.alloc(layout(1, 1)) } as usize;
877        assert_eq!(a, heap.start + SCRATCH_END);
878        assert_eq!(
879            b,
880            heap.start + SCRATCH_END + 16,
881            "aligned up past the 10 bytes"
882        );
883        assert_eq!(c, b + 8);
884        assert_eq!(heap.used(), 25);
885        // The scratch region was not written.
886        assert!(backing[1..SCRATCH_END / 8].iter().all(|w| *w == 0));
887    }
888
889    #[test]
890    fn every_alignment_is_honoured() {
891        let (_backing, heap) = test_heap(HEAP_LENGTH);
892        for shift in 0..8 {
893            let align = 1usize << shift;
894            // SAFETY: a test allocation from a private region.
895            let odd = unsafe { heap.alloc(layout(3, 1)) };
896            assert!(!odd.is_null());
897            // SAFETY: as above.
898            let ptr = unsafe { heap.alloc(layout(5, align)) } as usize;
899            assert_eq!(ptr % align, 0, "alignment {align}");
900        }
901    }
902
903    #[test]
904    fn the_heap_is_exhausted_at_its_declared_end_and_not_before() {
905        let (_backing, heap) = test_heap(HEAP_LENGTH);
906        let room = HEAP_LENGTH - SCRATCH_END;
907        // SAFETY: a test allocation from a private region.
908        let all = unsafe { heap.alloc(layout(room, 1)) };
909        assert!(!all.is_null());
910        assert_eq!(heap.remaining(), 0);
911        // SAFETY: as above.
912        assert!(unsafe { heap.alloc(layout(1, 1)) }.is_null());
913        // A refused allocation leaves the cursor where it was.
914        assert_eq!(heap.used(), room);
915
916        let (_backing, heap) = test_heap(HEAP_LENGTH);
917        // SAFETY: as above.
918        assert!(unsafe { heap.alloc(layout(room + 1, 1)) }.is_null());
919        assert_eq!(heap.used(), 0);
920        // A size that would wrap the address space is refused too.
921        // SAFETY: as above.
922        assert!(unsafe { heap.alloc(layout(isize::MAX as usize - 64, 1)) }.is_null());
923    }
924
925    #[test]
926    fn a_requested_heap_frame_is_usable_to_its_end() {
927        let (_backing, heap) = test_heap(MAX_HEAP_LENGTH);
928        // More than the default heap holds in one allocation.
929        // SAFETY: a test allocation from a private region.
930        let big = unsafe { heap.alloc(layout(200 * 1024, 8)) };
931        assert!(!big.is_null());
932        // SAFETY: the block is 200 KiB of the private region.
933        unsafe { core::ptr::write_bytes(big, 0xA5, 200 * 1024) };
934        assert_eq!(heap.remaining(), MAX_HEAP_LENGTH - SCRATCH_END - 200 * 1024);
935        // A small allocation made first lands in the first 32 KiB, which
936        // every transaction has.
937        let (_backing, heap) = test_heap(MAX_HEAP_LENGTH);
938        // SAFETY: as above.
939        let small = unsafe { heap.alloc(layout(64, 8)) } as usize;
940        assert!(small + 64 <= heap.start + HEAP_LENGTH);
941    }
942
943    #[test]
944    fn the_last_allocation_resizes_in_place() {
945        let (_backing, heap) = test_heap(HEAP_LENGTH);
946        let first = layout(16, 8);
947        // SAFETY: test allocations from a private region; each block is
948        // used only within its size.
949        unsafe {
950            let ptr = heap.alloc(first);
951            core::ptr::write_bytes(ptr, 7, 16);
952            let grown = heap.realloc(ptr, first, 64);
953            assert_eq!(grown, ptr, "grown where it is");
954            assert_eq!(heap.used(), 64);
955            assert_eq!(*grown.add(15), 7);
956            let shrunk = heap.realloc(grown, layout(64, 8), 8);
957            assert_eq!(shrunk, ptr);
958            assert_eq!(heap.used(), 8, "the tail was given back");
959
960            // Growing past the end is refused and changes nothing.
961            let refused = heap.realloc(shrunk, layout(8, 8), HEAP_LENGTH);
962            assert!(refused.is_null());
963            assert_eq!(heap.used(), 8);
964        }
965    }
966
967    #[test]
968    fn an_older_allocation_moves_when_it_grows() {
969        let (_backing, heap) = test_heap(HEAP_LENGTH);
970        let old = layout(8, 8);
971        // SAFETY: test allocations from a private region; each block is
972        // used only within its size.
973        unsafe {
974            let a = heap.alloc(old);
975            core::ptr::write_bytes(a, 0x11, 8);
976            let b = heap.alloc(layout(8, 8));
977            core::ptr::write_bytes(b, 0x22, 8);
978            let moved = heap.realloc(a, old, 24);
979            assert!(
980                moved as usize > b as usize,
981                "a new block above the newer one"
982            );
983            assert_eq!(core::slice::from_raw_parts(moved, 8), &[0x11; 8]);
984            assert_eq!(
985                core::slice::from_raw_parts(b, 8),
986                &[0x22; 8],
987                "the neighbour is intact"
988            );
989            // Shrinking an older block keeps it in place.
990            assert_eq!(heap.realloc(b, layout(8, 8), 4), b);
991        }
992    }
993
994    #[test]
995    fn a_checkpoint_gives_the_heap_back() {
996        let (_backing, heap) = test_heap(HEAP_LENGTH);
997        // SAFETY: test allocations from a private region; nothing
998        // allocated after the mark is used after the release.
999        unsafe {
1000            let kept = heap.alloc(layout(32, 8));
1001            let mark = heap.mark();
1002            let mut previous: *mut u8 = core::ptr::null_mut();
1003            for _ in 0..1_000 {
1004                let scratch = heap.alloc(layout(4096, 8));
1005                assert!(!scratch.is_null(), "the loop never runs out");
1006                if !previous.is_null() {
1007                    assert_eq!(scratch, previous, "the same bytes every time");
1008                }
1009                previous = scratch;
1010                heap.release_to(mark);
1011            }
1012            assert_eq!(heap.used(), 32);
1013            assert_eq!(heap.mark(), mark);
1014            assert!(!kept.is_null());
1015            // A mark from above the cursor or below the floor is ignored.
1016            let after = heap.alloc(layout(8, 8));
1017            let high = heap.mark();
1018            heap.release_to(mark);
1019            heap.release_to(high);
1020            assert_eq!(heap.used(), 32, "a stale mark cannot move the cursor up");
1021            assert!(!after.is_null());
1022        }
1023    }
1024
1025    #[test]
1026    fn the_declared_heap_is_checked() {
1027        assert_eq!(BumpAllocator::new(HEAP_LENGTH).len, HEAP_LENGTH);
1028        assert_eq!(
1029            BumpAllocator::new(MAX_HEAP_LENGTH).start,
1030            HEAP_START_ADDRESS
1031        );
1032        for bad in [
1033            0,
1034            HEAP_LENGTH - 1024,
1035            HEAP_LENGTH + 1,
1036            MAX_HEAP_LENGTH + 1024,
1037        ] {
1038            assert!(
1039                std::panic::catch_unwind(|| BumpAllocator::new(bad)).is_err(),
1040                "{bad}"
1041            );
1042        }
1043    }
1044}