Skip to content

Implementation of feat109: add a trait to serialize instruction data #131

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

Chazzzzzzz
Copy link

@Chazzzzzzz Chazzzzzzz commented Apr 13, 2025

This implements #109 for Token program

If things look good I will implement this for all other programs.

I am not using Deref because it requires to implement Deref for all Struct like ApproveChecked/Burn/FreezeAccount

"pub trait InstructionData: Deref<Target = [u8]> {}"

@Chazzzzzzz Chazzzzzzz changed the title Chaz/feat109 Implementation of feat109 Apr 13, 2025
@Chazzzzzzz Chazzzzzzz changed the title Implementation of feat109 Implementation of feat109: add a trait to serialize instruction data Apr 13, 2025
// Instruction data layout:
// - [0]: instruction discriminator (1 byte, u8)
// - [1..9]: amount (8 bytes, u64)
let mut instruction_data = Box::new([UNINIT_BYTE; 9]);
Copy link
Collaborator

Choose a reason for hiding this comment

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

We need to avoid using a Box here since it will be expensive. You can use: core::mem::MaybeUninit<[u8; 9]>.

Copy link
Contributor

Choose a reason for hiding this comment

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

there will be a lifetime issue using stack alone. one way to do this is by specifying this function #[inline(always)] so that the variable will always be on the same stack with the caller. but this is too hacky wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

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

assuming using the bump allocator, allocating a fixed size may only increment the free pointer, like 2 ebpf instructions?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Based on some quick tests that I did, using a Box is a bit more expensive (#126) than just the pointer increment.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Copy link
Contributor

@publicqi publicqi Apr 15, 2025

Choose a reason for hiding this comment

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

assuming using the bump allocator, allocating a fixed size may only increment the free pointer, like 2 ebpf instructions?

this is interesting. one overhead in the example would be zero initialization. i cannot think of any other operation a simple alloc would do. can verify this later.

update: confirmed. beside zero initialization, alignment stuff and pointer update, the alloc crate itself emits tons of rust error unwinding like we had in #85. using alloc crate should be discouraged if we want max performance.

Copy link
Author

@Chazzzzzzz Chazzzzzzz Apr 16, 2025

Choose a reason for hiding this comment

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

We can use a similar approach than the ReturnData: https://github.com/anza-xyz/pinocchio/blob/main/sdk/pinocchio/src/cpi.rs#L316-L325

In the example, seems only get_return_data generates the data, are you suggesting that we do not parse pointer around, instead just return [u8] back?

If we want to parse pointer around, the data has to live on the heap somewhere.

@febo

// Set amount as u64 at offset [1..9]
write_bytes(&mut instruction_data[1..], &self.amount.to_le_bytes());

unsafe { from_raw_parts(instruction_data.as_ptr() as _, 9) }
Copy link
Collaborator

@febo febo Apr 15, 2025

Choose a reason for hiding this comment

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

Then here you can have unsafe { instruction_data.assume_init() }.

@@ -15,3 +15,7 @@ fn write_bytes(destination: &mut [MaybeUninit<u8>], source: &[u8]) {
d.write(*s);
}
}

pub trait InstructionData {
fn get_instruction_data(&self) -> &[u8];
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe we can name this get.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants