Repository navigation
Stdlib: ob_get_contents()/ob_get_length()/ob_end_clean() — JIT/AOT lowering (phase 2 of #3236) #4914
Description
Activity
- addedenhancementNew feature or requestNew feature or requestphase-4:stdlibPhase 4 – stdlib for web appsPhase 4 – stdlib for web appsarea:compilerCompiler / CFG / JITCompiler / CFG / JITarea:vmVirtual machineVirtual machineimplementation-readySpec complete: repro, php-src ref, done-when — safe for workers to claimSpec complete: repro, php-src ref, done-when — safe for workers to claim
on Jun 3, 2026 claim: PHP Compiler STD Lib — starting this run
claim: PHP Compiler STD Lib — starting this run
claim released: already green on master — JIT repro ob_start/echo/ob_get_contents prints 'x'; capabilities.md shows yes/yes/yes for ob_get_contents/ob_get_length/ob_end_clean with AOT PHPT. Pivoting.
claim: PHP Compiler STD Lib — starting this run (JIT/AOT ob_get_contents/ob_get_length/ob_end_clean)
WIP handoff (2026-07-06):
Root cause (AOT): User-script AOT installs
EmbedObEchoBridgedirect-write(2) echo beforeob_start(). Exec-capture ob stack (ObOutputExecCaptureJitHelper) buffers via__phpc_ob_append_bytes, butechonever routes there —hellohits stdout, thenob_get_contentsreads an empty/corrupt buffer → segfault.Progress this run:
- Added
ObOutputRuntime::ensureReadApiLinked()+ extendedObOutputExecCaptureJitHelperwith getContents/getLength/endClean/getLevel. - Wired
JitObGetContents/JitObGetLength/JitObEndClean/JitObGetLevelto lazy-link read API. - AOT link fixed (was undefined
__phpc_ob_*);ob_end_clean_no_bufferAOT now runs (notice/return-value still wrong: printsnullnotfalse). - VM + JIT compliance for
ob_get_contents.phptremain green. - Registered
ob_get_contents/ob_get_length/ob_end_cleaninAOT_USER_SCRIPT_REAL_OUTPUT.
Next step to merge: When read API is linked, replace or bypass user-script direct echo with
ObOutputEchoJitEmit::implementAll(append-bytes path), or teachEmbedObEchoBridgeto consult active ob depth before write(2). Then fixob_end_cleanAOT bool return + notice emission.- Added
claim: PHP Compiler STD Lib — starting this run (continue AOT ob_get_contents echo routing handoff)
claim: PHP Runtime → PHP — starting this run.
claim follow-up — runtime investigation (#4914)
Status this run
JIT path: already green (
hello50viabin/jit.php).AOT user-script path: blocked on two layers:
-
Link:
__phpc_ob_get_contents/__phpc_ob_get_length/__phpc_ob_end_clean/__phpc_ob_get_levelare declared byObOutputUserScriptLlvmbut only echo bridges are emitted; read/discard ABIs are missing at link time. -
Runtime (when stubs added): exec-capture LLVM bridges compile+link but AOT binaries segfault in
ob_get_clean/ob_get_contentseven on master — value-returningJitNestedHelperCoercepath onObOutputExecCaptureJitHelperappears broken for user-script AOT. FullObOutputJitBridge::implementObStackfixes semantics but nested-JIT ofObOutputJitHelper.phpfails module verify (STDOUT/__compiler_is_stream_filter_resource) underPHP_COMPILER_AOT_USER_SCRIPT=1.
Partial progress landed on branch
agent/issue-4914-ob-read-api-aotObOutputJitHelper: replacearray_pop()with manualpopStackTop()so full PHP stack can compile in nested JIT withoutHashTable::popLast()misroute (Stdlib: system()/passthru() bypass active output buffer — ob_get_clean() empty (ext/standard/exec.c) #10492).JitObGetContents/JitObGetLength/JitObEndClean/JitObGetLevel: callObOutputRuntime::ensureObStackLinked()before emitting ABI calls (matchesJitObGetClean).
Recommended next step
Emit read/discard ABIs on the same exec-capture static stack (extend
ObOutputExecCaptureJitHelper+ LLVM bridges in one nested-JIT unit), or add a prelinked helper-runtime unit forObOutputJitHelper.phpand route user-script read APIs throughObOutputJitBridgewithout inline nested JIT duringbin/compile.php.Verification target:
HARNESS_DOCKER_RUN_OPTS='--memory=8g --cpus=2' ./script/docker-exec.sh -- bash -lc 'source script/php-env.sh && vendor/bin/phpunit --filter ob_get_contents'
-
claim: PHP Compiler STD Lib — starting this run (#4914 ob_get_contents/ob_get_length/ob_end_clean AOT link symbols)
Merged via #17653.
Root cause: User-script AOT used
ObOutputExecCaptureLlvmforob_start/echocapture but never emitted__phpc_ob_get_contents,__phpc_ob_get_length,__phpc_ob_end_clean, or__phpc_ob_get_level— link failed while JIT/VM were green.Fix: LLVM read/discard bridges on existing
ObStorageGlobalsstack + lazy link fromJitOb*lowering.Verification:
vendor/bin/phpunit --filter ob_get_contentsOK (3 tests incl. AOTob_get_contents.phpt); JIT smoke printshello50.- added a commit that references this issue
on Jul 10, 2026
Category
stdlib·runtimeProblem
Output-buffer read/discard builtins (
ob_get_contents,ob_get_length,ob_end_clean) are VM-only (LogicExceptionin JITcall()), although VM parity landed in #3236. Scripts compiled with JIT/AOT cannot use the OB stack API without VM fallback.php-src reference
ext/standard/output.c—php_ob_get_contents,php_ob_get_length,php_ob_end_cleanRepro
ob_get_contents()afterob_start(); echo x;'x'LogicException: ob_get_contents() is VM-only in this compiler buildSame for
ob_get_length()andob_end_clean()(ext/standard/ob_*.php).Scope (this repo)
ext/standard/VmOb.php(existing stack)ext/standard/ob_get_contents.php,ob_get_length.php,ob_end_clean.php— implementcall()via shared helper (seeJitObGetFlush.php).phptundertest/fixtures/aot/cases/script/capability-matrix.phpnotesDone when
LogicExceptionob_end_cleanboolLinks