Skip to content

v0.15.0

Choose a tag to compare

@github-actions github-actions released this 18 Oct 13:13
· 6 commits to main since this release
Immutable release. Only release title and notes can be modified.
fe5476b
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-app builds to one binary that serves / (200) and an HTML
    404 under Http\Server, in prod and in dev, including a cold start with no
    var/. examples/symfony-console runs.
  • GET / on macOS arm64 with wrk: 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_shift results, 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::X defaults evaluated
    in the declaring class, class constants through every extended interface,
    ReflectionProperty::setValue() on Exception/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 in lib/manticore_stdlib.o and 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.o grows 7.2 -> 8.9 MB. Hello world is byte-identical. CI linux-amd64 suite: 91 -> 42 min.

CLI

  • manticore upgrade [--check] [--version X]: checksummed, staged swap with
    rollback, previous binary kept as bin/.manticore.prev; package-manager and
    container installs are refused. --check exits 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 / build progress: --progress=auto|live|plain|json|off, --quiet,
    --timings, --color. Piped output stays silent unless CI is set; stdout is
    never touched.
  • Arg-parse errors read <command>: <error> (exit code still 64).

New extensions and language features

  • ext/bcmath in pure PHP: all 14 functions, byte-identical to Zend on 58k fuzz
    cases; BcMath\Number matches methods, scales and messages (30k fuzz cases).
  • ext/openssl over 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\Overloadable with __operator /
    __compare for + - * / % ** <=> == != < <= > >=, 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.
  • match without a default arm throws UnhandledMatchError.
  • Readonly classes: dynamic properties and unset throw Error like 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
    void var_dump, enum array cast, callable slots, by-ref stdClass
    properties and generator valid (#250).
  • Int/bool arguments to float parameters convert on every call path (#228).
  • A by-value closure capture written through ($l[] = 1, array_pop($l)) was
    released twice at exit (#275).
  • addslashes is binary safe (#279); hex2bin rejects odd and non-hex input
    (#281); numeric strings compare as numbers in < and sort (#283, #308).
  • array_merge(...$l) emptied the source's inner arrays (#309); a bare array
    parameter written with another element type stored raw pointers (#307);
    array_shift / array_pop leaked and kept destructors waiting (#314, #315).
  • printf/sprintf format non-finite floats as NaN/INF (#266); a literal
    printf('%f', NAN) stays on the inline snprintf path and prints nan.
  • ReflectionFunction, ReflectionProperty and trait-method reflection gaps
    (#287, #288, #289); first dev boot segfault in DeepClone::getHydrator (#290).
  • Ds typed arrays: inline bitwise stores and TypedArray::apply()
    (28 -> 6.6 ns/element).

Compatibility and upgrade notes

  • Memory ABI v22. array_shift/array_unshift slide the array header
    (ARRAY_HEAD_GAP_OFFSET). Rebuild programs and any .o built with an older
    compiler. No bootstrap pin raise is needed.
  • COMPILER_VERSION is the single version source; tools/check_version.sh
    enforces it in CI and the release workflow.
  • BOOTSTRAP_VERSION stays 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-console differs 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_call does 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 0 2220 passed; xfail.sh 0 XPASS.
  • Not run: difftest, selfhost fixpoint, full local Linux gate.

Full log: v0.14.0...v0.15.0