Skip to content

v0.14.0

Choose a tag to compare

@github-actions github-actions released this 09 Oct 14:33
· 54 commits to main since this release
Immutable release. Only release title and notes can be modified.
7f55d99
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> and Set<T>: insertion-ordered hash containers with
    strict keys. Keys are int, string or object (by identity); 1 and "1"
    are two different keys, and anything else is a TypeError.
    An absent key throws OutOfBoundsException.
  • Vec<T>: a growable list over the native cell buffer.
  • foreach over a Map, Set or Vec walks the storage directly, with no generator
    frame. each, map, filter, reduce, any, all and find are 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 a Set<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_decode and json_validate stay native for every flag
    (JSON_THROW_ON_ERROR, JSON_PRETTY_PRINT, JSON_UNESCAPED_*, JSON_HEX_*,
    JSON_PARTIAL_OUTPUT, JSON_BIGINT_AS_STRING and 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_objects benchmark went from 0.14 s to 0.08 s, 4.4 times php.
  • UInt64Array encodes values at or above 2^63 exactly.

ext/zlib over the host libz

  • gzencode, gzcompress and friends call the system zlib, so they produce php's
    bytes. gzencode is 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), and Http\Server listens with 511, so a burst of connects no
    longer overflows the queue into resets. stream_context_set_options is 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 by tools/fetch_bootstrap.sh. bin/build, CI, the release workflow,
    the Dockerfiles and install.sh all start there. The Zend cold seed and its
    tooling are removed, and a clean clone needs no php. A new bootstrap-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 how array_eq_nested_lists_null_rows failed 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 a ValueError instead of a silent truncation, and f32
    rounds to binary32.
  • ReflectionFunction reflects a Closure, including first-class-callable
    closures, parameter attributes and defaults, and getClosureThis(). A dynamic
    closure call boxes an object argument, and a spread into a closure pads omitted
    optional parameters. symfony/console 7.4 runs.
  • foreach over 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, isset and by-ref
    element paths, and ArrayAccess offsets reach offset*() as written.
  • A string-keyed array hands its keys out as int|string, as php canonicalises
    numeric-string keys at the store.