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
master
韩天峰 4 days ago
parent 0f6cc17450
commit 41e11a66e6
  1. 24
      docs/CPP_SYMBOL_NAMING.md
  2. 4
      docs/SCOPE_MANAGEMENT.md
  3. 9
      docs/WASI_BUILD.md
  4. 6
      examples/lib-demo/cpp-src/exports.cc
  5. 10
      examples/minecraft-godot/cpp-src/typephp_world_api.cc
  6. 10
      examples/ocean-godot/cpp-src/typephp_ocean_api.cc
  7. 8
      package.php
  8. 11
      phpunit/src/Backend/BackendOptionsTest.php
  9. 42
      phpunit/src/Build/NativeBuildConfigurationTest.php
  10. 5
      phpunit/src/Build/WasmInterfaceGeneratorTest.php
  11. 9
      phpunit/src/CompilerBaseApiTest.php
  12. 7
      src/Backend/GccLikeBackend.php
  13. 22
      src/Build/NativeBuildConfigurationTrait.php
  14. 1
      src/Build/NativeCommandOptionsTrait.php
  15. 20
      src/Build/WasmInterfaceGenerator.php
  16. 21
      src/Translator.php
  17. 1
      wasm/build-program.sh
  18. 1
      wasm/build-sdk.sh

@ -41,7 +41,7 @@ typephp_call_parent_constructor(object, constructor, args);
typephp_call_parent_clone(object, clone_method); typephp_call_parent_clone(object, clone_method);
typephp_install_property_handlers(class_entry, handlers); typephp_install_property_handlers(class_entry, handlers);
typephp_write_property_scoped(object, member, value, scope); typephp_write_property_scoped(object, member, value, scope);
typephp_runtime_init(argc, argv); TYPEPHP_RUNTIME_INIT(project)(argc, argv);
``` ```
### 2.1 使用边界 ### 2.1 使用边界
@ -286,9 +286,27 @@ class User
```cpp ```cpp
php_<project>_embed_get_module(); php_<project>_embed_get_module();
typephp_<project>_runtime_init(argc, argv);
typephp_<project>_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. 名称选择流程 ## 6. 名称选择流程
@ -334,5 +352,5 @@ tests/compiler/basic/helper-symbol-collision.phpt
| callable 组合冲突检测 | `src/Preprocessor.php` | | callable 组合冲突检测 | `src/Preprocessor.php` |
| `typephp_<project>` 生成及项目私有表 | `src/Translator.php` | | `typephp_<project>` 生成及项目私有表 | `src/Translator.php` |
| TypePHP extension 前缀常量 | `src/Metadata/Constants.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` | | embed module accessor 拼接 | `vendor/swoole/phpx/src/misc/typephp_main.cc` |

