Status: PARTLY SHIPPED. The .sig module interface and
manticore build manticore.json (library + application targets, cross-target
linking) are in the tree — user guide: docs/modules.md. Still unbuilt, and
absent from src/ entirely: transitive dependency resolution, the global build
cache (MANTICORE_HOME, ~/.manticore/cache), the compiler_abi field, and
the composer packaging bootstrap.
⚠ The .sig example below is still partly aspirational. The emitter
(Manticore\Sig) writes {"schema":2,"abi":N,"functions":[…],"classes":[…], "constants":[…],"libs":[…],"weak":[…]} — so classes and constants are real
now (see docs/modules.md), but package, version and target are not, and
the ABI is pinned by a bare abi integer rather than a compiler_abi string.
Traits and generic classes are still not exportable.
Goal: cargo/go/swift-class module system. Seamless, config-level deps (composer.json-style). Easy to distribute the compiler, libraries, and compiled programs.
| Layer | Rust | Swift | Go | Manticore |
|---|---|---|---|---|
| manifest | Cargo.toml | Package.swift | go.mod | manticore.json |
| module interface | crate metadata | .swiftinterface |
export data | .sig (JSON) |
| code artifact | .rlib | .o/.a | .a | .o |
| package manager | cargo/crates | SwiftPM | go/VCS | composer/packagist (fetch) |
composer only fetches + resolves versions. manticore compiles + links.
The runtime preamble is linkonce_odr in every .o, so there is NO separate
runtime library to ship/link — compiled binaries are fully static.
- Lib distribution: source + OPTIONAL prebuilt. Default ships PHP source +
manticore.json (portable, cargo-style); a package MAY also ship prebuilt
.o+.sigfor popular target-triples (Swift binary-framework style). The compiler prefers a matching prebuilt, else builds from source into cache. .sigformat: JSON (reuse json_encode/json_decode; readable, extensible).- Build cache: global
~/.manticore/cache/, keyed by(source-hash, compiler_abi, target-triple)— Go-module-cache style, shared across projects, auto-rebuild on any key change.MANTICORE_HOMEoverrides.
A .sig is the serialized EXPORTED symbol table of a library target — the
same data LowerFromAst builds in fnDecls/fnAliasByBare, so a consumer
hydrates it and resolves calls IDENTICALLY to having the source in-module.
{
"schema": 1,
"package": "manticorephp/stdlib",
"version": "1.0.0",
"compiler_abi": "mir-1",
"target": "any", // or e.g. "arm64-apple-darwin" for a prebuilt
"functions": [
{ "name": "str_starts_with", "symbol": "manticore_str_starts_with",
"params": [ {"name":"haystack","type":"string"}, {"name":"needle","type":"string"} ],
"ret": "bool" },
{ "name": "trim", "symbol": "manticore_trim",
"params": [ {"name":"s","type":"string"},
{"name":"mask","type":"string","default":{"k":"str","v":" \t\n\r\\0�"}} ],
"ret": "string" },
{ "name": "array_keys", "symbol": "manticore_array_keys",
"params": [ {"name":"arr","type":"array"} ],
"ret": "vec<int|string>" }, // element type → fixes typing loss
{ "name": "Runtime\\Libc\\strncmp", "symbol": "manticore_Runtime_Libc_strncmp",
"params": [ {"name":"a","type":"string"}, {"name":"b","type":"string"}, {"name":"n","type":"int"} ],
"ret": "int" } // namespaced/FFI exported too
],
"libs": ["pcre2-8", "ssl", "crypto"], // #[Ffi\Library] of the wrappers in here
"weak": ["signalfd", "__errno_location"], // extern_weak symbols it declared
"classes": [], // v2: cross-lib classes (layout + method sigs)
"constants": [] // v2
}Rules (these are exactly what unblocks the compiler self-build):
- Export EVERYTHING, incl. namespaced/FFI bindings (
Runtime\Libc\*). The consumer rebuildsfnAliasByBarefrom them, so a barestrncmpresolves toRuntime\Libc\strncmpagain.symbolstored explicitly so the mangling never diverges. - Types carry element info (
vec<int|string>,assoc<string>,obj:Foo,cell). Codec mirrorsType↔ string. - Defaults are const-folded at lib-build time (PHP defaults are constant
exprs) and stored as a literal
{k,v}; the consumer reconstructs a literal node for default-fill. No expr parsing on the consumer side. - Per-param
byref/variadic/ tagged(cell) flags for the call ABI. libs/weakare the library's LINK requirements, not part of its call interface. They ride here because linking is a whole-program property while an FFI wrapper is emitted exactly once — in the module owning the source. A program callingpreg_matchgets the pcre2 wrapper out of the stdlib.oand has no#[Ffi\Library]of its own to derive-lpcre2-8from. Both keys are additive: a reader that finds them absent falls back to the old unconditional set rather than linking nothing.compiler_abimismatch ⇒ rebuild from source (never link a stale.o).
{
"name": "acme/app",
"dependencies": { "manticorephp/stdlib": "^1.0", "acme/json-fast": "^2.1" },
"libraries": [ {"name":"foo","src":"lib","output":"...","exclude":[]} ],
"applications":[ {"name":"app","src":"src","entry":"src/main.php","output":"bin/app","exclude":[]} ]
}Versions/lock are composer's job (packagist constraints + composer.lock).
- Resolve deps transitively (composer.lock pins versions).
- For each dep: locate package (vendor/ or global), pick a matching prebuilt
.o+.sigif present+compatible, else build from source into~/.manticore/cache/<pkg>-<srchash>-<abi>-<triple>.{o,sig}. - Application target:
a. Load all dep
.sig→ merged imported symbol table. b.LowerFromAsthydratesfnDecls+fnAliasByBarefrom it and emits declare-only externs (FunctionDef::isExtern). c. Compile app sources →app.o. d.cc app.o <dep1>.o <dep2>.o … -o bin/app. linkonce_odrdedups the runtime preamble across all objects.
emitBuiltin strips the namespace, so a FQ call \Runtime\Libc\strlen is
hijacked by the strlen codegen builtin. Rule: a call that resolves to an
IMPORTED FQN symbol takes precedence over the builtin short-name. Disable
builtin interception for qualified calls that have an import.
Two libs exporting trim → exported lib symbols are weak, app definition
(strong) wins; lib-vs-lib conflict resolves by manifest dep order with a
warning (or hard error — TBD at impl).
- Compiled programs: static single binary (runtime linked in). Ship the file. No runtime install on the target.
- Libraries: source via packagist (built locally to cache); optional
prebuilt
.o+.sigper triple. - Compiler (
manticorephp/compiler): shipped as PHP SOURCE (manticore is self-hosting PHP).composer global require manticorephp/compiler→ post-install hook bootstraps the native binary via the Zend seed (php bin/compile) using the user's PHP, and emits the bundledstdlib.o+stdlib.sigfor the host triple. Chicken-and-egg solved by the system php.
~/.composer/vendor/manticorephp/compiler/ bin/manticore lib/stdlib.o lib/stdlib.sig manticore.json
~/.manticore/cache/ <pkg>-<srchash>-<abi>-<triple>.{o,sig}
<project>/ manticore.json composer.json composer.lock
vendor/acme/json-fast/ (source + manticore.json)
bin/app (static)
- Type↔string codec +
.sigwriter (lib build emits JSON.sig, incl. namespaced/FFI + const-folded defaults + element types). .sigreader → hydratefnDecls/fnAliasByBare+ extern FunctionDefs from JSON instead of re-parsing sources. Fix qualified-call-vs-builtin edge.- weak linkage for exported lib symbols.
- Wire
manticore build: per-target.sigemit/consume + global cache keyed by (srchash, abi, triple). Validate the COMPILER self-build via manticore.json. - deps resolution: read
dependencies, locate under vendor/ + global, transitive + composer.lock. - composer packaging:
manticorephp/compilerpost-install bootstrap. - Rewire
bin/compile/bin/rebuildontomanticore build; full gate (suite, difftest, stability, fixpoint).
Already done (commit 34418d9): stdlib bundling (prebuilt stdlib.o, declare
externs, linkonce/internal preamble, link tail), native json_decode +
mixed/cell consumption, manticore build for normal apps, str_replace O(n²) fix.