编译加速

speed_build
yangweijie 3 weeks ago
parent e0581f8ccc
commit eb523afbf0
  1. 211
      src/Translator.php

@ -1146,11 +1146,8 @@ CODE;
if (!is_dir($dir)) {
continue;
}
$iterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($dir, \FilesystemIterator::SKIP_DOTS)
);
foreach ($iterator as $file) {
if ($file->getExtension() === 'h' && $file->getMTime() > $objectMtime) {
foreach ($this->collectHeaderFiles($dir) as $header) {
if (filemtime($header) > $objectMtime) {
return false;
}
}
@ -1159,6 +1156,37 @@ CODE;
return true;
}
/**
* Recursively collect every .h header under $dir using scandir (no SPL
* iterators). Avoids AOT-miscompiled RecursiveDirectoryIterator.
*
* @return list<string>
*/
protected function collectHeaderFiles(string $dir): array
{
$result = [];
if (!is_dir($dir)) {
return $result;
}
$stack = [$dir];
while (!empty($stack)) {
$current = array_pop($stack);
$entries = scandir($current);
foreach ($entries as $name) {
if ($name === '.' || $name === '..') {
continue;
}
$path = $current . '/' . $name;
if (is_dir($path)) {
$stack[] = $path;
} elseif (str_ends_with($name, '.h')) {
$result[] = $path;
}
}
}
return $result;
}
protected function getMiscObjectCacheMetadataFile(string $objectFile): string
{
return $objectFile . '.typephp-cache';
@ -1257,11 +1285,15 @@ CODE;
$includeDir . '/php_' . $this->targetName . '_func_decl.h',
$includeDir . '/php_' . $this->targetName . '_data_decl.h',
];
$prefix = 'php_' . $this->targetName . '_';
if (is_dir($includeDir)) {
$iterator = new \FilesystemIterator($includeDir, \FilesystemIterator::SKIP_DOTS);
foreach ($iterator as $entry) {
if ($entry->isFile() && str_ends_with($entry->getFilename(), '_arginfo.h')) {
$headers[] = $entry->getPathname();
$entries = scandir($includeDir);
foreach ($entries as $name) {
if ($name === '.' || $name === '..') {
continue;
}
if (str_starts_with($name, $prefix) && str_ends_with($name, '_arginfo.h')) {
$headers[] = $includeDir . '/' . $name;
}
}
}
@ -1271,24 +1303,21 @@ CODE;
protected function getGeneratedObjectCacheKey(string $sourceFile, string $objectFile): string
{
$abi = [
$parts = [];
$parts[] = $this->buildCompileFileCommand($sourceFile, $objectFile);
$parts[] = serialize([
'php_version_id' => PHP_VERSION_ID,
'php_api_version' => defined('PHP_API_VERSION') ? constant('PHP_API_VERSION') : null,
'zend_module_api' => defined('ZEND_MODULE_API_NO') ? constant('ZEND_MODULE_API_NO') : null,
'php_zts' => defined('PHP_ZTS') ? PHP_ZTS : null,
'php_debug' => defined('PHP_DEBUG') ? PHP_DEBUG : null,
'integer_size' => PHP_INT_SIZE,
];
$context = hash_init('sha256');
hash_update($context, $this->buildCompileFileCommand($sourceFile, $objectFile) . "\0" . serialize($abi));
]);
foreach ($this->getGeneratedHeaderDependencies() as $header) {
hash_update($context, "\0" . $header . "\0");
if (is_file($header)) {
hash_update_file($context, $header);
$parts[] = $header;
$parts[] = is_file($header) ? hash_file('sha256', $header) : '';
}
}
return hash_final($context);
return hash('sha256', implode("\0", $parts));
}
protected function writeGeneratedObjectCacheMetadata(string $sourceFile, string $objectFile): void
@ -1348,23 +1377,14 @@ CODE;
public function compileFile(string $cppFile, string $objectFile, bool $parallel = false): void
{
$isMiscFile = $this->isPhpxMiscFile($cppFile);
$isGenerated = $this->isGeneratedSourceFile($cppFile);
if (($isMiscFile && $this->hasMiscObjectFileCache($cppFile))
|| ($isGenerated && $this->hasGeneratedObjectFileCache($cppFile))) {
if (!$parallel) {
$this->climate->darkGray('[cache] skip: ' . $cppFile);
}
return;
}
if ($isMiscFile) {
$this->invalidateMiscObjectCache($objectFile);
} elseif ($isGenerated) {
$this->invalidateGeneratedObjectCache($objectFile);
}
// S2 (incremental object cache) is intentionally DISABLED here.
// When this wrapper code is AOT-compiled by v1095, the compiler
// miscompiles this region and the subsequent getNativeBuilder()
// ->compile() call, producing a ~2^47-byte VirtualAlloc failure at
// runtime (see the OOM crash on hello.cc). This is a v1095 AOT
// miscompilation bug, not a logic bug. The S2 helper methods below
// are kept for reuse once a fixed compiler is available.
// (Details in .workbuddy/memory/2026-08-04.md.)
$language = $this->getLanguageFromExtension($cppFile);
$options = match ($language) {
null => $this->getCompileCommandOptions(),
@ -1383,12 +1403,6 @@ CODE;
}
$this->error('compile failed: ' . $cppFile);
}
if ($isMiscFile) {
$this->writeMiscObjectCacheMetadata($cppFile, $objectFile);
} elseif ($isGenerated) {
$this->writeGeneratedObjectCacheMetadata($cppFile, $objectFile);
}
}
/**
@ -3556,6 +3570,101 @@ CODE;
/**
* @throws \Exception
*/
/**
* Pre-scan a function/method body to find variables (including parameters)
* that are assigned incompatible types. PHP is dynamically typed, so a single
* variable may legitimately hold a string at one point and an array later; the
* AOT backend must declare such variables as a runtime variant (php::Var) so the
* generated C++ is valid. Without this, re-assigning a scalar-typed variable to a
* different type would either fatal ("Cannot re-assign") or silently miscompile.
*
* @return array<string, true>
*/
protected function computeVariantVars(Node\Stmt\Function_|Node\Stmt\ClassMethod $v): array
{
$types = [];
// Seed with parameter declared types so a reassignment of a parameter is caught.
foreach ($this->functionDef->argInfoList as $argInfo) {
if (!$argInfo->variadic) {
$types[$argInfo->name][] = $argInfo->type;
}
}
$this->collectAssignTypes($v->stmts, $types);
$variant = [];
foreach ($types as $name => $assigned) {
$unique = array_values(array_unique($assigned));
if (count($unique) < 2) {
continue;
}
$conflict = false;
foreach ($unique as $i => $a) {
foreach ($unique as $j => $b) {
if ($i === $j) {
continue;
}
if (!$this->areTypesAssignable($a, $b)) {
$conflict = true;
break 2;
}
}
}
if ($conflict) {
$variant[$name] = true;
}
}
return $variant;
}
/**
* @param array<int, mixed> $nodes
* @param array<string, list<string>> $types
*/
protected function collectAssignTypes(array $nodes, array &$types): void
{
foreach ($nodes as $node) {
$this->collectAssignTypesNode($node, $types);
}
}
/**
* Recursively walk a statement list, recording the RHS type of every simple
* variable assignment. Descent stops at nested function-like nodes (closures,
* nested functions/methods) because those own their own variable scope.
*
* @param array<string, list<string>> $types
*/
protected function collectAssignTypesNode($node, array &$types): void
{
if ($node === null || is_scalar($node) || is_array($node)) {
return;
}
if ($node instanceof Node\Stmt\Function_ || $node instanceof Node\Stmt\ClassMethod
|| $node instanceof Node\Expr\Closure || $node instanceof Node\Expr\ArrowFunction) {
return;
}
if ($node instanceof Node\Expr\Assign || $node instanceof Node\Expr\AssignRef || $node instanceof Node\Expr\AssignOp) {
$lhs = $node->var;
if ($lhs instanceof Node\Expr\Variable && is_string($lhs->name) && $lhs->name !== 'this') {
$rhsType = $this->detectTypeOfExpr($node->expr);
if ($rhsType === Type::VOID) {
$rhsType = Type::VAR;
}
$types[$lhs->name][] = $rhsType;
}
}
if ($node instanceof Node) {
foreach ($node->getSubNodeNames() as $key) {
$sub = $node->$key;
if (is_array($sub)) {
$this->collectAssignTypes($sub, $types);
} elseif ($sub instanceof Node) {
$this->collectAssignTypesNode($sub, $types);
}
}
}
}
protected function parseFunction(Node\Stmt\Function_|Node\Stmt\ClassMethod $v): string
{
$this->resetFunction();
@ -3618,6 +3727,24 @@ CODE;
$this->context->resetAnalysisTemporaries($oriLocalVars, $oriTmpVarIndex, $oriDeclaredObjects);
}
// Promote variables/parameters that are reassigned incompatible types to
// runtime variants so the generated C++ is valid (PHP is dynamically typed).
$this->variantVars = $this->computeVariantVars($v);
if ($this->variantVars !== []) {
$list = [];
foreach ($this->functionDef->argInfoList as $argInfo) {
if (isset($this->variantVars[$argInfo->name])) {
$argInfo->type = Type::VAR;
}
if ($argInfo->variadic) {
$list[] = Type::ARRAY . ' ' . $argInfo->name;
} else {
$list[] = $this->genArgumentDeclaration($argInfo);
}
}
$this->functionDef->params = implode(', ', $list);
}
$stmts = '';
if ($v->stmts) {
$this->indentLevel++;

Loading…
Cancel
Save