@ -383,8 +383,8 @@ Scope 修改至少应覆盖以下层次:
| `CallableScope` 及 public helper 声明 | `vendor/swoole/phpx/include/phpx.h` | | `CallableScope` 及 public helper 声明 | `vendor/swoole/phpx/include/phpx.h` |
| callable 解析与包装 | `vendor/swoole/phpx/src/core/base.cc`、`vendor/swoole/phpx/src/core/closure.cc` | | 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` | | `FakeScopeGuard` | `vendor/swoole/phpx/include/phpx_fake_scope_guard.h` |
| `UserCodeScopeGuard` | `vendor/swoole/phpx/src/misc/typephp_helper.h`、`typephp_main.cc` | | `UserCodeScopeGuard` | `vendor/swoole/phpx/include/typephp_helper.h`、`src/core/scope.cc` |
| `php::getCallableScope()` | `vendor/swoole/phpx/src/misc/typephp_helper.h` | | `php::getCallableScope()` | `vendor/swoole/phpx/include/typephp_helper.h` |
| callback 标记和 Scope 变量生成 | `src/CompilerBase.php` | | callback 标记和 Scope 变量生成 | `src/CompilerBase.php` |
| callback 参数包装 | `src/Generator/CallArgumentGenerator.php` | | callback 参数包装 | `src/Generator/CallArgumentGenerator.php` |
| Closure/Fiber fallback guard | `src/Generator/ClosureGenerator.php`、`FiberGenerator.php` | | Closure/Fiber fallback guard | `src/Generator/ClosureGenerator.php`、`FiberGenerator.php` |

@ -94,7 +94,7 @@ npm run dev
command 模式具有生成的 C++ `main()` 入口。入口依次调用: command 模式具有生成的 C++ `main()` 入口。入口依次调用:
```text ```text
typephp_runtime_init(argc, argv) typephp_<project>_runtime_init(argc, argv)
→ php_embed_init() → php_embed_init()
→ PHP/SAPI module startup 与 MINIT → PHP/SAPI module startup 与 MINIT
→ PHP request startup 与 RINIT → PHP request startup 与 RINIT
@ -103,7 +103,7 @@ typephp_runtime_init(argc, argv)
执行 TypePHP main() 执行 TypePHP main()
typephp_runtime_shutdown() typephp_<project>_runtime_shutdown()
→ 当前应用的 RSHUTDOWN 与模块清理 → 当前应用的 RSHUTDOWN 与模块清理
→ php_embed_shutdown() → php_embed_shutdown()
→ PHP request/module/SAPI 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(<project>)(1, argv)` 调用项目级初始化符号。Host
只需要调用这一层稳定接口,不应直接调用 `php_embed_init()`、MINIT、RINIT 或任何 Zend C API。
当前初始化顺序如下: 当前初始化顺序如下:
@ -156,7 +157,7 @@ try {
### 释放 resource 才会执行 RSHUTDOWN ### 释放 resource 才会执行 RSHUTDOWN
释放 WIT `runtime` resource 会调用 `typephp_runtime_shutdown()` 释放 WIT `runtime` resource 会通过 `TYPEPHP_RUNTIME_SHUTDOWN(<project>)()` 调用项目级关闭符号
1. 调用当前 TypePHP 应用模块的 RSHUTDOWN,清理 TypePHP 请求级对象和全局数据。 1. 调用当前 TypePHP 应用模块的 RSHUTDOWN,清理 TypePHP 请求级对象和全局数据。
2. 注销并关闭当前应用模块,执行相应模块清理。 2. 注销并关闭当前应用模块,执行相应模块清理。

@ -2,14 +2,16 @@
#include "../include/typephp_lib_demo.h" #include "../include/typephp_lib_demo.h"
#include <phpx.h> #include <phpx.h>
extern "C" int typephp_runtime_init(int argc, char **argv); #include <typephp_runtime.h>
TYPEPHP_RUNTIME_INIT_FUNCTION(demo);
extern php::Int php_demo_add(php::Int a, php::Int b); 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) extern "C" TYPEPHP_LIB_DEMO_API int typephp_lib_demo_add(int a, int b)
{ {
char app_name[] = "typephp_lib_demo"; char app_name[] = "typephp_lib_demo";
char *argv[] = {app_name, nullptr}; char *argv[] = {app_name, nullptr};
if (typephp_runtime_init(1, argv) != 0) { if (TYPEPHP_RUNTIME_INIT(demo)(1, argv) != 0) {
return 0; return 0;
} }
return static_cast<int>(php_demo_add(a, b)); return static_cast<int>(php_demo_add(a, b));

@ -18,8 +18,10 @@ enum DemoBlockType {
static constexpr int DEMO_WATER_LEVEL_C = 4; static constexpr int DEMO_WATER_LEVEL_C = 4;
extern "C" int typephp_runtime_init(int argc, char **argv); #include <typephp_runtime.h>
extern "C" void typephp_runtime_shutdown();
TYPEPHP_RUNTIME_INIT_FUNCTION(typephp_world);
TYPEPHP_RUNTIME_SHUTDOWN_FUNCTION(typephp_world);
static bool g_typephp_world_initialized = false; static bool g_typephp_world_initialized = false;
@ -31,7 +33,7 @@ static int typephp_world_ensure_runtime()
char app_name[] = "typephp_world"; char app_name[] = "typephp_world";
char *argv[] = {app_name, nullptr}; char *argv[] = {app_name, nullptr};
if (typephp_runtime_init(1, argv) != 0) { if (TYPEPHP_RUNTIME_INIT(typephp_world)(1, argv) != 0) {
return 0; return 0;
} }
@ -50,7 +52,7 @@ TYPEPHP_WORLD_API void typephp_world_shutdown()
return; return;
} }
typephp_runtime_shutdown(); TYPEPHP_RUNTIME_SHUTDOWN(typephp_world)();
g_typephp_world_initialized = false; g_typephp_world_initialized = false;
} }

