Repository navigation
v0.14.0
docker run --rm -v "$PWD":/work -u "$(id -u):$(id -g)" \
ghcr.io/manticorephp/compiler:0.14.0 manticore compile app.php -o app
curl -fsSL https://github.com/manticorephp/compiler/releases/download/v0.14.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, curl and zlib (and ICU, for a program that uses intl).
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.14.0-alpine or the linux-musl tarball —
install.sh picks it by itself.
v0.14.0 adds Manticore\Ds\Map, Set and Vec — native, insertion-ordered
containers with strict keys — and rewrites JSON on a native encoder and decoder
that follow php's grammar and error codes. ext/zlib now runs on the host libz,
and Http\Server is tuned for real load. The compiler now bootstraps from a
pinned release instead of a Zend seed, so building from a clean clone needs no
php. A memory bug that glibc hid and musl exposed — a nested array literal freed
while the callee still held it — is fixed at its root, which also clears the
last open Alpine failure.
Highlights
Native containers: Map, Set, Vec (superset — docs/ds.md)
Manticore\Ds\Map<K,V>andSet<T>: insertion-ordered hash containers with
strict keys. Keys areint,stringorobject(by identity);1and"1"
are two different keys, and anything else is aTypeError.
An absent key throwsOutOfBoundsException.Vec<T>: a growable list over the native cell buffer.foreachover a Map, Set or Vec walks the storage directly, with no generator
frame.each,map,filter,reduce,any,allandfindare available,
and a captureless literal closure passed to them is fused into the loop.- Run under php through a polyfill (
tools/ds_polyfill.php), so the same program
can be checked against Zend. - 1M insert + lookup + remove on macOS arm64: string keys 270 ms against 284 ms for
a php array, int keys 85 ms against 82 ms, 1M object ids in aSet<object>
93 ms against 99 ms. The point is strict keys and identity keys at array speed,
not a faster array.
JSON on a native runtime
json_encode,json_decodeandjson_validatestay native for every flag
(JSON_THROW_ON_ERROR,JSON_PRETTY_PRINT,JSON_UNESCAPED_*,JSON_HEX_*,
JSON_PARTIAL_OUTPUT,JSON_BIGINT_AS_STRINGand the rest). A call with any
flag used to fall back to compiled PHP at about three times php's time.- The decoder follows php's grammar and error codes (syntax, control character,
UTF-8, UTF-16, depth, state mismatch), canonicalises int keys and turns big
integers into floats or strings as php does. - Objects encode through a class descriptor walker instead of a per-object map: the
json_objectsbenchmark went from 0.14 s to 0.08 s, 4.4 times php. UInt64Arrayencodes values at or above 2^63 exactly.
ext/zlib over the host libz
gzencode,gzcompressand friends call the system zlib, so they produce php's
bytes.gzencodeis about ten times faster than the previous pure-PHP encoder.
Http\Server under load
- A listener honours the stream context's
socket.backlog(php's default is 32;
it was a fixed 16), andHttp\Serverlistens with 511, so a burst of connects no
longer overflows the queue into resets.stream_context_set_optionsis added. - Server defaults fit real load: no keep-alive request cap, 4096 connections per
worker, and a worker yields after each accept so prefork workers share a burst
instead of the first one taking it all.
Bootstrap without php
- The compiler builds from the release named in
BOOTSTRAP_VERSION, fetched and
checksummed bytools/fetch_bootstrap.sh.bin/build, CI, the release workflow,
the Dockerfiles andinstall.shall start there. The Zend cold seed and its
tooling are removed, and a clean clone needs no php. A newbootstrap-from-pin
CI job builds the tree from the pin alone.
Language & runtime
- A nested array literal passed to a callee that keeps it (
$this->rows = $r, a
global, a static) is no longer released after the call. The inner arrays used to
be given back while the kept buffer still listed them. glibc masked the freed
read; musl faulted, which is howarray_eq_nested_lists_null_rowsfailed on
Alpine. Constructor, method, static and function calls are all covered. - A narrow
#[TypeDef]representation is enforced where a value is created: an
out-of-range value is aValueErrorinstead of a silent truncation, andf32
rounds to binary32. ReflectionFunctionreflects aClosure, including first-class-callable
closures, parameter attributes and defaults, andgetClosureThis(). A dynamic
closure call boxes an object argument, and a spread into a closure pads omitted
optional parameters. symfony/console 7.4 runs.foreachover an array of cells co-owns its value, so a key write in the loop
copies instead of mutating the source's element in place or freeing a buffer the
array still holds. This was heap corruption in the native php-cs-fixer fork, which
reported 28 bogus files on a symfony slice.- Numeric-string keys are canonicalised on dynamic array set,
issetand by-ref
element paths, andArrayAccessoffsets reachoffset*()as written. - A string-keyed array hands its keys out as
int|string, as php canonicalises
numeric-string keys at the store.