Skip to content

v0.13.0

Choose a tag to compare

@github-actions github-actions released this 07 Oct 09:25
· 64 commits to main since this release
Immutable release. Only release title and notes can be modified.
5757310
docker run --rm -v "$PWD":/work -u "$(id -u):$(id -g)" \
    ghcr.io/manticorephp/compiler:0.13.0 manticore compile app.php -o app

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

v0.13.0 rebuilds memory ownership and adds native data structures. Ownership of
locals is now decided per program point by a dataflow pass, generators own what
they hold, exceptions unwind at zero cost, and the destructors still owed run at
shutdown. On top of that, Manticore\Ds brings fixed-width typed arrays over a
native buffer, SplFixedArray moves onto the same buffer, and the SPL heaps and
queues are added or rewritten with php's behaviour. Array lookups got faster, and
every open bug now lives as a runnable repro next to its GitHub issue.

Highlights

Native containers: Manticore\Ds (superset — docs/ds.md)

  • Typed arrays over one native buffer runtime: Int8/16/32/64Array,
    UInt8/16/32/64Array, Float32/64Array, BitArray, and ByteBuffer (typed,
    endianness-aware reads and writes at a byte offset).
  • Growable (push(), pop(), $a[] = $v) with whole-array sum(), min(),
    max(), equals(); values out of an element's range throw ValueError, never wrap.
  • Bound to an element type through the usual generics docblocks:
    /** @var UInt16Array<TokenKind> */ stores 2 bytes and reads back as the
    #[TypeDef] class, methods included. A binding the array cannot hold is a compile error.
  • For a receiver in a local, a property or a static property, $a[$i],
    $a[$i] = $v and $a[] = $v are an inline bounds check plus a load or store, and a
    counted loop loads the buffer and its length once. 10M get/set on an Int32Array
    in a property: 168 ms → 6.4 ms (array<int,int>: ~39 ms); 10M appends: 27 ms
    (array: 51 ms); a 2M-read getUInt32 scan is ~60× faster than unpack().

SPL with php's behaviour

  • SplFixedArray stores its elements in the native buffer (no PHP array underneath).
  • SplHeap, SplMinHeap, SplMaxHeap, SplPriorityQueue — new, with Zend's sift
    order, corruption protocol, dump and serialization.
  • SplDoublyLinkedList, SplQueue, SplStack rewritten: O(1) shift / dequeue,
    LIFO offsets, add(), frozen stack / queue modes.
  • Both families were fuzzed against php's own classes (operations, exceptions,
    var_dump, print_r, serialize, iteration) with no difference.

Ownership, rebuilt

  • Locals are owned per program point (OwnershipFlow): a join compensates with a
    retain, drops sit before an overwrite and at every exit, a store into a container is
    a move only when the local is dead after it. A representation mismatch at a join is
    a build failure, never a silent wrong answer.
  • Property, static property, global and catch-variable overwrites release the old
    value; cell elements are owned by their hint; by-ref write-through checks the retype.
  • Generators own their params, locals, key, sent and return values; destroying a
    suspended generator runs its finally blocks first, as php does.
  • Zero-cost exceptions: landing pads and unwind tables, finally on every exit,
    exception chaining when a throw leaves finally.
  • Destructors still owed at shutdown run in creation order (statics, cycles, exit()).
  • A WebSocket soak of 5000 connect/echo/close cycles grows RSS by 368 KB (7.4 MB at
    3000 before). On the php-cs-fixer fork the symfony corpus peaks at 2.6 GB instead of
    3.3 GB.

Faster arrays

  • The bucket index keeps a 32-bit hash tag beside each entry: a probe skips mismatching
    slots without loading the entry; string-keyed lookups are about 30% faster.
  • isset and ?? probe once and evaluate base and key once, key first as php's
    delayed fetch does; string-keyed reads, array_key_exists and unset normalise a
    canonical int-string key the same way.
  • Backtrace push/pop are emitted inline, and split builds inline small callees across
    parts.

Bugs live as runnable repros

  • An open bug is a repro under tests/aot/repro/{wrong,crash,leak} plus a GitHub issue;
    tests/aot/xfail.sh runs them on every CI push and reports a fixed one as XPASS.
    tools/file_bug.sh files the issue from the repro.

Language & runtime

  • json_encode honours JsonSerializable and emits a backed enum's value;
    print_r and var_dump show object properties with php's :protected /
    :"Class":private markers; __unserialize receives int keys as ints.
  • pack() / unpack() implement the float codes e E d g G f.
  • Generics keep a #[TypeDef] binding: short class names in Foo<Bar> resolve through
    use, specializations are built over the TypeDef, and @return T refines a
    compatible native hint.
  • Fixed crashes on values held in mixed: an ArrayAccess element write, an enum case
    after instanceof, a float ??.
  • The tokenizer follows Zend's number grammar and heredoc escapes; proc_open wires a
    stream resource onto the child's fd.
  • IntlTimeZone::getIanaID() needs ICU 74, as in php; a program using intl now links
    against older ICU.
  • analyze reports front-end errors instead of answering "No errors found".

Compatibility

  • Memory ABI v18. A library .o built by an older compiler must be rebuilt;
    bin/build needs no seed for the bump.
  • Manticore\Ds is a superset with no php oracle.

Known gaps

  • #110 a narrow #[TypeDef] repr is not enforced on creation (an out-of-range value is
    truncated in a narrow slot) — fix ready, next release.
  • #107 foreach over a typed array bound to a #[TypeDef] yields the plain scalar.
  • #106 a class with a #[TypeDef] property makes any var_dump() a compile error.
  • #109 json_encode of a UInt64Array writes values ≥ 2^63 as negative ints.
  • #67 nested-list == still crashes on Alpine arm64 (musl); correct elsewhere.
  • Self-build peak RSS is ~12% above the pre-ownership baseline.