Skip to content

AOT: strlen() and other builtins fold on a stale compileTimeString literal — $s = ""; for (...) $s .= "x"; strlen($s) returns 1 (silent wrong output, 888 consumer sites) (lib/JIT/JitStringArg.php compileTimeLiteral, lib/JIT/Variable.php $compileTimeString, ext/types/JitStrlen.php) #36244

Description

@PurHur

Category

Regression: · silent wrong output (AGENTS.md §3 class) · child of #36188

Problem

Verified on master 4eed6a2785 in the pinned image (build/audit/v_dot1.php):

program Zend AOT
$s = ""; for ($i=0;$i<5;$i++) { $s .= "x"; } echo $s, "|", strlen($s); xxxxx|5 xxxxx|1
$t = ""; $t .= "ab"; $t .= "cd"; echo $t, "|", strlen($t); abcd|4 abcd|0
$u = "a"; for (...) { $u = $u . "x"; } echo $u, "|", strlen($u); axxxxx|6 axxxxx|2

The bytes are right (echo prints the full string) and the length is wrong: JitStrlen::lowerLength() (ext/types/JitStrlen.php:41-51) asks JitStringArg::compileTimeLiteral($arg) and, when it returns a string, emits constInt(strlen($literal)). compileTimeLiteral() (lib/JIT/JitStringArg.php) just returns $arg->compileTimeString. That annotation is stamped when a variable is initialised from a literal and is propagated across phis from one incoming arm ($phiVar->compileTimeString = $newVal->compileTimeString, lib/JIT.php:10902; also :10971, :11133, :11164, :11183) and cleared in only 12 places — so after a loop back-edge, a .=, or a re-assignment the variable still carries its first literal. strlen is one consumer; grep -rn compileTimeString lib/JIT.php lib/JIT/*.php ext finds 888 references in 353 files (json_encode, property-name lowering, str_* folds, include paths, class names…), each a potential wrong fold.

Consequence beyond the wrong number: the guard if (strlen($s) > 1000) { $s = substr($s, 500); } never fires, so a 300k-iteration append loop that Zend runs in 40 ms was killed at the 12 GB container cap under AOT during the audit (build/audit/run.log, micro m_string). This is very likely the same mechanism behind several "returns wrong value after loop" issues the fleet has been fixing one builtin at a time.

php-src reference

  • Zend folds strlen() only on a literal argument at compile time (zend_compile.c zend_compile_func_strlen: if (arg is ZEND_AST_ZVAL string)); a CV is never folded.

PHP implementation target

  • Make compileTimeString sound: it may only survive on KIND_VALUE temporaries produced directly from a literal (or a fold of literals) in the same basic block. Clear it on every assignment to a named variable, on every phi (unless all incoming arms carry the identical literal), on .=/concat, on by-ref binding, and on passing to a by-ref parameter. Best done centrally in Variable (setter that requires the producing Op to be a literal/constant) rather than at 888 sites.
  • Add an opcode-corpus and differential guard: the three probes above plus loop/branch/phi shapes for strlen, json_encode, str_repeat, substr, dynamic property names, include $path.
  • Audit the 12 explicit clears for the same shape; remove them once the setter enforces the rule.

Repro

./script/docker-exec.sh -- bash -lc 'source script/php-env.sh && printf "<?php \$s=\"\"; for(\$i=0;\$i<5;\$i++){ \$s.=\"x\"; } echo \$s,\"|\",strlen(\$s),\"\\n\";" > build/sl.php && php bin/compile.php -o build/sl build/sl.php && ./build/sl && php build/sl.php'

Done when

  • The three probes match Zend under AOT and are in test/differential/cases/; the m_string micro (300k appends with a strlen guard) completes in < 2 s
  • compileTimeString can only be set from a literal-producing op (enforced by a setter + unit test); grep -c "compileTimeString = \$" lib/JIT.php propagation-through-phi sites removed
  • script/differential-sweep.sh --aot --repeat 3 unchanged; compliance name-set unchanged

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

    MOST IMPORTANTThis are the most important targetsarea:compilerCompiler / CFG / JITbugSomething isn't workingimplementation-readySpec complete: repro, php-src ref, done-when — safe for workers to claimphase-2:languagePhase 2 – language features

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions