Zum Inhalt

Jade Compiler — CompilerDB & Query/Provider-System

Status: Normativ
Version: 0.6.0
Scope: CompilerDB-Substrate, Stage-1-Bootstrap, Ring-1-Engine-Provider, Query-System, Descriptor-Materialisierung
Priorität: Bei Widerspruch mit anderen Dokumenten gilt diese Spec für CompilerDB-Interna.


Inhaltsverzeichnis

  1. Motivation und Designziele
  2. Architekturformel
  3. Kernkonzepte
  4. Query — Definition und Anatomie
  5. Provider-Modell
  6. CompilerDB — Struktur und Zuständigkeit
  7. Query-Auswertung
  8. Descriptoren, Seeds und Materialisierung
  9. db.* — TypeFn-Fassade
  10. Dependency-Tracking
  11. Lazy Revalidierung und Early Cutoff
  12. Durability
  13. Cancellation
  14. Arena-Integration
  15. Fehlerbehandlung und Diagnostics
  16. Compiler-Pipeline und Engines
  17. Tools und Consumer
  18. Query-Katalog
  19. Erweiterung — neue Queries und Provider
  20. Normative Invarianten
  21. Implementierungshinweise

1. Motivation und Designziele

1.1 Das Problem

Ein Compiler besteht aus mehreren Compile-Time-Domänen: Source, Syntax, Name-Resolution, Typen, Meta-Records, Layouts, Dispatch, Effekte, IR, Artefakte und Diagnostics. Diese Domänen benötigen Informationen voneinander. Ohne ein strukturiertes Query-System entstehen die üblichen Freuden technischer Verwahrlosung:

  • direkte Kopplungen zwischen Phasen,
  • unklare Zuständigkeiten,
  • keine saubere Inkrementalität,
  • widersprüchliche Cache-Strategien,
  • schwer testbare Spezialpfade,
  • D-Subsystemlogik, die sich schleichend in JDL-Semantik verwandelt.

Die alte Sicht lautete: Parser, TypeEngine, Resolver und Generator sind Subsysteme, die direkt über D-Handler Query-Ergebnisse bauen. Diese Sicht ist für Stage 1 nützlich, aber nicht mehr der normative Architekturkern.

Die neue Sicht lautet:

Die CompilerDB berechnet keine hohe JDL-Semantik. Sie verwaltet deterministische Compile-Time-Queries, Seeds, Descriptoren, Dependency-Tracking und Materialisierung. Semantik wird durch QueryProvider geliefert: im Bootstrap durch D, danach zunehmend durch Ring-1-JDL-Engines.

1.2 Die Lösung: Query/Provider-System

Alle Compile-Time-Kommunikation läuft über typisierte Queries:

auto result = db.query(SomeQuery { ...params });

Eine Query beschreibt, was bekannt sein soll. Ein Provider beschreibt, wie dieses Wissen für eine bestimmte Stage erzeugt wird.

Query          = typisierte Anfrage
Provider       = berechnet oder liefert das Ergebnis
CompilerDB     = Cache, DepGraph, Seeds, DescriptorStore, Materializer
Descriptor     = materialisiertes Compile-Time-Artefakt

Damit bleibt das Query-System stabil, auch wenn die Implementierung eines Providers von D nach JDL wandert.

1.3 Designziele

Ziel Beschreibung
Entkopplung Phasen kennen Queries, nicht fremde interne Zustände.
Stage-Fähigkeit Stage 1 nutzt D-BootstrapProvider; Stage 2+ nutzt Ring-1-EngineProvider.
Determinismus Gleiche Inputs und gleiche Provider-Version erzeugen gleiche Query-Ergebnisse.
Inkrementalität Änderungen revalidieren nur abhängige Query-Ergebnisse.
Descriptor-Zentralität Type-, Layout-, Dispatch-, FFI- und Generator-Artefakte sind materialisierte Descriptoren.
Testbarkeit Jede Query und jeder Provider ist isoliert testbar.
Debuggbarkeit DepGraph, Proof Trace und Materialisierungslog sind inspizierbar.
Ring-Grenzen D bleibt Substrate; JDL-Engines erzeugen Semantik über Descriptoren.

1.4 Philosophische Konsistenz

Die CompilerDB ist die Compile-Time-Entsprechung zum RuntimeBus:

CompilerDB   -> Query      -> Compile-Zeit, deterministisch, cachebar
RuntimeBus   -> Command    -> Laufzeit, zustandsändernd, nicht cachebar

Beide sind zentrale Substrate. Beide vermeiden direkte Fremdzugriffe und machen Zuständigkeiten sichtbar. Die zusätzliche Struktur ist Teil des Sicherheits- und Wartbarkeitsmodells.


2. Architekturformel

D / Ring 0      = Substrate + Trust Anchor
JDL / Ring 1    = Semantik + Evolution Layer
CompilerDB      = Compile-Time Query/Descriptor-Substrate
RuntimeBus      = Runtime Command-Substrate
Engine          = JDL-seitige Auswertung und Descriptor-Erzeugung
Descriptor      = materialisierter Vertrag zwischen Semantik und Maschine

Die CompilerDB besitzt keine hohe JDL-Domänenlogik. Sie verwaltet:

  • QueryKeys,
  • QueryResults,
  • Provider-Registrierung,
  • Dependency-Graph,
  • Revisionen,
  • Seeds,
  • Descriptoren,
  • Materialisierungspatches,
  • Diagnostics,
  • Proof-Traces,
  • Arenen und Cache-Lebenszeiten.

