Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

PetaPerl Architekturübersicht

Ein umfassender Leitfaden für Entwickler zum Verständnis und zur Mitarbeit an PetaPerl.


Inhaltsverzeichnis

  1. Vision und Ziele
  2. Architektur auf hoher Ebene
  3. Ausführungspipeline
  4. Quellcodestruktur
  5. Zentrale Datenstrukturen
  6. Der Op-Baum
  7. Wertesystem
  8. Laufzeitumgebung und Interpreter
  9. Parser und Codegenerierung
  10. Testinfrastruktur
  11. Entwicklungsablauf
  12. Wesentliche Architekturentscheidungen
  13. Einstieg für Mitarbeiter

Vision und Ziele

PetaPerl ist keine Portierung des Perl-5-C-Interpreters nach Rust. Es ist eine Perl-Laufzeitumgebung der nächsten Generation mit drei strategischen Alleinstellungsmerkmalen:

ZielBeschreibung
Auto-ParallelisierungAutomatische parallele map-, grep-, for-Schleifen mittels Rayon Work-Stealing
JIT-KompilierungV8-Klasse-Performance über Cranelift (gestufte Kompilierung)
Pure-Perl-TauglichkeitSchnell genug, damit XS optional und nicht mehr zwingend erforderlich ist

Ziel: Pure Perl wettbewerbsfähig mit XS-beschleunigtem Code machen.

Stufenweise Umsetzung

Phase 1: Walking    [ERLEDIGT]  - Minimaler Interpreter (print "Hello")
Phase 2: Running    [ERLEDIGT]  - Variablen, Operatoren, Interpolation
Phase 3: Climbing   [ERLEDIGT]  - Arrays, Hashes, Subs, Kontrollfluss
Phase 4: Leaping    [ERLEDIGT]  - Parallele Ausführung (Rayon)
Phase 5: Flying     [ERLEDIGT]  - JIT-Kompilierung (Cranelift)

Rahmenbedingungen

EinschränkungWertBegründung
PlattformNur LinuxEinfachheit; beliebige Architektur
Perl-Version5.42+ stableNicht blead (5.43.x)
LizenzProprietärGeschäftsentscheidung

Architektur auf hoher Ebene

┌─────────────────────────────────────────────────────────────────┐
│                        PetaPerl Runtime                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐       │
│  │   Parser     │    │   Codegen    │    │   OpArena    │       │
│  │  (lexer.rs)  │──▶│ (codegen.rs) │──▶│  (tree.rs)   │       │
│  │ (parser.rs)  │    │              │    │              │       │
│  └──────────────┘    └──────────────┘    └──────┬───────┘       │
│                                                  │              │
│                                                  ▼              │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                     Interpreter                          │   │
│  │  ┌────────────┐  ┌────────────┐  ┌────────────────────┐  │   │
│  │  │   Stack    │  │    Pad     │  │  PackageRegistry   │  │   │
│  │  │  (Werte)   │  │ (Lexikale) │  │   (Globale)        │  │   │
│  │  └────────────┘  └────────────┘  └────────────────────┘  │   │
│  │                                                          │   │
│  │  ┌────────────────────────────────────────────────────┐  │   │
│  │  │              PP-Dispatch (27 Module)              │  │   │
│  │  │  control | strings | io | arrays | hashes | ...    │  │   │
│  │  └────────────────────────────────────────────────────┘  │   │
│  └──────────────────────────────────────────────────────────┘   │
│                                                                 │
│  ┌──────────────┐    ┌──────────────┐    ┌──────────────┐       │
│  │    Values    │    │    Regex     │    │     I/O      │       │
│  │  Sv/Av/Hv/Cv │    │   Engine     │    │    Layer     │       │
│  └──────────────┘    └──────────────┘    └──────────────┘       │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

Ausführungspipeline

Vom Quellcode zur Ausgabe

┌─────────────────┐
│  Perl-Quellcode │   my $x = 10;
│                 │   print $x + 5;
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│     Lexer       │   Tokens: [MY, SCALAR($x), ASSIGN, INT(10), ...]
│   (lexer.rs)    │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│     Parser      │   AST: Stmt[VarDecl($x, 10), Print(BinOp(+, $x, 5))]
│  (parser.rs)    │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│    Codegen      │   OpArena mit verknüpften Op-Knoten
│  (codegen.rs)   │
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│   Interpreter   │   Ops sequenziell ausführen
│(interpreter.rs) │   Stapelbasierte Wertübergabe
└────────┬────────┘
         │
         ▼
