JDL — Empfehlung für ein Context-bound Resource Model und die Neuordnung von Speicher, Allokation und Storage¶
Status: Arbeitsnotiz / ernsthaft erwogene Designempfehlung
Geltung: Allgemeines Modell für contextgebundene, profilierte und scoped Ressourcen; darauf aufbauend Neuordnung der JDL-Speichersemantik, der Allokationskonfiguration und ihrer Einbettung in Context, Storage-Service, Engines und JadeME.
Nicht normativ: Diese Notiz beschreibt eine empfohlene Richtung. Sie ersetzt noch keine bestehende Spec.
Ziel: Zuerst eine allgemeine, wiederverwendbare Ressourcenarchitektur benennen; anschließend Storage als deren erste konkrete Ausprägung neu ordnen. Das Speichermodell soll orthogonaler, leichter erklärbar und für normalen JDL-Code deutlich weniger fehleranfällig werden, ohne die explizite Kontrolle über Speicher aufzugeben.
0. Kurzfassung¶
Die bisherige Speichersemantik von JDL vermischt mehrere unterschiedliche Fragen in einer gemeinsamen Memory(...)-Achse. Value, Rc beziehungsweise historisch Ref, Arena[A], Pool[P] und in älteren Notizen auch mapped stehen nebeneinander, obwohl sie nicht dieselbe Art von Entscheidung beschreiben.
Bei der Auflösung dieses Knotens zeigt sich ein allgemeineres Muster, das über Speicher hinausgeht:
Resource Role
Was will der Code semantisch?
↓
Resource Profile
Welche Policy soll dafür gelten?
↓
Resource Descriptor
Welche geprüfte Semantik gilt konkret?
↓
Context Binding
Welche Ausprägung gilt in dieser Ausführungsdomäne?
↓
Resource Instance
Welche laufende Ressource wurde tatsächlich etabliert?
↓
Scopeable / ResourceEngine
Wie wird ihr Lifecycle strukturiert und garantiert?
Storage ist eine besonders wichtige Spezialisierung dieses Musters, aber nicht dessen Definition. Dieselbe Form kann auch für Transaktionen, Locks, Dateien, Prozesse oder andere Ressourcen gelten, sofern diese tatsächlich einen relevanten Acquire-/Scope-/Release-Lifecycle besitzen.
Die Empfehlung schärft zugleich die bisherige Construction-/Cleanup-Achse: Create soll am Ursprungstyp eine geschlossene Menge zulässiger Konstruktionsübergänge beschreiben. Ein Create-Arm führt mit eigenen Inputs und eigener Row zu einem konkreten Created Type. Drop gehört anschließend diesem Created Type und nicht dem historischen Create-Pfad. Unterschiedliche Lifecycle-Pflichten werden damit typisiert statt als versteckte Creation-Provenance im Wert konserviert.
Eine Lifecycle-Meta-Ableitung kann diese Trennung automatisieren, ohne sie wieder zu vermischen: Sie liest die Create-Domain eines Ursprungstyps, sammelt die tatsächlich erreichbaren Created Types und prüft beziehungsweise leitet deren Drop-Semantik aus bereits materialisierten Facts ab. Sie koppelt also nicht jeden Create-Arm an einen Drop-Pfad. Mehrere Create-Arme, die denselben Created Type liefern, führen zu genau einer Lifecycle-Prüfung dieses Resultattyps. Wo die Drop-Semantik nicht eindeutig beweisbar ist, muss eine explizite Policy beziehungsweise ein Handler angegeben werden.
Die Empfehlung dieser Notiz lautet deshalb zweistufig:
JDL sollte ein allgemeines Context-bound Resource Model verwenden: Rollen benennen Absicht, Profile beschreiben Policy, Descriptoren tragen geprüfte Semantik, Contexts binden sie an eine Ausführungsdomäne und
withstrukturiert den Lifecycle einer konkreten Resource.
Für Speicher folgt daraus:
Der Typ soll nicht seinen Allocator wählen. Der Typ beschreibt nur die Garantien, die seine Werte benötigen. Der aktive Context stellt Storage-Rollen bereit und bindet diese an geprüfte MemoryRegion-Profile. JadeME führt die daraus folgende Speichermechanik aus.
Für normalen Anwendungscode gilt dabei:
Speicher ist in JDL explizit verwaltet, aber einzelne Allokationen sind normalerweise implizit. Der Benutzer verwaltet Speichergrenzen und Lebensdauern, nicht Speicherblöcke.
Ein Context darf mehrere Storage-Regionen konfigurieren, aber an jedem Ausführungspunkt gibt es genau eine aktive Default-Region für implizite dynamische Allokationen. Wer lokal davon abweichen will, tut dies ausdrücklich über den Storage-Service und with:
Scratch ist dabei keine Allokatorimplementierung, sondern eine semantische Storage-Rolle. Der aktive Context entscheidet, welches Storage-Profil diese Rolle erfüllt.
Damit kann derselbe Code unverändert unter unterschiedlichen Contexts laufen:
ProductionContext:
Scratch -> FastScratchProfile
TestContext:
Scratch -> CheckedScratchProfile
BenchmarkContext:
Scratch -> InstrumentedScratchProfile
SandboxContext:
Scratch -> RestrictedScratchProfile
Die eigentliche Region wird aus orthogonalen Bausteinen beschrieben:
MemoryRegion
├── Backing
├── Allocation
├── Reclamation
├── Lifetime
├── Budget
├── Accounting
└── Guarantees
Beispiele:
RequestRegion
├── Backing = Mmap
├── Allocation = Bump
├── Reclamation = BulkReset
├── Lifetime = Scope
├── Budget = 32 MiB
└── Accounting = Enabled
GeneralRegion
├── Backing = Mmap
├── Allocation = FreeList
├── Reclamation = Individual
├── Lifetime = Context
└── Accounting = Basic
Rc und Weak gehören nicht in diese Allokationsachse. Sie beschreiben Ownership. Create beschreibt die geschlossene Menge zulässiger Konstruktionsübergänge eines Typs. Drop gehört dagegen zum jeweils erzeugten Typ und beschreibt dessen semantisches Cleanup am Lebensende. Share beschreibt Task-/Concurrency-Garantien. Diese Achsen sollen wieder sauber voneinander getrennt werden.
Teil I — Warum der heutige Zustand problematisch ist¶
1. Der aktuelle Knoten¶
Die aktuelle Dokumentation kennt beziehungsweise kannte unter anderem folgende Speicher- und Lebenszyklusbegriffe:
Memory(Value)
Memory(Ref) / Memory(Rc)
Memory(Arena[A])
Memory(Pool[P])
mapped
Own(Unique | Shared | Weak)
Share(Local | Send | Sync)
Create(Trivial | Factory[F])
Drop(Trivial | Custom)
Arena
Pool
StorageSpec
StorageEngine
ResourceEngine
JadeME
with Storage.arena[...]
HandleId
Generation Counter
Escape Analysis
Jeder einzelne Begriff hat eine nachvollziehbare Motivation. Das Problem ist ihre heutige Gruppierung.
Memory(Value) beschreibt hauptsächlich Repräsentation: inline statt indirekt.
Memory(Rc) beschreibt in der Praxis Repräsentation und Ownership-/Lifetime-Mechanik.
Memory(Arena[A]) beschreibt eine Regions- und Lebensdauerentscheidung und setzt meist eine bestimmte Allokationsstrategie voraus.
Memory(Pool[P]) beschreibt Wiederverwendung beziehungsweise einen speziellen Allocator.
mapped beschreibt eher die Herkunft beziehungsweise das Backing des Speichers.
Diese Dinge sind keine Varianten derselben ontologischen Frage. Sie wurden nur historisch unter demselben Begriff gesammelt.
Das erzeugt Scheinklarheit:
sieht einfach aus, zwingt aber Repräsentation, Ownership, Region, Allokator, Reclamation und Backing in ein gemeinsames Enum. Sobald weitere Strategien hinzukommen — mmap, Free-List, Slab, Buddy, TLSF, Shared Memory, File Mapping, Huge Pages — wird die Vermischung unhaltbar.
2. Die eigentliche Designfrage¶
JDL soll zwei Ziele gleichzeitig erfüllen:
- Der Entwickler soll wissen, dass Speicher eine endliche, verwaltete Ressource ist und bewusste Lebensdauerentscheidungen treffen können.
- Der normale Code soll nicht aussehen wie eine permanente Unterhaltung mit einem Allocator-Objekt.
Das Ziel ist ausdrücklich nicht:
aber ebenso wenig:
Low-Level-API:
"Reiche durch jede dritte Funktion einen Allocator und entscheide an jeder Allocation-Site über dessen konkrete Mechanik."
Die gewünschte Mitte ist:
Der Entwickler verwaltet Speicherdomänen und deren Lebensdauer bewusst. Innerhalb einer gültigen Domäne darf normaler JDL-Code gewöhnlich allozieren, ohne jede einzelne Allokation mechanisch zu steuern.
Teil II — Warum Speicherverwaltung für ungeübte Entwickler so schnell zur Hölle wird¶
3. Das Problem ist selten die einzelne alloc()-Operation¶
Die meisten schwierigen Speicherfehler entstehen nicht, weil ein Entwickler nicht weiß, dass Speicher angefordert werden muss. Sie entstehen, weil mehrere zeitlich und räumlich getrennte Entscheidungen zusammenpassen müssen.
Typisches Muster:
Fehler wird hier verursacht
↓
↓ viele Funktionsaufrufe
↓ andere Datenstrukturen
↓ andere Tasks
↓ Cleanup / Reuse
↓
Symptom tritt hier auf
Der Entwickler untersucht anschließend einen Stack-Trace an der Stelle des Symptoms, obwohl die Ursache hunderte oder tausende Operationen früher entstanden ist.
Jade sollte möglichst viele solcher Fehler von späten Symptomen zu frühen Grenzverletzungen machen.
4. Typische Fehlerklasse: falsche Lebensdauer¶
Ein Wert wird in kurzlebigem Speicher erzeugt, aber eine Referenz darauf lebt länger.
Konzeptionell:
In klassischen manuellen Speichermodellen wird daraus schnell ein Use-After-Free oder ein scheinbar zufälliger Crash.
In einem regions- und contextorientierten Modell soll daraus stattdessen eine explizite Verletzung werden:
oder beim kontrollierten Übergang:
5. Typische Fehlerklasse: falscher Allocator am falschen Ort¶
In allocator-zentrierten APIs muss häufig sichergestellt werden, dass Allocation und Deallocation über kompatible Mechanismen laufen.
Beispiele:
oder:
oder:
Solche Fehler sind besonders unangenehm, weil die API-Oberfläche syntaktisch korrekt aussehen kann.
Ein Context-/Region-Modell kann die Frage gröber und prüfbarer formulieren:
Diese Ausführungsdomäne besitzt Region R.
Diese Region besitzt Lifetime L.
Dieser Scope darf R verwenden.
Werte aus R dürfen nur unter definierten Crossing-Regeln entkommen.
6. Typische Fehlerklasse: Arena vergessen zurückzusetzen¶
Arenen sind einfach, solange ihre Lebensdauer wirklich klar begrenzt ist. Werden sie jedoch zu langlebig oder wird reset() vergessen, wächst der Speicherverbrauch ohne offensichtlichen einzelnen Leak.
Der Fehler ist semantisch:
und nicht mechanisch:
Deshalb sollte das Scope-Ende nicht der Disziplin eines Entwicklers überlassen werden, wenn es strukturell ausdrückbar ist.
ist dafür besser als:
7. Typische Fehlerklasse: Cleanup auf Fehlerpfaden¶
Manuelle Ressourcenverwaltung scheitert gern nicht im Happy Path, sondern bei:
Wenn Scope-Lifecycle über with und ResourceEngine zentral erzwungen wird, muss nicht jeder Storage-Provider seine eigene Variante von „hoffentlich wird Cleanup auf jedem Pfad ausgeführt“ implementieren.
Die zentrale Trennung lautet:
ResourceEngine
versteht Scope-Lifecycle
Storage
versteht MemoryRegions
JadeME
versteht physische Speichermechanik
8. Typische Fehlerklasse: dangling Views und Slices¶
Views sind effizient, solange ihre Quelle lebt. Wird eine View über die Region hinaus getragen, entsteht dieselbe Lebensdauerproblematik wie bei Zeigern, nur hübscher verpackt.
Ein gutes Regionsmodell erlaubt klare Regeln:
View bleibt innerhalb Region
-> zero-copy möglich
View verlässt Region
-> Copy / Move / Fehler, abhängig von Typ und Boundary
Damit wird die Entscheidung an der Grenze sichtbar und nicht erst bei späterer Dereferenzierung.
9. Typische Fehlerklasse: Refcount-Zyklen¶
Rc verhindert keine logischen Zyklen:
Beide Refcounts bleiben größer als null.
Das ist kein Allokatorproblem und sollte auch nicht als solches behandelt werden.
Die dafür vorgesehene Lösung bleibt Weak:
Damit wird deutlich, warum Rc/Weak aus einer gemeinsamen MemoryPolicy herausgelöst werden sollten: Sie beschreiben Ownership-Beziehungen, nicht die Herkunft des Speichers.
Weak löst allerdings nicht jede Form logischen Leaks. Ein Cache, der nie Einträge verwirft, bleibt ein Leak, auch wenn alle Backreferences vorbildlich Weak sind.
10. Typische Fehlerklasse: unkontrollierter Ressourcenverbrauch¶
Ein Programm kann vollständig speichersicher sein und trotzdem den Prozess durch unbeschränktes Wachstum ruinieren.
Beispiele:
Plugin leak
Parser für bösartige Eingabe
unbegrenzter Cache
versehentlich riesige temporäre Struktur
Benchmark mit falscher Datenmenge
Hier hilft kein Generation Counter und kein Weak.
Was hilft, ist Memory Containment:
Dann kann ein logischer Fehler weiterhin existieren, aber sein Schaden wird durch die Ausführungsdomäne begrenzt.
11. Typische Fehlerklasse: Context-Grenzen und fremde Handles¶
Wenn Speicherobjekte ohne kontrollierten Übergang zwischen Contexts wandern könnten, wäre Context-Isolation letztlich nur eine Konvention.
Jades bestehendes Handle-/Context-Modell bietet hier einen starken Ansatz:
Context A
Handle auf Objekt in Region A
Context B
darf dieses gewöhnliche Handle nicht einfach benutzen
Ein Übergang muss explizit erfolgen, etwa als:
Welche Formen davon normativ werden, ist noch offen. Entscheidend ist die Richtung: Eine Context-Grenze ist gleichzeitig eine natürliche Memory-Lifetime-Grenze.
12. Typische Fehlerklasse: Concurrency und Speichermechanik werden vermischt¶
Ein Allocator kann thread-safe oder thread-local sein. Ein Wert kann Send oder Sync sein. Eine Region kann an einen Task oder Context gebunden sein.
Das sind wieder unterschiedliche Fragen.
Share(Local | Send | Sync) sollte deshalb nicht Teil derselben Achse sein wie Bump, FreeList oder mmap.
Teil III — Das allgemeine Context-bound Resource Model¶
13. Der allgemeine Kern¶
Die Storage-Diskussion legt eine allgemeinere Architektur nahe, die nicht auf Speicher beschränkt werden sollte.
Der gemeinsame Ablauf lautet:
Service / fachliche API
↓
Resource Role
↓
Context Binding
↓
Resource Profile
↓
DescriptorEngine / fachliche Engine
↓
Resource Descriptor
↓
Resource Instance
↓
ResourceEngine / Scopeable
↓
enter → body → exit → mechanisches cleanup
Die Ebenen beantworten unterschiedliche Fragen:
Service
Welche semantische Leistung verlangt der Code von seiner Umgebung?
Resource Role
Welche Art von Ressource will der Code innerhalb dieser Leistung?
Resource Profile
Welche Policy soll für diese Rolle gelten?
Resource Descriptor
Welche geprüfte Semantik gilt nach Materialisierung tatsächlich?
Context Binding
Welche konkrete Ausprägung dieser Rolle gilt in dieser Ausführungsdomäne?
Resource Instance
Welche laufende Ressource wurde etabliert?
Scopeable / ResourceEngine
Wie wird die Lebensdauer strukturiert und auf allen regulären Pfaden beendet?
Das Muster ist bewusst allgemeiner als Storage. Storage soll darauf aufbauen, nicht es definieren.
Die Schichten sind dabei kein Zwang, jede Resource maximal auszudekorieren. Wenn eine Domäne keine contextabhängige Rollenauswahl braucht, können Role und Profile zusammenfallen oder ganz entfallen. Wenn keine eigene DescriptorEngine nötig ist, kann ein Provider einen bereits geprüften Vertrag direkt liefern. Wenn eine Ressource nicht scoped ist, muss sie nicht Scopeable sein.
Der Zweck des Modells ist Trennung dort, wo unterschiedliche Fragen existieren — nicht das Erfinden leerer Abstraktionsschichten.
14. Ein Service ist nicht automatisch eine Resource¶
Ein Service in einer Effect Row bedeutet zunächst nur:
Diese Funktion benötigt eine semantische Leistung aus ihrer Umgebung.
Eine Resource ist enger:
Eine Resource ist eine laufende Instanz mit relevanter Besitz-, Lebensdauer-, Abschluss- oder Freigabesemantik.
Daher:
Service
= Was brauche ich aus meiner Umgebung?
Resource
= Was habe ich erhalten und muss lebenszyklisch korrekt behandeln?
Beispiel ohne explizite Resource:
Logger ist ein Service. Für den Aufrufer entsteht daraus nicht automatisch eine scoped Resource.
Dagegen:
Hier gilt:
Ein Service kann Ressourcen bereitstellen oder vermitteln. Nicht jeder Service ist selbst eine Resource und nicht jede Service-Operation erzeugt eine Resource.
Diese Trennung verhindert, dass jede Row-Abhängigkeit künstlich enter/exit/cleanup tragen muss.
15. Resource Role: Absicht statt Mechanik¶
Eine Resource Role benennt, was der aufrufende Code semantisch benötigt.
Beispiele:
Storage:
Scratch
Request
Secure
Database:
ReadOnly
ReadWrite
Locking:
CacheWrite
MetadataRead
Filesystem:
Temporary
DurableOutput
Die Rolle beschreibt nicht, wie die Ressource technisch realisiert wird.
Die konkrete Policy gehört ins Profil und die konkrete Auswahl in den Context.
16. Resource Profile: wiederverwendbare Policy¶
Ein Profil beschreibt die Policy einer Ressource unabhängig vom aufrufenden Code.
Konzeptionell:
Beispiel Transaktion:
Ein TestContext könnte dieselbe Rolle anders binden:
Der Anwendungscode bleibt:
Das ist dieselbe Entkopplung, die für Storage.region[Scratch] empfohlen wird.
17. Resource Descriptor: geprüfte Semantik¶
Ein Profil ist Beschreibung. Ein Descriptor ist die materialisierte, geprüfte Form.
Der Descriptor muss alles enthalten, was spätere Phasen benötigen, ohne die CompilerDB erneut konsultieren zu müssen.
Er kann beispielsweise festhalten:
zulässige Operationen
Lifecycle-Semantik
Limits
Timeouts
Cleanup-/Reap-Vertrag
notwendige Capabilities
fachliche Policies
Kompatibilitätsbedingungen
Nicht jede Ressourcendomäne braucht zwingend eine eigene Engine. Wenn eine Domäne keine wiederverwendbare Ausführungssemantik besitzt, kann ein Service Provider ausreichen. Engines sind für Invarianten da, nicht als Belohnung dafür, dass ein Substantiv existiert.
18. Context Binding: Umgebung entscheidet über die konkrete Ausprägung¶
Der aufrufende Code soll context-agnostisch bleiben.
Beispiel:
ProductionContext:
Database.ReadWrite -> SerializableWriteProfile
TestContext:
Database.ReadWrite -> InMemoryWriteProfile
BenchmarkContext:
Database.ReadWrite -> InstrumentedWriteProfile
Der Code bleibt unverändert:
Dasselbe Muster gilt später für Storage:
ProductionContext:
Storage.Scratch -> FastScratchProfile
TestContext:
Storage.Scratch -> CheckedScratchProfile
Dadurch sitzt die Deployment-/Umgebungsentscheidung dort, wo sie hingehört: im Context statt im Typ oder an jeder einzelnen Aufrufstelle.
19. Resource Instance: das laufende Ding¶
Der Descriptor ist kein Laufzeitzustand.
Erst beim tatsächlichen Erwerb entsteht eine Resource Instance:
TransactionDescriptor
↓ acquire
Transaction Instance
StorageDescriptor
↓ enter region
MemoryRegion Instance
FileDescriptorSpec
↓ open
File Instance
Die Resource Instance kann Handles, Leases oder andere lauf-lokale Identitäten enthalten. Diese gehören nicht in die kanonische Descriptor-Semantik.
20. Scopeable und ResourceEngine¶
with sollte nicht von Storage, Database, Locking oder Files jeweils neu erfunden werden. Gleichzeitig ist nicht jede Resource automatisch scoped. Ein langlebiger Context-eigener Pool oder ein persistentes Handle kann eine Resource sein, ohne als lokaler with-Scope aufzutreten. Scopeable beschreibt nur jene Ressourcen, deren Lifecycle lexikalisch oder strukturiert begrenzt werden soll.
Ebenso sollte daraus kein universeller Resource.open[...]-Service entstehen. Die fachliche API bleibt erhalten:
Storage.region[Scratch]
Database.transaction[ReadWrite]
Lock.acquire[CacheWrite]
FileSystem.file[Temporary]
Gemeinsam ist der Lifecycle, nicht die fachliche Bedeutung.
Der allgemeine Vertrag ist:
ResourceEngine
besitzt die strukturierte Scope-Semantik
fachlicher Service / Engine
besitzt die Domänensemantik
Resource Descriptor
trägt den geprüften Lifecycle-Vertrag
Konzeptionell kann eine Resource Scopeable sein:
enter
etabliert die Ressource vollständig
body
benutzt die Ressource
exit
beendet den fachlichen Scope abhängig vom Exit-Grund
cleanup / reap
stellt mechanische Ressourceninvarianten garantiert wieder her
Die genaue JDL-Syntax ist offen. Entscheidend ist, dass cleanup nicht einfach ein beliebiger User-Callback sein sollte, wenn Jade auch bei beschädigten Fehlerpfaden mechanische Rückgewinnung garantieren will.
21. Fehlersemantik einer scoped Resource¶
Eine allgemeine Resource-Abstraktion muss den Fehlerfall genauso sauber tragen wie den Happy Path.
enter scheitert¶
Falls enter intern bereits Teilressourcen beschafft hat, muss es deren mechanisches Cleanup selbst garantieren, bevor der Fehler sichtbar wird.
Eine Resource darf dem Body erst sichtbar werden, wenn ihre Scope-Invariante vollständig etabliert ist.
Body scheitert mit Throw[E]¶
with garantiert damit das Ende der Ressourcenbeziehung, nicht den Erfolg der Arbeit.
exit scheitert¶
Bei erfolgreichem Body kann ein fachlich fallibles exit einen normalen Fehler erzeugen, etwa CommitError.
Schwieriger ist:
Ein Exit-Fehler darf einen bereits laufenden Primärfehler nicht stillschweigend überschreiben. Wie mehrere Fehler in JDL konkret repräsentiert werden, bleibt offen; die semantische Anforderung sollte jedoch feststehen.
Mechanisches cleanup scheitert¶
Mechanisches Cleanup sollte kein gewöhnlicher fachlicher Fehlerkanal sein.
Wenn die Runtime ihre elementaren Ressourceninvarianten nicht wiederherstellen kann, liegt eher eine gebrochene Runtime-Invariante vor:
Sonst würde aus einem Fehlerpfad schnell eine Kaskade aus Body-, Exit- und Cleanup-Fehlern, bei der niemand mehr weiß, welcher Fehler eigentlich die Ursache war.
Trap im Body¶
Ein Trap ist nicht dasselbe wie Throw[E].
Auf einem Trap-Pfad sollte Jade nicht beliebig weitere User-Semantik ausführen müssen. Die ResourceEngine beziehungsweise Ring 0 muss jedoch mechanische Rückgewinnung ermöglichen können, solange die betreffende Runtime-Domäne noch fortsetzbar ist. Ein Prozessabbruch, Hostverlust oder SIGKILL kann naturgemäß keine in-process Cleanup-Garantie erfüllen.
Damit ergibt sich die Trennung:
Throw
-> geordneter fachlicher Exit
-> mechanisches Cleanup
-> Propagation
Trap
-> keine beliebige User-Semantik voraussetzen
-> mechanisches Reaping
-> Trap propagiert
22. Statische Übergangsgarantien¶
Der allgemeine Resource-/Context-Ansatz erlaubt einen stärkeren Anspruch als bloß „wir prüfen ein paar Typen“.
Der Zielpfad lautet:
Für vollständig statisch bekannte Programme fällt ein großer Teil davon in die Übersetzung. Für nachgeladene Plugins oder andere dynamische Bestandteile wandert die Prüfung an die späteste legitime Grenze.
Der sinnvolle Anspruch lautet nicht:
Wenn es kompiliert, kann zur Laufzeit nichts mehr schiefgehen.
Netzwerk, Speicherknappheit, externe Systeme und fachlich fallible Operationen bleiben dynamisch.
Der stärkere und realistischere Satz ist:
Was Jade zur Ausführung zulässt, soll nicht mehr an einer bereits statisch beziehungsweise bei Admission beschreibbaren strukturellen Unzulässigkeit scheitern.
Beispiele:
Role fehlt im Context
Profile ist inkompatibel
Capability fehlt
Lifetime passt nicht
Bridge kann Transferform nicht tragen
Storage Guarantee wird nicht erfüllt
Resource Scope ist strukturell ungültig
Diese Fehler sollen vor der eigentlichen Ausführung an der jeweiligen Materialisierungs-/Admission-Grenze erscheinen.
Die Runtime führt dann geprüfte Übergänge aus, statt erst dort zu entscheiden, ob sie semantisch überhaupt erlaubt sind.
23. Beispiele derselben Architektur¶
Storage¶
Storage Service
Scratch Resource Role
FastScratchProfile Resource Profile
StorageDescriptor Resource Descriptor
MemoryRegion Resource Instance
ResourceEngine Scope Lifecycle
StorageEngine/JME Storage-Semantik und Mechanik
Database¶
Konzeptionell:
tag TransactionMode : enum =
| ReadOnly
| ReadWrite
type Database : service {
def transaction[mode: TransactionMode](self)
->{Throw[TransactionOpenError]}
Transaction[mode]
}
Die Resource selbst bleibt ein normaler Created Type mit Lifecycle-Vertrag:
// spekulativ
type Transaction[
phantom mode: TransactionMode
] : struct {
handle: TransactionHandle
}
:> Drop(Handler[DatabaseTransactionCleanup])
Database.transaction[mode] ist hier der fachliche Konstruktionsweg, der den Created Type Transaction[mode] liefert. Falls die allgemeine Create-Domain für solche Service-Ressourcen verwendet wird, würde die Service-Operation einen entsprechenden Create-Arm realisieren; der spätere Lifecycle hängt trotzdem am erzeugten Transaction[mode], nicht an der historischen Erzeugungsoperation.
Benutzung:
Lock¶
CacheWrite benennt die Rolle; das Profil kann Fairness, Timeout oder Reentrancy festlegen.
File¶
Temporary kann im Context auf ein Profil mit privaten Rechten, bestimmtem Temp-Bereich und delete-on-scope-exit gebunden werden.
Diese Beispiele sollen nicht beweisen, dass bereits eine vollständige universelle Resource-Abstraktion normiert ist. Sie zeigen vielmehr einen Test: Wenn mehrere unabhängige Domänen ohne Sondergymnastik dieselbe Form tragen, ist das ein starkes Indiz, dass die Abstraktion tatsächlich allgemein ist.
Teil IV — Storage als erste Spezialisierung des allgemeinen Resource-Modells¶
24. Leitprinzip für Storage¶
Storage übernimmt das allgemeine Resource-Muster und ergänzt die domänenspezifischen Speicherachsen. Die empfohlene Schichtung lautet:
Typ
beschreibt notwendige Garantien
↓
Code
benutzt semantische Storage-Rollen
↓
Context
bindet Rollen an Storage-Profile
↓
StorageEngine
materialisiert / orchestriert MemoryRegions
↓
ResourceEngine
erzwingt Scope-Lifecycle
↓
JadeME
führt die physische Speichermechanik aus
Daraus folgt eine wichtige Vereinfachung:
Normale JDL-Werte benötigen keine sichtbare Allokatorwahl. Explizite Storage-Semantik erscheint dort, wo der Entwickler bewusst eine andere Lebensdauer, Region oder Ressourcenpolicy verlangt.
Teil V — Die orthogonalen Achsen¶
25. Representation: Wie wird ein Wert dargestellt?¶
Beispiele:
Diese Entscheidung sollte größtenteils aus Typstruktur, Größe und benötigten Garantien ableitbar sein.
Sie ist kein guter Kandidat für dieselbe öffentliche Policy wie Arena- oder Pool-Auswahl.
Ein Descriptor-Fact kann weiterhin festhalten:
oder:
aber normaler JDL-Code sollte selten gezwungen sein, dies selbst festzulegen.
26. Ownership: Wer hält den Wert am Leben?¶
Beispiele:
Ownership beschreibt keine Speicherregion.
Rc[T] bedeutet nicht „benutze den Rc-Allocator“. Es bedeutet, dass mehrere logische Besitzer die Lebensdauer eines Wertes gemeinsam kontrollieren.
Weak[T] ist die nicht-besitzende Gegenrichtung für Ownership-Graphen.
27. Sharing: Wo darf ein Wert verwendet werden?¶
Beispiele:
Das ist eine Concurrency-/Task-Garantie, keine Allocation Policy.
28. Construction: Create beschreibt eine geschlossene Konstruktionsdomäne¶
Create gehört weiterhin nicht in die Speicherachse. Die bisherige Lesart Create(Factory[F]) ist jedoch wahrscheinlich zu eng: Sie beantwortet primär die Frage, wer konstruieren darf, obwohl für den Compiler die interessantere Semantik lautet, welche zulässigen Konstruktionsübergänge es überhaupt gibt und welcher Typ daraus entsteht.
Empfohlen wird deshalb, Create am konstruierbaren Ursprungstyp als geschlossene Konstruktionsdomäne zu modellieren. Eine Tag-/Enum-Domain bietet dafür eine natürliche Form:
// spekulativ
tag ConnectionCreate : enum =
| Pooled
| Direct
| Replica
| Borrowed
type Connection : struct {
...
}
:> Create(ConnectionCreate)
Die Enum-Arme benennen nicht bloß Factory-Namen. Jeder Arm beschreibt konzeptionell einen Konstruktionsübergang mit eigenen Eingaben, eigener Row und einem konkreten Resultattyp:
ConnectionCreate.Pooled(...)
->{ConnectionPool}
PooledConnection
ConnectionCreate.Direct(...)
->{Network}
OwnedConnection
ConnectionCreate.Replica(...)
->{ReplicaService}
OwnedConnection
ConnectionCreate.Borrowed(...)
->{}
BorrowedConnection
Die genaue Syntax ist offen. Entscheidend ist die Zuständigkeit:
Create am Ursprungstyp
= welche geschlossenen Erzeugungswege existieren?
welche Eingaben und Services benötigen sie?
welcher Created Type entsteht?
Damit wird Create zu einer kleinen, exhaustiv prüfbaren Konstruktionsalgebra statt zu einer offenen Sammlung verstreuter Factory-Funktionen. IDE, LSP, CompilerDB und :why können die vollständige Create-Domain eines Typs direkt anzeigen.
Mehrere Create-Arme dürfen denselben Resultattyp erzeugen. Direct und Replica können beispielsweise beide einen OwnedConnection liefern. Umgekehrt muss ein Unterschied, der nach erfolgreicher Konstruktion für Ownership oder Cleanup relevant bleibt, im Created Type sichtbar werden und nicht als versteckte Creation-Provenance im Wert weiterleben.
Damit wird ausdrücklich vermieden:
Wenn der Erzeugungsweg einen anderen Lifecycle erzeugt, soll der Resultattyp diesen Unterschied tragen.
Create allokiert dabei weiterhin nichts und wählt keine MemoryRegion. Die Konstruktionssemantik und die Storage-Semantik bleiben orthogonal.
Ebenso wenig legt ein Create-Arm selbst seinen späteren Drop-Pfad fest. Seine Verantwortung endet mit dem erfolgreichen Übergang in einen Created Type. Ab dort gilt ausschließlich dessen materialisierte Lifecycle-Semantik. Das verhindert, dass Construction-Provenance nur deshalb in den Laufzeitwert eingebaut werden muss, damit später ein Cleanup-Pfad ausgewählt werden kann.
29. Cleanup: Drop gehört zum Created Type¶
Drop wird nicht an einen Create-Arm gekoppelt. Es gehört zum Typ des fertig erzeugten Wertes.
Konzeptionell:
// spekulativ
type PooledConnection : struct {
...
}
:> Drop(Handler[ConnectionPool])
type OwnedConnection : struct {
...
}
:> Drop(Handler[ConnectionCleanup])
type BorrowedConnection : struct {
...
}
:> Drop(Trivial)
Damit gilt:
Create Arm
-> erzeugt einen Created Type
Created Type
-> trägt Ownership, Share, Drop und weitere Lifecycle-Garantien
Es gibt deshalb keine 1:1-Regel zwischen Create und Drop. Mehrere Create-Arme können denselben Created Type und damit dieselbe Drop-Semantik liefern. Unterschiedliche Drop-Semantik ist dagegen ein starkes Indiz dafür, dass unterschiedliche Created Types vorliegen sollten.
Die gewünschte Trennung lautet:
vor erfolgreicher Konstruktion
Create Domain / Create Arm / Inputs / Row
nach erfolgreicher Konstruktion
Created Type / Ownership / Share / Drop / weitere Invarianten
Drop bleibt semantisches Lifecycle-/Resource-Cleanup und ist nicht synonym mit physischem free.
Beispiel:
Drop(Handler[ConnectionCleanup])
-> Socket schließen
-> Protokollzustand sauber beenden
Drop(Handler[SecureCleanup])
-> Schlüssel zeroen
-> native Ressource freigeben
JME reclaim
-> Speicher mechanisch zurückgewinnen
Diese Trennung ist besonders wichtig bei Trap-/Unwind-Semantik und bei Bulk-Reclamation: Der Lifecycle des Created Type bleibt semantisch, die mechanische Speicherrückgewinnung bleibt Aufgabe von Region/JadeME.
29.1 Lifecycle-Meta: Create und Drop automatisch zusammenführen, ohne sie zu koppeln¶
Die Trennung zwischen Create-Domain und Created Type eröffnet eine sinnvolle Meta-Ableitung. Ihr Zweck ist nicht, zu jedem Create-Arm automatisch einen korrespondierenden Drop-Arm zu erzeugen. Genau das würde die eben eingeführte Zuständigkeitsgrenze wieder aufheben.
Die Meta-Ableitung arbeitet stattdessen auf dem resultierenden Construction-/Lifecycle-Graphen:
Origin Type
↓ Create Domain
Create Arms
↓
Created Types
↓
Lifecycle Meta
├── vorhandenes Drop prüfen
├── eindeutiges Drop ableiten
└── sonst explizite Policy verlangen
Konzeptionell könnte sie als CompilerDB-Ableitung erscheinen:
// spekulativ; Name und konkrete Fassade sind offen
def deriveLifecycle[T: TypeSubject](subject: T) ->Meta{} Unit
Für Connection würde sie zunächst die geschlossene Create-Domain lesen:
Connection.Create
├── Pooled(...) -> PooledConnection
├── Direct(...) -> OwnedConnection
├── Replica(...) -> OwnedConnection
└── Borrowed(...) -> BorrowedConnection
Danach wird nicht pro Arm, sondern pro eindeutigem Created Type gearbeitet:
Direct und Replica führen daher nicht zu zwei Drop-Ableitungen. Beide enden im selben OwnedConnection und teilen dessen Lifecycle-Vertrag.
29.2 Drei mögliche Ergebnisse der Ableitung¶
Für jeden erreichbaren Created Type gibt es drei sinnvolle Fälle.
1. Trivial. Der Typ besitzt keine nicht-trivialen Lifecycle-Pflichten.
2. Eindeutig strukturell ableitbar. Die Lifecycle-Semantik folgt vollständig aus bereits materialisierten Facts der enthaltenen Ressourcen beziehungsweise Felder.
Wenn für OwnedSocket eindeutig feststeht, dass sein reguläres Lebensende Close verlangt, kann die Lifecycle-Meta den Drop von OwnedConnection strukturell daraus ableiten. Entsprechendes gilt etwa für:
wenn ConnectionLease eindeutig beschreibt, dass die Lease an den Pool zurückgegeben werden muss.
3. Mehrdeutig. Aus den vorhandenen Facts folgen mehrere semantisch mögliche Cleanup-Wege. Dann darf Meta nicht raten.
Aus einem bloßen RawHandle folgt nicht eindeutig, ob er geschlossen, zurückgegeben, übertragen oder bewusst nicht besessen wird. In diesem Fall ist eine explizite Lifecycle-Policy erforderlich:
Die Leitregel lautet deshalb:
Lifecycle-Meta darf
Dropnur dort automatisch ableiten, wo die Semantik eindeutig aus bereits materialisierten Facts folgt. Mehrdeutigkeit verlangt eine explizite Entscheidung.
29.3 Die CompilerDB-Fassade bleibt die konstruktive Grenze¶
Die Lifecycle-Meta benötigt keinen privilegierten Sonderpfad im Compiler. Sie arbeitet wie andere Meta-Ableitungen ausschließlich über die CompilerDB-Fassade. Was diese Fassade nicht ausdrücken oder abfragen kann, kann die Meta-Ableitung weder konstruieren noch als Fact behaupten.
Konzeptionell:
CompilerDB-Fassade
↓
Create-Domain lesen
↓
Created Types sammeln
↓
Lifecycle-Facts lesen
↓
Drop ableiten oder explizite Policy verlangen
↓
zuständige Materialisierung / Validierung
Damit bleibt dieselbe Architekturgrenze erhalten wie im übrigen Meta-System:
Meta
konstruiert und leitet ab
zuständige Engine / Materializer
validiert
CompilerDB
hält kanonische Semantik und Provenance
Die Herkunft der Drop-Semantik bleibt dadurch erklärbar. :why kann beispielsweise zeigen:
Why does OwnedConnection close its socket?
OwnedConnection
-> field socket: OwnedSocket
-> OwnedSocket lifecycle requires Close
-> structural lifecycle derivation
-> Drop fact for OwnedConnection
Diese Provenance ist besonders wichtig, weil automatisch erzeugte Lifecycle-Semantik sonst leicht zu unsichtbarer Compiler-Magie würde.
29.4 Lifecycle-Meta ist breiter als Drop-Codegenerierung¶
Langfristig sollte die Ableitung nicht als Generator für Destruktorfunktionen verstanden werden. Sie prüft einen Zustandsübergang und dessen resultierende Invarianten:
Source Type
↓ Create Arm
Created Type
├── Ownership
├── Share
├── Drop
├── Scopeability
├── Transferability
└── Storage Requirements
Drop ist nur eine besonders offensichtliche Endbedingung des erzeugten Zustands. Ob und wie weit diese Meta-Ableitung später auch andere Lifecycle-Facts ableiten darf, bleibt offen. Für v0.1/v0.2 reicht die engere Regel: Create-Domain erfassen, Created Types deduplizieren, deren Drop-Semantik prüfen beziehungsweise bei Eindeutigkeit ableiten.
30. Storage Region: In welcher Speicherdomäne lebt dynamischer Speicher?¶
Das ist die Ebene, auf der Arena, Pool, Scratch, langlebiger Heap, Shared Mapping und ähnliche Mechanismen neu eingeordnet werden sollten.
Die oberste Abstraktion ist MemoryRegion.
Teil VI — MemoryRegion als allgemeine Speicherdomäne¶
31. Vorschlag für die Struktur¶
MemoryRegion
├── Backing
├── Allocation
├── Reclamation
├── Lifetime
├── Budget
├── Accounting
└── Guarantees
Backing¶
Frage:
Woher kommt der Speicher überhaupt?
Beispiele:
Allocation¶
Frage:
Wie wird Speicher innerhalb der Region aufgeteilt?
Beispiele:
Reclamation¶
Frage:
Wie wird belegter Speicher wieder verfügbar?
Beispiele:
Lifetime¶
Frage:
Wie lange existiert die Region beziehungsweise ihre logische Bindung?
Beispiele:
Budget¶
Beispiele:
Accounting¶
Beispiele:
Guarantees¶
Beispiele:
Nicht jede dieser Eigenschaften muss in v0.1 existieren. Wichtig ist die kategoriale Trennung.
32. Warum mmap und FreeList nicht nebeneinander stehen sollten¶
mmap und FreeList werden häufig beide umgangssprachlich als „Allokationsstrategie“ bezeichnet, beantworten aber unterschiedliche Fragen.
mmap
-> Backing / Beschaffung virtueller Speicherseiten
FreeList
-> Allocation/Reclamation innerhalb bereits vorhandenen Speichers
Ebenso:
Arena
-> Region-/Lifetime-Muster, typischerweise Bump + BulkReset
Pool
-> Allocation-/Wiederverwendungsmuster, häufig FixedSlot/Slab + ReturnToPool
Rc
-> Ownership
Die frühere gemeinsame MemoryPolicy versteckt diese Unterschiede.
33. Beispiele für zusammengesetzte Regionen¶
Request Region¶
RequestRegion
├── Backing = Mmap
├── Allocation = Bump
├── Reclamation = BulkReset
├── Lifetime = Scope / Request
├── Budget = 32 MiB
└── Accounting = Basic
General Region¶
GeneralRegion
├── Backing = Mmap
├── Allocation = FreeList
├── Reclamation = Individual
├── Lifetime = Context
├── Budget = 1 GiB
└── Accounting = Basic
Connection Region¶
ConnectionRegion
├── Backing = System
├── Allocation = FixedSlot
├── Reclamation = ReturnToPool
├── Lifetime = Context
├── Capacity = 4096
└── Accounting = Basic
Secure Region¶
SecureRegion
├── Backing = Mmap
├── Allocation = FreeList
├── Reclamation = Individual
├── Lifetime = Context
├── Budget = 64 MiB
└── Guarantees
├── zeroOnRelease
└── stableAddress
Teil VII — Storage-Rollen und Context-Bindung¶
34. StorageRole als semantische Auswahl¶
Anwendungscode sollte nicht auf konkrete Allocator- oder Regionstypen gekoppelt sein.
Empfohlen ist eine geschlossene nominale Rolle, etwa:
Der Tag sagt nur:
Welche Art Speicher brauche ich semantisch?
Er sagt nicht:
Diese Mechanik gehört in ein Profil und dessen Context-Bindung.
35. StorageProfile als beschreibbare Konfiguration¶
Konzeptionell:
// spekulativ
type StorageProfile : blueprint {
backing: BackingSpec
allocation: AllocationSpec
reclamation: ReclamationSpec
lifetime: LifetimeSpec
budget: MemoryBudget
accounting: AccountingSpec
guarantees: StorageGuarantees
}
Beispiel:
// spekulativ
type FastScratchProfile = StorageProfile & {
backing: Mmap
allocation: Bump
reclamation: BulkReset
lifetime: Scope
budget: 64_MiB
}
Ob StorageProfile tatsächlich ein Blueprint ist, hängt davon ab, ob die StorageEngine daraus einen Descriptor materialisieren muss. Das ist derzeit eine plausible und konsistente Richtung, aber noch nicht normiert.
36. Context bindet Rolle an Profil¶
WebContext
└── Storage
├── Default -> GeneralProfile
├── Scratch -> FastScratchProfile
├── Request -> RequestProfile
└── Secure -> SecureProfile
Damit bleibt eine Library context-agnostisch.
Sie sagt nur:
und muss nicht wissen, wie Scratch in Produktion, Tests oder Benchmarks realisiert wird.
37. Gleicher Code, andere Contexts¶
ProductionContext
Scratch -> FastScratchProfile
TestContext
Scratch -> CheckedScratchProfile
BenchmarkContext
Scratch -> InstrumentedScratchProfile
SandboxContext
Scratch -> RestrictedScratchProfile
Der Anwendungscode bleibt identisch.
Das ist eine konsequente Anwendung des Context-Prinzips:
38. Alias und globale Projektkonfiguration¶
Ein Projekt kann zusätzlich Aliase oder Profile verwenden, um eine Default-Konfiguration zentral auszutauschen.
Empfohlen ist jedoch, die semantische Rolle und die technische Konfiguration getrennt zu halten:
Ein Alias sollte daher eher ein Profil aliasen oder eine projektweite Binding-Vorgabe beeinflussen, statt den Tag selbst mit mechanischen Details zu überladen.
Der Grund ist einfach: Derselbe Scratch-Code soll unter unterschiedlichen Contexts unterschiedliche Realisierungen erhalten können.
Teil VIII — Eine aktive Default-Region, mehrere konfigurierte Regionen¶
39. Präzisierung¶
Ein Context soll nicht auf genau eine einzige konfigurierte Speicherstrategie beschränkt sein.
Er kann mehrere Regionen beziehungsweise Rollen anbieten.
Aber:
Zu jedem Ausführungspunkt gibt es genau eine aktive Default-Region für implizite dynamische Allokationen.
Beispiel:
WebContext
├── Default -> GeneralRegion
├── Request -> RequestRegion
├── Scratch -> ScratchRegion
└── Secure -> SecureRegion
Normaler Code:
Dynamische Allokationen benutzen implizit die aktive Default-Region.
Dadurch muss nicht jede allokierende Funktion ->{Storage} deklarieren.
40. Warum normale Allocation kein Storage-Effect sein sollte¶
Wenn jede String-Vergrößerung oder Array-Allokation Storage in die Row ziehen würde, wäre praktisch jede nichttriviale Funktion mit einem Storage-Effect versehen.
Das wäre semantisch wenig hilfreich.
Daher:
implizite normale Allokation
-> aktive Default-Region
-> kein expliziter Storage-Service-Use
explizite Storage-Kontrolle
-> Storage-Service
-> Storage erscheint in der Effect Row
Der Effekt drückt damit nicht aus:
Diese Funktion braucht irgendwo Bytes.
sondern:
Diese Funktion verlangt bewusste Kontrolle über die Storage-Umgebung.
Teil IX — Storage als Service¶
41. Rolle des Services¶
Storage sollte die JDL-seitige semantische Fassade für explizite Storage-Kontrolle sein.
Er ist nicht selbst der Allocator.
Konzeptionell:
Storage Service
"Ich möchte Region mit Rolle Scratch benutzen."
StorageEngine
"Welche geprüfte Region erfüllt Scratch und wie wird sie orchestriert?"
ResourceEngine
"Wie wird der Scope sicher betreten und verlassen?"
JadeME
"Wie werden die Bytes tatsächlich bereitgestellt und zurückgewonnen?"
42. Storage in der Effect Row¶
Beispiel:
Der Context bindet den Service wie jeden anderen Service:
Der EffectEngine führt Storage nicht mechanisch aus. Er ist nur für die Service-/Handler-Semantik zuständig.
Teil X — with, Scopeable und ResourceEngine¶
43. with ist die lokale Escape Hatch¶
Der normale Code benutzt die Default-Region.
Wer bewusst eine andere Storage-Domäne braucht, schreibt:
Das ist die sichtbare Stelle, an der der Entwickler Verantwortung übernimmt.
Innerhalb des Blocks bleiben die einzelnen Allokationen normal und implizit.
44. Gewünschte Semantik¶
vor Scope:
activeRegion = Context.Default
enter:
StorageRole.Scratch auflösen
passende Region aktivieren/binden
Body:
implizite dynamische Allokationen -> ScratchRegion
exit:
vorherige aktive Region wiederherstellen
Scope-Regeln prüfen
Reclamation-/Lifetime-Policy anwenden
45. Storage verwendet das allgemeine Scopeable-Modell¶
Storage soll keine eigene with-Semantik besitzen. Es übernimmt den in Teil III beschriebenen Resource-Lifecycle.
Für eine Storage-Region bedeutet das konkret:
enter
StorageRole auflösen
Region etablieren / aktivieren
vorherige Default-Region sichern
body
implizite Allokationen benutzen die neue aktive Region
exit
Escape-/Lifetime-Regeln abschließen
vorherige Default-Region wiederherstellen
fachliche Region-Policy anwenden
mechanisches cleanup / reap
reservierte Ressourcen gemäß Descriptor sicher zurückgewinnen
Damit bleibt die Trennung bestehen:
ResourceEngine
universelle Scope- und Fehlersemantik
StorageEngine
MemoryRegion-spezifische Semantik
JadeME / Ring 0
mechanische Speicheroperationen
Andere Ressourcen können denselben Scope-Mechanismus verwenden, ohne Storage nachzuahmen:
with Storage.region[Scratch] {
with Database.transaction[ReadWrite] as tx {
with Lock.acquire[CacheWrite] {
...
}
}
}
46. exit und mechanisches cleanup bleiben bei Storage verschieden¶
Für eine Arena-artige Region:
exit
-> logischer Scope endet
-> Escape-Regeln werden abgeschlossen
-> BulkReset möglich
mechanisches cleanup / reap
-> reservierte Pages tatsächlich zurückgeben
Eine Region kann nach exit logisch leer sein, aber reservierten Speicher für spätere Wiederverwendung behalten.
Diese Trennung folgt dem allgemeinen Fehler- und Lifecycle-Modell aus §21: fachliches Scope-Ende darf von mechanischer Rückgewinnung unterschieden werden.
Teil XI — StorageEngine, ResourceEngine und JadeME¶
47. StorageEngine¶
Die bisherige Beschreibung „Arenen, Pools, Rc, Allokationspolicies“ ist nach dieser Empfehlung zu grob und vermischt Ownership mit Storage.
Empfohlen:
StorageEngine materialisiert und orchestriert MemoryRegions, löst Storage-Rollen auf Regionsbindungen auf und vermittelt deren Allocation-, Reclamation-, Budget-, Accounting- und Guarantee-Semantik gegenüber der JadeME.
Rc gehört nicht zu ihrer Kernzuständigkeit.
48. ResourceEngine¶
Empfohlen:
ResourceEngine erzwingt die allgemeine Scope-Lifecycle-Semantik von
with/Scopeable, einschließlich Enter, Exit und garantiertem Cleanup gemäß dem jeweiligen Ressourcenvertrag.
49. JadeME¶
JadeME behält die mechanische Speicherhoheit.
Sie übernimmt unter anderem:
Backing anfordern/freigeben
Allocation-Mechanik
Handle-Tabelle
Generation Counter
Bounds / Typprüfung
Region Accounting
Budget Enforcement
Reclamation
gegebenenfalls Refcount-Mechanik
Der Context bekommt nicht „die JME“. Er bekommt geprüfte Storage-Policies beziehungsweise Regionsbindungen.
Teil XII — Context-Grenzen als Memory-Grenzen¶
50. Context und Region gehören zusammen¶
Contexts bieten eine natürliche Lebensdauer- und Autoritätsgrenze für Speicher.
Konzeptionell:
Wenn der RequestContext endet, darf seine Region nicht beliebig als lebende Ressource außerhalb fortbestehen, sofern keine ausdrückliche Transfersemantik dies erlaubt.
51. Cross-Context-Transfer¶
Ein gewöhnlicher Handle aus Context A darf nicht automatisch in Context B gültig sein.
Mögliche Transferformen bleiben:
Diese Formen müssen später sauber spezifiziert werden.
Der entscheidende Vorteil ist bereits jetzt klar:
Ein Wert, der einen Context verlässt, überquert eine explizite Boundary. Genau dort kann Jade Lifetime-, Ownership- und Transferregeln prüfen.
Dadurch wird eine klassische Fehlerklasse strukturell erschwert:
52. Memory Containment¶
Context-Limits und Regions-Budgets erlauben zusätzlich Schadensbegrenzung:
oder:
Damit löst Jade nicht jeden logischen Leak, begrenzt aber dessen Reichweite.
Teil XIII — Was mit den bisherigen Refinements geschehen sollte¶
53. Memory(...) muss vollständig auditiert werden¶
Die heutige MemoryPolicy ist der Hauptkandidat für eine Neuordnung.
Historisch:
Empfehlung:
Value¶
Nicht als öffentliche Allokationsstrategie behandeln.
Stattdessen größtenteils als abgeleitete Representation:
Rc¶
Aus der Storage-/Allocation-Achse herauslösen.
Als Ownership-Konstrukt behandeln:
Arena[A]¶
Nicht mehr als Typ-Memory-Policy verstehen.
Als Region-/Profilmuster neu modellieren:
Pool[P]¶
Nicht mehr als Typ-Memory-Policy verstehen.
Als Regions-/Allocation-/Reclamation-Profil:
mapped¶
Nicht als gleichrangige MemoryPolicy führen.
Eher als Backing beziehungsweise spezielle Region-Guarantee behandeln.
54. Own, Share, Create, Drop¶
Diese Konzepte bleiben grundsätzlich sinnvoll, müssen aber sauber von Allocation getrennt und untereinander präziser geschnitten werden.
Own
Ownership / Move / Shared-Beziehung des fertigen Wertes
Share
Task-/Concurrency-Garantien des fertigen Wertes
Create
geschlossene Konstruktionsdomäne am Ursprungstyp
Create Arm -> Inputs + Row + Created Type
Drop
semantisches Cleanup des Created Type
Die empfohlene Zuständigkeitsgrenze lautet:
Creategehört an den Typ, dessen zulässige Erzeugungswege beschrieben werden.Dropgehört an den jeweils daraus entstehenden Created Type.
Damit muss ein Wert seine Erzeugungsgeschichte nicht als versteckten Runtime-State mitschleppen. Wenn mehrere Create-Arme denselben Lifecycle erzeugen, dürfen sie denselben Created Type zurückgeben. Wenn sich der Lifecycle unterscheidet, soll der Unterschied im Resultattyp sichtbar sein.
Ob Own langfristig als Refinement, als Wrapper-Typen oder als Kombination daraus bestehen bleibt, ist separat zu entscheiden. Ebenso ist noch offen, wie Create-Arme syntaktisch implementiert beziehungsweise an Handler/Provider gebunden werden; die semantische Zuständigkeit sollte davon unabhängig feststehen.
55. Typen sollten nur echte Storage-Anforderungen tragen¶
Bestimmte Speicheranforderungen sind tatsächlich Eigenschaften eines Typs oder einer Verwendung.
Beispiele:
Diese Anforderungen sollten nicht den konkreten Allocator bestimmen.
Stattdessen:
Damit bleibt die Implementierung austauschbar.
Ein kryptographischer Schlüssel muss vielleicht zeroOnRelease verlangen. Er muss nicht wissen, ob diese Garantie durch mmap, Locked Pages oder einen speziellen Secure Allocator realisiert wird.
Teil XIV — Was der Benutzer davon merkt¶
56. Weniger Tipparbeit ohne versteckte Magie¶
Das Ziel ist nicht, Speicherverwaltung zu verstecken.
Das Ziel ist, die richtige Granularität der Explizitheit zu wählen.
Der Benutzer entscheidet bewusst:
Welche Speicherdomäne brauche ich?
Welche Lebensdauer soll sie haben?
Wann betrete ich eine besondere Region?
Wann verlasse ich sie?
Welche Rolle soll der Context bereitstellen?
Der Benutzer entscheidet normalerweise nicht:
Welcher FreeList-Knoten wird genommen?
Welcher mmap-Block bedient diese 48 Bytes?
Welche einzelne Allocation muss wie freigegeben werden?
Welcher Allocator-Parameter muss durch fünf Funktionssignaturen gereicht werden?
Das ergibt den gewünschten Charakter:
Manuelle Speicherverwaltung, die sich nicht permanent wie manuelle Speicherverwaltung anfühlt.
57. Normalfall¶
Keine sichtbare Allocator-API.
Implizite Allokationen verwenden die aktive Default-Region des Contexts.
58. Bewusste Abweichung¶
Hier ist sichtbar, dass die Funktion bewusst eine besondere Speicherdomäne verlangt.
59. Library-Code bleibt context-agnostisch¶
Eine Library kann Scratch anfordern, ohne Produktion, Test, Benchmark oder Sandbox zu kennen.
Das verhindert eine enge Kopplung an Deployment- oder Hostentscheidungen.
Teil XV — Debugging und Tooling¶
60. Fehler sollen an der Boundary erklärt werden¶
Statt:
soll Jade möglichst Informationen besitzen wie:
allocation failed
Context:
PluginContext
Storage role:
Scratch
Profile:
PluginScratchProfile
Region:
PluginScratch#17
Budget:
64 MiB
Used:
63.8 MiB
Requested:
384 KiB
oder:
value cannot escape RequestRegion
created in:
RequestContext / StorageRole.Request
attempted escape to:
ApplicationContext
allowed transfer forms:
Copy
61. Mission Control¶
Das Modell ist für Mission Control besonders geeignet:
Context
→ StorageRole
→ StorageProfile
→ MemoryRegion
→ Backing
→ Allocator
→ Reclamation
→ Lifetime
→ Budget
→ Accounting
Daraus ergeben sich sinnvolle Probes und Metriken:
reserved
committed
used
peak
allocation count
reclamation count
fragmentation
per-type use
per-callsite use
budget pressure
Ein BenchmarkContext könnte dieselben Regionen mit zusätzlichem Accounting binden, ohne den Anwendungscode zu verändern.
Teil XVI — Optimierungen¶
62. Escape Analysis bleibt wichtig, aber ihre Rolle ändert sich¶
Die heutige Escape-Analyse betrachtet unter anderem NoEscape, Argument-Escape und globale Escapes und darf Refcount-Operationen elidieren.
Nach dieser Empfehlung sollte sie nicht eigenmächtig die semantische Storage-Rolle wechseln.
Sie kann aber innerhalb der garantierten Semantik optimieren:
Retain/Release elidieren
Copy vermeiden
Views beibehalten
Region Crossing früh erkennen
Allokationsgrößen/Layouts optimieren
Ob später automatische Region-Promotion beziehungsweise -Demotion erlaubt wird, sollte separat entschieden werden. Für den Anfang ist eine konservative Regel vorzuziehen:
Optimierungen dürfen die beobachtbare Lifetime-, Boundary- und Storage-Garantie nicht verändern.
63. Context-spezifische Optimierung¶
Weil Storage-Profile im Context gebunden werden, kann dasselbe Programm in verschiedenen Umgebungen unterschiedlich optimiert werden:
Production
FastScratch
Debug
CheckedScratch + poisoning
Benchmark
InstrumentedScratch
Test
TinyBudgetScratch
Das ist kein Quellcode-Fork, sondern eine andere geprüfte Ausführungsumgebung.
Teil XVII — Vorteile der Empfehlung¶
64. Klare Orthogonalität¶
Die größte Verbesserung ist konzeptuell:
Representation
Ownership
Sharing
Construction
Cleanup
Storage Region
Allocation
Reclamation
Lifetime
werden nicht mehr künstlich in eine gemeinsame MemoryPolicy gedrückt.
65. Weniger lokale Konfiguration¶
Speicherentscheidungen, die für eine gesamte Ausführungsdomäne gelten, werden einmal im Context konfiguriert statt an vielen Typen und Allocation-Sites wiederholt.
66. Context-agnostischer Anwendungscode¶
Eine Library arbeitet mit semantischen Rollen statt konkreten Allokatoren.
Das verbessert Wiederverwendbarkeit und Testbarkeit.
67. Explizite Kontrolle bleibt erhalten¶
with Storage.region[...] ist eine sichtbare, lokale Entscheidung.
JDL wird dadurch nicht zu einer Sprache, in der Speicher einfach „irgendwie passiert“.
68. Weniger gefährliche Escape-Situationen¶
Contexts und Bridges bilden natürliche Stellen, an denen Lifetime- und Transferregeln geprüft werden können.
69. Memory Containment¶
Budgets und Context-Limits reduzieren den Schaden aus logischen Leaks oder pathologischen Eingaben.
70. Besseres Tooling¶
Die klare Kette
liefert bessere Diagnosen, :why, Mission-Control-Ansichten und Benchmarks.
71. Erweiterbarkeit¶
Neue Allocator- oder Backing-Mechanismen können ergänzt werden, ohne neue Sprachkonstrukte einzuführen.
Beispielsweise:
solange sie dieselben Descriptor-Verträge erfüllen.
72. Weniger Framework-/API-Lärm¶
Kein permanentes allocator-Argument durch normale Business-Logik.
Kein eigener Arena-Scope-Mechanismus neben with.
Kein Pool-Sonderfall im Typ-System, wenn er als Storage-Profil ausdrückbar ist.
Teil XVIII — Nachteile und Risiken¶
73. Die aktive Region ist impliziter Zustand¶
Das Modell hat bewusst eine implizite Komponente:
Das ist bequem, kann aber verwirrend werden, wenn Code nicht weiß, unter welcher Region er ausgeführt wird.
Gegenmaßnahmen:
Descriptor-/Context-Introspektion
LSP-Hints
Mission Control
klare Scope-Grenzen
keine willkürliche Runtime-Mutation
Die aktive Region darf nicht zu einem versteckten globalen Allocator werden. Sie muss Teil der geprüften Context-/Scope-Semantik bleiben.
74. Escape-Prüfung wird wichtiger¶
Je stärker Regionen die Lebensdauer strukturieren, desto wichtiger wird die Frage:
Darf dieser Wert diese Region verlassen?
Das verlangt saubere Regeln für:
Der Gewinn ist groß, aber die Compilerarbeit verschwindet nicht.
75. Profile können kombinatorisch wachsen¶
Wenn jede Kleinigkeit ein eigenes StorageProfile bekommt, entsteht dieselbe Konfigurationshölle nur eine Ebene höher.
Daher sollte gelten:
Profile sind wiederverwendbare technische Konfigurationen. Rollen sind wenige semantische Namen. Contexts binden beides gezielt zusammen.
76. Zu viel implizite Optimierung wäre gefährlich¶
Wenn die Runtime spontan andere Regionen oder Allokatoren auswählt, können Performance und Lifetime schwer vorhersehbar werden.
Die erste Version sollte daher konservativ bleiben.
77. Komplexe Policy-Kompatibilität¶
Nicht jede Kombination ist gültig:
Bump + Individual Free
FileMapped + Moving Compaction
zeroOnRelease + bestimmte External Backings
stableAddress + moving allocator
Die StorageEngine beziehungsweise der Materializer braucht eine klare Kompatibilitätsprüfung.
78. Allocation Failure bleibt eine echte Sprachdesignfrage¶
Die Region kann ihr Budget erreichen. mmap kann scheitern. Ein Pool kann voll sein.
Offen ist, welche Fehlerklasse daraus entsteht.
Wenn jede implizite Allocation Throw[OutOfMemory] erzeugt, verseucht dies praktisch alle Effect Rows.
Wenn jede Allocation stattdessen trappt, verliert der Benutzer bei absichtlich begrenzten Regionen Kontrolle.
Eine mögliche Trennung:
normale implizite Default-Allocation
-> definierter Resource Trap bei harter Erschöpfung
explizit fallible / begrenzte Region
-> AllocationError als normaler Fehler
Das ist noch zu entscheiden.
Teil XIX — Offene Fragen¶
79. Allgemeine offene Fragen des Resource-Modells¶
Bevor das Muster normativ wird, müssen einige Fragen unabhängig von Storage beantwortet werden.
Ist ResourceRole immer ein Tag?¶
Tags sind für geschlossene nominale Rollen wie Scratch, ReadWrite oder Temporary sehr attraktiv. Es sollte aber nicht vorschnell normiert werden, dass jede Resource-Domäne dieselbe Deklarationsform braucht.
Entscheidend ist die Semantik:
nicht das konkrete Syntaxkonstrukt.
Muss jedes ResourceProfile ein Blueprint sein?¶
Wenn ein Profil von einer DescriptorEngine materialisiert und durch Refinements erweitert wird, passt blueprint sehr gut zum bestehenden Jade-Modell.
Für triviale Ressourcen könnte dies unnötig sein. Zu entscheiden ist, ob ResourceProfile eine allgemeine semantische Rolle von Typen ist oder ob jede Fachdomäne ihre eigenen Profiltypen definiert.
Gibt es einen allgemeinen ResourceDescriptor?¶
Wahrscheinlich sollte es keinen riesigen universellen Descriptor mit Feldern für jede Ressource geben.
Sinnvoller erscheint:
Beispielsweise braucht eine Transaction Isolation und Commit-Policy, eine MemoryRegion dagegen Backing und Allocation. Gemeinsam sind nur die Lifecycle-/Scope-Invarianten.
Wie sieht Scopeable exakt aus?¶
Offen sind unter anderem:
enter-Signatur
ExitReason / ScopeExit
assoziierte Resource-Typen
fallible exit
Cancellation
ob enter das Resource-Objekt selbst oder eine Lease liefert
Die Syntax sollte erst nach der Semantik festgelegt werden.
Wie werden mehrere Fehler erhalten?¶
Der schwierige Fall ist:
Fehler B darf A nicht stillschweigend überschreiben. Zu entscheiden ist, ob JDL dafür eine allgemeine Fehlerkomposition, einen ScopeFailure oder eine andere Provenance-/Suppressed-Error-Form erhält.
Welche Cleanup-Garantie ist wirklich universal?¶
Zu definieren ist, welche mechanischen Invarianten die ResourceEngine beziehungsweise Ring 0 ohne beliebigen User-Code wiederherstellen können muss.
Insbesondere:
haben unterschiedliche Wiederherstellungsmöglichkeiten.
Wie verhalten sich Resources an Context-Grenzen?¶
Nicht jede Resource darf einen Context verlassen. Für manche kommen Copy, Transfer oder Lease in Frage; andere müssen strikt contextgebunden bleiben.
Das Resource-Modell sollte hier mit der bestehenden Bridge-/Handle-Semantik zusammenlaufen, nicht einen zweiten Transfermechanismus erfinden.
Wie wird eine Role an einen Service gebunden?¶
Storage.Scratch und Database.ReadWrite gehören unterschiedlichen fachlichen Domänen an. Zu entscheiden ist, ob ContextDescriptoren dafür eine gemeinsame generische Binding-Struktur besitzen oder jede Service-/Engine-Domäne ihre eigenen Descriptorfelder materialisiert.
Braucht jede Resource-Domäne eine Engine?¶
Nein als Designziel; aber die genaue Schwelle sollte dokumentiert werden.
Faustregel:
Eine eigene Engine ist nur gerechtfertigt, wenn wiederverwendbare Ausführungsinvarianten orchestriert werden müssen, die nicht bloß Provider-Logik sind.
Wie interagiert with mit Cancellation und Scheduler?¶
Cancellation ist weder normaler Return noch zwingend Throw. Für scoped Resources muss festgelegt werden, ob und wann exit noch fachlich ausgeführt wird und welche mechanischen Cleanup-Garantien gelten.
Wie werden nicht-scoped Resources modelliert?¶
Pools, langlebige Verbindungen, Daemons oder Context-eigene Ressourcen können einen Lifecycle besitzen, ohne in einem lokalen with zu leben. Das allgemeine Resource-Modell darf Resource und Scopeable daher nicht synonym machen.
80. Was gilt exakt als JME-Allokation?¶
Ist jede indirekte Wertrepräsentation eine Allocation?
Wie werden Reallocations dynamischer Collections behandelt?
Wie werden Inline-Werte und temporäre Compiler-/VM-Frames abgegrenzt?
81. Hat jede dynamische JME-Allokation genau eine MemoryRegion?¶
Empfehlung: ja.
Das würde Accounting, Lifetime und Containment erheblich vereinfachen.
Es muss aber geprüft werden, ob bestimmte globale JME-Strukturen oder internierte Runtime-Daten eine Sonderdomäne brauchen.
82. Wie wird die aktive Default-Region gespeichert?¶
Mögliche Orte:
Sie muss billig erreichbar sein, ohne jeden Allocation-Hot-Path über RuntimeBus zu schicken.
83. Wie funktioniert with Storage.region[...] mechanisch?¶
Die ältere Arena-Command-Skizze verwendet Push/PopReset und RuntimeBus.
Für das allgemeine Regionsmodell sollte getrennt werden:
Der Hot Path sollte direkte privilegierte JME-Mechanik benutzen können, nachdem die Region einmal autorisiert und gebunden wurde.
84. Wie werden Region-Crossings behandelt?¶
Für verschiedene Wertformen werden unterschiedliche Regeln nötig:
Value
Copy trivial
Copyable handle-backed value
Copy in Zielregion möglich
Unique
Transfer möglich?
Rc
darf Rc über Context-/Region-Grenzen geteilt werden?
View/Slice
Copy oder Verbot
non-Copyable resource
Lease / Transfer / Fehler
85. Was bedeutet Rc[T] regionsübergreifend?¶
Wenn der referenzierte Wert in einer kurzlebigen Region liegt, darf ein langlebiger Rc[T] darauf zeigen?
Wahrscheinlich nur, wenn die Region-Lifetime die Ownership-Lifetime tragen kann oder eine Promotion/Copy stattfindet.
Das muss formalisiert werden.
86. Wie interagieren Created Types mit nicht-trivialem Drop und Bulk Reclamation?¶
Eine Bump-/BulkReset-Region kann Speicher billig verwerfen. Ein Created Type mit Drop(Handler[H]) kann aber semantisches Cleanup verlangen, das vor der mechanischen Rückgewinnung ausgeführt werden muss.
Mögliche Regeln:
oder:
oder:
Hier muss Performance gegen semantische Korrektheit abgewogen werden. Wichtig ist, dass diese Entscheidung am Created Type und dessen Drop-Vertrag hängt, nicht am Create-Arm, über den der Wert ursprünglich entstanden ist.
86.1 Wie weit darf Lifecycle-Meta automatisch ableiten?¶
Die Grundregel ist klar: nur bei Eindeutigkeit. Offen bleibt jedoch, welche Facts für diesen Beweis zulässig sind und wie weit strukturelle Ableitung reichen darf.
Zu klären sind insbesondere:
Darf Drop rekursiv aus Feldern abgeleitet werden?
Wie werden rekursive Typen behandelt?
Welche Resource-/Ownership-Facts gelten als hinreichend eindeutig?
Darf ein Provider zusätzliche Lifecycle-Facts materialisieren?
Wie werden widersprüchliche explizite und abgeleitete Facts diagnostiziert?
Wann gilt ein Raw-/Native-Handle als prinzipiell nicht ableitbar?
Wie werden Ableitung und Provenance in CompilerDB gecacht/invalidiert?
Die sichere Default-Position sollte konservativ sein: nicht beweisbar bedeutet nicht ableiten. Ein zusätzlicher expliziter Handler ist lästig; ein falsch automatisch erzeugter Cleanup-Pfad ist dagegen ein Runtime-Schaden mit akademischer Begründung.
87. Welche Storage-Garantien gehören wirklich auf den Typ?¶
Kandidaten:
Es sollte vermieden werden, wieder technische Implementierungsdetails als Typmetadaten einzuführen.
88. Sind StorageRole-Tags global, namespace-lokal oder projektspezifisch?¶
Scratch, Request, Secure sind naheliegende Stdlib-Rollen.
Anwendungen und Libraries brauchen vermutlich eigene Rollen.
Die Namens- und Konfliktregeln müssen zum bestehenden Namespace-/Tag-Modell passen.
89. Wie funktioniert ein fehlendes Role-Binding?¶
Wenn Code verlangt:
aber der Context Scratch nicht bindet, sollte das möglichst früh scheitern:
abhängig davon, wann der Context bekannt ist.
90. Darf ein Context dieselbe Rolle mehrfach binden?¶
Wahrscheinlich nein, sofern kein expliziter Auswahlmechanismus existiert.
Eine Rolle sollte innerhalb eines materialisierten Contexts eindeutig aufgelöst werden.
91. Wie stark darf Storage Meta sein?¶
Wenn StorageProfile ein Blueprint ist und die StorageEngine Descriptoren erzeugt, kann Meta Profile ableiten und komponieren.
Das passt zum allgemeinen Jade-Modell, muss aber gegen die CompilerDB-Fassade und die Materialisierungsinvarianten spezifiziert werden.
92. Wie werden benutzerdefinierte Allocator-Bausteine zugelassen?¶
Mögliche Ebenen:
nur Stdlib/Ring 1
Plugins
gewöhnliches JDL mit Engine/Provider-Vertrag
native Provider über Jade Value Bridge
Nicht jeder benutzerdefinierte Allocator sollte automatisch Trusted-Core-Rechte bekommen.
93. Welche Teile sind compile-time, load-time und runtime?¶
Beispielsweise:
StorageProfile
meist compile-time materialisierbar
Budget aus Deployment-Konfiguration
eventuell load-time
Region-Instanz
runtime
aktive Region
runtime scope state
Die Descriptor-Grenzen müssen diese Staffelung sauber tragen.
94. Wie verhält sich Storage zu Envelopes?¶
Ein Envelope darf die Storage-Autorität des Targets nur erhalten oder einschränken.
Beispiele:
SandboxEnvelope
ersetzt DefaultStorage durch RestrictedStorage
reduziert Budget
entfernt Secure/Network-backed roles
Ein Envelope kann eigene Storage-Rollen besitzen, ohne sie dem Target automatisch zu exponieren.
Das folgt derselben Authority-Monotonie wie bei anderen Context-Eigenschaften.
Teil XX — Empfohlene Vereinfachung der bisherigen Architektur¶
95. Was verschwinden oder zurückgestuft werden könnte¶
Die Empfehlung würde mehrere heutige Sonderfälle vereinfachen:
Memory(Arena[A])
-> StorageRole + StorageProfile + Region
Memory(Pool[P])
-> StorageProfile / Region
Memory(mapped)
-> Backing
Memory(Rc)
-> Ownership / Representation
Arena-spezifisches with
-> allgemeines with + Storage.region[Role]
Damit sinkt die Zahl der Dinge, die TypeEngine und normaler Anwendungscode als spezielle Memory-Arten kennen müssen.
96. Was bestehen bleibt¶
JadeME Handle-Safety
Generation Counter
Context Isolation
Bridge Transfer
Rc / Weak Ownership
Share / Send / Sync
Create Domain / Create Arms
Drop Lifecycle des Created Type
ResourceEngine
Effect Rows
Storage Service
Die Empfehlung verwirft diese Mechanismen nicht. Sie ordnet sie neu.
97. StorageEngine neu zuschneiden¶
Alt, grob:
Empfohlen:
MemoryRegion descriptors
Storage role resolution
Region lifecycle/orchestration
Backing/allocation/reclamation contracts
Budgets/accounting/guarantees
JME binding
Ownership (Rc, Weak) wird herausgelöst.
Teil XXI — Migrations- und Audit-Liste¶
98. Dokumente, die bei Annahme überprüft werden müssten¶
Mindestens:
00-refinements
jade-design-decisions
02-typsystem
08-intrinsics-vm-api
10-runtime-handles-und-memory-safety
jade-commands-with-arena
sketch-escape-analysis-refcount-elision
spec-context-model
Engine-Beschreibungen / Foundation Engine Set
JadeValue/Register-Layout
JME-/Handle-Konsolidierungen
Die wichtigste Arbeit ist nicht mechanisches Umbenennen, sondern das Entfernen falscher Querabhängigkeiten.
99. Besonders kritisch zu auditieren¶
MemoryPolicy¶
Welche Teile sind Representation, Ownership, Region oder Backing?
Rc / historisches Ref¶
Ist Rc Typ-/Ownership-Semantik oder JME-Repräsentation? Wahrscheinlich beides intern, aber nicht dieselbe öffentliche Achse.
Arena / Pool¶
Von Type-Policy zu StorageProfile/MemoryRegion verschieben.
Escape Analysis¶
Alte Invariante „Allokationsort bleibt identisch“ auf das neue Regionsmodell übertragen.
Commands with Arena¶
Von Arena-Sonderfall auf generischen Region-Control-Path heben.
StorageEngine¶
Ownership entfernen, Region-Semantik schärfen.
Create / Drop¶
Create(Factory[F]) und Drop(Custom) gegen die neue Zuständigkeitsgrenze auditieren:
Create
-> geschlossene Create-Domain am Ursprungstyp
-> Create Arm beschreibt Inputs, Row und Created Type
Drop
-> Eigenschaft des Created Type
-> Trivial oder expliziter Handler/Vertrag
Creation-Provenance darf nicht nur deshalb als Runtime-State konserviert werden, damit später der richtige Drop-Pfad gefunden wird. Unterschiedliche Lifecycle-Pflichten sollen durch unterschiedliche Created Types ausgedrückt werden.
Zusätzlich sollte eine Lifecycle-Meta-Ableitung vorgesehen werden, die die Create-Domain liest, die erreichbaren Created Types dedupliziert und deren Drop-Semantik prüft beziehungsweise nur bei eindeutiger Herleitung automatisch materialisiert. Die heutige Factory[F]-/Custom-Form enthält dafür weder die geschlossene Transition-Domain noch genügend Provenance.
Teil XXII — Gegenüberstellung alt gegen empfohlen¶
100. Alter mentaler Pfad¶
Typ
:> Memory(Arena[X])
:> Own(...)
:> Share(...)
:> Drop(...)
:> Create(...)
Compiler/JME versucht daraus Storage, Konstruktion und Lifecycle gleichzeitig abzuleiten
Problem: Der Typ wird zum Sammelort von Entscheidungen, die eigentlich aus Umgebung, Lebensdauer und konkreter Verwendung stammen.
101. Empfohlener mentaler Pfad¶
Ursprungstyp
-> echte Typgarantien
-> geschlossene Create-Domain
Create Arm
-> Inputs + Row + Created Type
Created Type
-> Ownership / Share / Drop / weitere Lifecycle-Garantien
Code
-> StorageRole bei bewusster Abweichung
Context
-> Role -> Profile
StorageProfile
-> Backing + Allocation + Reclamation + Lifetime + ...
StorageEngine
-> geprüfter Descriptor / Region Binding
JadeME
-> Bytes + Handles + Enforcement
Damit bekommt jede Ebene genau die Entscheidungen, die zu ihr gehören.
Teil XXIII — Vorläufige Designregeln¶
Die folgenden Regeln sind als Empfehlung zu verstehen, nicht als bereits normative Spec.
Allgemeines Resource-Modell:
- Ein Service ist eine semantische Umgebungsanforderung, nicht automatisch eine Resource.
- Eine Resource ist eine laufende Instanz mit relevantem Lifecycle.
- Resource Roles benennen Absicht, nicht Mechanik.
- Resource Profiles beschreiben wiederverwendbare Policy.
- Contexts binden Roles an konkrete Profiles beziehungsweise Descriptoren.
withverwendet einen allgemeinen, geprüften Resource-Lifecycle statt domänenspezifischer Scope-Sondermechanismen.enterdarf fachlich scheitern; der Body wird dann nicht betreten.- Ein
Throwim Body führt zu geordnetemexit, anschließendem mechanischem Cleanup und danach Propagation. - Ein Exit-Fehler darf einen bereits laufenden Primärfehler nicht stillschweigend vernichten.
- Mechanisches Cleanup/Reaping ist Runtime-Invariante und kein gewöhnlicher fachlicher Fehlerkanal.
- Strukturell beschreibbare Unzulässigkeiten sollen vor Ausführung bei Materialisierung/Admission scheitern.
Createbeschreibt am Ursprungstyp eine geschlossene Menge zulässiger Konstruktionsübergänge.- Ein Create-Arm beschreibt mindestens seine Eingaben, seine erforderliche Row und den daraus entstehenden Created Type.
Dropgehört zum Created Type, nicht zum Create-Arm. Mehrere Create-Arme dürfen denselben Created Type und damit dieselbe Drop-Semantik liefern.- Unterschiedliche Lifecycle-/Drop-Pflichten sollen durch unterschiedliche Created Types ausgedrückt werden, nicht durch versteckte Creation-Provenance im Wert.
- Lifecycle-Meta arbeitet auf eindeutigen Created Types, nicht auf einer 1:1-Paarung von Create-Armen und Drop-Pfaden.
- Automatische Drop-Ableitung ist nur zulässig, wenn sie eindeutig aus bereits materialisierter Semantik folgt; Mehrdeutigkeit verlangt eine explizite Policy beziehungsweise einen Handler.
- Automatisch abgeleitete Lifecycle-Semantik muss Provenance tragen und über die normale CompilerDB-Fassade sowie die zuständige Materialisierung/Validierung laufen.
Storage-spezifische Regeln:
- Jede dynamische JME-Allokation gehört genau einer MemoryRegion.
- Ein Context darf mehrere Storage-Rollen binden.
- Zu jedem Ausführungspunkt existiert genau eine aktive Default-Region für implizite Allokationen.
- Normale implizite Allokationen erzeugen keinen
Storage-Eintrag in der Effect Row. - Explizite Auswahl oder Manipulation einer Storage-Region erfolgt über den
Storage-Service und ist in der Row sichtbar. with Storage.region[R]ist die normale lokale Form zum temporären Wechsel der aktiven Region.- StorageRole benennt Bedeutung, nicht Mechanik.
- Der Context bindet StorageRole an ein StorageProfile beziehungsweise einen materialisierten StorageDescriptor.
- StorageProfile zerlegt Speichermechanik mindestens in Backing, Allocation, Reclamation und Lifetime.
- Budget, Accounting und Guarantees sind orthogonale Region-Eigenschaften.
RcundWeakgehören zur Ownership-Semantik, nicht zur Storage-Strategie.Create,ShareundDropbleiben von Allocation/Storage getrennte Achsen;Createsitzt am Ursprungstyp,ShareundDropgelten für den fertigen beziehungsweise Created Type.- Typen dürfen notwendige Storage-Garantien formulieren, aber keine konkrete Deployment-Allocator-Mechanik erzwingen, sofern diese nicht tatsächlich Teil der Typsemantik ist.
- Context-Grenzen sind zugleich Memory-Boundaries; gewöhnliche Handles überschreiten sie nicht unkontrolliert.
- Cross-Context-Werte benötigen eine explizite Transferform.
- ResourceEngine besitzt die allgemeine Scope-Semantik; Storage implementiert keinen zweiten
with-Mechanismus. - StorageEngine orchestriert Regionen; JadeME besitzt die mechanische Speicherhoheit.
- Der Allocation-Hot-Path darf nach erfolgter Autorisierung nicht unnötig über RuntimeBus gehen.
- Debugging- und Toolingdaten sollen Role, Profile, Region, Budget und Lifetime nachvollziehbar machen.
- Optimierungen dürfen die beobachtbare Storage-/Lifetime-Semantik nicht verändern.
Teil XXIV — Abwägung¶
102. Warum diese Richtung attraktiv ist¶
Sie passt zu mehreren bereits etablierten Jade-Prinzipien gleichzeitig:
Context statt globaler Umgebung
Descriptor statt versteckter Runtime-Magie
Service Row statt Ambient Service Locator
Engines für Ausführungssemantik
JadeME für mechanische Speicherhoheit
with für deterministischen Scope
Tags für nominale Bedeutung ohne Runtime-Layout
Vor allem reduziert sie die Zahl lokaler Entscheidungen, ohne Kontrolle zu entfernen.
Sie ist deshalb keine „höhere Abstraktion um weniger sehen zu müssen“, sondern eine Verschiebung der Entscheidung an den Ort, an dem sie semantisch hingehört.
103. Was sie nicht löst¶
Sie verhindert nicht automatisch:
Rc-Zyklen
logisch unbeschränkte Caches
schlechte Datenstrukturen
falsche Weak-Beziehungen
pathologische Algorithmen
FFI-Bugs außerhalb der sicheren Grenze
Sie kann diese Fehler aber besser begrenzen und diagnostizieren.
104. Was sie voraussichtlich wirklich eliminiert oder stark reduziert¶
Besonders die lästigen Klassen:
falsche Region am falschen Ort
vergessenes Arena-Reset
inkompatible Allocator-Lebensdauer
versteckte temporäre Speicher-Escapes
fehlendes Cleanup auf normalen Scope-Pfaden
unbeabsichtigte langlebige temporäre Allokationen
unkontrollierte Context-übergreifende Handles
fehlende Speicherbudgets pro Domäne
Allocator-Konfiguration über die Codebase verteilt
Das sind genau die Fehler, bei denen ein Entwickler sonst lange Stack-Traces analysiert, obwohl der Fehler an einer völlig anderen Stelle entstanden ist.
Teil XXV — Empfehlung¶
105. Empfehlung für die weitere Planung¶
Diese Richtung sollte ernsthaft weiterverfolgt werden.
Nicht empfohlen ist, sofort Syntax festzulegen.
Zuerst sollte das semantische Modell stabilisiert werden:
1. Allgemeines Resource-Modell festziehen: Service, Role, Profile, Descriptor, Instance, Scopeable.
2. Fehlersemantik von `with` festlegen: enter, body, exit, mechanisches cleanup/reap, Trap-Pfad.
3. Context-Binding für Resource Roles und Profiles normieren.
4. MemoryPolicy inventarisieren und jede Variante einer echten Achse zuordnen.
5. `Create`/`Drop` neu schneiden: geschlossene Create-Domain am Ursprungstyp, Drop-Vertrag am Created Type; alte `Factory[F]`-/`Custom`-Formen auditieren.
6. Lifecycle-Meta definieren: Create-Domain lesen, Created Types deduplizieren, explizite Drop-Facts prüfen und nur eindeutig beweisbare Drop-Semantik automatisch ableiten; Provenance verpflichtend erhalten.
7. MemoryRegion formal definieren.
8. StorageRole -> StorageProfile -> StorageDescriptor -> Region festlegen.
9. Default-Region-Semantik festlegen.
10. `with Storage.region[...]` und Scope-Crossing definieren.
11. Rc/Weak/Ownership aus Storage herauslösen.
12. StorageEngine und ResourceEngine neu abgrenzen.
13. Cross-Region- und Cross-Context-Regeln definieren.
14. Allocation-Failure entscheiden.
15. Das allgemeine Resource-Muster an mindestens Transaction, Lock und File gegenprüfen.
16. Erst danach Syntax und Stdlib-Profile normieren.
Der wahrscheinlich stärkste Leitsatz für die weitere Arbeit lautet:
Der Benutzer verwaltet in JDL Speichergrenzen und Lebensdauern bewusst; die konkrete Einzelallokation bleibt innerhalb einer geprüften Speicherdomäne gewöhnlich implizit.
Und die allgemeine architektonische Kurzform:
Code benennt Rollen. Profile beschreiben Policy. Descriptoren tragen geprüfte Semantik. Contexts binden konkrete Ausprägungen.
ResourceEnginestrukturiert den Lifecycle. Fachliche Engines beziehungsweise Provider führen die Domänensemantik aus. Ring 0 erledigt die Mechanik.
Für Storage konkret:
Typen formulieren Garantien. Code benennt Storage-Rollen. Contexts binden Rollen an Profile. StorageEngine materialisiert Regionen. JadeME verwaltet die Bytes.
Glossar¶
Context-bound Resource Model
Empfohlenes allgemeines Muster, bei dem Resource Roles semantische Absicht benennen, Profiles Policy beschreiben, Descriptoren geprüfte Semantik tragen, Contexts konkrete Ausprägungen binden und ResourceEngine den strukturierten Lifecycle einer laufenden Resource garantiert.
Service
Semantische Umgebungsanforderung in einer Effect Row. Ein Service kann Ressourcen bereitstellen oder vermitteln, ist aber nicht automatisch selbst eine Resource.
Resource
Laufende Instanz mit relevanter Besitz-, Lebensdauer-, Abschluss- oder Freigabesemantik, etwa eine MemoryRegion, Transaction, Lock-Lease oder geöffnete Datei.
Resource Role
Nominale semantische Auswahl für eine Ressource, etwa Scratch, ReadWrite, CacheWrite oder Temporary. Die Rolle benennt Absicht, nicht konkrete Mechanik.
Resource Profile
Deklarative Policy für eine Resource Role. Ein Context kann dieselbe Rolle an unterschiedliche Profile binden.
Resource Descriptor
Materialisierte und geprüfte Form eines Resource Profiles beziehungsweise Ressourcenvertrags. Enthält die Semantik, die spätere Phasen ohne erneuten CompilerDB-Zugriff benötigen.
Resource Instance
Konkrete laufende Ressource, die aus einem geprüften Descriptor beziehungsweise Provider hervorgeht. Darf lauf-lokale Handles oder Leases enthalten.
Scopeable
Allgemeiner Lifecycle-Vertrag für Ressourcen, die über with strukturiert werden können. Fachliches enter/exit wird von mechanischem Cleanup beziehungsweise Reaping unterschieden.
ResourceEngine
Foundation Engine für allgemeine with-/Scope-/Fehlerpfad-Semantik. Sie kennt nicht die fachliche Bedeutung jeder Ressource, sondern erzwingt deren geprüften Lifecycle-Vertrag.
Admission / Materialisierung
Prüfgrenze, an der eine beschriebene Resource-/Context-Konfiguration gültig werden muss, bevor sie ausgeführt werden darf. Ziel ist, strukturell beschreibbare Unzulässigkeiten vor der eigentlichen Ausführung zu eliminieren.
Create Domain
Geschlossene, am Ursprungstyp definierte Menge zulässiger Konstruktionsformen. Die Domain sollte nominal und exhaustiv prüfbar sein, beispielsweise über eine Tag-/Enum-Menge.
Create Arm
Ein einzelner Konstruktionsübergang innerhalb einer Create Domain. Beschreibt konzeptionell Eingaben, erforderliche Services/Rows und den daraus entstehenden Created Type.
Created Type
Der konkrete Resultattyp nach erfolgreicher Konstruktion. Er trägt die für den fertigen Wert relevanten Garantien wie Ownership, Sharing und insbesondere Drop. Mehrere Create-Arme dürfen denselben Created Type liefern.
Drop
Semantische Cleanup-Verpflichtung eines Created Type am regulären Lebensende. Drop ist nicht der Gegenpart eines einzelnen Create-Arms und nicht synonym mit mechanischem Speicher-Reclaim.
Lifecycle-Meta
Meta-Ableitung über dem Construction-/Lifecycle-Graphen. Sie liest die Create-Domain eines Ursprungstyps, sammelt die eindeutigen Created Types und prüft beziehungsweise leitet deren Lifecycle-/Drop-Semantik aus bereits materialisierten Facts ab. Sie paart nicht Create-Arme mit Drops und darf bei Mehrdeutigkeit keine Semantik erfinden.
Structural Drop Derivation
Automatische Ableitung eines Drop-Facts für einen Created Type aus eindeutigem Lifecycle-Verhalten seiner enthaltenen Ressourcen beziehungsweise Felder. Ist die Ableitung nicht eindeutig, ist eine explizite Policy oder ein Handler erforderlich.
Allocation
Bereitstellung eines Speicherbereichs für einen konkreten Laufzeitwert innerhalb einer Region.
Backing
Quelle beziehungsweise unterliegende Beschaffung des Speichers, etwa System-Speicher, mmap, File Mapping oder Shared Memory.
Allocation Strategy
Mechanismus, mit dem vorhandener Regionsspeicher in konkrete Allokationen aufgeteilt wird, etwa Bump, FreeList oder Slab.
Reclamation
Mechanismus, mit dem belegter Speicher wieder verfügbar wird, etwa Individual Free, Bulk Reset oder Return to Pool.
Lifetime
Regel, wie lange eine MemoryRegion beziehungsweise ihre logische Bindung existiert.
MemoryRegion
Geprüfte Speicher- und Lebensdauerdomäne mit Backing, Allocation, Reclamation, Lifetime, Budget, Accounting und Garantien.
StorageRole
Nominale semantische Bezeichnung dafür, wofür Speicher benötigt wird, etwa Scratch, Request oder Secure. Die Rolle beschreibt nicht die konkrete Mechanik.
StorageProfile
Deklarative Konfiguration, welche Speichermechanik und Garantien eine Region besitzen soll.
StorageDescriptor
Materialisierte, geprüfte Form eines StorageProfiles beziehungsweise einer Regionskonfiguration.
Default-Region
Die an einem Ausführungspunkt aktive Region, die normale implizite dynamische Allokationen bedient.
Storage Service
JDL-seitige Service-Fassade für bewusste, explizite Storage-Kontrolle. Nur diese bewusste Kontrolle soll Storage in die Effect Row bringen.
StorageEngine
Engine für Materialisierung und Orchestrierung von MemoryRegions sowie für Role-/Profile-/Descriptor-Auflösung. Sie besitzt nicht die physische Speicherhoheit.
JadeME
Privilegiertes Jade-Memory-Subsystem. Führt mechanische Speicheroperationen aus, verwaltet Handles, Generation Counter, Region Accounting und Enforcement.
Memory Containment
Begrenzung des Schadens eines Speicher- oder Ownership-Fehlers durch Context- und Region-Grenzen, Budgets und kontrollierte Übergänge. Es ist nicht dasselbe wie vollständige Leak-Vermeidung.
Ownership
Semantik darüber, wer einen Wert am Leben hält und wie Besitz geteilt oder übertragen wird. Rc und Weak gehören hierher.
Representation
Physische beziehungsweise VM-seitige Darstellung eines Wertes, beispielsweise inline oder über Handle. Diese sollte weitgehend abgeleitet werden.
Storage Guarantee
Anforderung oder Zusage wie Alignment, stabile Adresse, Zero-on-Release oder Non-Movable. Eine Garantie sagt nicht, mit welcher konkreten Allocator-Implementierung sie erreicht wird.
Quellenbasis und Bezug zum heutigen Stand¶
Diese Notiz baut auf dem derzeitigen Jade/JDL-Stand auf, insbesondere auf:
JDL — Context-Modell: Context als descriptor-materialisierte Ausführungsgrenze;StorageSpec; Storage-Policy im Context, operative Speicherhoheit bei JadeME.Refinements: heutige compilerinterneMemoryPolicymitValue,Ref,Arena[A],Pool[P]sowie getrennteOwn,Share,Create,Drop; diese Notiz empfiehlt fürCreate/Dropzusätzlich einen neuen Zuständigkeitsschnitt mit Create-Domain am Ursprungstyp und Drop am Created Type.Runtime Handles & Memory Safety: HandleId + Generation Counter, Trennung von Speicherkorruption und logischen Leaks, Arena-/Pool-/Mapped-Policies als historische Speicherstrategien.Commands with Arena: bestehendewith Storage.arena[...]-Semantik, Push/Pop/Reset und Arena-Crossing.Escape Analysis & Refcount Elision: EscapeInfo, Refcount-Elision und die heutige Forderung, beobachtbare Allokationssemantik bei Optimierungen nicht zu verändern.Jade Design Decisions:Rc/Weak, Arena- und Pool-Motive sowie der bisherige composability-orientierte Memory-Ansatz.
Die Empfehlung dieser Notiz ist bewusst eine Neuordnung dieser bestehenden Bausteine. Sie behauptet nicht, dass der vorgeschlagene Schnitt bereits normativ wäre.
Neu hinzu kommt die übergeordnete Empfehlung eines Context-bound Resource Model: Services bleiben semantische Umgebungsanforderungen; Ressourcen sind konkrete laufende Instanzen mit Lifecycle; Rollen benennen Absicht; Profile beschreiben Policy; Descriptoren tragen geprüfte Semantik; Contexts binden konkrete Ausprägungen; ResourceEngine strukturiert with und Fehlerpfade. Storage wird als erste ausführlich ausgearbeitete Spezialisierung dieses allgemeineren Musters verstanden.
Für Construction/Lifecycle ergänzt die Notiz außerdem eine Lifecycle-Meta-Ableitung: Create bleibt geschlossene Transition-Domain am Ursprungstyp; die Meta-Ableitung dedupliziert die daraus erreichbaren Created Types und prüft beziehungsweise materialisiert deren Drop nur bei eindeutiger Herleitung. Nicht eindeutige Fälle bleiben explizit.
Online-Referenzen:
- https://specs.iowling.at/spec-context-model/
- https://specs.iowling.at/00-refinements/
- https://specs.iowling.at/10-runtime-handles-und-memory-safety/
- https://specs.iowling.at/jade-commands-with-arena/
- https://specs.iowling.at/sketch-escape-analysis-refcount-elision/
- https://specs.iowling.at/jade-design-decisions/