Files
AILang/docs/JOURNAL.md
T
Brummel a20ab93c66 Iter 5c: Cross-Module-Codegen
Symbol-Mangling-Schema einheitlich auf @ail_<modul>_<def> umgestellt
(auch für Single-Modul-Programme), String-Globals als
@.str_<modul>_<idx>. main bleibt LLVM-/C-ABI-Eintrittspunkt und ist
ein Trampoline auf @ail_<entry>_main. lower_workspace emittiert eine
einzige .ll für den ganzen Workspace, alphabetisch nach Modulname,
Cross-Module-Calls über Import-Map aufgelöst. ail build / ail emit-ir
laufen jetzt durch den Workspace-Pfad. IR-Snapshots regeneriert,
neuer ws_main-Snapshot. E2E-Test workspace_build_runs_imported_fn
prüft, dass das Binary die importierte Funktion korrekt aufruft.
Schuld #19 (source_filename) durch einheitliches <entry>.ail-Schema
geschlossen.
2026-05-07 11:39:59 +02:00

14 KiB

JOURNAL

Chronologische Notizen für mich. Nicht jede Änderung; nur Entscheidungen, Hindernisse, Beobachtungen, die zukünftige Iterationen brauchen.

2026-05-07 — Tag 0

  • Repo initialisiert. Auftrag in CLAUDE.md: LLM-native Sprache, LLVM-Backend.
  • Designentscheidungen festgehalten in docs/DESIGN.md.
  • Toolchain: rustc 1.94, llvm-config 22.1.3, clang vorhanden.
  • Entschieden gegen inkwell zugunsten LLVM-IR-Text-Emit. Begründung im DESIGN.md.
  • Workspace-Layout:
    • crates/ailang-core — AST, Type, Hash, JSON-Schema
    • crates/ailang-check — Typchecker (kommt später)
    • crates/ailang-codegen — Lowering + LLVM IR Emit
    • crates/ail — CLI
  • MVP-Ziel: examples/sum.ail.json → Binary, das 55 druckt. Erreicht.

2026-05-07 — Architektur-Review nach MVP

Wieder am Pfad? Im Großen ja. Konkrete Beobachtungen:

Was hält:

  • JSON-AST + canonical form + content-hash sind alle Lego-Steine, auf denen spätere Tools ohne Refactor aufsetzen können (ail deps, ail diff).
  • LLVM-IR-Text-Pipeline arbeitet wie geplant. Keine libllvm-Versionsschmerzen.
  • Effekt-Set ist im Typsystem von Anfang an verbaut. Erweiterbar zu row-poly, ohne den Kern zu ändern.

Schulden, die Zinsen tragen:

  1. current_block_label_for_phi ist eine Heuristik (siehe codegen). Bei verschachtelten if-Termen wird sie das falsche Block-Label zurückgeben, weil sie rückwärts den Body scannt. Tickend, weil keine Test-Fälle das bisher triggern. Muss als nächstes fixiert werden, bevor neue Sprachfeatures dazu kommen.
  2. Kein typed-AST. Codegen liest direkt das Quell-AST und verlässt sich darauf, dass der Typchecker vorher lief. Für MVP OK; sobald ADTs oder Closures dazukommen, brauche ich eine getrennte typisierte IR-Stufe (TIR).
  3. hash-Feld ist nicht im AST. Aktuell hashen wir das Def-Objekt direkt. Wenn ich Hashes später als Felder mitserialisiere (Caching), muss der Hash das Feld vor der Berechnung ausschließen.

Plan Iteration 2 (jetzt):

  1. Block-Label-Tracking sauber machen, verschachteltes-if-Test.
  2. Strings als Literal + io/print_str.
  3. Hello-world-Beispiel als zweiten E2E-Test.
  4. CLI: --json-Output für maschinelle Konsumenten überall, wo es passt.

Plan Iteration 3:

ADTs + Pattern Matching. Das ist der nächste große Sprung. Erfordert typisierte IR-Stufe (TIR), weil Pattern Matching zu Decision-Trees lowered wird, was eine andere Form ist als der AST.

2026-05-07 — Iteration 2 fertig

  • Block-Label-Tracking robust (verschachtelte ifs funktionieren). Test max3_picks_largest schützt das.
  • Strings als Lit::Str { value }, Type Str -> LLVM ptr, mit io/print_str Effekt-Op. examples/hello.ail.json druckt einen String.
  • CLI: manifest --json, builtins --json für Tool-Konsumenten.
  • ail deps [--of NAME] [--json] listet Aufruf-Edges. Effekt-Ops sind als effect:NAME markiert, damit ein Konsument sie filtern kann.

