Zum Inhalt

07 — D-Implementierung: Tag-Subsystem in der Type Engine

Status: Implementierungsentwurf für Bootstrap-Compiler in D


1. Entscheidung

Tags sind compile-time-only. Sie gehören in die Type Engine. Sie brauchen keine RuntimeBus-Schnittstelle. Sie brauchen aber eigene CompilerDb-Queries.

CompilerDb -> compile-time, deterministische Queries
RuntimeBus -> runtime, side-effect Commands

Tags -> CompilerDb
Tags -> kein RuntimeBus

2. Kein TypeId-Sonderfall

Nicht empfohlen:

struct TypeDescriptor {
    bool isTag;
    // ...
}

Das macht Tags zu Pseudo-Types und lädt später zu internen Sonderfällen ein. Diese Sorte Einladung nimmt schlechte Architektur immer begeistert an.

Empfohlen:

struct TypeId { uint id; }
struct TagId  { uint id; }

Separate ID-Spaces oder mindestens stark typisierte Wrapper.


3. Symbolarten

enum SymbolKind {
    type_,
    tag,
    protocol,
    service,
    typefn_,
    function_,
    module_,
}

Symbol:

struct Symbol {
    SymbolId id;
    SymbolKind kind;

    union Payload {
        TypeId     typeId;
        TagId      tagId;
        ProtocolId protocolId;
        ServiceId  serviceId;
        TypeFnId   typeFnId;
        FunctionId functionId;
        ModuleId   moduleId;
    }

    Payload payload;
}

4. TagEngine

Die TagEngine ist ein interner Dienst der Type Engine.

struct TagEngine {
    TagDescriptor[] tags;
    TagEnumDomain[] domains;
    TagEnumVariant[] variants;
}

Descriptors:

enum TagKind {
    plain,
    enumDomain,
    enumVariant,
}

struct TagDescriptor {
    TagId id;
    SymbolId symbol;
    TagKind kind;
    MetaRecordId meta;
    SourceSpan declaredAt;
}

struct TagEnumDomain {
    TagId id;
    TagId[] variants;
}

struct TagEnumVariant {
    TagId id;
    TagId domain;
}

Beispiele:

tag RequestArena
TagKind.plain
tag SharePolicy : enum =
    | Local
    | Send
    | Sync
SharePolicy -> TagKind.enumDomain
Local       -> TagKind.enumVariant, domain = SharePolicy
Send        -> TagKind.enumVariant, domain = SharePolicy
Sync        -> TagKind.enumVariant, domain = SharePolicy

5. Type-Level-Werte

TypeFn-Auswertung benötigt eine gemeinsame Repräsentation für Type-Level-Werte.

enum TypeLevelValueKind {
    type_,
    tag,
    metaRecord,
    protocol,
    service,
    literal,
    labelPredicate,
}

struct TypeLevelValue {
    TypeLevelValueKind kind;

    union Payload {
        TypeId typeId;
        TagId tagId;
        MetaRecordId metaId;
        ProtocolId protocolId;
        ServiceId serviceId;
        LiteralValue literal;
        LabelPredicate label;
    }

    Payload payload;
}

Damit kann typefn Share(policy: SharePolicy) sauber prüfen:

Argument ist TypeLevelValueKind.tag.
Tag gehört zur Domain SharePolicy.

6. CompilerDb Queries

Minimaler Query-Satz:

struct ResolveTag {
    ScopeId scope;
    SymbolName name;
}
// -> TagId

struct GetTagDescriptor {
    TagId id;
}
// -> TagDescriptor

struct IsTagInDomain {
    TagId tag;
    TagId domain;
}
// -> bool

struct GetTagMeta {
    TagId id;
}
// -> MetaRecord

struct NormalizeAppliedTag {
    TagId id;
}
// -> MetaRecord

Später:

struct GetTagVariants {
    TagId domain;
}
// -> TagId[]

struct ResolveTagVariant {
    TagId domain;
    SymbolName name;
}
// -> TagId

struct GetTagFingerprint {
    TagId id;
}
// -> Fingerprint

struct CheckTagCycles {
    ModuleId module_;
}
// -> Diagnostic[]

7. Query-Abhängigkeiten

Wenn ein Typ TaskLocal anwendet:

type Cache : struct { ... } :> TaskLocal

dann hängt seine Meta-Normalisierung ab von:

ResolveTag(TaskLocal)
GetTagMeta(TaskLocal)
EvalTypeFn(Own, Unique)
EvalTypeFn(Share, Local)

Ändert sich TaskLocal, muss nur davon abhängige Meta-Normalisierung und Label-Closure invalidiert werden.

Genau dafür ist CompilerDb da. Sonst darf der Compiler wieder in den Kaffeesatz greifen. Eher unpräzise, auch für Kaffee.


8. Keine RuntimeBus-Integration

Tags haben:

- keine Runtime-Werte
- kein Runtime-Layout
- keine VM-Operationen
- keine Intrinsics
- keine RuntimeBus-Commands

RuntimeBus bleibt für runtime side effects:

- JME retain/release
- task spawn/cancel
- arena open/reset/close
- messaging
- sync

Tags sind TypeEngine-Daten.


9. Artifact- und Plugin-Relevanz

Tags müssen in Artefakten als TypeEngine-Metadaten exportierbar sein.

TagFqn:
    package::module::TagName

TagFingerprint:
    hash(package, module, name, declaration-shape)

Für Varianten:

package::module::SharePolicy::Local

Tag-Domains sind nominal. Zwei Tags mit gleichem Namen aus verschiedenen Packages sind verschieden.


10. Invarianten

D-1: Jeder TagId referenziert genau einen TagDescriptor.
D-2: Jeder enumVariant-Tag gehört genau zu einer enumDomain.
D-3: Eine Tag-Domain enthält keine doppelten Variantennamen.
D-4: Tags besitzen kein Runtime-Layout.
D-5: Tags können keine Protocol-Implementierungen besitzen.
D-6: Tag-Meta muss normalisierbar sein.
D-7: Tag-Meta-Zyklen werden als Diagnostic gemeldet.
D-8: Andere Subsysteme greifen nie direkt auf TagEngine-Tabellen zu.
D-9: Zugriff erfolgt über CompilerDb Queries.
D-10: Tags erzeugen keine RuntimeBus-Commands.

11. Empfohlene Dateistruktur

compiler/
  symbols/
    symbol_table.d
    name_resolution.d

  type_engine/
    types.d
    tags.d
    protocols.d
    services.d
    meta_records.d
    typefn_eval.d
    closure.d
    truth_profile.d

  queries/
    type_queries.d
    tag_queries.d
    meta_queries.d
    closure_queries.d