Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions bin/lint.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
require __DIR__.'/../vendor/autoload.php';
require __DIR__.'/../script/bootstrap-lib.php';

// Fast frontend paths (use-chain Simplifier, worklist type resolver) change
// internal visit/resolution ORDER, which is observable in AOT codegen but not
// in lint issues — proven output-identical across the full 4037-file
// inventory. Opt in for lint only; compiles keep the legacy paths (#16077).
// PHP_COMPILER_LINT_FRONTEND_FAST=0 opts out (workers inherit via putenv).
// Fast frontend paths: use-chain Simplifier is now the compile default (#23056);
// still force it here so lint stays fast even if PHPCFG_SIMPLIFIER_LEGACY=1 is
// set in the environment. Worklist type resolver remains lint-only (#16077) —
// resolution ORDER can affect AOT. PHP_COMPILER_LINT_FRONTEND_FAST=0 opts out.
if ('0' !== getenv('PHP_COMPILER_LINT_FRONTEND_FAST')) {
putenv('PHPCFG_SIMPLIFIER_USECHAIN=1');
putenv('PHPCFG_SIMPLIFIER_LEGACY');
putenv('PHPTYPES_RESOLVER_WORKLIST=1');
}

Expand Down
26 changes: 15 additions & 11 deletions patches/php-cfg-simplifier-use-chain.patch
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
--- vendor/ircmaxell/php-cfg/lib/PHPCfg/Visitor/Simplifier.php
+++ vendor/ircmaxell/php-cfg/lib/PHPCfg/Visitor/Simplifier.php
@@ -206,6 +206,42 @@
@@ -205,6 +205,46 @@
}

