From a6a06ca8599225669fdb57cb50415deeec386e15 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Mon, 26 Jan 2026 15:22:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(translator):=20=E5=A2=9E=E5=BC=BAPHP?= =?UTF-8?q?=E5=88=B0C++=E7=BC=96=E8=AF=91=E5=99=A8=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在ClassLikeDef中添加getNamespacedName方法的symbolic参数支持 - 简化compiler.php中的文件扫描逻辑并移除冗余代码 - 在CompilerBase中添加函数定义存储数组和相关属性 - 更新错误处理方法结构并分离基本错误和致命错误处理 - 在extension.cc模板中移除类信息函数声明并添加函数入口表 - 修改属性偏移获取逻辑以支持非符号化命名空间 - 添加嵌入式模块获取函数实现 - 重构gen_stub.php中的类和函数信息生成逻辑 - 移除全局变量genClass并更新命令行选项处理 - 重命名examples/class/test.php为examples/appx/test.php并添加测试常量和函数 - 在Translator中添加PHP源文件和arginfo头文件数组管理 - 更新目标名称验证以检查C++保留关键字 - 实现getFiles方法统一文件路径处理逻辑 - 添加函数包装器生成功能和类注册函数提取 - 优化类CE列表生成逻辑并修复头文件包含顺序问题 - 重构存根文件生成流程并改进错误报告机制 --- bin/compiler.php | 19 +----- bin/gen_stub.php | 35 +++------- examples/{class => appx}/MyClass.php | 0 examples/{class => appx}/main.php | 0 examples/{class => appx}/test.php | 7 ++ src/Php/ClassLikeDef.php | 7 +- src/Php/CompilerBase.php | 22 ++++++- src/Php/Translator.php | 95 +++++++++++++++++++++++----- src/cpp/main.cc | 11 ++-- src/template/extension.cc.php | 29 ++++----- 10 files changed, 141 insertions(+), 84 deletions(-) rename examples/{class => appx}/MyClass.php (100%) rename examples/{class => appx}/main.php (100%) rename examples/{class => appx}/test.php (87%) diff --git a/bin/compiler.php b/bin/compiler.php index 38852197..9c9c3b17 100755 --- a/bin/compiler.php +++ b/bin/compiler.php @@ -14,22 +14,7 @@ if (empty($argv[1])) { $path = $argv[1]; $translator = new Translator(ROOT_PATH); $translator->setIndent(' '); - -$realpath = realpath($path); -if ($realpath === false) { - die("path not exists: $path\n"); -} -$path = $realpath; - -if (is_dir($path)) { - $scanner = new FileScanner($path); - $list = $scanner->scan(); - $targetName = basename($path); -} else { - $list = [$path]; - $targetName = FileScanner::getFileName($path); -} -$translator->setTargetName($targetName); +$list = $translator->getFiles($path); $sourceFiles = []; $objectFiles = []; @@ -79,7 +64,7 @@ $translator->genExternGlobalVars($translator->getIncludeDir() . '/php_global_var // 生成所有全局变量源文件 $extensionSourceFile = $translator->getBuildDir() . '/extension.cc'; -$translator->genExtension($extensionSourceFile, $targetName); +$translator->genExtension($extensionSourceFile); $sourceFiles[] = $extensionSourceFile; // 添加 main.cc 文件 diff --git a/bin/gen_stub.php b/bin/gen_stub.php index f104ada5..f7d15ca5 100755 --- a/bin/gen_stub.php +++ b/bin/gen_stub.php @@ -1,6 +1,7 @@ #!/usr/bin/env php cond}\n"; } - $code .= "static zend_class_entry *register_class_$escapedName(" . (empty($params) ? "void" : implode(", ", $params)) . ")\n"; + $code .= "static zend_class_entry *" . Translator::PREFIX . "register_class_$escapedName(" . (empty($params) ? "void" : implode(", ", $params)) . ")\n"; $code .= "{\n"; @@ -4231,14 +4232,10 @@ class FileInfo { */ public function getAllFuncInfos(): iterable { - global $genClass; - if ($genClass) { - foreach ($this->classInfos as $classInfo) { - yield from $classInfo->funcInfos; - } - } else { - yield from $this->funcInfos; + foreach ($this->classInfos as $classInfo) { + yield from $classInfo->funcInfos; } + yield from $this->funcInfos; } /** @return array */ @@ -5168,8 +5165,6 @@ function generateArgInfoCode( $generatedFuncInfos = []; - global $genClass; - $argInfoCode = generateCodeWithConditions( $fileInfo->getAllFuncInfos(), "\n", static function (FuncInfo $funcInfo) use (&$generatedFuncInfos, $fileInfo) { @@ -5220,16 +5215,14 @@ function generateArgInfoCode( $code .= generateFunctionEntries(null, $fileInfo->funcInfos); - if ($genClass) { - foreach ($fileInfo->classInfos as $classInfo) { - $code .= generateFunctionEntries($classInfo->name, $classInfo->funcInfos, $classInfo->cond); - } + foreach ($fileInfo->classInfos as $classInfo) { + $code .= generateFunctionEntries($classInfo->name, $classInfo->funcInfos, $classInfo->cond); } } $php80MinimumCompatibility = $fileInfo->getMinimumPhpVersionIdCompatibility() === null || $fileInfo->getMinimumPhpVersionIdCompatibility() >= PHP_80_VERSION_ID; - if ($genClass and $fileInfo->generateClassEntries) { + if ($fileInfo->generateClassEntries) { $declaredStrings = []; $attributeInitializationCode = generateFunctionAttributeInitialization($fileInfo->funcInfos, $allConstInfos, $fileInfo->getMinimumPhpVersionIdCompatibility(), null, $declaredStrings); $attributeInitializationCode .= generateGlobalConstantAttributeInitialization($fileInfo->constInfos, $allConstInfos, $fileInfo->getMinimumPhpVersionIdCompatibility(), null, $declaredStrings); @@ -6100,7 +6093,7 @@ function initPhpParser() { function main() { - global $argv, $argc, $genClass, $translator; + global $argv, $argc, $translator; error_reporting(E_ALL); ini_set("precision", "-1"); @@ -6116,7 +6109,7 @@ function main() [ "force-regeneration", "parameter-stats", "help", "verify", "verify-manual", "replace-predefined-constants", "generate-classsynopses", "replace-classsynopses", "generate-methodsynopses", "replace-methodsynopses", - "generate-optimizer-info", "gen-class-info", "gen-func-info", + "generate-optimizer-info", ], $opt_index ); @@ -6132,14 +6125,6 @@ function main() $replaceMethodSynopses = isset($options["replace-methodsynopses"]); $generateOptimizerInfo = isset($options["generate-optimizer-info"]); - if (isset($options["gen-class-info"])) { - $genClass = true; - } elseif (isset($options["gen-func-info"])) { - $genClass = false; - } else { - die("Please specify whether to generate class or function info\n"); - } - $context->forceRegeneration = isset($options["f"]) || isset($options["force-regeneration"]); $context->forceParse = $context->forceRegeneration || $printParameterStats || $verify || $verifyManual || $replacePredefinedConstants || $generateClassSynopses || $generateOptimizerInfo || $replaceClassSynopses || $generateMethodSynopses || $replaceMethodSynopses; diff --git a/examples/class/MyClass.php b/examples/appx/MyClass.php similarity index 100% rename from examples/class/MyClass.php rename to examples/appx/MyClass.php diff --git a/examples/class/main.php b/examples/appx/main.php similarity index 100% rename from examples/class/main.php rename to examples/appx/main.php diff --git a/examples/class/test.php b/examples/appx/test.php similarity index 87% rename from examples/class/test.php rename to examples/appx/test.php index fc0e15b8..04cdb15d 100644 --- a/examples/class/test.php +++ b/examples/appx/test.php @@ -37,3 +37,10 @@ class Test } } +const TEST_CONST = 1.001; + +function my_sum(int $a, int $b) +{ + return $a + $b; +} + diff --git a/src/Php/ClassLikeDef.php b/src/Php/ClassLikeDef.php index 3dfebcba..40c40011 100644 --- a/src/Php/ClassLikeDef.php +++ b/src/Php/ClassLikeDef.php @@ -15,11 +15,14 @@ class ClassLikeDef $this->namespace = $namespace; } - public function getNamespacedName(): string + public function getNamespacedName(bool $symbolic = true): string { if ($this->namespace === '') { return $this->name; } - return str_replace('\\', '_', $this->namespace . '_' . $this->name); + if ($symbolic) { + return str_replace('\\', '_', $this->namespace . '_' . $this->name); + } + return $this->namespace . '\\\\' . $this->name; } } \ No newline at end of file diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 43d33993..420b0b6e 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -104,6 +104,10 @@ class CompilerBase extends \PhpAot\Core\Translator */ protected array $classes = []; protected array $interfaces = []; + /** + * @var array + */ + protected array $functions = []; /** * @var array */ @@ -112,6 +116,10 @@ class CompilerBase extends \PhpAot\Core\Translator * @var array */ protected array $interfacesDefineInFile = []; + /** + * @var array + */ + protected array $functionDefineInFile = []; /** * @var array */ @@ -151,7 +159,6 @@ class CompilerBase extends \PhpAot\Core\Translator protected bool $inLoop = false; protected bool $inAssignExpr = false; protected bool $stubFile = false; - protected bool $stubFileIncluded = false; protected bool $enableProfiler = false; protected Parser $parser; @@ -238,6 +245,7 @@ class CompilerBase extends \PhpAot\Core\Translator $this->strictTypes = false; $this->classesDefineInFile = []; $this->interfacesDefineInFile = []; + $this->functionDefineInFile = []; } protected function resetNamespace(): void @@ -301,6 +309,9 @@ class CompilerBase extends \PhpAot\Core\Translator if ($this->class) { $this->arguments['this_'] = self::TYPE_OBJECT; $this->addLocalVar('this_', self::TYPE_OBJECT); + } else { + $this->functions[$name] = $this->functionDef; + $this->functionDefineInFile[$name] = $this->functionDef; } foreach ($this->functionDef->argInfoList as $argInfo) { @@ -1372,13 +1383,18 @@ class CompilerBase extends \PhpAot\Core\Translator return $this->parseAssignOp($expr, '**='); } - protected function fatalError(Node $node, string $msg): void + protected function error(string $msg): void { - $this->climate->red("Fatal error: $msg in {$this->file}:" . $node->getStartLine()); + $this->climate->red("Fatal error: $msg"); debug_print_backtrace(); exit(255); } + protected function fatalError(Node $node, string $msg): void + { + $this->error("$msg in {$this->file}:{$node->getStartLine()}"); + } + protected function dump(NodeAbstract $v): void { if ($this->debugLine == $v->getStartLine()) { diff --git a/src/Php/Translator.php b/src/Php/Translator.php index c9209b29..83c9d7f1 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -15,6 +15,9 @@ class Translator extends Preprocessor protected string $targetName = 'app'; protected bool $verbose = false; + protected array $phpSrcFiles = []; + protected array $argInfoHeaderFiles = []; + protected array $unsupportedFunctions = [ 'compact', 'extract', @@ -129,13 +132,14 @@ class Translator extends Preprocessor return $this->getCppFile($file); } $phpCode = $this->loadFile($file); - $this->stubFileIncluded = false; + $this->genStubFile($this->file); $this->localHeaders = []; while (true) { try { $cppCode = $this->doConvert($phpCode); $cppFile = $this->getCppFile($file); $this->save($cppCode, $cppFile); + $this->phpSrcFiles[] = $file; return $cppFile; } catch (RedoException $e) { continue; @@ -191,9 +195,33 @@ class Translator extends Preprocessor $this->climate->red('The target name must be a valid identifier'); exit(1); } + if (in_array($name, Constants::CPP_RESERVED_NAMES)) { + $this->climate->red('The target name must not be a reserved keyword'); + exit(1); + } $this->targetName = $name; } + public function getFiles(string $path): array + { + $realpath = realpath($path); + if ($realpath === false) { + die("path not exists: $path\n"); + } + $path = $realpath; + + if (is_dir($path)) { + $scanner = new FileScanner($path); + $list = $scanner->scan(); + $targetName = basename($path); + } else { + $list = [$path]; + $targetName = FileScanner::getFileName($path); + } + $this->setTargetName($targetName); + return $list; + } + protected function getInternalCeInfo(string $ce): array { return [ @@ -273,6 +301,10 @@ class Translator extends Preprocessor $cppCode .= $this->genClassWrapper($interfaceDef); } + foreach ($this->functionDefineInFile as $functionDef) { + $cppCode .= $this->genFunctionWrapper($functionDef); + } + return $this->genIncludeHeaderFiles() . $cppCode; } @@ -330,13 +362,13 @@ class Translator extends Preprocessor protected function genClassCeList(): void { - if (empty($this->interfacesDefineInFile) and empty($this->classesDefineInFile)) { + if (empty($this->interfaces) and empty($this->classes)) { return; } $sorter = new StringSort(); - foreach ($this->interfacesDefineInFile as $interfaceDef) { + foreach ($this->interfaces as $interfaceDef) { $parent = $interfaceDef->extends; $ce = $this->getClassCe($interfaceDef); $deps = []; @@ -359,7 +391,7 @@ class Translator extends Preprocessor $sorter->add($ce, $deps); } - foreach ($this->classesDefineInFile as $classDef) { + foreach ($this->classes as $classDef) { $ce = $this->getClassCe($classDef); $deps = []; $parent = $classDef->extends; @@ -403,11 +435,12 @@ class Translator extends Preprocessor exit(1); } } - $this->localHeaders = []; + $this->localHeaders = $this->argInfoHeaderFiles; $this->genClassCeList(); $code = $this->render('extension.cc.php'); $this->writeFile($file, $code); $this->formatCppCode($file); + $this->localHeaders = []; } public function hasObjectFileCache(string $cppFile): bool @@ -537,28 +570,23 @@ class Translator extends Preprocessor return $code; } - protected function genClassStubFile(Node\Stmt\Class_|Node\Stmt\Trait_ $class, string $file): void + protected function genStubFile(string $file): void { - $genStubCmd = PHP_BINARY. ' ' . $this->rootPath . '/bin/gen_stub.php --gen-class-info -f ' . $file; + $genStubCmd = PHP_BINARY. ' ' . $this->rootPath . '/bin/gen_stub.php -f ' . $file; $output = shell_exec($genStubCmd); $this->climate->info('generate stub file: ' . $file); $this->climate->comment($genStubCmd); $stubFilenameWithoutExtension = str_replace([".stub.php", '.php'], "", $file); $headerFile = $this->getArgInfoHeaderFile($stubFilenameWithoutExtension, true); if (!str_starts_with($output, "Saved")) { - $this->fatalError($class, "failed to generate arginfo header file: `$headerFile`, output: $output"); + $this->error("failed to generate arginfo header file: `$headerFile`, output: $output"); } - $this->localHeaders[] = $headerFile; - $this->stubFileIncluded = true; + $this->argInfoHeaderFiles[] = $headerFile; } protected function parseClass(Node\Stmt\Class_|Node\Stmt\Trait_ $class): string { $this->class = $this->parseIdentifier($class->name); - if (!$this->stubFileIncluded) { - $this->genClassStubFile($class, $this->file); - } - $this->classDef = new ClassDef($this->class, $class->flags, $this->namespace); if ($class->extends) { $this->classDef->extends = $this->parseIdentifier($class->extends); @@ -607,7 +635,7 @@ class Translator extends Preprocessor public function getArgInfoHeaderFile(string $stubFilenameWithoutExtension, bool $relative = false): string { - $basename = basename($stubFilenameWithoutExtension); + $basename = self::PREFIX . basename($stubFilenameWithoutExtension); $absPath = $this->getIncludeDir() . "/{$basename}_arginfo.h"; if ($relative) { return ltrim($this->removeCommonPrefix($this->getIncludeDir(), $absPath), '/'); @@ -659,8 +687,37 @@ class Translator extends Preprocessor return $cppCode; } + private function genFunctionWrapper(FunctionDef $functionDef) + { + $name = $functionDef->name; + $cppCode = 'ZEND_FUNCTION(' . $name . '){' . PHP_EOL; + $fn = self::PREFIX . $this->getNativeName($functionDef->name); - protected function genClassWrapper(ClassDef|InterfaceDef $classDef): string + $callParams = ''; + foreach ($functionDef->argInfoList as $k => $argInfo) { + if ($argInfo->default) { + $argExpr = 'php::getCallArg(' . $k . ', ' . $argInfo->default . ')'; + } else { + $argExpr = 'php::getCallArg(' . $k . ')'; + } + $expr = $this->convertExprFromType($argInfo->type, $argExpr); + $cppCode .= $this->getIndent() . $argInfo->type . ' arg_' . $argInfo->name . ' = ' . $expr . ';' . PHP_EOL; + $callParams .= 'arg_' . $argInfo->name . ','; + } + $callParams = $functionDef->argInfoList ? rtrim($callParams, ',') : ''; + + if ($functionDef->returnType !== self::TYPE_VOID) { + $cppCode .= $this->getIndent() . 'auto retval = ' . $fn . '(' . $callParams . ');' . PHP_EOL; + $cppCode .= $this->getIndent() . 'php::move(retval, return_value);' . PHP_EOL; + } else { + $cppCode .= $this->getIndent() . $fn . '(' . $callParams . ');' . PHP_EOL; + } + $cppCode .= '}' . PHP_EOL . PHP_EOL; + + return $cppCode; + } + + protected function getClassRegisterCeFunc(ClassDef|InterfaceDef $classDef): void { $cppCode = ''; $name = $classDef->getNamespacedName(); @@ -669,6 +726,12 @@ class Translator extends Preprocessor $cppCode .= 'zend_class_entry *' . $this->getRegisterClassFunction($name) . '(' . $argsDef . ') {' . PHP_EOL; $cppCode .= $this->getIndent() . 'return register_class_' . $name . '(' . $param . ');' . PHP_EOL; $cppCode .= '}' . PHP_EOL . PHP_EOL; + } + + + protected function genClassWrapper(ClassDef|InterfaceDef $classDef): string + { + $cppCode = ''; // 接口没有方法实体 if ($classDef instanceof ClassDef) { diff --git a/src/cpp/main.cc b/src/cpp/main.cc index 6b6ad624..c990b9e7 100644 --- a/src/cpp/main.cc +++ b/src/cpp/main.cc @@ -9,7 +9,7 @@ extern php::Var argv; extern void php_app_init(); extern void php_app_clean(); -extern zend_module_entry app_module_entry; +extern zend_module_entry *php_embed_get_module(); static void throw_exception(zend_object *ex) { zend_bailout(); @@ -18,13 +18,14 @@ static void throw_exception(zend_object *ex) { int main(int cpp_argc, char **cpp_argv) { php_embed_init(cpp_argc, cpp_argv); zend_throw_exception_hook = throw_exception; + zend_module_entry *module = php_embed_get_module(); - if (zend_register_module_ex(&app_module_entry, MODULE_TEMPORARY) == NULL) { - zend_error(E_ERROR, "Failed to register module"); + if (zend_register_module_ex(module, MODULE_TEMPORARY) == NULL) { + zend_error(E_ERROR, "Failed to register module [%s]", module->name); } - if (zend_startup_module_ex(&app_module_entry) == FAILURE) { - zend_error(E_ERROR, "Failed to startup module"); + if (zend_startup_module_ex(module) == FAILURE) { + zend_error(E_ERROR, "Failed to startup module [%s]", module->name); } int rc = 0; diff --git a/src/template/extension.cc.php b/src/template/extension.cc.php index 66d8ac80..204c418d 100644 --- a/src/template/extension.cc.php +++ b/src/template/extension.cc.php @@ -19,12 +19,6 @@ foreach ($this->globalVars as $name => $type): foreach ($this->classCeList as $ce): ?> zend_class_entry * ; -classCeInfo[$ce])): - $info = $this->classCeInfo[$ce]; -?> -extern zend_class_entry *(); - // literal strings @@ -54,17 +48,16 @@ uint32_t getPropertyOffset($propertyDef->name, $c endforeach; ?> -static ZEND_FUNCTION(main) { - php_main(); -} - -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_void, 0, 0, IS_VOID, 0) -ZEND_END_ARG_INFO() - +// clang-format off static const zend_function_entry ext_functions[] = { - ZEND_FE(main, arginfo_void) +functions as $functionDef): +?> + ZEND_FE(name?>, arginfo_name?>) + ZEND_FE_END }; +// clang-format on static PHP_MINIT_FUNCTION(targetName?>) { // class/interface class entries @@ -99,7 +92,7 @@ foreach ($this->globalVars as $name => $type): foreach ($this->classes as $classDef): foreach ($classDef->properties as $propertyDef): ?> - getPropertyOffset($propertyDef->name, $classDef->name, $classDef->namespace)?> = php::getPropertyOffset("getNamespacedName()?>", "name?>"); + getPropertyOffset($propertyDef->name, $classDef->name, $classDef->namespace)?> = php::getPropertyOffset("getNamespacedName(false)?>", "name?>"); targetName?>_module_entry = { #ifdef BUILD_PHP_EXTENSION ZEND_GET_MODULE(targetName?>); -#endif \ No newline at end of file +#else +zend_module_entry *get_module() { + return &targetName ?>_module_entry; +} +#endif