Architektur-Check: Keine strukturellen Abweichungen. Codegen liest noch direkt das Quell-AST (TIR-Stufe wird mit ADTs in Iteration 3 nötig).

2026-05-07 — Iteration 3 fertig: ADTs

  • TypeDef im AST mit Ctors. Ein Ctor hat name und fields: [Type...].
  • Term::Ctor (Konstruktion) und Term::Match (Pattern Matching).
  • Patterns: Wild, Var, Lit, Ctor { ctor, fields }. Im MVP sind nested Ctor-Patterns NICHT erlaubt — Sub-Patterns müssen Var oder Wild sein.
  • Typchecker mit Type-Registry und ctor_index (ctor-name → ADT). Im Match wird Exhaustiveness gegen die volle Konstruktormenge geprüft. Negativ-Test schützt das.
  • Codegen: Boxed-Heap-Layout. Pro Ctor-Anwendung malloc(8 + 8*n) Bytes; Tag in offset 0, Felder ab offset 8 (8-Byte-Slots, native typed load/store). Match: load tag + switch + arm-blocks + phi am Join.
  • examples/list.ail.json (Cons/Nil-Liste, sum_list über match) liefert 42.

Erstaunlich problemlos. Die Architekturentscheidungen aus Tag 0 haben sich ausgezahlt: opaque ptr in LLVM 22 macht Boxed-Layout fast ohne Glue-Code möglich; das Effekt-Tracking blieb von ADTs unberührt; der JSON-AST nimmt neue Knoten-Typen sauber auf.

Gewachsene Schulden:

  1. Codegen liest immer noch direkt den Quell-AST. Die Versuchung war stark, ohne TIR weiterzumachen — und hat funktioniert, weil meine Match-Restriktionen flach sind (keine nested Patterns). Sobald nested Patterns kommen, braucht es ein Decision-Tree-Lowering, das ohne TIR nicht sauber wird. Schulden anerkannt; nicht jetzt fällig.
  2. Kein GC. Heap leakt. Akzeptabel für Demo-Programme; muss vor jedem längerläufigen Programm angegangen werden. Optionen für Phase 4: Refcount, Boehm-GC-Linkage, Region-Inference.
  3. Pretty-Printer für ADT-Werte zur Laufzeit fehlt. io/print_int reicht für Demos, aber ein generisches show :: a -> Str für ADTs wäre wertvoll. Erfordert dispatch über tag — machbar, aber nicht jetzt.

Plan Iteration 4:

Die nächsten Schritte sind weniger eindeutig. Drei Kandidaten in Prioritätsreihenfolge:

  1. Modulsystem (Imports). Aktuell ist alles in einem Modul. Mit mehreren Modulen + Cross-Module-Hashing wird die Sprache erst praktikabel für mehrere Defs.
  2. Strukturierte Fehlerausgabe (ail check --json). Damit Tools auf Typfehler reagieren können, ohne Text zu parsen.
  3. Closures / höherwertige Funktionen. Erfordert Closure-Konvertierung und ist ein größerer Schritt.

Iteration 4 wird (1) + (2) — beides macht das LLM-Tooling stärker und hat moderates Risiko.

2026-05-07 — Workflow-Wechsel: Orchestrator + Agent-Repo

Auf Anregung des Auftraggebers Wechsel auf Orchestrator-Modus: ich delegiere abgrenzte Implementierungs-Brocken an Sub-Agenten und behalte nur Architektur-Entscheidungen, Reviews und Commit-Disziplin. Vier spezialisierte Agenten formuliert: implementer, architect, tester, debugger.

Wichtige Korrektur: Auftraggeber hat verlangt, dass die Agenten nicht in .claude/agents/ versteckt liegen, sondern als sichtbarer Bestandteil des Projekts unter agents/ versioniert werden. DESIGN.md hat einen neuen Abschnitt "Projekt-Ökosystem" bekommen, der das festhält: AILang ist nicht nur eine Sprache, sondern Sprachkern + CLI + Examples + Agents + Doku + Tests, alle gleichwertig.

Aufruf-Schema: System-Prompt-Body aus agents/<name>.md als Präfix vor die konkrete Aufgabe + an general-purpose-Agent. Funktional identisch zum Subagent-Loading aus .claude/agents/, aber sichtbar im Repo.

Plan Iteration 4 (überarbeitet):

