Приклад програми
Тепер давайте подивимося, як регістри (r0-r10), області пам'яті та інструкції працюють разом у реальній програмі. Ми почнемо з найпростішої можливої програми sBPF, щоб зрозуміти фундаментальний процес виконання.
NoOp-program
Програма "NoOp" (No Operation, без операцій) ідеально підходить для навчання, оскільки демонструє основну структуру програми без зайвої складності:
- Як програми отримують вхідні дані (у регістрі r1)
- Як вони повертають результати (у регістрі r0)
- Базовий потік входу/виходу, якому слідує кожна програма sBPF
- Як Rust компілюється в асемблер sBPF
Хоча вона "нічого не робить", вона показує вам основу, на якій будується кожна програма Solana.
Тепер, коли ми знаємо основні операції sBPF, давайте подивимося, як вони виглядають у реальній (навіть якщо крихітній) програмі.
Pinocchio NoOp
Нижче наведено високопродуктивний "noop", написаний за допомогою Pinocchio. Все, що він робить, це повертає успішний результат:
#![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(())
}
Якщо ми скомпілюємо цей код за допомогою cargo build-sbf --dump
, ми отримаємо дамп ELF, який надасть нам інформацію про наш бінарний файл у директорії /target/deploy/
.
Потім ми захочемо знайти секцію .text
— частину нашого бінарного файлу, де зберігається виконуваний код.
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 exit
Давайте розберемо, що насправді означають ці шістнадцяткові байти, використовуючи формат інструкцій, який ми вивчили:
Це перша інструкція: 120 b7 00 00 00 00 00 00 00
- Адреса:
0x120
(в текстовій області, що починається з 0x100000000) - Опкод:
0xb7
=mov64
з безпосереднім значенням - dst:
0x00
= регістрr0
- src:
0x00
= не використовується (операція з безпосереднім значенням) - offset:
0x0000
= не використовується - imm:
0x00000000
= безпосереднє значення 0
І це друга інструкція: 128 95 00 00 00 00 00 00 00
- Адреса:
0x128
(8 байтів після першої інструкції) - Опкод:
0x95
= вихід/повернення - Усі інші поля:
0x00
= не використовуються для виходу
Assembly NoOp
Якщо ми дизасемблюємо бінарний файл, щоб перетворити його назад у компільований sBPF Assembly, код виглядатиме так:
.globl entrypoint
entrypoint:
mov64 r0, 0x00 // r0 <- success
exit // finish, return r0
Розберемо код:
.globl entrypoint
: Це директива асемблера, яка вказує компонувальнику зробити символ точки входу глобально видимим. Середовище виконання Solana шукає цей символ, щоб знати, з якого місця почати виконання вашої програми.entrypoint
: Це мітка, яка позначає адресу пам'яті, з якої починається виконання програми. Коли середовище виконання завантажує вашу програму, воно переходить на цю адресу.mov64 r0, 0x00
: Переміщення безпосереднього значення 0 в регістр r0. Оскільки r0 є регістром повернення, це встановлює код успішного повернення.exit
: Завершення виконання програми та повернення значення з r0 до середовища виконання.
Це надзвичайно маленька програма, з лише 2 інструкціями, які споживають тільки 2 обчислювальні одиниці (CU) для виконання, що ідеально відповідає нашому коду Rust:
- Ми визначили функцію точки входу
- Ми повертаємо Ok(()) що обчислюється як 0
- Компілятор згенерував відповідні інструкції
mov64
таexit
Оптимізована збірка NoOp
Однак ми можемо оптимізувати це ще більше. Оскільки середовище виконання Solana ініціалізує r0
значенням 0 за замовчуванням, ми можемо усунути надлишкову інструкцію mov64
:
.globl entrypoint
entrypoint:
exit
Ця оптимізована версія:
- Коштує лише 1 обчислювальну одиницю (зменшення на 50%!)
- Дає ідентичні результати (
r0
все ще містить 0)
Ця оптимізація можлива, оскільки ми знаємо початкові стани регістрів — щось, чим компілятор Rust не завжди користується. Розуміння асемблера sBPF дозволяє виявляти та усувати такі неефективності в критичному для продуктивності коді.