@ -6,8 +6,10 @@
#define TYPEPHP_OCEAN_API extern "C" __attribute__((visibility("default"))) #define TYPEPHP_OCEAN_API extern "C" __attribute__((visibility("default")))
#endif #endif
extern "C" int typephp_runtime_init(int argc, char **argv); #include <typephp_runtime.h>
extern "C" void typephp_runtime_shutdown();
TYPEPHP_RUNTIME_INIT_FUNCTION(typephp_ocean);
TYPEPHP_RUNTIME_SHUTDOWN_FUNCTION(typephp_ocean);
static bool g_typephp_ocean_initialized = false; static bool g_typephp_ocean_initialized = false;
@ -19,7 +21,7 @@ static int typephp_ocean_ensure_runtime()
char app_name[] = "typephp_ocean"; char app_name[] = "typephp_ocean";
char *argv[] = {app_name, nullptr}; char *argv[] = {app_name, nullptr};
if (typephp_runtime_init(1, argv) != 0) { if (TYPEPHP_RUNTIME_INIT(typephp_ocean)(1, argv) != 0) {
return 0; return 0;
} }
@ -37,7 +39,7 @@ TYPEPHP_OCEAN_API void typephp_ocean_shutdown()
if (!g_typephp_ocean_initialized) { if (!g_typephp_ocean_initialized) {
return; return;
} }
typephp_runtime_shutdown(); TYPEPHP_RUNTIME_SHUTDOWN(typephp_ocean)();
g_typephp_ocean_initialized = false; g_typephp_ocean_initialized = false;
} }

