diff --git a/README.md b/README.md index 9015e1e5b25..75b1836fa64 100755 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Presenter script and troubleshooting: [`docs/GETTING-STARTED.md`](docs/GETTING-S | **003-MiniWebApp** VM + AOT link | βœ… | | **003** AOT execute (home, hello, PATH_INFO, contact) | βœ… native execute ([#764](https://github.com/PurHur/php-compiler/issues/764) closed; close tracker [#1044](https://github.com/PurHur/php-compiler/issues/1044)) | | Self-host **M0–M1** | βœ… | -| Self-host **M2** spine | 🚧 **584** / 588 units ([#1492](https://github.com/PurHur/php-compiler/issues/1492)) | +| Self-host **M2** spine | 🚧 **589** / 593 units ([#1492](https://github.com/PurHur/php-compiler/issues/1492)) | | Full Zend PHP compatibility | ❌ β€” subset only (matrices in `docs/`, not on status site) | MiniWebApp gates: [docs/miniwebapp-gates.md](docs/miniwebapp-gates.md) ([#472](https://github.com/PurHur/php-compiler/issues/472)). Presenter verify: `make north-star1-verify` ([#1845](https://github.com/PurHur/php-compiler/issues/1845)). Docker image: `php-compiler:22.04-dev`. @@ -141,7 +141,7 @@ Matrix details: [docs/local-ci-matrix.md](docs/local-ci-matrix.md). |-----------|----------------|--------| | **M0 β€” Bundled subset runs** | ~**109** literal `require_once` units in `test/selfhost/compiler_minimal/main.php` compile+link under AOT; native binary prints `compiler_minimal bundle OK` | βœ… ([#557](https://github.com/PurHur/php-compiler/issues/557), [#913](https://github.com/PurHur/php-compiler/issues/913)) | | **M1 β€” Compiler-shaped bundle** | Same bundle **lints** as one translation unit; **compile-smoke** links a tiny fixture and runs AOT echo (`compiler smoke`); driver smoke bundles `bin/compile.php`-adjacent units | βœ… ([#1025](https://github.com/PurHur/php-compiler/issues/1025)) | -| **M2 β€” Full top-level `lib/` + spine** | All **14** top-level `lib/*.php` lint βœ…; **`compiler_lib_spine_smoke`** (**584** / **588** inventory units) native link βœ…; grow toward full `bin/vm.php` path | 🚧 ~99% of inventory | +| **M2 β€” Full top-level `lib/` + spine** | All **14** top-level `lib/*.php` lint βœ…; **`compiler_lib_spine_smoke`** (**589** / **593** inventory units) native link βœ…; grow toward full `bin/vm.php` path | 🚧 ~99% of inventory | | **M3 β€” Native compiles PHP** | Self-host bundle links; HelloWorld AOT **runs** natively; **compile emit still Zend fallback** | 🚧 partial | | **M4 β€” Bootstrap loop** | Native toolchain rebuilds the **next** compiler sources | ⬜ | | **M5 β€” Full self-host** | Real `bin/vm.php` / `bin/compile.php` on full inventory; **no Zend bootstrap** | ⬜ **north star** ([#1492](https://github.com/PurHur/php-compiler/issues/1492)) | @@ -186,7 +186,7 @@ On harness hosts with an empty bind-mount, use `./script/docker-ci-local.sh` or | Gate | Command | Status | |------|---------|--------| -| Phase A inventory | `php script/bootstrap-inventory.php --check` | βœ… **588** files; **0** blockers | +| Phase A inventory | `php script/bootstrap-inventory.php --check` | βœ… **593** files; **0** blockers | | Phase B lib AOT lint | `php bin/compile.php -l lib/*.php` | βœ… **14** top-level `lib/*.php` ([#534](https://github.com/PurHur/php-compiler/pull/534)) | | Phase B fixture lint | `php script/bootstrap-aot-lint.php` | βœ… **83** procedural targets | | Phase C native link | `make bootstrap-aot-link` | βœ… **71/71** link targets OK | diff --git a/docs/bootstrap-inventory.md b/docs/bootstrap-inventory.md index 0be54b9e3af..7f2a43b801a 100644 --- a/docs/bootstrap-inventory.md +++ b/docs/bootstrap-inventory.md @@ -8,7 +8,7 @@ Regenerate: `php script/bootstrap-inventory.php` | Metric | Count | |--------|------:| -| PHP files on vm.php path | 588 | +| PHP files on vm.php path | 593 | | Source constructs flagged (blockers) | 0 | | Source constructs flagged (warnings) | 1541 | diff --git a/docs/bootstrap-selfhost.md b/docs/bootstrap-selfhost.md index 0411c9339f9..ede41a87177 100644 --- a/docs/bootstrap-selfhost.md +++ b/docs/bootstrap-selfhost.md @@ -6,7 +6,7 @@ | Gate | Command | Status | |------|---------|--------| -| Phase A inventory | `php script/bootstrap-inventory.php --check` | βœ… **588** files on `bin/vm.php` path; **0** source blockers (excluded paths: `lib/AOT/Linker.php`, `lib/JIT/Builtin/StringPregMatch.php`; `lib/VM/HashTable.php` bundled via `ArrayIterator`) | +| Phase A inventory | `php script/bootstrap-inventory.php --check` | βœ… **593** files on `bin/vm.php` path; **0** source blockers (excluded paths: `lib/AOT/Linker.php`, `lib/JIT/Builtin/StringPregMatch.php`; `lib/VM/HashTable.php` bundled via `ArrayIterator`) | | Phase B lib AOT lint | `php bin/compile.php -l lib/*.php` (with `script/php-env.sh`) | βœ… **14/14** top-level `lib/*.php` units ([#534](https://github.com/PurHur/php-compiler/pull/534)) | | Phase B fixture lint | `php script/bootstrap-aot-lint.php` | βœ… **13** procedural targets under `test/bootstrap-aot/` + `examples/000-HelloWorld` | | Phase C native run | `make bootstrap-aot-link` or `./script/bootstrap-aot-link.sh` | βœ… **71/71** link targets OK | @@ -20,7 +20,7 @@ | Self-host probe in full CI | `./script/ci-local.sh` (LLVM tail) | βœ… default-on when LLVM 9 present; `BOOTSTRAP_SELFHOST_PROBE_GATE=0` to skip ([#829](https://github.com/PurHur/php-compiler/issues/829)) | | Wave gate in full CI | `./script/ci-local.sh` (LLVM tail) | βœ… default-on when LLVM 9 present; `BOOTSTRAP_WAVE_CHECK=0` to skip; `./script/bootstrap-wave-check.sh --fail-fast` | | Self-host native link | `./script/bootstrap-selfhost-link.sh` | βœ… `build/selfhost` prints `compiler_minimal bundle OK` ([#557](https://github.com/PurHur/php-compiler/issues/557), [#913](https://github.com/PurHur/php-compiler/issues/913)) | -| M2 lib spine smoke lint | `php bin/compile.php -l test/selfhost/compiler_lib_spine_smoke/main.php` | βœ… **584** units ([#1492](https://github.com/PurHur/php-compiler/issues/1492), [#1419](https://github.com/PurHur/php-compiler/issues/1419) +| M2 lib spine smoke lint | `php bin/compile.php -l test/selfhost/compiler_lib_spine_smoke/main.php` | βœ… **589** units ([#1492](https://github.com/PurHur/php-compiler/issues/1492), [#1419](https://github.com/PurHur/php-compiler/issues/1419) | M2 lib spine smoke native run | `./script/bootstrap-selfhost-lib-spine-smoke-link.sh` or `BOOTSTRAP_LIB_SPINE_SMOKE=1 make bootstrap-selfhost-lib-spine-smoke` | βœ… `build/selfhost-lib-spine-smoke` prints `compiler_lib_spine_smoke bundle OK` | | M2 lib spine VM `-r` smoke | `./script/bootstrap-selfhost-lib-spine-vm-smoke.sh` or `BOOTSTRAP_LIB_SPINE_VM_SMOKE=1 make bootstrap-selfhost-lib-spine-vm-smoke` | βœ… same binary + `PHP_COMPILER_VM_SPINE_SMOKE=1` prints `vm-spine-ok` ([#1846](https://github.com/PurHur/php-compiler/issues/1846); CI opt-in `BOOTSTRAP_LIB_SPINE_VM_SMOKE_GATE=1`) | | M3 HelloWorld self-host probe lint | `php bin/compile.php -l test/selfhost/compiler_helloworld_smoke/main.php` | βœ… `compiler_compile_smoke` spine + `helloworld_compile_smoke` driver (linkable) | diff --git a/docs/capabilities.md b/docs/capabilities.md index 6324535abc6..adbab8b8334 100644 --- a/docs/capabilities.md +++ b/docs/capabilities.md @@ -162,10 +162,10 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand. | `mkdir` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `nl2br` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `number_format` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | -| `ob_end_flush` | yes | no | no | standard | doc: VM only; not implemented for JIT in this compiler build | -| `ob_get_clean` | yes | no | no | standard | doc: VM only; not implemented for JIT in this compiler build | -| `ob_get_level` | yes | no | no | standard | doc: VM only; not implemented for JIT in this compiler build | -| `ob_start` | yes | no | no | standard | doc: VM only; not implemented for JIT in this compiler build | +| `ob_end_flush` | yes | no | no | standard | not implemented for JIT in this compiler build (#118) | +| `ob_get_clean` | yes | no | no | standard | not implemented for JIT in this compiler build (#118) | +| `ob_get_level` | yes | no | no | standard | not implemented for JIT in this compiler build (#118) | +| `ob_start` | yes | no | no | standard | not implemented for JIT in this compiler build (#118) | | `octdec` | yes | yes | yes | standard | | | `ord` | yes | yes | yes | standard | | | `pack` | yes | yes | yes | standard | JIT PHPT | @@ -234,7 +234,7 @@ Auto-generated by `script/capability-matrix.php`. Do not edit by hand. | `strchr` | yes | yes | yes | standard | | | `strcmp` | yes | yes | yes | standard | | | `strcspn` | yes | yes | yes | standard | AOT PHPT | -| `stream_context_create` | yes | yes | no | standard | zero-arg empty context; options deferred (#1377, #1056) | +| `stream_context_create` | yes | yes | yes | standard | JIT PHPT | | `strip_tags` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `stripos` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | | `stripslashes` | yes | yes | yes | standard | JIT PHPT; AOT PHPT | diff --git a/docs/pages/development-status.md b/docs/pages/development-status.md index 98b6d788548..e22f8700651 100644 --- a/docs/pages/development-status.md +++ b/docs/pages/development-status.md @@ -15,7 +15,7 @@ permalink: /development-status.html | **Overall progress** | ~**50%** toward web-capable + self-hosting compiler (indicative) | | **Wave 3 (May 2026)** | Language **10/13** Β· Stdlib **12/13** ([#1380](https://github.com/PurHur/php-compiler/issues/1380)) | | **North Star 1** | **Achieved** β€” reference web app VM + AOT execute + default-on CI gates ([#1044](https://github.com/PurHur/php-compiler/issues/1044) closed); follow-ups [#1750](https://github.com/PurHur/php-compiler/issues/1750), [#587](https://github.com/PurHur/php-compiler/issues/587), [#445](https://github.com/PurHur/php-compiler/issues/445), [#173](https://github.com/PurHur/php-compiler/issues/173) | -| **North Star 2** | Self-compile β€” M0–M1 βœ… Β· M2 spine **584/588** Β· M5 ⬜ ([#1492](https://github.com/PurHur/php-compiler/issues/1492) +| **North Star 2** | Self-compile β€” M0–M1 βœ… Β· M2 spine **589/593** Β· M5 ⬜ ([#1492](https://github.com/PurHur/php-compiler/issues/1492) | **Not Zend parity** | Subset compiler (not full Zend PHP) | [← Visual overview](index.html) Β· [Repository](https://github.com/PurHur/php-compiler) @@ -51,7 +51,7 @@ Indicative composite toward a **web-capable, self-hosting** compiler (not line-c | **Stdlib** | ~58% | Wave-3 batch ([#1367](https://github.com/PurHur/php-compiler/issues/1367)–[#1379](https://github.com/PurHur/php-compiler/issues/1379)): **13/13** closed on master | | **Web AOT** (build, deploy) | ~70% | Project link βœ…; CLI execute βœ…; HTTP serve-aot / layout-edge bisect 🚧 | | **Reference app** (MiniWebApp) | ~90% | North Star 1 **achieved** β€” VM + AOT execute + default-on gates; optional bisect [#1750](https://github.com/PurHur/php-compiler/issues/1750) | -| **Self-host** (North Star 2, M0–M5) | ~99% | M0–M1 βœ…; M2 spine **584/588**; M3 partial (native run βœ…, Zend emit); M4–M5 ⬜ β€” [self-host-target.md](https://github.com/PurHur/php-compiler/blob/master/docs/self-host-target.md +| **Self-host** (North Star 2, M0–M5) | ~99% | M0–M1 βœ…; M2 spine **589/593**; M3 partial (native run βœ…, Zend emit); M4–M5 ⬜ β€” [self-host-target.md](https://github.com/PurHur/php-compiler/blob/master/docs/self-host-target.md **Overall (indicative): ~52%** toward the stated north stars below. @@ -179,7 +179,7 @@ The **compiler fully compiles itself** β€” the stretch goal behind every bootstr - Self-hosted binary runs **real** `bin/compile.php` / `bin/vm.php` driver paths (not stub-only `echo` demos) - Self-hosted binary compiles **`examples/000-HelloWorld`** without Zend - Self-hosted binary compiles the **next** compiler revision β€” **bootstrap loop closed** -- Honest AOT bundle toward the full `bin/vm.php` inventory (**588** files), with `PHP_COMPILER_SELFHOST_AOT` stub surface **shrinking** as lowering lands +- Honest AOT bundle toward the full `bin/vm.php` inventory (**593** files), with `PHP_COMPILER_SELFHOST_AOT` stub surface **shrinking** as lowering lands This is **North Star 2**. It is **orthogonal** to [North Star 1 (web app)](#north-star-1-web-app) β€” user-facing web apps vs. the compiler eating its own `lib/` tree. @@ -204,7 +204,7 @@ Zend PHP still runs `bin/compile.php` during bootstrap. The output is a **curate |-----------|---------|--------| | **M0 β€” Bundled subset runs** | ~109 literal `require_once` units in `test/selfhost/compiler_minimal/main.php` β†’ `build/selfhost` prints `compiler_minimal bundle OK` | βœ… [#557](https://github.com/PurHur/php-compiler/issues/557), [#913](https://github.com/PurHur/php-compiler/issues/913) | | **M1 β€” Compiler-shaped bundle** | Bundled `Compiler.php` AOT lint; compile-smoke native link + AOT echo (`compiler smoke`); driver smoke toward `bin/compile.php` | βœ… [#1025](https://github.com/PurHur/php-compiler/issues/1025), [#1095](https://github.com/PurHur/php-compiler/issues/1095) | -| **M2 β€” Lib spine growth** | `compiler_lib_spine_smoke` bundle toward full `bin/vm.php` inventory (**588** files) | 🚧 **584** / 588 units (~99%; [#1492](https://github.com/PurHur/php-compiler/issues/1492) +| **M2 β€” Lib spine growth** | `compiler_lib_spine_smoke` bundle toward full `bin/vm.php` inventory (**593** files) | 🚧 **589** / 593 units (~99%; [#1492](https://github.com/PurHur/php-compiler/issues/1492) | **M3 β€” Native compiles PHP** | Self-hosted bundle links; HelloWorld AOT **runs** natively; **emit still uses Zend** `bin/compile.php` fallback | 🚧 partial ([#1492](https://github.com/PurHur/php-compiler/issues/1492)) | | **M4 β€” Bootstrap loop** | Native toolchain rebuilds the **next** compiler sources (same tree, new revision) | ⬜ | | **M5 β€” Full self-host** | Real `bin/vm.php` / `bin/compile.php` path on full inventory; **no Zend bootstrap** | ⬜ **north star** ([#1492](https://github.com/PurHur/php-compiler/issues/1492)) | @@ -214,9 +214,9 @@ Zend PHP still runs `bin/compile.php` during bootstrap. The output is a **curate | Set | ~Files | Notes | |-----|--------|-------| | `compiler_minimal` bundle (M0) | **109** | Literal `require_once` closure | -| `compiler_lib_spine_smoke` (M2) | **584** | vm.php-path lib/ + ext/standard growth bundl +| `compiler_lib_spine_smoke` (M2) | **589** | vm.php-path lib/ + ext/standard growth bundl | Top-level `lib/*.php` | **14** | Per-file AOT lint βœ… | -| Full vm.php inventory | **588** | Phase A inventory (`php script/bootstrap-inventory.php`) | +| Full vm.php inventory | **593** | Phase A inventory (`php script/bootstrap-inventory.php`) | **M2 β†’ M5 gap:** ~**152** inventory files still outside the honest native bundle; plus native compile driver (`parseAndCompile` emit, [#1402](https://github.com/PurHur/php-compiler/issues/1402)); vendor prelink ([#1416](https://github.com/PurHur/php-compiler/issues/1416)). @@ -233,7 +233,7 @@ Details: [self-host-target.md](https://github.com/PurHur/php-compiler/blob/maste | Phase | Command / doc | Status | |-------|---------------|--------| -| **A β€” Inventory** | `php script/bootstrap-inventory.php --check` | βœ… **588** files; 0 source blockers | +| **A β€” Inventory** | `php script/bootstrap-inventory.php --check` | βœ… **593** files; 0 source blockers | | **B β€” AOT lint** | `lib/*.php`, `test/bootstrap-aot/`, selfhost bundles | βœ… (requires `script/apply-patches.sh` locally) | | **C β€” Native fixtures** | `make bootstrap-aot-link` | βœ… **71/71** bootstrap-aot link targets OK | | **D β€” `lib/` in bundle** | `lib/OpCode.php` etc. | βœ… [#540](https://github.com/PurHur/php-compiler/issues/540) | @@ -249,7 +249,7 @@ Details: [self-host-target.md](https://github.com/PurHur/php-compiler/blob/maste | Native link + run | `./script/bootstrap-selfhost-link.sh` | βœ… M0 | | Compile smoke link | `make bootstrap-selfhost-compile-smoke` | βœ… M1 | | Compile smoke AOT echo | `make bootstrap-selfhost-compile-smoke-run` | βœ… M1 | -| M2 spine native link | `BOOTSTRAP_LIB_SPINE_SMOKE=1 make bootstrap-selfhost-lib-spine-smoke` | βœ… `compiler_lib_spine_smoke bundle OK` (**584** units +| M2 spine native link | `BOOTSTRAP_LIB_SPINE_SMOKE=1 make bootstrap-selfhost-lib-spine-smoke` | βœ… `compiler_lib_spine_smoke bundle OK` (**589** units | M3 HelloWorld probe | `make bootstrap-selfhost-helloworld` | 🚧 partial β€” native **run** βœ…; emit Zend fallback | | Wave gate | `./script/bootstrap-wave-check.sh` | βœ… locally / Docker; GHA workflow disabled | | Next includes probe | `php script/bootstrap-selfhost-next-includes.php` | 🚧 bundle growth | diff --git a/docs/roadmap-wave3.md b/docs/roadmap-wave3.md index 459a8f1b090..c90a8e95521 100644 --- a/docs/roadmap-wave3.md +++ b/docs/roadmap-wave3.md @@ -17,7 +17,7 @@ Regenerate capability truth: `php script/capability-matrix.php`, `php script/cap Related merges outside this wave: `goto` ([#1228](https://github.com/PurHur/php-compiler/issues/1228) / [#1333](https://github.com/PurHur/php-compiler/pull/1333)), anonymous classes ([#1233](https://github.com/PurHur/php-compiler/issues/1233) / [#1386](https://github.com/PurHur/php-compiler/pull/1386)). -**M2 spine:** **584** / **588** inventory (`compiler_lib_spine_smoke` native link βœ…; [#1492](https://github.com/PurHur/php-compiler/issues/1492), M2 batches [#1419](https://github.com/PurHur/php-compiler/issues/1419), [#1497](https://github.com/PurHur/php-compiler/issues/1497)). Target doc: [self-host-target.md](self-host-target.m +**M2 spine:** **589** / **593** inventory (`compiler_lib_spine_smoke` native link βœ…; [#1492](https://github.com/PurHur/php-compiler/issues/1492), M2 batches [#1419](https://github.com/PurHur/php-compiler/issues/1419), [#1497](https://github.com/PurHur/php-compiler/issues/1497)). Target doc: [self-host-target.md](self-host-target.m ## Language (#1354–#1366) diff --git a/docs/self-host-target.md b/docs/self-host-target.md index 1ca00eb15b1..cfa98f2d32d 100644 --- a/docs/self-host-target.md +++ b/docs/self-host-target.md @@ -21,7 +21,7 @@ That is **M5**. Everything below is the honest path from today’s bootstrap to | **Compiler in PHP** | Front/middle/back end stay in `lib/`, `ext/` β€” not rewritten in C | | **No `vendor/` at cold boot** | Parser/types/LLVM FFI prelinked once ([#1416](https://github.com/PurHur/php-compiler/issues/1416)); see [`bootstrap-vendor-inventory.md`](bootstrap-vendor-inventory.md) | | **No Zend bootstrap** | `bin/compile.php` / `bin/vm.php` run as **compiled** code, not `php bin/compile.php` | -| **Full inventory** | Honest bundle covers the `bin/vm.php` path (~**588** files; [`bootstrap-inventory.md`](bootstrap-inventory.md)) | +| **Full inventory** | Honest bundle covers the `bin/vm.php` path (~**593** files; [`bootstrap-inventory.md`](bootstrap-inventory.md)) | | **Stub surface minimal** | `PHP_COMPILER_SELFHOST_AOT` stubs shrink; compiler behavior is real, not link-only | | **Small native floor OK** | `lib/AOT/runtime/*.c` + external `clang` via `lib/AOT/Linker.php` β€” **not** required to disappear | @@ -34,8 +34,8 @@ That is **M5**. Everything below is the honest path from today’s bootstrap to | Layer | Today | Target | |-------|-------|--------| | **Bootstrap driver** | Zend runs `php bin/compile.php` | Compiled `bin/compile.php` | -| **Bundle size** | **584** curated `require_once` in spine smoke (108 minimal overlap + **476** M2-only) | **588** inventory files | -| **Inventory coverage** | **~99%** of vm.php path in spine smoke (**584/588**) | **100%* +| **Bundle size** | **589** curated `require_once` in spine smoke (108 minimal overlap + **476** M2-only) | **593** inventory files | +| **Inventory coverage** | **~99%** of vm.php path in spine smoke (**589/593**) | **100%* | **HelloWorld** | Native **run** βœ…; **emit** still Zend fallback | Native compile + emit | | **Bootstrap loop (M4)** | Gen-1 link + gen-2 Zend partial | Native gen-2 emit + full tree rebuild | | **Vendor** | `composer install` + patches on host | Prelinked artifacts only | @@ -51,7 +51,7 @@ That is **M5**. Everything below is the honest path from today’s bootstrap to | M4 `make bootstrap-loop-gen1-link` | 🚧 partial β€” gen-1 link + gen-2 Zend emit; native gen-2 blocked on M3 ([#1498](https://github.com/PurHur/php-compiler/issues/1498)) | | M4 `make bootstrap-loop-probe` | 🚧 ladder β€” `--dry-run` validates lint+M2+M3 partial+gen-1; full exits **2** until M3 strict ([#1498](https://github.com/PurHur/php-compiler/issues/1498)) | | `make bootstrap-aot-link` | βœ… **71/71** | -| `php script/bootstrap-inventory.php --check` | βœ… **588** files, **0** source blockers | +| `php script/bootstrap-inventory.php --check` | βœ… **593** files, **0** source blockers | --- @@ -61,7 +61,7 @@ That is **M5**. Everything below is the honest path from today’s bootstrap to |-----------|----------------|--------|-----| | **M0** | AOT can link a **small** honest `lib/` subset | βœ… | 100% | | **M1** | Bundle is **compiler-shaped** (lint + compile-smoke) | βœ… | 100% | -| **M2** | Spine grows toward full `bin/vm.php` inventory | 🚧 **584/588** units | **~99%* +| **M2** | Spine grows toward full `bin/vm.php` inventory | 🚧 **589/593** units | **~99%* | **M3** | Self-host binary **compiles external PHP** (HelloWorld) without Zend emit | 🚧 partial | **~45%** | | **M4** | Self-host binary **rebuilds** the next compiler tree | ⬜ | 0% | | **M5** | Full self-host; Zend retired from loop | ⬜ north star | 0% | @@ -72,7 +72,7 @@ That is **M5**. Everything below is the honest path from today’s bootstrap to | Indicator | Formula | May 2026 | |-----------|---------|----------| -| **M2 spine progress** | `require_once` units in `compiler_lib_spine_smoke` Γ· Phase A inventory file count | **584 / 588 β‰ˆ 99%* +| **M2 spine progress** | `require_once` units in `compiler_lib_spine_smoke` Γ· Phase A inventory file count | **589 / 593 β‰ˆ 99%* | **Public β€œSelf-host” row** | Same M2 ratio until M3–M5 gates add weight ([`development-status.md`](pages/development-status.md)) | **~61%** | | **Composite (internal)** | Milestone weights in table above (M0–M1 = 100%, M2 = spine %, M3–M5 = gate %) | **~54%** | @@ -124,7 +124,7 @@ Parallel batches ([#1419](https://github.com/PurHur/php-compiler/issues/1419), [ | Entry | Units | Role | |-------|------:|------| | `test/selfhost/compiler_minimal/main.php` | **109** | M0 core | -| `test/selfhost/compiler_lib_spine_smoke/main.php` | **584** | M2 growth (ext/standard + Vm* spine batch +| `test/selfhost/compiler_lib_spine_smoke/main.php` | **589** | M2 growth (ext/standard + Vm* spine batch | `test/selfhost/compiler_helloworld_smoke/` | β€” | M3 probe + compile driver | | `test/selfhost/bootstrap_loop_smoke/` | β€” | M4 scaffold (gen-1β†’gen-2 loop probe; [#1498](https://github.com/PurHur/php-compiler/issues/1498)) | diff --git a/docs/stdlib-jit-audit.md b/docs/stdlib-jit-audit.md index f4c7d85a97d..fe9b45b7b96 100644 --- a/docs/stdlib-jit-audit.md +++ b/docs/stdlib-jit-audit.md @@ -5,18 +5,18 @@ Auto-generated by `script/audit-stdlib-jit.php`. Regenerate: `php script/audit-s | Metric | Count | |--------|------:| | `call()` implementations | 257 | -| With JitStringArg/JitLongArg (or zero-arg LLVM) | 242 | +| With JitStringArg/JitLongArg (or zero-arg LLVM) | 243 | | Missing arg helpers (actionable for JIT) | 0 | | Deferred (VM-only) | 14 | | Self-host auto-stub batch | 11 | ## Missing (sorted) -_(none)_ +_None β€” all JIT `call()` builtins are lowered or deferred._ ## Deferred (VM-only) -- `debug_backtrace` (debug) β€” `ext/standard/debug_backtrace.php` (JIT deferred #1751) +- `debug_backtrace` (debug) β€” `ext/standard/debug_backtrace.php` - `ob_end_flush` (output) β€” `ext/standard/ob_end_flush.php` - `ob_get_clean` (output) β€” `ext/standard/ob_get_clean.php` - `ob_get_level` (output) β€” `ext/standard/ob_get_level.php` @@ -221,7 +221,6 @@ _(none)_ - `sprintf` β€” `ext/standard/sprintf_.php` - `sqrt` β€” `ext/standard/sqrt.php` - `stat` β€” `ext/standard/stat_.php` -- `stream_context_create` β€” `ext/standard/stream_context_create.php` - `str_contains` β€” `ext/standard/str_contains.php` - `str_ends_with` β€” `ext/standard/str_ends_with.php` - `str_pad` β€” `ext/standard/str_pad.php` @@ -235,6 +234,7 @@ _(none)_ - `strchr` β€” `ext/standard/strchr.php` - `strcmp` β€” `ext/standard/strcmp.php` - `strcspn` β€” `ext/standard/strcspn.php` +- `stream_context_create` β€” `ext/standard/stream_context_create.php` - `strip_tags` β€” `ext/standard/strip_tags.php` - `stripos` β€” `ext/standard/stripos.php` - `stripslashes` β€” `ext/standard/stripslashes.php` diff --git a/ext/standard/JitObEndFlush.php b/ext/standard/JitObEndFlush.php new file mode 100644 index 00000000000..1fc00857b1a --- /dev/null +++ b/ext/standard/JitObEndFlush.php @@ -0,0 +1,30 @@ + 0) { + throw new \LogicException('ob_end_flush() takes no arguments'); + } + $slot = JitValueBox::alloc($context); + $ptr = JitValueBox::pointer($context, $slot); + $context->builder->call( + $context->lookupFunction('__phpc_ob_end_flush'), + $ptr + ); + + return $ptr; + } +} diff --git a/ext/standard/JitObGetClean.php b/ext/standard/JitObGetClean.php new file mode 100644 index 00000000000..75b6a551b85 --- /dev/null +++ b/ext/standard/JitObGetClean.php @@ -0,0 +1,30 @@ + 0) { + throw new \LogicException('ob_get_clean() takes no arguments'); + } + $slot = JitValueBox::alloc($context); + $ptr = JitValueBox::pointer($context, $slot); + $context->builder->call( + $context->lookupFunction('__phpc_ob_get_clean'), + $ptr + ); + + return $ptr; + } +} diff --git a/ext/standard/JitObGetLevel.php b/ext/standard/JitObGetLevel.php new file mode 100644 index 00000000000..6ccbcd7fec3 --- /dev/null +++ b/ext/standard/JitObGetLevel.php @@ -0,0 +1,33 @@ + 0) { + throw new \LogicException('ob_get_level() takes no arguments'); + } + $level = $context->builder->call($context->lookupFunction('__phpc_ob_get_level')); + $slot = JitValueBox::alloc($context); + $ptr = JitValueBox::pointer($context, $slot); + $i64 = $context->getTypeFromString('int64'); + $context->builder->call( + $context->lookupFunction('__value__writeLong'), + $ptr, + $context->builder->zExt($level, $i64) + ); + + return $ptr; + } +} diff --git a/ext/standard/JitObStart.php b/ext/standard/JitObStart.php new file mode 100644 index 00000000000..bef9fce6195 --- /dev/null +++ b/ext/standard/JitObStart.php @@ -0,0 +1,24 @@ + 0) { + throw new \LogicException('ob_start() callback arguments not supported in this compiler build'); + } + $context->builder->call($context->lookupFunction('__phpc_ob_start')); + + return $context->getTypeFromString('int32')->constInt(0, false); + } +} diff --git a/ext/standard/debug_backtrace.php b/ext/standard/debug_backtrace.php index 7ff08d5037a..00864453b19 100644 --- a/ext/standard/debug_backtrace.php +++ b/ext/standard/debug_backtrace.php @@ -13,8 +13,7 @@ /** * debug_backtrace() β€” minimal stack trace array (issue #1378). * - * VM: walks Frame parent chain. JIT lowering in {@see JitDebugBacktrace} (deferred until MCJIT - * nested frame hashtables are stable β€” see issue #1751). + * VM: walks Frame parent chain. JIT lowering in {@see JitDebugBacktrace} (call() enablement deferred, #1378, #1056). */ final class debug_backtrace extends Internal { diff --git a/ext/standard/ob_end_flush.php b/ext/standard/ob_end_flush.php index d47ab70addd..f57574dd056 100644 --- a/ext/standard/ob_end_flush.php +++ b/ext/standard/ob_end_flush.php @@ -12,7 +12,7 @@ use PHPLLVM\Value; /** - * ob_end_flush() β€” flush active buffer and end buffering (VM only; issue #118). + * ob_end_flush() β€” flush active buffer and end buffering (VM; JIT scaffold {@see JitObEndFlush}, #118, #1056). */ final class ob_end_flush extends Internal { @@ -41,6 +41,6 @@ public function execute(Frame $frame): void public function call(Context $context, JITVariable ...$args): Value { - throw new \LogicException('ob_end_flush() is not implemented for JIT in this compiler build'); + throw new \LogicException('ob_end_flush() is not implemented for JIT in this compiler build (#118)'); } } diff --git a/ext/standard/ob_get_clean.php b/ext/standard/ob_get_clean.php index 4ecc3ae5cbd..4329931f7f2 100644 --- a/ext/standard/ob_get_clean.php +++ b/ext/standard/ob_get_clean.php @@ -12,7 +12,7 @@ use PHPLLVM\Value; /** - * ob_get_clean() β€” return active buffer and end buffering (VM only; issue #118). + * ob_get_clean() β€” return active buffer and end buffering (VM; JIT scaffold {@see JitObGetClean}, #118, #1056). */ final class ob_get_clean extends Internal { @@ -39,6 +39,6 @@ public function execute(Frame $frame): void public function call(Context $context, JITVariable ...$args): Value { - throw new \LogicException('ob_get_clean() is not implemented for JIT in this compiler build'); + throw new \LogicException('ob_get_clean() is not implemented for JIT in this compiler build (#118)'); } } diff --git a/ext/standard/ob_get_level.php b/ext/standard/ob_get_level.php index 2d27aa8c087..422537a86b6 100644 --- a/ext/standard/ob_get_level.php +++ b/ext/standard/ob_get_level.php @@ -12,7 +12,7 @@ use PHPLLVM\Value; /** - * ob_get_level() β€” active output buffer depth (VM only; issue #118). + * ob_get_level() β€” active output buffer depth (VM; JIT scaffold {@see JitObGetLevel}, #118, #1056). */ final class ob_get_level extends Internal { @@ -34,6 +34,6 @@ public function execute(Frame $frame): void public function call(Context $context, JITVariable ...$args): Value { - throw new \LogicException('ob_get_level() is not implemented for JIT in this compiler build'); + throw new \LogicException('ob_get_level() is not implemented for JIT in this compiler build (#118)'); } } diff --git a/ext/standard/ob_start.php b/ext/standard/ob_start.php index e5cf65a6a8d..57ae9946427 100644 --- a/ext/standard/ob_start.php +++ b/ext/standard/ob_start.php @@ -12,7 +12,7 @@ use PHPLLVM\Value; /** - * ob_start() β€” begin output buffering (VM only; issue #118). + * ob_start() β€” begin output buffering (VM; JIT scaffold {@see JitObStart}, #118, #1056). */ final class ob_start extends Internal { @@ -31,6 +31,6 @@ public function execute(Frame $frame): void public function call(Context $context, JITVariable ...$args): Value { - throw new \LogicException('ob_start() is not implemented for JIT in this compiler build'); + throw new \LogicException('ob_start() is not implemented for JIT in this compiler build (#118)'); } } diff --git a/lib/AOT/Linker.php b/lib/AOT/Linker.php index 5aa84d9bfde..6c0e7609a75 100644 --- a/lib/AOT/Linker.php +++ b/lib/AOT/Linker.php @@ -22,6 +22,8 @@ final class Linker __DIR__.'/runtime/phpc_fs_dir.c', __DIR__.'/runtime/phpc_session_id_storage.c', __DIR__.'/runtime/phpc_session_name_storage.c', + __DIR__.'/runtime/phpc_ob_storage.c', + __DIR__.'/runtime/phpc_ob.c', __DIR__.'/runtime/phpc_parse_url.c', __DIR__.'/runtime/phpc_parse_str.c', __DIR__.'/runtime/phpc_json_decode.c', diff --git a/lib/AOT/runtime/phpc_ob.c b/lib/AOT/runtime/phpc_ob.c new file mode 100644 index 00000000000..a7d8a9e4558 --- /dev/null +++ b/lib/AOT/runtime/phpc_ob.c @@ -0,0 +1,139 @@ +/* + * JIT/AOT output buffering for ob_start / ob_get_clean / ob_end_flush (issue #118, #1056). + */ + +#include +#include + +#define PHPC_OB_MAX_DEPTH 8 +#define PHPC_OB_BUF_SIZE 65536 + +extern int __phpc_ob_level; +extern char __phpc_ob_storage[PHPC_OB_MAX_DEPTH][PHPC_OB_BUF_SIZE]; +extern unsigned long __phpc_ob_len[PHPC_OB_MAX_DEPTH]; + +struct __value__; +struct __string__; + +void __value__writeBool(struct __value__ *out, int value); +void __value__writeString(struct __value__ *out, struct __string__ *str); +struct __string__ *__string__init(long long len, const char *value); + +static int ob_active_index(void) +{ + return __phpc_ob_level > 0 ? __phpc_ob_level - 1 : -1; +} + +static void ob_append_bytes(const char *data, size_t len) +{ + int idx = ob_active_index(); + if (idx < 0) { + if (data && len > 0) { + fwrite(data, 1, len, stdout); + } + return; + } + unsigned long cap = PHPC_OB_BUF_SIZE - 1; + unsigned long pos = __phpc_ob_len[idx]; + if (pos >= cap) { + return; + } + if (len > cap - pos) { + len = (size_t) (cap - pos); + } + memcpy(__phpc_ob_storage[idx] + pos, data, len); + pos += (unsigned long) len; + __phpc_ob_len[idx] = pos; + __phpc_ob_storage[idx][pos] = '\0'; +} + +void __phpc_ob_start(void) +{ + if (__phpc_ob_level >= PHPC_OB_MAX_DEPTH) { + return; + } + __phpc_ob_len[__phpc_ob_level] = 0; + __phpc_ob_storage[__phpc_ob_level][0] = '\0'; + __phpc_ob_level++; +} + +int __phpc_ob_get_level(void) +{ + return __phpc_ob_level; +} + +void __phpc_ob_echo_cstr(const char *s) +{ + if (!s) { + return; + } + ob_append_bytes(s, strlen(s)); +} + +void __phpc_ob_echo_char(char c) +{ + ob_append_bytes(&c, 1); +} + +void __phpc_ob_echo_ll(long long v) +{ + char buf[32]; + int n = snprintf(buf, sizeof buf, "%lld", v); + if (n > 0) { + ob_append_bytes(buf, (size_t) n); + } +} + +void __phpc_ob_echo_double(double v) +{ + char buf[64]; + int n = snprintf(buf, sizeof buf, "%G", v); + if (n > 0) { + ob_append_bytes(buf, (size_t) n); + } +} + +void __phpc_ob_echo_substr(const char *s, unsigned long len) +{ + if (!s) { + return; + } + ob_append_bytes(s, (size_t) len); +} + +int __phpc_ob_get_clean(struct __value__ *out) +{ + if (!out || __phpc_ob_level <= 0) { + if (out) { + __value__writeBool(out, 0); + } + return 0; + } + __phpc_ob_level--; + int idx = __phpc_ob_level; + unsigned long len = __phpc_ob_len[idx]; + __value__writeString(out, __string__init((long long) len, __phpc_ob_storage[idx])); + __phpc_ob_len[idx] = 0; + __phpc_ob_storage[idx][0] = '\0'; + return 1; +} + +int __phpc_ob_end_flush(struct __value__ *out) +{ + if (!out || __phpc_ob_level <= 0) { + if (out) { + __value__writeBool(out, 0); + } + return 0; + } + __phpc_ob_level--; + int idx = __phpc_ob_level; + unsigned long len = __phpc_ob_len[idx]; + if (len > 0) { + ob_append_bytes(__phpc_ob_storage[idx], (size_t) len); + } + __phpc_ob_len[idx] = 0; + __phpc_ob_storage[idx][0] = '\0'; + __value__writeBool(out, 1); + return 1; +} diff --git a/lib/AOT/runtime/phpc_ob_storage.c b/lib/AOT/runtime/phpc_ob_storage.c new file mode 100644 index 00000000000..096d06fa75a --- /dev/null +++ b/lib/AOT/runtime/phpc_ob_storage.c @@ -0,0 +1,12 @@ +/* + * Output-buffer stack for JIT/AOT ob_*() (issue #118, #1056). + * + * LLVM references these globals; this unit provides storage. + */ + +#define PHPC_OB_MAX_DEPTH 8 +#define PHPC_OB_BUF_SIZE 65536 + +int __phpc_ob_level = 0; +char __phpc_ob_storage[PHPC_OB_MAX_DEPTH][PHPC_OB_BUF_SIZE]; +unsigned long __phpc_ob_len[PHPC_OB_MAX_DEPTH]; diff --git a/lib/JIT/Builtin/ObOutput.php b/lib/JIT/Builtin/ObOutput.php new file mode 100644 index 00000000000..9b98a9df995 --- /dev/null +++ b/lib/JIT/Builtin/ObOutput.php @@ -0,0 +1,41 @@ +context->voidType(); + $i8 = $context->getTypeFromString('int8'); + $i32 = $context->getTypeFromString('int32'); + $i64 = $context->getTypeFromString('int64'); + $i8p = $context->getTypeFromString('int8*'); + $sizeT = $context->getTypeFromString('size_t'); + $valuePtr = $context->getTypeFromString('__value__*'); + $doubleTy = $context->getTypeFromString('double'); + + $decls = [ + '__phpc_ob_start' => [$void, false, []], + '__phpc_ob_get_level' => [$i32, false, []], + '__phpc_ob_get_clean' => [$i32, false, [$valuePtr]], + '__phpc_ob_end_flush' => [$i32, false, [$valuePtr]], + '__phpc_ob_echo_cstr' => [$void, false, [$i8p]], + '__phpc_ob_echo_char' => [$void, false, [$i8]], + '__phpc_ob_echo_ll' => [$void, false, [$i64]], + '__phpc_ob_echo_double' => [$void, false, [$doubleTy]], + '__phpc_ob_echo_substr' => [$void, false, [$i8p, $sizeT]], + ]; + + foreach ($decls as $name => [$ret, $vararg, $params]) { + $ft = $context->context->functionType($ret, $vararg, ...$params); + $fn = $context->module->addFunction($name, $ft); + $context->registerFunction($name, $fn); + } + } +} diff --git a/lib/JIT/Builtin/Type.php b/lib/JIT/Builtin/Type.php index c0329d8aea7..c69ea04ce54 100755 --- a/lib/JIT/Builtin/Type.php +++ b/lib/JIT/Builtin/Type.php @@ -104,6 +104,7 @@ public function register(): void { HttpResponseCode::implement($this->context); SessionId::implement($this->context); SessionName::implement($this->context); + ObOutput::registerExternals($this->context); CallArgv::implement($this->context); $i8p = $this->context->getTypeFromString('int8*'); $i32 = $this->context->getTypeFromString('int32'); diff --git a/lib/JIT/SelfHostBuiltinPolicy.php b/lib/JIT/SelfHostBuiltinPolicy.php index a09db3539e7..db70d1d55b6 100644 --- a/lib/JIT/SelfHostBuiltinPolicy.php +++ b/lib/JIT/SelfHostBuiltinPolicy.php @@ -40,11 +40,11 @@ final class SelfHostBuiltinPolicy /** @var array */ private const VM_ONLY_DEFERRED = [ 'debug_backtrace' => 'debug', + 'ob_start' => 'output', 'ob_get_clean' => 'output', 'ob_end_flush' => 'output', + 'ob_get_level' => 'output', 'spl_autoload_register' => 'spl', 'set_error_handler' => 'error', 'restore_error_handler' => 'error', - 'ob_start' => 'output', 'ob_get_clean' => 'output', 'ob_end_flush' => 'output', - 'ob_get_level' => 'output', 'password_hash' => 'password', 'password_verify' => 'password', 'session_start' => 'session', 'session_destroy' => 'session', 'session_write_close' => 'session', diff --git a/test/selfhost/compiler_lib_spine_smoke/main.php b/test/selfhost/compiler_lib_spine_smoke/main.php index c505ba8cb5e..2f0692bd950 100644 --- a/test/selfhost/compiler_lib_spine_smoke/main.php +++ b/test/selfhost/compiler_lib_spine_smoke/main.php @@ -190,6 +190,10 @@ require_once __DIR__.'/../../../ext/standard/JitCrc32.php'; require_once __DIR__.'/../../../ext/standard/JitDate.php'; require_once __DIR__.'/../../../ext/standard/JitDebugBacktrace.php'; +require_once __DIR__.'/../../../ext/standard/JitObStart.php'; +require_once __DIR__.'/../../../ext/standard/JitObGetLevel.php'; +require_once __DIR__.'/../../../ext/standard/JitObGetClean.php'; +require_once __DIR__.'/../../../ext/standard/JitObEndFlush.php'; require_once __DIR__.'/../../../ext/standard/JitDeployPath.php'; require_once __DIR__.'/../../../ext/standard/JitEnumExists.php'; require_once __DIR__.'/../../../ext/standard/JitEnv.php'; diff --git a/test/unit/BootstrapSelfhostBundleTest.php b/test/unit/BootstrapSelfhostBundleTest.php index a4625eeb3b8..f6d7d3ff8c5 100644 --- a/test/unit/BootstrapSelfhostBundleTest.php +++ b/test/unit/BootstrapSelfhostBundleTest.php @@ -371,7 +371,7 @@ public function testCompilerLibSpineSmokeBundleUnitCountAndKeyUnits(): void $this->assertFileExists($entry); $contents = (string) file_get_contents($entry); $count = substr_count($contents, 'require_once __DIR__'); - $this->assertSame(584, $count, '108 compiler_minimal overlap + 476 M2 spine units; bin/vm.php + src/cli.php deferred (#1423, #1467)'); + $this->assertSame(589, $count, '108 compiler_minimal overlap + 481 M2 spine units; bin/vm.php + src/cli.php deferred (#1423, #1467)'); foreach (self::LIB_SPINE_SMOKE_NEW_UNITS as $unit) { $this->assertStringContainsString( "require_once __DIR__.'/../../../{$unit}';",