Memory management / Lifetime regions and escape validation

Every allocation has exactly one semantic lifetime owner. GC cannot bypass topology validation.

Lifetime regions and escape validation

Design contract

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.

Terminology

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

R_value >= R_container means the referenced region lives at least as long as the retaining region.

Implicit region

A compiler-inferred lifetime owner for ordinary ungrouped allocations.

Declared group

An accepted deferred hard lifetime boundary introduced by group name:.

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.

One semantic lifetime owner

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.

Stored-edge outlives rule

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 escape lexical bindings

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.

Implicit lifetime inference

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:

  1. an already-existing common semantic owner
  2. an enclosing declared memory group
  3. a builder-declared common lifecycle
  4. independent storage created by copy

Declared groups are hard boundaries and cannot be silently widened.

Fresh destination allocation and final-use transfer

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 and allocation families

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.

Returns and multi-return aliasing

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.

Cycles and strongly connected graphs

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 and builder-owned lifetimes

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 lifetime boundaries

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.

Analysis boundary

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.

Local per-function and module work

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.

Project and link work

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.

Diagnostics are part of the model

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:

  1. allocate directly into the required destination region
  2. place observers under one common group
  3. create independent storage with copy
  4. shorten the alias or retained edge
  5. repair package-owned external lifetime metadata

There is no backend-specific escape from semantic lifetime diagnostics.

Conservative precision

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.

What this analysis must not do

Common mistakes

Related reading