Modulsystem ist aufwändiger als erwartet (Cross-Module-Hashing, Import-Resolution). Erst die kleineren Tooling-Wins ziehen, dann das Modulsystem als Iteration 5:

  1. Strukturierte Fehlerausgabe (ail check --json mit Diagnostic- Struct, stabile Codes wie unbound-var, type-mismatch).
  2. ail diff <a> <b> — semantischer Modul-Diff via Hash-Vergleich pro Def.
  3. IR-Snapshot-Tests — Regressions-Schutz für die Codegen-Pipeline.

2026-05-07 — Iteration 4 fertig: LLM-Tooling-Konsolidierung

Drei Sub-Commits, jeder von einem ailang-implementer-Aufruf erzeugt und stichprobenartig vom Orchestrator verifiziert:

  • 93fe723 Iter 4a: ail check --json mit Diagnostic-Struct (severity, code, message, def, ctx). Stabile Codes: unbound-var, type-mismatch, arity-mismatch, non-exhaustive-match, unknown-ctor, unknown-ctor-in-pattern, nested-ctor-pattern-not-allowed, duplicate-def, unknown-effect-op, unknown-type, schema-mismatch. API: check_module(&Module) -> Vec<Diagnostic>.
  • c652b12 Iter 4b: ail diff <a> <b> [--json] als struktureller Top- Level-Def-Diff via BLAKE3-Hash. Vier Kategorien (added/removed/ changed/unchanged), alphabetisch sortiert, Exit-Code 1 bei Diff.
  • 74a2005 Iter 4c: IR-Snapshot-Tests in crates/ail/tests/snapshots/{sum,max3,hello,list}.ll. Normalisierung von target triple. Update via UPDATE_SNAPSHOTS=1 cargo test ir_snapshot_. Mismatch erzeugt .actual-Datei.

Test-Stand: 28 (vorher 19). 7 E2E + 4 IR-Snapshot + 9 ailang-check + 1 ailang-codegen + 7 ailang-core.

Erledigt aus dem Schulden-Register:

  • Block-Tracking im Codegen ist seit Iter 2 kein Heuristik-Risiko mehr; die explizite current_block: String-Spur wurde durch Iter 4c jetzt zusätzlich von Snapshot-Tests gegen Regression geschützt. Schuld geschlossen.

Neue / präzisierte Schulden:

  1. check_module ist single-shot — der erste Fehler bricht ab, kein Multi-Diagnose-Sammeln. Spec war so, aber das Format suggeriert Vec-Semantik. Echter Multi-Diagnose-Refactor wird billiger, sobald TIR existiert (zentraler Fehler-Akku durch separate Stufe). Nicht jetzt fällig.
  2. source_filename im IR ist hartkodiert auf "<module>.ail". Solange es nur ein Top-Level-Modul gibt, plattform-stabil. Mit Iter 5 (Modulsystem + Imports) wird der Pfad relevant — dort gleich beim Bau pfad-unabhängig halten, sonst kippen die Snapshots.

Plan Iteration 5: Modulsystem mit Imports. Cross-Module-Hashing, Import-Auflösung, mehrere .ail.json-Files in einem Build. Multi- Diagnose-Refactor erst danach.

Sub-Schritte:

  • 5a — Workspace-Loader. ailang_core::Workspace { modules: BTreeMap<String, Module> } plus load_workspace(entry: &Path), das imports rekursiv vom Eintrittsmodul folgt. Konvention: import { module: "foo" } löst auf <dir>/foo.ail.json neben dem Entry. Zyklus- Erkennung. CLI: bestehende Subkommandos arbeiten weiter auf einzelnem Modul; ein neues ail workspace <entry> listet alle erreichbaren Module mit Hash. Tests: zwei kleine Beispielmodule mit Import-Beziehung; Zyklus-Test.
  • 5b — Cross-Module-Typcheck. Typchecker bekommt &Workspace statt &Module. Imports werden im Env als Namespace eingehängt (alias.def oder bei fehlendem Alias module.def). Neue Diagnostic- Codes: unknown-module, unknown-import, import-cycle, ambiguous-name. Tests pro Code.
  • 5c — Cross-Module-Codegen. Emitter erzeugt IR für alle Module im Workspace, prefix-mangelt mit @ail_<module>_<def>. E2E-Test: Programm, das eine Funktion aus Modul B in Modul A nutzt, liefert korrektes Ergebnis im Binary.
  • 5d — Tooling-Anpassungen. manifest, describe, deps, diff bekommen einen --workspace-Modus (rekursiv). Single-Modus bleibt Default für Rückwärtskompatibilität.

Während Iter 5 gleich beim Bau source_filename pfad-unabhängig halten (nur Modulname, kein Verzeichnis-Prefix), sonst kippen die IR-Snapshots.