┌─────────────────┐
│     Ausgabe     │   "15"
└─────────────────┘

Interpreterschleife

#![allow(unused)]
fn main() {
fn run(&mut self) -> RuntimeResult<Vec<Sv>> {
    let mut current = self.arena.first();

    while current.is_some() {
        let op = self.arena.get(current)?;
        let next = self.dispatch(op)?;  // Op ausführen, nächsten holen
        current = next;
    }

    Ok(self.stack.drain())
}
}

Quellcodestruktur

peta-perl/src/
│
├── main.rs              # CLI-Einstiegspunkt (729 Zeilen)
├── lib.rs               # Bibliothekswurzel, öffentliche Exporte
│
├── op/                  # Op-Baum-Schicht
│   ├── mod.rs           # Öffentliche API
│   ├── tree.rs          # OpArena, Op-Varianten (51KB)
│   ├── flags.rs         # OpFlags, OpPrivate
│   └── opcode/          # OpCode-Definitionen
│       ├── mod.rs       # ~200 Opcodes
│       ├── category.rs  # 22 Kategorien
│       └── encoding.rs  # 6-Bit-Kategorie + 10-Bit-Op
│
├── value/               # Werttypen (172KB gesamt)
│   ├── mod.rs           # Öffentliche API
│   ├── sv.rs            # Skalarer Wert (53KB) - KERNTYP
│   ├── av.rs            # Array-Wert (23KB)
│   ├── hv.rs            # Hash-Wert (24KB)
│   ├── cv.rs            # Code-Wert (24KB)
│   ├── glob.rs          # Typeglobs, Stash (19KB)
│   └── aliased_av.rs    # Unterstützung für @_-Aliasing
│
├── runtime/             # Ausführungsschicht (177KB)
│   ├── mod.rs           # Öffentliche API
│   ├── interpreter.rs   # Hauptschleife (83KB) - KERN
│   ├── stack.rs         # Wertstapel (11KB)
│   ├── pad.rs           # Lexikalische Variablen (15KB)
│   ├── error.rs         # RuntimeError-Typen
│   └── pp/              # PP-Operationen (~1MB über 27 Dateien)
│       ├── dispatch.rs  # Kategoriebasierter Dispatcher
│       ├── helpers.rs   # Häufige Muster
│       ├── control.rs   # if/while/for/foreach
│       ├── strings.rs   # Zeichenkettenoperationen (88KB)
│       ├── sub.rs       # Subroutinenaufrufe (90KB)
│       ├── io.rs        # Print/open/close (56KB)
│       ├── arrays.rs    # Array-Operationen (64KB)
│       ├── regex.rs     # Musterabgleich (63KB)
│       └── ...          # 18+ weitere Module
│
├── parser/              # Nativer Parser (810KB)
│   ├── mod.rs           # Öffentliche API
│   ├── lexer.rs         # Tokenizer (85KB)
│   ├── parser.rs        # Rekursiver Abstieg (257KB)
│   ├── ast.rs           # AST-Definitionen (16KB)
│   ├── codegen.rs       # AST → OpArena (401KB) - GRÖẞTE DATEI
│   ├── symtab.rs        # Symboltabelle
│   └── token.rs         # Tokentypen
│
├── regex/               # Regex-Engine (268KB)
│   ├── mod.rs           # Öffentliche API
│   ├── compile.rs       # Musterkompilierer (78KB)
│   ├── exec.rs          # Backtracking-Ausführung (115KB)
│   └── types.rs         # Kerntypen (43KB)
│
├── loader/              # Älteres Op-Laden
│   ├── parser.rs        # B::Concise-Parser
│   ├── json.rs          # B::PetaPerl JSON
│   └── builder.rs       # Programmatische Konstruktion
│
└── io/                  # I/O-Schicht (zukünftig)
    └── mod.rs

Codezeilen nach Modul

ModulZeilenZweck
parser/~19.700Lexing, Parsing, Codegenerierung
runtime/pp/~45.500Implementierungen der PP-Operationen
value/~8.500Werttypen (Sv, Av, Hv, Cv)
op/~4.000Op-Baum-Strukturen
regex/~6.500Regex-Kompilierung und -Ausführung
Gesamt~72.000

