refactor(compiler): replace internal symbol caches with persistent cache mechanism

- Rename internal symbol maps to persistent maps (internal_class_map to
  persistent_class_map, etc.)
- Replace process-level stable symbol caching with module lifecycle
  persistent caching
- Implement lazy initialization of persistent caches after PHP startup
- Add support for built-in class property resolution via reflection
- Remove dynamic property map as all cached properties are module-stable
- Update cache initialization to use atomic operations for ZTS safety
- Modify RSHUTDOWN cleanup to only clear user-defined caches
- Add test coverage for persistent symbol cache behavior
- Update GCC backend to hide extension symbols by default with -fvisibility=hidden
pull/48/head
韩天峰 2 weeks ago
parent 6fb8187b07
commit 7190bea4a5
  1. 15
      phpunit/code/compiler_api/persistent_symbol_cache.php
  2. 10
      phpunit/src/Backend/BackendOptionsTest.php
  3. 35
      phpunit/src/CompilerBaseApiTest.php
  4. 3
      src/Backend/GccLikeBackend.php
  5. 83
      src/CompilerBase.php
  6. 113
      src/Resolver/PropertyAccessResolver.php
  7. 162
      src/Translator.php

@ -0,0 +1,15 @@
<?php
class PersistentSymbolCacheItem
{
public int $value = 1;
}
function exercise_persistent_symbol_cache(
DateTime $date,
PersistentSymbolCacheItem $item,
): string {
ob_start();
echo $date->format('Y-m-d'), $item->value;
return ob_get_clean();
}

@ -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 编译选项 - 调试模式
*/

@ -936,12 +936,45 @@ YAML);
$this->assertStringContainsString('#include <cstring>', $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<zend_class_entry *>', $data);
$this->assertStringContainsString('extern php::PersistentCacheSlot<zend_function *>', $data);
$this->assertStringContainsString('extern php::PersistentCacheSlot<uint32_t>', $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;

@ -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';
}

@ -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<string, int>
*/
protected array $internalClassMap = [];
protected int $internalClassIndex = 0;
protected array $persistentClassMap = [];
protected int $persistentClassIndex = 0;
/**
* @var array<string, int>
*/
@ -296,22 +294,18 @@ class CompilerBase implements PropertyAccessContext
*/
protected array $funcMap = [];
/**
* 内置/编译产物(进程生命周期)函数/方法 → ID,运行期为全局缓存,MINIT 填充,永不清理
* 内置/编译产物(模块生命周期)函数/方法 → ID,PHP 启动完成后惰性填充,RSHUTDOWN 不清理
* key 为函数名或 `Class::method`
* @var array<string, int>
*/
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

@ -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);

@ -720,6 +720,7 @@ class Translator extends Preprocessor
public function genDataDeclarations(string $file): void
{
$lines[] = '#include <phpx.h>';
$lines[] = '#include <typephp_helper.h>';
$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<zend_class_entry *> ' . 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<zend_function *> ' . 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<uint32_t> ' . 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<zend_class_entry *> ' . 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<zend_function *> ' . 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<uint32_t> ' . 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;

Loading…
Cancel
Save