2026-05-07 — Iter 5b fertig: Cross-Module-Typcheck

  • check_workspace(&Workspace) -> Vec<Diagnostic> als Top-Level-API. check_module bleibt erhalten und hebt das Modul intern in einen Trivial-Workspace.
  • Konvention für qualifizierte Verweise (in DESIGN.md festgehalten): Term::Var { name } mit genau einem Punkt = <prefix>.<def>. Prefix ist Import-Alias oder Modulname. Kein neuer AST-Knoten, keine umbenannten Felder ⇒ Hashes bleiben stabil; alle ir_snapshot_* weiterhin grün.
  • Drei neue Diagnostic-Codes: unknown-module, unknown-import, invalid-def-name (mit ctx.reason: "contains-dot").
  • CLI: ail check <entry> lädt jetzt immer über load_workspace. Workspace-Lade-Fehler werden im JSON-Modus zu strukturierten Diagnostics mit Codes module-not-found, module-cycle, module-name-mismatch, module-hash-mismatch, schema-mismatch. ail build und ail emit-ir bleiben pro Einzelmodul (Codegen cross-module ist 5c).
  • Beispiele: ws_main.ail.json ruft jetzt ws_lib.add auf (beobachtbar). Neu: ws_broken.ail.json (unknown-import), ws_unknown_module.ail.json (unknown-module).
  • Tests: 37 grün (vorher 32). 4 neue Workspace-Integrationstests in crates/ailang-check/tests/workspace.rs, ein neuer e2e-Test check_workspace_resolves_import.
  • Schulden: Single-Shot-Diagnostics weiterhin (Multi-Diagnose nach 5c). Punkt-Konvention deckt nur einen Punkt ab — verschachtelte Modulpfade (a.b.c) gibt es nicht; das wäre erst mit Hierarchie-Modulen ein Thema und fällt aktuell als unbound-var durch.

2026-05-07 — Iter 5c fertig: Cross-Module-Codegen

  • Mangling-Bruch (mit Bedacht). Alle AILang-Funktionen heißen jetzt @ail_<modul>_<def>, auch in Single-Modul-Programmen. Die alte Form @ail_<def> ist weg. Strings/Const-Globals analog (@.str_<modul>_<hint>_<idx>, @ail_<modul>_<const>). Eintrittspunkt bleibt main als C-ABI: ein define i32 @main()-Trampoline ruft @ail_<entry-modul>_main(). Fehlt im Eintrittsmodul ein main : () -> Unit !IO, fällt der Build mit MissingEntryMain.
  • Workspace-Lowering. Neue Top-Level-API ailang_codegen::lower_workspace(ws: &Workspace) -> Result<String> produziert eine einzelne .ll für den ganzen Workspace. Module alphabetisch (BTreeMap-Order); Defs in AST-Reihenfolge. Cross-Module- Calls werden im Codegen über die Import-Map des aufrufenden Moduls aufgelöst — gleiche Logik wie im Typchecker, lokal dupliziert mit Verweis (kein gemeinsames Helper-Modul, weil die Typ-Welten unterschiedlich sind: Typchecker hantiert mit Type, Codegen mit FnSig aus llvm-Typen).
  • CLI. ail build und ail emit-ir laden jetzt immer den Workspace und checken/lowern ihn vollständig. Single-Modul-Programme funktionieren weiter (Trivial-Workspace mit einem Modul). emit_ir(m) bleibt im Codegen-Crate als Bequemlichkeits-API erhalten und wrapt intern in einen Trivial-Workspace.
  • Snapshots regeneriert. sum.ll, max3.ll, hello.ll, list.ll zeigen das neue Mangling. Neuer ws_main.ll-Snapshot dokumentiert den Cross-Module-Build: @ail_ws_main_main ruft @ail_ws_lib_add auf.
  • Tests. 40 grün (vorher 37). Neu: workspace_build_runs_imported_fn (e2e: druckt 5), ir_snapshot_ws_main, missing_entry_main_is_error (codegen-unit). Bestehende Verhaltens-Tests (sum_1_to_10_is_55, max3_picks_largest, hello_world_str_lit, list_sum_via_match) bleiben grün — Verhalten unverändert, nur das Mangling ist neu.

Schulden geschlossen:

  • #19 (source_filename härten). In der Workspace-Welt ist source_filename jetzt einheitlich <entry-modul>.ail, einmal pro Workspace. Der bisherige hartkodierte Pfad-Punkt entfällt damit.

Stand: Modulsystem ist Ende-zu-Ende geschlossen — Loader + Typcheck

  • Codegen + Build sehen den Workspace als kohärente Einheit. Multi- Diagnose-Refactor und ggf. ADT-Cross-Module bleiben für später.