Zentrale Datenstrukturen

Modulabhängigkeiten

                    ┌─────────────┐
                    │   main.rs   │
                    └──────┬──────┘
                           │
            ┌──────────────┼──────────────┐
            │              │              │
            ▼              ▼              ▼
     ┌──────────┐   ┌──────────┐   ┌──────────┐
     │  parser  │   │  loader  │   │  runtime │
     └────┬─────┘   └────┬─────┘   └────┬─────┘
          │              │              │
          └──────────────┼──────────────┘
                         │
                         ▼
                  ┌──────────────┐
                  │   op::tree   │◀───────────┐
                  │   OpArena    │             │
                  └──────┬───────┘             │
                         │                     │
            ┌────────────┼────────────┐        │
            │            │            │        │
            ▼            ▼            ▼        │
     ┌──────────┐ ┌───────────┐ ┌──────────┐   │
     │  value   │ │  runtime  │ │  regex   │───┘
     │ Sv/Av/Hv │ │Interpreter│ │ Engine   │
     └──────────┘ └───────────┘ └──────────┘

Der Op-Baum

Arena-Allokation

#![allow(unused)]
fn main() {
pub struct OpArena {
    ops: Vec<Op>,              // Alle Ops zusammenhängend gespeichert
    svs: Vec<Sv>,              // Konstantenpool
    root: OpId,                // Einstiegspunkt
    subs: HashMap<String, Cv>, // Definierte Subroutinen
}

pub struct OpId(u32);          // Typsicherer Index (NONE = u32::MAX)
}

Warum Arena?

  • Cache-Lokalität: Ops zusammenhängend gespeichert
  • Kein Referenzzählungs-Overhead
  • JIT-freundlich: linearer Durchlauf
  • Serialisierbar: Op-Bäume können gespeichert/geladen werden

Op-Varianten

#![allow(unused)]
fn main() {
pub enum Op {
    Base(OpData),           // Basis-Op
    UnOp(UnOp),             // Unär: negate, defined, not
    BinOp(BinOp),           // Binär: add, concat, eq
    ListOp(ListOp),         // Liste: push, print, map
    PmOp(PmOp),             // Musterabgleich
    SvOp(SvOp),             // Konstanter Wert
    PadOp(PadOp),           // Zugriff auf lexikalische Variablen
    Loop(Loop),             // Schleifenkonstrukte
    Cop(Cop),               // Kontextoperationen (Zeilen-/Dateiinfo)
    MulticoncatOp(...),     // Optimierte Zeichenkettenverkettung
    MultiderefOp(...),      // Optimierte Dereferenzierung
}
}

Op-Ausführungsverkettung

Jeder Op hat ZWEI Ordnungen:

AUSFÜHRUNGSREIHENFOLGE (op_next):     Lineare Sequenz für den Interpreter
  enter → nextstate → add → print → leave

BAUMORDNUNG (op_first/sibling): Verschachtelte Geltungsbereichsstruktur
  enter
    └─ nextstate
         └─ print
              └─ add
                   ├─ const(5)
                   └─ padsv($x)

OpCode-Kategorien (22 insgesamt)

Bits 15-10: Kategorie (6 Bits)
Bits 9-0:   Op (10 Bits)

Kategorien:
  0x01 META        null, wantarray, pushmark
  0x03 CONTEXT     enter, leave, nextstate
  0x04 CONTROL     if, while, for, return
  0x05 LOGIC       and, or, not, xor
  0x07 COMPARE     ==, eq, <, lt, cmp, <=>
  0x09 ASSIGN      =, +=, list assign
  0x11 ARITHMETIC  +, -, *, /, %
  0x13 STRINGS     concat, substr, length
  0x14 ARRAYS      push, pop, shift, splice
  0x15 HASHES      keys, values, each, delete
  0x17 SUBROUTINES entersub, leavesub, return
  0x1F IO          print, say, open, close
  0x20 REGEX       m//, s///, tr///
  ...

Wertesystem

Skalarer Wert (Sv)