@ -405,7 +405,7 @@ if (!is_dir($phpxDir)) {
mustCopy($phpxLibFile, "{$topLevelDir}/phpx/lib/phpx.lib"); mustCopy($phpxLibFile, "{$topLevelDir}/phpx/lib/phpx.lib");
echo " 复制: phpx.lib -> phpx/lib/\n"; echo " 复制: phpx.lib -> phpx/lib/\n";
// 复制 phpx/src/misc 目录(辅助工具和头文件 // 复制 phpx/src/misc 目录(Embed/CLI 运行时适配代码
mustCreateDirectory("{$topLevelDir}/phpx/src/misc"); mustCreateDirectory("{$topLevelDir}/phpx/src/misc");
// 排除 .obj 文件(MSVC 目标文件)和 .d 文件(依赖文件) // 排除 .obj 文件(MSVC 目标文件)和 .d 文件(依赖文件)
copyDirectory($phpxMiscDir, "{$topLevelDir}/phpx/src/misc", [], null, ['obj', 'd']); copyDirectory($phpxMiscDir, "{$topLevelDir}/phpx/src/misc", [], null, ['obj', 'd']);
@ -478,7 +478,8 @@ $requiredArchiveEntries = [
"{$topLevelDir}/{$phpCoreLibRelativePath}", "{$topLevelDir}/{$phpCoreLibRelativePath}",
"{$topLevelDir}/phpx/include/phpx.h", "{$topLevelDir}/phpx/include/phpx.h",
"{$topLevelDir}/phpx/lib/phpx.lib", "{$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) { foreach ($windowsLinkLibraryFiles as $libraryFile) {
$requiredArchiveEntries[] = "{$topLevelDir}/SDK/lib/{$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.dll (PHPX 运行时库)\n";
echo " - phpx/include/ (PHPX 头文件)\n"; echo " - phpx/include/ (PHPX 头文件)\n";
echo " - phpx/lib/ (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 " - PHP 运行时环境 (完整目录结构)\n";
echo " - vendor/ (Composer 依赖包,无需再次安装)\n"; echo " - vendor/ (Composer 依赖包,无需再次安装)\n";
echo " - composer.json (Composer 配置文件)\n"; echo " - composer.json (Composer 配置文件)\n";
@ -616,6 +617,7 @@ function packageUnixLike(): void
"{$wasiSdkSourceRoot}/include/php/Zend/zend_config.h", "{$wasiSdkSourceRoot}/include/php/Zend/zend_config.h",
"{$wasiSdkSourceRoot}/include/php/ext/date/lib/timelib_config.h", "{$wasiSdkSourceRoot}/include/php/ext/date/lib/timelib_config.h",
"{$wasiSdkSourceRoot}/include/phpx/phpx.h", "{$wasiSdkSourceRoot}/include/phpx/phpx.h",
"{$wasiSdkSourceRoot}/include/phpx/phpx_python.h",
"{$wasiSdkSourceRoot}/include/phpx/typephp_helper.h", "{$wasiSdkSourceRoot}/include/phpx/typephp_helper.h",
"{$wasiSdkSourceRoot}/include/gmp.h", "{$wasiSdkSourceRoot}/include/gmp.h",
"{$wasiSdkSourceRoot}/include/mpfr.h", "{$wasiSdkSourceRoot}/include/mpfr.h",

@ -378,6 +378,17 @@ class BackendOptionsTest extends TestCase
$this->assertStringContainsString('-shared', $options); $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 * 测试 GCC 链接选项 - RPATH
*/ */

@ -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 public function testPhpxDirPrefersPhpxHomeOverVendor(): void
{ {
$root = $this->temporaryDirectory('phpx-priority-root'); $root = $this->temporaryDirectory('phpx-priority-root');
@ -133,6 +170,11 @@ final class NativeBuildConfigurationTest extends TestCase
{ {
$this->validatePhpxLibrary(); $this->validatePhpxLibrary();
} }
public function getLibrariesForTest(): array
{
return $this->getLibraries();
}
}; };
return $compiler->withPlatform($platform); return $compiler->withPlatform($platform);

@ -28,6 +28,7 @@ final class WasmInterfaceGeneratorTest extends TestCase
[$function], [$function],
'acme:demo@1.0.0', 'acme:demo@1.0.0',
'demo', 'demo',
'demo',
static fn (): string => 'php_app__greetuser', 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('auto result = php_app__greetuser(', $adapter);
self::assertStringContainsString('catch (zend_object *exception)', $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( self::assertSame(
"acme:demo/api@1.0.0#create-runtime\n" "acme:demo/api@1.0.0#create-runtime\n"
. "acme:demo/api@1.0.0#[method]runtime.greet-user\n", . "acme:demo/api@1.0.0#[method]runtime.greet-user\n",
@ -67,6 +70,7 @@ final class WasmInterfaceGeneratorTest extends TestCase
[$first, $second], [$first, $second],
'acme:demo@1.0.0', 'acme:demo@1.0.0',
'demo', 'demo',
'demo',
static fn (FunctionDef $function): string => $function->name, static fn (FunctionDef $function): string => $function->name,
); );
} }
@ -83,6 +87,7 @@ final class WasmInterfaceGeneratorTest extends TestCase
[$function], [$function],
'acme:demo@1.0.0', 'acme:demo@1.0.0',
'demo', 'demo',
'demo',
static fn (): string => 'php_dynamicvalue', static fn (): string => 'php_dynamicvalue',
); );
} }

