feat(translator): 增强PHP到C++编译器功能

- 在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列表生成逻辑并修复头文件包含顺序问题
- 重构存根文件生成流程并改进错误报告机制
pull/1/head
韩天峰 7 months ago
parent ea77b6d746
commit a6a06ca859
  1. 19
      bin/compiler.php
  2. 35
      bin/gen_stub.php
  3. 0
      examples/appx/MyClass.php
  4. 0
      examples/appx/main.php
  5. 7
      examples/appx/test.php
  6. 7
      src/Php/ClassLikeDef.php
  7. 22
      src/Php/CompilerBase.php
  8. 95
      src/Php/Translator.php
  9. 11
      src/cpp/main.cc
  10. 29
      src/template/extension.cc.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 文件

@ -1,6 +1,7 @@
#!/usr/bin/env php
<?php declare(strict_types=1);
use PhpAot\Php\Translator;
use PhpParser\Comment\Doc as DocComment;
use PhpParser\ConstExprEvaluator;
use PhpParser\Modifiers;
@ -3504,7 +3505,7 @@ class ClassInfo {
$code .= "#if {$this->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<string, ConstInfo> */
@ -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;

@ -37,3 +37,10 @@ class Test
}
}
const TEST_CONST = 1.001;
function my_sum(int $a, int $b)
{
return $a + $b;
}

@ -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;
}
}

@ -104,6 +104,10 @@ class CompilerBase extends \PhpAot\Core\Translator
*/
protected array $classes = [];
protected array $interfaces = [];
/**
* @var array<string, FunctionDef>
*/
protected array $functions = [];
/**
* @var array<string, ClassDef>
*/
@ -112,6 +116,10 @@ class CompilerBase extends \PhpAot\Core\Translator
* @var array<string, InterfaceDef>
*/
protected array $interfacesDefineInFile = [];
/**
* @var array<string, FunctionDef>
*/
protected array $functionDefineInFile = [];
/**
* @var array<string>
*/
@ -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()) {

@ -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) {

@ -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;

@ -19,12 +19,6 @@ foreach ($this->globalVars as $name => $type):
foreach ($this->classCeList as $ce):
?>
zend_class_entry * <?= $ce ?>;
<?php
if (isset($this->classCeInfo[$ce])):
$info = $this->classCeInfo[$ce];
?>
extern zend_class_entry *<?= $info['func'] ?>(<?= $info['argDef'] ?>);
<?php endif; ?>
<?php endforeach; ?>
// literal strings
@ -54,17 +48,16 @@ uint32_t <?=Translator::PREFIX . $this->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)
<?php
foreach ($this->functions as $functionDef):
?>
ZEND_FE(<?=$functionDef->name?>, arginfo_<?=$functionDef->name?>)
<?php endforeach;?>
ZEND_FE_END
};
// clang-format on
static PHP_MINIT_FUNCTION(<?=$this->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):
?>
<?=Translator::PREFIX . $this->getPropertyOffset($propertyDef->name, $classDef->name, $classDef->namespace)?> = php::getPropertyOffset("<?=$classDef->getNamespacedName()?>", "<?=$propertyDef->name?>");
<?=Translator::PREFIX . $this->getPropertyOffset($propertyDef->name, $classDef->name, $classDef->namespace)?> = php::getPropertyOffset("<?=$classDef->getNamespacedName(false)?>", "<?=$propertyDef->name?>");
<?php
endforeach;
endforeach;
@ -137,4 +130,8 @@ zend_module_entry <?=$this->targetName?>_module_entry = {
#ifdef BUILD_PHP_EXTENSION
ZEND_GET_MODULE(<?=$this->targetName?>);
#endif
#else
zend_module_entry *<?= self::PREFIX . 'embed_' ?>get_module() {
return &<?= $this->targetName ?>_module_entry;
}
#endif

Loading…
Cancel
Save