#![allow(unused)]
fn main() {
pub enum Sv {
    Undef,                          // undef
    Iv(i64),                        // Ganzzahl
    Uv(u64),                        // Vorzeichenlose Ganzzahl
    Nv(f64),                        // Gleitkommazahl
    Pv(Arc<str>),                   // Zeichenkette
    Rv(RvInner),                    // Referenz
    Av(Av),                         // Array
    Hv(Hv),                         // Hash
    Cv(Cv),                         // Subroutine
    Alias(Arc<SvCell>),             // Für @_-Aliasing
    ArrayElemAlias(Av, i64),        // Array-Element-Alias
    Regex(Arc<CompiledRegex>),      // Kompiliertes Muster
}
}

Speicherstrategie: Arc + RefCell

┌─────────────────────────────────────────────────────────┐
│  Sv::Pv(Arc<str>)                                       │
│    └─ Arc ermöglicht günstiges Klonen (nur Refcount++)  │
│    └─ Thread-sicher für zukünftige parallele Ausführung │
│    └─ Unveränderlicher Inhalt (Zeichenkettendaten)      │
├─────────────────────────────────────────────────────────┤
│  Av / Hv                                                │
│    └─ Arc<RefCell<Inner>>                               │
│    └─ Arc: geteilte Eigentümerschaft                    │
│    └─ RefCell: innere Veränderbarkeit (Single-Threaded) │
├─────────────────────────────────────────────────────────┤
│  SvCell (für @_-Aliasing)                               │
│    └─ Arc<RefCell<Sv>>                                  │
│    └─ Mehrere Referenzen auf denselben Speicher         │
│    └─ Ermöglicht: sub foo { $_[0] = 10 } # ändert Aufrufer │
└─────────────────────────────────────────────────────────┘

Typhierarchie

Sv (Skalar)
 ├─ Undef
 ├─ Zahlen: Iv, Uv, Nv
 ├─ Zeichenkette: Pv(Arc<str>)
 ├─ Referenz: Rv(RvInner)
 │             └─ value: Arc<Sv>
 │             └─ blessed: Option<Arc<str>>
 ├─ Sammlungen:
 │   ├─ Av (Array)
 │   │   └─ elements: Vec<Sv>
 │   │   └─ blessed: Option<Arc<str>>
 │   └─ Hv (Hash)
 │       └─ entries: HashMap<Arc<str>, Sv>
 │       └─ blessed: Option<Arc<str>>
 ├─ Cv (Code-Wert)
 │   └─ start_op: OpId
 │   └─ prototype: Option<String>
 │   └─ captures: Vec<(u32, Arc<SvCell>)>
 └─ Aliase (für @_):
     └─ Alias, ArrayElemAlias, AliasedAv

Laufzeitumgebung und Interpreter

Duale Laufzeitarchitektur

PetaPerl liefert zwei unabhängige Laufzeitmaschinen, die sich denselben Parser, Op-Baum und die native Modulinfrastruktur teilen. Die Laufzeitumgebung wird beim Aufruf über Kommandozeilenparameter ausgewählt.

PP-Laufzeit (Standard)P5-Laufzeit (--p5)
EntwurfsphilosophieEigenständige Rust-Implementierung1:1-Transliteration des Perl5-C-Interpreters
WertdarstellungSv-Enum (32 Bytes, getaggte Union)Sv(u64) NaN-geboxed, nachempfunden von perl5s SV*
SpeichersicherheitDurchgehend sicheres RustUmfangreicher unsafe-Code — Rohzeiger, manuelle Referenzzählung
Performance~25% von perl5 (ohne JIT)Auf Augenhöhe mit perl5
JIT-KompilierungCranelift JIT für heiße SchleifenNicht verfügbar
Auto-ParallelisierungRayon Work-Stealing für parallele SchleifenNicht verfügbar
KompatibilitätHoch — deckt die üblichen Fälle abSehr hoch — bildet perl5s subtile Randfälle nach
Native ModuleAlle 47 Module17 Module (wachsend)

PP-Laufzeit (src/runtime/) ist die primäre Engine. Sie verwendet idiomatisches, sicheres Rust mit einem sauberen Wertesystem (Sv-Enum mit Arc-basiertem COW für Zeichenketten). Die Performance liegt im Basis-Interpreter hinter perl5, aber der JIT-Compiler und die Auto-Parallelisierung kompensieren dies für heiße Codepfade mehr als ausreichend — Mandelbrot-Benchmarks laufen 76x-259x schneller als perl5.