@ -934,6 +934,7 @@ YAML);
$options = $this->invokeMethod('getCommonCompileCommandOptions'); $options = $this->invokeMethod('getCommonCompileCommandOptions');
$this->assertContains('TYPEPHP_PROJECT_NAME=module_accessor', $options['user_defines'], $mode); $this->assertContains('TYPEPHP_PROJECT_NAME=module_accessor', $options['user_defines'], $mode);
$this->assertContains('TYPEPHP_RUNTIME_EXPORTS=1', $options['user_defines'], $mode);
$this->assertSame( $this->assertSame(
[], [],
array_values(array_filter( array_values(array_filter(
@ -1021,15 +1022,15 @@ YAML);
$this->assertStringContainsString('zend_class_entry *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_init()', $extension, $mode);
$this->assertStringContainsString('static void module_clean()', $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_init', $extension, $mode);
$this->assertStringNotContainsString('php_app_clean', $extension, $mode); $this->assertStringNotContainsString('php_app_clean', $extension, $mode);
if ($mode === CompilerBase::BUILD_MODE_BIN || $mode === CompilerBase::BUILD_MODE_LIB) { if ($mode === CompilerBase::BUILD_MODE_BIN || $mode === CompilerBase::BUILD_MODE_LIB) {
$this->assertStringNotContainsString('zend_module_entry *php_embed_get_module()', $extension); $this->assertStringNotContainsString('zend_module_entry *php_embed_get_module()', $extension);
$this->assertStringContainsString( $this->assertStringContainsString('#include <typephp_runtime.h>', $extension);
'zend_module_entry *php_' . $target . '_embed_get_module()', $this->assertStringContainsString('TYPEPHP_EMBED_GET_MODULE_FUNCTION(' . $target . ')', $extension);
$extension,
);
$this->assertStringContainsString( $this->assertStringContainsString(
'return &' . $namespace . '::' . $namespace . '_module_entry;', 'return &' . $namespace . '::' . $namespace . '_module_entry;',
$extension, $extension,

@ -160,6 +160,13 @@ abstract class GccLikeBackend extends CompilerBackend
$flags .= ' -Wl,-z,defs'; $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'])) { if ($this->platform instanceof \TypePhp\Platform\Macos && !empty($config['install_name'])) {
$flags .= ' ' . $this->platform->getCurrentInstallNameOption($config['install_name']); $flags .= ' ' . $this->platform->getCurrentInstallNameOption($config['install_name']);
} }

@ -122,8 +122,12 @@ trait NativeBuildConfigurationTrait
$libraries[] = 'libmpdec-4.0.1.dll.lib'; $libraries[] = 'libmpdec-4.0.1.dll.lib';
$libraries[] = 'libmpdec++-4.0.1.dll.lib'; $libraries[] = 'libmpdec++-4.0.1.dll.lib';
} else { } else {
// Linux/macOS: extension 和 bin 模式都需要添加 php 库 // Unix PHP extensions resolve Zend/PHP symbols from the host SAPI.
$libraries[] = 'php'; // 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[] = 'gmp';
$libraries[] = 'gmpxx'; $libraries[] = 'gmpxx';
$libraries[] = 'mpfr'; $libraries[] = 'mpfr';
@ -155,6 +159,14 @@ trait NativeBuildConfigurationTrait
return $phpxLibPath; 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'; $phpxStaticPath = $this->getPhpxDir() . '/lib/libphpx.a';
return is_file($phpxStaticPath) ? $phpxStaticPath : null; 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)'; $buildHint = 'Build PHPX first (for example, run `nmake phpx` in ' . $this->getPhpxDir() . '\\build)';
} else { } else {
$sharedLibExt = ltrim($platform->getSharedLibraryExtension(), '.'); $sharedLibExt = ltrim($platform->getSharedLibraryExtension(), '.');
$expected = $this->getPhpxDir() . '/lib/libphpx.' . $sharedLibExt $expected = $this->getPhpxDir() . '/lib/libphpx.' . $sharedLibExt;
. ' or ' . $this->getPhpxDir() . '/lib/libphpx.a'; 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`)'; $buildHint = 'Build phpx first (e.g. run `cmake --build ' . $this->getPhpxDir() . '/build`)';
} }

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

@ -18,8 +18,12 @@ final class WasmInterfaceGenerator
iterable $functions, iterable $functions,
string $package, string $package,
string $world, string $world,
string $runtimeProject,
callable $nativeName, callable $nativeName,
): array { ): array {
if (!preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/D', $runtimeProject)) {
throw new RuntimeException("Invalid TypePHP runtime project name `{$runtimeProject}`");
}
$exports = []; $exports = [];
$names = []; $names = [];
foreach ($functions as $function) { foreach ($functions as $function) {
@ -86,8 +90,9 @@ final class WasmInterfaceGenerator
'runtime' => [ 'runtime' => [
'threading' => 'nts', 'threading' => 'nts',
'lifecycle' => 'wit-resource', 'lifecycle' => 'wit-resource',
'init-symbol' => 'typephp_runtime_init', 'project' => $runtimeProject,
'shutdown-symbol' => 'typephp_runtime_shutdown', 'init-symbol' => 'typephp_' . $runtimeProject . '_runtime_init',
'shutdown-symbol' => 'typephp_' . $runtimeProject . '_runtime_shutdown',
'error-model' => 'result', 'error-model' => 'result',
], ],
'functions' => $exports, 'functions' => $exports,
@ -170,10 +175,11 @@ final class WasmInterfaceGenerator
'#include <new>', '#include <new>',
'#include <phpx.h>', '#include <phpx.h>',
'#include <typephp_helper.h>', '#include <typephp_helper.h>',
'#include <typephp_runtime.h>',
'#include "' . $this->cName($manifest['world']) . '.h"', '#include "' . $this->cName($manifest['world']) . '.h"',
'', '',
'extern "C" int typephp_runtime_init(int argc, char **argv);', 'TYPEPHP_RUNTIME_INIT_FUNCTION(' . $manifest['runtime']['project'] . ');',
'extern "C" void typephp_runtime_shutdown();', 'TYPEPHP_RUNTIME_SHUTDOWN_FUNCTION(' . $manifest['runtime']['project'] . ');',
'', '',
'struct ' . $prefix . '_runtime_t {', 'struct ' . $prefix . '_runtime_t {',
' bool call_active = false;', ' bool call_active = false;',
@ -229,14 +235,14 @@ final class WasmInterfaceGenerator
' }', ' }',
' char program[] = "typephp-component";', ' char program[] = "typephp-component";',
' char *argv[] = {program, nullptr};', ' char *argv[] = {program, nullptr};',
' if (typephp_runtime_init(1, argv) != 0) {', ' if (TYPEPHP_RUNTIME_INIT(' . $manifest['runtime']['project'] . ')(1, argv) != 0) {',
' runtime_failed = true;', ' runtime_failed = true;',
' set_error(error, "Unable to initialize the TypePHP runtime");', ' set_error(error, "Unable to initialize the TypePHP runtime");',
' return false;', ' return false;',
' }', ' }',
' auto *runtime = new (std::nothrow) ' . $prefix . '_runtime_t();', ' auto *runtime = new (std::nothrow) ' . $prefix . '_runtime_t();',
' if (runtime == nullptr) {', ' if (runtime == nullptr) {',
' typephp_runtime_shutdown();', ' TYPEPHP_RUNTIME_SHUTDOWN(' . $manifest['runtime']['project'] . ')();',
' set_error(error, "Unable to allocate the TypePHP runtime resource");', ' set_error(error, "Unable to allocate the TypePHP runtime resource");',
' return false;', ' return false;',
' }', ' }',
@ -248,7 +254,7 @@ final class WasmInterfaceGenerator
'extern "C" void ' . $prefix . '_runtime_destructor(' . $prefix . '_runtime_t *runtime) {', 'extern "C" void ' . $prefix . '_runtime_destructor(' . $prefix . '_runtime_t *runtime) {',
' delete runtime;', ' delete runtime;',
' if (runtime_started) {', ' if (runtime_started) {',
' typephp_runtime_shutdown();', ' TYPEPHP_RUNTIME_SHUTDOWN(' . $manifest['runtime']['project'] . ')();',
' }', ' }',
' runtime_started = false;', ' runtime_started = false;',
' runtime_failed = false;', ' runtime_failed = false;',

@ -816,6 +816,10 @@ class Translator extends Preprocessor
$code = $this->genIncludeHeaderFiles(); $code = $this->genIncludeHeaderFiles();
if ($this->isBuildModeEmbed()) {
$code .= '#include <typephp_runtime.h>' . PHP_EOL;
}
if ($this->isBuildModeLib() && !$this->isWindows()) { if ($this->isBuildModeLib() && !$this->isWindows()) {
// PHPX's embedded runtime references this CLI-only symbol even when main() is disabled. // 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; $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 .= 'PHP_MINIT_FUNCTION(' . $this->getModuleName() . ') {' . PHP_EOL;
$code .= 'zend_try {' . PHP_EOL; $code .= 'zend_try {' . PHP_EOL;
$code .= '// class/interface class entries' . 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 .= 'if (typephp_install_reflection_attribute_handlers() != SUCCESS) {' . PHP_EOL;
$code .= $this->getIndent() . 'return FAILURE;' . PHP_EOL; $code .= $this->getIndent() . 'return FAILURE;' . PHP_EOL;
$code .= '}' . PHP_EOL; $code .= '}' . PHP_EOL;
if (!$this->isWasiTarget()) {
$code .= 'typephp_register_fiber_generator_class();' . PHP_EOL;
}
$code .= $this->genClassPropertyInit() . PHP_EOL; $code .= $this->genClassPropertyInit() . PHP_EOL;
$code .= '// register symbols' . 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 .= 'for (auto &slot : ' . self::PREFIX . self::PERSISTENT_PROP_MAP . ') {' . PHP_EOL;
$code .= $this->getIndent() . 'php::resetPersistentCache(slot);' . PHP_EOL; $code .= $this->getIndent() . 'php::resetPersistentCache(slot);' . PHP_EOL;
$code .= '}' . 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 .= 'typephp_uninstall_reflection_attribute_handlers();' . PHP_EOL;
$code .= 'return SUCCESS;' . PHP_EOL; $code .= 'return SUCCESS;' . PHP_EOL;
$code .= '}' . PHP_EOL . PHP_EOL; $code .= '}' . PHP_EOL . PHP_EOL;
@ -1248,7 +1255,7 @@ CODE;
$code .= '} // namespace ' . $projectNamespace . PHP_EOL; $code .= '} // namespace ' . $projectNamespace . PHP_EOL;
} elseif ($this->isBuildModeEmbed()) { } elseif ($this->isBuildModeEmbed()) {
$code .= '} // namespace ' . $projectNamespace . PHP_EOL . PHP_EOL; $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 .= $this->getIndent() . 'return &' . $projectNamespace . '::' . $moduleName . '_module_entry;' . PHP_EOL;
$code .= '}' . PHP_EOL; $code .= '}' . PHP_EOL;
} else { } else {
@ -1453,11 +1460,6 @@ CODE;
{ {
$job = $this->maxJob; $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 的内置函数定义 // embed 需要 main 函数,以及 cli 的内置函数定义
if ($this->isBuildModeEmbed()) { if ($this->isBuildModeEmbed()) {
$sourceFiles[] = $this->getPhpxDir() . '/src/misc/typephp_main.cc'; $sourceFiles[] = $this->getPhpxDir() . '/src/misc/typephp_main.cc';
@ -1918,6 +1920,7 @@ CODE;
$this->symbols->functions(), $this->symbols->functions(),
$package, $package,
$world, $world,
$this->targetName,
fn (FunctionDef $function): string => self::PREFIX fn (FunctionDef $function): string => self::PREFIX
. $this->getNativeName($function->name, $function->namespace), . $this->getNativeName($function->name, $function->namespace),
); );

@ -149,6 +149,7 @@ required_headers=(
php/Zend/zend_config.h php/Zend/zend_config.h
php/ext/date/lib/timelib_config.h php/ext/date/lib/timelib_config.h
phpx/phpx.h phpx/phpx.h
phpx/phpx_python.h
phpx/typephp_helper.h phpx/typephp_helper.h
zlib.h zlib.h
zconf.h zconf.h

@ -104,6 +104,7 @@ required_files=(
include/php/main/php.h include/php/main/php.h
include/php/main/php_config.h include/php/main/php_config.h
include/phpx/phpx.h include/phpx/phpx.h
include/phpx/phpx_python.h
include/phpx/typephp_helper.h include/phpx/typephp_helper.h
include/zlib.h include/zlib.h
include/zconf.h include/zconf.h

Loading…
Cancel
Save