# TBVM — Designentwurf Eigene Stack-basierte Bytecode-VM (Entscheidung siehe PLAN.md, 0.2). Die VM ist in `tbdos` (IDE) und in von `tbdosc` erzeugte Executables eingebettet — identischer Code, identisches Verhalten, schneller Turnaround. ## Werte-Modell Tagged Enum, kein NaN-Boxing (Einfachheit und Debugbarkeit vor Mikro- performance): ```rust enum Value { Int(i16), // INTEGER Long(i32), // LONG Single(f32), // SINGLE Double(f64), // DOUBLE Currency(i64), // CURRENCY, Festkomma ×10 000 Str(Rc), // STRING (immutabel geteilt; Copy-on-Write bei MID$-Anweisung) // Arrays und TYPE-Instanzen leben im Heap-Bereich der VM, // Variablen-Slots referenzieren sie per Handle (Rc>). } ``` - Kein GC nötig: der Dialekt kennt keine Zyklen (keine Objektreferenzen in TYPEs, keine Closures) → `Rc` genügt. - Feste Strings (`STRING * n`) und TYPE-Records werden als eigene Speicherobjekte mit fester Zeichen-/Elementstruktur geführt. ## Bytecode - Stack-Maschine, Opcodes 1 Byte + Operanden variabler Länge (u8/u16/u32, little-endian). - **Modul-Struktur:** Konstantenpool (Strings, Zahlen), Typtabelle (TYPE-Layouts), Prozedurtabelle (Signatur, Locals-Anzahl, Code-Offset), globale Slots, DATA-Segment (für READ/RESTORE), Zeilentabelle. - **Zeilentabelle:** Abbildung Code-Offset → (Moduldatei, Zeile). Grundlage für `ERL`, Fehlermeldungen, Breakpoints und Einzelschritt. - **Aufrufkonventionen:** - SUB/FUNCTION: eigener Frame (Locals-Slots, Operandenstack-Basis); BYREF-Parameter als Referenz-Slots (Handle auf Variablen-Slot). - GOSUB: **kein** Frame — Rücksprungadresse auf separatem GOSUB-Stack im aktuellen Frame (RETURN prüft diesen zuerst). - **Fehlerbehandlung:** Pro Frame ein Handler-Zustand (`ON ERROR GOTO x`). Laufzeitfehler → VM sucht aktiven Handler im aktuellen Modulkontext (Vorbild: Handler sind modul-/prozedurlokal, TODO: exakte Scoping-Regel testen), setzt `ERR`/`ERL`, springt. `RESUME` nutzt gemerkten Anweisungs-Offset. ## Ausführungsmodell / Unterbrechbarkeit - Die Interpreterschleife läuft in **Ticks**: nach jeder Anweisung (Grenze aus der Zeilentabelle) prüft sie ein Flag-Set: Breakpoint? Einzelschritt? Strg+Untbr? Ereignis-Queue nicht leer und Zustellung erlaubt? - Ereigniszustellung (Forms, Timer) erfolgt kooperativ: nur an Zustellpunkten (`DOEVENTS`, `SLEEP`, blockierende Eingabe, Ende einer Ereignisprozedur) — wie im Vorbild. - Die VM ist eine gewöhnliche zustandsbehaftete Struktur, `step()`-basiert; die einbettende Schleife (IDE-Debugger oder Runner) treibt sie. Kein eigener Thread nötig; Terminal-Events werden zwischen Ticks gepollt. ## `.tbc`-Container (Entwurf) ``` Magic "TBC\0" · Formatversion u16 · Flags Abschnittstabelle: [ (Kennung, Offset, Länge) ] Abschnitte: CONSTS, TYPES, PROCS, CODE, DATA, LINES, FORMS (serialisierte .FRM-Beschreibungen) ``` Serialisierung mit einfachem eigenem Writer/Reader (kein serde nötig, Format bleibt stabil und dokumentiert). ## Eigenständige Executables `tbdosc build --exe` kopiert den vorkompilierten Runner (dieselbe tb-vm/tb-runtime/tb-ui-Bibliothek wie die IDE) und hängt das `.tbc` als Ressource an (Anhängen ans Binary + Fußzeile mit Offset/Magic; portabel für alle drei Plattformen). Alternative — `include_bytes!` + Cargo-Build beim Nutzer — verworfen: erfordert Rust-Toolchain beim Anwender. ## Performance **Compiler (Instant-Compile, Anforderung siehe PLAN.md):** - Single-Pass pro Modul: Lexen, Parsen und Codegen in einem Durchlauf; Vorwärtsreferenzen (Prozeduren, Labels) über Fixup-Listen statt zweitem Pass. Die Sprache ist dafür gemacht — das Vorbild kompilierte auf 1992er-Hardware gefühlt sofort. - Modulweise inkrementell: `.BAS`/`.FRM` werden unabhängig zu Bytecode- Einheiten übersetzt und beim Build nur zusammengebunden; die IDE recompiliert nur geänderte Module (Hash über Quelltext). - Keine Optimierungspasses. Erlaubt sind nur Gratis-Optimierungen im Codegen (Konstantenfaltung im Ausdruck, Peephole beim Emit). - Budget als Test verankern (Phase 2): Benchmark-Projekt (~50k Zeilen) muss unter 1 s kompilieren (Release-Build der Toolchain, Referenzrechner); einzelnes Modul < 50 ms. **VM-Ausführung:** - Alle Namen werden zur Compilezeit aufgelöst: Variablen/Parameter sind Slot-Indizes, Prozeduren Tabellenindizes — zur Laufzeit keine Hash-Lookups. - Statische Typen des Dialekts ausnutzen: typisierte Opcodes (`ADD_I16`, `ADD_F64`, `CONCAT` …) statt generischem Dispatch über `Value`-Tags in heißen Pfaden. - Dichte Opcodes, `match`-Dispatch in einer engen Schleife; Tick-Prüfung (Events/Breakpoints) nur an Anweisungsgrenzen über ein einzelnes Flag-Wort, nicht pro Opcode. - Strings immutabel via `Rc` (Kopien sind Pointer-Kopien); Konstantenpool dedupliziert. - Messlatte (Phase 2, Benchmarks in `benches/`): typische Schleifen-/String-Lasten mindestens ~100× schneller als das Vorbild auf Originalhardware; Richtwert grob Lua-Interpreter-Klasse, gemessen und dokumentiert statt geraten. ## Offene Punkte - Opcode-Satz konkret ausformulieren (mit Phase 2) - Zahlenkonvertierungs-Matrix (implizite Casts, Rundung, Überlauf) als Tabelle - Verhalten von `STOP`/`CONT` im Runner (ohne IDE): Abbruch mit Meldung?