Die CompilerDB darf Stage-1-D-Provider aufrufen. Ab Stage 2 darf sie auch EngineProvider aufrufen, deren Implementierung als JDL-Bytecode vorliegt. Der Wechsel des Providers darf die Query-Semantik nicht ändern, solange Query-Signatur, Eingaben und Descriptor-Schema gleich bleiben.


3. Kernkonzepte

3.1 Query

Eine Query ist eine typisierte, idempotente Anfrage an die CompilerDB. Sie beschreibt was man wissen will, nicht wie es berechnet wird.

struct ResolveType {
    TypeId id;
    // -> JadeResult!TypeDescriptor
}

Eigenschaften:

  • deterministisch,
  • cachebar,
  • dependency-trackbar,
  • ohne beobachtbare Seiteneffekte,
  • durch genau einen aktiven Provider berechenbar.

3.2 Input Query

Input Queries werden von außen gesetzt. Sie haben keinen Provider.

struct FileContent { string path; }     // -> string
struct BuildConfig { }                  // -> BuildConfigDescriptor
struct ModuleList  { }                  // -> ModulePath[]

Schreiben erfolgt über setInput. Jede Änderung erhöht die Revision.

3.3 Derived Query

Derived Queries werden aus anderen Queries berechnet. Sie haben genau einen aktiven Provider.

struct GetFileAST      { SourceFileId file; }     // -> JadeResult!AstModule
struct ResolveType     { TypeRef ref_; }          // -> JadeResult!TypeDescriptor
struct GetMemoryLayout { TypeId id; }             // -> JadeResult!LayoutDescriptor

3.4 Materialized Query

Eine Materialized Query liest ein bereits materialisiertes Descriptor- oder Seed-Artefakt.

struct GetTypeDescriptor { TypeId id; }           // -> JadeResult!TypeDescriptor
struct GetRing0Facts     { TypeId id; }           // -> JadeResult!Ring0FactSet
struct GetDispatchTable  { ProtocolId p; TypeId t; } // -> JadeResult!DispatchDescriptor

Materialized Queries werden nicht frei berechnet. Sie lesen aus dem DescriptorStore oder SeedStore. Materialisierung geschieht atomar über Patches.

3.5 QueryProvider

Ein QueryProvider berechnet oder liefert das Ergebnis einer Derived Query.

Provider-Arten:

BootstrapProvider  D-Handler fuer Stage 1
EngineProvider     Ring-1-JDL-Engine ab Stage 2
BuiltinProvider    feste Ring-0-Facts, Intrinsic-Tabelle, primitive Seeds
GeneratedProvider  aus bereits materialisierten Descriptoren abgeleitet

Die CompilerDB registriert Provider. Sie besitzt deren Semantik nicht.

3.6 SeedRecord

Ein SeedRecord ist ein Stage-1-Input mit axiomatischem Charakter.

Beispiele:

primitive VM-Typen
Ring-0-Facts
Intrinsic-IDs und Signaturen
Core-Protocols
minimale TypeDescriptoren
Bootstrap-Module
Descriptor-Schemata

Seeds sind die Brücke zwischen D-Bootstrap und Ring-1-JDL-Engines.

3.7 Descriptor

Ein Descriptor ist ein geprüftes, materialisiertes Compile-Time-Artefakt:

TypeDescriptor
LayoutDescriptor
LabelProfile
DispatchDescriptor
BindingTableDescriptor
FfiCallDescriptor
MarshalDescriptor
IrModule
LoweredModule
Proto
VmModuleArtifact
EngineDescriptor

Descriptoren sind kein Kommentar zum Build. Sie sind der Vertrag, den spätere Phasen und Ring-0-Domänen konsumieren.

3.8 MaterializationPatch

TypeFns und Engines schreiben nicht direkt in beliebige CompilerDB-Tabellen. Sie erzeugen Patches:

MetaPatch
TypeDescriptorPatch
ProvidePatch
DispatchPatch
ArtifactPatch
DiagnosticPatch

Der Materializer validiert und committed diese Patches atomar. Dadurch bleiben Cache, DepGraph und Proof Trace konsistent.


4. Query — Definition und Anatomie

4.1 Query-Struktur

Eine Query ist ein reiner Datensatz. Felder sind Parameter. Methoden gehören nicht in die Query.

struct LookupSymbol {
    SymbolName name;
    ScopeId    scope;
    // -> JadeResult!SymbolDescriptor
}

4.2 QueryKey

Jede Query hat einen stabilen Cache-Schlüssel:

struct QueryKey {
    TypeTag tag;      // Query-Art
    ubyte[] data;     // kanonisch serialisierte Parameter
}

Normativ:

  • identische Query-Parameter erzeugen denselben QueryKey,
  • Serialisierung ist kanonisch,
  • Feldreihenfolge ist stabil,
  • keine Host-spezifischen Pointerwerte im Key,
  • Provider-Version und Stage-Profil sind Teil des effektiven Schlüsselraums, wenn sie das Ergebnis beeinflussen.

4.3 QueryResult

struct QueryResult {
    ubyte[]    data;
    TypeTag    tag;
    ulong      changedAt;
    ulong      verifiedAt;
    Durability durability;
    Hash       contentHash;
    ProofId    proof;
}

Semantik:

  • changedAt: Revision, in der sich der Inhalt zuletzt geändert hat.
  • verifiedAt: Revision, in der das Ergebnis zuletzt revalidiert wurde.
  • durability: höchste Änderungsfrequenz transitiver Inputs.
  • contentHash: kanonischer Hash des Ergebnisses.
  • proof: Verweis auf Proof Trace oder Materialisierungsursprung.

