Repository navigation
Description
Goal
Laravel example (examples/laravel, PR #564 merged as cac708f9) serves requests natively. What's left: memory footprint, per-request leaks, and throughput on real framework code. Target is Zend parity or better on all three. Zend is the oracle for both behaviour and the baseline numbers.
Baseline (2026-11-08, macOS arm64, -O2, pool on)
| Metric | Manticore | Zend 8.5 |
|---|---|---|
| RSS under load | 200–310 MB (boot 154 MB) | ~47 MB |
Leak, GET /ping |
~1.2 KB/req | — |
Leak, GET /hello |
~3 KB/req | 0.7 KB/req (SESSION_DRIVER=array) |
Leak, GET /api/notes |
~1.5 KB/req (RSS flat) | — |
Instr/req ping / hello / notes |
1.87M / 3.22M / 127M | Laravel-side ratio ≈ 1.34–1.5× of Zend |
Measure with tools/prof/serve.sh + report.php (see docs/profiling.md).
Scope
1. Deferred free of buffered rc=0 objects (RSS)
An object whose refcount drops to 0 while it is in the cycle-collector root buffer is marked dead (rc word 0x84…) but is freed only on the next collection. The threshold is 10 000 roots, so whole dead graphs stay resident. Zend frees right away and unlinks the node from the buffer.
- Free on rc→0 even when buffered: unlink from the root buffer, or tombstone the slot.
- Done when: RSS under
wrkis within 2× of Zend, and the self-host fixpoint is unchanged.
2. Per-request leak on /ping (~1.2 KB/req)
Known roots from hello: closures of $x ?? fn, setRouteResolver(fn), Str::random, hash_hmac/CRYPTO, arrays via __mir_realloc_tagged. notes is not broken down yet.
- Each root gets a minimal repro in
tests/aot/repro/leak/plus an issue (tools/file_bug.sh), then a fix. - Check at
-O0without pool too:-O2deletes unread mallocs and hides leaks. - Done when:
ping≤ Zend's per-request growth.
3. #590 and #592
- A closure literal in an arm of a conditional callee is never released: ($f ?? function () {...})() keeps the env and its captures alive. #590: a closure literal in an arm of a conditional callee is never released.
- Overwriting a dynamic property through a runtime name never releases the old value; releasing it needs a dynamic read to take its own count first. #592: overwriting a dynamic property through a runtime name never releases the old value. A dynamic read must co-own first; a naive drop is a UAF.
4. Promote 8 XPASS repros
bash tests/aot/xfail.sh reports 8 XPASS (fixed as a side effect of the Laravel work). For each one: git mv the repro to tests/aot/cases/ and its .expected to tests/aot/expected/<name>.out, drop the // issue: line, close its issue (Fixes #N).
5. Throughput on real PHP code (profile first, then fix)
Profile ping/hello/notes by instruction count (Linux perf as well as macOS) and attack the top hotspots. Known so far: refcount + malloc ≈ 20%, __mir_array_index_find ≈ 5% (now alwaysinline). Candidates to confirm or drop by measurement:
- Retain/release pair elision: borrowed params, temporaries passed straight through,
foreachvalues. - Dynamic dispatch:
$obj->$method(...$args),__call/__callStatic,call_user_func_array,static::LSB. Laravel facades and Eloquent forwarding go through these on every call. Try a per-call-site inline cache keyed by class id plus method name. - Constant-key array access: precompute the hash of a literal string key at compile time and skip rehashing on
$a['key']. Config/container arrays are the hot case. - Dynamic property access: name lookup on erased objects /
stdClass(Eloquent attributes,PDO::FETCH_OBJ). - Closure allocation: env boxing for short-lived
fnpassed totap/collect()->mapetc. - String building: concat arena /
sprintf/implodeon view rendering paths.
Each candidate ships with a microbench and a before/after instr/req. No change lands without a measured win.
- Done when: Laravel instr/req ≤ Zend on
pingandnotes, and symfony-demo shows no regression.
Rules
- Branch per item off local
main. PR per item, never push tomain. - Linux gate required for anything that touches
src/Runtime/, the allocator or the cycle collector. - Full AOT suite + difftest before merge (run by the maintainer).
Activity
blacktrs commentedon Nov 8, 2026
Measured on 2026-11-08 (macOS arm64,
-O2,WORKERS=00,/api/ping). The RSS numbers in item 1 were misleading:footprint(dirty anon)php -S__manticore_cc_drop_deaddrops a buffered rc=0 object immediately, and only a ~100 B shell waits in the root buffer.Revised plan: item 2 (per-request leak) is the RSS lever. Item 1 shrinks to "unlink the dead shell from the root buffer", low priority.