Repository navigation
v0.15.0
docker run --rm -v "$PWD":/work -u "$(id -u):$(id -g)" \
ghcr.io/manticorephp/compiler:0.15.0 manticore compile app.php -o app
curl -fsSL https://github.com/manticorephp/compiler/releases/download/v0.15.0/install.sh | bash
The tarballs carry the compiler and its stdlib, not a toolchain: manticore
assembles its IR with clang and links with cc, against the host's
pcre2, openssl, sqlite3 and curl. install.sh checks for those and says
which are missing. The image carries the toolchain it was built with.
On Alpine (musl) use ghcr.io/manticorephp/compiler:0.15.0-alpine or the linux-musl tarball —
install.sh picks it by itself.
Built cold from v0.15.0 — no cached compiler took part.
v0.15.0 compiles and serves a Symfony application natively, adds ext/bcmath, a
much larger ext/openssl and operator overloading, and gives the CLI an
upgrade, a doctor and build progress. A performance sweep over ~150 string,
array, SPL, date and regex functions removes the quadratic cases and brings 109
of 151 measured functions within 1.2x of php 8.5. Memory ABI is now v22.
Highlights
A Symfony app, native
examples/symfony-appbuilds to one binary that serves/(200) and an HTML
404 underHttp\Server, in prod and in dev, including a cold start with no
var/.examples/symfony-consoleruns.GET /on macOS arm64 withwrk: 1 worker 8.9k -> 15.8k rps, 8 workers
56k -> 83k rps. Linux (Docker VM): ~14k rps per worker, ~100k on 8.- The request path no longer leaks: a 404 grew 1.7 KB per request, now ~60 B.
Causes: property stores through an erased receiver,+and[...$a]retaining
a key twice,array_pop/array_shiftresults, in-place closure envs, and a
cycle collector that did not count freed shells. - Dev boot needed reflection: parameter default factories for trait-imported,
interface and prelude-class methods,self::X/parent::Xdefaults evaluated
in the declaring class, class constants through every extended interface,
ReflectionProperty::setValue()onException/Error.
Performance sweep (tools/bench/basic_fns_sweep.php)
| Case (80k) | Before | After |
|---|---|---|
array_diff / array_intersect / *_key |
1.1 – 15 s | 7 – 11 ms |
ArrayObject / ArrayIterator foreach |
1.7 s (20k) | 0.8 ms |
fwrite to php://memory (40k) |
120 ms | 0.9 ms |
.= through a string &$s parameter (40k) |
12.7 ms | 0.4 ms |
array_shift / array_unshift loop |
x17 | x3.7, O(1) amortized |
similar_text, periodic input |
770 ms | 4 us |
| call by runtime name | 22 us | 5 ns (inline cache) |
Also 5-150x faster: str_repeat, array_unique, strrev, base64/hex,
serialize/unserialize, in_array/array_search, ksort/asort, sort
(native stable merge sort), array_slice/reverse/merge, range, date(),
str_replace, preg_* (PCRE JIT, in-place ovector). WebSocket applyMask
74 us -> 5.8 us. Still >= 2x slower than php: base64 (needs SIMD),
nested_arr_read_modify, http_build_query, str_ireplace, ArrayObject,
strtotime.
Smaller IR, faster builds
- Dynamic-call-by-name thunks (
$cb(...),callable) are emitted once inlib/manticore_stdlib.oand exported, instead of rebuilt in every program. On 19 sampled cases IR drops 54.9 -> 41.2 MB and compile wall 52.8 -> 34.3 s;lib/manticore_stdlib.ogrows 7.2 -> 8.9 MB. Hello world is byte-identical. CIlinux-amd64suite: 91 -> 42 min.
CLI
manticore upgrade [--check] [--version X]: checksummed, staged swap with
rollback, previous binary kept asbin/.manticore.prev; package-manager and
container installs are refused.--checkexits 0 current / 1 update / 2 error.manticore doctor [--offline] [--no-smoke]:[ OK ]/[WARN]/[FAIL]for
clang >= 15,cc, stdlib, pcre2/openssl/zlib, a hello-world compile and run,
update availability.manticore completions bash|zsh|fish, generated from the command metadata.compile/buildprogress:--progress=auto|live|plain|json|off,--quiet,
--timings,--color. Piped output stays silent unlessCIis set; stdout is
never touched.- Arg-parse errors read
<command>: <error>(exit code still 64).
New extensions and language features
ext/bcmathin pure PHP: all 14 functions, byte-identical to Zend on 58k fuzz
cases;BcMath\Numbermatches methods, scales and messages (30k fuzz cases).ext/opensslover libcrypto, ~85% of functions: ciphers (CBC, CTR, GCM, CCM,
OCB, chacha20-poly1305), digests, keys (RSA, EC, DSA, DH, Ed25519, X25519,
Ed448, X448), sign/verify, x509, CSR, SPKAC, seal/open, CMS, PKCS#7/#12.
Not done:openssl.cnf-driven options.- Operator overloading (superset):
Manticore\Overloadablewith__operator/
__comparefor+ - * / % ** <=> == != < <= > >=, unary minus,+=,++/--
on a statically typed operand. Operators on an erased operand are not
dispatched yet (#263). - Stdlib: SPL iterators (
LimitIterator,CachingIterator,RegexIterator, ...),
ext/random(Randomizer,Mt19937,Xoshiro256StarStar,Pcg*; seeded
sequences match Zend),ext/filter,crypt/password_*,hash_pbkdf2/
hash_hkdf,str_increment, quoted-printable, uuencode,soundex,
metaphone,fpow,log1p/expm1/asinh/acosh/atanh,error_clear_last,
forward_static_call,get_error_handler, and more. matchwithout a default arm throwsUnhandledMatchError.- Readonly classes: dynamic properties and
unsetthrowErrorlike Zend.
Fixes
About 110 bugs, mostly found by the sweeps and the Symfony work:
- Coercion failures throw instead of coercing silently (#188); 39 bugs in eval
order, lifetimes, numerics, raw-int erasure and TypeDef/GC (#247); 10 more in
voidvar_dump, enum array cast, callable slots, by-refstdClass
properties and generatorvalid(#250). - Int/bool arguments to
floatparameters convert on every call path (#228). - A by-value closure capture written through (
$l[] = 1,array_pop($l)) was
released twice at exit (#275). addslashesis binary safe (#279);hex2binrejects odd and non-hex input
(#281); numeric strings compare as numbers in<andsort(#283, #308).array_merge(...$l)emptied the source's inner arrays (#309); a barearray
parameter written with another element type stored raw pointers (#307);
array_shift/array_popleaked and kept destructors waiting (#314, #315).printf/sprintfformat non-finite floats asNaN/INF(#266); a literal
printf('%f', NAN)stays on the inlinesnprintfpath and printsnan.ReflectionFunction,ReflectionPropertyand trait-method reflection gaps
(#287, #288, #289); first dev boot segfault inDeepClone::getHydrator(#290).Dstyped arrays: inline bitwise stores andTypedArray::apply()
(28 -> 6.6 ns/element).
Compatibility and upgrade notes
- Memory ABI v22.
array_shift/array_unshiftslide the array header
(ARRAY_HEAD_GAP_OFFSET). Rebuild programs and any.obuilt with an older
compiler. No bootstrap pin raise is needed. COMPILER_VERSIONis the single version source;tools/check_version.sh
enforces it in CI and the release workflow.BOOTSTRAP_VERSIONstays 0.14.0.- PCRE JIT is new and used when the host PCRE2 has it; without JIT, matching
falls back to the interpreter silently. - Where Zend warns and carries on, the new functions throw
(ini_parse_quantity,convert_uudecode, openssl on junk certs/keys).
Known issues
- JSON and XML descriptor output in
symfony-consolediffers from Zend (#296,
#297). - The lenient cell-to-string cast does not raise php's
TypeError(#332, partial). - Dynamic dispatch on an untyped receiver is fixed narrowly for
bindTo(#292). forward_static_calldoes not forward the late-static-binding class.- Open bugs found during the sweep: #319-#325, #329.
Verification
- CI on
fe5476b4: linux-arm64, linux-amd64, alpine-arm64, alpine-amd64, native-runner, bootstrap-from-pin, version-source green; macOS arm64 2221/2224 with one load flake (http_workers, passes alone 3/3). - Local macOS arm64 on the pre-#335 tree:
tests/aot/run.sh -j 02220 passed;xfail.sh0 XPASS. - Not run: difftest, selfhost fixpoint, full local Linux gate.
Full log: v0.14.0...v0.15.0