From e722501b3d16af4139d6db13d6b5a549ebfcdf13 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Fri, 21 Aug 2026 21:31:56 +0800 Subject: [PATCH] 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 --- docs/AOT_BUILD_SPEED_RESEARCH.md | 2 +- docs/OBJECT_CREATION.md | 6 +- docs/SCOPE_MANAGEMENT.md | 12 +- docs/aot-optimization-priority.md | 2 +- phpunit/code/generated-code-indentation.php | 9 ++ phpunit/src/ClassTest.php | 2 +- phpunit/src/CompilerBaseApiTest.php | 71 +++++++-- phpunit/src/GeneratedCodeIndentationTest.php | 9 ++ phpunit/src/HotPathCodegenTest.php | 6 +- phpunit/src/LocalVariableInitializerTest.php | 16 +- phpunit/src/NativePropertyTest.php | 6 +- phpunit/src/NewObjectCodegenTest.php | 22 +-- phpunit/src/Python/PythonModuleTest.php | 2 +- phpunit/src/ScopedCallContextTest.php | 4 +- phpunit/src/SymbolTest.php | 9 +- src/Build/NativeCommandOptionsTrait.php | 3 + src/CompilerBase.php | 21 +-- src/Generator/CallArgumentGenerator.php | 8 +- src/Generator/ClosureGenerator.php | 2 +- src/Generator/Symbol.php | 6 +- src/Generator/TypeCheckGenerator.php | 2 +- src/Optimizer/FuncCallOptimizer.php | 2 +- src/Parser/ClassConstantFetchTrait.php | 5 +- src/Parser/TypeConversionTrait.php | 2 +- src/Translator.php | 138 ++++++++++-------- .../basic/helper-symbol-collision.phpt | 95 ++++++++++++ wasm/build-program.sh | 36 ++++- 27 files changed, 351 insertions(+), 147 deletions(-) create mode 100644 tests/compiler/basic/helper-symbol-collision.phpt diff --git a/docs/AOT_BUILD_SPEED_RESEARCH.md b/docs/AOT_BUILD_SPEED_RESEARCH.md index b5ae6cb4..d21efa25 100644 --- a/docs/AOT_BUILD_SPEED_RESEARCH.md +++ b/docs/AOT_BUILD_SPEED_RESEARCH.md @@ -276,7 +276,7 @@ clang-format -i 字面量数组与字面量字符串不同: - **字面量字符串** 可以利用永久字符串,绕开 Zend request 生命周期 -- **字面量数组** 必须存在于 `app_init()` 到 `app_clean()`,即 PHP 的 `RINIT/RSHUTDOWN` 之间 +- **字面量数组** 必须存在于 `module_init()` 到 `module_clean()`,即 PHP 的 `RINIT/RSHUTDOWN` 之间 因此后续所有“数组初始化缓存”研究都必须遵守: diff --git a/docs/OBJECT_CREATION.md b/docs/OBJECT_CREATION.md index 973b3d34..75b1291b 100644 --- a/docs/OBJECT_CREATION.md +++ b/docs/OBJECT_CREATION.md @@ -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。 以如下默认值为例: diff --git a/docs/SCOPE_MANAGEMENT.md b/docs/SCOPE_MANAGEMENT.md index 08631fb5..1bd9dcd6 100644 --- a/docs/SCOPE_MANAGEMENT.md +++ b/docs/SCOPE_MANAGEMENT.md @@ -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` | diff --git a/docs/aot-optimization-priority.md b/docs/aot-optimization-priority.md index bd16ae5a..a20d536d 100644 --- a/docs/aot-optimization-priority.md +++ b/docs/aot-optimization-priority.md @@ -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); // 直接调用(最后一种不用判断) diff --git a/phpunit/code/generated-code-indentation.php b/phpunit/code/generated-code-indentation.php index e809e93f..c9b8398a 100644 --- a/phpunit/code/generated-code-indentation.php +++ b/phpunit/code/generated-code-indentation.php @@ -18,3 +18,12 @@ function generatedCodeIndentation(array $items): int return -1; } + +function generated_empty_function() +{ +} + +function generated_implicit_return() +{ + $value = 1; +} diff --git a/phpunit/src/ClassTest.php b/phpunit/src/ClassTest.php index d67f65df..748dfc88 100644 --- a/phpunit/src/ClassTest.php +++ b/phpunit/src/ClassTest.php @@ -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), ); } diff --git a/phpunit/src/CompilerBaseApiTest.php b/phpunit/src/CompilerBaseApiTest.php index 68688ad3..7835cdbe 100644 --- a/phpunit/src/CompilerBaseApiTest.php +++ b/phpunit/src/CompilerBaseApiTest.php @@ -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', $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->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 php_persistent_class_map', + $extension, + ); + $this->assertStringContainsString( + 'static php::PersistentCacheSlot php_persistent_func_map', + $extension, + ); + $this->assertStringContainsString( + 'static php::PersistentCacheSlot 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); } diff --git a/phpunit/src/GeneratedCodeIndentationTest.php b/phpunit/src/GeneratedCodeIndentationTest.php index add31d27..06a3ad59 100644 --- a/phpunit/src/GeneratedCodeIndentationTest.php +++ b/phpunit/src/GeneratedCodeIndentationTest.php @@ -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); } } diff --git a/phpunit/src/HotPathCodegenTest.php b/phpunit/src/HotPathCodegenTest.php index e1130de3..97e13f3a 100644 --- a/phpunit/src/HotPathCodegenTest.php +++ b/phpunit/src/HotPathCodegenTest.php @@ -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, ); } diff --git a/phpunit/src/LocalVariableInitializerTest.php b/phpunit/src/LocalVariableInitializerTest.php index fbf54b3b..a406354e 100644 --- a/phpunit/src/LocalVariableInitializerTest.php +++ b/phpunit/src/LocalVariableInitializerTest.php @@ -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); diff --git a/phpunit/src/NativePropertyTest.php b/phpunit/src/NativePropertyTest.php index cf9e288f..fcf61157 100644 --- a/phpunit/src/NativePropertyTest.php +++ b/phpunit/src/NativePropertyTest.php @@ -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 diff --git a/phpunit/src/NewObjectCodegenTest.php b/phpunit/src/NewObjectCodegenTest.php index e5e65b37..afe43231 100644 --- a/phpunit/src/NewObjectCodegenTest.php +++ b/phpunit/src/NewObjectCodegenTest.php @@ -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, ); } diff --git a/phpunit/src/Python/PythonModuleTest.php b/phpunit/src/Python/PythonModuleTest.php index 00249631..0d71624f 100644 --- a/phpunit/src/Python/PythonModuleTest.php +++ b/phpunit/src/Python/PythonModuleTest.php @@ -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); diff --git a/phpunit/src/ScopedCallContextTest.php b/phpunit/src/ScopedCallContextTest.php index ca4f69c9..ab20ffee 100644 --- a/phpunit/src/ScopedCallContextTest.php +++ b/phpunit/src/ScopedCallContextTest.php @@ -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); diff --git a/phpunit/src/SymbolTest.php b/phpunit/src/SymbolTest.php index 288ded6d..1ffcb6df 100644 --- a/phpunit/src/SymbolTest.php +++ b/phpunit/src/SymbolTest.php @@ -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 diff --git a/src/Build/NativeCommandOptionsTrait.php b/src/Build/NativeCommandOptionsTrait.php index 13f56348..32988202 100644 --- a/src/Build/NativeCommandOptionsTrait.php +++ b/src/Build/NativeCommandOptionsTrait.php @@ -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'; diff --git a/src/CompilerBase.php b/src/CompilerBase.php index 1a1bfb29..4b00fd36 100644 --- a/src/CompilerBase.php +++ b/src/CompilerBase.php @@ -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; } diff --git a/src/Generator/CallArgumentGenerator.php b/src/Generator/CallArgumentGenerator.php index 8038dd2b..35b9f5fb 100644 --- a/src/Generator/CallArgumentGenerator.php +++ b/src/Generator/CallArgumentGenerator.php @@ -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'); diff --git a/src/Generator/ClosureGenerator.php b/src/Generator/ClosureGenerator.php index 12a602a9..15297027 100644 --- a/src/Generator/ClosureGenerator.php +++ b/src/Generator/ClosureGenerator.php @@ -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()) diff --git a/src/Generator/Symbol.php b/src/Generator/Symbol.php index 76aa51d1..02d4b9e2 100644 --- a/src/Generator/Symbol.php +++ b/src/Generator/Symbol.php @@ -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 diff --git a/src/Generator/TypeCheckGenerator.php b/src/Generator/TypeCheckGenerator.php index 48a7332d..b6e16df6 100644 --- a/src/Generator/TypeCheckGenerator.php +++ b/src/Generator/TypeCheckGenerator.php @@ -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 => '', }; diff --git a/src/Optimizer/FuncCallOptimizer.php b/src/Optimizer/FuncCallOptimizer.php index eec93303..135f5703 100644 --- a/src/Optimizer/FuncCallOptimizer.php +++ b/src/Optimizer/FuncCallOptimizer.php @@ -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); } diff --git a/src/Parser/ClassConstantFetchTrait.php b/src/Parser/ClassConstantFetchTrait.php index 957271f4..f262e1b3 100644 --- a/src/Parser/ClassConstantFetchTrait.php +++ b/src/Parser/ClassConstantFetchTrait.php @@ -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 */ diff --git a/src/Parser/TypeConversionTrait.php b/src/Parser/TypeConversionTrait.php index 055a7d26..62a86066 100644 --- a/src/Parser/TypeConversionTrait.php +++ b/src/Parser/TypeConversionTrait.php @@ -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')) { diff --git a/src/Translator.php b/src/Translator.php index fff36921..3b3218be 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -740,9 +740,12 @@ class Translator extends Preprocessor public function genDataDeclarations(string $file): void { + $projectNamespace = $this->getProjectNamespace(); $lines[] = '#include '; $lines[] = '#include '; $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 ' . 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 ' . 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 ' . 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 ' . self::PREFIX . self::PERSISTENT_CLASS_MAP . '[' . max(1, count($this->persistentClassMap)) . ']{};' . PHP_EOL; + $code .= 'static 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 .= 'php::PersistentCacheSlot ' . 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 ' . 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 ' . self::PREFIX . self::PERSISTENT_PROP_MAP . '[' . max(1, count($this->persistentPropMap)) . ']{};' . PHP_EOL; + $code .= 'static php::PersistentCacheSlot ' . 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 .= <<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 { + $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(); + } catch (Skip) { + $this->climate->cyan('Skip function ' . $name); } + $this->indentLevel--; $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 diff --git a/tests/compiler/basic/helper-symbol-collision.phpt b/tests/compiler/basic/helper-symbol-collision.phpt new file mode 100644 index 00000000..bd7fbf88 --- /dev/null +++ b/tests/compiler/basic/helper-symbol-collision.phpt @@ -0,0 +1,95 @@ +--TEST-- +PHPX helper names do not collide with user functions +--FILE-- +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 diff --git a/wasm/build-program.sh b/wasm/build-program.sh index a3ae4e92..730d0e0d 100755 --- a/wasm/build-program.sh +++ b/wasm/build-program.sh @@ -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 - object=${source%.cc}.o + 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