From a1d8e525f0980690e89e08fadbd703bd7a538f45 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 4 Sep 2025 11:56:12 +0300 Subject: [PATCH 1/9] gen_stub: drop unused parameter in `ConstInfo::getClassConstDeclaration()` --- build/gen_stub.php | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index ca14022cc5241..ef24505796c51 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -2759,7 +2759,7 @@ public function getDeclaration(array $allConstInfos): string // Condition will be added by generateCodeWithConditions() if ($this->name instanceof ClassConstName) { - $code = $this->getClassConstDeclaration($value, $allConstInfos); + $code = $this->getClassConstDeclaration($value); } else { $code = $this->getGlobalConstDeclaration($value); } @@ -2820,12 +2820,10 @@ private function getGlobalConstDeclaration(EvaluatedValue $value): string throw new Exception("Unimplemented constant type"); } - /** @param array $allConstInfos */ - private function getClassConstDeclaration(EvaluatedValue $value, array $allConstInfos): string + private function getClassConstDeclaration(EvaluatedValue $value): string { $constName = $this->name->getDeclarationName(); - // TODO $allConstInfos is unused $zvalCode = $value->initializeZval("const_{$constName}_value"); $code = "\n" . $zvalCode; From 8ee5a524facae60614c303bf51dd7bb135fd4fd0 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 4 Sep 2025 12:16:05 +0300 Subject: [PATCH 2/9] gen_stub: polyfill and use `array_any()` --- build/gen_stub.php | 146 +++++++++++++++++++-------------------------- 1 file changed, 61 insertions(+), 85 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index ef24505796c51..3a79f2e703e4b 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -43,6 +43,17 @@ function reportFilePutContents(string $filename, string $content): void { } } +if (!function_exists('array_any')) { + function array_any(array $array, callable $callback): bool { + foreach ($array as $key => $value) { + if ($callback($value, $key)) { + return true; + } + } + return false; + } +} + /** * @return FileInfo[] */ @@ -599,23 +610,11 @@ private function __construct(array $types, bool $isIntersection) { } public function isScalar(): bool { - foreach ($this->types as $type) { - if (!$type->isScalar()) { - return false; - } - } - - return true; + return !array_any($this->types, static fn (SimpleType $type): bool => !$type->isScalar()); } public function isNullable(): bool { - foreach ($this->types as $type) { - if ($type->isNull()) { - return true; - } - } - - return false; + return array_any($this->types, static fn (SimpleType $type): bool => $type->isNull()); } public function tryToSimpleType(): ?SimpleType { @@ -1237,12 +1236,10 @@ public function addForVersionsAbove(string $flag, int $minimumVersionId): void { } public function isEmpty(): bool { - foreach (ALL_PHP_VERSION_IDS as $version) { - if ($this->flagsByVersion[$version] !== []) { - return false; - } - } - return true; + return !array_any( + ALL_PHP_VERSION_IDS, + fn (int $version): bool => $this->flagsByVersion[$version] !== [] + ); } public function generateVersionDependentFlagCode( @@ -1439,13 +1436,10 @@ private function getModifierNames(): array private function hasParamWithUnknownDefaultValue(): bool { - foreach ($this->args as $arg) { - if ($arg->defaultValue && !$arg->hasProperDefaultValue()) { - return true; - } - } - - return false; + return array_any( + $this->args, + static fn (ArgInfo $arg): bool => $arg->defaultValue && !$arg->hasProperDefaultValue() + ); } private function equalsApartFromNameAndRefcount(FuncInfo $other): bool { @@ -1666,12 +1660,11 @@ private function getArginfoFlagsByPhpVersions(): VersionFlags $flags[] = "ZEND_ACC_DEPRECATED"; } - foreach ($this->attributes as $attr) { - switch ($attr->class) { - case "Deprecated": - $flags[] = "ZEND_ACC_DEPRECATED"; - break; - } + if (array_any( + $this->attributes, + static fn (AttributeInfo $attr): bool => $attr->class === "Deprecated" + )) { + $flags[] = "ZEND_ACC_DEPRECATED"; } $flags = new VersionFlags($flags); @@ -1680,12 +1673,11 @@ private function getArginfoFlagsByPhpVersions(): VersionFlags $flags->addForVersionsAbove("ZEND_ACC_COMPILE_TIME_EVAL", PHP_82_VERSION_ID); } - foreach ($this->attributes as $attr) { - switch ($attr->class) { - case "NoDiscard": - $flags->addForVersionsAbove("ZEND_ACC_NODISCARD", PHP_85_VERSION_ID); - break; - } + if (array_any( + $this->attributes, + static fn (AttributeInfo $attr): bool => $attr->class === "NoDiscard" + )) { + $flags->addForVersionsAbove("ZEND_ACC_NODISCARD", PHP_85_VERSION_ID); } return $flags; @@ -2635,11 +2627,11 @@ public function __construct( ?ExposedDocComment $exposedDocComment, bool $isFileCacheAllowed ) { - foreach ($attributes as $attr) { - if ($attr->class === "Deprecated") { - $isDeprecated = true; - break; - } + if (array_any( + $attributes, + static fn (AttributeInfo $attr): bool => $attr->class === "Deprecated" + )) { + $isDeprecated = true; } $this->name = $name; @@ -3758,11 +3750,11 @@ private function getFlagsByPhpVersion(): VersionFlags $flags->addForVersionsAbove("ZEND_ACC_READONLY_CLASS", PHP_82_VERSION_ID); } - foreach ($this->attributes as $attr) { - if ($attr->class === "AllowDynamicProperties") { - $flags->addForVersionsAbove("ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES", PHP_82_VERSION_ID); - break; - } + if (array_any( + $this->attributes, + static fn (AttributeInfo $attr): bool => $attr->class === "AllowDynamicProperties" + )) { + $flags->addForVersionsAbove("ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES", PHP_82_VERSION_ID); } return $flags; @@ -4169,46 +4161,34 @@ private function isException(array $classMap): bool private function hasConstructor(): bool { - foreach ($this->funcInfos as $funcInfo) { - if ($funcInfo->name->isConstructor()) { - return true; - } - } - - return false; + return array_any( + $this->funcInfos, + static fn (FuncInfo $funcInfo): bool => $funcInfo->name->isConstructor() + ); } private function hasNonPrivateConstructor(): bool { - foreach ($this->funcInfos as $funcInfo) { - if ($funcInfo->name->isConstructor() && !($funcInfo->flags & Modifiers::PRIVATE)) { - return true; - } - } - - return false; + return array_any( + $this->funcInfos, + static fn (FuncInfo $funcInfo): bool => $funcInfo->name->isConstructor() && !($funcInfo->flags & Modifiers::PRIVATE) + ); } private function hasDestructor(): bool { - foreach ($this->funcInfos as $funcInfo) { - if ($funcInfo->name->isDestructor()) { - return true; - } - } - - return false; + return array_any( + $this->funcInfos, + static fn (FuncInfo $funcInfo): bool => $funcInfo->name->isDestructor() + ); } private function hasMethods(): bool { - foreach ($this->funcInfos as $funcInfo) { - if (!$funcInfo->name->isConstructor() && !$funcInfo->name->isDestructor()) { - return true; - } - } - - return false; + return array_any( + $this->funcInfos, + static fn (FuncInfo $funcInfo): bool => !$funcInfo->name->isConstructor() && !$funcInfo->name->isDestructor() + ); } public function getNamespace(): ?string { @@ -5138,7 +5118,6 @@ function parseClass( ): ClassInfo { $comments = $class->getComments(); $alias = null; - $allowsDynamicProperties = false; $tags = DocCommentTag::parseDocComments($comments); $tagMap = DocCommentTag::makeTagMap($tags); @@ -5154,13 +5133,10 @@ function parseClass( } $attributes = AttributeInfo::createFromGroups($class->attrGroups); - foreach ($attributes as $attribute) { - switch ($attribute->class) { - case 'AllowDynamicProperties': - $allowsDynamicProperties = true; - break 2; - } - } + $allowsDynamicProperties = array_any( + $attributes, + static fn (AttributeInfo $attribute): bool => $attribute->class === 'AllowDynamicProperties' + ); if ($isStrictProperties && $allowsDynamicProperties) { throw new Exception("A class may not have '@strict-properties' and '#[\\AllowDynamicProperties]' at the same time."); From 777fa5d15800a381bd35b6a97023e55e254d609e Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 4 Sep 2025 12:17:02 +0300 Subject: [PATCH 3/9] gen_stub: fix typo `$minPHPCompatability` to `$minPHPCompatibility` --- build/gen_stub.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index 3a79f2e703e4b..c83d25d6302db 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -2226,11 +2226,11 @@ public function findEquivalent(array $generatedFuncInfos): ?FuncInfo { return null; } - public function toArgInfoCode(?int $minPHPCompatability): string { + public function toArgInfoCode(?int $minPHPCompatibility): string { $code = $this->return->beginArgInfo( $this->getArgInfoName(), $this->numRequiredArgs, - $minPHPCompatability === null || $minPHPCompatability >= PHP_81_VERSION_ID + $minPHPCompatibility === null || $minPHPCompatibility >= PHP_81_VERSION_ID ); foreach ($this->args as $argInfo) { From f1dd3f2f3031f0e62347277ac1a4be4c361215a3 Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Sat, 14 Mar 2026 16:32:54 -0700 Subject: [PATCH 4/9] gen_stub: move `generateArgInfoCode()` into `FileInfo` Reduce the number of global functions by moving it to instance method `FileInfo::generateArgInfoCode()`. In the process, make the following parts of `FileInfo` private: - `$funcInfos` - `$generateFunctionEntries` - `$declarationPrefix` - `$generateClassEntries` - `$generateCEnums` - `::getMinimumPhpVersionIdCompatibility()` --- build/gen_stub.php | 266 ++++++++++++++++++++++----------------------- 1 file changed, 132 insertions(+), 134 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index c83d25d6302db..ddfde6cd5c367 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -144,9 +144,8 @@ function processStubFile(string $stubFile, Context $context, bool $includeOnly = return $fileInfo; } - [$arginfoCode, $declCode] = generateArgInfoCode( + [$arginfoCode, $declCode] = $fileInfo->generateArgInfoCode( basename($stubFilenameWithoutExtension), - $fileInfo, $context->allConstInfos, $stubHash ); @@ -162,9 +161,8 @@ function processStubFile(string $stubFile, Context $context, bool $includeOnly = if ($fileInfo->shouldGenerateLegacyArginfo()) { $legacyFileInfo = $fileInfo->getLegacyVersion(); - [$arginfoCode] = generateArgInfoCode( + [$arginfoCode] = $legacyFileInfo->generateArgInfoCode( basename($stubFilenameWithoutExtension), - $legacyFileInfo, $context->allConstInfos, $stubHash ); @@ -4270,13 +4268,13 @@ class FileInfo { /** @var ConstInfo[] */ public array $constInfos = []; /** @var FuncInfo[] */ - public array $funcInfos = []; + private array $funcInfos = []; /** @var ClassInfo[] */ public array $classInfos = []; - public bool $generateFunctionEntries = false; - public string $declarationPrefix = ""; - public bool $generateClassEntries = false; - public bool $generateCEnums = false; + private bool $generateFunctionEntries = false; + private string $declarationPrefix = ""; + private bool $generateClassEntries = false; + private bool $generateCEnums = false; private bool $isUndocumentable = false; private bool $legacyArginfoGeneration = false; private ?int $minimumPhpVersionIdCompatibility = null; @@ -4355,7 +4353,7 @@ public function __clone() } } - public function getMinimumPhpVersionIdCompatibility(): ?int { + private function getMinimumPhpVersionIdCompatibility(): ?int { // Non-legacy arginfo files are always PHP 8.0+ compatible if (!$this->legacyArginfoGeneration && $this->minimumPhpVersionIdCompatibility !== null && @@ -4619,6 +4617,130 @@ public function generateCDeclarations(): string { return $code; } + + + /** + * @param array $allConstInfos + * @return array{string, string} + */ + public function generateArgInfoCode( + string $stubFilenameWithoutExtension, + array $allConstInfos, + string $stubHash + ): array { + $code = ""; + + $generatedFuncInfos = []; + + $argInfoCode = generateCodeWithConditions( + $this->getAllFuncInfos(), "\n", + function (FuncInfo $funcInfo) use (&$generatedFuncInfos) { + /* If there already is an equivalent arginfo structure, only emit a #define */ + if ($generatedFuncInfo = $funcInfo->findEquivalent($generatedFuncInfos)) { + $code = sprintf( + "#define %s %s\n", + $funcInfo->getArgInfoName(), $generatedFuncInfo->getArgInfoName() + ); + } else { + $code = $funcInfo->toArgInfoCode($this->getMinimumPhpVersionIdCompatibility()); + } + + $generatedFuncInfos[] = $funcInfo; + return $code; + } + ); + + if ($argInfoCode !== "") { + $code .= "$argInfoCode\n"; + } + + if ($this->generateFunctionEntries) { + $framelessFunctionCode = generateCodeWithConditions( + $this->getAllFuncInfos(), "\n", + static function (FuncInfo $funcInfo) { + return $funcInfo->getFramelessDeclaration(); + } + ); + + if ($framelessFunctionCode !== "") { + $code .= "$framelessFunctionCode\n"; + } + + $generatedFunctionDeclarations = []; + $code .= generateCodeWithConditions( + $this->getAllFuncInfos(), "", + function (FuncInfo $funcInfo) use (&$generatedFunctionDeclarations) { + $key = $funcInfo->getDeclarationKey(); + if (isset($generatedFunctionDeclarations[$key])) { + return null; + } + + $generatedFunctionDeclarations[$key] = true; + return $this->declarationPrefix . $funcInfo->getDeclaration(); + } + ); + + $code .= generateFunctionEntries(null, $this->funcInfos); + + foreach ($this->classInfos as $classInfo) { + $code .= generateFunctionEntries($classInfo->name, $classInfo->funcInfos, $classInfo->cond); + } + } + + $php80MinimumCompatibility = $this->getMinimumPhpVersionIdCompatibility() === null || $this->getMinimumPhpVersionIdCompatibility() >= PHP_80_VERSION_ID; + + if ($this->generateClassEntries) { + $declaredStrings = []; + $attributeInitializationCode = generateFunctionAttributeInitialization($this->funcInfos, $allConstInfos, $this->getMinimumPhpVersionIdCompatibility(), null, $declaredStrings); + $attributeInitializationCode .= generateGlobalConstantAttributeInitialization($this->constInfos, $allConstInfos, $this->getMinimumPhpVersionIdCompatibility(), null, $declaredStrings); + if ($attributeInitializationCode) { + if (!$php80MinimumCompatibility) { + $attributeInitializationCode = "\n#if (PHP_VERSION_ID >= " . PHP_80_VERSION_ID . ")" . $attributeInitializationCode . "#endif\n"; + } + } + + if ($attributeInitializationCode !== "" || !empty($this->constInfos)) { + $code .= "\nstatic void register_{$stubFilenameWithoutExtension}_symbols(int module_number)\n"; + $code .= "{\n"; + + $code .= generateCodeWithConditions( + $this->constInfos, + '', + static fn (ConstInfo $constInfo): string => $constInfo->getDeclaration($allConstInfos) + ); + + if ($attributeInitializationCode !== "" && $this->constInfos) { + $code .= "\n"; + } + + $code .= $attributeInitializationCode; + $code .= "}\n"; + } + + $code .= $this->generateClassEntryCode($allConstInfos); + } + + $hasDeclFile = false; + $declCode = $this->generateCDeclarations(); + if ($declCode !== '') { + $hasDeclFile = true; + $headerName = "ZEND_" . strtoupper($stubFilenameWithoutExtension) . "_DECL_{$stubHash}_H"; + $declCode = "/* This is a generated file, edit {$stubFilenameWithoutExtension}.stub.php instead.\n" + . " * Stub hash: $stubHash */\n" + . "\n" + . "#ifndef {$headerName}\n" + . "#define {$headerName}\n" + . $declCode . "\n" + . "#endif /* {$headerName} */\n"; + } + + $code = "/* This is a generated file, edit {$stubFilenameWithoutExtension}.stub.php instead.\n" + . " * Stub hash: $stubHash" + . ($hasDeclFile ? "\n * Has decl header: yes */\n" : " */\n") + . $code; + + return [$code, $declCode]; + } } class DocCommentTag { @@ -5248,130 +5370,6 @@ function generateCodeWithConditions( return $code; } -/** - * @param array $allConstInfos - * @return array{string, string} - */ -function generateArgInfoCode( - string $stubFilenameWithoutExtension, - FileInfo $fileInfo, - array $allConstInfos, - string $stubHash -): array { - $code = ""; - - $generatedFuncInfos = []; - - $argInfoCode = generateCodeWithConditions( - $fileInfo->getAllFuncInfos(), "\n", - static function (FuncInfo $funcInfo) use (&$generatedFuncInfos, $fileInfo) { - /* If there already is an equivalent arginfo structure, only emit a #define */ - if ($generatedFuncInfo = $funcInfo->findEquivalent($generatedFuncInfos)) { - $code = sprintf( - "#define %s %s\n", - $funcInfo->getArgInfoName(), $generatedFuncInfo->getArgInfoName() - ); - } else { - $code = $funcInfo->toArgInfoCode($fileInfo->getMinimumPhpVersionIdCompatibility()); - } - - $generatedFuncInfos[] = $funcInfo; - return $code; - } - ); - - if ($argInfoCode !== "") { - $code .= "$argInfoCode\n"; - } - - if ($fileInfo->generateFunctionEntries) { - $framelessFunctionCode = generateCodeWithConditions( - $fileInfo->getAllFuncInfos(), "\n", - static function (FuncInfo $funcInfo) { - return $funcInfo->getFramelessDeclaration(); - } - ); - - if ($framelessFunctionCode !== "") { - $code .= "$framelessFunctionCode\n"; - } - - $generatedFunctionDeclarations = []; - $code .= generateCodeWithConditions( - $fileInfo->getAllFuncInfos(), "", - static function (FuncInfo $funcInfo) use ($fileInfo, &$generatedFunctionDeclarations) { - $key = $funcInfo->getDeclarationKey(); - if (isset($generatedFunctionDeclarations[$key])) { - return null; - } - - $generatedFunctionDeclarations[$key] = true; - return $fileInfo->declarationPrefix . $funcInfo->getDeclaration(); - } - ); - - $code .= generateFunctionEntries(null, $fileInfo->funcInfos); - - foreach ($fileInfo->classInfos as $classInfo) { - $code .= generateFunctionEntries($classInfo->name, $classInfo->funcInfos, $classInfo->cond); - } - } - - $php80MinimumCompatibility = $fileInfo->getMinimumPhpVersionIdCompatibility() === null || $fileInfo->getMinimumPhpVersionIdCompatibility() >= PHP_80_VERSION_ID; - - if ($fileInfo->generateClassEntries) { - $declaredStrings = []; - $attributeInitializationCode = generateFunctionAttributeInitialization($fileInfo->funcInfos, $allConstInfos, $fileInfo->getMinimumPhpVersionIdCompatibility(), null, $declaredStrings); - $attributeInitializationCode .= generateGlobalConstantAttributeInitialization($fileInfo->constInfos, $allConstInfos, $fileInfo->getMinimumPhpVersionIdCompatibility(), null, $declaredStrings); - if ($attributeInitializationCode) { - if (!$php80MinimumCompatibility) { - $attributeInitializationCode = "\n#if (PHP_VERSION_ID >= " . PHP_80_VERSION_ID . ")" . $attributeInitializationCode . "#endif\n"; - } - } - - if ($attributeInitializationCode !== "" || !empty($fileInfo->constInfos)) { - $code .= "\nstatic void register_{$stubFilenameWithoutExtension}_symbols(int module_number)\n"; - $code .= "{\n"; - - $code .= generateCodeWithConditions( - $fileInfo->constInfos, - '', - static fn (ConstInfo $constInfo): string => $constInfo->getDeclaration($allConstInfos) - ); - - if ($attributeInitializationCode !== "" && $fileInfo->constInfos) { - $code .= "\n"; - } - - $code .= $attributeInitializationCode; - $code .= "}\n"; - } - - $code .= $fileInfo->generateClassEntryCode($allConstInfos); - } - - $hasDeclFile = false; - $declCode = $fileInfo->generateCDeclarations(); - if ($declCode !== '') { - $hasDeclFile = true; - $headerName = "ZEND_" . strtoupper($stubFilenameWithoutExtension) . "_DECL_{$stubHash}_H"; - $declCode = "/* This is a generated file, edit {$stubFilenameWithoutExtension}.stub.php instead.\n" - . " * Stub hash: $stubHash */\n" - . "\n" - . "#ifndef {$headerName}\n" - . "#define {$headerName}\n" - . $declCode . "\n" - . "#endif /* {$headerName} */\n"; - } - - $code = "/* This is a generated file, edit {$stubFilenameWithoutExtension}.stub.php instead.\n" - . " * Stub hash: $stubHash" - . ($hasDeclFile ? "\n * Has decl header: yes */\n" : " */\n") - . $code; - - return [$code, $declCode]; -} - /** @param FuncInfo[] $funcInfos */ function generateFunctionEntries(?Name $className, array $funcInfos, ?string $cond = null): string { // No need to add anything if there are no function entries From 25f62cfa1f4920244d10e6efe5eb31e2f572a70d Mon Sep 17 00:00:00 2001 From: Daniel Scherzer Date: Thu, 4 Sep 2025 12:48:24 +0300 Subject: [PATCH 5/9] gen_stub: simplify with early returns --- build/gen_stub.php | 128 +++++++++++++++++++++------------------------ 1 file changed, 60 insertions(+), 68 deletions(-) diff --git a/build/gen_stub.php b/build/gen_stub.php index ddfde6cd5c367..c64aa65530f97 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1872,56 +1872,56 @@ private function getParameterSection(DOMDocument $doc): DOMElement { $noParamEntity = $doc->createEntityReference('no.function.parameters'); $parametersRefSec->appendChild($noParamEntity); return $parametersRefSec; - } else { - $parametersContainer = $doc->createDocumentFragment(); - - $parametersContainer->appendChild(new DOMText("\n ")); - $parametersList = $doc->createElement('variablelist'); - $parametersContainer->appendChild($parametersList); - - /* - - name - - - Description. - - - - */ - foreach ($this->args as $arg) { - $parameter = $doc->createElement('parameter', $arg->name); - $parameterTerm = $doc->createElement('term'); - $parameterTerm->appendChild($parameter); - - $listItemPara = $doc->createElement('simpara'); - $listItemPara->append( - "\n ", - "Description.", - "\n ", - ); + } + $parametersContainer = $doc->createDocumentFragment(); + + $parametersContainer->appendChild(new DOMText("\n ")); + $parametersList = $doc->createElement('variablelist'); + $parametersContainer->appendChild($parametersList); + + /* + + name + + + Description. + + + + */ + foreach ($this->args as $arg) { + $parameter = $doc->createElement('parameter', $arg->name); + $parameterTerm = $doc->createElement('term'); + $parameterTerm->appendChild($parameter); + + $listItemPara = $doc->createElement('simpara'); + $listItemPara->append( + "\n ", + "Description.", + "\n ", + ); - $parameterEntryListItem = $doc->createElement('listitem'); - $parameterEntryListItem->append( - "\n ", - $listItemPara, - "\n ", - ); + $parameterEntryListItem = $doc->createElement('listitem'); + $parameterEntryListItem->append( + "\n ", + $listItemPara, + "\n ", + ); - $parameterEntry = $doc->createElement('varlistentry'); - $parameterEntry->append( - "\n ", - $parameterTerm, - "\n ", - $parameterEntryListItem, - "\n ", - ); + $parameterEntry = $doc->createElement('varlistentry'); + $parameterEntry->append( + "\n ", + $parameterTerm, + "\n ", + $parameterEntryListItem, + "\n ", + ); - $parametersList->appendChild(new DOMText("\n ")); - $parametersList->appendChild($parameterEntry); - } - $parametersList->appendChild(new DOMText("\n ")); + $parametersList->appendChild(new DOMText("\n ")); + $parametersList->appendChild($parameterEntry); } + $parametersList->appendChild(new DOMText("\n ")); + $parametersContainer->appendChild(new DOMText("\n ")); $parametersRefSec->appendChild($parametersContainer); $parametersRefSec->appendChild(new DOMText("\n ")); @@ -2493,8 +2493,6 @@ protected function getFlagsByPhpVersion(): VersionFlags protected function getTypeCode(string $variableLikeName, string &$code): string { $variableLikeType = $this->getVariableTypeName(); - - $typeCode = ""; if ($this->type) { $arginfoType = $this->type->toArginfoType(); if ($arginfoType->hasClassType()) { @@ -2521,22 +2519,17 @@ protected function getTypeCode(string $variableLikeName, string &$code): string } else { $code .= "\tzend_type {$variableLikeType}_{$variableLikeName}_type = ZEND_TYPE_INIT_UNION({$variableLikeType}_{$variableLikeName}_type_list, $typeMaskCode);\n"; } - $typeCode = "{$variableLikeType}_{$variableLikeName}_type"; - } else { - $escapedClassName = $arginfoType->classTypes[0]->toEscapedName(); - $varEscapedClassName = $arginfoType->classTypes[0]->toVarEscapedName(); - $code .= "\tzend_string *{$variableLikeType}_{$variableLikeName}_class_{$varEscapedClassName} = zend_string_init(\"{$escapedClassName}\", sizeof(\"{$escapedClassName}\")-1, 1);\n"; - - $typeCode = "(zend_type) ZEND_TYPE_INIT_CLASS({$variableLikeType}_{$variableLikeName}_class_{$varEscapedClassName}, 0, " . $arginfoType->toTypeMask() . ")"; + return "{$variableLikeType}_{$variableLikeName}_type"; } - } else { - $typeCode = "(zend_type) ZEND_TYPE_INIT_MASK(" . $arginfoType->toTypeMask() . ")"; + $escapedClassName = $arginfoType->classTypes[0]->toEscapedName(); + $varEscapedClassName = $arginfoType->classTypes[0]->toVarEscapedName(); + $code .= "\tzend_string *{$variableLikeType}_{$variableLikeName}_class_{$varEscapedClassName} = zend_string_init(\"{$escapedClassName}\", sizeof(\"{$escapedClassName}\")-1, 1);\n"; + + return "(zend_type) ZEND_TYPE_INIT_CLASS({$variableLikeType}_{$variableLikeName}_class_{$varEscapedClassName}, 0, " . $arginfoType->toTypeMask() . ")"; } - } else { - $typeCode = "(zend_type) ZEND_TYPE_INIT_NONE(0)"; + return "(zend_type) ZEND_TYPE_INIT_MASK(" . $arginfoType->toTypeMask() . ")"; } - - return $typeCode; + return "(zend_type) ZEND_TYPE_INIT_NONE(0)"; } /** @param array $allConstInfos */ @@ -3311,14 +3304,13 @@ public function __construct(string $name, ?Expr $value) { public function getDeclaration(array $allConstInfos): string { $escapedName = addslashes($this->name); if ($this->value === null) { - $code = "\n\tzend_enum_add_case_cstr(class_entry, \"$escapedName\", NULL);\n"; - } else { - $value = EvaluatedValue::createFromExpression($this->value, null, null, $allConstInfos); - - $zvalName = "enum_case_{$escapedName}_value"; - $code = "\n" . $value->initializeZval($zvalName); - $code .= "\tzend_enum_add_case_cstr(class_entry, \"$escapedName\", &$zvalName);\n"; + return "\n\tzend_enum_add_case_cstr(class_entry, \"$escapedName\", NULL);\n"; } + $value = EvaluatedValue::createFromExpression($this->value, null, null, $allConstInfos); + + $zvalName = "enum_case_{$escapedName}_value"; + $code = "\n" . $value->initializeZval($zvalName); + $code .= "\tzend_enum_add_case_cstr(class_entry, \"$escapedName\", &$zvalName);\n"; return $code; } From e1a3a4c9a47bea89646d888692fe75b4aaac5831 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Mon, 16 Mar 2026 14:31:49 -0400 Subject: [PATCH 6/9] Fix GH-21267: JIT infinite loop on FETCH_OBJ_R with IS_UNDEF property (#21368) When the JIT defers the IS_UNDEF check for FETCH_OBJ_R to the result type guard, the deoptimization escape path dispatches to opline->handler via the trace_escape stub. If opline->handler has been overwritten with JIT code (e.g. a function entry trace), this creates an infinite loop. Fix by dispatching to the original VM handler (orig_handler from the trace extension) instead of going through the trace_escape stub. This avoids the extra IS_UNDEF guard on every property read while correctly handling the rare IS_UNDEF case during deoptimization. Also set current_op_array in zend_jit_trace_exit_to_vm so that the blacklisted exit deoptimizer can resolve orig_handler, covering the case where side trace compilation is exhausted. Closes GH-21368. --- ext/opcache/jit/zend_jit_ir.c | 17 +++++++-- ext/opcache/jit/zend_jit_trace.c | 2 +- ext/opcache/tests/jit/gh21267.phpt | 35 +++++++++++++++++++ ext/opcache/tests/jit/gh21267_blacklist.phpt | 36 ++++++++++++++++++++ 4 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 ext/opcache/tests/jit/gh21267.phpt create mode 100644 ext/opcache/tests/jit/gh21267_blacklist.phpt diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 3ddaa0270881e..b87abad436183 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -8066,7 +8066,7 @@ static int zend_jit_defined(zend_jit_ctx *jit, const zend_op *opline, uint8_t sm return 1; } -static int zend_jit_escape_if_undef(zend_jit_ctx *jit, int var, uint32_t flags, const zend_op *opline, int8_t reg) +static int zend_jit_escape_if_undef(zend_jit_ctx *jit, int var, uint32_t flags, const zend_op *opline, const zend_op_array *op_array, int8_t reg) { zend_jit_addr reg_addr = ZEND_ADDR_REF_ZVAL(zend_jit_deopt_rload(jit, IR_ADDR, reg)); ir_ref if_def = ir_IF(jit_Z_TYPE(jit, reg_addr)); @@ -8089,7 +8089,20 @@ static int zend_jit_escape_if_undef(zend_jit_ctx *jit, int var, uint32_t flags, } jit_LOAD_IP_ADDR(jit, opline - 1); - ir_IJMP(jit_STUB_ADDR(jit, jit_stub_trace_escape)); + + /* We can't use trace_escape() because opcode handler may be overridden by JIT */ + zend_jit_op_array_trace_extension *jit_extension = + (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); + size_t offset = jit_extension->offset; + ir_ref ref = ir_CONST_ADDR(ZEND_OP_TRACE_INFO((opline - 1), offset)->orig_handler); + if (GCC_GLOBAL_REGS) { + ir_TAILCALL(IR_VOID, ref); + } else { +#if defined(IR_TARGET_X86) + ref = ir_CAST_FC_FUNC(ref); +#endif + ir_TAILCALL_1(IR_I32, ref, jit_FP(jit)); + } ir_IF_TRUE(if_def); diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 03b59eea61535..b5d980ca5afc6 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -3603,7 +3603,7 @@ static int zend_jit_trace_deoptimization( ZEND_ASSERT(STACK_FLAGS(parent_stack, check2) == ZREG_ZVAL_COPY); ZEND_ASSERT(reg != ZREG_NONE); - if (!zend_jit_escape_if_undef(jit, check2, flags, opline, reg)) { + if (!zend_jit_escape_if_undef(jit, check2, flags, opline, exit_info->op_array, reg)) { return 0; } if (!zend_jit_restore_zval(jit, EX_NUM_TO_VAR(check2), reg)) { diff --git a/ext/opcache/tests/jit/gh21267.phpt b/ext/opcache/tests/jit/gh21267.phpt new file mode 100644 index 0000000000000..91c8de29c2bfa --- /dev/null +++ b/ext/opcache/tests/jit/gh21267.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-21267 (JIT infinite loop on FETCH_OBJ_R with IS_UNDEF property in polymorphic context) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=tracing +opcache.jit_buffer_size=64M +opcache.jit_hot_loop=0 +opcache.jit_hot_func=2 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=1 +--FILE-- +x; } +} + +$o1 = new C; +$o2 = new C; +$o2->x = false; +$o3 = new C; +unset($o3->x); +$a = [$o1, $o2, $o3]; + +for ($i = 0; $i < 8; $i++) { + $m = $a[$i % 3]; + $m->getX(); + $m->getX(); +} +?> +OK +--EXPECT-- +OK diff --git a/ext/opcache/tests/jit/gh21267_blacklist.phpt b/ext/opcache/tests/jit/gh21267_blacklist.phpt new file mode 100644 index 0000000000000..3ec222dc42509 --- /dev/null +++ b/ext/opcache/tests/jit/gh21267_blacklist.phpt @@ -0,0 +1,36 @@ +--TEST-- +GH-21267 (JIT infinite loop on FETCH_OBJ_R with IS_UNDEF via blacklisted trace exit) +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.jit=tracing +opcache.jit_buffer_size=64M +opcache.jit_hot_loop=0 +opcache.jit_hot_func=2 +opcache.jit_hot_return=0 +opcache.jit_hot_side_exit=1 +opcache.jit_max_side_traces=0 +--FILE-- +x; } +} + +$o1 = new C; +$o2 = new C; +$o2->x = false; +$o3 = new C; +unset($o3->x); +$a = [$o1, $o2, $o3]; + +for ($i = 0; $i < 8; $i++) { + $m = $a[$i % 3]; + $m->getX(); + $m->getX(); +} +?> +OK +--EXPECT-- +OK From 00ff93d660dfc8de81d0a81d0e1eb143e6a197f6 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Mon, 16 Mar 2026 21:57:40 +0300 Subject: [PATCH 7/9] Fix support for TAILCALL VM --- ext/opcache/jit/zend_jit_ir.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 53de30fb80ab0..b76d7d2ee486f 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -7978,13 +7978,13 @@ static int zend_jit_escape_if_undef(zend_jit_ctx *jit, int var, uint32_t flags, (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); size_t offset = jit_extension->offset; ir_ref ref = ir_CONST_ADDR(ZEND_OP_TRACE_INFO((opline - 1), offset)->orig_handler); - if (GCC_GLOBAL_REGS) { - ir_TAILCALL(IR_VOID, ref); + if (GCC_GLOBAL_REGS || ZEND_VM_KIND == ZEND_VM_KIND_TAILCALL) { + ir_TAILCALL(IR_OPCODE_HANDLER_RET, ref); } else { #if defined(IR_TARGET_X86) ref = ir_CAST_FC_FUNC(ref); #endif - ir_TAILCALL_1(IR_I32, ref, jit_FP(jit)); + ir_TAILCALL_2(IR_ADDR, ref, jit_FP(jit), jit_IP(jit)); } ir_IF_TRUE(if_def); From c43880587b17dddf5e95fdc50dbe86261d71a54e Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Mon, 16 Mar 2026 22:52:40 +0300 Subject: [PATCH 8/9] Update IR (#21462) IR commit: 7fed7999743ba6a5ffc5535e786725d5577f6f34 --- ext/opcache/jit/ir/ir_cfg.c | 125 +++++++++++++++++--------------- ext/opcache/jit/ir/ir_gdb.c | 13 ++++ ext/opcache/jit/ir/ir_perf.c | 4 +- ext/opcache/jit/ir/ir_private.h | 7 +- 4 files changed, 87 insertions(+), 62 deletions(-) diff --git a/ext/opcache/jit/ir/ir_cfg.c b/ext/opcache/jit/ir/ir_cfg.c index ca57845a0c7a7..40041004c5615 100644 --- a/ext/opcache/jit/ir/ir_cfg.c +++ b/ext/opcache/jit/ir/ir_cfg.c @@ -211,6 +211,7 @@ static uint32_t IR_NEVER_INLINE ir_cfg_remove_dead_inputs(ir_ctx *ctx, uint32_t if (life_inputs) { ir_remove_phis_inputs(ctx, &ctx->use_lists[bb->start], insn->inputs_count, life_inputs); ir_mem_free(life_inputs); + life_inputs = NULL; } } } @@ -613,59 +614,64 @@ static int ir_remove_unreachable_blocks(ir_ctx *ctx) return 1; } -static void compute_postnum(const ir_ctx *ctx, uint32_t *cur, uint32_t b) -{ - uint32_t i, *p; - ir_block *bb = &ctx->cfg_blocks[b]; - - if (bb->postnum != 0) { - return; - } - - if (bb->successors_count) { - bb->postnum = -1; /* Marker for "currently visiting" */ - p = ctx->cfg_edges + bb->successors; - i = bb->successors_count; - do { - compute_postnum(ctx, cur, *p); - p++; - } while (--i); - } - bb->postnum = (*cur)++; -} - /* Computes dominator tree using algorithm from "A Simple, Fast Dominance Algorithm" by * Cooper, Harvey and Kennedy. */ static IR_NEVER_INLINE int ir_build_dominators_tree_slow(ir_ctx *ctx) { - uint32_t blocks_count, b, postnum; + uint32_t blocks_count, b, postnum, i; ir_block *blocks, *bb; uint32_t *edges; + uint32_t *rpo = ir_mem_malloc((ctx->cfg_blocks_count + 1) * sizeof(uint32_t)); bool changed; blocks = ctx->cfg_blocks; edges = ctx->cfg_edges; blocks_count = ctx->cfg_blocks_count; - /* Clear the dominators tree */ - for (b = 0, bb = &blocks[0]; b <= blocks_count; b++, bb++) { - bb->idom = 0; - bb->dom_depth = 0; - bb->dom_child = 0; - bb->dom_next_child = 0; - } - ctx->flags2 &= ~IR_NO_LOOPS; postnum = 1; - compute_postnum(ctx, &postnum, 1); + ir_worklist work; + ir_worklist_init(&work, ctx->cfg_blocks_count + 1); + ir_worklist_push(&work, 1); + IR_ASSERT(blocks[1].next_succ == 0); + while (ir_worklist_len(&work)) { +next: + b = ir_worklist_peek(&work); + bb = &blocks[b]; + uint32_t n = bb->successors_count - bb->next_succ; + if (n) { + uint32_t *p = edges + bb->successors + bb->next_succ; + for (; n > 0; p++, n--) { + uint32_t succ = *p; + if (ir_worklist_push(&work, succ)) { + bb->next_succ = bb->successors_count - n + 1; + IR_ASSERT(blocks[succ].next_succ == 0); + goto next; + } + } + } + + /* Start from bb->idom calculated by the fast dominators algorithm */ + // bb->idom = 0; + bb->next_succ = 0; + rpo[postnum] = b; + bb->postnum = postnum++; + ir_worklist_pop(&work); + } + ir_worklist_free(&work); + + IR_ASSERT(rpo[blocks_count] == 1); /* Find immediate dominators by iterative fixed-point algorithm */ blocks[1].idom = 1; do { changed = 0; + /* Iterating in Reverse Post Order */ - for (b = 2, bb = &blocks[2]; b <= blocks_count; b++, bb++) { + for (i = blocks_count - 1; i > 0; i--) { + b = rpo[i]; + bb = &blocks[b]; IR_ASSERT(!(bb->flags & IR_BB_UNREACHABLE)); IR_ASSERT(bb->predecessors_count > 0); if (bb->predecessors_count == 1) { @@ -718,6 +724,8 @@ static IR_NEVER_INLINE int ir_build_dominators_tree_slow(ir_ctx *ctx) } } while (changed); + ir_mem_free(rpo); + /* Build dominators tree */ blocks[1].idom = 0; blocks[1].dom_depth = 0; @@ -771,7 +779,7 @@ int ir_build_dominators_tree(ir_ctx *ctx) blocks[1].idom = 1; blocks[1].dom_depth = 0; - /* Iterating in Reverse Post Order */ + /* Iterating in Reverse Post Order (relay on existing BB order and fall-back to slow algorithm) */ for (b = 2, bb = &blocks[2]; b <= blocks_count; b++, bb++) { IR_ASSERT(!(bb->flags & IR_BB_UNREACHABLE)); IR_ASSERT(bb->predecessors_count > 0); @@ -783,8 +791,8 @@ int ir_build_dominators_tree(ir_ctx *ctx) if (UNEXPECTED(idom >= b)) { /* In rare cases, LOOP_BEGIN.op1 may be a back-edge. Skip back-edges. */ ctx->flags2 &= ~IR_NO_LOOPS; -// IR_ASSERT(k > 1 && "Wrong blocks order: BB is before its single predecessor"); if (UNEXPECTED(k <= 1)) { + // IR_ASSERT(k > 1 && "Wrong blocks order: BB is before its single predecessor"); slow_case: ir_list_free(&worklist); return ir_build_dominators_tree_slow(ctx); @@ -798,6 +806,7 @@ int ir_build_dominators_tree(ir_ctx *ctx) break; } if (UNEXPECTED(k == 0)) { + // IR_ASSERT(0 && "Wrong blocks order: BB is before all its predecessors"); goto slow_case; } ir_list_push(&worklist, idom); @@ -830,13 +839,6 @@ int ir_build_dominators_tree(ir_ctx *ctx) bb->dom_depth = idom_bb->dom_depth + 1; } - /* Construct children lists sorted by block number */ - for (b = blocks_count, bb = &blocks[b]; b >= 2; b--, bb--) { - ir_block *idom_bb = &blocks[bb->idom]; - bb->dom_next_child = idom_bb->dom_child; - idom_bb->dom_child = b; - } - blocks[1].idom = 0; if (ir_list_len(&worklist) != 0) { @@ -874,10 +876,18 @@ int ir_build_dominators_tree(ir_ctx *ctx) if (UNEXPECTED(!complete)) { ir_list_free(&worklist); + // TODO: this algorithm may be incorrect. Prove and/or switch to ir_build_dominators_tree_slow() ??? return ir_build_dominators_tree_iterative(ctx); } } + /* Construct children lists sorted by block number */ + for (b = blocks_count, bb = &blocks[b]; b >= 2; b--, bb--) { + ir_block *idom_bb = &blocks[bb->idom]; + bb->dom_next_child = idom_bb->dom_child; + idom_bb->dom_child = b; + } + ir_list_free(&worklist); return 1; @@ -898,8 +908,6 @@ static int ir_build_dominators_tree_iterative(ir_ctx *ctx) /* Clear the dominators tree, but keep already found dominators */ for (b = 0, bb = &blocks[0]; b <= blocks_count; b++, bb++) { bb->dom_depth = 0; - bb->dom_child = 0; - bb->dom_next_child = 0; } /* Find immediate dominators by iterative fixed-point algorithm */ @@ -917,20 +925,20 @@ static int ir_build_dominators_tree_iterative(ir_ctx *ctx) if (blocks[idom].idom == 0) { while (1) { k--; + if (UNEXPECTED(k == 0)) break; p++; idom = *p; if (blocks[idom].idom > 0) { break; } - IR_ASSERT(k > 0); } + if (UNEXPECTED(k == 0)) continue; } IR_ASSERT(k != 0); while (--k > 0) { uint32_t pred_b = *(++p); if (blocks[pred_b].idom > 0) { - IR_ASSERT(blocks[pred_b].idom > 0); while (idom != pred_b) { while (pred_b > idom) { pred_b = blocks[pred_b].idom; @@ -1094,35 +1102,36 @@ int ir_find_loops(ir_ctx *ctx) times = ir_mem_malloc((ctx->cfg_blocks_count + 1) * 3 * sizeof(uint32_t)); sorted_blocks = times + (ctx->cfg_blocks_count + 1) * 2; + ir_bitset visited = ir_bitset_malloc(ctx->cfg_blocks_count + 1); ir_worklist_push(&work, 1); - ENTRY_TIME(1) = time++; - while (ir_worklist_len(&work)) { - ir_block *bb; - +next: b = ir_worklist_peek(&work); + ir_block *bb = &blocks[b]; - /* Visit successors of "b". */ -next: - bb = &blocks[b]; - n = bb->successors_count; - if (n) { - uint32_t *p = edges + bb->successors; + if (!ir_bitset_in(visited, b)) { + ir_bitset_incl(visited, b); + ENTRY_TIME(b) = time++; + } + uint32_t n = bb->successors_count - bb->next_succ; + if (n) { + uint32_t *p = edges + bb->successors + bb->next_succ; for (; n > 0; p++, n--) { uint32_t succ = *p; - if (ir_worklist_push(&work, succ)) { - b = succ; - ENTRY_TIME(b) = time++; + bb->next_succ = bb->successors_count - n + 1; + IR_ASSERT(blocks[succ].next_succ == 0); goto next; } } } + bb->next_succ = 0; EXIT_TIME(b) = time++; ir_worklist_pop(&work); } + ir_mem_free(visited); /* Sort blocks by level, which is the opposite order in which we want to process them */ /* (Breadth First Search using "sorted_blocks" as a queue) */ diff --git a/ext/opcache/jit/ir/ir_gdb.c b/ext/opcache/jit/ir/ir_gdb.c index 8b5fba6b1533a..41141bd287157 100644 --- a/ext/opcache/jit/ir/ir_gdb.c +++ b/ext/opcache/jit/ir/ir_gdb.c @@ -521,6 +521,8 @@ IR_NEVER_INLINE void __jit_debug_register_code(void) static bool ir_gdb_register_code(const void *object, size_t size) { ir_gdbjit_code_entry *entry; + ir_elf_header *elf_header; + ir_elf_sectheader *elf_section, *elf_section_end; entry = malloc(sizeof(ir_gdbjit_code_entry) + size); if (entry == NULL) { @@ -532,6 +534,17 @@ static bool ir_gdb_register_code(const void *object, size_t size) memcpy((char *)entry->symfile_addr, object, size); + elf_header = (ir_elf_header*)entry->symfile_addr; + elf_section = (ir_elf_sectheader*)(entry->symfile_addr + elf_header->shofs); + elf_section_end = (ir_elf_sectheader*)((char*)elf_section + (elf_header->shentsize * elf_header->shnum)); + + while (elf_section < elf_section_end) { + if ((elf_section->flags & ELFSECT_FLAGS_ALLOC) && elf_section->addr == 0) { + elf_section->addr = (uintptr_t)(entry->symfile_addr + elf_section->ofs); + } + elf_section = (ir_elf_sectheader*)((char*)elf_section + elf_header->shentsize); + } + entry->prev_entry = NULL; entry->next_entry = __jit_debug_descriptor.first_entry; diff --git a/ext/opcache/jit/ir/ir_perf.c b/ext/opcache/jit/ir/ir_perf.c index e5a5e59374076..c0561ff86ac1f 100644 --- a/ext/opcache/jit/ir/ir_perf.c +++ b/ext/opcache/jit/ir/ir_perf.c @@ -30,7 +30,7 @@ #if defined(__linux__) #include -#elif defined(__darwin__) +#elif defined(__APPLE__) # include #elif defined(__FreeBSD__) # include @@ -215,7 +215,7 @@ int ir_perf_jitdump_register(const char *name, const void *start, size_t size) uint32_t thread_id = 0; #if defined(__linux__) thread_id = syscall(SYS_gettid); -#elif defined(__darwin__) +#elif defined(__APPLE__) uint64_t thread_id_u64; pthread_threadid_np(NULL, &thread_id_u64); thread_id = (uint32_t) thread_id_u64; diff --git a/ext/opcache/jit/ir/ir_private.h b/ext/opcache/jit/ir/ir_private.h index 115c5121d7551..96b81a0fcd721 100644 --- a/ext/opcache/jit/ir/ir_private.h +++ b/ext/opcache/jit/ir/ir_private.h @@ -1145,12 +1145,15 @@ struct _ir_block { }; union { uint32_t dom_depth; /* depth from the root of the dominators tree */ - uint32_t postnum; /* used temporary during tree constructon */ + uint32_t postnum; /* used temporary for iterative Post Ordering */ }; uint32_t dom_child; /* first dominated blocks */ uint32_t dom_next_child; /* next dominated block (linked list) */ uint32_t loop_header; - uint32_t loop_depth; + union { + uint32_t loop_depth; + uint32_t next_succ; /* used temporary for iterative Post Ordering */ + }; }; void ir_build_prev_refs(ir_ctx *ctx); From a5637221eef5b88bec61f4adb61acdeec0493e78 Mon Sep 17 00:00:00 2001 From: Dmitry Stogov Date: Mon, 16 Mar 2026 22:52:40 +0300 Subject: [PATCH 9/9] Update IR (#21462) IR commit: 7fed7999743ba6a5ffc5535e786725d5577f6f34 --- ext/opcache/jit/ir/ir_cfg.c | 125 +++++++++++++++++--------------- ext/opcache/jit/ir/ir_gdb.c | 13 ++++ ext/opcache/jit/ir/ir_perf.c | 4 +- ext/opcache/jit/ir/ir_private.h | 7 +- 4 files changed, 87 insertions(+), 62 deletions(-) diff --git a/ext/opcache/jit/ir/ir_cfg.c b/ext/opcache/jit/ir/ir_cfg.c index ca57845a0c7a7..40041004c5615 100644 --- a/ext/opcache/jit/ir/ir_cfg.c +++ b/ext/opcache/jit/ir/ir_cfg.c @@ -211,6 +211,7 @@ static uint32_t IR_NEVER_INLINE ir_cfg_remove_dead_inputs(ir_ctx *ctx, uint32_t if (life_inputs) { ir_remove_phis_inputs(ctx, &ctx->use_lists[bb->start], insn->inputs_count, life_inputs); ir_mem_free(life_inputs); + life_inputs = NULL; } } } @@ -613,59 +614,64 @@ static int ir_remove_unreachable_blocks(ir_ctx *ctx) return 1; } -static void compute_postnum(const ir_ctx *ctx, uint32_t *cur, uint32_t b) -{ - uint32_t i, *p; - ir_block *bb = &ctx->cfg_blocks[b]; - - if (bb->postnum != 0) { - return; - } - - if (bb->successors_count) { - bb->postnum = -1; /* Marker for "currently visiting" */ - p = ctx->cfg_edges + bb->successors; - i = bb->successors_count; - do { - compute_postnum(ctx, cur, *p); - p++; - } while (--i); - } - bb->postnum = (*cur)++; -} - /* Computes dominator tree using algorithm from "A Simple, Fast Dominance Algorithm" by * Cooper, Harvey and Kennedy. */ static IR_NEVER_INLINE int ir_build_dominators_tree_slow(ir_ctx *ctx) { - uint32_t blocks_count, b, postnum; + uint32_t blocks_count, b, postnum, i; ir_block *blocks, *bb; uint32_t *edges; + uint32_t *rpo = ir_mem_malloc((ctx->cfg_blocks_count + 1) * sizeof(uint32_t)); bool changed; blocks = ctx->cfg_blocks; edges = ctx->cfg_edges; blocks_count = ctx->cfg_blocks_count; - /* Clear the dominators tree */ - for (b = 0, bb = &blocks[0]; b <= blocks_count; b++, bb++) { - bb->idom = 0; - bb->dom_depth = 0; - bb->dom_child = 0; - bb->dom_next_child = 0; - } - ctx->flags2 &= ~IR_NO_LOOPS; postnum = 1; - compute_postnum(ctx, &postnum, 1); + ir_worklist work; + ir_worklist_init(&work, ctx->cfg_blocks_count + 1); + ir_worklist_push(&work, 1); + IR_ASSERT(blocks[1].next_succ == 0); + while (ir_worklist_len(&work)) { +next: + b = ir_worklist_peek(&work); + bb = &blocks[b]; + uint32_t n = bb->successors_count - bb->next_succ; + if (n) { + uint32_t *p = edges + bb->successors + bb->next_succ; + for (; n > 0; p++, n--) { + uint32_t succ = *p; + if (ir_worklist_push(&work, succ)) { + bb->next_succ = bb->successors_count - n + 1; + IR_ASSERT(blocks[succ].next_succ == 0); + goto next; + } + } + } + + /* Start from bb->idom calculated by the fast dominators algorithm */ + // bb->idom = 0; + bb->next_succ = 0; + rpo[postnum] = b; + bb->postnum = postnum++; + ir_worklist_pop(&work); + } + ir_worklist_free(&work); + + IR_ASSERT(rpo[blocks_count] == 1); /* Find immediate dominators by iterative fixed-point algorithm */ blocks[1].idom = 1; do { changed = 0; + /* Iterating in Reverse Post Order */ - for (b = 2, bb = &blocks[2]; b <= blocks_count; b++, bb++) { + for (i = blocks_count - 1; i > 0; i--) { + b = rpo[i]; + bb = &blocks[b]; IR_ASSERT(!(bb->flags & IR_BB_UNREACHABLE)); IR_ASSERT(bb->predecessors_count > 0); if (bb->predecessors_count == 1) { @@ -718,6 +724,8 @@ static IR_NEVER_INLINE int ir_build_dominators_tree_slow(ir_ctx *ctx) } } while (changed); + ir_mem_free(rpo); + /* Build dominators tree */ blocks[1].idom = 0; blocks[1].dom_depth = 0; @@ -771,7 +779,7 @@ int ir_build_dominators_tree(ir_ctx *ctx) blocks[1].idom = 1; blocks[1].dom_depth = 0; - /* Iterating in Reverse Post Order */ + /* Iterating in Reverse Post Order (relay on existing BB order and fall-back to slow algorithm) */ for (b = 2, bb = &blocks[2]; b <= blocks_count; b++, bb++) { IR_ASSERT(!(bb->flags & IR_BB_UNREACHABLE)); IR_ASSERT(bb->predecessors_count > 0); @@ -783,8 +791,8 @@ int ir_build_dominators_tree(ir_ctx *ctx) if (UNEXPECTED(idom >= b)) { /* In rare cases, LOOP_BEGIN.op1 may be a back-edge. Skip back-edges. */ ctx->flags2 &= ~IR_NO_LOOPS; -// IR_ASSERT(k > 1 && "Wrong blocks order: BB is before its single predecessor"); if (UNEXPECTED(k <= 1)) { + // IR_ASSERT(k > 1 && "Wrong blocks order: BB is before its single predecessor"); slow_case: ir_list_free(&worklist); return ir_build_dominators_tree_slow(ctx); @@ -798,6 +806,7 @@ int ir_build_dominators_tree(ir_ctx *ctx) break; } if (UNEXPECTED(k == 0)) { + // IR_ASSERT(0 && "Wrong blocks order: BB is before all its predecessors"); goto slow_case; } ir_list_push(&worklist, idom); @@ -830,13 +839,6 @@ int ir_build_dominators_tree(ir_ctx *ctx) bb->dom_depth = idom_bb->dom_depth + 1; } - /* Construct children lists sorted by block number */ - for (b = blocks_count, bb = &blocks[b]; b >= 2; b--, bb--) { - ir_block *idom_bb = &blocks[bb->idom]; - bb->dom_next_child = idom_bb->dom_child; - idom_bb->dom_child = b; - } - blocks[1].idom = 0; if (ir_list_len(&worklist) != 0) { @@ -874,10 +876,18 @@ int ir_build_dominators_tree(ir_ctx *ctx) if (UNEXPECTED(!complete)) { ir_list_free(&worklist); + // TODO: this algorithm may be incorrect. Prove and/or switch to ir_build_dominators_tree_slow() ??? return ir_build_dominators_tree_iterative(ctx); } } + /* Construct children lists sorted by block number */ + for (b = blocks_count, bb = &blocks[b]; b >= 2; b--, bb--) { + ir_block *idom_bb = &blocks[bb->idom]; + bb->dom_next_child = idom_bb->dom_child; + idom_bb->dom_child = b; + } + ir_list_free(&worklist); return 1; @@ -898,8 +908,6 @@ static int ir_build_dominators_tree_iterative(ir_ctx *ctx) /* Clear the dominators tree, but keep already found dominators */ for (b = 0, bb = &blocks[0]; b <= blocks_count; b++, bb++) { bb->dom_depth = 0; - bb->dom_child = 0; - bb->dom_next_child = 0; } /* Find immediate dominators by iterative fixed-point algorithm */ @@ -917,20 +925,20 @@ static int ir_build_dominators_tree_iterative(ir_ctx *ctx) if (blocks[idom].idom == 0) { while (1) { k--; + if (UNEXPECTED(k == 0)) break; p++; idom = *p; if (blocks[idom].idom > 0) { break; } - IR_ASSERT(k > 0); } + if (UNEXPECTED(k == 0)) continue; } IR_ASSERT(k != 0); while (--k > 0) { uint32_t pred_b = *(++p); if (blocks[pred_b].idom > 0) { - IR_ASSERT(blocks[pred_b].idom > 0); while (idom != pred_b) { while (pred_b > idom) { pred_b = blocks[pred_b].idom; @@ -1094,35 +1102,36 @@ int ir_find_loops(ir_ctx *ctx) times = ir_mem_malloc((ctx->cfg_blocks_count + 1) * 3 * sizeof(uint32_t)); sorted_blocks = times + (ctx->cfg_blocks_count + 1) * 2; + ir_bitset visited = ir_bitset_malloc(ctx->cfg_blocks_count + 1); ir_worklist_push(&work, 1); - ENTRY_TIME(1) = time++; - while (ir_worklist_len(&work)) { - ir_block *bb; - +next: b = ir_worklist_peek(&work); + ir_block *bb = &blocks[b]; - /* Visit successors of "b". */ -next: - bb = &blocks[b]; - n = bb->successors_count; - if (n) { - uint32_t *p = edges + bb->successors; + if (!ir_bitset_in(visited, b)) { + ir_bitset_incl(visited, b); + ENTRY_TIME(b) = time++; + } + uint32_t n = bb->successors_count - bb->next_succ; + if (n) { + uint32_t *p = edges + bb->successors + bb->next_succ; for (; n > 0; p++, n--) { uint32_t succ = *p; - if (ir_worklist_push(&work, succ)) { - b = succ; - ENTRY_TIME(b) = time++; + bb->next_succ = bb->successors_count - n + 1; + IR_ASSERT(blocks[succ].next_succ == 0); goto next; } } } + bb->next_succ = 0; EXIT_TIME(b) = time++; ir_worklist_pop(&work); } + ir_mem_free(visited); /* Sort blocks by level, which is the opposite order in which we want to process them */ /* (Breadth First Search using "sorted_blocks" as a queue) */ diff --git a/ext/opcache/jit/ir/ir_gdb.c b/ext/opcache/jit/ir/ir_gdb.c index 8b5fba6b1533a..41141bd287157 100644 --- a/ext/opcache/jit/ir/ir_gdb.c +++ b/ext/opcache/jit/ir/ir_gdb.c @@ -521,6 +521,8 @@ IR_NEVER_INLINE void __jit_debug_register_code(void) static bool ir_gdb_register_code(const void *object, size_t size) { ir_gdbjit_code_entry *entry; + ir_elf_header *elf_header; + ir_elf_sectheader *elf_section, *elf_section_end; entry = malloc(sizeof(ir_gdbjit_code_entry) + size); if (entry == NULL) { @@ -532,6 +534,17 @@ static bool ir_gdb_register_code(const void *object, size_t size) memcpy((char *)entry->symfile_addr, object, size); + elf_header = (ir_elf_header*)entry->symfile_addr; + elf_section = (ir_elf_sectheader*)(entry->symfile_addr + elf_header->shofs); + elf_section_end = (ir_elf_sectheader*)((char*)elf_section + (elf_header->shentsize * elf_header->shnum)); + + while (elf_section < elf_section_end) { + if ((elf_section->flags & ELFSECT_FLAGS_ALLOC) && elf_section->addr == 0) { + elf_section->addr = (uintptr_t)(entry->symfile_addr + elf_section->ofs); + } + elf_section = (ir_elf_sectheader*)((char*)elf_section + elf_header->shentsize); + } + entry->prev_entry = NULL; entry->next_entry = __jit_debug_descriptor.first_entry; diff --git a/ext/opcache/jit/ir/ir_perf.c b/ext/opcache/jit/ir/ir_perf.c index e5a5e59374076..c0561ff86ac1f 100644 --- a/ext/opcache/jit/ir/ir_perf.c +++ b/ext/opcache/jit/ir/ir_perf.c @@ -30,7 +30,7 @@ #if defined(__linux__) #include -#elif defined(__darwin__) +#elif defined(__APPLE__) # include #elif defined(__FreeBSD__) # include @@ -215,7 +215,7 @@ int ir_perf_jitdump_register(const char *name, const void *start, size_t size) uint32_t thread_id = 0; #if defined(__linux__) thread_id = syscall(SYS_gettid); -#elif defined(__darwin__) +#elif defined(__APPLE__) uint64_t thread_id_u64; pthread_threadid_np(NULL, &thread_id_u64); thread_id = (uint32_t) thread_id_u64; diff --git a/ext/opcache/jit/ir/ir_private.h b/ext/opcache/jit/ir/ir_private.h index 115c5121d7551..96b81a0fcd721 100644 --- a/ext/opcache/jit/ir/ir_private.h +++ b/ext/opcache/jit/ir/ir_private.h @@ -1145,12 +1145,15 @@ struct _ir_block { }; union { uint32_t dom_depth; /* depth from the root of the dominators tree */ - uint32_t postnum; /* used temporary during tree constructon */ + uint32_t postnum; /* used temporary for iterative Post Ordering */ }; uint32_t dom_child; /* first dominated blocks */ uint32_t dom_next_child; /* next dominated block (linked list) */ uint32_t loop_header; - uint32_t loop_depth; + union { + uint32_t loop_depth; + uint32_t next_succ; /* used temporary for iterative Post Ordering */ + }; }; void ir_build_prev_refs(ir_ctx *ctx);