Phase 2 abgeschlossen: Bytecode, TBVM, Runtime-Scheibe, tbc run
- Sema zum Lowering-Pass umgebaut: typisiertes HIR (Slots, explizite Konvertierungsknoten) als Codegen-Eingabe; BYREF verlangt exakten Typ - Bytecode-Feindesign umgesetzt: monomorpher Opcode-Satz, .tbc-Container (Formatversion 1) mit eigenem Writer/Reader - Codegenerator HIR -> Bytecode (Fixup-Listen, keine globalen Passes) - TBVM-Interpreter: Kontrollfluss, GOSUB-Stack je Frame, BYREF/BYVAL, STATIC, DEF FN, DATA/READ/RESTORE, ON [LOCAL] ERROR/RESUME/ERR/ERL, Breakpoints/Einzelschritt/Inspektion, STOP fortsetzbar - Runtime-Scheibe: Host-Trait (Konsole/Capture), Builtin-Tabelle, Konvertierungsmatrix, PRINT-Formatierung/Druckzonen, Stringfunktionen - tbc run/build/check mit Exit-Codes nach Entscheidung D6 - Korpus-Harness (byte-genauer Vergleich) + 3 neue Korpusdateien (konvertierung, fehlerbehandlung, byref); 137 Tests gruen - Benchmarks: Einzelmodul 1,2 ms / Projekt 49.760 Zeilen 124 ms (Budgets eingehalten), VM ~5 Mio Schleifeniterationen/s - Doku fortgeschrieben (tbvm-design, sprachreferenz, PLAN); verlagerte Punkte als explizite Aufgaben in Phase 3 - OpenSpec-Change phase-2-bytecode-vm (27/27 Tasks) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -59,16 +59,29 @@ enum Value {
|
||||
die einbettende Schleife (IDE-Debugger oder Runner) treibt sie. Kein
|
||||
eigener Thread nötig; Terminal-Events werden zwischen Ticks gepollt.
|
||||
|
||||
## `.tbc`-Container (Entwurf)
|
||||
## `.tbc`-Container (Stand Phase 2, Formatversion 1)
|
||||
|
||||
```
|
||||
Magic "TBC\0" · Formatversion u16 · Flags
|
||||
Abschnittstabelle: [ (Kennung, Offset, Länge) ]
|
||||
Abschnitte: CONSTS, TYPES, PROCS, CODE, DATA, LINES, FORMS (serialisierte .FRM-Beschreibungen)
|
||||
Magic "TBC\0" · Formatversion u16 · Flags u16 · Abschnittsanzahl u32
|
||||
Abschnittstabelle: [ (Kennung 4 Byte, Offset u32, Länge u32) ]
|
||||
Abschnitte:
|
||||
MODN Modulname + OPTION BASE
|
||||
CONS deduplizierter Stringpool
|
||||
TYPS TYPE-Layouts (Feld-Initialisierungstypen)
|
||||
GLOB globale Slots (Initialisierungstyp + Name für Debugger)
|
||||
PROC Prozedurtabelle: Name, Parameteranzahl, Frame-Slots, Code
|
||||
(Zeileninfo liegt inline im Code: Stmt-/SetErl-Instruktionen —
|
||||
ersetzt den früher geplanten separaten LINES-Abschnitt)
|
||||
DATA DATA-Konstanten (Rohtext + Quellzeile)
|
||||
JMPT Sprungtabellen für ON n GOTO/GOSUB
|
||||
FORMS (ab Phase 4: serialisierte .FRM-Beschreibungen)
|
||||
```
|
||||
|
||||
Serialisierung mit einfachem eigenem Writer/Reader (kein serde nötig,
|
||||
Format bleibt stabil und dokumentiert).
|
||||
Format bleibt stabil und dokumentiert); unbekannte Formatversionen
|
||||
werden mit Meldung abgewiesen. Instruktions-Encoding: 1 Opcode-Byte +
|
||||
Operanden little-endian, Opcode-Bytes gruppenweise mit Lücken vergeben
|
||||
(`crates/tb-vm/src/bytecode.rs` ist die normative Liste).
|
||||
|
||||
## Eigenständige Executables
|
||||
|
||||
@@ -111,8 +124,148 @@ Nutzer — verworfen: erfordert Rust-Toolchain beim Anwender.
|
||||
Originalhardware; Richtwert grob Lua-Interpreter-Klasse, gemessen und
|
||||
dokumentiert statt geraten.
|
||||
|
||||
**Benchmark-Ergebnisse (2026-09-02, Release-Build, Entwicklerrechner
|
||||
Windows 11, `cargo bench -p tb-vm`):**
|
||||
|
||||
- Compile-Budget (`benches/compile.rs`):
|
||||
- Einzelmodul, 508 Zeilen: **1,2 ms** (Budget 50 ms — eingehalten)
|
||||
- Projekt, 49 760 Zeilen über 20 Module: **124 ms** (Budget 1 000 ms —
|
||||
eingehalten); Durchsatz ≈ 400 000 Zeilen/s
|
||||
- VM-Durchsatz (`benches/vm.rs`):
|
||||
- INTEGER-Schleife (Arithmetik + MOD): ≈ 4,8 Mio Iterationen/s
|
||||
- DOUBLE-Schleife: ≈ 5,2 Mio Iterationen/s
|
||||
- SUB-Aufrufe mit BYREF: ≈ 5,2 Mio Aufrufe/s
|
||||
- String-Lasten (MID$/INSTR/Verkettung): ≈ 0,8 Mio Runden/s
|
||||
- Einordnung: bei ≈ 13 Instruktionen je Schleifeniteration entspricht
|
||||
das ≈ 60 Mio Instruktionen/s — Vorbild-Programme auf 1992er-Hardware
|
||||
werden um weit mehr als den Faktor 100 übertroffen; für 80×25-
|
||||
Terminalprogramme mehr als ausreichend. Ein Performance-Pass ist erst
|
||||
bei Bedarf vorgesehen (Phase 6).
|
||||
|
||||
## Zahlenkonvertierungs-Matrix (Feindesign, 2026-09-02)
|
||||
|
||||
Grundlage: Original-Hilfe (Topics „Arithmetic Operators", „CINT/CLNG",
|
||||
„MOD", „Numeric Types"). Zellen ohne belastbare Fundstelle sind mit
|
||||
`TODO verify` markiert; der Korpustest `konvertierung.bas` verankert die
|
||||
Matrix ausführbar.
|
||||
|
||||
**Rangfolge für Promotion:** INTEGER < LONG < CURRENCY < SINGLE < DOUBLE.
|
||||
Ausnahme: CURRENCY gemischt mit SINGLE oder DOUBLE ergibt **DOUBLE**
|
||||
(nicht SINGLE), damit keine Festkomma-Präzision in f32 verloren geht
|
||||
(`TODO verify` gegen PDS-Hilfe; plausibelste Lesart).
|
||||
|
||||
**Ergebnistyp binärer Operatoren** (l op r; T = promoteter Typ):
|
||||
|
||||
| Operator | Ergebnistyp | Besonderheiten |
|
||||
|---|---|---|
|
||||
| `+ - *` | T; gleiche Typen bleiben erhalten | Ganzzahl-/CURRENCY-Überlauf → Fehler 6 (auch INTEGER+INTEGER promotet **nicht**) |
|
||||
| `/` | DOUBLE, wenn ein Operand DOUBLE oder CURRENCY, sonst SINGLE | Division durch 0 → Fehler 11 |
|
||||
| `\` `MOD` | INTEGER, wenn beide Operanden INTEGER, sonst LONG | Operanden werden **vorher** kaufmännisch (banker's) auf Ganzzahl gerundet; außerhalb LONG → Fehler 6; Divisor 0 → Fehler 11; `MOD`-Vorzeichen wie Dividend |
|
||||
| `^` | DOUBLE, wenn ein Operand DOUBLE oder CURRENCY, sonst SINGLE (`TODO verify`) | Rechnung intern in DOUBLE; 0 ^ negativ → Fehler 11; negative Basis mit nicht-ganzem Exponent → Fehler 5 |
|
||||
| `= <> < <= > >=` | INTEGER (−1/0) | Vergleich im promoteten Typ; Strings codepoint-weise |
|
||||
| `NOT AND OR XOR EQV IMP` | INTEGER, wenn alle Operanden INTEGER, sonst LONG | bitweise; Operanden vorher banker's-gerundet auf Ganzzahl, außerhalb LONG → Fehler 6 |
|
||||
| unäres `-` | Typ des Operanden | −(−32768) INTEGER → Fehler 6 |
|
||||
|
||||
**Konvertierung Quelle → Ziel** (implizit bei Zuweisung/Argument, explizit
|
||||
via `CINT`/`CLNG`/`CSNG`/`CDBL`/`CCUR` — identische Semantik):
|
||||
|
||||
| von \ nach | INTEGER | LONG | SINGLE | DOUBLE | CURRENCY |
|
||||
|---|---|---|---|---|---|
|
||||
| INTEGER | — | exakt | exakt | exakt | ×10 000 |
|
||||
| LONG | Bereich prüfen → 6 | — | nächstgelegener f32 | exakt | ×10 000 |
|
||||
| SINGLE | banker's + Bereich → 6 | banker's + Bereich → 6 | — | exakt (Widening) | banker's auf 4 Nachkommastellen + Bereich → 6 |
|
||||
| DOUBLE | banker's + Bereich → 6 | banker's + Bereich → 6 | Bereich prüfen → 6, sonst nächstgelegener f32 | — | banker's auf 4 Nachkommastellen + Bereich → 6 |
|
||||
| CURRENCY | ÷10 000, banker's + Bereich → 6 | ÷10 000, banker's + Bereich → 6 | ÷10 000.0 | ÷10 000.0 | — |
|
||||
|
||||
- Banker's Rounding: zur nächsten Ganzzahl, bei .5 zur **geraden**
|
||||
(Original-Hilfe: CINT). Gilt für alle Gleitkomma→Ganzzahl-Übergänge
|
||||
inklusive `\`/`MOD`/Logik-Operanden und CURRENCY-Skalierung.
|
||||
- Zahl ↔ String nie implizit (Compile-Zeit „Type mismatch"); explizit nur
|
||||
über `STR$`/`VAL`.
|
||||
|
||||
**Textdarstellung (PRINT und STR$):**
|
||||
|
||||
- INTEGER/LONG: dezimal ohne Dezimalpunkt.
|
||||
- SINGLE: bis 7 signifikante Stellen, kürzeste Darstellung, keine
|
||||
führende Null vor dem Dezimalpunkt (`.5`), keine nachgestellten
|
||||
Nullen; außerhalb des Festformat-Bereichs Exponentialform `mE±xx`
|
||||
(exakte Schwelle `TODO verify`; umgesetzt: Exponentialform, wenn der
|
||||
Dezimalexponent < −7 oder ≥ 7 ist).
|
||||
- DOUBLE: wie SINGLE mit bis 16 signifikanten Stellen und `D±xx` als
|
||||
Exponentmarke.
|
||||
- CURRENCY: Festformat mit bis zu 4 Nachkommastellen, nachgestellte
|
||||
Nullen entfallen (`TODO verify`).
|
||||
- `PRINT` stellt nicht-negativen Zahlen ein Leerzeichen, negativen ein
|
||||
`-` voran und hängt stets ein Leerzeichen an; `STR$` nur das führende
|
||||
Leerzeichen/`-`, kein nachgestelltes.
|
||||
|
||||
## Opcode-Satz (Feindesign, 2026-09-02)
|
||||
|
||||
In-Memory führt die VM dekodierte Instruktionen (`Vec<Instr>`, ein Rust-
|
||||
Enum mit eingebetteten Operanden — Wort-Dispatch, keine Byte-Dekodierung
|
||||
im heißen Pfad); die `.tbc`-Serialisierung bildet jede Instruktion auf
|
||||
1 Opcode-Byte + Operanden (little-endian) ab. Typkürzel: `I2`=INTEGER,
|
||||
`I4`=LONG, `R4`=SINGLE, `R8`=DOUBLE, `CY`=CURRENCY, `STR`=STRING.
|
||||
|
||||
| Gruppe | Instruktionen | Bemerkung |
|
||||
|---|---|---|
|
||||
| Anweisungsgrenze | `Stmt(line:u32)` · `SetErl(n:u32)` | `Stmt` prüft das Tick-Flag-Wort (Breakpoint/Einzelschritt/Abbruch), aktualisiert Zeile und Resume-Punkt; `SetErl` bei numerischen Zeilennummern |
|
||||
| Konstanten/Stack | `PushInt(i16)` `PushLng(i32)` `PushSng(f32)` `PushDbl(f64)` `PushCur(i64)` `PushStr(pool:u16)` · `Dup` `Pop` | Stringpool dedupliziert |
|
||||
| Variablen | `LoadGlobal/StoreGlobal(u16)` · `LoadLocal/StoreLocal(u16)` · `MakeRefGlobal/MakeRefLocal(u16)` · `LoadRef/StoreRef(u16)` | Slots statisch aufgelöst; `*Ref` bedienen BYREF-Parameter (Referenz-Werte) |
|
||||
| Arrays | `DimGlobal/DimLocal{slot,dims,elem}` `RedimGlobal/RedimLocal{…}` `EraseGlobal/EraseLocal(u16)` · `LoadElem/StoreElem{dims:u8}` · `MakeRefElem{dims:u8}` | Grenzen auf dem Stack (lo/hi je Dimension als LONG); Indexprüfung → Fehler 9 |
|
||||
| UDT | `LoadField/StoreField(u16)` · `MakeRefField(u16)` | Feldindex aus Typtabelle; verschachtelt durch Verkettung |
|
||||
| Arithmetik | `Add/Sub/Mul{I2,I4,R4,R8,CY}` · `Neg{…}` · `Div{R4,R8}` · `IDiv/Mod{I2,I4}` · `PowR8` (`^` rechnet in DOUBLE, SINGLE-Ergebnis per Conv) | monomorph; Ganzzahl-/CY-Überlauf → Fehler 6, Division durch 0 → Fehler 11 |
|
||||
| Konvertierung | `Conv_<src>_<dst>` für alle 20 geordneten Paare aus {I2,I4,R4,R8,CY} | Semantik exakt nach Matrix (Rundung/Überlauf) |
|
||||
| Logik | `Not/And/Or/Xor/Eqv/Imp{I2,I4}` | bitweise |
|
||||
| Vergleich | `Cmp{Eq,Ne,Lt,Le,Gt,Ge}{I2,I4,R4,R8,CY,STR}` | Ergebnis INTEGER −1/0 |
|
||||
| Strings | `Concat` | `Rc<str>`, Kopien sind Pointer-Kopien |
|
||||
| Kontrollfluss | `Jump(u32)` `JumpIfFalse(u32)` `JumpIfTrue(u32)` | Ziele absolut (Instruktionsindex) innerhalb der Code-Einheit; Fixups beim Emit |
|
||||
| GOSUB | `Gosub(u32)` `RetGosub` `RetGosubTo(u32)` | GOSUB-Stack pro Frame; leer → Fehler 3 |
|
||||
| Berechnete Sprünge | `OnJump{table:u16,gosub:bool}` | Sprungtabellen im Modul; 0/zu groß: kein Sprung; negativ/>255 → Fehler 5 |
|
||||
| Prozeduren | `Call(proc:u16)` `RetProc` `RetFn` `CallBuiltin{id:u16,argc:u8}` | Argumente links→rechts auf dem Stack (Werte oder Referenzen); `RetFn` transportiert den Funktionswert über den Frame-Abbau |
|
||||
| Fehler | `OnErrorGoto(u32)` `OnErrorLocal(u32)` `OnErrorDisable` `OnErrorLocalDisable` · `Resume0` `ResumeNext` `ResumeLabel(u32)` · `RaiseError` | Scoping siehe unten |
|
||||
| DATA | `ReadData(art)` `Restore(u32)` | art 0 = String, 1 = Zahl (DOUBLE, dann Conv nach Matrix); Ende → Fehler 4, unkonvertierbar → Fehler 13 |
|
||||
| E/A | `Input{…}` sowie Builtins (`PRINT`-Familie über `CallBuiltin`) | Konsolenwirkung ausschließlich über das `Host`-Trait |
|
||||
| Ende | `End` `StopInstr` `SystemInstr` | Verhalten siehe Runner |
|
||||
|
||||
FOR/NEXT wird ohne Spezial-Opcodes kompiliert: Grenz-/Schrittwert einmal
|
||||
in versteckte Slots ausgewertet; bei konstantem STEP wählt der Codegen
|
||||
die Vergleichsrichtung zur Compilezeit, sonst verzweigt ein einmalig
|
||||
berechnetes Vorzeichen-Flag auf zwei Vergleichssequenzen. Inkrement mit
|
||||
regulärer typisierter Addition (Überlauf → Fehler 6, wie Vorbild).
|
||||
`SELECT CASE` wertet den Selektor in einen versteckten Slot aus und wird
|
||||
als Vergleichs-/Sprungkette abgesenkt.
|
||||
|
||||
## Fehlerbehandlung: Scoping (Feindesign, 2026-09-02)
|
||||
|
||||
- `ON ERROR GOTO x` setzt den **modulweiten** Handler — auch aus einer
|
||||
Prozedur heraus. `ON LOCAL ERROR GOTO x` setzt einen **Frame-lokalen**
|
||||
Handler, der den modulweiten für die Dauer des Prozedurlaufs verdeckt.
|
||||
`… GOTO 0` deaktiviert den jeweiligen Handler.
|
||||
- Fehlerfall: erst die Frame-Kette von innen nach außen nach
|
||||
LOCAL-Handlern absuchen, sonst modulweiter Handler; Stack wird bis zum
|
||||
Handler-Frame abgewickelt, Anweisungs-Offset und Frame für `RESUME`
|
||||
gemerkt, `ERR`/`ERL` gesetzt. Ohne Handler: Abbruch mit Meldung und
|
||||
Quellzeile.
|
||||
- Fehler **während aktiver Behandlung** (zwischen Handler-Eintritt und
|
||||
`RESUME`): sofortiger Abbruch, keine Kaskade (Vorbild).
|
||||
- `ERL` liefert die zuletzt durchlaufene **numerische** Zeilennummer
|
||||
(Labels zählen nicht; ohne numerische Zeilen: 0).
|
||||
|
||||
## Runner-Verhalten `STOP`/`CONT` (Entscheidung 2026-09-02)
|
||||
|
||||
- `END`, `SYSTEM` und das Erreichen des Programmendes beenden den
|
||||
Prozess mit Exit-Code 0.
|
||||
- `STOP` außerhalb der IDE terminiert mit der Meldung `STOP in line n`
|
||||
(bzw. Modul/Zeile) und Exit-Code ≠ 0. Offene Dateien werden wie bei
|
||||
`END` geschlossen.
|
||||
- `CONT` ist ein Direktmodus-Konzept der IDE (Phase 5): dort suspendiert
|
||||
`STOP` die VM (`step()`-Architektur), `CONT` setzt fort. Im Kompilat
|
||||
existiert kein `CONT`.
|
||||
|
||||
## 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?
|
||||
Keine — die Punkte aus Phase 0 (Opcode-Satz, Konvertierungsmatrix,
|
||||
`STOP`/`CONT` im Runner) sind oben ausgearbeitet; verbleibende
|
||||
Detailunsicherheiten sind als `TODO verify` direkt an den betroffenen
|
||||
Zellen markiert und über den Testkorpus abgesichert.
|
||||
|
||||
Reference in New Issue
Block a user