#low-level #macro #asm #register #optimization #sbpf

sbpf-asm-macros

Ergonomic macros for low-level sBPF ASM functionality

2 releases

0.1.1 Aug 13, 2024
0.1.0 Aug 13, 2024

#542 in Rust patterns

Download history 99/week @ 2024-08-07 120/week @ 2024-08-14

219 downloads per month

MIT license

7KB

sbpf-asm-macros

A series of macros to enable optimal low-level sBPF functionality such as directly setting register values which would otherwise be inaccessible.

Usage

These macros enable you to directly set registers, and may also provide other low-level functionality in the future to enable extreme optimisation down to the bytecode level. Below is one such example.

Near-optimal bytecode

A simple example of close-to-optimal Rust program that checks the balance of a token account against an amount in instruction data would be:

#[no_mangle]
pub unsafe extern "C" fn entrypoint(ptr: *mut u8) -> u64 {
    let ix_balance = (ptr as *const u8).add(0x2918) as *const u64;
    let account_balance = (ptr as *const u8).add(0x00a0) as *const u64;
    if *ix_balance > *account_balance {
        return 1
    }
    0
}

This produces the following ASM:

ldxdw r2, [r1+160]    ; 1CU
ldxdw r1, [r1+10520]  ; 1CU
mov64 r0, 1           ; 1CU
jgt r1, r2, +1        ; 1CU
mov64 r0, 0           ; 1CU
exit                  ; 1CU

CU Costs: Success: 6 CUs Failure: 5 CUs

Optimal bytecode

As SVM initializes its return register 0 with the value of 0, it is possible to save compute units by removing the return signature of the entrypoint and anticipating the value of the return register to be 0 rather than explicitly setting it. With sbpf-asm-macros, we are able to ergonomically access the return register to make such optimizations:

#![cfg_attr(target_os = "solana", feature(asm_experimental_arch, asm_const))]
use sbpf_asm_macros::set_return_imm;

#[no_mangle]
pub unsafe extern "C" fn entrypoint(ptr: *mut u8) {
    let ix_balance = (ptr as *const u8).add(0x2918) as *const u64;
    let account_balance = (ptr as *const u8).add(0x00a0) as *const u64;
    if *ix_balance > *account_balance {
        set_return_imm!(1);
    }
}

This produces the following ASM:

ldxdw r2, [r1+10520] ; 1CU
ldxdw r1, [r1+160]   ; 1CU
jge r1, r2, +2       ; 1CU
lddw r0, 1           ; 1CU
exit                 ; 1CU

CU Costs: Success: 4 CUs Failure: 5 CUs

As you can see, ASM unlocks some very low-level optimizations that would otherwise be unreachable.

No runtime deps