Skip to content

Regression: phpc build fails module verification for every user script — 4 lowering defects, 266/266 helper units degraded (lib/JIT/Builtin/StringOffsetRuntime.php) #22638

Description

@PurHur

Category

Regression: · AOT/JIT lowering · release blocker

Problem

On pristine master (e1850c9) in php-compiler:22.04-dev, ./phpc build fails module verification for any user script — including the 5-line tier-1 fixture that check-aot-build-smoke.sh (#16010) uses. ./script/ci-fast.sh is therefore red at its first gate.

The same four defects also break the split-compilation helper-object cache: 266 of 266 helper units fail to emit, every one silently degraded to the "nested-lowering fallback (#15642)". Because every JIT context builds the htmlspecialchars helper, and that helper indexes a string, one bad zext poisoned every module in the tree.

Repro Zend 8.2 AOT on master (2026-07-23)
$w = "AOT"; echo $w, "\n", strlen($w), "\n"; AOT / 3 RuntimeException: Module verification failed
php script/emit-helper-runtime-object.php — 0 emitted, 266 failed

Four independent defects, all reported by one Module->verify():

  1. ZExt only operates on integer — lib/JIT/Builtin/StringOffsetRuntime.php::normalizeOffset blind-zexts its operands. A box-backed offset arrives as a %__value__ struct, so it emits zext %__value__ ... to i64. 221 of 266 unit failures.
  2. Call parameter type does not match function signature! — lib/JIT/JitNestedHelperCoerce.php::coerceArgForHelper always returns a pointer when boxing, but isValueBoxType() also matches a by-value __value__ parameter, so call ...(%__value__* %slot) hits a by-value param.
  3. Incorrect number of arguments passed to called function! — JIT::instanceMethodUsesThis() falls back to context->scope->className (Epic: architecture rearrangement — TU-based pipeline, differential gates, generated maintenance (docs/architecture-review-2026-07.md) #16075). For a file whose class is compiled before its top level, {main} inherits the leftover class name and is given an %__object__* parameter; standalone main then emits call void @internal_N() with no arguments.
  4. Both operands to ICmp instruction are not of the same type! — strcmp is declared i32 in lib/JIT/LibcExtern.php, but four sites compare its result against an i64 zero (ReflectionFunctionVariadicLookupRuntime.php, ParamSensitiveLookupRuntime.php ×3).

Why it went unobserved

Three layers of false green stacked up:

Repro

./script/docker-exec.sh -- bash -lc 'printf "<?php\n\$w = \"AOT\";\necho \$w, \"\\n\", strlen(\$w), \"\\n\";\n" > /tmp/t1.php && ./phpc build -o /tmp/t1 /tmp/t1.php'
./script/docker-exec.sh -- bash -lc 'php script/emit-helper-runtime-object.php'

Fast single-unit repro (~10 s), with the operand asserts that locate each defect in one shot:

./script/docker-exec.sh -- bash -lc 'PHP_COMPILER_LLVM_ASSERT=1 php script/emit-helper-runtime-object.php --unit=/lib/VM/CompareJitHelperScalars.php'

Done when

  • ./phpc build + execute green on the tier-1 fixture; output matches VM
  • ./script/ci-fast.sh green at the AOT build smoke gate
  • php script/emit-helper-runtime-object.php emits the large majority of units instead of 0
  • Child stderr is captured, so a unit failure names its cause
  • A gate fails when zero units emit (the blind spot above)
  • PHP_COMPILER_LLVM_ASSERT=1 also validates iCmp operand types

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    IMPORTANTCritical patharea:compilerCompiler / CFG / JITbugSomething isn't workingimplementation-readySpec complete: repro, php-src ref, done-when — safe for workers to claimphase-0:FoundationPhase 0 – foundation & DevExreleaseUser-facing release trackingrelease-blockerBlocks tagged user release

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions