# Écrire un programme invité

> Un programme invité est un binaire Rust no_std doté d’un point d’entrée et de trois régions mémoire. L’organisation de la crate, l’environnement d’exécution sous votre code, les dépendances, et l’organisation qui permet de le tester d’abord sur l’hôte, comme n’importe quel code Rust.

## La crate

Dans la v1.0.0, un programme invité est une crate binaire de l’espace de travail `guests/` du dépôt. L’espace de travail fournit la cible, les options de l’éditeur de liens, les profils fixés et les crates embarquées, si bien que le manifeste propre au programme invité reste court :

```toml title="guests/my-app/Cargo.toml"
[package]
name = "my-app"
version.workspace = true
edition.workspace = true
publish.workspace = true

[dependencies]
guest-sdk.workspace = true
```

```rust title="guests/my-app/src/main.rs"
#![no_std]
#![no_main]

extern crate alloc; // Vec, Box, String, BTreeMap, over the SDK's allocator

use alloc::vec::Vec;

guest_sdk::entry!(main);

fn main() {
    let input = guest_sdk::public_input();
    let mut out = Vec::with_capacity(input.len());
    out.extend(input.iter().rev());
    guest_sdk::commit(&out);
}
```

Ajoutez `"my-app"` à `members` dans `guests/Cargo.toml`, et compilez à partir du répertoire du programme invité lui-même : `cargo build --release --target riscv32imac-unknown-none-elf`.

## Ce qui s’exécute sous votre code

Le SDK des programmes invités constitue tout l’environnement d’exécution. Il est assez petit pour être décrit en entier :

