PetaPerl Architekturübersicht
Ein umfassender Leitfaden für Entwickler zum Verständnis und zur Mitarbeit an PetaPerl.
Inhaltsverzeichnis
- Vision und Ziele
- Architektur auf hoher Ebene
- Ausführungspipeline
- Quellcodestruktur
- Zentrale Datenstrukturen
- Der Op-Baum
- Wertesystem
- Laufzeitumgebung und Interpreter
- Parser und Codegenerierung
- Testinfrastruktur
- Entwicklungsablauf
- Wesentliche Architekturentscheidungen
- 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:
| Ziel | Beschreibung |
|---|---|
| Auto-Parallelisierung | Automatische parallele map-, grep-, for-Schleifen mittels Rayon Work-Stealing |
| JIT-Kompilierung | V8-Klasse-Performance über Cranelift (gestufte Kompilierung) |
| Pure-Perl-Tauglichkeit | Schnell 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änkung | Wert | Begründung |
|---|---|---|
| Plattform | Nur Linux | Einfachheit; beliebige Architektur |
| Perl-Version | 5.42+ stable | Nicht blead (5.43.x) |
| Lizenz | Proprietär | Geschä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
| Modul | Zeilen | Zweck |
|---|---|---|
| parser/ | ~19.700 | Lexing, Parsing, Codegenerierung |
| runtime/pp/ | ~45.500 | Implementierungen der PP-Operationen |
| value/ | ~8.500 | Werttypen (Sv, Av, Hv, Cv) |
| op/ | ~4.000 | Op-Baum-Strukturen |
| regex/ | ~6.500 | Regex-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) | |
|---|---|---|
| Entwurfsphilosophie | Eigenständige Rust-Implementierung | 1:1-Transliteration des Perl5-C-Interpreters |
| Wertdarstellung | Sv-Enum (32 Bytes, getaggte Union) | Sv(u64) NaN-geboxed, nachempfunden von perl5s SV* |
| Speichersicherheit | Durchgehend sicheres Rust | Umfangreicher unsafe-Code — Rohzeiger, manuelle Referenzzählung |
| Performance | ~25% von perl5 (ohne JIT) | Auf Augenhöhe mit perl5 |
| JIT-Kompilierung | Cranelift JIT für heiße Schleifen | Nicht verfügbar |
| Auto-Parallelisierung | Rayon Work-Stealing für parallele Schleifen | Nicht verfügbar |
| Kompatibilität | Hoch — deckt die üblichen Fälle ab | Sehr hoch — bildet perl5s subtile Randfälle nach |
| Native Module | Alle 47 Module | 17 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 einepp.rs(PP-Laufzeit) und optional einep5.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
| Befehl | Sicherheit | Zweck |
|---|---|---|
cargo build | SICHER | Kompilieren |
cargo test --lib | SICHER | Rust-Unit-Tests |
perl t/TEST --skip=bytecode,tier | SICHER | Integrationstests (~30s) |
perl -c file.pl | SICHER | Syntaxprüfung |
Verbotene Befehle
| Befehl | Gefahr | Grund |
|---|---|---|
cargo run -- file.pl | VERBOTEN | Kann unbegrenzt hängen |
echo 'x' | cargo run | VERBOTEN | Stdin + pperl = Hänger |
| Direkter pperl-Aufruf | VERBOTEN | Keine 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
-
Mit Werten beginnen (
src/value/sv.rs)- Verstehen, wie Perl-Werte dargestellt werden
- Schlüssel:
Sv-Enum und seine Varianten
-
Den Interpreter studieren (
src/runtime/interpreter.rs)- Der
run()-Methode folgen - Sehen, wie Ops dispatcht werden
- Der
-
Ein PP-Modul erkunden (z.B.
src/runtime/pp/strings.rs)- Sehen, wie spezifische Operationen implementiert sind
- Muster: Operanden holen, berechnen, Ergebnis ablegen
-
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:
- Opcode zu
src/op/opcode/mod.rshinzufügen - Dispatch-Fall im passenden
pp/*.rs-Modul ergänzen - Die PP-Funktion implementieren
- Tests zu
t/45-builtin/hinzufügen
Einen fehlschlagenden Test beheben:
- Test ausführlich ausführen:
perl t/TEST --skip=bytecode,tier -v 45-builtin - Fehlschlagende Prüfung identifizieren
- Durch den Interpreter mit
--trace-Flag nachverfolgen - Implementierung korrigieren
- Verifizieren mit
perl t/TEST --skip=bytecode,tier --compare
Eine neue Testdatei hinzufügen:
- TAP-Format einhalten
- 5er-Lücken-Nummerierung verwenden
- Tests fokussiert halten (10-30 Prüfungen)
- Randfälle testen
Wichtige Dateien
| Datei | Zweck | Lesen bei |
|---|---|---|
src/value/sv.rs | Skalare Werte | Typenverständnis |
src/runtime/interpreter.rs | Hauptschleife | Ausführungsverständnis |
src/runtime/pp/dispatch.rs | Op-Routing | Neue Ops hinzufügen |
src/parser/codegen.rs | Codegenerierung | Parser-Probleme |
t/TEST | Test-Harness | Testinfrastruktur |
docs/TODO.md | Aufgabenliste | Arbeit finden |
Hilfe erhalten
CLAUDE.mdim übergeordneten Verzeichnis für Projektrichtlinien lesendocs/TODO.mdfür aktuelle Prioritäten prüfent/99-debug/für Regressionstest-Muster ansehen- Letzte Commits für Code-Stil-Beispiele durchsehen
Anhang: Glossar
| Begriff | Definition |
|---|---|
| Sv | Skalarer Wert — beliebiger einzelner Perl-Wert |
| Av | Array-Wert — Perl-Array |
| Hv | Hash-Wert — Perl-Hash |
| Cv | Code-Wert — Perl-Subroutine |
| Op | Operation — einzelne Instruktion im Op-Baum |
| OpId | Index in die OpArena (u32-Wrapper) |
| OpArena | Behälter für alle Ops und Konstanten |
| PP | Push-Pop — Funktion, die einen Opcode implementiert |
| Pad | Speicher für lexikalische Variablen eines Geltungsbereichs |
| Stash | Symboltabelle eines Pakets |
| TAP | Test Anything Protocol — Ausgabeformat für Tests |
| Glob | Typeglob — Eintrag in der Symboltabelle |