Complete Absence of a Tracing Garbage Collector
Unlike legacy runtimes (PHP Zend Engine, Go, Node.js V8, JVM) that incur stop-the-world pauses and cyclic GC sweeps, RHP is 100% free of a tracing garbage collector:
print("Is GC Free: " . (Memory::isGcFree() ? 'true' : 'false')); // true
print("GC Total Runs: " . Memory::gcRuns()); // 0
print("GC Pause Duration: " . Memory::gcPauseMs() . " ms"); // 0.0 ms
Request-Scoped Bump Arena Allocation (RequestArena)
In a web application, short-lived allocations (request payloads, JSON string slices, temporary lists) are allocated from a contiguous pre-allocated memory chunk using an 8-byte aligned bump pointer:
- Sequential Allocation: Fast pointer increments without thread-locking global heap mutexes.
- Instant $O(1)$ Reset: When an HTTP request completes, the entire arena bump pointer is reset to 0 in $O(1)$ time (
finish_request()). - Zero Heap Fragmentation: Pre-allocated chunks are retained across requests, avoiding OS
mmap/mallocthrashing.
Static Cycle Prevention at the Language Boundary
In traditional dynamic languages, cyclic references (e.g. object A referencing object B, which references object A) cannot be freed by reference counting alone, necessitating a cyclic garbage collector.
RHP prevents cycles statically at compile time:
- Pass-By-Reference Forbidden:
&$varsyntax is eliminated from the language grammar. - Closure Reference Capture Forbidden: Closures cannot capture variables by mutable reference (
use (&$var)is rejected). - Weak References (
Weak::to): Bidirectional graph relationships (e.g., parent-child nodes) must use explicit weak references:
class Node {
public ?WeakReference $parent = null;
public list $children;
public function setParent(Node $parent): void {
$this->parent = Weak::to($parent);
}
}
Scoped Arenas (Memory::scope)
For compute-intensive operations, isolate allocations inside a lexical memory scope:
$result = Memory::scope(function(): int {
$buffer = Str::repeat("DATA", 100000);
return Str::byteLength($buffer);
// $buffer is reclaimed immediately upon scope exit
});
Subtyped Collection Memory Layout & Streaming Iteration
Rather than using Zend Engine’s monolithic zend_array (which incurs massive memory overhead with 56-byte hash buckets and separate doubly linked list pointers for iteration order), RHP uses dedicated, cache-conscious Rust structures:
1. Arr::dict: Rc<RefCell<IndexMap<String, PhpValue>>>
- Dense Linear Storage: Keys and values are stored in contiguous vectors indexed by a sparse hash table (
IndexMap), achieving compact memory layout with near-zero pointer chasing. - Strict Deterministic Ordering: Iteration order strictly matches insertion order. Updating an existing key modifies its value in-place without altering its slot position in the iteration order.
- Zero-Allocation Streaming Iteration:
foreach ($dict as $key => $val)initializes anActiveIterator::Dict { dict, cursor: usize }. Each iteration step readsdict.get_index(cursor)directly by index offset, allocating zero temporary vectors or key-value tuples. - Copy-On-Write (CoW) Detachment: Reassignment (
$copy = $dict) shares the underlying reference-counted buffer with zero allocations (Rc::clone). In-place mutations on shared buffers trigger transparent detachment (ensure_dict_mut()), cloning only when necessary.
2. Arr::list: Rc<RefCell<Vec<PhpValue>>>
- Contiguous Vector Buffer: Stored directly as a dense vector of
PhpValuetagged unions, offering direct CPU L1/L2 cache prefetching and sequential indexing without hashtable hashing overhead.
Memory & State Rules
1. Boot Heap Immutability (server.rhp $\rightarrow$ Arc)
The boot script (server.rhp) executes once at startup. All parsed routes, compiled classes, and container singletons are frozen into an immutable boot heap (Arc<EngineState>), shared read-only across all worker threads.
- Read-Only Invariant: Request code cannot mutate boot-time structures. Any attempt to write to boot data throws a fatal runtime error.
- Shared Mutable State: Any state that must be shared across requests or threads must pass through explicit host-provided native APIs.
2. Execution Invariant: Everything Finishes Before Arena Drop
- Deferred Work: Callbacks registered via
Future::defer()execute strictly before the request arena is dropped and reset. - Job Queues: Asynchronous or queued tasks are serialized into the host queue before teardown.
- Bulk Reclamation: Once all deferred logic completes, the entire request arena drops in a single $O(1)$ step, leaving no leaks and requiring no GC traversal.
3. Host-Owned Resource Handles
- Operating system resources (database connections, raw sockets, log file descriptors) live in persistent, host-owned pools.
- Request code holds lightweight handles rather than raw OS objects.
- Borrowed connections are automatically sanitized and reset upon return to the pool, and are reclaimed on timeout.
4. Cache Data Invariant (Cache::get/set)
- The cache accepts plain data only: primitive scalars (integers, floats, strings, booleans) and arrays/dictionaries.
- Live object instances, closures, and resource handles cannot be cached, guaranteeing zero cross-request pointer corruption.
- Supported cache drivers include file, database, and Redis, with an optional in-memory L1 cache in front.
5. VM Deadlines and Memory Caps
- The VM checks execution deadlines at loop headers and function invocations.
- A per-arena memory cap kills runaway requests before they exceed system capacity.