Escape analysis

Nyra proves which values stay local at compile time, then promotes them to the stack, inlines sequential channels without mutexes, and honors #[no_escape] on references.

What is escape analysis?

After typecheck and borrow checking, Nyra builds an AST-level escape graph for every function. Each binding is classified as NoEscape (stays in the function), ArgEscape (passed as &T but not returned or spawned), or GlobalEscape (returned, sent on a channel, or captured by spawn).

The plan flows into LLVM codegen — you do not annotate ordinary locals. Optimizations apply on every build; use --verbose to see the report.

parse → typecheck → borrow → escape graph → codegen (EscapePlan)

Escape states

StateMeaningCodegen effect
NoEscapeCreated and consumed in the same functionStack promotion, SROA, skip redundant clone/free
ArgEscapePassed by reference to a calleeStays on caller stack
GlobalEscapereturn, spawn, or channel payloadHeap / runtime channel as usual

Stack promotion & SROA

For NoEscape values, codegen avoids unnecessary heap work:

Struct literals with spread (..p) are excluded from SROA — the compiler keeps a single struct binding.

LocalChannel

When a Channel<T> is NoEscape and never captured by spawn, Nyra emits a stack LocalChannel: a fixed-capacity ring buffer (16 slots) with inline send/recv — no locking and no runtime channel calls.

If the channel handle or its messages escape to another thread, the plan falls back to the normal runtime channel.

See also: Concurrency · Memory & ownership.

#[no_escape] on parameters

Advanced callers can promise that a reference parameter never escapes the callee:

fn process(#[no_escape] data) {
    print(data)
}

fn bad(#[no_escape] data){
    return data   // compile error E0602
}

When valid, codegen treats the parameter as NoEscape for stack optimizations in the caller’s frame.

Verbose report

nyra build --verbose file.ny
nyra build -v .

Example output:

   Checking  escape analysis
  escape: main::user → NoEscape
  escape: main::chan → NoEscape
  local channel: main::chan → LocalChannel (stack ring buffer)
  no_escape param: process::data (must not return/spawn/send)

Example

fn main() {
    let p = { x: 10, y: 20 }
    print(p.x + p.y)
}

With --verbose: p is NoEscape and the struct literal is eligible for stack promotion (SROA). For LocalChannel, see the section above and Concurrency → Channels.

Limitations

Related