Menu

Memory model — how Manticore manages memory, and how to control it

Manticore compiles PHP to native code with no garbage-collected runtime. Every value (string, array, object) is reclaimed by one of three strategies, chosen at compile time and refined per-allocation by escape analysis. This page explains what happens by default and the knobs you have.


The default: hybrid (escape-routed)#

With no flag, Manticore runs in hybrid mode. An escape analysis (InferAllocKind) classifies every allocation:

This gives arena speed for the common confined case and rc safety for the rest, with no annotations.

Reference counting#

Escaping strings, arrays, and objects carry a refcount; assignment/retain bumps it, scope exit/overwrite drops it, and reaching zero frees immediately (deterministic — no GC pauses). Arrays and object properties use copy-on-write (Zend-style): $b = $a shares the buffer until one side mutates.

Reference counting cannot reclaim cycles ($a->x = $b; $b->x = $a). Those leak until you ask for them to be collected (see Cycle collection below).

Arenas#

A confined allocation goes to a process arena — a growing bump-pointer region. Allocation is a pointer add; nothing is individually freed. At the owning scope's exit the arena is rewound in bulk. This is why confined temporaries are cheap.

Arrays reach the arena too, not just scalars and strings. An array is arena-eligible when InferAllocKind::isArenaEligibleType() says so, which today means a flat int / float / bool array with integer keys — deliberately a first cut, not every non-escaping array. An arena array carries its own tag, so the refcount helpers recognise it and bail: it is never rc-bumped and never free()d. Disable with MANTICORE_ARENA_ARRAYS=0.


Choosing a strategy#

The mode is a whole-program, compile-time choice. There is no per-function or per-scope override from code today (that's a roadmap item — see Manual control).

ModeConfined allocationsEscaping allocationsUse when
hybrid (default)arena (bulk-free)reference countedalmost always — best of both
rcfreed per-local at scope exit (no rc overhead)reference countedyou want fully deterministic, arena-free reclaim
arenaarenaarena (+ a runtime escape-bypass guard)short-lived batch/CLI runs where you never need mid-run reclaim — fastest allocation, frees everything at the end

Select it:

manticore compile app.php -o app --memory=arena   # compile flag
MANTICORE_MEMORY=rc manticore compile app.php      # env var

arena mode trades memory for speed: nothing is reclaimed until scope/program exit, so peak memory is higher, but there is zero refcount traffic. rc mode is the most predictable for long-running processes. hybrid is the right default.


Runtime control#

The one memory operation you call from PHP code is cycle collection:

$freed = gc_collect_cycles();   // run the collector, returns objects reclaimed

Manticore ships a synchronous Bacon–Rajan cycle collector. It is linked in only by a program that references gc_collect_cycles() (zero overhead otherwise, and cycles then leak). Once linked it also runs automatically at allocations when the root buffer reaches an adaptive threshold, as php's gc_adjust_threshold does; call it explicitly at a natural boundary to reclaim sooner.


Manual control#

"Manual" memory management today means picking the model for your workload rather than fine-grained per-allocation control:

Finer-grained manual control — a #[Arena] per-function hint, explicit arena scopes, or free()-style release from code — is designed but not yet wired (Compile\Debug::$arenaScopedFns is the reserved hook). For now, structure + the global mode + gc_collect_cycles() are the levers.


Compile-time knobs#

Six environment variables are read once at startup (src/Compile/Debug.php). Only the first is something you would normally touch:

Env varDefaultEffect
MANTICORE_MEMORYhybridallocation strategy; same as --memory=<rc|arena|hybrid>
MANTICORE_ARENA_ARRAYSonarena-allocate eligible non-escaping arrays
MANTICORE_EMPTY_SINGLETONonshare one immortal buffer for every empty []
MANTICORE_DEBUG_VERIFYoffplant slow-path invariant checks at every memory op
MANTICORE_PROFILEoffthread-local refcount / allocation counters
MANTICORE_REFLECT_REPORToffreport which classes reflection kept alive

Under the hood (reference)#

Edit this page on GitHub