3

We have a CPI that uses ~4000 bytes of heap, and it executes a few times in a loop, so for a few executes the program will fail with memory allocation failed, out of memory.

The solution has been to move the heap pointer manually with a custom global allocator, like so:

        let heap_start = unsafe { A.pos() };
        ctx.accounts.big_bad_cpi(
           args
        )?;
        unsafe { A.move_cursor(heap_start); }

This works well. Is there a better way to tell the Solana runtime to release the heap allocations made by the CPI? It seems like, since a CPI doesn't do anything to the program's variables, it should reset the heap pointer automatically to where it started when the CPI returns control.

Here is the bump allocator, based on Solana's reference implementation:

pub struct BumpAllocator {
    pub start: usize,
    pub len: usize,
}

impl BumpAllocator {
    const RESERVED_MEM: usize = 1 * size_of::<*mut u8>();

    /// Return heap position as of this call
    pub unsafe fn pos(&self) -> usize {
        let pos_ptr = self.start as *mut usize;
        *pos_ptr
    }

    /// Reset heap start cursor to position. 
    /// ### This is very unsafe, use wisely
    pub unsafe fn move_cursor(&self, pos: usize) {
        let pos_ptr = self.start as *mut usize;
        *pos_ptr = pos;
    }
}
unsafe impl std::alloc::GlobalAlloc for BumpAllocator {
    #[inline]
    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
        let pos_ptr = self.start as *mut usize;

        let mut pos = *pos_ptr;
        if pos == 0 {
            // First time, set starting position
            pos = self.start + self.len;
        }
        pos = pos.saturating_sub(layout.size());
        pos &= !(layout.align().wrapping_sub(1));
        if pos < self.start + BumpAllocator::RESERVED_MEM {
            return null_mut();
        }
        *pos_ptr = pos;
        pos as *mut u8
    }
    #[inline]
    unsafe fn dealloc(&self, _: *mut u8, _: Layout) { 
        // no dellaoc in Solana runtime :*(
    }
}

#[global_allocator]
static A: BumpAllocator = BumpAllocator {
    start: HEAP_START_ADDRESS as usize,
    len: HEAP_LENGTH,
};

1 Answer 1

3

Is there a better way to tell the Solana runtime to release the heap allocations made by the CPI? It seems like, since a CPI doesn't do anything to the program's variables, it should reset the heap pointer automatically to where it started when the CPI returns control.

There's no way to do this automatically, since there's no garbage collector or smarter allocator provided by default. There's just the default bump allocator, as you've noticed.

In order to save compute units and reduce program complexity, the default allocator is the simplest possible. Programs are meant to terminate very quickly, so anything more intricate is probably wasted compute units.

The runtime gives you 32kb that you can use as you want 100%, so if you need something better, you can find or write a better allocator!

Your technique is actually pretty good, since you're essentially doing the job of re-using the heap after every CPI.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.