refactor(generator): replace php prefixed helper functions with php namespace

- Replace php_get_class with get_class throughout the codebase
- Replace php_get_persistent_class with get_persistent_class
- Replace php_get_func with get_func and update related helpers
- Replace php_get_persistent_method with get_persistent_method
- Replace php_get_persistent_prop with get_persistent_prop
- Replace php_globals_array with php::globalsArray
- Replace php_deindirect with php::deindirect
- Replace php_get_called_ce with php::getCalledCe
- Replace php_get_callable_scope with php::getCallableScope
- Replace php_get_str with get_str for literal string access
- Update build script to handle TypePHP project module accessor
- Add test case for helper symbol collision prevention
- Modify module initialization from app_init/app_clean to module_init/module_clean
master
韩天峰 4 days ago
parent d80f4a0d20
commit e722501b3d
  1. 2
      docs/AOT_BUILD_SPEED_RESEARCH.md
  2. 6
      docs/OBJECT_CREATION.md
  3. 12
      docs/SCOPE_MANAGEMENT.md
  4. 2
      docs/aot-optimization-priority.md
  5. 9
      phpunit/code/generated-code-indentation.php
  6. 2
      phpunit/src/ClassTest.php
  7. 71
      phpunit/src/CompilerBaseApiTest.php
  8. 9
      phpunit/src/GeneratedCodeIndentationTest.php
  9. 6
      phpunit/src/HotPathCodegenTest.php
  10. 16
      phpunit/src/LocalVariableInitializerTest.php
  11. 6
      phpunit/src/NativePropertyTest.php
  12. 22
      phpunit/src/NewObjectCodegenTest.php
  13. 2
      phpunit/src/Python/PythonModuleTest.php
  14. 4
      phpunit/src/ScopedCallContextTest.php
  15. 9
      phpunit/src/SymbolTest.php
  16. 3
      src/Build/NativeCommandOptionsTrait.php
  17. 21
      src/CompilerBase.php
  18. 8
      src/Generator/CallArgumentGenerator.php
  19. 2
      src/Generator/ClosureGenerator.php
  20. 6
      src/Generator/Symbol.php
  21. 2
      src/Generator/TypeCheckGenerator.php
  22. 2
      src/Optimizer/FuncCallOptimizer.php
  23. 5
      src/Parser/ClassConstantFetchTrait.php
  24. 2
      src/Parser/TypeConversionTrait.php
  25. 130
      src/Translator.php
  26. 95
      tests/compiler/basic/helper-symbol-collision.phpt
  27. 34
      wasm/build-program.sh

@ -276,7 +276,7 @@ clang-format -i <file>
字面量数组与字面量字符串不同:
- **字面量字符串** 可以利用永久字符串,绕开 Zend request 生命周期
- **字面量数组** 必须存在于 `app_init()``app_clean()`,即 PHP 的 `RINIT/RSHUTDOWN` 之间
- **字面量数组** 必须存在于 `module_init()``module_clean()`,即 PHP 的 `RINIT/RSHUTDOWN` 之间
因此后续所有“数组初始化缓存”研究都必须遵守:

@ -88,7 +88,7 @@ class Task
### 4.1 Property Hook 与非对称 set 可见性不单独触发
PHP 8.4 Property Hook、`private(set)` 和 `protected(set)` 会安装 TypePHP 自定义 object handlers,但这本身不要求覆盖 `create_object`。Zend 8.4 的 `object_properties_init()` 直接复制 class default table,不调用 read/write handler;普通 `php_std_create_object` 已能正确设置最终 handlers。
PHP 8.4 Property Hook、`private(set)` 和 `protected(set)` 会安装 TypePHP 自定义 object handlers,但这本身不要求覆盖 `create_object`。Zend 8.4 的 `object_properties_init()` 直接复制 class default table,不调用 read/write handler;普通 `php::stdCreateObject()` 已能正确设置最终 handlers。
只有该类同时含有非空数组、enum case 等运行时默认值时,才需要自定义创建流程。补充初始化必须绕过 setter;即使使用 `zend_std_write_property()`,PHP 8.4 也会根据 Hook 元数据调用 setter。当前生成代码因此使用编译期已知的 property offset,经 PHPX `Object::attr(offset)` 直接更新 backing slot。
@ -115,7 +115,7 @@ TypePHP 父类已经安装自定义 allocator 时,普通子类通常直接继
以下行为不属于 `create_object`
- PHP `__construct()` 的函数体;
- static property 默认值初始化;它在 `php_app_init()` 中完成;
- static property 默认值初始化;它在 `module_init()` 中完成;
- 已由默认属性表表达的标量、`null` 和空数组赋值;
- clone 后重新应用默认值;clone 应复制源对象当前状态,而不是重新创建默认状态。
@ -147,7 +147,7 @@ TypePHP 父类已经安装自定义 allocator 时,普通子类通常直接继
4. 模板初始化发生在对象分配之前,失败时不会遗留一个尚未返回的对象;
5. 后续创建对象时只把模板 zval 复制到目标 backing slot,即增加一次数组引用计数;
6. 某个对象第一次修改该属性时,由 Zend/PHPX 的 `SEPARATE_ARRAY` 执行 copy-on-write;
7. 在 `php_app_clean()` 中释放模板并重置初始化状态,request allocator 分配的 HashTable 不会跨越 RSHUTDOWN。
7. 在 `module_clean()` 中释放模板并重置初始化状态,request allocator 分配的 HashTable 不会跨越 RSHUTDOWN。
以如下默认值为例:

@ -91,13 +91,13 @@ synthetic frame 不会安装到 `EG(current_execute_data)`,因此不会污染
编译器通过 `FunctionContext::$callableScopeVar` 延迟申请 Scope 变量。第一次需要显式 callable scope 时,`getCallableScopeExpr()` 分配临时变量;随后 `genScopeVarDecl()` 将初始化代码提升到函数入口:
```cpp
php::CallableScope tmp_var_1 = php_get_callable_scope(
php_get_persistent_method(...),
php::CallableScope tmp_var_1 = php::getCallableScope(
get_persistent_method(...),
this_
);
```
`php_get_callable_scope()` 根据 `this_` 同时构建 called scope 和真实实例信息。一个方法内所有 scoped call 都引用同一个 `tmp_var_1`,因此循环中的重复调用不会重复创建 synthetic frame。
`php::getCallableScope()` 根据 `this_` 同时构建 called scope 和真实实例信息。一个方法内所有 scoped call 都引用同一个 `tmp_var_1`,因此循环中的重复调用不会重复创建 synthetic frame。
如果方法从未使用 scoped dynamic call、first-class callable 或 scoped callback,编译器不会生成该变量。
@ -205,7 +205,7 @@ FunctionContext::$needsUserCodeCallableScope
当编译器遇到 `call_user_func*` 的动态 callback,或一个已知会同步调用 callback 的内置函数存在无法匹配的参数展开时,`markUserCodeCallableScope()` 设置该标记。状态属于当前 `FunctionContext`,因此普通方法、嵌套 Closure 和 Fiber 各自独立,不会把 guard 错误泄漏到外层函数。每个函数体入口只生成一个:
```cpp
php::CallableScope tmp_var_1 = php_get_callable_scope(..., this_);
php::CallableScope tmp_var_1 = php::getCallableScope(..., this_);
php::UserCodeScopeGuard tmp_var_2{tmp_var_1};
```
@ -358,7 +358,7 @@ save EG(fake_scope)
Scope 修改至少应覆盖以下层次:
- PHPX 单测:`FakeScopeGuard` 保存、嵌套、恢复和提前 `restore()`
- 编译器结构测试:一个方法只生成一个 `php_get_callable_scope()`,多处调用复用同一变量;
- 编译器结构测试:一个方法只生成一个 `php::getCallableScope()`,多处调用复用同一变量;
- PHPT:private/protected callback、非静态 `self::method(...)`、public callback;
- PHPT:callback map 中 public 与 scoped callback 混合;
- PHPT:`...$args` 中 private callback 可调用,异常退出后 scope 已恢复;
@ -384,7 +384,7 @@ Scope 修改至少应覆盖以下层次:
| callable 解析与包装 | `vendor/swoole/phpx/src/core/base.cc`、`vendor/swoole/phpx/src/core/closure.cc` |
| `FakeScopeGuard` | `vendor/swoole/phpx/include/phpx_fake_scope_guard.h` |
| `UserCodeScopeGuard` | `vendor/swoole/phpx/src/misc/typephp_helper.h`、`typephp_main.cc` |
| `php_get_callable_scope()` | `vendor/swoole/phpx/src/misc/typephp_helper.h` |
| `php::getCallableScope()` | `vendor/swoole/phpx/src/misc/typephp_helper.h` |
| callback 标记和 Scope 变量生成 | `src/CompilerBase.php` |
| callback 参数包装 | `src/Generator/CallArgumentGenerator.php` |
| Closure/Fiber fallback guard | `src/Generator/ClosureGenerator.php`、`FiberGenerator.php` |

