Memory management / Lifetime regions and escape validation
Every allocation has exactly one semantic lifetime owner. GC cannot bypass topology validation.
Moth is reference-semantic by default, copy-explicit and move-inferred. It omits explicit reference types and lifetime syntax, not references themselves.
Lifetime-region and escape validation is the mandatory semantic proof that every retained reference is legal under one lifetime owner. It is distinct from optional ownership optimisation.
Mandatory semantic proof covers:
User-authored topology that is invalid or cannot be proven legal produces a structured CompilerDiagnostic. Missing or inconsistent compiler-owned summaries, boundary classifications, lifecycle roots, or metadata produce CompilerError.
Optional optimisation proof covers final-use transfer, deterministic drop placement, retain/drop elimination, stack/arena/region allocation, collector elision and ownership specialisation. Failure to prove an optimisation preserves semantics through a conservative representation such as GC. It must not reject the program.
GC is a permitted runtime representation for a statically legal lifetime topology. It is not a mechanism for accepting invalid retained edges, lateral lifetime promotion, unowned escapes or cross-region cycles.
Implementation of this analysis is deferred. This leaf is the canonical design authority for lifetime topology. Canonical grouped-memory semantics live under Declared memory groups. The roadmap file owns implementation sequencing and deferred follow-up only.
Term | Definition |
|---|---|
Allocation | One runtime storage unit created for a value graph node. |
Allocation family | The containing allocation plus projections that remain rooted in it. |
Lifetime owner | The one semantic region responsible for an allocation's lifetime. |
Lifetime region | A semantic owner domain with an outlives relation to other regions. |
Lexical scope | Name visibility and control-flow exit structure. It does not define allocation lifetime. |
Retained reference | A stored reference that survives the immediate operation. |
Retained edge | A directed storage relationship from a retaining object to a referenced allocation. |
Outlives relation | |
Implicit region | A compiler-inferred lifetime owner for ordinary ungrouped allocations. |
Declared group | An accepted deferred hard lifetime boundary introduced by |
Builder lifecycle root | A page, mount, request, frame or arena owner supplied by build/runtime architecture. |
Physical representation | GC, RC, arena, stack, handle or hybrid storage chosen after topology is validated. |
Fresh result root | Newly allocated result root that may retain legal references to older allocations. |
Alias result | Result root that is an existing argument, projection, external allocation, or another result. |
Independent result graph | New graph with no mutable sharing or retained Moth reference to pre-existing storage. |
An allocation family is the containing allocation plus field, element and other projections that remain rooted in it. A projection does not silently become an independent allocation.
Every runtime allocation has exactly one semantic lifetime owner.
Multiple bindings, fields, elements or returned values may alias one allocation without becoming additional lifetime owners.
Page region owns Theme
header.theme ─┐
footer.theme ──┼── Theme allocation
theme binding ─┘
The ownership bit records destruction responsibility. It does not prove uniqueness, identify the final observer, establish lifetime legality or create dynamic shared ownership.
Every allocation has exactly one semantic lifetime owner. An object may retain a reference only when the referenced allocation belongs to the same lifetime region or to a region statically known to outlive the retaining object.
Formal rule:
R_value >= R_container
where R_value lives at least as long as R_container.
Valid edges:
Invalid edges:
Shared aliases may outlive the lexical binding that first named the storage.
Lexical scope does not define allocation lifetime. Escaping or retained aliases do not create unrestricted dynamic shared ownership. They remain under one lifetime owner and must satisfy the stored-edge outlives rule.
An ordinary allocation begins in the narrowest inferred region capable of owning its initial uses. The compiler may widen that allocation only to the nearest existing ancestor on the same ordered owner chain that outlives every retained observer.
The inference sequence:
choose the narrowest initial owner
-> collect retained observers
-> widen only when required
-> choose the nearest existing ancestor on the same owner chain
Implicit region inference may not silently promote an allocation laterally across independently ending sibling lifetime domains.
Widening may follow one ordered owner chain only. The compiler must not widen farther than necessary or invent a page-, application- or process-lifetime owner merely to avoid a diagnostic.
Independently ending sibling lifetime domains do not form one ordered chain.
Sharing across sibling domains requires one of:
copyDeclared groups are hard boundaries and cannot be silently widened.
Fresh escaping values should be allocated directly into the destination lifetime where possible.
Existing values may transfer destruction responsibility only at a proven final use. When later use exists, the outcome is path-dependent, or proof is imprecise, the operation remains a borrow.
Inferred transfer is optional. When safe transfer is not proven on every relevant path, the operation remains a borrow. Failure to prove an ownership optimisation must not make an otherwise valid source program invalid.
copy place creates independent storage that may be allocated directly into an inferred or declared destination region. See Access and aliasing for the complete graph-level copy contract.
Interior projections remain rooted in their containing allocation family. Returning, storing, or escaping a projection retains that family. At a proven final use, analysis may transfer the containing allocation family. A child is independently releasable only when it was already established as a separately owned allocation before the escape.
A projection does not silently become an independent allocation or independent copy. Returning, storing or escaping a field or element alias retains the base family under the stored-edge rule.
Field-sensitive allocation splitting may remain as a future compatible optimisation only when the compiler establishes separate child ownership before the alias escapes. It is not retroactive child extraction.
Function lifetime summaries cover:
Multiple return values may alias one allocation when they remain under one caller lifetime owner.
A caller may place a fresh result root directly into a destination region only when every retained edge in the result is legal for that destination. An alias result remains tied to its existing lifetime owner. An independent result graph may enter an unrelated destination lifetime without retained edges to its source graph.
Cross-region cycles are invalid.
Every strongly connected allocation graph must belong to one lifetime region.
Same-region cycles are lifetime-safe. Direct source construction of cyclic reference graphs remains deferred. V1 programs should model cyclic domain graphs through scalar IDs, indexes or keys inside one region when needed.
Reactive subscriptions are read-only dependencies, not active borrow lifetimes.
Builder-supplied page, mount, request, frame and arena roots are lifecycle inputs. They obey the same retained-edge rule as source regions. Builders supply lifecycle roots but cannot change source legality.
Reactive storage that outlives a lexical function must still satisfy one lifetime owner and the stored-edge outlives rule. A collector-free path may use builder-owned lifecycle regions; a GC path may use ordinary reachability for a topology already proven legal.
External bindings use closed semantic boundary profiles. They do not expose arbitrary user-defined lifetime graphs.
WIT value-only V1 profile. Moth imports general external Wasm libraries through a value-only WIT component profile. Supported arguments are lowered from shared reads into independent component values, and results are lifted into independent Moth result graphs. No Moth alias, lifetime owner, ownership state or destruction responsibility crosses the component boundary.
Restricted host-binding profile. Existing Core, Builder and JavaScript-backed bindings use a restricted host-binding profile. Ordinary Moth values cross by value, host code may not retain references into ordinary Moth storage, and opaque handles represent foreign identities rather than Moth reference types.
Richer resource-capable profiles are deferred and require a separate complete design. Detail lives in Runtime and backend lowering, docs/build-system-design.md and docs/language-overview.md.
Lifetime-topology legality belongs to a distinct backend-neutral analysis after borrow validation and before target planning.
Do not invent a numbered Stage 7 for this boundary. Stage 6 remains borrow validation. Lifetime-region and escape validation is an explicit unnumbered analysis after Stage 6.
Per-function and module analysis produces local allocation, alias, retention, escape, result and outlives constraints plus exported summaries.
It reads validated HIR and read-only borrow/effect facts. It writes immutable side-table facts and summaries. It does not rewrite HIR. It does not choose JS versus Wasm. It does not choose physical allocation representation. It does not make source syntax decisions.
Build/link analysis instantiates those summaries over the reachable call graph and builder-supplied lifecycle roots. Local module compilation cannot validate every cross-module or builder-lifecycle relationship by itself.
Backends receive a validated topology and may not reconsider source legality.
Lifetime diagnostics are part of the memory model, not an afterthought. They must identify the failed topology and steer the programmer toward direct destination allocation, a common lifetime group, independent storage through copy, or a shorter retained edge.
High-quality diagnostics are a central reason Moth can reject hidden dynamic shared ownership without exposing source-level RC.
Lifetime diagnostics must distinguish:
User-facing diagnostics use stable codes and structured reason payloads. Internal impossible or inconsistent metadata uses CompilerError.
Every lifetime diagnostic should identify, where applicable:
Remedies must be ranked in this order:
copyThere is no backend-specific escape from semantic lifetime diagnostics.
Stronger future analysis may prove more legal programs or narrower owners, but cannot change the meaning of already-accepted programs.
Conservative analysis may reject topology it cannot prove legal. That is a source diagnostic, not permission to fall back to GC for an unproven graph.
~ validity.TypeId or source signatures.TypeId.group / into surface and hard destination lifetimes