Runtime and backend memory lowering
Design contract
Backends may choose different allocation, GC, handle and ownership representations. They may not choose different language semantics.
Every backend must preserve shared read-only access, exclusive mutation, explicit copy behaviour, optional transfer safety, lifetime-topology legality, reactive liveness and GC-equivalent observable results.
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.
Main backend paths
src/backends/js/src/backends/wasm/src/backends/wasm/hir_to_lir/ownership.rssrc/backends/wasm/lir/src/backends/wasm/runtime/
Backend-neutral semantics
- Borrow validation and lifetime-region validation precede target lowering.
- Invalid source access or topology is rejected before backend codegen.
- Backend doesn't infer access or topology from source syntax.
- Backend consumes HIR plus borrow facts plus validated lifetime facts.
- Ownership optimisation is optional.
- Backend representation can't affect
copy, alias or ~ semantics. - Missing optimisation proof may retain GC for already-legal topology.
- Missing topology proof is not a backend fallback.
GC baseline
- Deterministic destruction is not required for correctness.
- Valid topology may be represented by host or runtime GC.
- Mandatory access and lifetime validation still run.
- Optional ownership operations may be erased.
drop_if_owned may be a no-op.- Borrowed/owned distinctions may be omitted at runtime when irrelevant.
GC can be host GC, Wasm GC, a runtime collector or another backend strategy. Exact current target support belongs in the progress matrix.
Internal RC, arenas, handles, regions, stack storage, pools and hybrid representations are permitted for valid topology. Source-visible RC and hidden unrestricted dynamic sharing are not permitted.
Debug and development configurations
Debug and development configurations may deliberately retain the GC baseline even where ownership-aware lowering is available.
Unless a configuration is specifically intended to exercise ownership behaviour, debuggability, instrumentation and predictable runtime behaviour take priority over deterministic-destruction optimisation.
Release configurations may still fall back to GC per value whenever ownership proof is unavailable. Profiles differ only in optimisation effort and physical representation, never in source legality.
Build-owned target and lifecycle planning
The build system owns reachability, target partition, page/request/mount/frame roots and page runtime topology. Backends consume explicit plans rather than selecting them.
See docs/build-system-design.md for project and link orchestration.
Moth-linked page memory and imported components
Each page may own one runtime and memory shared by its linked Moth Wasm variants. Imported WIT components are separate runtimes with component-private memory and communicate only through the closed value boundary. This is build architecture, not source memory semantics. The one-page/one-memory rule applies to Moth-linked Wasm variants, not arbitrary imported components. Imported components are not members of Moth page memory or the Moth lifetime graph and do not need to declare a GC strategy to Moth.
WIT value-only V1 profile
Moth imports general external Wasm libraries as WIT-described Wasm Components. The V1 component boundary is value-only. Every supported argument is lowered from a shared read of a Moth value into an independent component value, and every result is lifted into an independent Moth result graph. No Moth alias, lifetime owner, ownership state, or destruction responsibility crosses the component boundary.
The component may use any private allocation or garbage-collection strategy. That strategy is not part of the Moth binding contract and does not require a GC declaration.
Fixed facts
| Fact | WIT value-only V1 | |---|---| | Parameter source access | Shared read | | Boundary crossing | Independent semantic value | | Retained Moth reference | Never | | Result provenance | Independent result graph | | Result aliases an argument | Never | | Source call consumes argument | Never | | Destruction responsibility crosses | Never | | External lifetime owner | Component-private | | Cross-runtime allocation cycle | Impossible by contract |
Supported V1 WIT value families
- primitives
- strings
- records whose members are supported values
- variants whose payloads are supported values
- options of supported values
- recursively supported lists
Rejected V1 WIT surfaces
- WIT resources
- borrowed or owned resource handles
- resource constructors or methods
- callbacks into Moth
- retained closures or executable state
- async functions
- futures
- streams
- shared-memory views
- raw pointers or pointer/length escape hatches
- returned aliases
- retained Moth references
- project-authored Moth functions imported by the component
Unsupported interfaces must receive structured import diagnostics identifying:
- unsupported WIT feature
- function
- parameter or result
- why it cannot cross the value-only boundary
- whether a wrapper component could expose a supported value-only interface
Required importer metadata
The importer requires:
- stable package identity
- stable world identity
- stable interface identity
- stable function identity
- Moth-facing parameter and result types
- corresponding WIT value types
- component export identity
- trap or failure behavior
- required component imports
- target and runtime capability requirements
The boundary classification generates the fixed memory facts. The importer must not infer them from a generic Fresh result marker, and binding authors must not restate them per function. No uses_gc field is required.
Restricted host-binding profile
Existing Core, Builder, JavaScript-provider and curated platform bindings use a restricted host-binding profile.
- Ordinary Moth values cross by value as non-retained inputs.
- Host code cannot retain references into ordinary Moth storage.
- Mutable host access is supported only for opaque foreign handles.
- A mutable opaque-handle argument controls legal access to the foreign identity; it is not direct mutation of Moth storage.
- Ordinary Moth values are not passed by mutable reference to host code.
- Host operations that conceptually modify ordinary Moth data return a fresh or independent result instead of mutating Moth storage through copy-in/copy-out.
- V1 does not define copy-in/copy-out writeback semantics.
- Opaque handles represent foreign identities, not Moth reference types.
- Observable external resources require explicit close or teardown operations.
- Host finalization timing cannot define observable Moth behavior.
Restricted host-binding V1 permits mutable access only to opaque foreign handles. Ordinary Moth values cross by value as non-retained inputs. A host operation that conceptually changes ordinary Moth data returns a fresh or independent result instead of mutating Moth storage through the boundary.
Deferred resource-capable profiles
A future resource-capable profile requires a separate complete design for identity, transfer, teardown, callbacks, async suspension, reentrancy, cycles and failures. It is not part of V1.
JavaScript lowering
- Host references realise validated aliases.
- JS does not weaken topology.
- Explicit copy remains graph-independent semantically even if current implementation lags.
- Ownership-specific advisory facts may be ignored.
- JS host behaviour must not silently mutate shared aliases or duplicate values contrary to Moth semantics.
- Reactive mounts follow builder lifecycle roots and stay alive through runtime reachability.
Wasm lowering
- Borrow and lifetime facts precede LIR.
- Ownership bit is responsibility only.
- Group/region representation is separate from the ownership bit.
- Missing optimisation proof may retain GC.
- Missing topology proof is not a backend fallback.
- Ownership-aware heap values use handles or pointers carrying ownership state.
- Conditional drops release owned handles where individual release is legal.
- Runtime helpers own allocation, release, string, collection and handle contracts.
- The unified ownership ABI applies across ownership-aware calls.
- Source semantics remain backend independent.
src/backends/wasm/hir_to_lir/ownership.rs is the ownership projection seam, and Wasm LIR includes conditional DropIfOwned plus reserved retain support.
Runtime ownership metadata
- ownership bit or tag on heap handles
- borrowed versus owned state
- mask and extract value representation
- callee responsibility
- propagation of ownership metadata through calls
- no source-visible owned/borrowed overload
- scalar values need not use tagged handles
- representation details can evolve while preserving the accepted contract
Drop and retain behaviour
- Release only owned values that are individually releasable or whose region is ending.
- Never release borrowed values.
- Retain when a runtime representation requires extending shared storage lifetime.
- Remove retain and release checks when static proof permits.
- Avoid double release.
- Ensure all potential ownership paths reach release or safe transfer.
- GC lowering can erase ownership-specific operations.
- Internal RC may be used only for already-legal topology.
Fact consumption
Backends may consume:
- value access classifications
- call effects
- return-alias summaries
- validated transfer decisions
- advisory drop sites
- validated lifetime topology and allocation-family facts
- builder lifecycle roots
- region/exit information
- reactive invalidation and liveness metadata
- external boundary classifications
Backends must not:
- mutate those facts
- reinterpret source syntax
- invent new move points that invalidate borrow or lifetime validation
- treat missing facts as proof of ownership or topology legality
- reconsider source legality
Reactive and mounted-fragment liveness
Live reactive subscriptions and mounted fragments can keep state observable. Ownership-aware lowering must not free a source or template instance while it remains observable. GC reachability is the semantic reference behaviour for already-legal topology. Finer invalidation granularity may improve performance but not lifetime semantics. Runtime fragment and reactive mount behaviour remain separate concerns.
Backend specialisation
Allowed optimisations include:
- ownership-effect specialisation
- drop-check elimination
- retain elimination
- region allocation
- stack or arena allocation
- GC elision
- internal RC for legal topology
- devirtualized runtime helpers
- target-specific handle representation
Requirements:
- same source behaviour
- same validation result
- no unchecked aliasing
- no source-visible lifetime or ownership surface
- graceful GC representation when optimisation is unavailable
Fallback requirements
When a backend can't prove or implement an ownership optimisation safely, it must retain GC-managed behaviour for that value or feature on already-legal topology.
A missing optimisation is valid. A semantic divergence isn't. Unknown external retention is not a GC fallback.
What backends must not do
- Skip borrow validation or lifetime-region validation.
- Accept aliasing or topology rejected by other backends.
- Reinterpret
~ as an unconditional move. - Silently copy shared values.
- Destroy borrowed data.
- Expose source lifetimes.
- Rediscover source imports or access syntax.
- Require deterministic destruction for correctness.
- Make reactive observability depend on target-specific early release.
- Assume page memory includes arbitrary imported components.
- Invent a numbered Stage 7 for backend lowering.
Common mistakes
- Treating the JS path as semantically weaker because it uses GC.
- Treating GC as permission for invalid topology.
- Presenting current Wasm scaffolding as the whole accepted memory architecture.
- Treating every runtime value as a tagged heap pointer.
- Lowering advisory drops without checking ownership.
- Freeing reactive state too early.
- Treating missing ownership proof as an error.
- Treating missing topology proof as backend GC fallback.
- Creating separate borrowed/owned language APIs.
- Assuming imported components share Moth page memory.
Related reading