4.4 Query-Ergebnisarten

Einzelwert-Queries verwenden JadeResult[T]:

Ok(T)       Erfolg
Err(Diagnostic)
Empty       korrekt ausgeführt, aber kein Wert

Check-Queries geben DiagnosticSet oder Diagnostic[] zurück.


5. Provider-Modell

5.1 Grundregel

Für jede Derived Query gibt es pro Stage-Profil genau einen aktiven Provider.

ResolveType in Stage 1 -> BootstrapTypeProvider
ResolveType in Stage 2 -> TypeEngineProvider

Mehrere Provider für dieselbe Query sind nur zulässig, wenn die Auswahl durch Stage, Target, FeatureSet oder BuildProfile eindeutig ist.

5.2 BootstrapProvider

BootstrapProvider sind D-Funktionen. Sie existieren, damit Stage 1 die CompilerDB überhaupt befüllen kann.

@handles!GetSeedTypeDescriptor
JadeResult!TypeDescriptor getSeedTypeDescriptor(GetSeedTypeDescriptor q, CompilerDb* db)
    @nogc nothrow
{
    return db.seedStore.getTypeDescriptor(q.id);
}

@handles ist damit eine D-seitige Provider-Registrierung, nicht das normative Query-Modell selbst.

5.3 EngineProvider

EngineProvider sind Ring-1-JDL-Engines, die eine Query beantworten.

Konzeptionell:

type ResolveTypeSpec : blueprint {
    subject: TypeRef
}

provide DescriptorEngine[ResolveTypeSpec, TypeDescriptor] for TypeEngine {
    def describe(spec: ResolveTypeSpec) -> Result[TypeDescriptor, DiagnosticSet] {
        // liest CompilerDB-Queries, erzeugt Descriptor
    }
}

Die CompilerDB ruft EngineProvider über eine VM-interne, deterministische Stage-2-Ausführung auf. Der Provider darf nur über die CompilerDB-Fassade lesen und Patches zurückgeben.

5.4 BuiltinProvider

BuiltinProvider liefern axiomatische Facts:

  • primitive Ring-0-Facts,
  • Intrinsic-Tabellen,
  • Core-Opcode-Descriptoren,
  • Bootstrap-Symbole.

Diese Facts sind lesbar, aber nicht durch TypeFns überschreibbar.

5.5 GeneratedProvider

GeneratedProvider beantworten Queries aus materialisierten Descriptoren.

Beispiel:

GetMemoryLayout(TypeId)
  liest LayoutDescriptor aus DescriptorStore

Der Provider berechnet keine neue Semantik. Er projiziert vorhandene Descriptoren in eine Query-Antwort.

5.6 Provider-Reinheit

Provider müssen deterministisch sein. Für Derived Queries gilt:

  • kein Dateisystemzugriff außer über Input Queries,
  • kein Netzwerk,
  • keine Uhrzeit,
  • kein zufälliger Seed,
  • keine RuntimeBus-Commands,
  • keine direkte Mutation fremder Domänen.

Stage-1-D-Provider dürfen intern D-Strukturen nutzen, müssen aber nach außen denselben Query-Vertrag erfüllen wie spätere EngineProvider. Ja, auch wenn D hier „eh alles könnte“. Das ist genau der Grund, es nicht zu tun.


6. CompilerDB — Struktur und Zuständigkeit

6.1 Normative Struktur

Die CompilerDB besitzt keine direkten semantischen Subsystem-Pointer als normativen Bestandteil des Modells. Eine Implementierung darf interne Bootstrap-Objekte verwenden, aber sie sind Provider-Details, nicht CompilerDB-Semantik.

Normativ ist:

struct CompilerDb {
    QueryCache       cache;
    DepGraph         deps;
    ProviderRegistry providers;
    SeedStore        seeds;
    DescriptorStore  descriptors;
    Materializer     materializer;
    DiagnosticStore  diagnostics;
    ProofStore       proofs;

    Arena            keyArena;
    Arena            resultArena;
    Arena            depArena;
    Arena            proofArena;

    ulong            currentRevision;
    ActiveQueryStack activeQueries;
    DurabilityState  durability;
    Cancellation     cancellation;
}

6.2 Zuständigkeit

Die CompilerDB darf:

  • Inputs setzen,
  • Queries auswerten,
  • Provider auswählen,
  • Ergebnisse cachen,
  • Dependencies tracken,
  • Descriptoren speichern,
  • Patches materialisieren,
  • Diagnostics sammeln,
  • Proof-Traces referenzieren.

Die CompilerDB darf nicht:

  • JDL-Domänenlogik hart kodieren,
  • Ring-0-Facts überschreiben,
  • RuntimeBus-Commands ausführen,
  • Scheduler/JME/FFI-Zustand manipulieren,
  • beliebige TypeFn-Mutation zulassen,
  • Provider-internen Zustand als öffentliche API exponieren.

6.3 Input setzen

void setInput(Q, V)(CompilerDb* db, Q input, V value, Durability dur = Durability.Medium) {
    auto key = makeKey(input, db.keyArena);
    auto data = serializeCanonical(value, db.resultArena);

    auto prev = db.cache.get(key);
    bool changed = prev is null || !bytesEqual(prev.data, data);

    db.cache.put(key, QueryResult {
        data:       data,
        tag:        TypeTag.of!V,
        changedAt:  changed ? db.currentRevision : prev.changedAt,
        verifiedAt: db.currentRevision,
        durability: dur,
        contentHash: hash(data),
        proof:      db.proofs.input(input)
    });

    if (changed)
        db.durability.markChanged(dur, db.currentRevision);

    db.currentRevision++;
}