P5-Laufzeit (src/p5/) ist eine experimentelle zweite Engine, die hinzugefügt wurde, um grundlegende Performance-Parität mit perl5 für Code zu erreichen, der nicht vom JIT profitiert. Sie spiegelt perl5s C-Implementierung nahezu Zeile für Zeile in Rust wider: NaN-geboxte SVs, manuelle Referenzzählung über rc_inc()/rc_dec(), direkte Zeigermanipulation. Der Kompromiss ist allgegenwärtiger unsafe-Code, aber die Engine reproduziert perl5s Performance-Eigenschaften und subtile Verhaltensdetails originalgetreu.

Beide Laufzeiten teilen sich:

  • Den nativen Rust-Parser und Codegen (src/parser/)
  • Die Op-Baum-Darstellung (src/op/)
  • Native Modulimplementierungen (src/native/) — jedes Modul stellt eine pp.rs (PP-Laufzeit) und optional eine p5.rs (P5-Laufzeit) bereit
  • Die Testinfrastruktur (t/TEST)

Interpreterzustand

#![allow(unused)]
fn main() {
pub struct Interpreter {
    // Ausführungskontext
    arena: Arc<OpArena>,          // Unveränderlicher Op-Baum (geteilt)
    stack: Stack,                 // Wertstapel
    pad: Pad,                     // Lexikalische Variablen

    // Kontrollfluss
    loop_stack: Vec<LoopContext>,    // Für next/last/redo
    call_stack: Vec<CallFrame>,      // Subroutinenaufrufe
    iter_stack: Vec<IterContext>,    // Foreach-Iteration

    // Map/Grep-Zustand
    map_stack: Vec<MapGrepContext>,

    // Zuweisungsverfolgung
    lvalue_targets: Vec<u32>,
    last_helem_target: Option<(Hv, Sv)>,
    last_aelem_target: Option<(Av, i64)>,

    // Sicherungsstapel (für `local`)
    save_stack: Vec<SaveEntry>,

    // Ausnahmebehandlung
    try_stack: Vec<TryContext>,

    // Globaler Zustand
    packages: PackageRegistry,       // Symboltabellen
    current_package: String,
    current_file: String,
    current_line: u32,
}
}

Stapelmaschine

┌─────────────────────────────────────────┐
│              Wertstapel                 │
├─────────────────────────────────────────┤
│  [0] Sv::Integer(5)                     │
│  [1] Sv::Integer(3)                     │
│  [2] Sv::String("result")               │
│  [3] MARK  ◀── Listenkontextgrenze     │
│  [4] Sv::Integer(1)                     │
│  [5] Sv::Integer(2)                     │
└─────────────────────────────────────────┘

Operationen:
  push(sv)      - Wert oben ablegen
  pop()         - Oberstes Element entfernen und zurückgeben
  pushmark()    - Listenkontextanfang markieren
  popmark()     - Werte seit der Markierung zurückgeben

PP-Dispatch (zweistufig)

execute_op(opcode)
       │
       ▼
dispatch_by_category(opcode.category())
       │
       ├─ META ──────────▶ dispatch_meta()
       ├─ ARITHMETIC ────▶ dispatch_arithmetic()
       ├─ STRINGS ───────▶ dispatch_strings()
       ├─ CONTROL ───────▶ dispatch_control()
       ├─ IO ────────────▶ dispatch_io()
       └─ ... (22 Kategorien)
              │
              ▼
       spezifische pp_*-Funktion

Beispiel: Binäroperator

#![allow(unused)]
fn main() {
// In pp/helpers.rs
fn pp_binop<F>(&mut self, op: F) -> RuntimeResult<()>
where
    F: Fn(&Sv, &Sv) -> Sv,
{
    let b = self.stack.pop()?;    // Rechten Operanden holen
    let a = self.stack.pop()?;    // Linken Operanden holen
    let result = op(&a, &b);      // Operation anwenden
    self.stack.push(result);      // Ergebnis ablegen
    Ok(())
}

// Verwendung für Addition:
self.pp_binop(|a, b| {
    Sv::Nv(a.to_number() + b.to_number())
})
}

Parser und Codegenerierung

Dreistufige Parserstrategie