private function replaceVariables(Operand $from, Operand $to, Block $block)
{
+ if ('1' !== getenv('PHPCFG_SIMPLIFIER_USECHAIN')) {
+ // Default: legacy CFG walk. The use-chain path changes replacement
+ // ORDER, which is observable in downstream codegen (phi operand
+ // types) — safe and proven only for lint workloads, which opt in
+ // via bin/lint.php (#16077).
+ {
+ // Use-chain is the default (#23056): O(uses) instead of O(phis×blocks).
+ // Opcode dumps match legacy on a 193-file corpus; CFG type pretty-print
+ // can still differ. Opt out with PHPCFG_SIMPLIFIER_USECHAIN=0 or
+ // PHPCFG_SIMPLIFIER_LEGACY=1 for bisect (#16077).
+ $usechain = getenv('PHPCFG_SIMPLIFIER_USECHAIN');
+ $legacy = ('0' === $usechain)
+ || ('1' === getenv('PHPCFG_SIMPLIFIER_LEGACY'))
+ || ('false' === strtolower((string) $usechain));
+ if ($legacy) {
+ $this->replaceVariablesByCfgWalk($from, $to, $block);
+
+ return;
+ }
+ // Use-chain replacement: visit only the ops that actually reference
+ // $from (Operand tracks usages/write-ops) instead of re-walking the
+ // whole CFG per removed phi. The CFG walk was O(phis × blocks) and
+ // took 121 s on a 32k-line file; this is O(uses) (#16077 root cause).
+ // took 121 s on a 32k-line file; this is O(uses) (#16077 / #23056).
+ $ops = array_merge($from->usages, $from->ops);
+ foreach ($ops as $op) {
+ if ($op instanceof Op\Phi) {
Expand All @@ -37,9 +42,8 @@
+ $from->ops = [];
+ }
+
+ /** Legacy whole-CFG replacement, selectable via PHPCFG_SIMPLIFIER_LEGACY=1. */
+ /** Legacy whole-CFG replacement (PHPCFG_SIMPLIFIER_LEGACY=1 or USECHAIN=0). */
+ private function replaceVariablesByCfgWalk(Operand $from, Operand $to, Block $block)
+ {
{
$toReplace = new \SplObjectStorage();
$replaced = new \SplObjectStorage();
$toReplace->attach($block);
3 changes: 3 additions & 0 deletions script/bootstrap-gen0-refresh-exclusive-docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ docker run -d \
# HELPER_RUNTIME_O=1 with stale core fingerprint → ~1 file/min). Opt in with
# BOOTSTRAP_GEN0_HELPER_RUNTIME_O=1 after warming script/emit-helper-runtime-object.php.
export PHP_COMPILER_HELPER_RUNTIME_O=\${BOOTSTRAP_GEN0_HELPER_RUNTIME_O:-0}
# Use-chain Simplifier default (#23056) — ~3× less CPU on full-spine parseAndCompile.
export PHPCFG_SIMPLIFIER_USECHAIN=\${PHPCFG_SIMPLIFIER_USECHAIN:-1}
unset PHPCFG_SIMPLIFIER_LEGACY
# Flat spine requires: keep shared include slots (pre-#22845 pace). MiniWebApp keeps
# default remapping when this env is unset (#22642 r14 vs r13).
export PHP_COMPILER_INCLUDE_SCOPE_REMAP=\${BOOTSTRAP_GEN0_INCLUDE_SCOPE_REMAP:-0}
Expand Down
4 changes: 4 additions & 0 deletions script/bootstrap-refresh-gen0-sidecar.sh
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ if [[ "${SKIP_LINK}" -eq 0 ]]; then
# Match exclusive launcher defaults: flat include slots + no cold helper NestedJIT (#22642).
export PHP_COMPILER_INCLUDE_SCOPE_REMAP="${PHP_COMPILER_INCLUDE_SCOPE_REMAP:-0}"
export PHP_COMPILER_HELPER_RUNTIME_O="${PHP_COMPILER_HELPER_RUNTIME_O:-0}"
# Use-chain Simplifier is compile default (#23056); clear LEGACY so a bisect env cannot
# silently re-enable the O(phis×blocks) CFG walk on the multi-hour Zend spine.
export PHPCFG_SIMPLIFIER_USECHAIN="${PHPCFG_SIMPLIFIER_USECHAIN:-1}"
unset PHPCFG_SIMPLIFIER_LEGACY
mkdir -p "${ROOT}/build"
rm -f "${SPINE_OUT}" "${LIB_BLOB}"
if ! bootstrap_compiler_lib_honest_zend_compile "${SPINE_OUT}" "${SPINE_ENTRY}" full; then
Expand Down
92 changes: 92 additions & 0 deletions test/unit/SimplifierUseChainOpcodeEquivalenceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

declare(strict_types=1);

namespace PHPCompiler;

use PHPCompiler\Printer as OpCodePrinter;
use PHPUnit\Framework\TestCase;

/**
* Default PHPCFG Simplifier use-chain path must lower to the same opcodes as legacy (#23056).
*
* CFG pretty-print / inferred type labels can still differ (replacement ORDER); the compile
* spine cares about opcode identity. Opt out remains PHPCFG_SIMPLIFIER_USECHAIN=0 /
* PHPCFG_SIMPLIFIER_LEGACY=1.
*/
final class SimplifierUseChainOpcodeEquivalenceTest extends TestCase
{
protected function tearDown(): void
{
putenv('PHPCFG_SIMPLIFIER_USECHAIN');
putenv('PHPCFG_SIMPLIFIER_LEGACY');
}

/** @return list<array{0: string}> */
public static function corpusProvider(): array
{
$root = \dirname(__DIR__, 2);
$rels = [
'examples/000-HelloWorld/example.php',
'lib/Printer.php',
'ext/standard/Module.php',
'bin/print.php',
'bin/vm.php',
];
$out = [];
foreach ($rels as $rel) {
$abs = $root.'/'.$rel;
if (is_readable($abs)) {
$out[$rel] = [$abs];
}
}

return $out;
}

private function opcodes(string $file, bool $legacy): string
{
if ($legacy) {
putenv('PHPCFG_SIMPLIFIER_USECHAIN=0');
putenv('PHPCFG_SIMPLIFIER_LEGACY=1');
} else {
putenv('PHPCFG_SIMPLIFIER_USECHAIN');
putenv('PHPCFG_SIMPLIFIER_LEGACY');
}
$runtime = new Runtime();
$code = (string) file_get_contents($file);
$block = $runtime->compile($runtime->parse($code, $file));

return (new OpCodePrinter())->print($block);
}

/**
* @dataProvider corpusProvider
*/
public function testDefaultUseChainMatchesLegacyOpcodes(string $file): void
{
$legacy = $this->opcodes($file, true);
$usechain = $this->opcodes($file, false);
$this->assertSame(
$legacy,
$usechain,
basename($file).': use-chain opcodes diverged from legacy CFG walk'
);
}

public function testLegacyEnvForcesCfgWalkPath(): void
{
// Smoke: both opt-out knobs are accepted without throwing on a tiny script.
$root = \dirname(__DIR__, 2);
$file = $root.'/examples/000-HelloWorld/example.php';
putenv('PHPCFG_SIMPLIFIER_USECHAIN=0');
$a = $this->opcodes($file, true);
putenv('PHPCFG_SIMPLIFIER_USECHAIN');
putenv('PHPCFG_SIMPLIFIER_LEGACY=1');
$runtime = new Runtime();
$b = (new OpCodePrinter())->print(
$runtime->compile($runtime->parse((string) file_get_contents($file), $file))
);
$this->assertSame($a, $b);
}
}
Loading