Exemple de programme
Voyons maintenant comment les registres (r0-r10), les régions mémoire et les instructions fonctionnent ensemble dans un programme réel. Nous commencerons par le programme sBPF le plus simple possible pour comprendre le flux d'exécution fondamental.
NoOp-program
Un programme "NoOp" (No Operation) est parfait pour l'apprentissage car il démontre la structure essentielle du programme sans aucune complexité :
Comment les programmes reçoivent des entrées (dans le registre r1)
Comment ils renvoient des résultats (dans le registre r0)
Le flux d'entrée/sortie de base que suit chaque programme sBPF
Comment Rust se compile en assembleur sBPF
Même s'il ne "fait rien", il vous montre la base sur laquelle chaque programme Solana est construit.
Maintenant que nous connaissons les opérations sBPF de base, voyons à quoi elles ressemblent dans un vrai programme (même minuscule).
Pinocchio NoOp
Ci-dessous se trouve un "noop" haute performance écrit avec Pinocchio. Tout ce qu'il fait est de retourner un succès :
#![no_std]
use pinocchio::{entrypoint::InstructionContext, lazy_program_entrypoint, no_allocator, nostd_panic_handler, ProgramResult};
lazy_program_entrypoint!(process_instruction);
nostd_panic_handler!();
no_allocator!();
fn process_instruction(
_context: InstructionContext, // wrapper around the input buffer
) -> ProgramResult {
Ok(())
}Si nous compilons ce code avec cargo build-sbf --dump, nous obtiendrons un dump ELF qui nous donne des informations sur notre binaire dans le répertoire /target/deploy/.
Nous voudrons ensuite chercher la section .text — la partie de notre binaire où le code exécutable est stocké.
Disassembly of section .text
0000000000000120 <entrypoint>
120 b7 00 00 00 00 00 00 00 mov64 r0, 0x0
128 95 00 00 00 00 00 00 00 exitDécomposons ce que ces octets hexadécimaux signifient réellement en utilisant le format d'instruction que nous avons appris :
Voici la première instruction : 120 b7 00 00 00 00 00 00 00
Adresse :
0x120(dans la région de texte commençant à 0x100000000)Opcode :
0xb7=mov64avec valeur immédiatedst :
0x00= registrer0src :
0x00= non utilisé (opération immédiate)offset :
0x0000= non utiliséimm :
0x00000000= valeur immédiate 0
Et voici la seconde instruction : 128 95 00 00 00 00 00 00 00
Adresse :
0x128(8 octets après la première instruction)Code opération :
0x95= sortie/retourTous les autres champs :
0x00= non utilisés pour la sortie
Assembly NoOp
Si nous désassemblions le binaire pour le reconvertir en code assembleur sBPF compilable, le code ressemblerait à ceci :
.globl entrypoint
entrypoint:
mov64 r0, 0x00 // r0 <- success
exit // finish, return r0Analysons ce code :
.globl entrypoint: c'est une directive d'assemblage qui indique à l'éditeur de liens de rendre le symbole du point d'entrée visible globalement. L'environnement d'exécution Solana recherche ce symbole pour savoir où commencer l'exécution de votre programme.entrypoint: c'est une étiquette qui marque l'adresse mémoire où l'exécution du programme commence. Lorsque l'environnement d'exécution charge votre programme, il saute à cette adresse.mov64 r0, 0x00: déplace la valeur immédiate 0 dans le registre r0. Comme r0 est le registre de retour, cela définit un code de retour de succès.exit: termine l'exécution du programme et renvoie la valeur de r0 à l'environnement d'exécution.
C'est un programme extrêmement petit, avec seulement 2 instructions consommant uniquement 2 unités de calcul (CU) à exécuter, qui correspond parfaitement à notre code Rust :
Nous avons défini une fonction de point d'entrée
Nous retournons Ok(()) qui s'évalue à 0
Le compilateur a généré les instructions
mov64etexitappropriées
NoOp d'assemblage optimisé
Cependant, nous pouvons optimiser davantage. Puisque l'environnement d'exécution de Solana initialise r0 à 0 par défaut, nous pouvons éliminer l'instruction redondante mov64 :
.globl entrypoint
entrypoint:
exitCette version optimisée :
Coûte seulement 1 unité de calcul (réduction de 50% !)
Produit des résultats identiques (
r0contient toujours 0)
Cette optimisation est possible car nous connaissons les états initiaux des registres — un avantage dont le compilateur Rust ne tire pas toujours parti. Comprendre l'assemblage sBPF vous permet d'identifier et d'éliminer de telles inefficacités dans le code critique en termes de performance.