From e5f42d0d3a7addd1e084f6feccae4f026f53a8c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20=E5=88=A9=E8=BF=AA=E6=81=A9?= Date: Fri, 5 Sep 2025 18:48:01 +0800 Subject: [PATCH 1/6] feat: middleware entrypoint --- sdk/pinocchio/src/entrypoint/middleware.rs | 35 ++++++++++++++++++++++ sdk/pinocchio/src/entrypoint/mod.rs | 2 ++ 2 files changed, 37 insertions(+) create mode 100644 sdk/pinocchio/src/entrypoint/middleware.rs diff --git a/sdk/pinocchio/src/entrypoint/middleware.rs b/sdk/pinocchio/src/entrypoint/middleware.rs new file mode 100644 index 00000000..b946952c --- /dev/null +++ b/sdk/pinocchio/src/entrypoint/middleware.rs @@ -0,0 +1,35 @@ +//! Defines the middleware entrypoint, enabling a hot path to bypass +//! entrypoint deserialization, ejecting to the cold path on failure. +#[macro_export] +macro_rules! middleware_entrypoint { + ($hot:expr, $cold:expr) => { + $crate::middleware_entrypoint!($hot, $cold, { $crate::MAX_TX_ACCOUNTS }); + }; + ($hot:expr, $cold:expr, $maximum:expr ) => { + + #[no_mangle] + pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 { + if $hot(input) == 0 { + return $crate::SUCCESS + } + + const UNINIT: core::mem::MaybeUninit<$crate::account_info::AccountInfo> = core::mem::MaybeUninit::<$crate::account_info::AccountInfo>::uninit(); + // Create an array of uninitialized account infos. + let mut accounts = [UNINIT; $maximum]; + + let (program_id, count, instruction_data) = unsafe { + $crate::entrypoint::deserialize::<$maximum>(input, &mut accounts) }; + + // Call the program's entrypoint passing `count` account infos; we know that + // they are initialized so we cast the pointer to a slice of `[AccountInfo]`. + match $cold( + &program_id, + unsafe { core::slice::from_raw_parts(accounts.as_ptr() as _, count) }, + &instruction_data, + ) { + Ok(()) => $crate::SUCCESS, + Err(error) => error.into(), + } + } + }; +} \ No newline at end of file diff --git a/sdk/pinocchio/src/entrypoint/mod.rs b/sdk/pinocchio/src/entrypoint/mod.rs index a2e7135b..93a085c9 100644 --- a/sdk/pinocchio/src/entrypoint/mod.rs +++ b/sdk/pinocchio/src/entrypoint/mod.rs @@ -5,6 +5,8 @@ pub mod lazy; pub use lazy::{InstructionContext, MaybeAccount}; +pub mod middleware; + #[cfg(not(feature = "std"))] use core::alloc::{GlobalAlloc, Layout}; From fa62057f117b7ebec2fdf5abfdd9d07044aa89d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20=E5=88=A9=E8=BF=AA=E6=81=A9?= <10921578+deanmlittle@users.noreply.github.com> Date: Fri, 5 Sep 2025 19:21:27 +0800 Subject: [PATCH 2/6] Update sdk/pinocchio/src/entrypoint/middleware.rs Co-authored-by: Fernando Otero --- sdk/pinocchio/src/entrypoint/middleware.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/pinocchio/src/entrypoint/middleware.rs b/sdk/pinocchio/src/entrypoint/middleware.rs index b946952c..b5c3f069 100644 --- a/sdk/pinocchio/src/entrypoint/middleware.rs +++ b/sdk/pinocchio/src/entrypoint/middleware.rs @@ -1,7 +1,7 @@ //! Defines the middleware entrypoint, enabling a hot path to bypass //! entrypoint deserialization, ejecting to the cold path on failure. #[macro_export] -macro_rules! middleware_entrypoint { +macro_rules! middleware_program_entrypoint { ($hot:expr, $cold:expr) => { $crate::middleware_entrypoint!($hot, $cold, { $crate::MAX_TX_ACCOUNTS }); }; From 60379aa9bebca4097dc972035d5c08d163698738 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20=E5=88=A9=E8=BF=AA=E6=81=A9?= <10921578+deanmlittle@users.noreply.github.com> Date: Fri, 5 Sep 2025 19:21:35 +0800 Subject: [PATCH 3/6] Update sdk/pinocchio/src/entrypoint/middleware.rs Co-authored-by: Fernando Otero --- sdk/pinocchio/src/entrypoint/middleware.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/pinocchio/src/entrypoint/middleware.rs b/sdk/pinocchio/src/entrypoint/middleware.rs index b5c3f069..7cfea314 100644 --- a/sdk/pinocchio/src/entrypoint/middleware.rs +++ b/sdk/pinocchio/src/entrypoint/middleware.rs @@ -1,4 +1,4 @@ -//! Defines the middleware entrypoint, enabling a hot path to bypass +//! Defines the middleware program entrypoint, enabling a hot path to bypass //! entrypoint deserialization, ejecting to the cold path on failure. #[macro_export] macro_rules! middleware_program_entrypoint { From 57a94b0e741f697a771d095bf48b5439b568e439 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20=E5=88=A9=E8=BF=AA=E6=81=A9?= <10921578+deanmlittle@users.noreply.github.com> Date: Fri, 5 Sep 2025 20:25:46 +0800 Subject: [PATCH 4/6] Update sdk/pinocchio/src/entrypoint/middleware.rs Co-authored-by: Fernando Otero --- sdk/pinocchio/src/entrypoint/middleware.rs | 59 ++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/sdk/pinocchio/src/entrypoint/middleware.rs b/sdk/pinocchio/src/entrypoint/middleware.rs index 7cfea314..e8389572 100644 --- a/sdk/pinocchio/src/entrypoint/middleware.rs +++ b/sdk/pinocchio/src/entrypoint/middleware.rs @@ -1,5 +1,64 @@ //! Defines the middleware program entrypoint, enabling a hot path to bypass //! entrypoint deserialization, ejecting to the cold path on failure. + +/// Declare the middleware program entrypoint. +/// +/// The macro expect a `hot` and `cold` path expressions. The `hot` is a function with +/// the following type signature: +/// +/// ```ignore +/// fn hot(input: mut *u8) -> u64; +/// ``` +/// The `input` argument represents the program input parameters serialized by the SVM +/// loader. The `cold` is a function with the following type signature: +/// +/// ```ignore +/// fn cold( +/// program_id: &Pubkey, // Public key of the account the program was loaded into +/// accounts: &[AccountInfo], // All accounts required to process the instruction +/// instruction_data: &[u8], // Serialized instruction-specific data +/// ) -> ProgramResult; +/// ``` +/// # Example +/// +/// A middleware program entrypoint where an invocation with zero accounts will lead to +/// the "hot" path, and anything else will fallback to the "cold" path: +/// +/// ```norun +/// #![cfg_attr(target_os = "solana", no_std)] +/// use pinocchio::{ +/// ProgramResult, +/// account_info::AccountInfo, +/// middleware_entrypoint, +/// msg, +/// no_allocator, +/// nostd_panic_handler, +/// pubkey::Pubkey +/// }; +/// +/// nostd_panic_handler!(); +/// no_allocator!(); +/// +/// middleware_entrypoint!(hot,cold); +/// +/// // This uses 4 CUs +/// #[inline(always)] +/// pub fn hot(input: *mut u8) -> u64 { +/// unsafe { *input as u64 } +/// } +/// +/// // This uses 113 CUs +/// #[cold] +/// #[inline(always)] +/// pub fn cold( +/// _program_id: &Pubkey, +/// _accounts: &[AccountInfo], +/// _instruction_data: &[u8], +/// ) -> ProgramResult { +/// msg!("Hello from my Pinocchio!"); +/// Ok(()) +/// } +/// ``` #[macro_export] macro_rules! middleware_program_entrypoint { ($hot:expr, $cold:expr) => { From d3d3c95c1e3daf3cdf40293ee32aa77ecc401334 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20=E5=88=A9=E8=BF=AA=E6=81=A9?= Date: Fri, 5 Sep 2025 20:55:09 +0800 Subject: [PATCH 5/6] documentation and typo fixes --- README.md | 42 ++++++++++++++++++++++ sdk/pinocchio/src/entrypoint/middleware.rs | 10 +++--- 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1f16fb20..352e7ebd 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,48 @@ pub fn process_instruction( > ⚠️ **Note:** > The `no_allocator!` macro can also be used in combination with the `lazy_program_entrypoint!`. +📌 [`middleware_program_entrypoint!`](https://docs.rs/pinocchio/latest/pinocchio/macro.middleware_program_entrypoint.html) + +The `middleware_program_entrypoint!` macro defines a dual entrypoint implementation consisting of a `hot` path which bypasses entrypoint deserialization, and a `cold` path with behaves like a regular pinocchio entrypoint that has undergone `program_entrypoint!` deserialization. This gives the user flexibility to implement extreme CU optimizations against the most critical paths in their proggram with the convenience of being able to fallback to a regular pinocchio program to manage the fail case and other instructions that do not require such optimizations. + +To use the `middleware_program_entrypoint!` macro, use the following in your entrypoint definition: + +```rust +#![cfg_attr(target_os = "solana", no_std)] +use pinocchio::{ + ProgramResult, + account_info::AccountInfo, + middleware_program_entrypoint, + msg, + no_allocator, + nostd_panic_handler, + pubkey::Pubkey +}; + +nostd_panic_handler!(); +no_allocator!(); + +middleware_program_entrypoint!(hot,cold); + +// This uses 4 CUs +#[inline(always)] +pub fn hot(input: *mut u8) -> u64 { + unsafe { *input as u64 } +} + +// This uses 113 CUs +#[cold] +#[inline(always)] +pub fn cold( + _program_id: &Pubkey, + _accounts: &[AccountInfo], + _instruction_data: &[u8], +) -> ProgramResult { + msg!("Hello from Pinocchio!"); + Ok(()) +} +``` + ## Crate feature: `std` 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 one particular apparent difference. In a `no_std` environment, the `msg!` macro does not provide any formatting options since the `format!` macro requires the `std` library. In order to use `msg!` with formatting, the `std` feature should be enabled when adding `pinocchio` as a dependency: diff --git a/sdk/pinocchio/src/entrypoint/middleware.rs b/sdk/pinocchio/src/entrypoint/middleware.rs index e8389572..4b9d6409 100644 --- a/sdk/pinocchio/src/entrypoint/middleware.rs +++ b/sdk/pinocchio/src/entrypoint/middleware.rs @@ -3,7 +3,7 @@ /// Declare the middleware program entrypoint. /// -/// The macro expect a `hot` and `cold` path expressions. The `hot` is a function with +/// The macro expects a `hot` and `cold` path expressions. The `hot` is a function with /// the following type signature: /// /// ```ignore @@ -29,7 +29,7 @@ /// use pinocchio::{ /// ProgramResult, /// account_info::AccountInfo, -/// middleware_entrypoint, +/// middleware_program_entrypoint, /// msg, /// no_allocator, /// nostd_panic_handler, @@ -39,7 +39,7 @@ /// nostd_panic_handler!(); /// no_allocator!(); /// -/// middleware_entrypoint!(hot,cold); +/// middleware_program_entrypoint!(hot,cold); /// /// // This uses 4 CUs /// #[inline(always)] @@ -55,14 +55,14 @@ /// _accounts: &[AccountInfo], /// _instruction_data: &[u8], /// ) -> ProgramResult { -/// msg!("Hello from my Pinocchio!"); +/// msg!("Hello from Pinocchio!"); /// Ok(()) /// } /// ``` #[macro_export] macro_rules! middleware_program_entrypoint { ($hot:expr, $cold:expr) => { - $crate::middleware_entrypoint!($hot, $cold, { $crate::MAX_TX_ACCOUNTS }); + $crate::middleware_program_entrypoint!($hot, $cold, { $crate::MAX_TX_ACCOUNTS }); }; ($hot:expr, $cold:expr, $maximum:expr ) => { From d07ad3b252fafb89a21ead05d09462e82be53bfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dean=20=E5=88=A9=E8=BF=AA=E6=81=A9?= Date: Thu, 11 Sep 2025 14:54:28 +0800 Subject: [PATCH 6/6] Added feature for assembly-level entrypoint optimizations --- .gitignore | 1 + sdk/pinocchio/Cargo.toml | 1 + sdk/pinocchio/src/entrypoint/middleware.rs | 154 +++++++++++++++++++++ 3 files changed, 156 insertions(+) diff --git a/.gitignore b/.gitignore index 050c6035..f497c936 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /node_modules /target +.DS_Store \ No newline at end of file diff --git a/sdk/pinocchio/Cargo.toml b/sdk/pinocchio/Cargo.toml index 854ccda1..a01ac0db 100644 --- a/sdk/pinocchio/Cargo.toml +++ b/sdk/pinocchio/Cargo.toml @@ -18,6 +18,7 @@ unexpected_cfgs = { level = "warn", check-cfg = [ ] } [features] +asm = [] std = [] [dev-dependencies] diff --git a/sdk/pinocchio/src/entrypoint/middleware.rs b/sdk/pinocchio/src/entrypoint/middleware.rs index 4b9d6409..78c69913 100644 --- a/sdk/pinocchio/src/entrypoint/middleware.rs +++ b/sdk/pinocchio/src/entrypoint/middleware.rs @@ -91,4 +91,158 @@ macro_rules! middleware_program_entrypoint { } } }; +} + +#[cfg(feature = "asm")] +#[macro_export] +macro_rules! asm_middleware_program_entrypoint { + ($hot:expr, $cold:expr) => { + $crate::asm_middleware_program_entrypoint!($hot, $cold, { $crate::MAX_TX_ACCOUNTS }); + }; + ($hot:expr, $cold:expr, $maximum:expr ) => { + + #[no_mangle] + pub unsafe extern "C" fn entrypoint(input: *mut u8) { + // Inject `hot()` directly. Assume conditional return is handled within this function. + $hot(input); + const UNINIT: core::mem::MaybeUninit<$crate::account_info::AccountInfo> = core::mem::MaybeUninit::<$crate::account_info::AccountInfo>::uninit(); + // Create an array of uninitialized account infos. + let mut accounts = [UNINIT; $maximum]; + + let (program_id, count, instruction_data) = unsafe { + $crate::entrypoint::deserialize::<$maximum>(input, &mut accounts) }; + + // Call the program's entrypoint passing `count` account infos; we know that + // they are initialized so we cast the pointer to a slice of `[AccountInfo]`. + match $cold( + &program_id, + unsafe { core::slice::from_raw_parts(accounts.as_ptr() as _, count) }, + &instruction_data, + ) { + Ok(()) => { + $crate::set_return_imm!($crate::SUCCESS) + }, + Err(error) => { + $crate::set_return!(Into::::into(error) as u32) + }, + } + } + }; +} + +/// # Set Return +/// Sets the return register `r0` to a stored value. For static return values, consider using `set_return_imm` to save 1 CU by avoiding an additional register allocation. +/// +/// ### CU Cost +/// 1 CU (+1 CU for register allocation) +/// +/// ### ASM +/// `mov32 r0, r1` +/// +/// ### Parameters +/// - `value`: The stored value to set the return register. +/// +/// ### Example +/// ``` +/// let n = 1337; +/// set_return(n); +/// ``` +/// +/// This will assign `1337` to `n`, then set the register `r0` to `n`. +#[cfg(feature = "asm")] +#[macro_export] +macro_rules! set_return { + ($value:expr) => { + #[cfg(target_os = "solana")] + unsafe { + core::arch::asm!( + "mov32 r0, {0}", + in(reg) $value + ); + } + }; +} + +/// # Set Return Register from Immediate +/// Sets the return register `r0` to an immediate value. +/// +/// ### CU Cost +/// 1 CU +/// +/// ### ASM +/// `mov32 r0, r1` +/// +/// ### Parameters +/// - `value`: The stored value to set the return register. +/// +/// ### Example +/// ``` +/// set_return_imm(1337); // Set return value to error code 1 +/// ``` +/// +/// This will assign the return register `r0` to `1337`. +#[cfg(feature = "asm")] +#[macro_export] +macro_rules! set_return_imm { + ($value:expr) => { + #[cfg(target_os = "solana")] + unsafe { + core::arch::asm!( + "mov32 r0, {0}", + const $value + ); + } + }; +} + +/// # Exit +/// Immediately exits the program +/// +/// ### CU Cost +/// 1 CU +/// +/// ### ASM +/// `exit` +/// +/// ### Example +/// ``` +/// exit!(); // Set return value to error code 1 +/// ``` +#[cfg(feature = "asm")] +#[macro_export] +macro_rules! exit { + () => { + #[cfg(target_os = "solana")] + unsafe { + core::arch::asm!( + "exit" + ); + } + } +} + +/// # Reset +/// Resets r1 to the address of the serialized input region +/// +/// ### CU Cost +/// 1 CU +/// +/// ### ASM +/// `lddw r1, 0x400000000` +/// +/// ### Example +/// ``` +/// reset!(); // Set r1 to address of serialized input region +/// ``` +#[cfg(feature = "asm")] +#[macro_export] +macro_rules! reset { + () => { + #[cfg(target_os = "solana")] + unsafe { + core::arch::asm!( + "lddw r1, 0x400000000" + ); + } + } } \ No newline at end of file