manticorephp / compiler
Manticore — a PHP-to-native AOT compiler, written in PHP and self-hosted; compiles a large PHP 8.5 subset to standalone arm64/x86_64 binaries via LLVM.
Requires
None
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-16 18:14:51 UTC
README
Self-hosted PHP-to-native AOT compiler. Compiles a large subset of PHP 8.5+ to standalone native binaries (arm64 / x86_64) through LLVM IR — no PHP runtime, no interpreter, no extension loader. The compiler is written in PHP and compiles itself to a byte-identical fixpoint.
manticore compile app.php -o app && ./app # one file → one static binary
The output has no interpreter to install, no php.ini, no extension list — you ship
the binary. What you write is ordinary PHP: the Zend interpreter is the reference,
and every plain-runnable test case is diffed against it. On top of that sits a
superset php cannot run at all — structured concurrency, FFI,
a module system, compile-time attributes.
Requirements
Emitted binaries carry no PHP runtime; they link dynamically against libc plus
the system libraries the stdlib rides on — PCRE2 (preg_*), OpenSSL 3
(TLS, hash/hmac) and zlib (gz*) — and, on macOS, libiconv. A program that binds a native
library through FFI (PDO → sqlite3, curl_* → libcurl, …) adds that library to
its link line on demand. The compiler needs the same libraries as dev packages,
plus a real toolchain, because it ends in clang and cc:
| What | Version | Why |
|---|---|---|
clang + cc on PATH |
LLVM ≥ 15 | Manticore emits opaque-pointer IR; clang 14 rejects it |
php |
8.5 | optional — only the tools/difftest.sh oracle (and a few Zend-hosted dev tools); building needs none |
| libpcre2 (dev package) | 10.x | preg_* rides host PCRE2; needs pcre2-config; emitted binaries link it |
| OpenSSL 3 (dev package) | 3.x | TLS, hash/hmac; needs pkg-config; emitted binaries link it |
| zlib (dev package) | 1.2+ | gz*, deflate_*/inflate_*; emitted binaries link it |
| libxml2, libsqlite3, libcurl, libicu (dev packages) | — | only for a program that uses DOM*/SimpleXML, PDO, curl_*, or ext/intl (Normalizer, …) — each is demand-gated and linked (dynamically) on mention; Homebrew's keg-only icu4c is found by itself |
The -dev / -devel half matters: the headers are what the build looks for, not just
the runtime library. It is also what carries the unversioned lib<name>.so symlink
a -l<name> resolves against — a runtime libxml2.so.2 on its own does not link.
Platforms: macOS (arm64 / x86_64) and Linux (glibc ≥ 2.34 or musl / Alpine, arm64 /
x86_64). Each builds the compiler and passes the full suite including the self-host
fixpoint; Alpine has its own image tag (:alpine) and linux-musl tarballs. See
docs/install.md.
# macOS brew install php pcre2 openssl@3 pkg-config # Debian / Ubuntu — plus clang from your distro or apt.llvm.org, and PHP 8.5 from sury.org sudo apt-get install -y build-essential pkg-config libpcre2-dev libssl-dev
Per-OS package lists, Docker images and troubleshooting:
docs/install.md.
Install
The installer takes a published build when there is one for your platform (linux and
macOS, arm64 and amd64), verifies it against the release checksums, and otherwise
falls back to what it has always done: compile the compiler with itself. It checks
the toolchain above, tells you what is missing, then installs under $MANTICORE_HOME
(default ~/.manticore):
curl -fsSL https://raw.githubusercontent.com/manticorephp/compiler/main/install.sh | bash export PATH="$HOME/.manticore/bin:$PATH" manticore version # manticore 0.15.0
Re-running the installer upgrades in place. Knobs: MANTICORE_HOME,
MANTICORE_VERSION (a specific release), MANTICORE_FROM_SOURCE=1 (skip the
download), MANTICORE_REF (branch/tag), MANTICORE_REPO, MANTICORE_SRC (build a
local checkout instead of cloning). Building from source is the same self-hosting
loop it always was: an existing manticore rebuilds the new version with itself,
and a first build starts from the pinned bootstrap release (BOOTSTRAP_VERSION) — no php.
In a container, with the toolchain already in it:
docker run --rm -v "$PWD":/work -u "$(id -u):$(id -g)" \ ghcr.io/manticorephp/compiler manticore compile app.php -o app
A tarball carries the compiler and its stdlib, not a toolchain: manticore
assembles its IR with clang, links with cc, and the binaries it emits link the
host's pcre2, openssl, sqlite3 and curl. The image is the one artifact that brings
all of that with it.
Upgrading
manticore upgrade # install the latest release manticore upgrade --check # installed vs latest; exit 0 current, 1 update available, 2 error manticore upgrade --version 0.13.0 # a specific release (a downgrade is allowed)
upgrade runs the share/install.sh that ships in every release tarball, in strict
mode. The new build is staged and smoke-tested, then swapped in with rollback; the
previous binary stays as bin/.manticore.prev. The release checksum is mandatory: a
mismatch or missing SHA256SUMS aborts and leaves the install untouched, and strict
mode never falls back to a source build. A compiler not installed by install.sh
(package manager, container image, a layout without share/install.sh) is refused with
a message naming the likely owner — update it with that installer.
Strict-mode exit codes of install.sh (set MANTICORE_STRICT=1): 2 release
unreachable, 3 no published build for this platform, 4 checksum failure,
5 staging or smoke-test failure. Knobs: MANTICORE_STRICT, MANTICORE_HOME,
MANTICORE_VERSION.
Via Composer, which here is a delivery + build trigger rather than a runtime:
composer create-project manticorephp/compiler manticore # builds into ~/.manticore vendor/bin/manticore-install # if it is already a dependency
The installed layout is self-contained and needs no environment variables — the binary finds its runtime relative to itself:
$MANTICORE_HOME/bin/manticore
$MANTICORE_HOME/lib/manticore_stdlib.o(.sig)
$MANTICORE_HOME/lib/prelude/*.php
Checking your install
manticore doctor # clang, cc, bundled runtime files, host libraries, a hello-world smoke test, update check manticore doctor --offline # skip the update check manticore doctor --no-smoke # skip compiling and running the hello-world
Each line is [ OK ], [WARN] or [FAIL], with a hint for the last two. A FAIL means the
compiler cannot work here (clang missing or older than 15, no cc, a missing runtime file,
a failed smoke test); doctor then exits 1. A WARN is advisory (a host library not found, an
install not managed by install.sh, an update available or unreachable) and exits 0.
manticore version --check mirrors upgrade --check: installed vs latest, exit 0 current,
1 update available, 2 error.
Shell completions
manticore completions bash > ~/.local/share/bash-completion/completions/manticore manticore completions zsh > ~/.zfunc/_manticore # ~/.zfunc must be in fpath before compinit manticore completions fish > ~/.config/fish/completions/manticore.fish
The scripts are generated from the command and option declarations, so they match the installed compiler; regenerate them after an upgrade.
From a checkout
bin/build # first run: fetch the pinned release (BOOTSTRAP_VERSION), build, rebuild # thereafter it rebuilds ITSELF (this is the normal loop) bin/build --verify # + fixpoint + suite gate
Working with it
Every command and option: docs/cli.md.
# one file manticore compile path/to/app.php -o app && ./app echo '<?php echo "hi\n";' | manticore compile -o /tmp/hi && /tmp/hi # a project — a cargo-style manifest, see docs/modules.md manticore build # reads ./manticore.json manticore build --libs-only # library targets only # a Composer project: "composer": true in the manifest compiles the project's # autoload dirs AND every package in composer.lock — vendor/ is source, not an # autoload map evaluated at runtime.
Everything else is inspection — every stage of the pipeline is dumpable:
| Command | Purpose |
|---|---|
compile <file> -o <out> |
PHP source → native binary (file arg or stdin) |
build [manticore.json] |
build all manifest targets (libraries + applications) |
analyze <file> |
the static checks a compiler can make and an interpreter never gets to |
dump-ast / dump-mir / dump-llvm-mir |
parse / typed MIR / LLVM IR |
dump-llvm |
LLVM IR from stdin |
dump-sig <files> |
the module interface (exported symbol table) |
split-ir |
split a staged .ll module into N parts (dev tool behind build -j) |
version / help |
— |
Flags: -o <out>, -O<0|1|2|3|s|z> (clang opt level, default -O2),
--emit-library (a standalone .o with no @main), --memory=rc|arena|hybrid, and
--prelude / --effects for the dump-* commands. compile analyzes by default and
prints warnings without failing the build — --no-analyze turns it off, --analyze-strict
makes error-severity findings fail the compile (rc=65). analyze adds --deep (also run the
MIR type passes), --json, and --baseline / --generate-baseline to suppress known
findings. --backend=<mir|ast> is still parsed but inert — MIR is the only backend.
build also takes --libs-only (build the library targets and stop) and --keep-ir
(leave the generated <output>.dbg.ll / .dbg.o next to the target instead of staging
them in /tmp and deleting them — pair with -O0 to get a binary lldb can walk).
⚠ The dump-* commands do not link the stdlib, so a call into it resolves as
unknown. When that matters, read the final binary.
Build progress
Full reference: docs/cli.md.
compile and build report progress on stderr only; stdout is never touched.
| Flag | Meaning |
|---|---|
--progress=auto|live|plain|json|off |
output mode (default auto) |
--quiet |
no progress output at all (warnings and errors are still printed) |
--timings |
after done, list the 15 slowest stages |
--color=auto|always|never |
colour for the progress text (default auto) |
auto picks live when stderr is a terminal, plain when it is not but CI is set to
a non-empty value, and off otherwise — piped builds stay silent. live redraws one line
(● EmitLlvm 1.2s (total 4.0s) · functions 512/1881) and ends with ✓ done in 7.1s.
Counters: files, functions and, for a split build (MANTICORE_SPLIT_JOBS with -j 0),
clang (parts compiled so far / total, cache hits included).
Colour rules (NO_COLOR, TERM=dumb, --color=always), the plain line format and the
json event table with examples are in docs/cli.md.
The PHP you get
Classes / interfaces / traits / enums (enum methods, constants, interface-implementing
enums), abstract + anonymous classes, late static binding (new static,
static::method(), parent::/self:: forwarding), magic methods
(__get/__set/__call/__invoke/__clone/__destruct), clone-with, 8.4
property hooks + asymmetric visibility, closures + first-class callable syntax
(f(...)), by-ref / variadic params + argument unpacking, dynamic callables, the pipe
operator |>, match, DNF types, ?? / ?->, global / static locals, heredoc /
nowdoc, string interpolation, constants, generators (yield / yield from),
exceptions with try/catch/finally and real stack traces, references to an array
element / object property and out-parameter auto-vivification
(preg_match($re, $s, $m)), reference returns, attributes, Reflection, Fibers, and
file I/O over libc.
Generics are docblock-driven, so the source stays valid PHP: @template with
bounds and defaults, @extends/@implements, generic traits (zero-cost), and
reified @var Box<float> = new Box — a real specialized class, no boxing — plus
implicit monomorphization of erased array / callable params
(docs/generics.md).
Standard library: the array_* family in full, strings (incl. the whole preg_*
family over host PCRE2), mbstring (every encoding Zend ships, case mapping,
width, MIME/HTML-entity codecs — pure PHP; mb_ereg* not yet), ext/intl complete
over the system ICU (Collator, NumberFormatter, IntlDateFormatter, IntlCalendar,
MessageFormatter, Transliterator, IntlBreakIterator, IntlChar, …), type/reflection,
math, ctype_*, JSON, var_dump/print_r, SPL, date/time, sockets and streams, hashing
and crypto, bcmath (arbitrary-precision decimals, incl. BcMath\Number with operators). extension_loaded() answers true for mbstring, intl and bcmath. Each function is either a
PHP-level stdlib function (src/Runtime/Stdlib/, compiled into
lib/manticore_stdlib.o and auto-linked), an injected prelude helper, or an inlined
codegen builtin. No imports, no registration — they are simply there. The exact
name-by-name coverage per extension — and everything Manticore adds beyond PHP —
is generated into docs/builtins.md (php tools/builtins_audit.php).
Current gaps are tracked with repros in docs/ROADMAP.md; the
headline ones are listed under Limitations.
Beyond PHP — the superset
Parity is the north star and tools/difftest.sh enforces it. The rest — the surface
php cannot run, and difftest therefore cannot check — is catalogued in
docs/superset.md: concurrency, compile-time attributes, FFI,
the module system, the type system, the memory model.
The headline is structured concurrency — Go's model, PHP's spelling, written in
PHP over two primitives of ours (native Fiber on fcontext, and Io\Poll over
kqueue/epoll):
Async\async(function () { $a = Async\spawn(fn() => file_get_contents('https://example.com/one')); $b = Async\spawn(fn() => file_get_contents('https://example.com/two')); [$x, $y] = Async\awaitAll($a, $b); // ~1 RTT, not 2 });
Ordinary fread/fwrite/stream_socket_accept/sleep suspend the fiber instead of
the process — plain streams are the async API, TLS and DNS included. Every task is
owned by a scope, cancellation is delivered at the suspend point, a deadlock is
reported rather than exited, and Async\dump() names every live task and where it was
spawned. An 8-worker prefork HTTP server does 150–160k rps (wrk, plaintext
keep-alive). See docs/async.md.
Http\WebSocket puts RFC 6455 (+ RFC 7692 permessage-deflate) on the same
Http\Server, and as an outbound client to third-party servers — one reader per
connection, foreach over incoming messages, sending from any task, a callback
layer with Hub broadcast. No Zend oracle for this one (php has no WebSocket
layer), so it carries its own suite and an Autobahn|Testsuite gate.
docs/websocket.md.
Native libraries (zlib, libcurl, …) bind through FFI — #[Library, Symbol]
attributes compile to direct C calls, and #[Library] is what puts the library on the
link line; mechanism and C-type vocabulary in docs/ffi.md. The module system
(docs/modules.md) is a cargo-style manticore.json with
applications and libraries, .sig module interfaces so a dependent target resolves
cross-unit calls without re-parsing sources, and a distributable compiler that ships
bin/ + lib/ with no PHP sources at all.
Performance
Native AOT output vs the Zend interpreter on an Apple M1 Pro, -O2, PHP 8.5.11.
Each ordinary-PHP case is verified byte-for-byte against php before timing; loops
are data-dependent and $argc-seeded so LLVM cannot fold them away. Times are seconds
(lower is better); RSS is peak resident memory in MiB. Reproduce with
REPS=5 bash bench/run.sh (the script defaults to 3 runs; cases live in bench/cases/).
| Case | Native (s) | PHP (s) | Speedup | Native RSS (MiB) | PHP RSS (MiB) | Parity |
|---|---|---|---|---|---|---|
alloc_churn |
0.04 | 0.32 | 8.0× | 2.4 | 28.1 | ok |
array |
0.08 | 0.90 | 11.2× | 8.7 | 35.8 | ok |
assoc |
0.07 | 0.24 | 3.4× | 2.3 | 28.0 | ok |
assoc_small |
0.03 | 0.17 | 5.7× | 2.1 | 27.9 | ok |
closures |
0.02 | 0.61 | 30.5× | 2.1 | 28.1 | ok |
crc32 |
0.03 | 0.24 | 8.0× | 2.2 | 28.2 | ok |
dijkstra |
0.02 | 0.32 | 16.0× | 2.6 | 29.2 | ok |
explode |
0.05 | 0.33 | 6.6× | 2.0 | 28.1 | ok |
fib |
0.10 | 11.59 | 115.9× | 2.0 | 28.0 | ok |
fiber_pingpong |
0.03 | 0.36 | 12.0× | 2.3 | 28.1 | ok |
fiber_switch |
0.05 | 0.55 | 11.0× | 2.4 | 28.7 | ok |
foreach_assoc |
0.02 | 0.19 | 9.5× | 26.4 | 41.5 | ok |
funcarr |
0.03 | 1.02 | 34.0× | 2.5 | 28.2 | ok |
generator_yield |
0.02 | 0.72 | 36.0× | 2.0 | 28.0 | ok |
http_parse |
0.92 | — | — | 3.2 | — | php-skip |
http_scale |
0.93 | — | — | 3.3 | — | php-skip |
htmlspecialchars |
0.09 | 0.40 | 4.4× | 2.2 | 28.6 | ok |
implode_int |
0.19 | 0.27 | 1.4× | 2.2 | 28.3 | ok |
in_array |
0.11 | 0.26 | 2.4× | 2.1 | 27.9 | ok |
json |
0.05 | 0.23 | 4.6× | 2.1 | 28.4 | ok |
json_decode |
0.10 | 0.30 | 3.0× | 15.3 | 46.0 | ok |
json_decode_object |
0.01 | 0.15 | 15.0× | 6.3 | 33.9 | ok |
json_decode_records |
0.06 | 0.23 | 3.8× | 10.8 | 39.3 | ok |
json_deep |
0.03 | 0.19 | 6.3× | 2.2 | 28.4 | ok |
json_escape_heavy |
0.02 | 0.15 | 7.5× | 2.8 | 28.5 | ok |
json_objects |
0.08 | 0.35 | 4.4× | 4.6 | 29.3 | ok |
json_pretty |
0.01 | 0.15 | 15.0× | 4.9 | 29.8 | ok |
json_records |
0.20 | 0.62 | 3.1× | 11.2 | 33.3 | ok |
json_utf8 |
0.06 | 0.24 | 4.0× | 4.6 | 29.4 | ok |
ksort_asort |
0.02 | 0.13 | 6.5× | 5.8 | 29.1 | ok |
loop |
0.06 | 1.34 | 22.3× | 2.0 | 27.9 | ok |
mandelbrot |
0.03 | 0.96 | 32.0× | 2.0 | 28.3 | ok |
mathf |
0.02 | 0.72 | 36.0× | 2.0 | 28.0 | ok |
matmul |
0.01 | 0.17 | 17.0× | 2.6 | 28.9 | ok |
nbody |
0.04 | 0.35 | 8.8× | 2.1 | 28.3 | ok |
nested_array_local |
0.02 | 0.18 | 9.0× | 2.5 | 28.1 | ok |
net_bulk |
0.01 | 0.14 | 14.0× | 3.0 | 28.4 | ok |
net_lines |
0.02 | 0.15 | 7.5× | 2.8 | 28.1 | ok |
oop |
0.06 | 3.12 | 52.0× | 2.0 | 28.1 | ok |
refslot |
0.00 | 0.13 | ∞ | 2.6 | 28.2 | ok |
sieve |
0.03 | 0.43 | 14.3× | 28.2 | 58.7 | ok |
sort |
0.05 | 0.20 | 4.0× | 2.5 | 28.3 | ok |
spectralnorm |
0.01 | 1.55 | 155.0× | 2.2 | 28.0 | ok |
sprintf |
0.07 | 0.19 | 2.7× | 2.0 | 28.1 | ok |
strcat |
0.10 | 0.80 | 8.0× | 32.4 | 59.1 | ok |
strops |
0.03 | 0.22 | 7.3× | 2.1 | 28.1 | ok |
tokenize |
0.03 | — | — | 14.8 | — | php-skip |
unset_churn |
0.00 | 0.12 | ∞ | 3.6 | 29.4 | ok |
variadic_pack |
0.08 | 0.26 | 3.2× | 2.7 | 28.1 | ok |
wordcount |
0.02 | 0.16 | 8.0× | 2.0 | 28.0 | ok |
All 47 comparable cases are faster natively; three HTTP/tokenization cases are native-only because they use Manticore prelude APIs. The sub-10 ms values are at the harness's two-decimal precision, so treat their speedups as directional. The table also shows the start-up-memory advantage: most native binaries stay near 2–3 MiB RSS, while the PHP interpreter baseline is roughly 28 MiB before workload-specific allocations.
Examples
The runnable demos live in examples/: async/ covers
structured concurrency and http/ has native HTTP servers. The
symfony-console/ example is a small Composer application
compiled with Symfony Console itself — install its dependencies, then build the project:
cd examples/symfony-console
composer install
manticore build
./bin/demo greet Ada
Its manifest uses "composer": true, so Composer autoload roots and installed packages
are compiled as source; the generated binary does not load vendor/autoload.php at runtime.
symfony-app/ goes further: a Symfony framework application (routing,
dependency injection, HTTP kernel) served from one native binary. Its README lists what differs
from a stock skeleton and how to build it.
How it is built
PHP source
→ Lexer (src/Lexer) tokens
→ Parser (src/Parser) AST (recursive-descent + Pratt)
→ LowerFromAst ─┐
→ NarrowScalarGuards│
→ ConstFold │
→ DeadStore │
→ InferTypes │ MIR (src/Compile/Mir) — flat, typed, SSA-ish IR.
→ VivifyRefArgs │ The only backend. InferTypes re-runs after each pass
→ ResolveMethodFcc │
→ NarrowReturns │
→ InlineClosures │ that makes new types concrete, which is why
→ ResolveOverloads │
→ Monomorphize │ Monomorphize — specializing erased-array and callable
→ FuseSplitJoin │ params per call-site shape — sits this far down.
→ TypeCheck │
→ NarrowReturns │ Full annotation: src/Compile/README.md
→ CheckTypeDefs │
→ ReflectAnalysis │
→ DemoteCharLocals │
→ InferEffects │
→ InferAllocKind │
→ ApplyMemoryMode │
→ SpillFreshBases │
→ InsertMemoryOps │ (rc retain/release/CoW insertion)
→ OwnershipFlow │ (per-program-point ownership of locals)
→ Verify ─┘
→ EmitLlvm (src/Compile/Mir/Passes/EmitLlvm*) → LLVM IR text
→ clang -c IR → object
→ cc link (libc + pcre2 + openssl, + FFI-bound libraries on demand)
Memory (docs/memory.md): reference counting on strings,
objects, vecs and assoc arrays with copy-on-write, so frees are deterministic and there
are no GC pauses; a synchronous Bacon–Rajan cycle collector that is opt-in and
zero-overhead until gc_collect_cycles() is reached; and three allocation modes
(--memory / MANTICORE_MEMORY) — hybrid (default, escape analysis routes each
allocation between arena and heap-rc), rc, arena.
Source layout — pure PHP, one class per file, path mirrors FQN:
bin/ build & run scripts + the output binary
build self-host rebuild via the manifest (+ --verify, --fast); fetches the pinned release when no compiler exists
manticore-install the installer entry point Composer exposes
lib/ prebuilt stdlib object + .sig + prelude (build artifacts, gitignored)
prelude/ PHP injected into every program (Fiber, async runtime, Resource, …)
src/Lexer/ tokenizer
src/Parser/ recursive-descent + Pratt parser; AST node types
src/Compile/ AST → MIR lowering, MIR passes, EmitLlvm backend
src/Codegen/ low-level LLVM-IR text builders (no semantic logic)
src/Analyze/ the static analyzer behind `analyze` and compile-time warnings
src/Cli/ subcommand registry + argument parsing
src/Ffi/ the attributes and opaque pointer type for native bindings
src/Runtime/ PHP-level stdlib + libc / OS / FFI bindings compiled into binaries
src/Manticore/ driver (Main.php), Sig.php, the build command
tools/ build + gate scripts (selfhost, difftest, docker, …)
tests/aot/ the harness: cases/*.php + expected/*.out, auto-discovered
examples/ runnable demos (examples/async/ has the concurrency ones)
docs/ the guides; docs/ROADMAP.md is the status + gap matrix
Gates
bash tests/aot/run.sh # the AOT suite (cases/ + expected/, auto-discovered) bash tests/aot/run.sh -k hello # filter by substring bash tools/difftest.sh # parity vs `php` bash tools/selfhost_fixpoint.sh # fixpoint + self-host suite + rebuild stability bash tools/docker/run_tests.sh --gate # the same, on Linux bash tools/install_smoke.sh # an installed compiler ($PATH, symlink) finds its own lib/ bash tests/aot/xfail.sh # known-bug repros: XFAIL = still open, XPASS = fixed, promote it bash tools/file_bug.sh <repro.php> # file a minimised repro as a GitHub issue (-n: dry run)
CI runs the suite on every push to main and every PR — Linux arm64 and amd64 on
Debian (glibc) and on Alpine (musl), in the container, plus macOS arm64 bare —
self-hosting from the compiler the previous run cached, falling back to the
published main compiler and then to the pinned release (BOOTSTRAP_VERSION);
there is no Zend seed. The bootstrap-from-pin job checks on every push that the
tree still builds from the pin alone. Required checks: linux-arm64,
linux-amd64, linux-alpine-arm64, linux-alpine-amd64, macos-arm64 and
bootstrap-from-pin. difftest and the fixpoint are not run in CI: every AOT case already
carries a php-generated expected output, so run bash tools/difftest.sh by hand when php is upgraded.
selfhost_fixpoint.sh asserts gen2 IR == gen3 IR, runs the suite through the
self-built compiler, and rebuilds repeatedly to catch build-to-build layout roulette.
The Linux gate is not optional for anything touching src/Runtime/, syscalls or errno.
Limitations
- Integer overflow wraps (two's-complement) instead of promoting to float —
PHP_INT_MAX + 1givesPHP_INT_MIN. extract()is not implemented (dynamic symbol-table writes the typed frame does not model).compact()works.gotointo a loop body is unsupported (plain forward/backwardgotoworks).- Cycle collector is manual-trigger only and does not scan static/global roots.
traits and generic classes do not cross a compiled-library boundary. Classes, interfaces, enums and constants do (.sigschema 2); a trait and a@templateclass both need their method bodies on the far side.- Regular-file I/O blocks the async loop by design; see
docs/async.mdfor the measurements andAsync\readFile().
The full, current gap matrix with repros lives in
docs/ROADMAP.md.
License
Licensed under the MIT License.