From 41e11a66e6e3db82318ddaf48476c8204dd43933 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Sat, 22 Aug 2026 14:38:34 +0800 Subject: [PATCH] refactor(build): update runtime initialization and linking for native modules - Replace direct typephp_runtime_init calls with TYPEPHP_RUNTIME_INIT macro - Add conditional linking for Unix PHP extensions to prevent duplicate ZendVM - Implement dynamic lookup for macOS extensions to resolve host symbols - Move typephp_helper.h from misc to include directory - Update WASM interface generator to use project-specific runtime symbols - Add tests for native module linking behavior and macOS extension options - Refactor Translator to generate proper embed module entry functions - Update documentation for new runtime initialization patterns --- docs/CPP_SYMBOL_NAMING.md | 24 +++++++++-- docs/SCOPE_MANAGEMENT.md | 4 +- docs/WASI_BUILD.md | 9 ++-- examples/lib-demo/cpp-src/exports.cc | 6 ++- .../cpp-src/typephp_world_api.cc | 10 +++-- .../ocean-godot/cpp-src/typephp_ocean_api.cc | 10 +++-- package.php | 8 ++-- phpunit/src/Backend/BackendOptionsTest.php | 11 +++++ .../Build/NativeBuildConfigurationTest.php | 42 +++++++++++++++++++ .../src/Build/WasmInterfaceGeneratorTest.php | 5 +++ phpunit/src/CompilerBaseApiTest.php | 9 ++-- src/Backend/GccLikeBackend.php | 7 ++++ src/Build/NativeBuildConfigurationTrait.php | 22 ++++++++-- src/Build/NativeCommandOptionsTrait.php | 1 + src/Build/WasmInterfaceGenerator.php | 20 +++++---- src/Translator.php | 21 ++++++---- wasm/build-program.sh | 1 + wasm/build-sdk.sh | 1 + 18 files changed, 165 insertions(+), 46 deletions(-) diff --git a/docs/CPP_SYMBOL_NAMING.md b/docs/CPP_SYMBOL_NAMING.md index 8ea65d73..20d06e49 100644 --- a/docs/CPP_SYMBOL_NAMING.md +++ b/docs/CPP_SYMBOL_NAMING.md @@ -41,7 +41,7 @@ typephp_call_parent_constructor(object, constructor, args); typephp_call_parent_clone(object, clone_method); typephp_install_property_handlers(class_entry, handlers); typephp_write_property_scoped(object, member, value, scope); -typephp_runtime_init(argc, argv); +TYPEPHP_RUNTIME_INIT(project)(argc, argv); ``` ### 2.1 使用边界 @@ -286,9 +286,27 @@ class User ```cpp php__embed_get_module(); +typephp__runtime_init(argc, argv); +typephp__runtime_shutdown(); ``` -它是 binary/library embed runtime 与当前项目 module entry 的连接点。该名称包含项目名并由构建器和 `typephp_main.cc` 成对生成,不得作为通用 helper 命名模板。 +这些是 binary/library embed runtime 与当前项目 module entry 的连接点。定义和引用统一通过 +`TYPEPHP_EMBED_GET_MODULE_FUNCTION()`、`TYPEPHP_RUNTIME_INIT_FUNCTION()`、 +`TYPEPHP_RUNTIME_SHUTDOWN_FUNCTION()` 及对应的符号宏生成,风格与 Zend 的 +`PHP_MINIT_FUNCTION()`/`PHP_MINIT()` 一致。最终符号包含项目名,不得作为通用 helper 命名模板。 + +### 5.4 多扩展进程中的公共运行时 + +TypePHP 扩展不得分别编译或静态链接包含进程级 Zend 状态的 PHPX 实现。Reflection handler、 +`FiberGenerator` class entry、作用域和 Property Hook 运行时均由共享的 `libphpx` 唯一提供: + +- host 模式的 extension/library 必须链接 `libphpx.so`、`libphpx.dylib` 或 `phpx.dll`,不能回退到 `libphpx.a`; +- Unix PHP extension 不链接 Embed `libphp.so`,Zend/PHP 符号由加载它的 SAPI 提供; +- macOS extension 使用 `-undefined dynamic_lookup` 解析宿主符号; +- binary 和独立 WASI 程序仍可以静态链接,因为每个进程或 Wasm 实例只有一份运行时。 + +`src/core/typephp_*.cc` 只承载 TypePHP 专属的 `typephp_*` 运行时;`php::` ZendAPI 包装应放在不带 +`typephp_` 前缀的 core 源文件中,例如 `src/core/scope.cc`。 ## 6. 名称选择流程 @@ -334,5 +352,5 @@ tests/compiler/basic/helper-symbol-collision.phpt | callable 组合冲突检测 | `src/Preprocessor.php` | | `typephp_` 生成及项目私有表 | `src/Translator.php` | | TypePHP extension 前缀常量 | `src/Metadata/Constants.php` | -| PHPX/TypePHP helper 分类 | `vendor/swoole/phpx/src/misc/typephp_helper.h` | +| PHPX/TypePHP helper 分类 | `vendor/swoole/phpx/include/typephp_helper.h` | | embed module accessor 拼接 | `vendor/swoole/phpx/src/misc/typephp_main.cc` | diff --git a/docs/SCOPE_MANAGEMENT.md b/docs/SCOPE_MANAGEMENT.md index 1bd9dcd6..f378ec2c 100644 --- a/docs/SCOPE_MANAGEMENT.md +++ b/docs/SCOPE_MANAGEMENT.md @@ -383,8 +383,8 @@ Scope 修改至少应覆盖以下层次: | `CallableScope` 及 public helper 声明 | `vendor/swoole/phpx/include/phpx.h` | | 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::getCallableScope()` | `vendor/swoole/phpx/src/misc/typephp_helper.h` | +| `UserCodeScopeGuard` | `vendor/swoole/phpx/include/typephp_helper.h`、`src/core/scope.cc` | +| `php::getCallableScope()` | `vendor/swoole/phpx/include/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/WASI_BUILD.md b/docs/WASI_BUILD.md index 2b0bfbc7..b501e2bf 100644 --- a/docs/WASI_BUILD.md +++ b/docs/WASI_BUILD.md @@ -94,7 +94,7 @@ npm run dev command 模式具有生成的 C++ `main()` 入口。入口依次调用: ```text -typephp_runtime_init(argc, argv) +typephp__runtime_init(argc, argv) → php_embed_init() → PHP/SAPI module startup 与 MINIT → PHP request startup 与 RINIT @@ -103,7 +103,7 @@ typephp_runtime_init(argc, argv) 执行 TypePHP main() -typephp_runtime_shutdown() +typephp__runtime_shutdown() → 当前应用的 RSHUTDOWN 与模块清理 → php_embed_shutdown() → PHP request/module/SAPI shutdown @@ -132,7 +132,8 @@ try { } ``` -`createRuntime()` 内部调用 `typephp_runtime_init(1, argv)`。Host 只需要调用这一层稳定接口,不应直接调用 `php_embed_init()`、MINIT、RINIT 或任何 Zend C API。 +`createRuntime()` 内部通过 `TYPEPHP_RUNTIME_INIT()(1, argv)` 调用项目级初始化符号。Host +只需要调用这一层稳定接口,不应直接调用 `php_embed_init()`、MINIT、RINIT 或任何 Zend C API。 当前初始化顺序如下: @@ -156,7 +157,7 @@ try { ### 释放 resource 才会执行 RSHUTDOWN -释放 WIT `runtime` resource 会调用 `typephp_runtime_shutdown()`: +释放 WIT `runtime` resource 会通过 `TYPEPHP_RUNTIME_SHUTDOWN()()` 调用项目级关闭符号: 1. 调用当前 TypePHP 应用模块的 RSHUTDOWN,清理 TypePHP 请求级对象和全局数据。 2. 注销并关闭当前应用模块,执行相应模块清理。 diff --git a/examples/lib-demo/cpp-src/exports.cc b/examples/lib-demo/cpp-src/exports.cc index d356705b..da4169d6 100644 --- a/examples/lib-demo/cpp-src/exports.cc +++ b/examples/lib-demo/cpp-src/exports.cc @@ -2,14 +2,16 @@ #include "../include/typephp_lib_demo.h" #include -extern "C" int typephp_runtime_init(int argc, char **argv); +#include + +TYPEPHP_RUNTIME_INIT_FUNCTION(demo); extern php::Int php_demo_add(php::Int a, php::Int b); extern "C" TYPEPHP_LIB_DEMO_API int typephp_lib_demo_add(int a, int b) { char app_name[] = "typephp_lib_demo"; char *argv[] = {app_name, nullptr}; - if (typephp_runtime_init(1, argv) != 0) { + if (TYPEPHP_RUNTIME_INIT(demo)(1, argv) != 0) { return 0; } return static_cast(php_demo_add(a, b)); diff --git a/examples/minecraft-godot/cpp-src/typephp_world_api.cc b/examples/minecraft-godot/cpp-src/typephp_world_api.cc index 138332f1..c6bab40e 100644 --- a/examples/minecraft-godot/cpp-src/typephp_world_api.cc +++ b/examples/minecraft-godot/cpp-src/typephp_world_api.cc @@ -18,8 +18,10 @@ enum DemoBlockType { static constexpr int DEMO_WATER_LEVEL_C = 4; -extern "C" int typephp_runtime_init(int argc, char **argv); -extern "C" void typephp_runtime_shutdown(); +#include + +TYPEPHP_RUNTIME_INIT_FUNCTION(typephp_world); +TYPEPHP_RUNTIME_SHUTDOWN_FUNCTION(typephp_world); static bool g_typephp_world_initialized = false; @@ -31,7 +33,7 @@ static int typephp_world_ensure_runtime() char app_name[] = "typephp_world"; char *argv[] = {app_name, nullptr}; - if (typephp_runtime_init(1, argv) != 0) { + if (TYPEPHP_RUNTIME_INIT(typephp_world)(1, argv) != 0) { return 0; } @@ -50,7 +52,7 @@ TYPEPHP_WORLD_API void typephp_world_shutdown() return; } - typephp_runtime_shutdown(); + TYPEPHP_RUNTIME_SHUTDOWN(typephp_world)(); g_typephp_world_initialized = false; } diff --git a/examples/ocean-godot/cpp-src/typephp_ocean_api.cc b/examples/ocean-godot/cpp-src/typephp_ocean_api.cc index 8ea06061..b8e48a3b 100644 --- a/examples/ocean-godot/cpp-src/typephp_ocean_api.cc +++ b/examples/ocean-godot/cpp-src/typephp_ocean_api.cc @@ -6,8 +6,10 @@ #define TYPEPHP_OCEAN_API extern "C" __attribute__((visibility("default"))) #endif -extern "C" int typephp_runtime_init(int argc, char **argv); -extern "C" void typephp_runtime_shutdown(); +#include + +TYPEPHP_RUNTIME_INIT_FUNCTION(typephp_ocean); +TYPEPHP_RUNTIME_SHUTDOWN_FUNCTION(typephp_ocean); static bool g_typephp_ocean_initialized = false; @@ -19,7 +21,7 @@ static int typephp_ocean_ensure_runtime() char app_name[] = "typephp_ocean"; char *argv[] = {app_name, nullptr}; - if (typephp_runtime_init(1, argv) != 0) { + if (TYPEPHP_RUNTIME_INIT(typephp_ocean)(1, argv) != 0) { return 0; } @@ -37,7 +39,7 @@ TYPEPHP_OCEAN_API void typephp_ocean_shutdown() if (!g_typephp_ocean_initialized) { return; } - typephp_runtime_shutdown(); + TYPEPHP_RUNTIME_SHUTDOWN(typephp_ocean)(); g_typephp_ocean_initialized = false; } diff --git a/package.php b/package.php index 89aec679..ff853c03 100755 --- a/package.php +++ b/package.php @@ -405,7 +405,7 @@ if (!is_dir($phpxDir)) { mustCopy($phpxLibFile, "{$topLevelDir}/phpx/lib/phpx.lib"); echo " 复制: phpx.lib -> phpx/lib/\n"; - // 复制 phpx/src/misc 目录(辅助工具和头文件) + // 复制 phpx/src/misc 目录(Embed/CLI 运行时适配代码) mustCreateDirectory("{$topLevelDir}/phpx/src/misc"); // 排除 .obj 文件(MSVC 目标文件)和 .d 文件(依赖文件) copyDirectory($phpxMiscDir, "{$topLevelDir}/phpx/src/misc", [], null, ['obj', 'd']); @@ -478,7 +478,8 @@ $requiredArchiveEntries = [ "{$topLevelDir}/{$phpCoreLibRelativePath}", "{$topLevelDir}/phpx/include/phpx.h", "{$topLevelDir}/phpx/lib/phpx.lib", - "{$topLevelDir}/phpx/src/misc/typephp_helper.cc", + "{$topLevelDir}/phpx/include/typephp_helper.h", + "{$topLevelDir}/phpx/include/typephp_runtime.h", ]; foreach ($windowsLinkLibraryFiles as $libraryFile) { $requiredArchiveEntries[] = "{$topLevelDir}/SDK/lib/{$libraryFile}"; @@ -536,7 +537,7 @@ echo " - WINDOWS-SETUP.txt (Windows 环境变量与 PHPX_HOME 配置说明)\n"; echo " - phpx.dll (PHPX 运行时库)\n"; echo " - phpx/include/ (PHPX 头文件)\n"; echo " - phpx/lib/ (PHPX 库文件)\n"; -echo " - phpx/src/misc/ (PHPX 辅助工具和头文件)\n"; +echo " - phpx/src/misc/ (PHPX Embed/CLI runtime adapters)\n"; echo " - PHP 运行时环境 (完整目录结构)\n"; echo " - vendor/ (Composer 依赖包,无需再次安装)\n"; echo " - composer.json (Composer 配置文件)\n"; @@ -616,6 +617,7 @@ function packageUnixLike(): void "{$wasiSdkSourceRoot}/include/php/Zend/zend_config.h", "{$wasiSdkSourceRoot}/include/php/ext/date/lib/timelib_config.h", "{$wasiSdkSourceRoot}/include/phpx/phpx.h", + "{$wasiSdkSourceRoot}/include/phpx/phpx_python.h", "{$wasiSdkSourceRoot}/include/phpx/typephp_helper.h", "{$wasiSdkSourceRoot}/include/gmp.h", "{$wasiSdkSourceRoot}/include/mpfr.h", diff --git a/phpunit/src/Backend/BackendOptionsTest.php b/phpunit/src/Backend/BackendOptionsTest.php index 240848e5..16c46b61 100644 --- a/phpunit/src/Backend/BackendOptionsTest.php +++ b/phpunit/src/Backend/BackendOptionsTest.php @@ -378,6 +378,17 @@ class BackendOptionsTest extends TestCase $this->assertStringContainsString('-shared', $options); } + public function testMacosExtensionResolvesPhpSymbolsFromHost(): void + { + $compiler = new Clang(new Macos()); + $options = $compiler->buildLinkOptions([ + 'build_mode' => 'ext', + ]); + + self::assertStringContainsString('-dynamiclib', $options); + self::assertStringContainsString('-undefined dynamic_lookup', $options); + } + /** * 测试 GCC 链接选项 - RPATH */ diff --git a/phpunit/src/Build/NativeBuildConfigurationTest.php b/phpunit/src/Build/NativeBuildConfigurationTest.php index 8083bd35..1ffaef81 100644 --- a/phpunit/src/Build/NativeBuildConfigurationTest.php +++ b/phpunit/src/Build/NativeBuildConfigurationTest.php @@ -80,6 +80,43 @@ final class NativeBuildConfigurationTest extends TestCase } } + public function testNativeModulesDoNotFallBackToStaticPhpx(): void + { + $phpxDir = $this->temporaryDirectory('phpx-static-module'); + mkdir($phpxDir . '/lib', 0777, true); + touch($phpxDir . '/lib/libphpx.a'); + + $restore = $this->withPhpxHome($phpxDir); + try { + foreach ([CompilerTest::BUILD_MODE_EXT, CompilerTest::BUILD_MODE_LIB] as $mode) { + $compiler = $this->newCompiler(new Linux()); + $compiler->setBuildMode($mode); + self::assertNull($compiler->findPhpxLibraryForTest(), $mode); + } + } finally { + $restore(); + } + } + + public function testUnixExtensionDoesNotLinkEmbedPhpLibrary(): void + { + $phpxDir = $this->temporaryDirectory('phpx-extension-link'); + mkdir($phpxDir . '/lib', 0777, true); + touch($phpxDir . '/lib/libphpx.so'); + + $restore = $this->withPhpxHome($phpxDir); + try { + $compiler = $this->newCompiler(new Linux()); + $compiler->setBuildMode(CompilerTest::BUILD_MODE_EXT); + $libraries = $compiler->getLibrariesForTest(); + + self::assertNotContains('php', $libraries); + self::assertContains($phpxDir . '/lib/libphpx.so', $libraries); + } finally { + $restore(); + } + } + public function testPhpxDirPrefersPhpxHomeOverVendor(): void { $root = $this->temporaryDirectory('phpx-priority-root'); @@ -133,6 +170,11 @@ final class NativeBuildConfigurationTest extends TestCase { $this->validatePhpxLibrary(); } + + public function getLibrariesForTest(): array + { + return $this->getLibraries(); + } }; return $compiler->withPlatform($platform); diff --git a/phpunit/src/Build/WasmInterfaceGeneratorTest.php b/phpunit/src/Build/WasmInterfaceGeneratorTest.php index ae9b6085..4cdda08f 100644 --- a/phpunit/src/Build/WasmInterfaceGeneratorTest.php +++ b/phpunit/src/Build/WasmInterfaceGeneratorTest.php @@ -28,6 +28,7 @@ final class WasmInterfaceGeneratorTest extends TestCase [$function], 'acme:demo@1.0.0', 'demo', + 'demo', static fn (): string => 'php_app__greetuser', ); @@ -45,6 +46,8 @@ final class WasmInterfaceGeneratorTest extends TestCase ); self::assertStringContainsString('auto result = php_app__greetuser(', $adapter); self::assertStringContainsString('catch (zend_object *exception)', $adapter); + self::assertStringContainsString('TYPEPHP_RUNTIME_INIT(demo)(1, argv)', $adapter); + self::assertSame('typephp_demo_runtime_init', $manifest['runtime']['init-symbol']); self::assertSame( "acme:demo/api@1.0.0#create-runtime\n" . "acme:demo/api@1.0.0#[method]runtime.greet-user\n", @@ -67,6 +70,7 @@ final class WasmInterfaceGeneratorTest extends TestCase [$first, $second], 'acme:demo@1.0.0', 'demo', + 'demo', static fn (FunctionDef $function): string => $function->name, ); } @@ -83,6 +87,7 @@ final class WasmInterfaceGeneratorTest extends TestCase [$function], 'acme:demo@1.0.0', 'demo', + 'demo', static fn (): string => 'php_dynamicvalue', ); } diff --git a/phpunit/src/CompilerBaseApiTest.php b/phpunit/src/CompilerBaseApiTest.php index 7835cdbe..1af04faa 100644 --- a/phpunit/src/CompilerBaseApiTest.php +++ b/phpunit/src/CompilerBaseApiTest.php @@ -934,6 +934,7 @@ YAML); $options = $this->invokeMethod('getCommonCompileCommandOptions'); $this->assertContains('TYPEPHP_PROJECT_NAME=module_accessor', $options['user_defines'], $mode); + $this->assertContains('TYPEPHP_RUNTIME_EXPORTS=1', $options['user_defines'], $mode); $this->assertSame( [], array_values(array_filter( @@ -1021,15 +1022,15 @@ YAML); $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->assertStringContainsString('typephp_register_fiber_generator_class();', $extension, $mode); + $this->assertStringContainsString('typephp_unregister_fiber_generator_class();', $extension, $mode); $this->assertStringNotContainsString('php_app_init', $extension, $mode); $this->assertStringNotContainsString('php_app_clean', $extension, $mode); if ($mode === CompilerBase::BUILD_MODE_BIN || $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()', - $extension, - ); + $this->assertStringContainsString('#include ', $extension); + $this->assertStringContainsString('TYPEPHP_EMBED_GET_MODULE_FUNCTION(' . $target . ')', $extension); $this->assertStringContainsString( 'return &' . $namespace . '::' . $namespace . '_module_entry;', $extension, diff --git a/src/Backend/GccLikeBackend.php b/src/Backend/GccLikeBackend.php index a279a4ff..c887d2cc 100644 --- a/src/Backend/GccLikeBackend.php +++ b/src/Backend/GccLikeBackend.php @@ -160,6 +160,13 @@ abstract class GccLikeBackend extends CompilerBackend $flags .= ' -Wl,-z,defs'; } + // A macOS PHP extension intentionally leaves Zend/PHP symbols for + // the host SAPI to resolve. Linking libphp.dylib would create a + // second runtime, so use the platform's standard bundle behavior. + if (($config['build_mode'] ?? null) === 'ext' && $this->platform instanceof \TypePhp\Platform\Macos) { + $flags .= ' -undefined dynamic_lookup'; + } + if ($this->platform instanceof \TypePhp\Platform\Macos && !empty($config['install_name'])) { $flags .= ' ' . $this->platform->getCurrentInstallNameOption($config['install_name']); } diff --git a/src/Build/NativeBuildConfigurationTrait.php b/src/Build/NativeBuildConfigurationTrait.php index dbca0c71..924d8b49 100644 --- a/src/Build/NativeBuildConfigurationTrait.php +++ b/src/Build/NativeBuildConfigurationTrait.php @@ -122,8 +122,12 @@ trait NativeBuildConfigurationTrait $libraries[] = 'libmpdec-4.0.1.dll.lib'; $libraries[] = 'libmpdec++-4.0.1.dll.lib'; } else { - // Linux/macOS: extension 和 bin 模式都需要添加 php 库 - $libraries[] = 'php'; + // Unix PHP extensions resolve Zend/PHP symbols from the host SAPI. + // Linking libphp.so here would load a second ZendVM and give PHPX a + // different set of compiler/executor globals from the host process. + if (!$this->isBuildModeExt()) { + $libraries[] = 'php'; + } $libraries[] = 'gmp'; $libraries[] = 'gmpxx'; $libraries[] = 'mpfr'; @@ -155,6 +159,14 @@ trait NativeBuildConfigurationTrait return $phpxLibPath; } + // Stateful PHPX runtime facilities (global Zend handlers and internal + // classes) must have one process-wide owner when a native module is + // loaded into another process. Statically linking PHPX into each + // extension/library would duplicate that state. + if (!$this->isWasiTarget() && ($this->isBuildModeExt() || $this->isBuildModeLib())) { + return null; + } + $phpxStaticPath = $this->getPhpxDir() . '/lib/libphpx.a'; return is_file($phpxStaticPath) ? $phpxStaticPath : null; } @@ -170,8 +182,10 @@ trait NativeBuildConfigurationTrait $buildHint = 'Build PHPX first (for example, run `nmake phpx` in ' . $this->getPhpxDir() . '\\build)'; } else { $sharedLibExt = ltrim($platform->getSharedLibraryExtension(), '.'); - $expected = $this->getPhpxDir() . '/lib/libphpx.' . $sharedLibExt - . ' or ' . $this->getPhpxDir() . '/lib/libphpx.a'; + $expected = $this->getPhpxDir() . '/lib/libphpx.' . $sharedLibExt; + if ($this->isWasiTarget() || (!$this->isBuildModeExt() && !$this->isBuildModeLib())) { + $expected .= ' or ' . $this->getPhpxDir() . '/lib/libphpx.a'; + } $buildHint = 'Build phpx first (e.g. run `cmake --build ' . $this->getPhpxDir() . '/build`)'; } diff --git a/src/Build/NativeCommandOptionsTrait.php b/src/Build/NativeCommandOptionsTrait.php index 32988202..b1511c85 100644 --- a/src/Build/NativeCommandOptionsTrait.php +++ b/src/Build/NativeCommandOptionsTrait.php @@ -25,6 +25,7 @@ trait NativeCommandOptionsTrait $userDefines = $this->userDefines; if ($this->isBuildModeEmbed()) { $userDefines[] = 'TYPEPHP_PROJECT_NAME=' . $this->targetName; + $userDefines[] = 'TYPEPHP_RUNTIME_EXPORTS=1'; } if ($this->isBuildModeLib()) { $userDefines[] = 'TYPEPHP_NO_MAIN=1'; diff --git a/src/Build/WasmInterfaceGenerator.php b/src/Build/WasmInterfaceGenerator.php index 2177e018..1ab059be 100644 --- a/src/Build/WasmInterfaceGenerator.php +++ b/src/Build/WasmInterfaceGenerator.php @@ -18,8 +18,12 @@ final class WasmInterfaceGenerator iterable $functions, string $package, string $world, + string $runtimeProject, callable $nativeName, ): array { + if (!preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/D', $runtimeProject)) { + throw new RuntimeException("Invalid TypePHP runtime project name `{$runtimeProject}`"); + } $exports = []; $names = []; foreach ($functions as $function) { @@ -86,8 +90,9 @@ final class WasmInterfaceGenerator 'runtime' => [ 'threading' => 'nts', 'lifecycle' => 'wit-resource', - 'init-symbol' => 'typephp_runtime_init', - 'shutdown-symbol' => 'typephp_runtime_shutdown', + 'project' => $runtimeProject, + 'init-symbol' => 'typephp_' . $runtimeProject . '_runtime_init', + 'shutdown-symbol' => 'typephp_' . $runtimeProject . '_runtime_shutdown', 'error-model' => 'result', ], 'functions' => $exports, @@ -170,10 +175,11 @@ final class WasmInterfaceGenerator '#include ', '#include ', '#include ', + '#include ', '#include "' . $this->cName($manifest['world']) . '.h"', '', - 'extern "C" int typephp_runtime_init(int argc, char **argv);', - 'extern "C" void typephp_runtime_shutdown();', + 'TYPEPHP_RUNTIME_INIT_FUNCTION(' . $manifest['runtime']['project'] . ');', + 'TYPEPHP_RUNTIME_SHUTDOWN_FUNCTION(' . $manifest['runtime']['project'] . ');', '', 'struct ' . $prefix . '_runtime_t {', ' bool call_active = false;', @@ -229,14 +235,14 @@ final class WasmInterfaceGenerator ' }', ' char program[] = "typephp-component";', ' char *argv[] = {program, nullptr};', - ' if (typephp_runtime_init(1, argv) != 0) {', + ' if (TYPEPHP_RUNTIME_INIT(' . $manifest['runtime']['project'] . ')(1, argv) != 0) {', ' runtime_failed = true;', ' set_error(error, "Unable to initialize the TypePHP runtime");', ' return false;', ' }', ' auto *runtime = new (std::nothrow) ' . $prefix . '_runtime_t();', ' if (runtime == nullptr) {', - ' typephp_runtime_shutdown();', + ' TYPEPHP_RUNTIME_SHUTDOWN(' . $manifest['runtime']['project'] . ')();', ' set_error(error, "Unable to allocate the TypePHP runtime resource");', ' return false;', ' }', @@ -248,7 +254,7 @@ final class WasmInterfaceGenerator 'extern "C" void ' . $prefix . '_runtime_destructor(' . $prefix . '_runtime_t *runtime) {', ' delete runtime;', ' if (runtime_started) {', - ' typephp_runtime_shutdown();', + ' TYPEPHP_RUNTIME_SHUTDOWN(' . $manifest['runtime']['project'] . ')();', ' }', ' runtime_started = false;', ' runtime_failed = false;', diff --git a/src/Translator.php b/src/Translator.php index 3b3218be..f407e5fa 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -816,6 +816,10 @@ class Translator extends Preprocessor $code = $this->genIncludeHeaderFiles(); + if ($this->isBuildModeEmbed()) { + $code .= '#include ' . PHP_EOL; + } + if ($this->isBuildModeLib() && !$this->isWindows()) { // PHPX's embedded runtime references this CLI-only symbol even when main() is disabled. $code .= 'extern "C" void save_ps_args(int, char **) {}' . PHP_EOL; @@ -1007,12 +1011,12 @@ CODE; $code .= 'PHP_MINIT_FUNCTION(' . $this->getModuleName() . ') {' . PHP_EOL; $code .= 'zend_try {' . PHP_EOL; $code .= '// class/interface class entries' . PHP_EOL; - if (!$this->isWasiTarget()) { - $code .= 'typephp_register_fiber_generator_class();' . PHP_EOL; - } $code .= 'if (typephp_install_reflection_attribute_handlers() != SUCCESS) {' . PHP_EOL; $code .= $this->getIndent() . 'return FAILURE;' . PHP_EOL; $code .= '}' . PHP_EOL; + if (!$this->isWasiTarget()) { + $code .= 'typephp_register_fiber_generator_class();' . PHP_EOL; + } $code .= $this->genClassPropertyInit() . PHP_EOL; $code .= '// register symbols' . PHP_EOL; @@ -1036,6 +1040,9 @@ CODE; $code .= 'for (auto &slot : ' . self::PREFIX . self::PERSISTENT_PROP_MAP . ') {' . PHP_EOL; $code .= $this->getIndent() . 'php::resetPersistentCache(slot);' . PHP_EOL; $code .= '}' . PHP_EOL; + if (!$this->isWasiTarget()) { + $code .= 'typephp_unregister_fiber_generator_class();' . PHP_EOL; + } $code .= 'typephp_uninstall_reflection_attribute_handlers();' . PHP_EOL; $code .= 'return SUCCESS;' . PHP_EOL; $code .= '}' . PHP_EOL . PHP_EOL; @@ -1248,7 +1255,7 @@ CODE; $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 .= 'TYPEPHP_EMBED_GET_MODULE_FUNCTION(' . $this->targetName . ') {' . PHP_EOL; $code .= $this->getIndent() . 'return &' . $projectNamespace . '::' . $moduleName . '_module_entry;' . PHP_EOL; $code .= '}' . PHP_EOL; } else { @@ -1453,11 +1460,6 @@ CODE; { $job = $this->maxJob; - if (!$this->isWasiTarget()) { - $sourceFiles[] = $this->getPhpxDir() . '/src/misc/typephp_fiber_generator.cc'; - } - $sourceFiles[] = $this->getPhpxDir() . '/src/misc/typephp_helper.cc'; - // embed 需要 main 函数,以及 cli 的内置函数定义 if ($this->isBuildModeEmbed()) { $sourceFiles[] = $this->getPhpxDir() . '/src/misc/typephp_main.cc'; @@ -1918,6 +1920,7 @@ CODE; $this->symbols->functions(), $package, $world, + $this->targetName, fn (FunctionDef $function): string => self::PREFIX . $this->getNativeName($function->name, $function->namespace), ); diff --git a/wasm/build-program.sh b/wasm/build-program.sh index 730d0e0d..ea2c7dfd 100755 --- a/wasm/build-program.sh +++ b/wasm/build-program.sh @@ -149,6 +149,7 @@ required_headers=( php/Zend/zend_config.h php/ext/date/lib/timelib_config.h phpx/phpx.h + phpx/phpx_python.h phpx/typephp_helper.h zlib.h zconf.h diff --git a/wasm/build-sdk.sh b/wasm/build-sdk.sh index dd275a08..5b17caf5 100755 --- a/wasm/build-sdk.sh +++ b/wasm/build-sdk.sh @@ -104,6 +104,7 @@ required_files=( include/php/main/php.h include/php/main/php_config.h include/phpx/phpx.h + include/phpx/phpx_python.h include/phpx/typephp_helper.h include/zlib.h include/zconf.h