Skip to content

v0.18.0

Latest

Choose a tag to compare

@github-actions github-actions released this 10 Nov 07:21
· 7 commits to main since this release
Immutable release. Only release title and notes can be modified.
2f28dd1
docker run --rm -v "$PWD":/work -u "$(id -u):$(id -g)" \
    ghcr.io/manticorephp/compiler:0.18.0 manticore compile app.php -o app

curl -fsSL https://github.com/manticorephp/compiler/releases/download/v0.18.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.18.0-alpine or the linux-musl tarball —
install.sh picks it by itself.

Built cold from v0.18.0 — no cached compiler took part.

v0.18.0 runs full frameworks: a Laravel application and the symfony/demo
application each compile into one native binary and serve requests. Large
applications build 2–5x faster, mysqli is in, integer overflow is explicit, and
158 bug issues are closed at their root.

Memory ABI v24 → v25. Programs and libraries built by an older compiler must be
rebuilt (lib/*.o ship with the release). The object header grows from 16 to 24
bytes to carry a php-style object handle (#555).

Highlights

Laravel

examples/laravel-app — web and JSON routes, Eloquent over SQLite, sessions,
Blade, Carbon, Monolog — compiles into one binary and serves requests (#564).
Per-request leaks are gone: heap stays flat over tens of thousands of requests,
and the cycle collector now reaches cycles through untyped properties (#602).
Hot paths that scaled with the size of the program are now O(log n) or cached:
$o->$name visibility checks, get_object_vars, getcwd(), function_exists,
reflection member lists, class-name dispatch (#562, #605).

Laravel, macOS arm64, 1 process 0.17.0 + Laravel support 0.18.0
/api/ping 4148 rps 5290 rps
/hello 2596 rps 3047 rps
/api/notes (Eloquent) 62 rps 98 rps

symfony/demo

Symfony 8.1, Doctrine ORM 3.6, Twig, Symfony UX, security, 30 locales — one native
binary (#599). Every page with a php reference renders byte-identical to php 8.5;
/en/blog/search (a live component) renders as well (#605). New along the way:
lazy ghost objects (ReflectionClass::newLazyGhost and friends).

Known: the demo leaks per request and is not yet safe under concurrent requests
in one worker; both are tracked separately.

mysqli

The full ext/mysqli surface — mysqli, mysqli_stmt, mysqli_result, all 106
procedural functions and 110 constants with Zend's signatures — in pure PHP over the
MySQL wire, no libmysqlclient, with real async (#492). It also fixes PDO's prepared
CALL p(?, ?) with an OUT parameter.

Explicit integer overflow

+ - *, ++/--, unary - and abs() over ints compute the float php would when
the result feeds a float, and otherwise throw ArithmeticError("Integer overflow")
— no silent wrap (#556). An int stored into a float return, property or static is
converted. Manticore\wrapping_add/sub/mul are there for deliberate wraparound.
Still wrapping: ** on ints.

Faster builds, less memory

0.17.0 0.18.0
symfony-demo cold -O2 build 2161 s ~800 s
Laravel cold -O2 build 1688 s ~800 s
symfony-app cold build, CPU 5866 s 1086 s
largest clang part (symfony-demo) 6.8 GB ~3.6 GB
compiler self-build (bin/build) ~12 min ~1.5 min

Front-end passes lost their quadratic scans (Laravel lowering 128 s → 25 s), big
internal bodies are no longer copied into every split part, inference re-runs touch
only what changed, LLVM's MachineOutliner is off (it held a part's whole machine
code until the end), and generated per-class dispatch is split into small
functions (#510, #558, #585, #602, #605).

Bugs

Six sweeps closed most of them: lexer and parser corners, reference cells and
their representation, property hooks, reflection, named arguments with spread,
typed properties, case-insensitive lookup, define(), (array) of interface-typed
values, arrays, leaks and stdout ordering (#461, #470, #483, #486, #546, #555).
spl_object_id and var_dump's #N count 1, 2, 3 and reuse freed ids, like php
(#555). Reified generics: a misfit argument throws TypeError instead of
converting (#555).

Found while bringing up the frameworks and fixed (#599, #602, #605): erased calls of
variadic closures lost their argument pack; a generator foreach that unsets from
the array it walks lost its snapshot across yield; backtrace frames reached through
dynamic calls carried a synthetic line.

Tooling

  • Tolerant parser mode with source ranges and written names — the foundation of the
    coming language server; analyze reports every syntax error per file (#481).
  • tools/prof/serve.sh: profile a compiled server under load (#561).
  • MANTICORE_ASAN=1, MANTICORE_REF_QUARANTINE=1, MANTICORE_POOL_POISON=1 for
    heap-corruption hunts (#564).

Not fixed

#603 (class_exists family is case-sensitive for a run-time name), #604
(constant() on an undeclared class reports the wrong error), #606 (nested isset on ArrayAccess skips the outer offsetExists), #41, #45, #72, #353
(dynamic-property part); symfony-demo per-request leaks and concurrent requests.

Changes (22 pull requests, since v0.17.0)

  • Bind a lazy include unit's top-level variables to the global scope (#608)
  • Release 0.18.0: faster Laravel hot paths, split generated dispatch, symfony-demo serves every page (#605)
  • Laravel: build memory (no MachineOutliner), per-request leaks to zero, cycle collection through untyped properties, #599 regressions (#602)
  • Cut large-app build time: fewer inference passes, smaller IR, cheaper split and clang (#585)
  • symfony/demo compiles and serves every page identically to php (#599)
  • Laravel application compiles into a native binary and serves requests (#564)
  • Perf: function_exists binary search, reflection without quadratic scans or per-call rebuilds, O(log n) class-name dispatch (#562)
  • prof: profile a compiled server under load (#561)
  • Fix symfony-app SIGSEGV at boot: unbox a cell-typed name in the reflection builtins (#560)
  • Cut cold-build CPU 5.4x: stop duplicating big internal bodies across split parts; resolve manifest paths from the manifest directory (#558)
  • Explicit integer overflow: checked arithmetic, wrapping builtins, int to float coercion (#556)
  • Hard bugs: reference representation, object handles, reified fit, comptime require (#555)
  • Remove dead tools scripts and skip CI for dev-only tool paths (#550)
  • Backlog sweep: 26 bugs across arrays, refs, leaks, language semantics and stdout (#546)
  • Front-end, emitter and build sweep: Laravel lowering 128 s → 25 s, staged IR −42%, bin/build 12 → 1.5 min (#510)
  • mysqli on top of pdo_mysql: full php.net surface, real async (#492)
  • Add Claude Code GitHub Workflow (#496)
  • Bug sweep 4: crashes, named args and spread, typed properties, hooks, case-insensitive lookup (#486)
  • Parser: tolerant mode, source ranges and written names (LSP foundation, phase 1) (#481)
  • Bug sweep 3: erased channels, hooks, define(), IIFE unwind, DOM setAttribute (#483)
  • Bug sweep 2: (array) of interface-typed values, array_replace, dynamic new, reflection (#470)
  • Bug sweep: lexer, reference cells, hooks, reflection, array_splice (#461)
  • Raise BOOTSTRAP_VERSION to 0.17.0

Full log: v0.17.0...v0.18.0