@ -372,7 +372,7 @@ if ($x instanceof Logger) {
```cpp
// 守卫式去虚拟化
if (x.getInstanceOf(php_get_class(SubFooA))) {
if (x.getInstanceOf(get_class(SubFooA))) {
Aot_SubFooA_method(x); // 直接调用
} else {
Aot_SubFooB_method(x); // 直接调用(最后一种不用判断)

@ -18,3 +18,12 @@ function generatedCodeIndentation(array $items): int
return -1;
}
function generated_empty_function()
{
}
function generated_implicit_return()
{
$value = 1;
}

@ -557,7 +557,7 @@ class ClassTest extends \BaseTest
$cppFile = $compiler->convertFile($testFile);
$this->assertStringContainsString(
'this_.call(php_get_persistent_method(',
'this_.call(get_persistent_method(',
file_get_contents($cppFile),
);
}

@ -925,6 +925,26 @@ YAML);
$this->assertArrayNotHasKey('cxxflags', $options);
}
public function testEmbeddedCompileOptionsPassProjectNameForModuleAccessor(): void
{
$this->compiler->setTargetName('module_accessor');
foreach ([CompilerBase::BUILD_MODE_BIN, CompilerBase::BUILD_MODE_LIB] as $mode) {
$this->setPropertyValue('buildMode', $mode);
$options = $this->invokeMethod('getCommonCompileCommandOptions');
$this->assertContains('TYPEPHP_PROJECT_NAME=module_accessor', $options['user_defines'], $mode);
$this->assertSame(
[],
array_values(array_filter(
$options['user_defines'],
static fn (string $define): bool => str_starts_with($define, 'TYPEPHP_EMBED_GET_MODULE='),
)),
$mode,
);
}
}
public function testMacosNativeBuildOptionsIncludeHomebrewSearchPaths(): void
{
$this->setPropertyValue('platform', new Macos());
@ -996,20 +1016,22 @@ YAML);
$this->assertStringContainsString('namespace ' . $namespace . ' {', $data, $mode);
$this->assertStringContainsString('using namespace ' . $namespace . ';', $data, $mode);
$this->assertStringContainsString('zend_class_entry *php_get_class(', $data, $mode);
$this->assertStringContainsString('zend_class_entry *get_class(', $data, $mode);
$this->assertStringContainsString('namespace ' . $namespace . ' {', $extension, $mode);
$this->assertStringContainsString('zend_class_entry *php_get_class(', $extension, $mode);
$this->assertStringContainsString('zend_class_entry *get_class(', $extension, $mode);
$this->assertStringContainsString('static void module_init()', $extension, $mode);
$this->assertStringContainsString('static void module_clean()', $extension, $mode);
$this->assertStringNotContainsString('php_app_init', $extension, $mode);
$this->assertStringNotContainsString('php_app_clean', $extension, $mode);
if ($mode === CompilerBase::BUILD_MODE_BIN) {
$this->assertStringContainsString('zend_module_entry *php_embed_get_module()', $extension);
if ($mode === CompilerBase::BUILD_MODE_BIN || $mode === CompilerBase::BUILD_MODE_LIB) {
$this->assertStringNotContainsString('zend_module_entry *php_embed_get_module()', $extension);
$this->assertStringContainsString(
'return &' . $namespace . '::' . $namespace . '_module_entry;',
'zend_module_entry *php_' . $target . '_embed_get_module()',
$extension,
);
} elseif ($mode === CompilerBase::BUILD_MODE_LIB) {
$this->assertStringNotContainsString('zend_module_entry *php_embed_get_module()', $extension);
$this->assertStringContainsString(
'zend_module_entry *php_' . $target . '_embed_get_module()',
'return &' . $namespace . '::' . $namespace . '_module_entry;',
$extension,
);
} else {
@ -1043,11 +1065,22 @@ YAML);
$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->assertStringNotContainsString('php_persistent_class_map', $data);
$this->assertStringNotContainsString('php_persistent_func_map', $data);
$this->assertStringNotContainsString('php_persistent_property_map', $data);
$this->assertStringContainsString(
'static php::PersistentCacheSlot<zend_class_entry *> php_persistent_class_map',
$extension,
);
$this->assertStringContainsString(
'static php::PersistentCacheSlot<zend_function *> php_persistent_func_map',
$extension,
);
$this->assertStringContainsString(
'static php::PersistentCacheSlot<uint32_t> php_persistent_property_map',
$extension,
);
$this->assertStringContainsString('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);
@ -1123,13 +1156,19 @@ YAML);
$this->assertStringContainsString('extern php::Var _const_var_EXPORTED_ABI_INT;', $dataHeader);
$this->assertStringContainsString('extern php::Var _const_var_EXPORTED_ABI_STRING;', $dataHeader);
$this->assertStringContainsString('extern php::Var _const_var_EXPORTED_ABI_ARRAY;', $dataHeader);
$this->assertStringContainsString('extern php::Str _literal_strings[', $dataHeader);
$this->assertStringContainsString('extern THREAD_LOCAL zend_function *php_func_map[', $dataHeader);
$this->assertStringContainsString(
'ZEND_ATTRIBUTE_CONST php::Str &get_str(uint32_t index);',
$dataHeader,
);
$this->assertStringNotContainsString('_literal_strings', $dataHeader);
$this->assertStringNotContainsString('php_func_map', $dataHeader);
$this->assertStringNotContainsString('php_class_map', $dataHeader);
$extensionFile = $this->compiler->genExtension();
$extension = file_get_contents($extensionFile);
$this->assertStringContainsString('php::Str php_exported_defaults_arg_0_default_value() {', $extension);
$this->assertStringContainsString('return _literal_strings[', $extension);
$this->assertStringContainsString('static php::Str _literal_strings[]', $extension);
$this->assertStringContainsString('return get_str(', $extension);
$this->assertStringContainsString('php::Array php_exported_variadic_arg_0_default_value() {', $extension);
}

@ -36,5 +36,14 @@ class GeneratedCodeIndentationTest extends \PHPUnit\Framework\TestCase
$this->assertStringNotContainsString("\ntry {", $code);
$this->assertStringNotContainsString("\ncatch (zend_object", $code);
$this->assertDoesNotMatchRegularExpression('/}[ \\t]+}/', $code);
$this->assertStringContainsString(
"php::Var php_generated_empty_function() {\n\treturn php::null;\n}",
$code,
);
$this->assertStringContainsString(
"\tphp::Var value = 1L;\n\n\treturn php::null;\n}",
$code,
);
$this->assertStringNotContainsString('return php::null;}', $code);
}
}

@ -21,7 +21,7 @@ final class HotPathCodegenTest extends \BaseTest
$code = $this->compileFixture();
self::assertMatchesRegularExpression(
'/php::concat\(_literal_strings\[\d+\], limit\)/',
'/php::concat\(get_str\(\d+\), limit\)/',
$code,
);
self::assertStringContainsString('php::concat({', $code);
@ -41,7 +41,7 @@ final class HotPathCodegenTest extends \BaseTest
$code = $this->compileFixture();
self::assertMatchesRegularExpression(
'/hash\.get\(_literal_strings\[\d+\]\)/',
'/hash\.get\(get_str\(\d+\)\)/',
$code,
);
self::assertStringContainsString('str.offsetGet(0L)', $code);
@ -52,7 +52,7 @@ final class HotPathCodegenTest extends \BaseTest
// Compound receivers retain the evaluate-once chain implementation.
self::assertMatchesRegularExpression(
'/php::notEmpty\(hash, \{\{php::ArrayDimFetch, php::Var\(_literal_strings\[\d+\]\)\}\}, tmp_var_\d+\)/',
'/php::notEmpty\(hash, \{\{php::ArrayDimFetch, php::Var\(get_str\(\d+\)\)\}\}, tmp_var_\d+\)/',
$code,
);
}

@ -21,7 +21,7 @@ final class LocalVariableInitializerTest extends \BaseTest
self::assertStringContainsString('php::Var negative = -7L;', $code);
self::assertStringContainsString('php::Var floating = 1.25;', $code);
self::assertStringContainsString('php::Var boolean = true;', $code);
self::assertMatchesRegularExpression('/php::Str string = _literal_strings\[\d+\];/', $code);
self::assertMatchesRegularExpression('/php::Str string = get_str\(\d+\);/', $code);
self::assertStringContainsString('php::Var nullValue = php::null;', $code);
self::assertStringContainsString('php::Var nested;', $code);
@ -53,7 +53,7 @@ final class LocalVariableInitializerTest extends \BaseTest
self::assertStringContainsString('php::Int negative = php::toInt(-7L);', $code);
self::assertStringContainsString('php::Float floating = php::toFloat(1.25);', $code);
self::assertStringContainsString('php::Bool boolean = php::toBool(true);', $code);
self::assertMatchesRegularExpression('/php::Str string = _literal_strings\[\d+\];/', $code);
self::assertMatchesRegularExpression('/php::Str string = get_str\(\d+\);/', $code);
self::assertStringContainsString('php::Var nullValue = php::null;', $code);
}
@ -103,15 +103,15 @@ final class LocalVariableInitializerTest extends \BaseTest
$code = file_get_contents($generated);
self::assertIsString($code);
self::assertStringContainsString('php::Var selfValue = _literal_strings[', $code);
self::assertStringContainsString('php::Var selfValue = get_str(', $code);
self::assertStringContainsString('php::Var parentValue = 128L;', $code);
self::assertStringContainsString('php::Var concreteValue = _literal_strings[', $code);
self::assertStringContainsString('php::Var selfClass = _literal_strings[', $code);
self::assertStringContainsString('php::Var parentClass = _literal_strings[', $code);
self::assertStringContainsString('php::Var unknownClass = _literal_strings[', $code);
self::assertStringContainsString('php::Var concreteValue = get_str(', $code);
self::assertStringContainsString('php::Var selfClass = get_str(', $code);
self::assertStringContainsString('php::Var parentClass = get_str(', $code);
self::assertStringContainsString('php::Var unknownClass = get_str(', $code);
self::assertStringContainsString('php::Var lateStatic;', $code);
self::assertStringContainsString('lateStatic = php::constant(php_get_called_ce(this_)', $code);
self::assertStringContainsString('lateStatic = php::constant(php::getCalledCe(this_)', $code);
self::assertStringContainsString('php::Var external = "', $code);
self::assertStringNotContainsString("php::Var external;\n", $code);
self::assertStringContainsString('php::Var runtimeClassConstant;', $code);

@ -38,7 +38,7 @@ class NativePropertyTest extends \BaseTest
}
$code = file_get_contents($outputFile);
$this->assertStringContainsString('tmp_var_0 = php_get_called_class(this_);', $code);
$this->assertStringContainsString('tmp_var_0 = php::getCalledClass(this_);', $code);
$this->assertStringContainsString('php::getStaticProperty((tmp_var_0.isObject()', $code);
$this->assertStringContainsString('php::getStaticProperty((tmp_var_0.isObject() ? php::fn::get_class(tmp_var_0)', $code);
$this->assertStringContainsString('= php::toInt(value);', $code);
@ -56,8 +56,8 @@ class NativePropertyTest extends \BaseTest
$this->assertStringContainsString('typephp_static_int_ref(this_.attr(', $code);
$this->assertStringContainsString('typephp_static_int_ref(box.attr(', $code);
$this->assertSame(2, substr_count($code, 'typephp_static_int_ref('));
$this->assertStringNotContainsString('this_.attr(php_get_prop(0, _literal_strings[0], 0, _literal_strings[1]), true) +=', $code);
$this->assertStringNotContainsString('box.attr(php_get_prop(0, _literal_strings[0], 0, _literal_strings[1]), true) +=', $code);
$this->assertStringNotContainsString('this_.attr(get_persistent_prop(0, get_str(0), 0, get_str(1)), true) +=', $code);
$this->assertStringNotContainsString('box.attr(get_persistent_prop(0, get_str(0), 0, get_str(1)), true) +=', $code);
}
public function testReadonlyPropertiesDoNotUseNativeScalarReferences(): void

@ -9,11 +9,11 @@ final class NewObjectCodegenTest extends \BaseTest
$code = $this->compileFixture();
self::assertMatchesRegularExpression(
'/php_createknownobjects\([^)]*\) \{[\s\S]*?zend_class_entry \*(tmp_var_\d+) = php_get_persistent_class\([^;]+;[\s\S]*?php::newObject\(\1\)/',
'/php_createknownobjects\([^)]*\) \{[\s\S]*?zend_class_entry \*(tmp_var_\d+) = get_persistent_class\([^;]+;[\s\S]*?php::newObject\(\1\)/',
$code,
);
self::assertStringNotContainsString(
'php::newObject(php_get_persistent_class(',
'php::newObject(get_persistent_class(',
$code,
);
}
@ -23,7 +23,7 @@ final class NewObjectCodegenTest extends \BaseTest
$code = $this->compileFixture();
self::assertMatchesRegularExpression(
'/php_createruntimeobject\([^)]*\)[\s\S]*?php::newObject\(php_get_class\(/',
'/php_createruntimeobject\([^)]*\)[\s\S]*?php::newObject\(get_class\(/',
$code,
);
}
@ -33,23 +33,23 @@ final class NewObjectCodegenTest extends \BaseTest
[, $extension] = $this->compileFixtureAndExtension();
self::assertStringNotContainsString(
'create_object_KnownNewObjectCodegen = php_get_create_object_fn',
'create_object_KnownNewObjectCodegen = php::getCreateObjectFn',
$extension,
);
self::assertStringNotContainsString(
'create_object_EmptyArrayDefaultCodegen = php_get_create_object_fn',
'create_object_EmptyArrayDefaultCodegen = php::getCreateObjectFn',
$extension,
);
self::assertStringNotContainsString(
'create_object_ScalarExpressionDefaultCodegen = php_get_create_object_fn',
'create_object_ScalarExpressionDefaultCodegen = php::getCreateObjectFn',
$extension,
);
self::assertStringNotContainsString(
'create_object_ScalarConstantDefaultCodegen = php_get_create_object_fn',
'create_object_ScalarConstantDefaultCodegen = php::getCreateObjectFn',
$extension,
);
self::assertStringContainsString(
'create_object_RuntimeArrayDefaultCodegen = php_get_create_object_fn',
'create_object_RuntimeArrayDefaultCodegen = php::getCreateObjectFn',
$extension,
);
self::assertStringNotContainsString(
@ -57,15 +57,15 @@ final class NewObjectCodegenTest extends \BaseTest
$extension,
);
self::assertStringContainsString(
'create_object_EnumPropertyDefaultCodegen = php_get_create_object_fn',
'create_object_EnumPropertyDefaultCodegen = php::getCreateObjectFn',
$extension,
);
self::assertStringNotContainsString(
'create_object_HookOnlyDefaultCodegen = php_get_create_object_fn',
'create_object_HookOnlyDefaultCodegen = php::getCreateObjectFn',
$extension,
);
self::assertStringNotContainsString(
'create_object_AsymmetricOnlyDefaultCodegen = php_get_create_object_fn',
'create_object_AsymmetricOnlyDefaultCodegen = php::getCreateObjectFn',
$extension,
);
}

@ -244,7 +244,7 @@ final class PythonModuleTest extends TestCase
$this->assertStringContainsString('scalar = php::toInt(', $cpp);
$this->assertSame(8, substr_count($cpp, 'php::python::construct('));
$this->assertStringNotContainsString('php::newObject(', $cpp);
$this->assertStringNotContainsString('php::call(php_get_persistent_class(_literal_strings', $cpp);
$this->assertStringNotContainsString('php::call(get_persistent_class(get_str(', $cpp);
$this->assertStringNotContainsString('PyList', $extension);
$this->assertStringNotContainsString('PyDict', $extension);
$this->assertStringContainsString('THREAD_LOCAL zval php_python_module_map[1]', $extension);

@ -13,9 +13,9 @@ class ScopedCallContextTest extends \BaseTest
$cppFile = $compiler->convertFile($testFile);
$cpp = file_get_contents($cppFile);
$this->assertSame(1, substr_count($cpp, 'php_get_callable_scope('));
$this->assertSame(1, substr_count($cpp, 'php::getCallableScope('));
$this->assertMatchesRegularExpression(
'/php::CallableScope (tmp_var_\d+) = php_get_callable_scope\(/',
'/php::CallableScope (tmp_var_\d+) = php::getCallableScope\(/',
$cpp,
);
preg_match('/php::CallableScope (tmp_var_\d+) =/', $cpp, $matches);

@ -4,7 +4,6 @@ namespace TypePhp\Tests;
use PHPUnit\Framework\TestCase;
use TypePhp\Generator\Symbol;
use TypePhp\CompilerBase;
class SymbolTest extends TestCase
{
@ -45,16 +44,12 @@ class SymbolTest extends TestCase
public function testGetCalledCe(): void
{
$result = Symbol::getCalledCe();
$this->assertStringContainsString(CompilerBase::PREFIX, $result);
$this->assertStringContainsString('get_called_ce', $result);
$this->assertSame('php::getCalledCe(this_)', Symbol::getCalledCe());
}
public function testGetCalledClass(): void
{
$result = Symbol::getCalledClass();
$this->assertStringContainsString(CompilerBase::PREFIX, $result);
$this->assertStringContainsString('get_called_class', $result);
$this->assertSame('php::getCalledClass(this_)', Symbol::getCalledClass());
}
public function testSafeIndex(): void

@ -23,6 +23,9 @@ trait NativeCommandOptionsTrait
}
$userDefines = $this->userDefines;
if ($this->isBuildModeEmbed()) {
$userDefines[] = 'TYPEPHP_PROJECT_NAME=' . $this->targetName;
}
if ($this->isBuildModeLib()) {
$userDefines[] = 'TYPEPHP_NO_MAIN=1';
$userDefines[] = $this->getLibraryExportsMacroName() . '=1';

@ -240,6 +240,7 @@ class CompilerBase implements PropertyAccessContext
public const string VALUE_FALSE = 'php::false_';
public const string VALUE_TRUE = 'php::true_';
public const string LITERAL_STRINGS = '_literal_strings';
public const string LITERAL_STRING_GETTER = 'get_str';
public const string ANON_CLASS = '_anon_class_';
public const string DYNAMIC_CALLED_CLASS = '__dynamic_called_class__';
public const string STATIC_VAR = '_static_var_';
@ -881,9 +882,9 @@ class CompilerBase implements PropertyAccessContext
}
// $GLOBALS is an INDIRECT to &EG(symbol_table),
// whose refcount MUST NOT be directly manipulated.
// Use php_globals_array() to create a separated copy.
// Use php::globalsArray() to create a separated copy.
if ($varName === 'GLOBALS') {
return 'php_globals_array()';
return 'php::globalsArray()';
}
return $varName;
case 'Scalar_MagicConst_File':
@ -1335,7 +1336,7 @@ class CompilerBase implements PropertyAccessContext
protected function getClassEntryPtr(string $className): string
{
$id = $this->getClassId($className);
$helper = isset($this->persistentClassMap[$className]) ? 'php_get_persistent_class' : 'php_get_class';
$helper = isset($this->persistentClassMap[$className]) ? 'get_persistent_class' : 'get_class';
return $helper . '(' . $id . ', ' . $this->getLiteralString($className) . ')';
}
@ -1397,7 +1398,7 @@ class CompilerBase implements PropertyAccessContext
throw new \LogicException('Class methods must be resolved through getMethodPtr()');
}
$id = $this->getFuncId($funcName);
$helper = isset($this->persistentFuncMap[$funcName]) ? 'php_get_persistent_func' : 'php_get_func';
$helper = isset($this->persistentFuncMap[$funcName]) ? 'get_persistent_func' : 'get_func';
return $helper . '(' . $id . ', ' . $this->getLiteralString($funcName) . ')';
}
@ -1406,7 +1407,7 @@ class CompilerBase implements PropertyAccessContext
$funcId = $this->getFuncId($class . '::' . $method);
$classId = $this->getClassId($class);
// 方法的稳定性与所属类一致,因此 class_id 必定落在同一张表中
$helper = isset($this->persistentFuncMap[$class . '::' . $method]) ? 'php_get_persistent_method' : 'php_get_method';
$helper = isset($this->persistentFuncMap[$class . '::' . $method]) ? 'get_persistent_method' : 'get_method';
return $helper . '(' . $funcId . ', ' . $this->getLiteralString($method) . ', ' . $classId . ', ' . $this->getLiteralString($class) . ')';
}
@ -1414,7 +1415,7 @@ class CompilerBase implements PropertyAccessContext
{
$propId = $this->getPropertyId($class, $prop);
$classId = $this->getClassId($class);
return 'php_get_persistent_prop(' . $propId . ', ' . $this->getLiteralString($prop) . ', ' . $classId . ', ' . $this->getLiteralString($class) . ')';
return 'get_persistent_prop(' . $propId . ', ' . $this->getLiteralString($prop) . ', ' . $classId . ', ' . $this->getLiteralString($class) . ')';
}
protected function writeLog($msg): void
@ -1430,7 +1431,7 @@ class CompilerBase implements PropertyAccessContext
return $this->getInlineString($string);
}
$index = $this->literalStrings[$string] ?? $this->addLiteralString($string);
return self::LITERAL_STRINGS . '[' . $index . ']';
return self::LITERAL_STRING_GETTER . '(' . $index . ')';
}
/**
@ -1530,7 +1531,7 @@ class CompilerBase implements PropertyAccessContext
{
$this->assertNotNativeObjectArrayKey($expr);
$key = $this->parseIdentifier($expr);
if (str_starts_with($key, self::LITERAL_STRINGS)) {
if (str_starts_with($key, self::LITERAL_STRING_GETTER . '(')) {
$key = "{$key}.str()";
} elseif ($this->isZeroLiteral($expr)) {
$key = self::VALUE_ZERO;
@ -5136,7 +5137,7 @@ class CompilerBase implements PropertyAccessContext
}
if ($this->context->callableScopeVar !== null) {
$code .= $this->getIndent() . 'php::CallableScope '
. $this->context->callableScopeVar . ' = php_get_callable_scope('
. $this->context->callableScopeVar . ' = php::getCallableScope('
. $this->getMethodPtr($this->getFullClassName(), $this->methodDef->name)
. ', this_);' . PHP_EOL;
}
@ -5173,7 +5174,7 @@ class CompilerBase implements PropertyAccessContext
. $this->getClassEntryPtr($className) . ';' . PHP_EOL;
}
foreach ($this->context->globalVars as $name => $type) {
// $GLOBALS is handled via php_globals_array() at each read site
// $GLOBALS is handled via php::globalsArray() at each read site
if ($name === 'GLOBALS') {
continue;
}

@ -662,7 +662,7 @@ trait CallArgumentGenerator
protected function materializeCallArgValue(NodeAbstract $value, string $expr): string
{
// A Native property fetch is a typed C++ pointer, never an INDIRECT
// zval. Passing it through php_deindirect() would box the pointer as a
// zval. Passing it through php::deindirect() would box the pointer as a
// bool/Variant and break the Native ABI. Dynamic Zend calls reject the
// value before reaching here; direct Native calls keep it unchanged.
if ($this->isNativeObjectClass($this->detectClassOfExpr($value))) {
@ -678,7 +678,7 @@ trait CallArgumentGenerator
if (!$this->shouldMaterializeCallArg($value)) {
return $expr;
}
return 'php_deindirect(' . $expr . ')';
return 'php::deindirect(' . $expr . ')';
}
protected function shouldMaterializeCallArg(NodeAbstract $value): bool
@ -836,7 +836,7 @@ trait CallArgumentGenerator
}
$expr = $this->parseIdentifier($arg->value);
if ($this->isVarExpr($arg->value) and $arg->value->name === 'GLOBALS') {
return 'php_globals_array()';
return 'php::globalsArray()';
}
if ($this->isVarExpr($arg->value) and $this->isStdContainer($arg->value->name)) {
return $this->convertArrayExpr($expr . '_ref');
@ -850,7 +850,7 @@ trait CallArgumentGenerator
return $this->parseArg($arg);
}
if ($this->isVarExpr($arg->value) and $arg->value->name === 'GLOBALS') {
return 'php_globals_array()';
return 'php::globalsArray()';
}
if ($this->isVarExpr($arg->value) and $this->isStdContainer($arg->value->name)) {
return $this->convertArrayExpr($this->parseIdentifier($arg->value) . '_ref');

@ -31,7 +31,7 @@ trait ClosureGenerator
// PHP flattens a trait method into the consuming class. A closure
// declared in that method therefore uses the consuming class as
// its lexical scope, never the trait's own class entry.
$scope = 'php_get_called_ce(this_)';
$scope = 'php::getCalledCe(this_)';
} else {
$scope = $this->class
? $this->getClassEntryPtr($this->getFullClassName())

@ -8,8 +8,6 @@
namespace TypePhp\Generator;
use TypePhp\CompilerBase;
class Symbol
{
public static function getStaticProperty(): string
@ -39,12 +37,12 @@ class Symbol
public static function getCalledCe(): string
{
return CompilerBase::PREFIX . 'get_called_ce(this_)';
return 'php::getCalledCe(this_)';
}
public static function getCalledClass(): string
{
return CompilerBase::PREFIX . 'get_called_class(this_)';
return 'php::getCalledClass(this_)';
}
public static function constant(): string

@ -255,7 +255,7 @@ trait TypeCheckGenerator
'iterable' => '(' . $v . '.isArray() || (' . $v . '.isObject() && php::instanceOf(' . $v . ', zend_ce_traversable)))',
'allOf' => $this->genAllOfTypeCondition($varName, $entry['types']),
'instanceof' => $entry['class'] === 'static'
? '(' . $v . '.isObject() && php::instanceOf(' . $v . ', php_get_called_ce(this_)))'
? '(' . $v . '.isObject() && php::instanceOf(' . $v . ', php::getCalledCe(this_)))'
: '(' . $v . '.isObject() && php::instanceOf(' . $v . ', ' . $this->getClassEntryPtr($entry['class']) . '))',
default => '',
};

@ -374,7 +374,7 @@ trait FuncCallOptimizer
{
$arg = $expr->args[$i]->value;
if ($this->isVarExpr($arg) and $arg->name === 'GLOBALS') {
return 'php_globals_array()';
return 'php::globalsArray()';
}
return $this->parseOrderedOperand($arg, false);
}

@ -68,7 +68,10 @@ trait ClassConstantFetchTrait
private function classConstantValueRequiresRuntimeCall(string $value): bool
{
return preg_match('/\b[A-Za-z_][A-Za-z0-9_:]*\s*\(/', $value) === 1;
// get_str() is a pure accessor for the module's immutable literal pool,
// so moving it into a hoisted local initializer preserves semantics.
$value = preg_replace('/\bget_str\(\d+\)/', '', $value);
return preg_match('/\b[A-Za-z_][A-Za-z0-9_:]*\s*\(/', $value ?? '') === 1;
}
/** @return array{mixed}|null */

@ -144,7 +144,7 @@ trait TypeConversionTrait
protected function convertStringExpr(string $expr): string
{
if (preg_match('/^_literal_strings\[\d+\]$/', $expr) === 1) {
if (preg_match('/^get_str\(\d+\)$/', $expr) === 1) {
return $expr;
}
if (!$this->isClosedExpr($expr, 'php::toString')) {

@ -740,9 +740,12 @@ class Translator extends Preprocessor
public function genDataDeclarations(string $file): void
{
$projectNamespace = $this->getProjectNamespace();
$lines[] = '#include <phpx.h>';
$lines[] = '#include <typephp_helper.h>';
$lines[] = PHP_EOL;
$lines[] = 'namespace ' . $projectNamespace . ' {';
$lines[] = PHP_EOL;
// Embedded binaries populate the CLI script fields in $_SERVER at
// request startup, even when the source does not reference $_SERVER.
@ -761,34 +764,26 @@ class Translator extends Preprocessor
}
if ($this->literalStrings) {
$literalStringsCount = count($this->literalStrings);
$lines[] = 'extern ' . Type::STR . ' ' . self::LITERAL_STRINGS . '[' . $literalStringsCount . '];' . PHP_EOL;
$lines[] = 'ZEND_ATTRIBUTE_CONST ' . Type::STR . ' &'
. self::LITERAL_STRING_GETTER . '(uint32_t index);' . PHP_EOL;
}
foreach ($this->constants as $name => $constant) {
$lines[] = 'extern ' . $constant->type . ' ' . $name . ';';
}
// 确保数组大小至少为 1,避免 C/C++ 编译错误
$classCount = max(1, count($this->classMap));
$lines[] = 'extern THREAD_LOCAL zend_class_entry *' . self::PREFIX . self::CLASS_MAP . '[' . $classCount . '];' . 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;
$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;
}
// 无动态 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;
$lines[] = 'zend_class_entry *get_class(int class_id, const php::Str &class_name);';
$lines[] = 'zend_function *get_func(int func_id, const php::Str &func_name);';
$lines[] = 'zend_function *get_method(int func_id, const php::Str &method_name, int class_id, const php::Str &class_name);';
$lines[] = 'zend_class_entry *get_persistent_class(int class_id, const php::Str &class_name);';
$lines[] = 'zend_function *get_persistent_func(int func_id, const php::Str &func_name);';
$lines[] = 'zend_function *get_persistent_method(int func_id, const php::Str &method_name, int class_id, const php::Str &class_name);';
$lines[] = 'uint32_t get_persistent_prop(int prop_id, const php::Str &prop_name, int class_id, const php::Str &class_name);' . PHP_EOL;
foreach ($this->getClassLikesWithConstants() as $classDef) {
foreach ($classDef->constants as $constant) {
@ -799,6 +794,9 @@ class Translator extends Preprocessor
}
}
$lines[] = '} // namespace ' . $projectNamespace;
$lines[] = 'using namespace ' . $projectNamespace . ';';
$code = implode(PHP_EOL, $lines) . PHP_EOL . PHP_EOL;
$this->writeFile($file, $code);
}
@ -833,6 +831,9 @@ class Translator extends Preprocessor
$code .= '}' . PHP_EOL;
}
$projectNamespace = $this->getProjectNamespace();
$code .= 'namespace ' . $projectNamespace . ' {' . PHP_EOL . PHP_EOL;
$code .= "// global vars \n";
foreach ($this->globalVars as $name => $type) {
$cppType = isset($this->nativeGlobalObjects[$name])
@ -852,67 +853,67 @@ 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;
$code .= 'static THREAD_LOCAL zend_class_entry *' . self::PREFIX . self::CLASS_MAP . '[' . max(1, count($this->classMap)) . '];' . 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 .= 'static 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 .= 'php::PersistentCacheSlot<zend_function *> ' . self::PREFIX . self::PERSISTENT_FUNC_MAP . '[' . max(1, count($this->persistentFuncMap)) . ']{};' . PHP_EOL;
$code .= 'static THREAD_LOCAL zend_function *' . self::PREFIX . self::FUNC_MAP . '[' . max(1, count($this->funcMap)) . '];' . PHP_EOL;
$code .= 'static php::PersistentCacheSlot<zend_function *> ' . self::PREFIX . self::PERSISTENT_FUNC_MAP . '[' . max(1, count($this->persistentFuncMap)) . ']{};' . PHP_EOL;
$code .= $this->genPythonModuleStorage();
$code .= "// property \n";
// 无动态 propMap:属性 offset 缓存仅覆盖编译类/内置类的声明属性(见 getPropertyId)
$code .= 'php::PersistentCacheSlot<uint32_t> ' . self::PREFIX . self::PERSISTENT_PROP_MAP . '[' . max(1, count($this->persistentPropMap)) . ']{};' . PHP_EOL;
$code .= 'static php::PersistentCacheSlot<uint32_t> ' . self::PREFIX . self::PERSISTENT_PROP_MAP . '[' . max(1, count($this->persistentPropMap)) . ']{};' . PHP_EOL;
$code .= "// functions \n";
$code .= <<<'CODE'
zend_class_entry *php_get_class(int class_id, const php::Str &class_name) {
zend_class_entry *get_class(int class_id, const php::Str &class_name) {
if (UNEXPECTED(php_class_map[class_id] == nullptr)) {
php_class_map[class_id] = php::getClassEntrySafe(class_name);
}
return php_class_map[class_id];
}
zend_function *php_get_func(int func_id, const php::Str &func_name) {
zend_function *get_func(int func_id, const php::Str &func_name) {
if (UNEXPECTED(php_func_map[func_id] == nullptr)) {
php_func_map[func_id] = php::getFunction(func_name);
}
return php_func_map[func_id];
}
zend_function *php_get_method(int func_id, const php::Str &method_name, int class_id, const php::Str &class_name) {
zend_function *get_method(int func_id, const php::Str &method_name, int class_id, const php::Str &class_name) {
if (UNEXPECTED(php_func_map[func_id] == nullptr)) {
auto ce = php_get_class(class_id, class_name);
auto ce = get_class(class_id, class_name);
php_func_map[func_id] = php::getMethod(ce, method_name);
}
return php_func_map[func_id];
}
zend_class_entry *php_get_persistent_class(int class_id, const php::Str &class_name) {
zend_class_entry *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_function *php_get_persistent_func(int func_id, const php::Str &func_name) {
zend_function *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_persistent_method(int func_id, const php::Str &method_name, int class_id, const php::Str &class_name) {
zend_function *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);
auto ce = get_persistent_class(class_id, class_name);
return php::getMethod(ce, method_name);
});
}
uint32_t php_get_persistent_prop(int prop_id, const php::Str &prop_name, int class_id, const php::Str &class_name) {
uint32_t 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;
});
@ -925,7 +926,7 @@ CODE;
$code .= "// literal strings \n";
if ($this->literalStrings) {
$code .= Type::STR . ' ' . self::LITERAL_STRINGS . '[] = {' . PHP_EOL;
$code .= 'static ' . Type::STR . ' ' . self::LITERAL_STRINGS . '[] = {' . PHP_EOL;
foreach ($this->literalStrings as $str => $index) {
// PHP converts canonical integer-string array keys (for
// example "0" and "-1") to int. literalStrings only accepts
@ -933,13 +934,20 @@ CODE;
$code .= Type::STR . '{ZEND_STRL("' . $this->escapeString((string) $str) . '"), true}, // [' . $index . ']' . PHP_EOL;
}
$code .= '};' . PHP_EOL . PHP_EOL;
$code .= 'ZEND_ATTRIBUTE_CONST ' . Type::STR . ' &'
. self::LITERAL_STRING_GETTER . '(uint32_t index) {' . PHP_EOL;
$code .= $this->getIndent() . 'return ' . self::LITERAL_STRINGS . '[index];' . PHP_EOL;
$code .= '}' . PHP_EOL . PHP_EOL;
} else {
$code .= PHP_EOL;
}
$code .= '} // namespace ' . $projectNamespace . PHP_EOL . PHP_EOL;
$code .= "// default argument values \n";
$code .= $this->genDefaultArgumentHelperDefinitions();
$code .= 'namespace ' . $projectNamespace . ' {' . PHP_EOL . PHP_EOL;
$code .= "// constants \n";
foreach ($this->constants as $name => $const) {
$code .= $const->type . ' ' . $name . ";\n";
@ -1034,8 +1042,8 @@ CODE;
$code .= 'THREAD_LOCAL zval globals_array;' . PHP_EOL;
// php_app_init begin
$code .= 'void php_app_init() {' . PHP_EOL;
// request-level module state initialization
$code .= 'static void module_init() {' . PHP_EOL;
$code .= '// register constants' . PHP_EOL;
foreach ($this->constants as $name => $const) {
$code .= "{$name} = {$const->value};\n";
@ -1092,10 +1100,10 @@ CODE;
$code .= '// class array constants' . PHP_EOL;
$code .= $this->genClassArrayConstants();
$code .= '}' . PHP_EOL . PHP_EOL;
// php_app_init end
// module_init end
// php_app_clean begin
$code .= 'void php_app_clean() {' . PHP_EOL;
// request-level module state cleanup
$code .= 'static void module_clean() {' . PHP_EOL;
foreach ($this->globalVars as $name => $type) {
if ($name != 'GLOBALS') {
if (isset($this->nativeGlobalObjects[$name])) {
@ -1185,13 +1193,13 @@ CODE;
$code .= 'std::memset(' . self::PREFIX . self::CLASS_MAP . ', 0, sizeof(' . self::PREFIX . self::CLASS_MAP . '));' . PHP_EOL;
$code .= '}' . PHP_EOL . PHP_EOL;
// php_app_clean end
// module_clean end
$moduleName = $this->getModuleName();
// rinit begin
$code .= 'PHP_RINIT_FUNCTION(' . $moduleName . ') {' . PHP_EOL;
$code .= 'php::request_init();' . PHP_EOL;
$code .= 'php_app_init();' . PHP_EOL;
$code .= 'module_init();' . PHP_EOL;
if ($this->isBuildModeBin()) {
$entryFunction = $this->symbols->function(self::ENTRY_FUNCTION);
@ -1216,7 +1224,7 @@ CODE;
$code .= <<<CODE
PHP_RSHUTDOWN_FUNCTION({$moduleName}) {
php::request_shutdown();
php_app_clean();
module_clean();
return SUCCESS;
}
@ -1237,10 +1245,14 @@ CODE;
if ($this->isBuildModeExt()) {
$code .= "ZEND_GET_MODULE({$moduleName});\n";
} else {
$code .= 'zend_module_entry *' . self::PREFIX . 'embed_get_module() {' . PHP_EOL;
$code .= $this->getIndent() . 'return &' . $moduleName . '_module_entry;' . PHP_EOL;
$code .= '} // namespace ' . $projectNamespace . PHP_EOL;
} elseif ($this->isBuildModeEmbed()) {
$code .= '} // namespace ' . $projectNamespace . PHP_EOL . PHP_EOL;
$code .= 'zend_module_entry *' . self::PREFIX . $this->targetName . '_embed_get_module() {' . PHP_EOL;
$code .= $this->getIndent() . 'return &' . $projectNamespace . '::' . $moduleName . '_module_entry;' . PHP_EOL;
$code .= '}' . PHP_EOL;
} else {
$code .= '} // namespace ' . $projectNamespace . PHP_EOL;
}
$this->indentLevel--;
@ -1256,6 +1268,11 @@ CODE;
return Constants::EXTENSION_PREFIX . $this->targetName;
}
public function getProjectNamespace(): string
{
return $this->getModuleName();
}
/**
* 检查 phpx/src/misc/ 下的源文件是否已有有效缓存,始终生效(除非指定 --force)。
* 缓存必须匹配编译命令和 PHP ABI,且 .o 文件必须不早于源文件和 phpx 头文件。
@ -2188,7 +2205,7 @@ CODE;
$code .= "typephp_install_property_handlers({$ce}, &{$handlers});\n";
if ($classDef->requireCtor) {
$code .= "create_object_{$className} = php_get_create_object_fn({$ce});\n";
$code .= "create_object_{$className} = php::getCreateObjectFn({$ce});\n";
$code .= "{$ce}->create_object = [](zend_class_entry *class_type) -> zend_object* {\n";
$code .= $buildCreateBody();
$code .= "};\n";
@ -3948,20 +3965,21 @@ CODE;
}
$stmts = '';
if ($v->stmts) {
$this->indentLevel++;
try {
if ($v->stmts) {
$stmts = $this->parseStmts($v->stmts);
if (!$this->isReturnStmtInLastLine($v->stmts)) {
$stmts .= $this->genReturnCode();
}
if (!$this->isReturnStmtInLastLine($v->stmts ?? [])) {
$returnCode = $this->genReturnCode();
if ($returnCode !== '') {
$stmts .= rtrim($returnCode, "\r\n") . PHP_EOL;
}
}
} catch (Skip) {
$this->climate->cyan('Skip function ' . $name);
}
$this->indentLevel--;
} else {
$stmts = $this->genReturnCode();
}
$multiReturn = $this->functionDef->hasMultiReturn();
$cppReturnType = $multiReturn
@ -3987,13 +4005,12 @@ CODE;
$code = $functionDeclCode . ' {' . PHP_EOL;
$this->indentLevel++;
$code .= $this->genScopeVarDecl();
$code .= $this->genNativeObjectParameterChecks($this->functionDef);
$code .= "\n";
$preamble = $this->genScopeVarDecl();
$preamble .= $this->genNativeObjectParameterChecks($this->functionDef);
// Runtime union/nullable parameter type checks
foreach ($this->functionDef->argInfoList as $i => $argInfo) {
if (!empty($argInfo->typeCheck)) {
$code .= $this->genUnionParamCheck($argInfo, $i);
$preamble .= $this->genUnionParamCheck($argInfo, $i);
}
}
// Constructor Property Promotion happens after parameter type validation.
@ -4001,7 +4018,10 @@ CODE;
if (!$argInfo->property) {
continue;
}
$code .= $this->genPropertyPromotion($argInfo);
$preamble .= $this->genPropertyPromotion($argInfo);
}
if ($preamble !== '') {
$code .= $preamble . PHP_EOL;
}
$this->indentLevel--;
// 构建 PHP 级别的函数名用于 debug backtrace

@ -0,0 +1,95 @@
--TEST--
PHPX helper names do not collide with user functions
--FILE--
<?php
function deindirect(mixed $value): string
{
return 'user:' . $value;
}
function globals_array(): string
{
return 'user-globals';
}
function get_called_ce(): string
{
return 'user-called-ce';
}
function get_callable_scope(): string
{
return 'user-callable-scope';
}
function std_create_object(): string
{
return 'user-create-object';
}
function get_create_object_fn(): string
{
return 'user-create-object-fn';
}
function accept_value(mixed $value): mixed
{
return $value;
}
class HelperSymbolCollision
{
public array $values = [2, 3];
private function double(int $value): int
{
return $value * 2;
}
public function run(): array
{
return array_map([$this, 'double'], $this->values);
}
public function calledClass(): string
{
return static::class;
}
}
function main(): void
{
$values = ['key' => 'value'];
echo accept_value($values['key']), PHP_EOL;
var_dump(is_array($GLOBALS));
$object = new HelperSymbolCollision();
var_dump($object->run());
echo $object->calledClass(), PHP_EOL;
echo deindirect('ok'), PHP_EOL;
echo globals_array(), PHP_EOL;
echo get_called_ce(), PHP_EOL;
echo get_callable_scope(), PHP_EOL;
echo std_create_object(), PHP_EOL;
echo get_create_object_fn(), PHP_EOL;
}
?>
--EXPECT--
value
bool(true)
array(2) {
[0]=>
int(4)
[1]=>
int(6)
}
HelperSymbolCollision
user:ok
user-globals
user-called-ce
user-callable-scope
user-create-object
user-create-object-fn

@ -106,6 +106,29 @@ if [[ ${#generated_sources[@]} -eq 0 ]]; then
exit 1
fi
# typephp_main.cc references the project-specific module accessor. Keep it out
# of the reusable libphpx.a and compile it with this program's project token.
project_name=''
for source in "${generated_sources[@]}"; do
source_name=$(basename "${source}")
if [[ "${source_name}" == extension-*.cc ]]; then
candidate=${source_name#extension-}
candidate=${candidate%.cc}
if [[ -n "${project_name}" && "${project_name}" != "${candidate}" ]]; then
fatal_error "Multiple TypePHP project module sources were generated"
fi
project_name=${candidate}
fi
done
if [[ ! "${project_name}" =~ ^[a-zA-Z0-9_]+$ ]]; then
fatal_error "Unable to determine a valid TypePHP project name from generated sources"
fi
typephp_runtime_source=${phpx_dir}/src/misc/typephp_main.cc
if [[ ! -f "${typephp_runtime_source}" ]]; then
fatal_error "TypePHP embedded runtime source is missing: ${typephp_runtime_source}"
fi
generated_sources+=("${typephp_runtime_source}")
wasi_sdk_stamp=${wasi_sdk_dir}/.typephp-wasi-sdk-abi
if [[ ! -f "${wasi_sdk_stamp}" ]] \
|| ! grep -qx 'typephp-wasip2-sdk-abi-v4' "${wasi_sdk_stamp}"; then
@ -190,11 +213,20 @@ for source in "${generated_sources[@]}"; do
echo "Generated C++ source file not found: ${source}" >&2
exit 1
fi
source_compile_flags=()
if [[ "${source}" == "${typephp_runtime_source}" ]]; then
object=${build_root}/typephp_main.o
source_compile_flags+=("-DTYPEPHP_PROJECT_NAME=${project_name}")
if [[ "${wasm_mode}" == library ]]; then
source_compile_flags+=("-DTYPEPHP_NO_MAIN=1")
fi
else
object=${source%.cc}.o
fi
if [[ "${source}" == *.c ]]; then
"${TYPEPHP_WASI_CC:?TYPEPHP_WASI_CC is required}" -O2 -c "${source}" -o "${object}"
else
"${wasi_cxx}" "${compile_flags[@]}" "${include_flags[@]}" -I"${interface_dir}" -c "${source}" -o "${object}"
"${wasi_cxx}" "${compile_flags[@]}" "${source_compile_flags[@]}" "${include_flags[@]}" -I"${interface_dir}" -c "${source}" -o "${object}"
fi
generated_objects+=("${object}")
done

Loading…
Cancel
Save