Eager-Invalidierung ist verboten. Revalidierung ist lazy.


7. Query-Auswertung

7.1 Algorithmus

T query(T, Q)(CompilerDb* db, Q q)
    if (isQuery!Q)
{
    auto key = makeKey(q, db.keyArena);

    if (db.activeQueries.contains(key))
        return handleCycle!T(db, q);

    if (auto cached = db.cache.get(key)) {
        if (cached.verifiedAt == db.currentRevision) {
            trackDep(db, key);
            return deserialize!T(cached.data);
        }

        if (canSkipRevalidation(db, key, cached)) {
            cached.verifiedAt = db.currentRevision;
            trackDep(db, key);
            return deserialize!T(cached.data);
        }

        bool depsChanged = false;
        foreach (dep; db.deps.dependenciesOf(key)) {
            revalidate(db, dep);
            auto depResult = db.cache.get(dep);
            if (depResult !is null && depResult.changedAt > cached.verifiedAt) {
                depsChanged = true;
                break;
            }
        }

        if (!depsChanged) {
            cached.verifiedAt = db.currentRevision;
            trackDep(db, key);
            return deserialize!T(cached.data);
        }
    }

    return recompute!T(db, q, key);
}

7.2 Neuberechnung

T recompute(T, Q)(CompilerDb* db, Q q, QueryKey key) {
    auto prevActive = db.activeQueries.push(key);
    db.deps.clearDependencies(key);
    scope(exit) db.activeQueries.pop(prevActive);

    checkCancellation(db);

    auto provider = db.providers.resolve!Q(db.stageProfile);
    auto result = provider.compute!T(q, db);

    auto data = serializeCanonical(result, db.resultArena);
    auto cached = db.cache.get(key);
    bool changed = cached is null || !bytesEqual(cached.data, data);

    db.cache.put(key, QueryResult {
        data:       data,
        tag:        TypeTag.of!T,
        changedAt:  changed ? db.currentRevision : cached.changedAt,
        verifiedAt: db.currentRevision,
        durability: computeDurability(db, key),
        contentHash: hash(data),
        proof:      db.proofs.provider(provider, key)
    });

    trackDep(db, key);
    return result;
}

7.3 Zyklusbehandlung

Query-Zyklen sind Fehler, außer die Query ist explizit als zyklustolerant spezifiziert. Jade vermeidet allgemeine Fixpoint-Berechnung im TypeFn- und Descriptor-Pfad.

Bei Zyklus:

ResolveType(A) -> ResolveType(B) -> ResolveType(A)

liefert die CompilerDB ein strukturiertes Diagnostic mit Query-Trace.


8. Descriptoren, Seeds und Materialisierung

8.1 SeedStore

Der SeedStore enthält axiomatische Stage-1-Einträge:

PrimitiveTypeSeed
Ring0FactSeed
IntrinsicSeed
CoreProtocolSeed
BootstrapModuleSeed
DescriptorSchemaSeed

Seeds gelten als Input-Queries mit hoher Durability.

8.2 DescriptorStore

Der DescriptorStore enthält materialisierte Compile-Time-Artefakte:

TypeDescriptor
LayoutDescriptor
MetaRecordDescriptor
ProvideDescriptor
DispatchDescriptor
FfiCallDescriptor
IrModule
LoweredModule
VmModuleArtifact

Descriptoren sind immutable nach Commit. Änderungen erzeugen neue Descriptor-Versionen oder neue Revisionen.

8.3 MaterializationPatch

Provider und TypeFns erzeugen Patches:

type MaterializationPatch : enum =
    | SetMeta        { target: TypeSubject, field: str, value: Tag }
    | AddType        { desc: TypeDescriptor }
    | AddProvide     { desc: ProvideDescriptor }
    | AddDispatch    { desc: DispatchDescriptor }
    | AddArtifact    { desc: ArtifactDescriptor }
    | AddDiagnostic  { entry: DiagnosticEntry }

Der Materializer prüft:

  • Ring-Level,
  • Ring-0-Facts,
  • TypeSubject-Kompatibilität,
  • Descriptor-Schema,
  • Coherence-Regeln,
  • Provider-Berechtigung,
  • Konflikte mit bestehender Materialisierung.

Erst danach wird committed.

8.4 Kein Direkt-Schreiben aus TypeFns

db.setMeta(...) in TypeFns ist syntaktische Fassade. Semantisch erzeugt es einen MetaPatch. Der Patch wird nach erfolgreicher TypeFn-Auswertung atomar materialisiert.

TypeFn-Body -> PatchSet -> Materializer -> DescriptorStore/MetaRecordStore

Dadurch bleiben Diagnostics, Rollback, Caching und Proof Trace konsistent. Direkte Mutation ist verboten, weil sie partielle Compilerzustände erzeugen könnte.

8.5 TypeFnContext und ApplyRefinement

TypeFns werden nicht mit rohem CompilerDB-Zugriff ausgeführt. Jeder TypeFn-Aufruf erhält intern einen TypeFnContext:

// kanonische Struktur, nicht user-facing Syntax
type TypeFnContext : struct {
    db:        TypeFnDbView
    target:    Option[TypeSubject]
    phase:     TypeFnPhase
    namespace: ModulePath
    proof:     ProofTraceBuilder
}

