diff --git a/Cargo.toml b/Cargo.toml index 46f11ea4..5b2849a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,7 +19,7 @@ rust-version = "1.84" [workspace.dependencies] five8_const = "0.1.4" -pinocchio = { version = "0.9", path = "sdk/pinocchio" } +pinocchio = { version = "0.9", default-features = false, path = "sdk/pinocchio" } pinocchio-log-macro = { version = "0.5", path = "sdk/log/macro" } quote = "1.0" regex = "1" diff --git a/README.md b/README.md index 42cb2f2b..1a693af3 100644 --- a/README.md +++ b/README.md @@ -166,17 +166,19 @@ pub fn process_instruction( > ⚠️ **Note:** > The `no_allocator!` macro can also be used in combination with the `lazy_program_entrypoint!`. -## Crate feature: `std` +## Crate feature: `alloc` + +The `alloc` feature is enabled by default and it uses the [`alloc`](https://doc.rust-lang.org/alloc/) crate. This provides access to dynamic memory allocation in combination with the `default_allocator`, e.g., required to use `String` and `Vec` in a program. Helpers that need to allocate memory, such as fetching `SlotHashes` sysvar data, are also available. + +When no allocation is needed or desired, the feature can be disabled: -By default, Pinocchio is a `no_std` crate. This means that it does not use any code from the -standard (`std`) library. While this does not affect how Pinocchio is used, there is a one -particular apparent difference. Helpers that need to allocate memory, such as fetching `SlotHashes` -sysvar data, are not available. To enable these helpers, the `std` feature must be enabled when adding -Pinocchio as a dependency: ``` -pinocchio = { version = "0.10.0", features = ["std"] } +pinocchio = { version = "0.10.0", default-features = false } ``` +> ⚠️ **Note:** +> The `default_allocator` macro is not available when disabling the `alloc` feature. + ## Advance entrypoint configuration The symbols emitted by the entrypoint macros — program entrypoint, global allocator and default panic handler — can only be defined once globally. If the program crate is also intended to be used as a library, it is common practice to define a Cargo [feature](https://doc.rust-lang.org/cargo/reference/features.html) in your program crate to conditionally enable the module that includes the `entrypoint!` macro invocation. The convention is to name the feature `bpf-entrypoint`. diff --git a/sdk/pinocchio/Cargo.toml b/sdk/pinocchio/Cargo.toml index 809f5a43..a6cf88fd 100644 --- a/sdk/pinocchio/Cargo.toml +++ b/sdk/pinocchio/Cargo.toml @@ -18,9 +18,10 @@ unexpected_cfgs = { level = "warn", check-cfg = [ ] } [features] +alloc = [] copy = ["solana-account-view/copy", "solana-address/copy"] cpi = ["dep:solana-instruction-view"] -std = ["solana-address/std"] +default = ["alloc"] [dependencies] solana-account-view = { workspace = true } diff --git a/sdk/pinocchio/src/entrypoint/mod.rs b/sdk/pinocchio/src/entrypoint/mod.rs index e998cd29..e116fb0c 100644 --- a/sdk/pinocchio/src/entrypoint/mod.rs +++ b/sdk/pinocchio/src/entrypoint/mod.rs @@ -5,12 +5,8 @@ pub mod lazy; pub use lazy::{InstructionContext, MaybeAccount}; -#[cfg(not(feature = "std"))] -use core::alloc::{GlobalAlloc, Layout}; - -#[cfg(target_os = "solana")] -pub use alloc::BumpAllocator; use core::{ + alloc::{GlobalAlloc, Layout}, cmp::min, mem::{size_of, MaybeUninit}, ptr::with_exposed_provenance_mut, @@ -22,6 +18,9 @@ use crate::{ Address, BPF_ALIGN_OF_U128, MAX_TX_ACCOUNTS, }; +#[cfg(all(target_os = "solana", feature = "alloc"))] +pub use alloc::BumpAllocator; + /// Start address of the memory region used for program heap. pub const HEAP_START_ADDRESS: u64 = 0x300000000; @@ -125,6 +124,7 @@ const STATIC_ACCOUNT_DATA: usize = size_of::() + MAX_PERMITTED_DATA_INC /// manually. /// /// [`crate::nostd_panic_handler`]: https://docs.rs/pinocchio/latest/pinocchio/macro.nostd_panic_handler.html +#[cfg(feature = "alloc")] #[macro_export] macro_rules! entrypoint { ( $process_instruction:expr ) => { @@ -279,9 +279,9 @@ macro_rules! process_accounts { // // Note: The function is marked as `cold` to stop the compiler from optimizing the parsing of // duplicated accounts, which leads to an overall increase in CU consumption. -#[allow(clippy::clone_on_copy)] #[cold] #[inline(always)] +#[allow(clippy::clone_on_copy)] unsafe fn clone_account_info( accounts: *mut AccountView, accounts_slice: *const AccountView, @@ -459,14 +459,12 @@ macro_rules! default_panic_handler { /// This macro sets up a default panic handler that logs the location (file, line and column) where /// the panic occurred and then calls the syscall `abort()`. /// -/// This macro can only be used when all crates are `no_std` and the `"std"` feature is disabled. -#[cfg(not(feature = "std"))] +/// This macro should be used when all crates are `no_std`. #[macro_export] macro_rules! nostd_panic_handler { () => { /// A panic handler for `no_std`. #[cfg(target_os = "solana")] - #[no_mangle] #[panic_handler] fn handler(info: &core::panic::PanicInfo<'_>) -> ! { if let Some(location) = info.location() { @@ -502,6 +500,7 @@ macro_rules! nostd_panic_handler { /// Default global allocator. /// /// This macro sets up a default global allocator that uses a bump allocator to allocate memory. +#[cfg(feature = "alloc")] #[macro_export] macro_rules! default_allocator { () => { @@ -523,17 +522,6 @@ macro_rules! default_allocator { }; } -/// A global allocator that does not allocate memory. -/// -/// Using this macro with the `"std"` feature enabled will result in a compile error. -#[cfg(feature = "std")] -#[macro_export] -macro_rules! no_allocator { - () => { - compile_error!("Feature 'std' cannot be enabled."); - }; -} - /// A global allocator that does not dynamically allocate memory. /// /// This macro sets up a global allocator that denies all dynamic allocations, while allowing static @@ -542,8 +530,7 @@ macro_rules! no_allocator { /// /// The program will panic if it tries to dynamically allocate memory. /// -/// This is used when the `"std"` feature is disabled. -#[cfg(not(feature = "std"))] +/// This is used when the `"alloc"` feature is disabled. #[macro_export] macro_rules! no_allocator { () => { @@ -602,12 +589,25 @@ macro_rules! no_allocator { }; } -#[cfg(target_os = "solana")] -mod alloc { - //! The bump allocator used as the default rust heap when running programs. +/// An allocator that does not allocate memory. +#[cfg_attr(feature = "copy", derive(Copy))] +#[derive(Clone, Debug)] +pub struct NoAllocator; - extern crate alloc; +unsafe impl GlobalAlloc for NoAllocator { + #[inline] + unsafe fn alloc(&self, _: Layout) -> *mut u8 { + panic!("** NoAllocator::alloc() does not allocate memory **"); + } + + #[inline] + unsafe fn dealloc(&self, _: *mut u8, _: Layout) { + // I deny all allocations, so I don't need to free. + } +} +#[cfg(all(target_os = "solana", feature = "alloc"))] +mod alloc { use core::{ alloc::{GlobalAlloc, Layout}, mem::size_of, @@ -653,34 +653,15 @@ mod alloc { } } -#[cfg(not(feature = "std"))] -/// An allocator that does not allocate memory. -#[cfg_attr(feature = "copy", derive(Copy))] -#[derive(Clone, Debug)] -pub struct NoAllocator; - -#[cfg(not(feature = "std"))] -unsafe impl GlobalAlloc for NoAllocator { - #[inline] - unsafe fn alloc(&self, _: Layout) -> *mut u8 { - panic!("** NO ALLOCATOR **"); - } - - #[inline] - unsafe fn dealloc(&self, _: *mut u8, _: Layout) { - // I deny all allocations, so I don't need to free. - } -} - #[cfg(all(test, not(target_os = "solana")))] mod tests { - extern crate std; + extern crate alloc; - use core::{alloc::Layout, ptr::copy_nonoverlapping}; - use std::{ + use alloc::{ alloc::{alloc, dealloc}, vec, }; + use core::{alloc::Layout, ptr::copy_nonoverlapping}; use super::*; @@ -703,7 +684,7 @@ mod tests { unsafe { let ptr = alloc(layout); if ptr.is_null() { - std::alloc::handle_alloc_error(layout); + alloc::alloc::handle_alloc_error(layout); } AlignedMemory { ptr, layout } } diff --git a/sdk/pinocchio/src/lib.rs b/sdk/pinocchio/src/lib.rs index 51c4afec..7aa54f4d 100644 --- a/sdk/pinocchio/src/lib.rs +++ b/sdk/pinocchio/src/lib.rs @@ -166,10 +166,10 @@ //! code from the standard (`std`) library. While this does not affect how Pinocchio //! is used, there is a one particular apparent difference. Helpers that need to //! allocate memory, such as fetching `SlotHashes` sysvar, are not available. To -//! enable these helpers, the `std` feature must be enabled when adding Pinocchio +//! enable these helpers, the `alloc` feature must be enabled when adding Pinocchio //! as a dependency: //! ```ignore -//! pinocchio = { version = "0.10.0", features = ["std"] } +//! pinocchio = { version = "0.10.0", features = ["alloc"] } //! ``` //! //! ## Advanced entrypoint configuration @@ -208,10 +208,10 @@ //! cargo build-sbf --features bpf-entrypoint //! ``` -#![no_std] +#![cfg_attr(not(test), no_std)] -#[cfg(feature = "std")] -extern crate std; +#[cfg(feature = "alloc")] +extern crate alloc; pub mod entrypoint; pub mod sysvars; diff --git a/sdk/pinocchio/src/sysvars/slot_hashes/mod.rs b/sdk/pinocchio/src/sysvars/slot_hashes/mod.rs index a81fa4bd..1f33053f 100644 --- a/sdk/pinocchio/src/sysvars/slot_hashes/mod.rs +++ b/sdk/pinocchio/src/sysvars/slot_hashes/mod.rs @@ -23,8 +23,8 @@ use crate::{ Address, }; -#[cfg(feature = "std")] -use std::boxed::Box; +#[cfg(feature = "alloc")] +use alloc::{boxed::Box, vec::Vec}; /// `SysvarS1otHashes111111111111111111111111111` pub const SLOTHASHES_ID: Address = Address::new_from_array([ @@ -299,7 +299,7 @@ impl<'a> SlotHashes> { } } -#[cfg(feature = "std")] +#[cfg(feature = "alloc")] impl SlotHashes> { /// Fills the provided buffer with the full `SlotHashes` sysvar data. /// @@ -320,7 +320,7 @@ impl SlotHashes> { /// Allocates an optimal buffer for the sysvar data based on available features. #[inline(always)] fn allocate_and_fetch() -> Result, ProgramError> { - let mut buf = std::vec::Vec::with_capacity(MAX_SIZE); + let mut buf = Vec::with_capacity(MAX_SIZE); unsafe { // SAFETY: `buf` was allocated with capacity `MAX_SIZE` so its // pointer is valid for exactly that many bytes. `fill_from_sysvar` diff --git a/sdk/pinocchio/src/sysvars/slot_hashes/test.rs b/sdk/pinocchio/src/sysvars/slot_hashes/test.rs index bb5a9997..d89f7eaa 100644 --- a/sdk/pinocchio/src/sysvars/slot_hashes/test.rs +++ b/sdk/pinocchio/src/sysvars/slot_hashes/test.rs @@ -9,7 +9,6 @@ use core::{ ptr, }; -extern crate std; use std::io::Write; use std::vec::Vec; @@ -460,7 +459,7 @@ fn test_from_account_info_constructor() { /// `SlotHashes` getters to make sure the view itself works. We do not verify /// that the syscall populated real on-chain bytes, as doing so requires an /// environment outside the scope of host `cargo test`. -#[cfg(feature = "std")] +#[cfg(feature = "alloc")] #[test] fn test_fetch_allocates_buffer_host() { const START_SLOT: u64 = 500; diff --git a/sdk/pinocchio/src/sysvars/slot_hashes/test_edge.rs b/sdk/pinocchio/src/sysvars/slot_hashes/test_edge.rs index 069d8c2c..78ebb5f2 100644 --- a/sdk/pinocchio/src/sysvars/slot_hashes/test_edge.rs +++ b/sdk/pinocchio/src/sysvars/slot_hashes/test_edge.rs @@ -1,6 +1,5 @@ -use crate::{error::ProgramError, sysvars::slot_hashes::*}; -extern crate std; use super::test_utils::{build_slot_hashes_bytes as raw_slot_hashes, make_account_info}; +use crate::{error::ProgramError, sysvars::slot_hashes::*}; #[test] fn test_wrong_key_from_account_view() { diff --git a/sdk/pinocchio/src/sysvars/slot_hashes/test_raw.rs b/sdk/pinocchio/src/sysvars/slot_hashes/test_raw.rs index aa13dbf0..33b02ef6 100644 --- a/sdk/pinocchio/src/sysvars/slot_hashes/test_raw.rs +++ b/sdk/pinocchio/src/sysvars/slot_hashes/test_raw.rs @@ -2,7 +2,6 @@ use super::raw; use super::*; -extern crate std; #[test] fn test_validate_buffer_size() { diff --git a/sdk/pinocchio/src/sysvars/slot_hashes/test_utils.rs b/sdk/pinocchio/src/sysvars/slot_hashes/test_utils.rs index 0c2a18d1..97382e4f 100644 --- a/sdk/pinocchio/src/sysvars/slot_hashes/test_utils.rs +++ b/sdk/pinocchio/src/sysvars/slot_hashes/test_utils.rs @@ -3,7 +3,6 @@ //! freely while production code remains `#![no_std]`. use super::*; -extern crate std; use crate::account::{Account, AccountView}; use core::ptr; use std::vec::Vec;