diff --git a/phpunit/code/compiler_api/persistent_symbol_cache.php b/phpunit/code/compiler_api/persistent_symbol_cache.php new file mode 100644 index 00000000..2337c84f --- /dev/null +++ b/phpunit/code/compiler_api/persistent_symbol_cache.php @@ -0,0 +1,15 @@ +format('Y-m-d'), $item->value; + return ob_get_clean(); +} diff --git a/phpunit/src/Backend/BackendOptionsTest.php b/phpunit/src/Backend/BackendOptionsTest.php index 64901a27..240848e5 100644 --- a/phpunit/src/Backend/BackendOptionsTest.php +++ b/phpunit/src/Backend/BackendOptionsTest.php @@ -248,6 +248,16 @@ class BackendOptionsTest extends TestCase $this->assertStringContainsString('-std=c++17', $options); } + public function testGccExtensionSymbolsAreHiddenByDefault(): void + { + $compiler = new Gcc(new Linux()); + $options = $compiler->buildCompileOptions([ + 'build_mode' => 'ext', + ]); + + $this->assertStringContainsString('-fvisibility=hidden', $options); + } + /** * 测试 GCC 编译选项 - 调试模式 */ diff --git a/phpunit/src/CompilerBaseApiTest.php b/phpunit/src/CompilerBaseApiTest.php index 0483760c..a4e6d1e6 100644 --- a/phpunit/src/CompilerBaseApiTest.php +++ b/phpunit/src/CompilerBaseApiTest.php @@ -936,12 +936,45 @@ YAML); $this->assertStringContainsString('#include ', $code); $this->assertStringContainsString('std::memset(php_func_map, 0, sizeof(php_func_map));', $code); $this->assertStringContainsString('std::memset(php_class_map, 0, sizeof(php_class_map));', $code); - $this->assertStringContainsString('std::memset(php_property_map, 0, sizeof(php_property_map));', $code); + $this->assertStringNotContainsString('php_property_map', $code); $this->assertStringNotContainsString('func_map = {}', $code); $this->assertStringNotContainsString('class_map = {}', $code); $this->assertStringNotContainsString('property_map = {}', $code); } + public function testPersistentSymbolCachesAreLazyAndZtsSafe(): void + { + global $translator; + $compiler = CompilerTest::create(ROOT_PATH); + $translator = $compiler; + $compiler->setBuildMode(CompilerBase::BUILD_MODE_EXT); + + $testFile = ROOT_PATH . '/phpunit/code/compiler_api/persistent_symbol_cache.php'; + $compiler->addFiles([$testFile]); + $compiler->prepareFile($testFile); + $compiler->convertFile($testFile); + + $dataFile = $this->testDir . '/persistent_symbol_cache_data_decl.h'; + $compiler->genDataDeclarations($dataFile); + $data = file_get_contents($dataFile); + $extension = file_get_contents($compiler->genExtension()); + + $this->assertStringContainsString('extern php::PersistentCacheSlot', $data); + $this->assertStringContainsString('extern php::PersistentCacheSlot', $data); + $this->assertStringContainsString('extern php::PersistentCacheSlot', $data); + $this->assertStringContainsString('php_persistent_class_map', $data); + $this->assertStringContainsString('php_get_persistent_class', $extension); + $this->assertStringContainsString('php::getPersistentCache(php_persistent_class_map[class_id]', $extension); + $this->assertStringContainsString('for (auto &slot : php_persistent_class_map)', $extension); + $this->assertStringContainsString('php::resetPersistentCache(slot);', $extension); + $this->assertStringNotContainsString('#ifdef ZTS', $data); + $this->assertStringNotContainsString('compare_exchange_strong', $extension); + $this->assertStringNotContainsString('// internal symbol caches', $extension); + $this->assertStringNotContainsString('php_find_internal_function', $extension); + $this->assertStringNotContainsString('php_find_internal_method', $extension); + $this->assertStringNotContainsString('php_internal_', $extension); + } + public function testArrayableKeywordConversionCallsGeneratedMethodAtRuntime(): void { global $translator; diff --git a/src/Backend/GccLikeBackend.php b/src/Backend/GccLikeBackend.php index 0472b239..a279a4ff 100644 --- a/src/Backend/GccLikeBackend.php +++ b/src/Backend/GccLikeBackend.php @@ -102,7 +102,8 @@ abstract class GccLikeBackend extends CompilerBackend $cmd .= $this->getPICFlag($config); - if (($config['build_mode'] ?? null) === 'lib' && !($this->platform instanceof Windows)) { + if (in_array(($config['build_mode'] ?? null), ['ext', 'lib'], true) + && !($this->platform instanceof Windows)) { $cmd .= ' -fvisibility=hidden'; } diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 380ae538..52be7072 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -238,10 +238,9 @@ class CompilerBase implements PropertyAccessContext public const string OBJECT_PROP = '_object_prop_'; public const string CLASS_MAP = 'class_map'; public const string FUNC_MAP = 'func_map'; - public const string PROP_MAP = 'property_map'; - public const string INTERNAL_CLASS_MAP = 'internal_class_map'; - public const string INTERNAL_FUNC_MAP = 'internal_func_map'; - public const string INTERNAL_PROP_MAP = 'internal_property_map'; + public const string PERSISTENT_CLASS_MAP = 'persistent_class_map'; + public const string PERSISTENT_FUNC_MAP = 'persistent_func_map'; + public const string PERSISTENT_PROP_MAP = 'persistent_property_map'; public const string NAMESPACE_SEPARATOR = '__'; public const string PREFIX = 'php_'; @@ -255,7 +254,6 @@ class CompilerBase implements PropertyAccessContext public const string BUILD_MODE_EXT = 'ext'; public const string BUILD_MODE_LIB = 'lib'; public const string ENTRY_FUNCTION = 'main'; - public const string PHPX_VENDOR_DIR = '/vendor/swoole/phpx'; protected const string PHASE_IDLE = 'idle'; protected const string PHASE_PREPARE = 'prepare'; protected const string PHASE_CONVERT = 'convert'; @@ -278,11 +276,11 @@ class CompilerBase implements PropertyAccessContext */ protected array $classMap = []; /** - * 内置/编译产物(进程生命周期)类名 → ID,运行期为全局缓存,MINIT 填充,永不清理 + * 内置/编译产物(模块生命周期)类名 → ID,PHP 启动完成后惰性填充,RSHUTDOWN 不清理 * @var array */ - protected array $internalClassMap = []; - protected int $internalClassIndex = 0; + protected array $persistentClassMap = []; + protected int $persistentClassIndex = 0; /** * @var array */ @@ -296,22 +294,18 @@ class CompilerBase implements PropertyAccessContext */ protected array $funcMap = []; /** - * 内置/编译产物(进程生命周期)函数/方法 → ID,运行期为全局缓存,MINIT 填充,永不清理 + * 内置/编译产物(模块生命周期)函数/方法 → ID,PHP 启动完成后惰性填充,RSHUTDOWN 不清理 * key 为函数名或 `Class::method` * @var array */ - protected array $internalFuncMap = []; - protected int $internalFuncIndex = 0; - protected int $propIndex = 0; + protected array $persistentFuncMap = []; + protected int $persistentFuncIndex = 0; /** - * 用户定义类的属性 offset 缓存,key 为 `Class::prop`,RSHUTDOWN 清理 + * 内置/编译产物类的声明属性 offset 缓存,key 为 `Class::prop`,惰性填充,RSHUTDOWN 不清理。 + * 属性解析仅覆盖编译类与内置类的声明属性(进程级稳定),用户类属性走字符串路径,不进缓存。 */ - protected array $propMap = []; - /** - * 内置/编译产物类的属性 offset 缓存,key 为 `Class::prop`,MINIT 填充,永不清理 - */ - protected array $internalPropMap = []; - protected int $internalPropIndex = 0; + protected array $persistentPropMap = []; + protected int $persistentPropIndex = 0; protected const array PHP_RUNTIME_TYPE_MAP = [ 'integer' => Type::INT, 'double' => Type::FLOAT, @@ -1223,7 +1217,7 @@ class CompilerBase implements PropertyAccessContext } /** - * 判断类的符号指针是否进程级稳定(MINIT 注册,跨请求缓存安全)。 + * 判断类的符号指针是否在 PHP 模块生命周期内稳定(MINIT 注册,跨请求缓存安全)。 * 编译产物(本单元编译的类/接口)与 PHP 内置类/接口均满足条件。 */ protected function isProcessStableClass(string $className): bool @@ -1236,7 +1230,7 @@ class CompilerBase implements PropertyAccessContext } /** - * 判断函数/方法符号指针是否进程级稳定。 + * 判断函数/方法符号指针是否在 PHP 模块生命周期内稳定。 * `Class::method` 形式的 key 以其所属类的稳定性为准。 */ protected function isProcessStableFunction(string $funcName): bool @@ -1257,12 +1251,12 @@ class CompilerBase implements PropertyAccessContext if (isset($this->classMap[$className])) { return $this->classMap[$className]; } - if (isset($this->internalClassMap[$className])) { - return $this->internalClassMap[$className]; + if (isset($this->persistentClassMap[$className])) { + return $this->persistentClassMap[$className]; } if ($this->isProcessStableClass($className)) { - $id = $this->internalClassIndex++; - $this->internalClassMap[$className] = $id; + $id = $this->persistentClassIndex++; + $this->persistentClassMap[$className] = $id; } else { $id = $this->classIndex++; $this->classMap[$className] = $id; @@ -1275,12 +1269,12 @@ class CompilerBase implements PropertyAccessContext if (isset($this->funcMap[$funcName])) { return $this->funcMap[$funcName]; } - if (isset($this->internalFuncMap[$funcName])) { - return $this->internalFuncMap[$funcName]; + if (isset($this->persistentFuncMap[$funcName])) { + return $this->persistentFuncMap[$funcName]; } if ($this->isProcessStableFunction($funcName)) { - $id = $this->internalFuncIndex++; - $this->internalFuncMap[$funcName] = $id; + $id = $this->persistentFuncIndex++; + $this->persistentFuncMap[$funcName] = $id; } else { $id = $this->funcIndex++; $this->funcMap[$funcName] = $id; @@ -1290,30 +1284,28 @@ class CompilerBase implements PropertyAccessContext /** * @param string $className 必须是带有命名空间的完整类名 + * + * 注意:不存在与用户定义类对应的动态 propMap(区别于 classMap/funcMap)。 + * 属性 offset 缓存的前提是编译期能解析出声明属性(PropertyAccessResolver + * 只接受编译类的 ClassDef 或内置类的反射声明属性),用户类在编译期不可见, + * 其属性访问一律走 `.attr(name)` 字符串路径,因此所有条目必然进程级稳定, + * 全部进入 persistentPropMap。 */ protected function getPropertyId(string $className, string $propName): int { $key = $className . '::' . $propName; - if (isset($this->propMap[$key])) { - return $this->propMap[$key]; - } - if (isset($this->internalPropMap[$key])) { - return $this->internalPropMap[$key]; - } - if ($this->isProcessStableClass($className)) { - $id = $this->internalPropIndex++; - $this->internalPropMap[$key] = $id; - } else { - $id = $this->propIndex++; - $this->propMap[$key] = $id; + if (isset($this->persistentPropMap[$key])) { + return $this->persistentPropMap[$key]; } + $id = $this->persistentPropIndex++; + $this->persistentPropMap[$key] = $id; return $id; } protected function getClassEntryPtr(string $className): string { $id = $this->getClassId($className); - $helper = isset($this->internalClassMap[$className]) ? 'php_get_internal_class' : 'php_get_class'; + $helper = isset($this->persistentClassMap[$className]) ? 'php_get_persistent_class' : 'php_get_class'; return $helper . '(' . $id . ', ' . $this->getLiteralString($className) . ')'; } @@ -1330,7 +1322,7 @@ class CompilerBase implements PropertyAccessContext protected function getFuncPtr(string $funcName): string { $id = $this->getFuncId($funcName); - $helper = isset($this->internalFuncMap[$funcName]) ? 'php_get_internal_func' : 'php_get_func'; + $helper = isset($this->persistentFuncMap[$funcName]) ? 'php_get_persistent_func' : 'php_get_func'; return $helper . '(' . $id . ', ' . $this->getLiteralString($funcName) . ')'; } @@ -1339,7 +1331,7 @@ class CompilerBase implements PropertyAccessContext $funcId = $this->getFuncId($class . '::' . $method); $classId = $this->getClassId($class); // 方法的稳定性与所属类一致,因此 class_id 必定落在同一张表中 - $helper = isset($this->internalFuncMap[$class . '::' . $method]) ? 'php_get_internal_method' : 'php_get_method'; + $helper = isset($this->persistentFuncMap[$class . '::' . $method]) ? 'php_get_persistent_method' : 'php_get_method'; return $helper . '(' . $funcId . ', ' . $this->getLiteralString($method) . ', ' . $classId . ', ' . $this->getLiteralString($class) . ')'; } @@ -1347,8 +1339,7 @@ class CompilerBase implements PropertyAccessContext { $propId = $this->getPropertyId($class, $prop); $classId = $this->getClassId($class); - $helper = isset($this->internalPropMap[$class . '::' . $prop]) ? 'php_get_internal_prop' : 'php_get_prop'; - return $helper . '(' . $propId . ', ' . $this->getLiteralString($prop) . ', ' . $classId . ', ' . $this->getLiteralString($class) . ')'; + return 'php_get_persistent_prop(' . $propId . ', ' . $this->getLiteralString($prop) . ', ' . $classId . ', ' . $this->getLiteralString($class) . ')'; } protected function writeLog($msg): void diff --git a/src/Resolver/PropertyAccessResolver.php b/src/Resolver/PropertyAccessResolver.php index 8fc6588c..8ae4263d 100644 --- a/src/Resolver/PropertyAccessResolver.php +++ b/src/Resolver/PropertyAccessResolver.php @@ -8,7 +8,11 @@ namespace TypePhp\Resolver; +use PhpParser\Modifiers; use PhpParser\NodeAbstract; +use TypePhp\Entity\ClassDef; +use TypePhp\Entity\PropertyDef; +use TypePhp\Type; final class PropertyAccessResolver { @@ -57,7 +61,8 @@ final class PropertyAccessResolver while (true) { $classDef = $this->compiler->getClassDef($findClass); if ($classDef === null) { - break; + // 非编译单元内的类:尝试按内置类的声明属性解析(offset 缓存) + return $this->resolveInternalClassProperty($expr, $property, $findClass, $class, $scope, $static); } if ($classDef->hasProperty($property)) { @@ -145,6 +150,112 @@ final class PropertyAccessResolver return $resolved; } + /** + * 解析 PHP 内置类的声明属性,使其可以进入稳定属性 offset 缓存。 + * + * 仅处理反射可见的声明属性:动态属性、魔术属性(__get/__set)反射不可见, + * 返回 null 回退到按名字符串查找路径。内置类在 MINIT 注册、进程级存活, + * 其声明属性的 offset 终身不变,缓存安全。 + */ + private function resolveInternalClassProperty( + NodeAbstract $expr, + string $property, + string $findClass, + string $requestedClass, + string $scope, + bool $static, + ): ?PropertyAccessResult { + $ref = Reflection::getClass($findClass); + if ($ref === null || !$ref->isInternal()) { + return null; + } + if (!$ref->hasProperty($property)) { + return null; + } + $propRef = $ref->getProperty($property); + // PHP 8.4 属性钩子必须由引擎调用,offset 直读会绕过钩子,回退字符串路径 + if ($propRef->hasHooks()) { + return null; + } + if (!$static && $propRef->isStatic()) { + $this->fatal($expr, "Cannot access static property `{$requestedClass}::\${$property}` as non-static instance property."); + } + if ($static && !$propRef->isStatic()) { + $this->fatal($expr, "Cannot access non-static property `{$requestedClass}::\${$property}` as static property."); + } + + $declaringClass = $propRef->getDeclaringClass()->getName(); + if ($propRef->isProtected()) { + if (!$this->canAccessProtectedProperty($scope, $declaringClass)) { + $displayClass = ltrim($requestedClass, '\\'); + $this->fatal($expr, "Cannot access protected property `{$property}` of class `{$displayClass}`"); + } + } elseif ($propRef->isPrivate() && !$this->isSameClassName($scope, $declaringClass)) { + $displayClass = ltrim($requestedClass, '\\'); + $this->fatal($expr, "Cannot access private property `{$property}` of class `{$displayClass}`"); + } + + // 复合类型(union/intersection)的运行时检查结构依赖 AST 构建, + // 无法从反射便捷还原,回退字符串路径以保证类型安全 + $propType = $propRef->getType(); + if ($propType !== null && !$propType instanceof \ReflectionNamedType) { + return null; + } + + $type = Type::VAR; + $nullable = true; + $objectClass = ''; + if ($propType instanceof \ReflectionNamedType) { + $nullable = $propType->allowsNull(); + $typeName = $propType->getName(); + $type = match ($typeName) { + 'int' => Type::INT, + 'float' => Type::FLOAT, + 'bool', 'true', 'false' => Type::BOOL, + 'string' => Type::STR, + 'array' => Type::ARRAY, + 'object' => Type::OBJECT, + default => $propType->isBuiltin() ? Type::VAR : Type::OBJECT, + }; + if ($type === Type::OBJECT && $typeName !== 'object') { + $objectClass = $typeName; + } + } + + $flags = 0; + if ($propRef->isPublic()) { + $flags |= Modifiers::PUBLIC; + } + if ($propRef->isProtected()) { + $flags |= Modifiers::PROTECTED; + } + if ($propRef->isPrivate()) { + $flags |= Modifiers::PRIVATE; + } + if ($propRef->isStatic()) { + $flags |= Modifiers::STATIC; + } + if ($propRef->isPrivateSet()) { + $flags |= Modifiers::PRIVATE_SET; + } elseif ($propRef->isProtectedSet()) { + $flags |= Modifiers::PROTECTED_SET; + } + + $propertyDef = new PropertyDef($property, $flags, $type, null, $nullable); + $propertyDef->readonly = $propRef->isReadOnly(); + $propertyDef->class = $objectClass; + + $declaringName = $declaringClass; + $namespace = ''; + if (($pos = strrpos($declaringName, '\\')) !== false) { + $namespace = substr($declaringName, 0, $pos); + $declaringName = substr($declaringName, $pos + 1); + } + $classDef = new ClassDef($declaringName, 0, $namespace); + + return new PropertyAccessResult($requestedClass, $declaringClass, $property, $classDef, $propertyDef); + } + private function fatal(NodeAbstract $expr, string $message): never { $this->compiler->fatalError($expr, $message); diff --git a/src/Translator.php b/src/Translator.php index 43a2684c..dba70bf4 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -720,6 +720,7 @@ class Translator extends Preprocessor public function genDataDeclarations(string $file): void { $lines[] = '#include '; + $lines[] = '#include '; $lines[] = PHP_EOL; // Embedded binaries populate the CLI script fields in $_SERVER at @@ -744,23 +745,23 @@ class Translator extends Preprocessor // 确保数组大小至少为 1,避免 C/C++ 编译错误 $classCount = max(1, count($this->classMap)); $lines[] = 'extern THREAD_LOCAL zend_class_entry *' . self::PREFIX . self::CLASS_MAP . '[' . $classCount . '];' . PHP_EOL; - $internalClassCount = max(1, count($this->internalClassMap)); - $lines[] = 'extern zend_class_entry *' . self::PREFIX . self::INTERNAL_CLASS_MAP . '[' . $internalClassCount . '];' . PHP_EOL; + $persistentClassCount = max(1, count($this->persistentClassMap)); + $lines[] = 'extern php::PersistentCacheSlot ' . self::PREFIX . self::PERSISTENT_CLASS_MAP . '[' . $persistentClassCount . '];' . PHP_EOL; $funcCount = max(1, count($this->funcMap)); $lines[] = 'extern THREAD_LOCAL zend_function *' . self::PREFIX . self::FUNC_MAP . '[' . $funcCount . '];' . PHP_EOL; - $internalFuncCount = max(1, count($this->internalFuncMap)); - $lines[] = 'extern zend_function *' . self::PREFIX . self::INTERNAL_FUNC_MAP . '[' . $internalFuncCount . '];' . PHP_EOL; + $persistentFuncCount = max(1, count($this->persistentFuncMap)); + $lines[] = 'extern php::PersistentCacheSlot ' . self::PREFIX . self::PERSISTENT_FUNC_MAP . '[' . $persistentFuncCount . '];' . PHP_EOL; $pythonModuleDeclarations = $this->genPythonModuleDataDeclarations(); if ($pythonModuleDeclarations !== '') { $lines[] = $pythonModuleDeclarations; } - $propCount = max(1, count($this->propMap)); - $lines[] = 'extern THREAD_LOCAL uint32_t ' . self::PREFIX . self::PROP_MAP . '[' . $propCount . '];' . PHP_EOL; - $internalPropCount = max(1, count($this->internalPropMap)); - $lines[] = 'extern uint32_t ' . self::PREFIX . self::INTERNAL_PROP_MAP . '[' . $internalPropCount . '];' . PHP_EOL; + // 无动态 propMap:属性 offset 缓存仅覆盖编译类/内置类的声明属性(见 getPropertyId), + // 全部在模块生命周期内稳定,只保留 persistent prop map + $persistentPropCount = max(1, count($this->persistentPropMap)); + $lines[] = 'extern php::PersistentCacheSlot ' . self::PREFIX . self::PERSISTENT_PROP_MAP . '[' . $persistentPropCount . '];' . PHP_EOL; foreach ($this->getClassLikesWithConstants() as $classDef) { foreach ($classDef->constants as $constant) { @@ -818,18 +819,20 @@ class Translator extends Preprocessor $code .= "// class entry \n"; // 确保数组大小至少为 1,避免 C/C++ 编译错误 $code .= 'THREAD_LOCAL zend_class_entry *' . self::PREFIX . self::CLASS_MAP . '[' . max(1, count($this->classMap)) . '];' . PHP_EOL; - // 内置/编译产物类,进程级缓存,MINIT 填充,无需 THREAD_LOCAL,RSHUTDOWN 不清理 - $code .= 'zend_class_entry *' . self::PREFIX . self::INTERNAL_CLASS_MAP . '[' . max(1, count($this->internalClassMap)) . '];' . PHP_EOL; + // Internal/compiled symbols have module lifetime. They are initialized + // lazily after PHP startup, so disable_functions/disable_classes have + // already finalized the runtime tables. ZTS publishes them atomically. + $code .= 'php::PersistentCacheSlot ' . self::PREFIX . self::PERSISTENT_CLASS_MAP . '[' . max(1, count($this->persistentClassMap)) . ']{};' . PHP_EOL; $code .= "// func \n"; $code .= 'THREAD_LOCAL zend_function *' . self::PREFIX . self::FUNC_MAP . '[' . max(1, count($this->funcMap)) . '];' . PHP_EOL; - $code .= 'zend_function *' . self::PREFIX . self::INTERNAL_FUNC_MAP . '[' . max(1, count($this->internalFuncMap)) . '];' . PHP_EOL; + $code .= 'php::PersistentCacheSlot ' . self::PREFIX . self::PERSISTENT_FUNC_MAP . '[' . max(1, count($this->persistentFuncMap)) . ']{};' . PHP_EOL; $code .= $this->genPythonModuleStorage(); $code .= "// property \n"; - $code .= 'THREAD_LOCAL uint32_t ' . self::PREFIX . self::PROP_MAP . '[' . max(1, count($this->propMap)) . '];' . PHP_EOL; - $code .= 'uint32_t ' . self::PREFIX . self::INTERNAL_PROP_MAP . '[' . max(1, count($this->internalPropMap)) . '];' . PHP_EOL; + // 无动态 propMap:属性 offset 缓存仅覆盖编译类/内置类的声明属性(见 getPropertyId) + $code .= 'php::PersistentCacheSlot ' . self::PREFIX . self::PERSISTENT_PROP_MAP . '[' . max(1, count($this->persistentPropMap)) . ']{};' . PHP_EOL; $code .= "// functions \n"; @@ -856,67 +859,34 @@ zend_function *php_get_method(int func_id, const php::Str &method_name, int clas return php_func_map[func_id]; } -uint32_t php_get_prop(int prop_id, const php::Str &prop_name, int class_id, const php::Str &class_name) { - if (UNEXPECTED(php_property_map[prop_id] == 0)) { - php_property_map[prop_id] = php::getPropertyOffset(class_name, prop_name) + 1024; - } - return php_property_map[prop_id] - 1024; +zend_class_entry *php_get_persistent_class(int class_id, const php::Str &class_name) { + return php::getPersistentCache(php_persistent_class_map[class_id], [&]() { + return php::getClassEntrySafe(class_name); + }); } -zend_class_entry *php_get_internal_class(int class_id, const php::Str &class_name) { - if (UNEXPECTED(php_internal_class_map[class_id] == nullptr)) { - php_internal_class_map[class_id] = php::getClassEntrySafe(class_name); - } - return php_internal_class_map[class_id]; +zend_function *php_get_persistent_func(int func_id, const php::Str &func_name) { + return php::getPersistentCache(php_persistent_func_map[func_id], [&]() { + return php::getFunction(func_name); + }); } -zend_function *php_get_internal_func(int func_id, const php::Str &func_name) { - if (UNEXPECTED(php_internal_func_map[func_id] == nullptr)) { - php_internal_func_map[func_id] = php::getFunction(func_name); - } - return php_internal_func_map[func_id]; +zend_function *php_get_persistent_method(int func_id, const php::Str &method_name, int class_id, const php::Str &class_name) { + return php::getPersistentCache(php_persistent_func_map[func_id], [&]() { + auto ce = php_get_persistent_class(class_id, class_name); + return php::getMethod(ce, method_name); + }); } -zend_function *php_get_internal_method(int func_id, const php::Str &method_name, int class_id, const php::Str &class_name) { - if (UNEXPECTED(php_internal_func_map[func_id] == nullptr)) { - auto ce = php_get_internal_class(class_id, class_name); - php_internal_func_map[func_id] = php::getMethod(ce, method_name); - } - return php_internal_func_map[func_id]; -} - -uint32_t php_get_internal_prop(int prop_id, const php::Str &prop_name, int class_id, const php::Str &class_name) { - if (UNEXPECTED(php_internal_property_map[prop_id] == 0)) { - php_internal_property_map[prop_id] = php::getPropertyOffset(class_name, prop_name) + 1024; - } - return php_internal_property_map[prop_id] - 1024; +uint32_t php_get_persistent_prop(int prop_id, const php::Str &prop_name, int class_id, const php::Str &class_name) { + auto value = php::getPersistentCache(php_persistent_property_map[prop_id], [&]() { + return php::getPropertyOffset(class_name, prop_name) + 1024; + }); + return value - 1024; } CODE; $code .= "\n\n"; - // MINIT 阶段填充内部符号缓存使用的容错查找函数(查找失败返回空,留给运行期惰性重试) - if ($this->internalFuncMap !== []) { - $code .= <<<'CODE' -static zend_function *php_find_internal_function(const php::Str &name) { - auto lc_name = zend_string_tolower(name.str()); - auto fn = (zend_function *) zend_hash_find_ptr(CG(function_table), lc_name); - zend_string_release(lc_name); - return fn; -} - -static zend_function *php_find_internal_method(zend_class_entry *ce, const php::Str &name) { - if (UNEXPECTED(ce == nullptr)) { - return nullptr; - } - auto lc_name = zend_string_tolower(name.str()); - auto fn = (zend_function *) zend_hash_find_ptr(&ce->function_table, lc_name); - zend_string_release(lc_name); - return fn; -} -CODE; - $code .= "\n\n"; - } - $code .= $this->genPythonModuleGetter(); $code .= "// literal strings \n"; @@ -998,13 +968,23 @@ CODE; foreach ($this->registerSymbols as $registerSymbolFn) { $code .= $registerSymbolFn . '(module_number);' . PHP_EOL; } - $code .= $this->genInternalSymbolCacheInit(); $code .= '} zend_end_try();' . PHP_EOL; $code .= 'return SUCCESS;' . PHP_EOL; $code .= '}' . PHP_EOL . PHP_EOL; // minit end $code .= 'PHP_MSHUTDOWN_FUNCTION(' . $this->getModuleName() . ') {' . PHP_EOL; + // The cache owns no Zend symbols, but its pointers must not survive a + // complete module shutdown/startup cycle in an embedded process. + $code .= 'for (auto &slot : ' . self::PREFIX . self::PERSISTENT_CLASS_MAP . ') {' . PHP_EOL; + $code .= $this->getIndent() . 'php::resetPersistentCache(slot);' . PHP_EOL; + $code .= '}' . PHP_EOL; + $code .= 'for (auto &slot : ' . self::PREFIX . self::PERSISTENT_FUNC_MAP . ') {' . PHP_EOL; + $code .= $this->getIndent() . 'php::resetPersistentCache(slot);' . PHP_EOL; + $code .= '}' . PHP_EOL; + $code .= 'for (auto &slot : ' . self::PREFIX . self::PERSISTENT_PROP_MAP . ') {' . PHP_EOL; + $code .= $this->getIndent() . 'php::resetPersistentCache(slot);' . PHP_EOL; + $code .= '}' . PHP_EOL; $code .= 'typephp_uninstall_reflection_attribute_handlers();' . PHP_EOL; $code .= 'return SUCCESS;' . PHP_EOL; $code .= '}' . PHP_EOL . PHP_EOL; @@ -1136,12 +1116,12 @@ CODE; } } - // 扩展模式,需要在 RSHUTDOWN 阶段中清理用户定义的函数、类、属性缓存; - // 内置/编译产物符号在 internal_*_map 中,进程级存活,无需清理 + // 扩展模式,需要在 RSHUTDOWN 阶段中清理用户定义的函数、类缓存; + // 内置/编译产物符号在 persistent_*_map 中,跨请求存活,无需清理; + // 无动态 propMap(属性 offset 缓存仅含进程级稳定的声明属性),无需清理 if ($this->isBuildModeExt()) { $code .= 'std::memset(' . self::PREFIX . self::FUNC_MAP . ', 0, sizeof(' . self::PREFIX . self::FUNC_MAP . '));' . PHP_EOL; $code .= 'std::memset(' . self::PREFIX . self::CLASS_MAP . ', 0, sizeof(' . self::PREFIX . self::CLASS_MAP . '));' . PHP_EOL; - $code .= 'std::memset(' . self::PREFIX . self::PROP_MAP . ', 0, sizeof(' . self::PREFIX . self::PROP_MAP . '));' . PHP_EOL; } $code .= '}' . PHP_EOL . PHP_EOL; @@ -1211,52 +1191,6 @@ CODE; return $file; } - /** - * 生成 MINIT 阶段填充内部符号缓存(进程级类/函数/属性)的代码。 - * 类与函数使用容错查找:跨扩展符号可能因模块初始化顺序尚未注册, - * 查找失败时留空,由运行期 helper 惰性重试(此时抛错语义与拆分前一致)。 - * 属性缓存的所属类必为本模块编译产物(已在上方注册完毕),直接调用 helper 填充。 - */ - protected function genInternalSymbolCacheInit(): string - { - if ($this->internalClassMap === [] && $this->internalFuncMap === [] && $this->internalPropMap === []) { - return ''; - } - $code = '// internal symbol caches' . PHP_EOL; - foreach ($this->internalClassMap as $name => $id) { - $code .= self::PREFIX . self::INTERNAL_CLASS_MAP . '[' . $id . '] = php::getClassEntry(' - . $this->genSymbolNameExpr($name) . ');' . PHP_EOL; - } - foreach ($this->internalFuncMap as $key => $id) { - if (str_contains($key, '::')) { - [$class, $method] = explode('::', $key, 2); - $code .= self::PREFIX . self::INTERNAL_FUNC_MAP . '[' . $id . '] = php_find_internal_method(php::getClassEntry(' - . $this->genSymbolNameExpr($class) . '), ' . $this->genSymbolNameExpr($method) . ');' . PHP_EOL; - } else { - $code .= self::PREFIX . self::INTERNAL_FUNC_MAP . '[' . $id . '] = php_find_internal_function(' - . $this->genSymbolNameExpr($key) . ');' . PHP_EOL; - } - } - foreach ($this->internalPropMap as $key => $id) { - [$class, $prop] = explode('::', $key, 2); - $code .= '(void) php_get_internal_prop(' . $id . ', ' . $this->genSymbolNameExpr($prop) - . ', 0, ' . $this->genSymbolNameExpr($class) . ');' . PHP_EOL; - } - return $code; - } - - /** - * 生成符号名称的 C++ 表达式。优先复用字面量字符串表中的已有条目; - * 表条目在 MINIT 代码生成前已输出,缺失时(不应发生)退化为内联字符串。 - */ - private function genSymbolNameExpr(string $name): string - { - if (!$this->noLiteralStrings && isset($this->literalStrings[$name])) { - return self::LITERAL_STRINGS . '[' . $this->literalStrings[$name] . ']'; - } - return Type::STR . '{ZEND_STRL(' . $this->genCharPtr($name, true) . '), true}'; - } - public function getModuleName(): string { return Constants::EXTENSION_PREFIX . $this->targetName;