db im TypeFn-Body ist context.db. Die Fassade darf Queries lesen und Patches erzeugen, aber keine Descriptoren selbst committen.

Refinements werden als ApplyRefinement-Records erfasst:

ApplyRefinement {
    target:     TypeSubject
    refinement: TypeFnId
    args:       [Tag]
}

Die Query EvaluateRefinement evaluiert diese Anwendung und liefert ein PatchSet. Der Materializer committed dieses PatchSet erst nach Ring-, Coherence- und Konfliktprüfung.

8.6 Ring-0-Facts

Ring-0-Facts primitiver VM-Typen sind axiomatische Seeds:

size
align
memory
registerClass
vmClass
numericClass

Sie sind über Queries lesbar:

GetRing0Facts(TypeId) -> Ring0FactSet
GetRing0Fact(TypeId, field) -> Option[Tag]

Sie sind nicht durch Patches überschreibbar.


9. db.* — TypeFn-Fassade

9.1 Grundregel

db.* ist keine freie Datenbank-API. Jeder db.*-Aufruf ist eine kuratierte Fassade über eine definierte Query oder einen MaterializationPatch.

db ist immer die TypeFnDbView aus dem TypeFnContext, nicht die rohe CompilerDB. Source-seitig wirkt db implizit; kanonisch ist jeder Zugriff context.db.*.

db.target()         -> Context Target
db.fields(of: T)      -> Query GetFields(T)
db.meta(of: T, f)     -> Query GetMeta(T, f)
db.ring0Fact(T, f)    -> Query GetRing0Fact(T, f)
db.hasProvide(P,args) -> Query LookupProvide(P,args)
db.setMeta(...)       -> MetaPatch

9.2 Lesende Fassade

db.target() -> TypeSubject
db.fields(of: TypeSubject) -> [FieldDef]
db.typeOf(field: FieldDef) -> Type
db.name(t: TypeSubject) -> str
db.typeParam(t: TypeSubject, index: i32) -> Type
db.phantomParam(t: TypeSubject, name: str) -> Tag
db.refinements(of: TypeSubject) -> [Refinement]
db.meta(of: TypeSubject, field: str) -> Option[Tag]
db.metaRing(field: str) -> Ring
db.hasProvide(protocol, args) -> bool
db.variants(of: TypeSubject) -> [VariantDef]
db.isMutable(field: FieldDef) -> bool
db.isConcreteType(t: Type) -> bool
db.typeKind(t: TypeSubject) -> TypeKind
db.ring0Fact(t: TypeSubject, field: str) -> Option[Tag]
db.isRing0Fact(t: TypeSubject, field: str) -> bool

db.target() ist nur während der Auswertung einer Refinement-TypeFn definiert. Außerhalb eines ApplyRefinement-Contexts ist der Zugriff ein Compile-Time-Fehler.

9.3 Schreibende Fassade

Schreibende Operationen erzeugen Patches:

db.setMeta(target, field: str, value: Tag) -> MetaPatch
db.createStruct(name: str, fields) -> TypeDescriptorPatch
db.createEnum(name: str, variants) -> TypeDescriptorPatch
db.fnType(params, returnType) -> TypeDescriptorPatch
db.constructType(base, args) -> TypeDescriptorPatch
db.constructRefinement(r, args) -> RefinementPatch
db.addProvide(desc) -> ProvidePatch

Eine TypeFn darf Patches erzeugen, aber nicht committed. Commit ist Aufgabe des Materializers.

9.4 Diagnostics

db.diagnostic(entry: DiagnosticEntry) -> DiagnosticPatch
db.Never -> Type

db.Never ist der Bottom-Typ nach einem Fehler und verhindert Folgefehler, ohne die Auswertung mit Exceptions zu zerreißen.


10. Dependency-Tracking

10.1 Automatisches Tracking

Wenn Query A während ihrer Auswertung Query B liest, registriert die CompilerDB:

A -> B

Beispiel:

CheckFunction(f)
  -> BuildFunctionIR(f)
  -> ResolveType(T)
  -> GetFields(T)
  -> GetFileAST(file)
  -> FileContent(file)

Der Provider muss keine Kanten manuell eintragen.

10.2 DepGraph

struct DepGraph {
    QueryKey[][QueryKey] dependencies;
    QueryKey[][QueryKey] dependents;

    void addEdge(QueryKey from, QueryKey to);
    QueryKey[] dependenciesOf(QueryKey key);
    QueryKey[] dependentsOf(QueryKey key);
    void clearDependencies(QueryKey key);
}

dependentsOf ist für Debugging und optionale Tools erlaubt. Normative Revalidierung nutzt dependenciesOf lazy.

10.3 Keine fremden direkten Abfragen

Provider dürfen andere Phasen nicht direkt aufrufen.

Nicht zulässig:

return directTypeEngineResolve(ast, symbol);   // verboten: direkter Phasenaufruf

Zulässig:

auto ast = db.query!(JadeResult!AstModule)(GetFileAST { file });
auto sym = db.query!(JadeResult!SymbolDescriptor)(LookupSymbol { name, scope });

Auch Ring-1-Engines lesen ausschließlich über die Query-Fassade.


11. Lazy Revalidierung und Early Cutoff

11.1 Prinzip

Bei Input-Änderung wird nichts transitiv invalidiert. Die CompilerDB erhöht die Revision. Query-Ergebnisse werden erst beim nächsten Zugriff revalidiert.

Ein Ergebnis wird nur neu berechnet, wenn eine Dependency sich inhaltlich geändert hat.

11.2 Beispiel

Kommentaränderung:

