diff --git a/build/gen_stub.php b/build/gen_stub.php
index ca14022cc5241..c64aa65530f97 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[]
*/
@@ -133,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
);
@@ -151,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
);
@@ -599,23 +608,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 +1234,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 +1434,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 +1658,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 +1671,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;
@@ -1882,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 "));
@@ -2234,11 +2224,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) {
@@ -2503,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()) {
@@ -2531,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 */
@@ -2635,11 +2618,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;
@@ -2759,7 +2742,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 +2803,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;
@@ -3323,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;
}
@@ -3760,11 +3740,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;
@@ -4171,46 +4151,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 {
@@ -4292,13 +4260,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;
@@ -4377,7 +4345,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 &&
@@ -4641,6 +4609,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 {
@@ -5140,7 +5232,6 @@ function parseClass(
): ClassInfo {
$comments = $class->getComments();
$alias = null;
- $allowsDynamicProperties = false;
$tags = DocCommentTag::parseDocComments($comments);
$tagMap = DocCommentTag::makeTagMap($tags);
@@ -5156,13 +5247,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.");
@@ -5274,130 +5362,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
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);
diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c
index 19b3ce125733a..0d368ef11ce03 100644
--- a/ext/opcache/jit/zend_jit_ir.c
+++ b/ext/opcache/jit/zend_jit_ir.c
@@ -7949,7 +7949,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));
@@ -7972,7 +7972,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 || 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_2(IR_ADDR, ref, jit_FP(jit), jit_IP(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 82e173ba49947..2fcfaf96ab2d7 100644
--- a/ext/opcache/jit/zend_jit_trace.c
+++ b/ext/opcache/jit/zend_jit_trace.c
@@ -3649,7 +3649,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