Stufe 1 (Aktuell - Entwicklung):
  perl -MO=Concise → Op-Baum-Dump → PetaPerl interpretiert

Stufe 2 (Geplant - Produktion):
  FFI zu libperl → Op-Baum extrahieren → PetaPerl interpretiert/JIT-kompiliert

Stufe 3 (Langfristig):
  Nativer Rust-Parser → vollständige Unabhängigkeit

Begründung: „Only perl can parse Perl“ — Perls Grammatik ist kontextsensitiv. Der Tokenizer konsultiert die Symboltabelle während des Lexings. Unser Mehrwert liegt in der Laufzeit (Parallelismus, JIT), nicht im Parsing.

Pipeline des nativen Parsers

Quellcode
     │
     ▼
┌─────────────────────────────────────┐
│  Lexer (lexer.rs - 85KB)            │
│  - Kontextabhängige Tokenisierung   │
│  - Heredocs, Interpolation, Regex   │
└─────────────┬───────────────────────┘
              │ Token-Strom
              ▼
┌─────────────────────────────────────┐
│  Parser (parser.rs - 257KB)         │
│  - Rekursiver Abstieg               │
│  - Pratt-Präzedenzverfahren         │
│  - Vollständige Perl-Grammatik      │
└─────────────┬───────────────────────┘
              │ AST
              ▼
┌─────────────────────────────────────┐
│  Codegen (codegen.rs - 401KB)       │
│  - AST → OpArena-Absenkung          │
│  - Geltungsbereichsverfolgung       │
│  - Closure-Capture-Analyse          │
└─────────────┬───────────────────────┘
              │ OpArena
              ▼
         Interpreter

AST-Struktur

#![allow(unused)]
fn main() {
pub struct Ast {
    statements: Vec<Stmt>,
    subs: Vec<SubDef>,
    packages: Vec<String>,
}

pub struct Stmt {
    kind: StmtKind,
    span: Span,
}

pub enum StmtKind {
    Expression(Expr),
    VarDecl { name: String, init: Option<Expr> },
    If { cond: Expr, then: Block, else_: Option<Block> },
    While { cond: Expr, body: Block },
    For { init: Stmt, cond: Expr, incr: Expr, body: Block },
    Foreach { var: String, list: Expr, body: Block },
    SubDecl(SubDef),
    Return(Option<Expr>),
    // ...
}
}

Testinfrastruktur

Test-Harness (t/TEST)

┌─────────────────────────────────────────────────────────┐
│  perl t/TEST --skip=bytecode,tier                       │
│  - Läuft unter Perl 5 (NICHT pperl) zur Sicherheit     │
│  - Prozessisolation mit Ressourcenlimits               │
│  - --skip=bytecode,tier erforderlich für schnelle       │
│    Durchläufe (~30s)                                    │
├─────────────────────────────────────────────────────────┤
│  Schutzmaßnahmen:                                      │
│    - 30 Sekunden Zeitlimit pro Test                    │
│    - 512MB Speicherlimit                               │
│    - 60 Sekunden CPU-Limit                             │
│    - SIGTERM/SIGKILL bei Zeitüberschreitung            │
├─────────────────────────────────────────────────────────┤
│  Funktionen:                                           │
│    - TAP-Ausgabeparsing                                │
│    - Ergebnisverfolgung in .results/                   │
│    - Regressionserkennung (--compare)                  │
│    - Ausführlicher Modus (-v)                          │
│    - Überwachungsmodus (--monitor)                     │
└─────────────────────────────────────────────────────────┘

Testverzeichnisstruktur

t/
├── TEST                 # Perl5-Harness-Skript
├── .results/            # Zeitgestempelte Ergebnisdateien
│
├── 00-base/      (14)   # GATE-TESTS - müssen bestehen!
├── 01-parsing/   (12)   # Literalparsing
├── 05-op/        (42)   # Operatoren
├── 10-cmd/       (12)   # Kontrollfluss
├── 15-sub/       (19)   # Subroutinen
├── 20-data/      (37)   # Datenstrukturen
├── 22-unicode/   (80)   # Unicode-Unterstützung
├── 25-regex/     (12)   # Reguläre Ausdrücke
├── 30-special/   (3)    # Spezialvariablen
├── 40-package/   (24)   # Pakete & OOP
├── 45-builtin/   (111)  # Eingebaute Funktionen
├── 50-context/   (6)    # Kontextbehandlung
├── 55-io/        (67)   # Ein-/Ausgabe
├── 60-parallel/  (4)    # Parallelisierung
├── 65-jit/       (3)    # JIT-Kandidaten
├── 70-complex/   (1)    # Integrationstests
└── 99-debug/     (69)   # Regressionstests