FileContent.changedAt = 12
GetFileAST neu berechnet, AST contentHash identisch
GetFileAST.changedAt bleibt 7
ResolveType sieht keine geänderte Dependency
CheckFunction bleibt gültig

Typänderung:

FileContent.changedAt = 13
GetFileAST contentHash geändert
ResolveType neu berechnet
CheckFunction neu berechnet

11.3 Ergebnis-Early-Cutoff

Wenn ein Provider neu läuft, aber dasselbe kanonische Ergebnis erzeugt, bleibt changedAt unverändert. Abhängige Queries werden dadurch nicht unnötig neu berechnet.


12. Durability

12.1 Klassifikation

enum Durability {
    Low,      // aktive Datei, Editor-Input
    Medium,   // Projektdateien, lokale Konfiguration
    High      // Stdlib, Seeds, externe pinned Dependencies
}

Seeds, primitive Ring-0-Facts und Compiler-Versionen haben High-Durability.

12.2 Kurzschluss

Eine Query speichert die maximale Durability ihrer transitiven Inputs. Wenn seit der letzten Verifikation keine Inputs dieser Durability-Klasse geändert wurden, kann die Revalidierung übersprungen werden.

bool canSkipRevalidation(CompilerDb* db, QueryKey key, QueryResult cached) {
    return cached.verifiedAt >= db.durability.lastChanged(cached.durability);
}

13. Cancellation

13.1 Zweck

LSP- und REPL-Szenarien erzeugen häufig neue Revisionen, während alte Berechnungen noch laufen. Cancellation verhindert, dass veraltete Provider unnötig weiterrechnen.

13.2 Regel

Cancellation darf den Cache nicht korrumpieren.

  • Vor cache.put abgebrochene Berechnungen hinterlassen den alten Cache.
  • Patches abgebrochener Provider werden nicht committed.
  • Dependencies werden bei der nächsten vollständigen Neuberechnung sauber ersetzt.

13.3 Checkpoints

Checkpoints sind zulässig:

  • vor Provider-Aufruf,
  • vor teuren Materialisierungsschritten,
  • zwischen EngineProvider-Phasen.

Cancellation ist keine semantische Änderung. Sie beendet nur eine Berechnung.


14. Arena-Integration

14.1 Arena-Arten

keyArena       QueryKeys
resultArena    serialisierte Query-Ergebnisse
depArena       Dependency-Kanten
proofArena     Proof-Trace-Strukturen
providerArena  temporäre Provider-Daten

Provider dürfen temporäre Arenen nutzen, aber Query-Ergebnisse, die über Provider-Grenzen hinaus leben, müssen in resultArena oder DescriptorStore kopiert werden.

14.2 Descriptor-Lebenszeit

Descriptoren leben mindestens so lange wie die CompilerDB-Revision, in der sie gültig sind. Persistente Descriptoren können in einem separaten DescriptorStore über Revisionen hinweg gehalten werden.

14.3 Keine fremden Arena-Pointer

Query-Ergebnisse dürfen keine Pointer in Provider-interne Arenen enthalten. Sie müssen kanonisch serialisierbar sein.


15. Fehlerbehandlung und Diagnostics

15.1 Diagnostics als Werte

Provider werfen keine semantischen Exceptions. Fehler sind Werte:

JadeResult!TypeDescriptor resolveType(...)

oder Check-Ergebnisse:

DiagnosticSet checkModule(...)

15.2 JadeResult

Ok(T)       Ergebnis vorhanden
Err(DiagnosticEntry)
Empty       korrekt, aber kein Ergebnis

15.3 Diagnostic-Struktur

struct DiagnosticEntry {
    Phase      phase;
    Severity   severity;
    SourceSpan span;
    string     summary;
    string     explanation;
    Hint[]     hints;
    Note[]     notes;
    ProofId    proof;
}

Diagnostics können auf Proof-Traces zeigen. Das ist besonders wichtig für TypeFns, Refinements, Provides und Ring-0-Fact-Konflikte.


16. Compiler-Pipeline und Engines

16.1 Stage-1-Pipeline

Stage 1 darf D-Provider verwenden:

Source
  -> BootstrapTokenizerProvider
  -> BootstrapParserProvider
  -> BootstrapTypeProvider
  -> SeedDescriptorProvider
  -> MinimalIRProvider
  -> BootstrapGeneratorProvider

Diese Provider sind Implementierungswerkzeuge, nicht die finale Semantik.

16.2 Stage-2-Pipeline

Ab Stage 2 sind zentrale Phasen als Ring-1-JDL-Engines formulierbar:

ParserEngine              -> AST / ParserDescriptor
NameResolverEngine        -> SymbolDescriptor / ScopeDescriptor
TypeEngine                -> TypeDescriptor / LayoutDescriptor / LabelProfile
ProtocolDispatcherEngine  -> DispatchDescriptor / BindingTableDescriptor
FfiBindingEngine          -> FfiCallDescriptor / MarshalDescriptor
IRBuilderEngine           -> IrModule
GeneratorEngine           -> VmModuleArtifact / Proto

Die CompilerDB ist weiterhin Kommunikations- und Cache-Schicht. Engines greifen nicht direkt aufeinander zu.

16.3 Generator

Der Generator ist read-only gegenüber semantischen Descriptoren. Er darf Artefakte erzeugen, aber keine Type-, Effect-, Dispatch- oder FFI-Semantik nachträglich erfinden.

LoweredModule
+ TypeDescriptor
+ LayoutDescriptor
+ DispatchDescriptor
+ FfiCallDescriptor
-> GeneratorEngine
-> VmModuleArtifact

