# Ein Gastprogramm schreiben

> Ein Gastprogramm ist ein no_std-Rust-Binary mit einem Einsprungpunkt und drei Speicherbereichen. Crate-Aufbau, die Laufzeitumgebung darunter, Abhängigkeiten und der Aufbau, mit dem Sie es zuerst auf dem Host testen wie jedes andere Rust.

## Das Crate

In v1.0.0 ist ein Gastprogramm (Guest) ein Binary-Crate im Workspace `guests/` des Repositorys. Der Workspace liefert das Target, die Linker-Flags, die festgelegten Profile und die mitgelieferten Crates; das eigene Manifest eines Gastprogramms bleibt also kurz:

```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);
}
```

Tragen Sie `"my-app"` in `members` in `guests/Cargo.toml` ein und bauen Sie aus dem eigenen Verzeichnis des Gastprogramms: `cargo build --release --target riscv32imac-unknown-none-elf`.

## Was unter Ihnen läuft

Das Guest-SDK ist die gesamte Laufzeitumgebung. Es ist klein genug, um vollständig beschrieben zu werden:

- **Start.** `_start` liegt bei `0x0001_0000`, dem ersten Byte von `.text`. Es setzt `sp` auf das obere Ende des RAM, füllt `.bss` Byte für Byte mit Nullen und ruft `main` auf. `entry!(f)` exportiert dieses `main` als Wrapper um Ihre Funktion, die keine Argumente nimmt und `()` zurückgibt.
- **Exit.** Eine Rückkehr aus `main` ist gleichbedeutend mit `exit(0)`. `guest_sdk::exit(code)` beendet den Lauf mit einem beliebigen Status. Ein Status ungleich null ist eine fehlgeschlagene Ausführung, und auch eine fehlgeschlagene Ausführung ist beweisbar: Die Aussage enthält den Status, und ein Verifier liest ihn.
- **Panic.** Der Panic-Handler beendet den Lauf mit Status **101** und schreibt nichts. Es gibt keinen Diagnosekanal. Ein Gastprogramm, das einen Panic auslöst, hat trotzdem alles veröffentlicht, was es vor dem Panic festgeschrieben hat.
- **Heap.** Ein Bump-Allokator wächst ab `__heap_start`, direkt oberhalb von `.bss`, nach oben. Er gibt nie Speicher frei. Siehe [den Heap](#heap).
- **Systemaufrufe.** Die einzigen ecalls, die ein Gastprogramm absetzt, sind `EXIT` und die Delegationsaufrufe, die das SDK für Sie absetzt. Eingabe, Hilfsdaten (Advice) und Ausgabe sind Speicher, gelesen und geschrieben mit gewöhnlichen Lade- und Speicherbefehlen.

## Die Speicherkarte

Der gesamte 32-Bit-Adressraum, wie ein Gastprogramm ihn sieht:

| Bereich | Was es ist |
| --- | --- |
| `0x0000_0000 – 0x0000_8000` | Ein Loch. Nichts initialisiert es; ein Null- oder wilder Zeiger ist also ein fataler Fehler `OutOfBounds`, kein stiller Lesezugriff |
| `0x0000_8000 – 0x0000_C000` | Das Fenster der öffentlichen Eingabe, 16 KiB |
| `0x0000_C000 – 0x0001_0000` | Das Journal-Fenster, 16 KiB |
| `0x0001_0000 – …` | `.text` (mit `_start` am Anfang), dann `.rodata`, `.data` und `.bss`, jeweils an einer Seitengrenze ausgerichtet |
| `__heap_start` aufwärts | Der Heap, ab dem Ende von `.bss`, aufgerundet auf ein Vielfaches von 16 |
| `0x7F80_0000 – 0x8000_0000` | Die Reserve von 8 MiB für den Stack. Kein Heap-Block darf oberhalb von `0x7F80_0000` enden; der Stack wächst ab `0x8000_0000` nach unten |
| `0x8000_0000 – 2^32` | Der Bereich der Hilfsdaten, bis zu `2^29` Wörter, nur so weit adressierbar, wie der Host Daten geliefert hat |

Code ist statisch. Der Befehl jedes pc stammt aus den dekodierten Tabellen des Programms, nie aus dem RAM; ein Schreibzugriff auf `.text` ändert also, was ein späterer Lesezugriff liest, aber nicht, was ausgeführt wird.

## Der Heap

Der Allokator schiebt einen Zeiger weiter, und `dealloc` tut nichts. Das ist das richtige Design für ein kurzes Programm, bei dem jeder Zyklus Beweiszeit kostet, und es verändert, wie Sie Rust schreiben:

- **Was Ihnen den Speicher ausgehen lässt, ist die Gesamtmenge Ihrer Allokationen, nicht Ihr Spitzenbedarf.** Eine Schleife, die in jeder Iteration einen `Vec` aufbaut und verwirft, verbraucht jedes Mal frischen Heap.
- **Verwenden Sie Puffer wieder.** Ziehen Sie Allokationen aus Schleifen heraus, leeren Sie mit `clear()` und füllen Sie neu, statt neu zu allozieren, und dimensionieren Sie wachsende Collections mit `with_capacity`, damit sie beim Wachsen nicht neu allozieren und kopieren.
- **Die Obergrenze ist Exit 71.** Eine Allokation, die oberhalb von `0x7F80_0000` oder oberhalb des aktuellen Stack-Pointers enden würde, beendet den Lauf mit Status 71, statt null zurückzugeben oder den Stack zu überschreiben.

```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);
}
```

Der Stack hat seine Reserve von 8 MiB, und tiefe Rekursion innerhalb davon ist unproblematisch. Was nichts erkennt, ist ein Stack, der über die Reserve hinauswächst, nachdem der Heap den Raum darunter gefüllt hat: Heap-Blöcke würden sich dann unter einer tiefen Aufrufkette verändern. Halten Sie Rekursion beschränkt, oder formulieren Sie sie iterativ.

## Abhängigkeiten

Jedes Crate, das ohne `std` für `riscv32imac-unknown-none-elf` baut, ist geeignet. In der Praxis:

- Schalten Sie die Default-Features ab (`default-features = false`) und aktivieren Sie `alloc`, wo ein Crate es anbietet.
- Ein Crate, das `getrandom`, eine Uhr oder `std::collections::HashMap` mit seinem zufälligen Seed einbindet, hat keine Quelle, aus der es schöpfen kann. Ein solcher Aufruf erhält `-ENOSYS` als Antwort und macht den Lauf unbeweisbar. Bevorzugen Sie `BTreeMap` oder eine Hash-Map mit einem festen, deterministischen Hasher.
- Gleitkommaarithmetik wird zu Ganzzahl-Softwareroutinen kompiliert, weil das Target keine F- oder D-Erweiterung hat. Sie ist korrekt und deterministisch und kostet viele Befehle pro Operation. Ganzzahl- oder Festkommaarithmetik ist günstiger.
- Hashing und Arithmetik auf elliptischen Kurven haben eigene Schaltkreise. Verwenden Sie die Funktionen des SDK oder die mitgelieferten Crates, damit Ihre Abhängigkeiten sie erreichen: [Delegationen](https://apogee.gweb3networks.com/docs/launch/delegations).

## Zuerst auf dem Host testen

Ein Gastprogramm gibt nichts aus; das Debugging findet also auf dem Host statt. Der Aufbau, der das einfach macht, hält das Programm in einer `#![no_std]`-Bibliothek, die Bytes auf Bytes abbildet, beschränkt `main.rs` darauf, Bytes in die Speicherbereiche hinein und aus ihnen heraus zu bewegen, und macht das SDK zu einer Abhängigkeit allein des Gastprogramm-Targets:

```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),
    }
}
```

Host-Code hängt dann per Pfad von der Bibliothek ab, so wie `crates/emulator` von `guests/revm-block` abhängt, führt `my_app::run` nativ aus und vergleicht das Ergebnis mit dem Journal, das der Emulator für dieselbe Eingabe erzeugt ([Ausführen und profilieren](https://apogee.gweb3networks.com/docs/launch/run#emulator)). Ihre Logik bekommt auf dem Host Unit-Tests, einen Debugger und `println!`, und das Binary des Gastprogramms bleibt eine dünne Hülle um Code, den Sie bereits getestet haben.

> [!WARNING]
> **Die beiden Builds sind sich über `usize` uneinig.** Im Gastprogramm sind `usize` und jeder Zeiger 32 Bit breit, auf Ihrem Host 64. Ein Überlauf eines `usize` löst nur im Gastprogramm einen Panic aus, `x as usize` schneidet dort stillschweigend ab, und `size_of` und `core::hash` von allem, was eine Länge enthält, unterscheiden sich zwischen beiden. Halten Sie `usize` aus allem heraus, was Sie festschreiben, hashen oder serialisieren, und verwenden Sie an diesen Grenzen explizit `u32` und `u64`.

## Assembly und der Befehlssatz

Der Decoder akzeptiert genau die 59 Befehle von RV32IMA sowie komprimierte Befehle (C), die beim Laden expandiert werden. Inline-Assembly innerhalb dieser Menge ist unproblematisch. Alles außerhalb davon, etwa ein CSR-Zugriff, `fence.i`, eine Gleitkomma- oder eine RV64-Kodierung, führt dazu, dass sich das gesamte Programm nicht registrieren lässt, selbst wenn die Stelle nie erreicht wird: Die Ableitung meldet `Not all opcodes supported: pc=…`. Ein `ebreak`, ein Sprung auf ein Halbwort ohne Befehl oder ein nicht ausgerichteter Halbwort- oder Wortzugriff beendet den Lauf ohne Beweis.

Atomare Befehle werden dekodiert und bewiesen, mit einer Abweichung: `sc.w` gelingt immer, weil die Maschine keinen Reservierungszustand führt. Der [Programmierleitfaden für Gastprogramme](https://apogee.gweb3networks.com/docs/launch/guide#atomics) erklärt, warum neuer Code in Gastprogrammen überhaupt keine Atomics verwenden sollte.

## Nächste Schritte

- [Eingaben, Hilfsdaten und Journal](https://apogee.gweb3networks.com/docs/launch/io): Die drei Speicherbereiche und was der Beweis bindet.

- [Delegationen](https://apogee.gweb3networks.com/docs/launch/delegations): Hashing und Kurvenarithmetik zu einem Bruchteil der Kosten.

- [Bauen und inspizieren](https://apogee.gweb3networks.com/docs/launch/build): Profile, das Image, sein Bericht und seine Identität.
