Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/node_modules
/target
.DS_Store
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.
The `middleware_program_entrypoint!` macro defines a dual entrypoint implementation consisting of a `hot` path which bypasses entrypoint deserialization, and a `cold` path which 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 program 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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
middleware_program_entrypoint!(hot,cold);
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:
Expand Down
1 change: 1 addition & 0 deletions sdk/pinocchio/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ unexpected_cfgs = { level = "warn", check-cfg = [
] }

[features]
asm = []
std = []

[dev-dependencies]
Expand Down
248 changes: 248 additions & 0 deletions sdk/pinocchio/src/entrypoint/middleware.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
//! 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 expects 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_program_entrypoint,
/// msg,
/// no_allocator,
/// nostd_panic_handler,
/// pubkey::Pubkey
/// };
///
/// nostd_panic_handler!();
/// no_allocator!();
///
/// middleware_program_entrypoint!(hot,cold);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// middleware_program_entrypoint!(hot,cold);
/// 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(())
/// }
/// ```
#[macro_export]
macro_rules! middleware_program_entrypoint {
($hot:expr, $cold:expr) => {
$crate::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) -> 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(),
}
}
};
}

#[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::<u64>::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
);
}
Comment on lines +157 to +162
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a compile-time check that $value is a u32?

Suggested change
unsafe {
core::arch::asm!(
"mov32 r0, {0}",
in(reg) $value
);
}
unsafe {
let v: u32 = $value;
core::arch::asm!(
"mov32 r0, {0}",
in(reg) v
);
}

};
}

/// # 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"
);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
}
}

2 changes: 2 additions & 0 deletions sdk/pinocchio/src/entrypoint/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pub mod lazy;

pub use lazy::{InstructionContext, MaybeAccount};

pub mod middleware;

#[cfg(not(feature = "std"))]
use core::alloc::{GlobalAlloc, Layout};

Expand Down