Generator-Artefakte können als Materialized Query oder ArtifactPatch in die CompilerDB eingetragen werden.

16.4 IRBuilder

Der IRBuilder ist kein allwissendes Frontend. Er konsumiert getypte AST-/Descriptor-Ergebnisse über Queries und erzeugt IR. Typprüfung, Effektprüfung und Provide-Auflösung müssen vorher materialisiert sein.


17. Tools und Consumer

17.1 Consumer-Begriff

Ein Consumer nutzt Queries lesend, registriert aber keine normativen Provider für Compiler-Semantik.

Beispiele:

LSP
jade doc
Formatter
Linter
Refactoring-Tools
Build-Orchestrator
Playground
REPL

Consumer dürfen eigene Darstellungsqueries definieren, sofern diese keine Compiler-Wahrheiten materialisieren.

17.2 LSP

Der LSP liest Queries wie:

GetSymbolAt
ResolveType
GetDocComment
GetDiagnostics
GetReferences

Er setzt Input Queries bei Dateiänderungen und nutzt Cancellation.

17.3 jade doc

jade doc ist Consumer der CompilerDB. Dokumentation entsteht aus denselben Type-, Symbol-, Effect- und Descriptor-Queries wie der Compiler. Sie soll Quelltext nicht separat interpretieren, damit Dokumentation und Compiler-Wahrheiten konsistent bleiben.


18. Query-Katalog

Dieser Katalog ist initial und nicht abschließend.

18.1 SourceDomain

FileContent(path) -> str
ModuleList() -> [ModulePath]
BuildConfig() -> BuildConfigDescriptor
ProjectSpecInput() -> ProjectSpec

18.2 SyntaxDomain

GetFileTokens(file) -> JadeResult[TokenStream]
GetFileAST(file) -> JadeResult[AstModule]
GetAstNode(nodeId) -> JadeResult[AstNode]
GetDocComment(subject) -> JadeResult[DocNode]

18.3 NameDomain

GetModuleId(path) -> JadeResult[ModuleId]
LookupSymbol(name, scope) -> JadeResult[SymbolDescriptor]
GetExports(module) -> [SymbolDescriptor]
GetReferences(symbol) -> [SourceSpan]

18.4 TypeDomain

ResolveType(ref) -> JadeResult[TypeDescriptor]
GetTypeDescriptor(typeId) -> JadeResult[TypeDescriptor]
GetFields(typeId) -> [FieldDef]
GetVariants(typeId) -> [VariantDef]
GetTypeKind(typeId) -> TypeKind
GetRing0Facts(typeId) -> JadeResult[Ring0FactSet]
GetRing0Fact(typeId, field) -> Option[Tag]

18.5 MetaDomain

GetMetaRecord(subject) -> MetaRecordDescriptor
GetMeta(subject, field) -> Option[Tag]
GetProofTrace(subject, field) -> ProofTrace
EvaluateRefinement(app: ApplyRefinement) -> PatchSet

18.6 Provide/DispatchDomain

LookupProvide(protocol, args) -> JadeResult[ProvideDescriptor]
ListProvides(subject) -> [ProvideDescriptor]
BuildDispatchDescriptor(protocol, typeId) -> JadeResult[DispatchDescriptor]
GetDispatchTable(protocol, typeId) -> JadeResult[BindingTableDescriptor]

18.7 EffectDomain

GetEffectSignature(funcId) -> JadeResult[EffectDescriptor]
CheckEffects(funcId) -> DiagnosticSet
BuildCallGraphDescriptor(subject) -> JadeResult[CallGraphDescriptor]

18.8 LayoutDomain

GetMemoryLayout(typeId) -> JadeResult[LayoutDescriptor]
GetRegisterLayout(functionId) -> JadeResult[RegisterLayout]
GetAbiLayout(typeId, abi) -> JadeResult[AbiLayoutDescriptor]

18.9 IRDomain

BuildFunctionIR(functionId) -> JadeResult[IrFunction]
BuildModuleIR(moduleId) -> JadeResult[IrModule]
LowerModuleIR(moduleId) -> JadeResult[LoweredModule]
VerifyLoweredModule(moduleId) -> DiagnosticSet

18.10 ArtifactDomain

GenerateVmModule(moduleId, target) -> JadeResult[VmModuleArtifact]
GenerateBridgeArtifact(bindingId, target) -> JadeResult[BridgeArtifact]
GetArtifactHash(artifactId) -> ArtifactHash

19. Erweiterung — neue Queries und Provider

19.1 Neue Query definieren

  1. Query-Struct definieren.
  2. Ergebnis-Typ dokumentieren.
  3. Determinismus- und Durability-Regeln festlegen.
  4. Provider registrieren.
  5. Query in Katalog oder Domain-Spec aufnehmen.

19.2 D-BootstrapProvider hinzufügen

struct GetFunctionCallGraph {
    FuncId id;
    // -> JadeResult!CallGraphDescriptor
}

@handles!GetFunctionCallGraph
JadeResult!CallGraphDescriptor getFunctionCallGraph(GetFunctionCallGraph q, CompilerDb* db)
    @nogc nothrow
{
    // Stage-1-Provider, nur Bootstrap-Pfad
}

19.3 JDL-EngineProvider hinzufügen

type GetFunctionCallGraphSpec : blueprint {
    function: FuncId
}

