7.6 KiB
Lifecycle Markers, Program Init, and Frame Root Semantics Decision
Status: Accepted
Date: 2026-03-22
Related Agenda: docs/compiler/pbs/agendas/19.2. PBS Lifecycle Markers, Program Init, and Frame Root Semantics Agenda.md
Context
After closing the globals surface and module-boundary model, PBS v1 needed a precise lifecycle policy for:
- module bootstrap,
- project bootstrap,
- frame-root identification,
- host-call admissibility during init,
- and failure behavior at boot.
The key requirement was to keep initialization expressive enough for real setup work, while preserving deterministic semantics and preventing the declaration initializer of global from becoming a general procedural execution surface.
Important fixed inputs already existed:
declare globallowers into module-owned runtime storage,- global initializers remain declarative and restricted,
- top-level
fncalls are not allowed insidedeclare globalinitializers, FRAME_SYNCremains the only normative safepoint,- and PBS v1 remains deterministic and single-threaded.
Decision
PBS adopts the following lifecycle-marker and bootstrap policy in v1:
- PBS introduces the reserved userland attributes:
[Init][Frame]
[Frame]is mandatory for an executable project.[Frame]is unique per executable project.[Init]is optional.[Init]may appear in ordinary userland source both for module-local bootstrap and for project bootstrap.- Both
[Init]and[Frame]require the same signature shape:fn name() -> void
- Recommended canonical source forms are:
[Init]
fn init() -> void { ... }
[Frame]
fn frame() -> void { ... }
Module Init and Project Init Policy
PBS adopts the following bootstrap layering:
- Every module has exactly one synthetic module init.
declare globalalways lowers into that synthetic module init.- Each file may declare at most one
[Init]. - A file may declare
[Init]even when it declares no globals. - For one file:
- that file's globals are materialized first,
- then that file's
[Init], if present, executes.
- File-level init fragments are then composed into the one synthetic module init.
- The
[Init]colocated in the same file as[Frame]is the project init. [Init]in other files remains module init.- The distinction between module init and project init is one of ordering and semantic role, not one of different source syntax.
- There is therefore at most one project init per executable project.
- Project init uses the same signature shape:
fn init() -> void
Bootstrap Order
PBS adopts the following logical bootstrap order:
- materialize globals for each file according to dependency order,
- execute that file's
[Init], if present, - compose those steps into one synthetic module init,
- after required module work is complete, execute project init, if present,
- then enter the first
frame().
Within one module:
- file ordering must follow the order derived from the globals
DependencyGraphAnaliser, - and when two files have no dependency edge between them, the compiler must still use a deterministic stable order.
Host Interaction During Init
PBS adopts the following init-time host-call policy:
- Loops are permitted in module init and project init.
- Host calls are forbidden by default during init.
- The only host calls admissible during init are host methods marked with
[InitAllowed]. [InitAllowed]is a reserved SDK attribute, not a userland attribute.[InitAllowed]has no arguments in v1.[InitAllowed]is valid only on SDK host methods.[InitAllowed]is invalid on userlandfn,global,service, or any non-host-method surface.[InitAllowed]does not distinguish module init from project init.- The difference between those phases is ordering and semantic role, not a separate host-permission class.
- Admissibility of
[InitAllowed]must be checked at compile time.
Illustrative shape:
declare host Runtime {
[Host(module = "runtime", name = "random_u32", version = 1)]
[Capability(name = "runtime")]
[InitAllowed]
fn random_u32() -> int;
}
Failure Policy
PBS adopts a fail-fast boot policy:
- Failure during module init aborts boot.
- Failure during project init aborts boot.
- No automatic retry occurs on the next frame.
- Implementations must surface a coherent failure message identifying the failing boot phase.
Rationale
This decision keeps the declaration initializer of global simple, while still allowing practical bootstrap work in later lifecycle stages.
That balance matters because:
- arrays and derived data often require loops during setup,
- module bootstrap must remain expressive enough for real projects,
- host interaction during boot must still be tightly controlled,
- and the runtime model must remain deterministic and explainable.
Colocation of project init with frame was chosen deliberately as a source-organization rule. It avoids introducing a second project-level marker while keeping project bootstrap discoverable in source.
Mandatory Diagnostics
PBS must provide, at minimum, the following diagnostics for this policy:
project init must be colocated with framemultiple project init functions detectedmultiple frame functions detectedmissing frame function for executable projectinit function must have signature fn name() -> voidframe function must have signature fn name() -> voidhost call not allowed during initInitAllowed is valid only on SDK host methodsboot failed during module initboot failed during project initmultiple module init functions detectedInit attribute target invalid
Invariants
[Frame]remains unique per executable project.- Project init is identified by colocation with
[Frame]. - Each file contributes at most one init fragment.
- Each module contributes exactly one synthetic module init.
- Host-call admissibility during init is explicit and compile-time validated.
- Boot ordering remains deterministic.
Explicit Non-Decisions
- This decision does not define the published synthetic wrapper entrypoint.
- This decision does not define
FRAME_RETownership. - This decision does not define the final IR encoding of bootstrap.
- This decision does not define cartridge manifest publication details.
Spec Impact
This decision should feed at least:
docs/compiler/pbs/specs/3. Core Syntax Specification.mddocs/compiler/pbs/specs/4. Static Semantics Specification.mddocs/compiler/pbs/specs/9. Dynamic Semantics Specification.mddocs/compiler/pbs/specs/12. Diagnostics Specification.md
It also constrains future topic 19 work in:
docs/compiler/pbs/agendas/19.3. Published Entrypoint, Synthetic Wrapper, and FRAME_RET Ownership Agenda.mddocs/compiler/pbs/agendas/19.4. Globals and Lifecycle Lowering to IRBackend/IRVM Agenda.mddocs/compiler/pbs/agendas/19.5. Diagnostics, Manifest Propagation, and Conformance Coverage Agenda.md
Validation Notes
At minimum, validation for this decision should include:
- accepted fixtures for
[Init]and[Frame]with correct signatures; - accepted fixtures for file-level
[Init]without globals; - accepted fixtures for loops inside module init and project init;
- rejection fixtures for invalid marker targets and invalid signatures;
- rejection fixtures for illegal host calls during init;
- accepted fixtures for SDK host calls marked
[InitAllowed]; - failure fixtures that distinguish module-init boot failure from project-init boot failure;
- ordering fixtures proving deterministic file ordering within one module.