From 630eb3b907c9207467cfd6982510c69ac1ed59a7 Mon Sep 17 00:00:00 2001 From: Jack May Date: Tue, 6 Oct 2020 11:03:51 -0700 Subject: [PATCH] Local program allocator (#12679) --- programs/bpf/Cargo.lock | 7 + programs/bpf/Cargo.toml | 1 + programs/bpf/build.rs | 1 + programs/bpf/rust/custom_heap/Cargo.toml | 27 ++++ programs/bpf/rust/custom_heap/Xargo.toml | 2 + programs/bpf/rust/custom_heap/src/lib.rs | 68 ++++++++ programs/bpf/rust/invoked/src/lib.rs | 178 +-------------------- programs/bpf/rust/invoked/src/processor.rs | 174 ++++++++++++++++++++ programs/bpf/tests/programs.rs | 9 +- sdk/src/entrypoint.rs | 129 ++++++++++++++- 10 files changed, 408 insertions(+), 188 deletions(-) create mode 100644 programs/bpf/rust/custom_heap/Cargo.toml create mode 100644 programs/bpf/rust/custom_heap/Xargo.toml create mode 100644 programs/bpf/rust/custom_heap/src/lib.rs create mode 100644 programs/bpf/rust/invoked/src/processor.rs diff --git a/programs/bpf/Cargo.lock b/programs/bpf/Cargo.lock index 1340c93841..0bd268f637 100644 --- a/programs/bpf/Cargo.lock +++ b/programs/bpf/Cargo.lock @@ -1784,6 +1784,13 @@ dependencies = [ "solana-sdk", ] +[[package]] +name = "solana-bpf-rust-custom-heap" +version = "1.4.0" +dependencies = [ + "solana-sdk", +] + [[package]] name = "solana-bpf-rust-dep-crate" version = "1.4.0" diff --git a/programs/bpf/Cargo.toml b/programs/bpf/Cargo.toml index be436ebacf..e83a7fe35f 100644 --- a/programs/bpf/Cargo.toml +++ b/programs/bpf/Cargo.toml @@ -37,6 +37,7 @@ members = [ "rust/128bit", "rust/128bit_dep", "rust/alloc", + "rust/custom_heap", "rust/dep_crate", "rust/deprecated_loader", "rust/dup_accounts", diff --git a/programs/bpf/build.rs b/programs/bpf/build.rs index 50b4dcbd7a..41d290aaf8 100644 --- a/programs/bpf/build.rs +++ b/programs/bpf/build.rs @@ -67,6 +67,7 @@ fn main() { let rust_programs = [ "128bit", "alloc", + "custom_heap", "dep_crate", "deprecated_loader", "dup_accounts", diff --git a/programs/bpf/rust/custom_heap/Cargo.toml b/programs/bpf/rust/custom_heap/Cargo.toml new file mode 100644 index 0000000000..0cd423d390 --- /dev/null +++ b/programs/bpf/rust/custom_heap/Cargo.toml @@ -0,0 +1,27 @@ + +# Note: This crate must be built using do.sh + +[package] +name = "solana-bpf-rust-custom-heap" +version = "1.4.0" +description = "Solana BPF test program written in Rust" +authors = ["Solana Maintainers "] +repository = "https://github.com/solana-labs/solana" +license = "Apache-2.0" +homepage = "https://solana.com/" +edition = "2018" + +[dependencies] +solana-sdk = { path = "../../../../sdk/", version = "1.4.0", default-features = false } + +[features] +custom-heap = [] +program = ["custom-heap", "solana-sdk/program"] +default = ["program", "solana-sdk/default"] + +[lib] +name = "solana_bpf_rust_custom_heap" +crate-type = ["cdylib"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] diff --git a/programs/bpf/rust/custom_heap/Xargo.toml b/programs/bpf/rust/custom_heap/Xargo.toml new file mode 100644 index 0000000000..1744f098ae --- /dev/null +++ b/programs/bpf/rust/custom_heap/Xargo.toml @@ -0,0 +1,2 @@ +[target.bpfel-unknown-unknown.dependencies.std] +features = [] \ No newline at end of file diff --git a/programs/bpf/rust/custom_heap/src/lib.rs b/programs/bpf/rust/custom_heap/src/lib.rs new file mode 100644 index 0000000000..81b27bc339 --- /dev/null +++ b/programs/bpf/rust/custom_heap/src/lib.rs @@ -0,0 +1,68 @@ +//! @brief Example Rust-based BPF that tests out using a custom heap + +use solana_sdk::{ + account_info::AccountInfo, + entrypoint, + entrypoint::{ProgramResult, HEAP_LENGTH, HEAP_START_ADDRESS}, + info, + pubkey::Pubkey, +}; +use std::{ + alloc::{alloc, Layout}, + mem::{align_of, size_of}, + ptr::null_mut, + usize, +}; + +/// Developers can implement their own heap by defining their own +/// `#[global_allocator]`. The following implements a dummy for test purposes +/// but can be flushed out with whatever the developer sees fit. +struct BumpAllocator; +unsafe impl std::alloc::GlobalAlloc for BumpAllocator { + #[inline] + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + if layout.size() == usize::MAX - 0x42 { + // Return test value + 0x42 as *mut u8 + } else { + const POS_PTR: *mut usize = HEAP_START_ADDRESS as *mut usize; + const TOP_ADDRESS: usize = HEAP_START_ADDRESS + HEAP_LENGTH; + const BOTTOM_ADDRESS: usize = HEAP_START_ADDRESS + size_of::<*mut u8>(); + + let mut pos = *POS_PTR; + if pos == 0 { + // First time, set starting position + pos = TOP_ADDRESS; + } + pos = pos.saturating_sub(layout.size()); + pos &= !(layout.align().saturating_sub(1)); + if pos < BOTTOM_ADDRESS { + return null_mut(); + } + *POS_PTR = pos; + pos as *mut u8 + } + } + #[inline] + unsafe fn dealloc(&self, _: *mut u8, _: Layout) { + // I'm a bump allocator, I don't free + } +} +#[cfg(not(test))] +#[global_allocator] +static A: BumpAllocator = BumpAllocator; + +entrypoint!(process_instruction); +fn process_instruction( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + info!("Custom heap"); + unsafe { + let layout = Layout::from_size_align(usize::MAX - 0x42, align_of::()).unwrap(); + let ptr = alloc(layout); + assert_eq!(ptr as u64, 0x42); + } + Ok(()) +} diff --git a/programs/bpf/rust/invoked/src/lib.rs b/programs/bpf/rust/invoked/src/lib.rs index d3495ec0f9..6f5ecbba15 100644 --- a/programs/bpf/rust/invoked/src/lib.rs +++ b/programs/bpf/rust/invoked/src/lib.rs @@ -1,179 +1,5 @@ //! @brief Example Rust-based BPF program that issues a cross-program-invocation -#![allow(unreachable_code)] - pub mod instruction; - -extern crate solana_sdk; - -use crate::instruction::*; -use solana_sdk::{ - account_info::AccountInfo, bpf_loader, entrypoint, entrypoint::ProgramResult, info, - program::invoke, program_error::ProgramError, pubkey::Pubkey, -}; - -entrypoint!(process_instruction); -#[allow(clippy::cognitive_complexity)] -fn process_instruction( - program_id: &Pubkey, - accounts: &[AccountInfo], - instruction_data: &[u8], -) -> ProgramResult { - info!("Invoked program"); - - if instruction_data.is_empty() { - return Ok(()); - } - - match instruction_data[0] { - TEST_VERIFY_TRANSLATIONS => { - info!("verify data translations"); - - const ARGUMENT_INDEX: usize = 0; - const INVOKED_ARGUMENT_INDEX: usize = 1; - const INVOKED_PROGRAM_INDEX: usize = 2; - const INVOKED_PROGRAM_DUP_INDEX: usize = 3; - - assert_eq!(&instruction_data[1..], &[1, 2, 3, 4, 5]); - assert_eq!(accounts.len(), 4); - - assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 42); - assert_eq!(accounts[ARGUMENT_INDEX].data_len(), 100); - assert!(accounts[ARGUMENT_INDEX].is_signer); - assert!(accounts[ARGUMENT_INDEX].is_writable); - assert_eq!(accounts[ARGUMENT_INDEX].rent_epoch, 0); - assert!(!accounts[ARGUMENT_INDEX].executable); - { - let data = accounts[ARGUMENT_INDEX].try_borrow_data()?; - for i in 0..100 { - assert_eq!(data[i as usize], i); - } - } - - assert_eq!( - accounts[INVOKED_ARGUMENT_INDEX].owner, - accounts[INVOKED_PROGRAM_INDEX].key - ); - assert_eq!(accounts[INVOKED_ARGUMENT_INDEX].lamports(), 10); - assert_eq!(accounts[INVOKED_ARGUMENT_INDEX].data_len(), 10); - assert!(accounts[INVOKED_ARGUMENT_INDEX].is_signer); - assert!(accounts[INVOKED_ARGUMENT_INDEX].is_writable); - assert_eq!(accounts[INVOKED_ARGUMENT_INDEX].rent_epoch, 0); - assert!(!accounts[INVOKED_ARGUMENT_INDEX].executable); - - assert_eq!(accounts[INVOKED_PROGRAM_INDEX].key, program_id); - assert_eq!(accounts[INVOKED_PROGRAM_INDEX].owner, &bpf_loader::id()); - assert!(!accounts[INVOKED_PROGRAM_INDEX].is_signer); - assert!(!accounts[INVOKED_PROGRAM_INDEX].is_writable); - assert_eq!(accounts[INVOKED_PROGRAM_INDEX].rent_epoch, 0); - assert!(accounts[INVOKED_PROGRAM_INDEX].executable); - - assert_eq!( - accounts[INVOKED_PROGRAM_INDEX].key, - accounts[INVOKED_PROGRAM_DUP_INDEX].key - ); - assert_eq!( - accounts[INVOKED_PROGRAM_INDEX].owner, - accounts[INVOKED_PROGRAM_DUP_INDEX].owner - ); - assert_eq!( - accounts[INVOKED_PROGRAM_INDEX].lamports, - accounts[INVOKED_PROGRAM_DUP_INDEX].lamports - ); - assert_eq!( - accounts[INVOKED_PROGRAM_INDEX].is_signer, - accounts[INVOKED_PROGRAM_DUP_INDEX].is_signer - ); - assert_eq!( - accounts[INVOKED_PROGRAM_INDEX].is_writable, - accounts[INVOKED_PROGRAM_DUP_INDEX].is_writable - ); - assert_eq!( - accounts[INVOKED_PROGRAM_INDEX].rent_epoch, - accounts[INVOKED_PROGRAM_DUP_INDEX].rent_epoch - ); - assert_eq!( - accounts[INVOKED_PROGRAM_INDEX].executable, - accounts[INVOKED_PROGRAM_DUP_INDEX].executable - ); - { - let data = accounts[INVOKED_PROGRAM_INDEX].try_borrow_data()?; - assert!(accounts[INVOKED_PROGRAM_DUP_INDEX] - .try_borrow_mut_data() - .is_err()); - info!(data[0], 0, 0, 0, 0); - } - } - TEST_RETURN_ERROR => { - info!("return error"); - return Err(ProgramError::Custom(42)); - } - TEST_DERIVED_SIGNERS => { - info!("verify derived signers"); - const DERIVED_KEY1_INDEX: usize = 1; - const DERIVED_KEY2_INDEX: usize = 2; - const DERIVED_KEY3_INDEX: usize = 3; - - assert!(accounts[DERIVED_KEY1_INDEX].is_signer); - assert!(!accounts[DERIVED_KEY2_INDEX].is_signer); - assert!(!accounts[DERIVED_KEY3_INDEX].is_signer); - } - TEST_VERIFY_NESTED_SIGNERS => { - info!("verify nested derived signers"); - const DERIVED_KEY1_INDEX: usize = 0; - const DERIVED_KEY2_INDEX: usize = 1; - const DERIVED_KEY3_INDEX: usize = 2; - - assert!(!accounts[DERIVED_KEY1_INDEX].is_signer); - assert!(accounts[DERIVED_KEY2_INDEX].is_signer); - assert!(accounts[DERIVED_KEY3_INDEX].is_signer); - } - TEST_VERIFY_WRITER => { - info!("verify writable"); - const ARGUMENT_INDEX: usize = 0; - - assert!(!accounts[ARGUMENT_INDEX].is_writable); - } - TEST_VERIFY_PRIVILEGE_ESCALATION => { - info!("Success"); - } - TEST_NESTED_INVOKE => { - info!("nested invoke"); - - const ARGUMENT_INDEX: usize = 0; - const INVOKED_ARGUMENT_INDEX: usize = 1; - const INVOKED_PROGRAM_INDEX: usize = 3; - - assert!(accounts[INVOKED_ARGUMENT_INDEX].is_signer); - - **accounts[INVOKED_ARGUMENT_INDEX].lamports.borrow_mut() -= 1; - **accounts[ARGUMENT_INDEX].lamports.borrow_mut() += 1; - if accounts.len() > 2 { - info!("Invoke again"); - let invoked_instruction = create_instruction( - *accounts[INVOKED_PROGRAM_INDEX].key, - &[ - (accounts[ARGUMENT_INDEX].key, true, true), - (accounts[INVOKED_ARGUMENT_INDEX].key, true, true), - ], - vec![TEST_NESTED_INVOKE], - ); - invoke(&invoked_instruction, accounts)?; - } else { - info!("Last invoked"); - { - let mut data = accounts[INVOKED_ARGUMENT_INDEX].try_borrow_mut_data()?; - for i in 0..10 { - data[i as usize] = i; - } - } - } - } - _ => panic!(), - } - - Ok(()) -} - -// Pull in syscall stubs when building for non-BPF targets -solana_sdk::program_stubs!(); +#[cfg(feature = "program")] +pub mod processor; diff --git a/programs/bpf/rust/invoked/src/processor.rs b/programs/bpf/rust/invoked/src/processor.rs new file mode 100644 index 0000000000..f926061ed0 --- /dev/null +++ b/programs/bpf/rust/invoked/src/processor.rs @@ -0,0 +1,174 @@ +//! @brief Example Rust-based BPF program that issues a cross-program-invocation + +use crate::instruction::*; +use solana_sdk::entrypoint; +use solana_sdk::{ + account_info::AccountInfo, bpf_loader, entrypoint::ProgramResult, info, program::invoke, + program_error::ProgramError, pubkey::Pubkey, +}; + +entrypoint!(process_instruction); +#[allow(clippy::cognitive_complexity)] +fn process_instruction( + program_id: &Pubkey, + accounts: &[AccountInfo], + instruction_data: &[u8], +) -> ProgramResult { + info!("Invoked program"); + + if instruction_data.is_empty() { + return Ok(()); + } + + match instruction_data[0] { + TEST_VERIFY_TRANSLATIONS => { + info!("verify data translations"); + + const ARGUMENT_INDEX: usize = 0; + const INVOKED_ARGUMENT_INDEX: usize = 1; + const INVOKED_PROGRAM_INDEX: usize = 2; + const INVOKED_PROGRAM_DUP_INDEX: usize = 3; + + assert_eq!(&instruction_data[1..], &[1, 2, 3, 4, 5]); + assert_eq!(accounts.len(), 4); + + assert_eq!(accounts[ARGUMENT_INDEX].lamports(), 42); + assert_eq!(accounts[ARGUMENT_INDEX].data_len(), 100); + assert!(accounts[ARGUMENT_INDEX].is_signer); + assert!(accounts[ARGUMENT_INDEX].is_writable); + assert_eq!(accounts[ARGUMENT_INDEX].rent_epoch, 0); + assert!(!accounts[ARGUMENT_INDEX].executable); + { + let data = accounts[ARGUMENT_INDEX].try_borrow_data()?; + for i in 0..100 { + assert_eq!(data[i as usize], i); + } + } + + assert_eq!( + accounts[INVOKED_ARGUMENT_INDEX].owner, + accounts[INVOKED_PROGRAM_INDEX].key + ); + assert_eq!(accounts[INVOKED_ARGUMENT_INDEX].lamports(), 10); + assert_eq!(accounts[INVOKED_ARGUMENT_INDEX].data_len(), 10); + assert!(accounts[INVOKED_ARGUMENT_INDEX].is_signer); + assert!(accounts[INVOKED_ARGUMENT_INDEX].is_writable); + assert_eq!(accounts[INVOKED_ARGUMENT_INDEX].rent_epoch, 0); + assert!(!accounts[INVOKED_ARGUMENT_INDEX].executable); + + assert_eq!(accounts[INVOKED_PROGRAM_INDEX].key, program_id); + assert_eq!(accounts[INVOKED_PROGRAM_INDEX].owner, &bpf_loader::id()); + assert!(!accounts[INVOKED_PROGRAM_INDEX].is_signer); + assert!(!accounts[INVOKED_PROGRAM_INDEX].is_writable); + assert_eq!(accounts[INVOKED_PROGRAM_INDEX].rent_epoch, 0); + assert!(accounts[INVOKED_PROGRAM_INDEX].executable); + + assert_eq!( + accounts[INVOKED_PROGRAM_INDEX].key, + accounts[INVOKED_PROGRAM_DUP_INDEX].key + ); + assert_eq!( + accounts[INVOKED_PROGRAM_INDEX].owner, + accounts[INVOKED_PROGRAM_DUP_INDEX].owner + ); + assert_eq!( + accounts[INVOKED_PROGRAM_INDEX].lamports, + accounts[INVOKED_PROGRAM_DUP_INDEX].lamports + ); + assert_eq!( + accounts[INVOKED_PROGRAM_INDEX].is_signer, + accounts[INVOKED_PROGRAM_DUP_INDEX].is_signer + ); + assert_eq!( + accounts[INVOKED_PROGRAM_INDEX].is_writable, + accounts[INVOKED_PROGRAM_DUP_INDEX].is_writable + ); + assert_eq!( + accounts[INVOKED_PROGRAM_INDEX].rent_epoch, + accounts[INVOKED_PROGRAM_DUP_INDEX].rent_epoch + ); + assert_eq!( + accounts[INVOKED_PROGRAM_INDEX].executable, + accounts[INVOKED_PROGRAM_DUP_INDEX].executable + ); + { + let data = accounts[INVOKED_PROGRAM_INDEX].try_borrow_data()?; + assert!(accounts[INVOKED_PROGRAM_DUP_INDEX] + .try_borrow_mut_data() + .is_err()); + info!(data[0], 0, 0, 0, 0); + } + } + TEST_RETURN_ERROR => { + info!("return error"); + return Err(ProgramError::Custom(42)); + } + TEST_DERIVED_SIGNERS => { + info!("verify derived signers"); + const DERIVED_KEY1_INDEX: usize = 1; + const DERIVED_KEY2_INDEX: usize = 2; + const DERIVED_KEY3_INDEX: usize = 3; + + assert!(accounts[DERIVED_KEY1_INDEX].is_signer); + assert!(!accounts[DERIVED_KEY2_INDEX].is_signer); + assert!(!accounts[DERIVED_KEY3_INDEX].is_signer); + } + TEST_VERIFY_NESTED_SIGNERS => { + info!("verify nested derived signers"); + const DERIVED_KEY1_INDEX: usize = 0; + const DERIVED_KEY2_INDEX: usize = 1; + const DERIVED_KEY3_INDEX: usize = 2; + + assert!(!accounts[DERIVED_KEY1_INDEX].is_signer); + assert!(accounts[DERIVED_KEY2_INDEX].is_signer); + assert!(accounts[DERIVED_KEY3_INDEX].is_signer); + } + TEST_VERIFY_WRITER => { + info!("verify writable"); + const ARGUMENT_INDEX: usize = 0; + + assert!(!accounts[ARGUMENT_INDEX].is_writable); + } + TEST_VERIFY_PRIVILEGE_ESCALATION => { + info!("Success"); + } + TEST_NESTED_INVOKE => { + info!("nested invoke"); + + const ARGUMENT_INDEX: usize = 0; + const INVOKED_ARGUMENT_INDEX: usize = 1; + const INVOKED_PROGRAM_INDEX: usize = 3; + + assert!(accounts[INVOKED_ARGUMENT_INDEX].is_signer); + + **accounts[INVOKED_ARGUMENT_INDEX].lamports.borrow_mut() -= 1; + **accounts[ARGUMENT_INDEX].lamports.borrow_mut() += 1; + if accounts.len() > 2 { + info!("Invoke again"); + let invoked_instruction = create_instruction( + *accounts[INVOKED_PROGRAM_INDEX].key, + &[ + (accounts[ARGUMENT_INDEX].key, true, true), + (accounts[INVOKED_ARGUMENT_INDEX].key, true, true), + ], + vec![TEST_NESTED_INVOKE], + ); + invoke(&invoked_instruction, accounts)?; + } else { + info!("Last invoked"); + { + let mut data = accounts[INVOKED_ARGUMENT_INDEX].try_borrow_mut_data()?; + for i in 0..10 { + data[i as usize] = i; + } + } + } + } + _ => panic!(), + } + + Ok(()) +} + +// Pull in syscall stubs when building for non-BPF targets +solana_sdk::program_stubs!(); diff --git a/programs/bpf/tests/programs.rs b/programs/bpf/tests/programs.rs index ef2f745737..495b9dc3fb 100644 --- a/programs/bpf/tests/programs.rs +++ b/programs/bpf/tests/programs.rs @@ -146,6 +146,7 @@ fn test_program_bpf_sanity() { programs.extend_from_slice(&[ ("solana_bpf_rust_128bit", true), ("solana_bpf_rust_alloc", true), + ("solana_bpf_rust_custom_heap", true), ("solana_bpf_rust_dep_crate", true), ("solana_bpf_rust_external_spend", false), ("solana_bpf_rust_iter", true), @@ -663,12 +664,12 @@ fn assert_instruction_count() { ("solana_bpf_rust_128bit", 543), ("solana_bpf_rust_alloc", 19082), ("solana_bpf_rust_dep_crate", 2), - ("solana_bpf_rust_external_spend", 485), + ("solana_bpf_rust_external_spend", 538), ("solana_bpf_rust_iter", 723), ("solana_bpf_rust_many_args", 231), - ("solana_bpf_rust_noop", 459), - ("solana_bpf_rust_param_passing", 54), - ("solana_bpf_rust_sanity", 2223), + ("solana_bpf_rust_noop", 512), + ("solana_bpf_rust_param_passing", 46), + ("solana_bpf_rust_sanity", 1989), ]); } diff --git a/sdk/src/entrypoint.rs b/sdk/src/entrypoint.rs index 0c3bc0a362..01f98ef827 100644 --- a/sdk/src/entrypoint.rs +++ b/sdk/src/entrypoint.rs @@ -5,8 +5,10 @@ extern crate alloc; use crate::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}; use alloc::vec::Vec; use std::{ + alloc::Layout, cell::RefCell, mem::{align_of, size_of}, + ptr::null_mut, rc::Rc, // Hide Result from bindgen gets confused about generics in non-generic type declarations result::Result as ResultGeneric, @@ -17,24 +19,39 @@ pub type ProgramResult = ResultGeneric<(), ProgramError>; /// User implemented function to process an instruction /// -/// program_id: Program ID of the currently executing program -/// accounts: Accounts passed as part of the instruction -/// instruction_data: Instruction data +/// program_id: Program ID of the currently executing program accounts: Accounts +/// passed as part of the instruction instruction_data: Instruction data pub type ProcessInstruction = fn(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult; /// Programs indicate success with a return value of 0 pub const SUCCESS: u64 = 0; -/// Declare the entry point of the program. +/// Start address of the memory region used for program heap. +pub const HEAP_START_ADDRESS: usize = 0x300000000; +/// Length of the heap memory region used for program heap. +pub const HEAP_LENGTH: usize = 32 * 1024; + +/// Declare the entry point of the program and use the default local heap +/// implementation /// -/// Deserialize the program input arguments and call -/// the user defined `process_instruction` function. -/// Users must call this macro otherwise an entry point for -/// their program will not be created. +/// Deserialize the program input arguments and call the user defined +/// `process_instruction` function. Users must call this macro otherwise an +/// entry point for their program will not be created. +/// +/// If the program defines the feature `custom-heap` then the default heap +/// implementation will not be included and the program is free to implement +/// their own `#[global_allocator]` #[macro_export] macro_rules! entrypoint { ($process_instruction:ident) => { + #[cfg(all(not(feature = "custom-heap"), not(test)))] + #[global_allocator] + static A: $crate::entrypoint::BumpAllocator = $crate::entrypoint::BumpAllocator { + start: $crate::entrypoint::HEAP_START_ADDRESS, + len: $crate::entrypoint::HEAP_LENGTH, + }; + /// # Safety #[cfg(not(feature = "skip-no-mangle"))] #[no_mangle] @@ -49,6 +66,35 @@ macro_rules! entrypoint { }; } +/// The bump allocator used as the default rust heap when running programs. +pub struct BumpAllocator { + pub start: usize, + pub len: usize, +} +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 + size_of::<*mut u8>() { + return null_mut(); + } + *pos_ptr = pos; + pos as *mut u8 + } + #[inline] + unsafe fn dealloc(&self, _: *mut u8, _: Layout) { + // I'm a bump allocator, I don't free + } +} + /// Maximum number of bytes a program may add to an account during a single realloc pub const MAX_PERMITTED_DATA_INCREASE: usize = 1_024 * 10; @@ -143,3 +189,70 @@ pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec() { + let ptr = unsafe { + allocator.alloc(Layout::from_size_align(1, size_of::()).unwrap()) + }; + assert_eq!( + ptr as *const _ as usize, + heap.as_ptr() as *const _ as usize + heap.len() - 1 - i + ); + } + assert_eq!(null_mut(), unsafe { + allocator.alloc(Layout::from_size_align(1, 1).unwrap()) + }); + } + // check alignment + { + let heap = vec![0u8; 128]; + let allocator = BumpAllocator { + start: heap.as_ptr() as *const _ as usize, + len: heap.len(), + }; + let ptr = + unsafe { allocator.alloc(Layout::from_size_align(1, size_of::()).unwrap()) }; + assert_eq!(0, ptr.align_offset(size_of::())); + let ptr = + unsafe { allocator.alloc(Layout::from_size_align(1, size_of::()).unwrap()) }; + assert_eq!(0, ptr.align_offset(size_of::())); + let ptr = + unsafe { allocator.alloc(Layout::from_size_align(1, size_of::()).unwrap()) }; + assert_eq!(0, ptr.align_offset(size_of::())); + let ptr = + unsafe { allocator.alloc(Layout::from_size_align(1, size_of::()).unwrap()) }; + assert_eq!(0, ptr.align_offset(size_of::())); + let ptr = + unsafe { allocator.alloc(Layout::from_size_align(1, size_of::()).unwrap()) }; + assert_eq!(0, ptr.align_offset(size_of::())); + let ptr = unsafe { allocator.alloc(Layout::from_size_align(1, 64).unwrap()) }; + assert_eq!(0, ptr.align_offset(64)); + } + // alloc entire block (minus the pos ptr) + { + let heap = vec![0u8; 128]; + let allocator = BumpAllocator { + start: heap.as_ptr() as *const _ as usize, + len: heap.len(), + }; + let ptr = + unsafe { allocator.alloc(Layout::from_size_align(120, size_of::()).unwrap()) }; + assert_ne!(ptr, null_mut()); + assert_eq!(0, ptr.align_offset(size_of::())); + } + } +}