provide DescriptorEngine[GetFunctionCallGraphSpec, CallGraphDescriptor] for EffectEngine {
    def describe(spec: GetFunctionCallGraphSpec) -> Result[CallGraphDescriptor, DiagnosticSet] {
        // liest db.* Queries und erzeugt Descriptor
    }
}

19.4 Eindeutigkeit

Zwei Provider für dieselbe Query und dasselbe Stage-Profil sind ein CompilerDB-Initialisierungsfehler.


20. Normative Invarianten

  1. CompilerDB besitzt keine hohe JDL-Semantik. Sie orchestriert Queries, Provider, Cache, Seeds, Descriptoren und Materialisierung.
  2. Keine direkte Phasenkommunikation. Provider und Engines lesen andere Phasen ausschließlich über Queries.
  3. Provider sind deterministisch. Gleiche Inputs, gleiche Provider-Version, gleiche Stage-Konfiguration ergeben gleiche Ergebnisse.
  4. Input Queries haben keinen Provider. Sie werden gesetzt.
  5. Derived Queries haben genau einen aktiven Provider. Mehrdeutigkeit ist Fehler.
  6. Materialized Queries lesen Descriptoren oder Seeds. Sie erfinden keine neue Semantik.
  7. TypeFns mutieren nicht direkt. Schreibende db.*-Operationen erzeugen Patches.
  8. TypeFns laufen in einem TypeFnContext. db ist eine TypeFnDbView; target kommt nur aus ApplyRefinement.
  9. Der Materializer committed atomar. Ring-Level, Ring-0-Facts und Coherence-Regeln werden geprüft.
  10. Ring-0-Facts sind axiomatisch. Sie sind lesbar, aber nicht überschreibbar.
  11. Descriptoren sind immutable nach Commit. Änderungen erzeugen neue Revisionen oder Descriptor-Versionen.
  12. Dependency-Tracking ist automatisch. Provider tragen Query-Abhängigkeiten nicht manuell ein.
  13. Revalidierung ist lazy. Keine eager-transitive Invalidierung.
  14. Diagnostics sind Werte. Semantische Fehler werden nicht als Exceptions propagiert.
  15. Generator erzeugt Artefakte, keine neue Semantik. Semantik muss vorher als Descriptor materialisiert sein.
  16. RuntimeBus ist nicht über CompilerDB erreichbar. Compile-Time-Queries führen keine Runtime-Commands aus.
  17. Stage 1 darf D-Provider nutzen. Das ist Bootstrap, nicht endgültige Architektur.
  18. Stage 2+ darf Ring-1-EngineProvider nutzen. D bleibt Substrate und Recovery Path.

21. Implementierungshinweise

21.1 D-Implementierung darf pragmatisch sein

Die D-Implementierung darf intern Objekte wie Parser, BootstrapTypeProvider oder SeedLoader besitzen. Diese Objekte sind Provider-Details und nicht Teil des normativen CompilerDB-Modells.

21.2 Provider-Versionierung

Provider-Versionen sollten im Query-Key-Raum berücksichtigt werden, wenn sie das Ergebnis ändern können. Sonst erhält man stale Cache-Ergebnisse nach Engine-Updates. Ein Klassiker, den man nur einmal debuggen möchte, danach glaubt man wieder an klare Versionierung.

21.3 Canonical Serialization

Alle Query-Keys, Query-Results und Descriptoren benötigen kanonische Serialisierung. Nicht-deterministische Map-Reihenfolgen sind verboten. Listen müssen stabil sortiert sein, wenn ihre semantische Ordnung irrelevant ist.

21.4 Persistenter Cache

Persistente Caches dürfen nur Ergebnisse wiederverwenden, wenn folgende Werte übereinstimmen:

CompilerDB schema version
Query schema version
Provider version
Seed hash
BuildConfig hash
Target descriptor hash
Input content hashes

21.5 Proof Trace

Proof Traces sind keine Debug-Spielerei. Sie sind notwendig für:

  • :why in der REPL,
  • IDE-Diagnostics,
  • Refinement-Erklärungen,
  • Ring-0-Fact-Konflikte,
  • Coherence-Fehler bei Provides,
  • Reproducibility-Audits späterer Artefaktpfade.

21.6 Stage-Übergang

Beim Übergang von Stage 1 zu Stage 2 muss die CompilerDB explizit dokumentieren, welche Queries vom BootstrapProvider auf EngineProvider umgestellt werden. Ein stiller Provider-Wechsel ohne Stage-Profil ist verboten.


Änderungsprotokoll

0.6.1

  • TypeFnContext und TypeFnDbView als kanonisches Ausführungsmodell für TypeFns ergänzt.
  • ApplyRefinement als interne Darstellung von target :> Refinement(args) eingeführt.
  • db.target() als Zugriff auf das aktuelle Refinement-Target definiert.
  • EvaluateRefinement auf ApplyRefinement-Records geschärft.
  • Normative Invarianten um Context-/Target-Semantik ergänzt.

0.6.0

  • CompilerDB von D-Subsystem-Modell auf Query/Provider-Modell umgestellt.
  • DescriptorStore, SeedStore und Materializer normativ eingeführt.
  • @handles auf D-BootstrapProvider beschränkt.
  • Ring-1-JDL-EngineProvider als Stage-2-Normalpfad eingeführt.
  • db.* als kuratierte Query-/Patch-Fassade definiert.
  • Direkte TypeFn-Mutation durch Patch-Materialisierung ersetzt.
  • Generator als Artefakt-Provider, aber nicht als Semantik-Produzent geschärft.

0.5.0

  • Historische Fassung: CompilerDB als Subsystem-API mit D-Handlern und @handles.