-
Notifications
You must be signed in to change notification settings - Fork 21
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement custom heap to support SlotHistory deserialization (#22)
The SlotHistory sysvar is too big to deserialize on the stack, and too big for the default heap size (32kb). Solana supports up to 256KB of heap, but a custom heap implementation is needed, even if you request an increased heap size with the compute budget instruction, since the default global_allocator just uses the hard-coded 32kb. Mango's custom heap implementation implements a bottom-up heap as opposed to the top-down heap implemented by `solana_program::entrypoint:BumpAllocator`. The issue with the top down approach is, if you initialize the standard BumpAllocator with a larger heap length, instructions that don't need the extra heap space will start out allocating outside of the default heap bounds unless they submit a request_heap_frame instruction. By allocating from bottom up, allocs start in the valid bounds and don't need to submit the ix unless they go over 32kb.
- Loading branch information
Showing
5 changed files
with
98 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// // Copied from https://github.com/blockworks-foundation/mango-v4/blob/dev/programs/mango-v4/src/allocator.rs | ||
#![allow(dead_code)] | ||
|
||
use std::alloc::{GlobalAlloc, Layout}; | ||
|
||
/// The end of the region where heap space may be reserved for the program. | ||
/// | ||
/// The actual size of the heap is currently not available at runtime. | ||
/// https://solana.com/docs/programs/faq#memory-map | ||
pub const HEAP_END_ADDRESS: usize = 0x400000000; | ||
|
||
#[cfg(not(feature = "no-entrypoint"))] | ||
#[global_allocator] | ||
pub static ALLOCATOR: BumpAllocator = BumpAllocator {}; | ||
|
||
pub fn heap_used() -> usize { | ||
#[cfg(not(feature = "no-entrypoint"))] | ||
return ALLOCATOR.used(); | ||
|
||
#[cfg(feature = "no-entrypoint")] | ||
return 0; | ||
} | ||
|
||
/// Custom bump allocator for on-chain operations | ||
/// | ||
/// The default allocator is also a bump one, but grows from a fixed | ||
/// HEAP_START + 32kb downwards and has no way of making use of extra | ||
/// heap space requested for the transaction. | ||
/// | ||
/// This implementation starts at HEAP_START and grows upward, producing | ||
/// a segfault once out of available heap memory. | ||
pub struct BumpAllocator {} | ||
|
||
unsafe impl GlobalAlloc for BumpAllocator { | ||
#[inline] | ||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 { | ||
let heap_start = anchor_lang::solana_program::entrypoint::HEAP_START_ADDRESS as usize; | ||
let pos_ptr = heap_start as *mut usize; | ||
|
||
let mut pos = *pos_ptr; | ||
if pos == 0 { | ||
// First time, override the current position to be just past the location | ||
// where the current heap position is stored. | ||
pos = heap_start + 8; | ||
} | ||
|
||
// The result address needs to be aligned to layout.align(), | ||
// which is guaranteed to be a power of two. | ||
// Find the first address >=pos that has the required alignment. | ||
// Wrapping ops are used for performance. | ||
let mask = layout.align().wrapping_sub(1); | ||
let begin = pos.wrapping_add(mask) & (!mask); | ||
|
||
// Update allocator state | ||
let end = begin.checked_add(layout.size()).unwrap(); | ||
*pos_ptr = end; | ||
|
||
// Ensure huge allocations can't escape the dedicated heap memory region | ||
assert!(end < HEAP_END_ADDRESS); | ||
|
||
// Write a byte to trigger heap overflow errors early | ||
let end_ptr = end as *mut u8; | ||
*end_ptr = 0; | ||
|
||
begin as *mut u8 | ||
} | ||
#[inline] | ||
unsafe fn dealloc(&self, _: *mut u8, _: Layout) { | ||
// I'm a bump allocator, I don't free | ||
} | ||
} | ||
|
||
impl BumpAllocator { | ||
#[inline] | ||
pub fn used(&self) -> usize { | ||
let heap_start = anchor_lang::solana_program::entrypoint::HEAP_START_ADDRESS as usize; | ||
unsafe { | ||
let pos_ptr = heap_start as *mut usize; | ||
|
||
let pos = *pos_ptr; | ||
if pos == 0 { | ||
return 0; | ||
} | ||
pos - heap_start | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
use anchor_lang::prelude::*; | ||
|
||
mod allocator; | ||
pub mod constants; | ||
pub mod crds_value; | ||
pub mod errors; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters