Skip to content

AOT: default-on helper-runtime TU cache for user phpc build (#15889) - #16948

Merged
PurHur merged 1 commit into
masterfrom
agent/issue-15889-runtime-tu-cache
Jul 6, 2026
Merged

PurHur merged 1 commit into
masterfrom
agent/issue-15889-runtime-tu-cache

Conversation

@PurHur

@PurHur PurHur commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Closes #15889

Summary

  • Default-on split helper-unit compilation for user-script phpc build (PHP_COMPILER_HELPER_RUNTIME_O=1 when unset).
  • Warm build/helper-runtime-cache/ once per core fingerprint before lowering the user script.
  • Log phpc build: helper-runtime cache hit (...) on stderr when cached helpers bind.
  • Remove stray var_dump($type->getKind()) in JIT\Context::getStringFromType() (was spamming int(10) during AOT builds).
  • Refresh examples/README.md benchmark table (bin/compile.php column ~2.8–3.6s → ~0.99–1.23s).

C runtime

No new C — reuses existing HelperRuntimeCache + Linker merge path. PHP-in-PHP helpers stay in lib//ext/; C remains thin ABI (lib/AOT/runtime/phpc_progress.c).

Verification

# Unit: fingerprint invalidates on LLVM path change
./script/docker-exec.sh -- bash -lc './vendor/bin/phpunit --filter HelperRuntimeCacheFingerprintTest'
# OK (1 test, 1 assertion)

# User AOT: second script reuses warmed helper cache (stderr log)
./script/docker-exec.sh -- bash -lc 'rm -rf build/helper-runtime-cache && ./phpc build -o /tmp/hello examples/000-HelloWorld/example.php 2>&1 | tee /tmp/b1.log; ./phpc build -o /tmp/api examples/004-ApiJson/example.php 2>&1 | tee /tmp/b2.log; grep "helper-runtime cache hit" /tmp/b1.log /tmp/b2.log'
# /tmp/b1.log:1:phpc build: helper-runtime cache hit (3 helpers, core=...) (#15889)
# /tmp/b2.log:1:phpc build: helper-runtime cache hit (3 helpers, core=...) (#15889)

# Examples benchmark regen (bin/compile.php column drop)
./script/docker-exec.sh -- bash -lc 'MINIWEBAPP_LINT_GATE=1 php script/rebuild-examples.php'

Opt-out: PHP_COMPILER_HELPER_RUNTIME_O=0.

Made with Cursor

Enable split helper-unit compilation for user-script AOT builds: warm the
per-unit cache once per core fingerprint, bind cached helpers via
JitVmHelperLink, and log cache hits on stderr. Remove stray var_dump in
JIT Context type resolution. Refresh examples benchmark table (~3s → ~1s
bin/compile.php column).

Co-authored-by: Cursor <cursoragent@cursor.com>
@PurHur
PurHur merged commit 8c6cc73 into master Jul 6, 2026
@PurHur
PurHur deleted the agent/issue-15889-runtime-tu-cache branch July 6, 2026 15:02
PurHur added a commit that referenced this pull request Jul 6, 2026
…helper cache (#16075 step 4) (#16992)

* AOT: localize cached-unit declaration types + call-site-lazy sprintf link; document workflows in skills (#15642, #15889).

Cache-machinery fixes (each independently load-bearing):

- HelperRuntimeCache::tryProvide rebuilds extern declaration types
  against the LOCAL context's named structs. LLVM re-suffixes named
  structs on bitcode parse (__string__ -> __string__.12) and suffixed
  declaration types fail module verify at every call site — a latent
  correctness bug for ANY consumer whose module defines the structs
  before binding (getStringFromType cannot stringify suffixed structs,
  so the fallback read struct names via LLVMGetStructName).
- StringFormat gains implementIfDeclared(): user-standalone builds
  skip ensureLinked at init (#13571), so printf()/sprintf()/
  number_format() died at link with undefined __compiler_sprintf
  (#15642). JitSprintf::format now links on first call-site lowering
  (builder insert-point saved/restored), and ensureJitHelperCompiled
  consults the helper cache first — with PHP_COMPILER_HELPER_RUNTIME_O=1
  the corpus binds as externs instead of a nested compile that OOMs a
  default-memory build. Remaining gap: cached units lack their init
  chains, so the sprintf unit crashes at runtime in user builds until
  per-unit llvm.global_ctors lands (epic #16075 step 4/5) — the link
  error is fixed, the runtime story is documented on #15642.
- PackArgvSerialize memcpy calls adapt to WHATEVER signature the module
  already declares (main-script i8* vs runtime void* ordering flip).

Spine sync to 4181/4182 (3 new fleet DOM files) + sidecar refresh
(stamp d8ba2365). M5 fast green, AOT smoke green cache-on and -off,
full unit sweep 119 fresh / 0 failed.

Skills: phpc-verify gains the LLVM-assert debugging recipe, battery
babysitter, and gate-order notes; phpc-fix-loop gains merge-race
protocol, worktree-docker and vendor-patch guidance; phpc-selfhost
gains spine-sync flags, dedup check, and the deferral workflow; new
phpc-helper-cache skill documents the split-compilation cache
end-to-end; phpc-setup documents the committed cold-start caches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Merge follow-up: inventory regen.

* AOT: per-unit init chains + runtime-safety guards for the default-on helper cache (#16075 step 4, #15889, #15642).

The fleet flipped the helper cache default-on (#16948) while two
latent hazards were live; this lands the missing infrastructure and
contains the known-unsafe path:

- Unit emission suffixes __init__/__shutdown__/__header_pre_flush__
  per unit (PHP_COMPILER_INIT_SYMBOL_SUFFIX, recorded in the unit
  manifest). The colliding __init__ was -z muldefs-discarded and unit
  module state NEVER INITIALIZED — the root of the cached-unit runtime
  crash class. The consuming script's __init__ now calls each used
  unit's init explicitly (emitInInit at bind time).
- Unit __shutdown__ is deliberately NOT wired: muldefs unifies part of
  the unit's globals with the script's, and running both shutdowns
  double-frees (SIGABRT at exit — caught by the AOT smoke).
- Consumers skip units without an init symbol (pre-init-era manifests,
  including all previously committed prelinked units) and units marked
  runtime_safe=false. SprintfJitHelper is blocklisted: it constructs
  VM objects with class ids baked from the emitter process's registry,
  and the consuming script numbers classes differently — segfault in
  variablesFromPackedArgv (gdb data on #15642). Class-id unification
  is #16075 step 5.
- JitSprintf's call-site StringFormat link (from #16965) is gated OFF
  during unit emission: the emitter guards the cache, so the hook was
  dragging a full nested corpus compile into every sprintf-adjacent
  unit (user builds spun ~10 min in auto-warm; Opendir unit hit it
  hardest).
- prelinked/helper-runtime republished: 121 units with init symbols
  (34.8 MB).

Verified: AOT smoke green (was SIGABRT 134 with shutdown wiring / spin
before the gate), scalar+strlen differential AOT == VM with cache
default-on, M5 fast green, sprintf unit emits in seconds again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: PurHur <tedyyyyy@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AOT: split user-script builds into cached runtime TU + script TU — ~50% of every phpc build re-lowers identical JitHelpers (lib/Runtime.php)

1 participant