- **Démarrage.** `_start` se trouve à `0x0001_0000`, le premier octet de `.text`. Il fait pointer `sp` vers le haut de la RAM, met `.bss` à zéro octet par octet, et appelle `main`. `entry!(f)` exporte ce `main` comme une enveloppe autour de votre fonction, qui ne prend aucun argument et renvoie `()`.
- **Sortie.** Le retour de `main` équivaut à `exit(0)`. `guest_sdk::exit(code)` termine l’exécution avec un statut quelconque. Un statut non nul signale une exécution en échec, et une exécution en échec reste prouvable : l’énoncé porte le statut, et un vérificateur le lit.
- **Panique.** Le gestionnaire de panique termine l’exécution avec le statut **101** et n’écrit rien. Il n’y a aucun flux de diagnostic. Un programme invité qui panique a tout de même publié ce qu’il avait consigné avant la panique.
- **Tas.** Un allocateur linéaire (*bump allocator*) croît vers le haut à partir de `__heap_start`, juste au-dessus de `.bss`. Il ne libère jamais rien. Voir [le tas](#heap).
- **Appels système.** Les seuls ecalls qu’émet un programme invité sont `EXIT` et les appels de délégation que le SDK effectue pour vous. L’entrée, les données auxiliaires (*advice*) et la sortie sont de la mémoire, lue et écrite par des chargements et des rangements ordinaires.

## La carte mémoire

L’espace d’adressage complet de 32 bits, tel que le voit un programme invité :

| Plage | Ce que c’est |
| --- | --- |
| `0x0000_0000 – 0x0000_8000` | Un trou. Rien ne l’initialise, si bien qu’un pointeur nul ou sauvage provoque un `OutOfBounds` fatal, et non une lecture silencieuse |
| `0x0000_8000 – 0x0000_C000` | La fenêtre de l’entrée publique, 16 KiB |
| `0x0000_C000 – 0x0001_0000` | La fenêtre du journal, 16 KiB |
| `0x0001_0000 – …` | `.text` (avec `_start` en premier), puis `.rodata`, `.data` et `.bss`, chacun aligné sur une page |
| `__heap_start` et au-dessus | Le tas, à partir de la fin de `.bss` arrondie au multiple de 16 supérieur |
| `0x7F80_0000 – 0x8000_0000` | La réserve de 8 MiB de la pile. Aucun bloc du tas ne peut se terminer au-dessus de `0x7F80_0000`; la pile croît vers le bas à partir de `0x8000_0000` |
| `0x8000_0000 – 2^32` | La région des données auxiliaires, jusqu’à `2^29` mots, adressable seulement sur l’étendue que l’hôte a fournie |

Le code est statique. L’instruction de chaque pc provient des tables décodées du programme, jamais de la RAM, si bien qu’un rangement dans `.text` change ce que lit un chargement ultérieur, mais pas ce qui s’exécute.

## Le tas

L’allocateur fait avancer un pointeur, et `dealloc` ne fait rien. C’est la bonne conception pour un programme court dont chaque cycle coûte du temps de preuve, et cela change votre façon d’écrire du Rust :

- **Ce qui épuise votre mémoire, c’est le total que vous allouez, et non votre pic.** Une boucle qui construit puis détruit un `Vec` à chaque itération consomme du tas neuf à chaque fois.
- **Réutilisez les tampons.** Sortez les allocations des boucles, appelez `clear()` puis remplissez à nouveau au lieu de réallouer, et dimensionnez avec `with_capacity` les collections qui grandissent, pour qu’elles ne soient pas réallouées et recopiées à mesure qu’elles grandissent.
- **Le plafond, c’est le statut 71.** Une allocation qui se terminerait au-dessus de `0x7F80_0000`, ou au-dessus du pointeur de pile courant, termine l’exécution avec le statut 71 plutôt que de renvoyer un pointeur nul ou d’écraser la pile.

```rust
// Allocates a fresh Vec per record: total heap grows with the record count.
for record in records {
    let fields: Vec<&[u8]> = record.split(|b| *b == b',').collect();
    handle(&fields);
}

// One buffer, reused: total heap is the largest record's field count.
let mut fields: Vec<&[u8]> = Vec::with_capacity(16);
for record in records {
    fields.clear();
    fields.extend(record.split(|b| *b == b','));
    handle(&fields);
}
```

La pile dispose de sa réserve de 8 MiB, et une récursion profonde à l’intérieur de celle-ci ne pose aucun problème. Ce que rien ne détecte, c’est une pile qui dépasse sa réserve après que le tas a rempli l’espace situé sous elle : des blocs du tas changeraient alors sous une chaîne d’appels profonde. Gardez la récursion bornée, ou rendez-la itérative.

## Dépendances

Toute crate qui se compile pour `riscv32imac-unknown-none-elf` sans `std` fera l’affaire. En pratique :

- Désactivez les fonctionnalités par défaut (`default-features = false`) et activez `alloc` là où une crate le propose.
- Une crate qui entraîne `getrandom`, une horloge ou `std::collections::HashMap` avec sa graine aléatoire n’a aucune source où puiser. Un tel appel répond `-ENOSYS` et rend l’exécution non prouvable. Préférez `BTreeMap`, ou une table de hachage dotée d’une fonction de hachage fixe et déterministe.
- La virgule flottante se compile en routines logicielles sur entiers, parce que la cible n’a pas d’extension F ni D. Elle est correcte et déterministe, et coûte de nombreuses instructions par opération. L’arithmétique entière ou en virgule fixe coûte moins cher.
- Le hachage et l’arithmétique sur courbes elliptiques ont des circuits dédiés. Utilisez les fonctions du SDK ou les crates embarquées pour que vos dépendances les atteignent : [Délégations](https://apogee.gweb3networks.com/docs/launch/delegations).

## Le tester d’abord sur l’hôte

Un programme invité n’affiche rien : le débogage se fait donc sur l’hôte. L’organisation qui facilite cela place le programme dans une bibliothèque `#![no_std]` qui transforme des octets en octets, réduit `main.rs` au transfert des octets vers les régions et depuis celles-ci, et fait du SDK une dépendance de la seule cible des programmes invités :

```toml title="guests/my-app/Cargo.toml"
[package]
name = "my-app"
version.workspace = true
edition.workspace = true
publish.workspace = true

[target.'cfg(target_arch = "riscv32")'.dependencies]
guest-sdk.workspace = true
```

```rust title="guests/my-app/src/lib.rs"
#![no_std]
extern crate alloc;
use alloc::vec::Vec;

/// The whole application: public input and advice in, journal out.
pub fn run(input: &[u8], advice: &[u8]) -> Result<Vec<u8>, i32> {
    let _ = advice;
    let mut out = Vec::with_capacity(input.len());
    out.extend(input.iter().rev());
    Ok(out)
}
```

```rust title="guests/my-app/src/main.rs"
#![no_std]
#![no_main]

guest_sdk::entry!(main);

fn main() {
    match my_app::run(guest_sdk::public_input(), &[]) {
        Ok(journal) => guest_sdk::commit(&journal),
        Err(code) => guest_sdk::exit(code),
    }
}
```

Le code hôte dépend alors de la bibliothèque par son chemin, comme `crates/emulator` dépend de `guests/revm-block`, exécute `my_app::run` en natif, et compare le résultat au journal que produit l’émulateur pour la même entrée ([Exécuter et profiler](https://apogee.gweb3networks.com/docs/launch/run#emulator)). Votre logique bénéficie de tests unitaires, d’un débogueur et de `println!` sur l’hôte, et le binaire du programme invité reste une mince enveloppe autour d’un code que vous avez déjà testé.

> [!WARNING]
> **Les deux compilations ne s’entendent pas sur `usize`.** Dans le programme invité, `usize` et tous les pointeurs font 32 bits; sur votre hôte, ils en font 64. Un dépassement de `usize` provoque une panique dans le programme invité seulement, `x as usize` y tronque sans rien signaler, et `size_of` et `core::hash` de tout ce qui contient une longueur diffèrent entre les deux. Tenez `usize` à l’écart de tout ce que vous consignez, hachez ou sérialisez, et utilisez explicitement `u32` et `u64` à ces frontières.

## Assembleur et jeu d’instructions

Le décodeur accepte exactement les 59 instructions de RV32IMA, ainsi que les instructions compressées (C), qui sont développées au chargement. L’assembleur en ligne est permis à l’intérieur de cet ensemble. Tout ce qui en sort, comme un accès à un CSR, `fence.i`, un encodage à virgule flottante ou RV64, empêche l’enregistrement du programme entier, même si cette instruction n’est jamais atteinte : la dérivation signale `Not all opcodes supported: pc=…`. Un `ebreak`, un saut vers un demi-mot sans instruction, ou un accès non aligné à un demi-mot ou à un mot termine l’exécution sans preuve.

Les instructions atomiques se décodent et se prouvent, avec un seul écart : `sc.w` réussit toujours, parce que la machine ne conserve aucun état de réservation. Le [guide de programmation des programmes invités](https://apogee.gweb3networks.com/docs/launch/guide#atomics) explique pourquoi le code d’un nouveau programme invité ne devrait pas utiliser d’opérations atomiques du tout.

## Pour la suite

- [Entrées, données auxiliaires et journal](https://apogee.gweb3networks.com/docs/launch/io): Les trois régions et ce que lie la preuve.

- [Délégations](https://apogee.gweb3networks.com/docs/launch/delegations): Le hachage et l’arithmétique de courbe pour une fraction du coût.

- [Compiler et inspecter](https://apogee.gweb3networks.com/docs/launch/build): Les profils, l’image, son rapport et son identité.