Testdateiformat (TAP)

# Testbeschreibung
# Adaptiert von: perl5-upstream/t/op/something.t

print "1..5\n";  # Plan: 5 Tests

# Test 1
print 1 + 1 == 2 ? "ok 1 - addition\n" : "not ok 1\n";

# Test 2
my $x = "hello";
print length($x) == 5 ? "ok 2 - length\n" : "not ok 2\n";

# ... weitere Tests

Nummerierungskonvention für Tests

Dateien verwenden 5er-Lücken für zukünftige Einfügungen:

  005-print.t
  010-say.t       ← Lücke erlaubt 006, 007, 008, 009
  015-binmode.t
  020-open.t      ← Lücke erlaubt 016, 017, 018, 019
  ...

Entwicklungsablauf

Sichere Befehle

BefehlSicherheitZweck
cargo buildSICHERKompilieren
cargo test --libSICHERRust-Unit-Tests
perl t/TEST --skip=bytecode,tierSICHERIntegrationstests (~30s)
perl -c file.plSICHERSyntaxprüfung

Verbotene Befehle

BefehlGefahrGrund
cargo run -- file.plVERBOTENKann unbegrenzt hängen
echo 'x' | cargo runVERBOTENStdin + pperl = Hänger
Direkter pperl-AufrufVERBOTENKeine Ressourcenlimits

Standardentwicklungszyklus

# 1. Bauen
cargo build --release

# 2. Tests ausführen (IMMER --skip für schnelle Durchläufe!)
cd peta-perl && perl t/TEST --skip=bytecode,tier

# 3. Änderungen am Rust-Code vornehmen

# 4. Neu bauen
cargo build --release

# 5. Mit Vergleich testen
perl t/TEST --skip=bytecode,tier --compare    # Zeigt Regressionen/Verbesserungen

# 6. Bei Zufriedenheit committen

Einen neuen Test hinzufügen

# 1. Passendes Verzeichnis wählen
# 2. Nächste verfügbare Nummer mit 5er-Lücke wählen
# 3. Test im TAP-Format schreiben

# Beispiel: t/45-builtin/515-new-function.t
cat > t/45-builtin/515-new-function.t << 'EOF'
#!/usr/bin/env perl
# Test new_function() builtin

print "1..3\n";

print new_function(1) == 1 ? "ok 1\n" : "not ok 1\n";
print new_function(2) == 4 ? "ok 2\n" : "not ok 2\n";
print new_function(3) == 9 ? "ok 3\n" : "not ok 3\n";
EOF

# 4. Test ausführen
perl t/TEST --skip=bytecode,tier 45-builtin

Wesentliche Architekturentscheidungen

ADR-1: Arena-Allokation für Ops

Entscheidung: Ops in einem zusammenhängenden Vec<Op> mit indexbasierten Referenzen speichern.

Begründung:

  • Cache-Lokalität beim Durchlauf
  • Kein Referenzzählungs-Overhead
  • JIT-freundlich (linearer Speicher)
  • Einfache Serialisierung

ADR-2: Arc<str> für Zeichenketten

Entscheidung: Arc<str> für sämtliche Zeichenkettenspeicherung verwenden.

Begründung:

  • Günstiges Klonen (nur Referenzzähler erhöhen)
  • Thread-sicher für zukünftigen Parallelismus
  • Interning-fähig (häufige Zeichenketten können geteilt werden)

ADR-3: Zweistufiges Dispatch

Entscheidung: Ops nach Kategorie und dann nach spezifischem Opcode dispatchen.

Begründung:

  • Reduziert die Anzahl der Match-Arme pro Dispatch
  • Ermöglicht Optimierungen auf Kategorieebene
  • Saubere Modulstruktur

ADR-4: Cranelift für JIT (Zukunft)

Entscheidung: Cranelift verwenden, nicht LLVM oder dynasm-rs.

