Experimental Region Escape Analysis

Status: analysis-only North Star slice
Runtime effect: none

naux dev region <file.nx> emits a conservative heap escape plan for direct text, bytes, list, map, and closure bindings. The analysis model also resolves lexical captures for the internal anonymous-function AST, follows heap aliases, and propagates escape lifetimes through captured and container-retained values. The runtime still uses Rc; this report is evidence for a future region-lowering pass, not a zero-GC claim.

With the experimental-regions Cargo feature, the report is lowered into deterministic compiler metadata:

cargo run -p naux --features experimental-regions -- \
  dev region naux-lang/examples/hello.nx

The [LOWERING PLAN] assigns only proven local allocations to region-local storage and emits an inner-before-parent bulk-free schedule. Global, escaping, and closure allocations carry an explicit Rc fallback reason. Inference IDs are normalized to report-local ordinals, so identical AST input produces identical metadata across invocations. The compiler-side compile_script_with_region_plan API returns unchanged ordinary bytecode plus the sidecar, and refuses it unless the canonical lowering certificate revalidates against the escape report.

The feature also exposes observe-only run_vm_with_region_plan and run_jit_with_region_plan entry points. They execute the unchanged current backends and attach certificate-backed telemetry for region-local candidates, Rc fallbacks by reason, free points, and would-be bulk-freed allocations. VM/JIT value parity is regression-tested for both local and escaping cases. These counters describe the admitted plan; they do not claim that bulk free is executed yet.

Current proof boundary

An allocation is counted as bulk-free eligible only when:

  • it is in a non-global function, rite, or each region;
  • it is not returned from its function;
  • it is not promoted through a non-lexical if, loop, while, or unsafe boundary;
  • it is not reachable from an escaping closure or an escaping direct list/map binding.

A local closure no longer makes every allocation in its region ineligible. Only its lexically resolved captures are retained. If that closure escapes, the captured heap bindings are promoted to the closure’s target lifetime. Propagation runs to a fixpoint, so escaping closure -> captured closure -> captured payload and escaping container -> retained payload receive one consistent target. Parameter/local shadowing and overwritten aliases do not retain stale heap bindings.

Assignments inside if, loop, while, and unsafe are conservatively promoted to the parent region. That matches current Naux scope behavior: these constructs do not create lexical variable scopes. rite, function calls, and each iterations do.

Region lifetime comparison follows the parent chain. Creation order is not used, so one sibling region cannot incorrectly be treated as outliving another. Promoted bindings survive the source-region pop and are removed when their resolved target region exits.

Evidence

  • region::types tests cover parent-chain outlives checks, shadow restoration, and promoted-binding lifetime.
  • region::analyze tests cover local heap eligibility, resolved return targets, nested returns, lexical capture/shadow resolution, heap aliases, container retention, and transitive closure escape fixpoints.
  • region_escape_tests parses real Naux programs and checks escape decisions alongside VM/JIT value parity, including transitive loop -> function -> caller promotion.

Deliberately not implemented

  • replacing Rc allocation/reclamation;
  • user-facing anonymous-function parsing/execution and closure region polymorphism;
  • nested anonymous allocation-site planning;
  • executing the metadata as region create/free instructions or replacing the allocator;
  • claiming runtime speedups.

The next admissible slice is feature-gated execution of region create/free for the proven direct-allocation subset. It must keep Rc fallback for unsupported shapes, retain interpreter/VM/JIT parity, and compare executed allocation/free telemetry with the shadow plan before any runtime claim.