Begründung:

  • Rust-nativ mit hervorragender API
  • Produktionserprobt (Wasmtime)
  • Gute Balance: schnelle Kompilierung + ordentliche Codequalität

ADR-5: Rayon für Parallelismus (Zukunft)

Entscheidung: Rayon Work-Stealing auf Schleifenebene verwenden.

Begründung:

  • Kampferprobt, Rust-nativ
  • Work-Stealing verteilt die Last automatisch
  • Parallelismus auf Schleifenebene: 80% Nutzen, 20% Komplexität

Einstieg für Mitarbeiter

Schnellstart

# Klonen und bauen
git clone https://gl.petatech.eu/petatech/peta-perl.git
cd peta-perl
cargo build --release

# Tests ausführen (IMMER --skip für schnelle Durchläufe!)
perl t/TEST --skip=bytecode,tier

# Bestimmtes Testverzeichnis ausführen
perl t/TEST --skip=bytecode,tier 00-base
perl t/TEST --skip=bytecode,tier -v  # Ausführlich (Fehler anzeigen)

Die Codebasis verstehen

  1. Mit Werten beginnen (src/value/sv.rs)

    • Verstehen, wie Perl-Werte dargestellt werden
    • Schlüssel: Sv-Enum und seine Varianten
  2. Den Interpreter studieren (src/runtime/interpreter.rs)

    • Der run()-Methode folgen
    • Sehen, wie Ops dispatcht werden
  3. Ein PP-Modul erkunden (z.B. src/runtime/pp/strings.rs)

    • Sehen, wie spezifische Operationen implementiert sind
    • Muster: Operanden holen, berechnen, Ergebnis ablegen
  4. Eine Testdatei lesen (z.B. t/05-op/005-arith.t)

    • Erwartetes Perl-Verhalten verstehen
    • Als Spezifikation für die Implementierung verwenden

Häufige Aufgaben

Eine neue eingebaute Funktion implementieren:

  1. Opcode zu src/op/opcode/mod.rs hinzufügen
  2. Dispatch-Fall im passenden pp/*.rs-Modul ergänzen
  3. Die PP-Funktion implementieren
  4. Tests zu t/45-builtin/ hinzufügen

Einen fehlschlagenden Test beheben:

  1. Test ausführlich ausführen: perl t/TEST --skip=bytecode,tier -v 45-builtin
  2. Fehlschlagende Prüfung identifizieren
  3. Durch den Interpreter mit --trace-Flag nachverfolgen
  4. Implementierung korrigieren
  5. Verifizieren mit perl t/TEST --skip=bytecode,tier --compare

Eine neue Testdatei hinzufügen:

  1. TAP-Format einhalten
  2. 5er-Lücken-Nummerierung verwenden
  3. Tests fokussiert halten (10-30 Prüfungen)
  4. Randfälle testen

Wichtige Dateien

DateiZweckLesen bei
src/value/sv.rsSkalare WerteTypenverständnis
src/runtime/interpreter.rsHauptschleifeAusführungsverständnis
src/runtime/pp/dispatch.rsOp-RoutingNeue Ops hinzufügen
src/parser/codegen.rsCodegenerierungParser-Probleme
t/TESTTest-HarnessTestinfrastruktur
docs/TODO.mdAufgabenlisteArbeit finden

Hilfe erhalten

  • CLAUDE.md im übergeordneten Verzeichnis für Projektrichtlinien lesen
  • docs/TODO.md für aktuelle Prioritäten prüfen
  • t/99-debug/ für Regressionstest-Muster ansehen
  • Letzte Commits für Code-Stil-Beispiele durchsehen

Anhang: Glossar

BegriffDefinition
SvSkalarer Wert — beliebiger einzelner Perl-Wert
AvArray-Wert — Perl-Array
HvHash-Wert — Perl-Hash
CvCode-Wert — Perl-Subroutine
OpOperation — einzelne Instruktion im Op-Baum
OpIdIndex in die OpArena (u32-Wrapper)
OpArenaBehälter für alle Ops und Konstanten
PPPush-Pop — Funktion, die einen Opcode implementiert
PadSpeicher für lexikalische Variablen eines Geltungsbereichs
StashSymboltabelle eines Pakets
TAPTest Anything Protocol — Ausgabeformat für Tests
GlobTypeglob — Eintrag in der Symboltabelle