feat(php): 添加对原生类型和方法调用的支持

- 新增 isMethodCall 和 isStaticCall 方法用于检测方法调用类型
- 实现 genScopeSwitchCode 方法用于生成作用域切换代码
- 移除 lastNativeCall 属性并优化相关逻辑
- 添加 defaultNativeType 属性控制原生类型使用
- 移除 resetExpr 方法简化表达式解析
- 使用 empty 替代 isset 检查 nativeFunctions
- 重构函数体解析逻辑并添加 Skip 异常处理
- 支持 std::int、std::float、std::bool 原生类型转换
- 添加 checkNativeFunction 方法处理函数声明检查
- 使用 getNativeType 方法统一类型获取逻辑
- 重构参数解析逻辑支持可变参数展开
- 更新 C++ 辅助函数支持作用域管理
- 添加原生类型测试用例
pull/1/head
韩天峰 6 months ago
parent fed0d7c2a0
commit 208a34b134
  1. 10
      src/Php/AstNodeType.php
  2. 149
      src/Php/CompilerBase.php
  3. 8
      src/Php/Generator/ClosureGenerator.php
  4. 2
      src/Php/Translator.php
  5. 16
      src/cpp/main.cc
  6. 11
      src/cpp/php_aot_helper.h
  7. 18
      tests/aot/native-type.phpt

@ -70,6 +70,16 @@ trait AstNodeType
return $expr instanceof Expr\FuncCall; return $expr instanceof Expr\FuncCall;
} }
protected function isMethodCall(NodeAbstract $expr): bool
{
return $expr instanceof Expr\MethodCall;
}
protected function isStaticCall(NodeAbstract $expr): bool
{
return $expr instanceof Expr\StaticCall;
}
protected function isScalar(NodeAbstract $expr): bool protected function isScalar(NodeAbstract $expr): bool
{ {
return $expr instanceof Node\Scalar; return $expr instanceof Node\Scalar;

@ -198,7 +198,6 @@ class CompilerBase extends \PhpAot\Core\Translator
protected ?ClassDef $classDef = null; protected ?ClassDef $classDef = null;
protected ?MethodDef $methodDef = null; protected ?MethodDef $methodDef = null;
protected ?InterfaceDef $interfaceDef = null; protected ?InterfaceDef $interfaceDef = null;
protected ?FunctionDef $lastNativeCall = null;
protected array $superGlobalVars = [ protected array $superGlobalVars = [
'_GET' => self::TYPE_ARRAY, '_GET' => self::TYPE_ARRAY,
'_POST' => self::TYPE_ARRAY, '_POST' => self::TYPE_ARRAY,
@ -235,6 +234,7 @@ class CompilerBase extends \PhpAot\Core\Translator
protected array $afterStmtLines = []; protected array $afterStmtLines = [];
protected bool $inLoop = false; protected bool $inLoop = false;
protected bool $inClosure = false; protected bool $inClosure = false;
protected bool $defaultNativeType = false;
/** /**
* 赋值表达式的左值,写操作,右值为读操作. * 赋值表达式的左值,写操作,右值为读操作.
@ -303,7 +303,6 @@ class CompilerBase extends \PhpAot\Core\Translator
public function parseExpr(mixed $expr) public function parseExpr(mixed $expr)
{ {
$this->resetExpr();
$type = $expr->getType(); $type = $expr->getType();
$this->writeLog('Line ' . $this->getLine($expr) . ': ' . $type); $this->writeLog('Line ' . $this->getLine($expr) . ': ' . $type);
if ($expr->getLine() === $this->debugLine) { if ($expr->getLine() === $this->debugLine) {
@ -614,11 +613,6 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->namespace = ''; $this->namespace = '';
} }
protected function resetExpr(): void
{
$this->lastNativeCall = null;
}
protected function getFunctionName(FunctionLike $v): string protected function getFunctionName(FunctionLike $v): string
{ {
return $this->getNativeName($this->parseIdentifier($v->name), $this->namespace, $this->class); return $this->getNativeName($this->parseIdentifier($v->name), $this->namespace, $this->class);
@ -826,7 +820,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->resetFunction(); $this->resetFunction();
$this->function = $this->parseIdentifier($v->name); $this->function = $this->parseIdentifier($v->name);
$name = $this->getFunctionName($v); $name = $this->getFunctionName($v);
if (isset($this->nativeFunctions[$name])) { if (!empty($this->nativeFunctions[$name])) {
$this->functionDef = $this->nativeFunctions[$name]; $this->functionDef = $this->nativeFunctions[$name];
} else { } else {
$this->nativeFunctions[$name] = $this->parseFunctionDecl($v); $this->nativeFunctions[$name] = $this->parseFunctionDecl($v);
@ -853,19 +847,18 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->addArgument($argInfo->name, $argInfo->type); $this->addArgument($argInfo->name, $argInfo->type);
} }
$stmts = '';
if ($v->stmts) { if ($v->stmts) {
$this->indentLevel++; $this->indentLevel++;
try { try {
$stmts = $this->parseStmts($v->stmts); $stmts = $this->parseStmts($v->stmts);
} catch (Skip $e) {
$stmts = '';
}
if (!$this->isReturnStmtInLastLine($v->stmts)) { if (!$this->isReturnStmtInLastLine($v->stmts)) {
$stmts .= $this->genReturnCode(); $stmts .= $this->genReturnCode();
} }
} catch (Skip) {
$this->climate->cyan('Skip function ' . $name);
}
$this->indentLevel--; $this->indentLevel--;
} else {
$stmts = '';
} }
$functionDeclCode = $this->getReturnType() . ' ' . self::PREFIX . $name . '('; $functionDeclCode = $this->getReturnType() . ' ' . self::PREFIX . $name . '(';
@ -1315,11 +1308,33 @@ class CompilerBase extends \PhpAot\Core\Translator
if (!$this->hasVar($var)) { if (!$this->hasVar($var)) {
$this->addLocalVar($var, $type); $this->addLocalVar($var, $type);
} }
return $var . ' = ' . $this->parseIdentifier($right->args[0]->value); return $var . ' = ' . $this->parseIdentifier($right->args[0]->value);
} else { } else {
$type = $type === self::TYPE_VOID ? self::TYPE_VAR : $type; $type = $type === self::TYPE_VOID ? self::TYPE_VAR : $type;
} }
} elseif ($this->isStaticCall($right) and $this->isIdExpr($right->name)) {
$class = $this->parseIdentifier($right->class);
if ($class === 'std') {
$func = $this->parseIdentifier($right->name);
$type = match ($func) {
'int' => self::TYPE_INT,
'float' => self::TYPE_FLOAT,
'bool' => self::TYPE_BOOL,
default => '',
};
// Native 类型
if ($type) {
if (!$this->hasVar($var)) {
$this->addLocalVar($var, $type);
} else {
if ($this->getVarType($var) !== $type) {
$this->fatalError($left, "Cannot re-assign {$var} to {$type}");
}
}
$expr = $this->parseExpr($right->args[0]->value);
return $var . ' = ' . $this->convertExprFromType($type, $expr);
}
}
} }
if (!$this->hasVar($var)) { if (!$this->hasVar($var)) {
@ -1566,6 +1581,18 @@ class CompilerBase extends \PhpAot\Core\Translator
return array_key_exists($this->escapeFunction($name), $this->nativeFunctions); return array_key_exists($this->escapeFunction($name), $this->nativeFunctions);
} }
protected function checkNativeFunction(string $name): void
{
// 在预处理阶段检测到函数声明,但是未定义,说明在当前文件,但是顺序错误
// 跳过,稍后再处理
if (isset($this->functionDeclInFile[$name])
and $this->functionDeclInFile[$name] === $this->file
and !$this->hasNativeFunction($name)) {
$this->redoAfterDeclare[$name] = true;
throw new Skip();
}
}
protected function getNativeMethod(CallLike $expr, string $class, string $method): string|false protected function getNativeMethod(CallLike $expr, string $class, string $method): string|false
{ {
if (!$this->hasNativeClass($class)) { if (!$this->hasNativeClass($class)) {
@ -1628,14 +1655,14 @@ class CompilerBase extends \PhpAot\Core\Translator
switch ($exprType) { switch ($exprType) {
case 'Expr_Cast_Int': case 'Expr_Cast_Int':
case 'Scalar_Int': case 'Scalar_Int':
return self::TYPE_INT; return $this->getNativeType(self::TYPE_INT);
case 'Expr_Cast_Float': case 'Expr_Cast_Float':
case 'Expr_Cast_Double': case 'Expr_Cast_Double':
case 'Scalar_Float': case 'Scalar_Float':
return self::TYPE_FLOAT; return $this->getNativeType(self::TYPE_FLOAT);
case 'Expr_Cast_Bool': case 'Expr_Cast_Bool':
case 'Scalar_Bool': case 'Scalar_Bool':
return self::TYPE_BOOL; return $this->getNativeType(self::TYPE_BOOL);
case 'Expr_Array': case 'Expr_Array':
return self::TYPE_ARRAY; return self::TYPE_ARRAY;
case 'Expr_BinaryOp_Plus': case 'Expr_BinaryOp_Plus':
@ -2150,20 +2177,13 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
} }
foreach ($possibleFunctionNames as $name) { foreach ($possibleFunctionNames as $nativeFunc) {
if (str_contains($name, '\\')) { if (str_contains($nativeFunc, '\\')) {
$name = $this->escapeNamespace($name); $nativeFunc = $this->escapeNamespace($nativeFunc);
}
// 在预处理阶段检测到函数声明,但是未定义,说明在当前文件,但是顺序错误
// 跳过,稍后再处理
if (isset($this->functionDeclInFile[$name])
and $this->functionDeclInFile[$name] === $this->file
and !$this->hasNativeFunction($name)) {
$this->redoAfterDeclare[$name] = true;
throw new Skip();
} }
if ($this->hasNativeFunction($name)) { $this->checkNativeFunction($nativeFunc);
return $name; if ($this->hasNativeFunction($nativeFunc)) {
return $nativeFunc;
} }
} }
@ -2189,6 +2209,7 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
$nativeFn = $this->findNativeFunction($name); $nativeFn = $this->findNativeFunction($name);
if ($nativeFn) { if ($nativeFn) {
$expr->setAttribute('nativeCall', $nativeFn);
return self::PREFIX . $nativeFn . '(' . $this->parseNativeCallArgs($expr->args, $nativeFn) . ')'; return self::PREFIX . $nativeFn . '(' . $this->parseNativeCallArgs($expr->args, $nativeFn) . ')';
} }
$code = $this->parseFuncCallWithOptimizer($name, $expr); $code = $this->parseFuncCallWithOptimizer($name, $expr);
@ -2219,11 +2240,15 @@ class CompilerBase extends \PhpAot\Core\Translator
} }
} }
/**
* @param array<Node\Arg|Node\VariadicPlaceholder> $callArgs
* @param string $nativeFunc
* @return string
*/
protected function parseNativeCallArgs(array $callArgs, string $nativeFunc): string protected function parseNativeCallArgs(array $callArgs, string $nativeFunc): string
{ {
$argList = []; $argList = [];
$functionDef = $this->nativeFunctions[$nativeFunc]; $functionDef = $this->nativeFunctions[$nativeFunc];
$this->lastNativeCall = $functionDef;
$args = []; $args = [];
$hasNamedArg = false; $hasNamedArg = false;
// 对命名参数进行重排 // 对命名参数进行重排
@ -2251,13 +2276,29 @@ class CompilerBase extends \PhpAot\Core\Translator
foreach ($args as $i => $arg) { foreach ($args as $i => $arg) {
$argInfo = $this->getArgInfo($arg, $nativeFunc, $i); $argInfo = $this->getArgInfo($arg, $nativeFunc, $i);
if ($argInfo->variadic) { if ($argInfo->variadic) {
$vargs = array_slice($args, $i); $argsSlice = array_slice($args, $i);
$list_vargs = []; if (count($argsSlice) === 1 and $argsSlice[0]->unpack) {
foreach ($vargs as $varg) { if ($this->isVarExpr($arg->value) ) {
$list_vargs[] = $this->getTypeConvertedArg($varg, $argInfo); $var =$this->parseIdentifier($arg->value);
if ($this->getVarType($var) === self::TYPE_ARRAY) {
$argList[] = $var;
break;
}
}
$argList[] = $this->convertArrayExpr($this->parseExpr($arg->value));
} else {
$tmpVar = $this->addTmpVar(self::TYPE_ARRAY);
foreach ($argsSlice as $item) {
if ($item->unpack) {
$this->beforeStmtLines[] = $tmpVar . '.merge(' . $this->parseArg($item) . ');';
break;
} else {
$this->beforeStmtLines[] = $tmpVar . '.append(' . $this->parseArg($item) . ');';
}
} }
$argList[] = '{' . implode(', ', $list_vargs) . '}'; $argList[] = $tmpVar;
break; break;
}
} else { } else {
$argList[] = $this->getTypeConvertedArg($arg, $argInfo); $argList[] = $this->getTypeConvertedArg($arg, $argInfo);
} }
@ -3071,6 +3112,18 @@ class CompilerBase extends \PhpAot\Core\Translator
shell_exec($cmd); shell_exec($cmd);
} }
/**
* 为了兼容已有代码,默认不使用原生类型,而是将整数和浮点数作为 php 变量处理
* 原生 int/float/bool 类型,是不支持自动转换的,例如如果 int 计算超过最大值后,会自动转为 float,除法若不能除尽,则会转为 float
* 某些情况下高性能计算,可能需要使用原生类型,使用 $a = std::int(0) 来显式地使用原生类型
* @param string $type
* @return string
*/
protected function getNativeType(string $type): string
{
return $this->defaultNativeType ? $type : self::TYPE_VAR;
}
protected function detectConstType($expr): string protected function detectConstType($expr): string
{ {
$name = $this->parseIdentifier($expr->name); $name = $this->parseIdentifier($expr->name);
@ -3078,15 +3131,14 @@ class CompilerBase extends \PhpAot\Core\Translator
return $this->getConstantType($name); return $this->getConstantType($name);
} }
if ($name === 'true') { if ($name === 'true') {
return self::TYPE_BOOL; return $this->getNativeType(self::TYPE_BOOL);
} }
if ($name === 'false') { if ($name === 'false') {
return self::TYPE_BOOL; return $this->getNativeType(self::TYPE_BOOL);
} }
if ($name === 'NAN' or $name === 'INF') { if ($name === 'NAN' or $name === 'INF') {
return self::TYPE_FLOAT; return $this->getNativeType(self::TYPE_FLOAT);
} }
return self::TYPE_VAR; return self::TYPE_VAR;
} }
@ -3445,6 +3497,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->beforeStmtLines[] = '// Method Call: ' . $object . '->' . $this->parseIdentifier($expr->name) . '()'; $this->beforeStmtLines[] = '// Method Call: ' . $object . '->' . $this->parseIdentifier($expr->name) . '()';
$nativeFunc = $this->findNativeMethod($expr, $object, $this->parseIdentifier($expr->name)); $nativeFunc = $this->findNativeMethod($expr, $object, $this->parseIdentifier($expr->name));
if ($nativeFunc) { if ($nativeFunc) {
$expr->setAttribute('nativeCall', $nativeFunc);
return $this->parseNativeMethodCall($object, $nativeFunc, $expr->args); return $this->parseNativeMethodCall($object, $nativeFunc, $expr->args);
} }
} }
@ -3547,6 +3600,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($nativeFunc) { if ($nativeFunc) {
try { try {
$args = $this->parseNativeCallArgs($expr->args, $nativeFunc); $args = $this->parseNativeCallArgs($expr->args, $nativeFunc);
$expr->setAttribute('nativeCall', $nativeFunc);
} catch (PlaceHolder) { } catch (PlaceHolder) {
return $this->genPlaceHolder($this->genArray($callScope)); return $this->genPlaceHolder($this->genArray($callScope));
} }
@ -3619,16 +3673,16 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($classDef->hasProperty($property)) { if ($classDef->hasProperty($property)) {
$propertyDef = $classDef->getProperty($property); $propertyDef = $classDef->getProperty($property);
if ($propertyDef->isPublic()) { if ($propertyDef->isPublic()) {
return $this->getPropertyOffset($classDef->getNamespacedName(), $property); return $this->getPropertyOffset($classDef->getNamespacedName(false), $property);
} }
if ($propertyDef->isProtected()) { if ($propertyDef->isProtected()) {
if ($scope) { if ($scope) {
return $this->getPropertyOffset($classDef->getNamespacedName(), $property); return $this->getPropertyOffset($classDef->getNamespacedName(false), $property);
} }
$this->fatalError($object, "Cannot access protected property `{$property}` of class `{$class}`"); $this->fatalError($object, "Cannot access protected property `{$property}` of class `{$class}`");
} else { } else {
if ($scope === $findClass) { if ($scope === $findClass) {
return $this->getPropertyOffset($classDef->getNamespacedName(), $property); return $this->getPropertyOffset($classDef->getNamespacedName(false), $property);
} }
$this->fatalError($object, "Cannot access private property `{$property}` of class `{$class}`"); $this->fatalError($object, "Cannot access private property `{$property}` of class `{$class}`");
} }
@ -3976,16 +4030,18 @@ class CompilerBase extends \PhpAot\Core\Translator
if (isset($this->classMethodOverride[$fullMethodName]) and $this->classMethodOverride[$fullMethodName]) { if (isset($this->classMethodOverride[$fullMethodName]) and $this->classMethodOverride[$fullMethodName]) {
return false; return false;
} }
if ($nativeFunc and $this->hasNativeFunction($nativeFunc)) { if ($nativeFunc) {
$this->checkNativeFunction($nativeFunc);
if ($this->hasNativeFunction($nativeFunc)) {
return $nativeFunc; return $nativeFunc;
} }
}
return false; return false;
} }
protected function parseNativeMethodCall(string $object, string $nativeFunc, array $args): string protected function parseNativeMethodCall(string $object, string $nativeFunc, array $args): string
{ {
if (count($args) === 0) { if (count($args) === 0) {
$this->lastNativeCall = $this->nativeFunctions[$nativeFunc];
return self::PREFIX . $nativeFunc . '(' . $object . ')'; return self::PREFIX . $nativeFunc . '(' . $object . ')';
} }
return self::PREFIX . $nativeFunc . '(' . $object . ', ' . $this->parseNativeCallArgs($args, $nativeFunc) . ')'; return self::PREFIX . $nativeFunc . '(' . $object . ', ' . $this->parseNativeCallArgs($args, $nativeFunc) . ')';
@ -4077,7 +4133,8 @@ class CompilerBase extends \PhpAot\Core\Translator
$beforeCode = ''; $beforeCode = '';
} }
if ($this->isCallExpr($expr->expr)) { if ($this->isCallExpr($expr->expr)) {
if ($this->lastNativeCall and $this->lastNativeCall->returnType === self::TYPE_VOID) { $nativeCall = $expr->expr->getAttribute('nativeCall');
if ($nativeCall and $this->nativeFunctions[$nativeCall]->returnType === self::TYPE_VOID) {
return $beforeCode . PHP_EOL . $code . ";" . PHP_EOL . "return " . self::VALUE_NULL . ';'; return $beforeCode . PHP_EOL . $code . ";" . PHP_EOL . "return " . self::VALUE_NULL . ';';
} }
} }

@ -12,6 +12,14 @@ use PhpParser\NodeAbstract;
trait ClosureGenerator trait ClosureGenerator
{ {
protected function genScopeSwitchCode(): string
{
$tmpScope = $this->genTmpVarName();
$code = "auto $tmpScope = php_switch_scope(this_);" . PHP_EOL;
$code .= "ON_SCOPE_EXIT({ php_restore_scope($tmpScope); });" . PHP_EOL;
return $code;
}
/** /**
* @param $useCurrentScope bool 直接使用当前作用域,C++ 函数将使用 & 捕获所有闭包变量 * @param $useCurrentScope bool 直接使用当前作用域,C++ 函数将使用 & 捕获所有闭包变量
*/ */

@ -308,7 +308,7 @@ class Translator extends Preprocessor
if ($argInfoList) { if ($argInfoList) {
foreach ($argInfoList as $argInfo) { foreach ($argInfoList as $argInfo) {
if ($argInfo->variadic) { if ($argInfo->variadic) {
$arg = self::TYPE_ARRAY . ' ' . $argInfo->name . '()'; $arg = self::TYPE_ARRAY . ' ' . $argInfo->name;
} else { } else {
$arg = $argInfo->type . ' ' . $argInfo->name; $arg = $argInfo->type . ' ' . $argInfo->name;
if ($argInfo->default) { if ($argInfo->default) {

@ -2,6 +2,7 @@
#include <gperftools/profiler.h> #include <gperftools/profiler.h>
#endif #endif
#include <phpx.h>
#include <php_aot_helper.h> #include <php_aot_helper.h>
#include "sapi/embed/php_embed.h" #include "sapi/embed/php_embed.h"
#include "ps_title.h" #include "ps_title.h"
@ -44,15 +45,16 @@ static zend_execute_data *get_frame() {
return frame; return frame;
} }
zend_class_entry *php_switch_scope(php::Object &this_) { php::Scope php_switch_scope(php::Object &this_) {
auto frame = get_frame(); php::Scope scope;
auto ori_scope = frame->func->common.scope; scope.frame = get_frame();
frame->func->common.scope = php_get_called_ce(this_); scope.ce = scope.frame->func->common.scope;
return ori_scope; scope.frame->func->common.scope = php_get_called_ce(this_);
return scope;
} }
void php_restore_scope(zend_class_entry *ori_scope) { void php_restore_scope(php::Scope &ori_scope) {
get_frame()->func->common.scope = ori_scope; ori_scope.frame->func->common.scope = ori_scope.ce;
} }
void module_shutdown(zend_module_entry *module) { void module_shutdown(zend_module_entry *module) {

@ -8,10 +8,17 @@ extern zend_function *php_get_func(int func_id, const php::Str &func_name);
extern zend_function *php_get_method(int func_id, const php::Str &method_name, int class_id, const php::Str &class_name); extern zend_function *php_get_method(int func_id, const php::Str &method_name, int class_id, const php::Str &class_name);
extern uint32_t php_get_prop(int prop_id, const php::Str &prop_name, int class_id, const php::Str &class_name); extern uint32_t php_get_prop(int prop_id, const php::Str &prop_name, int class_id, const php::Str &class_name);
namespace php {
struct Scope {
zend_class_entry *ce;
zend_execute_data *frame;
};
};
extern const char *php_get_called_class(php::Object &this_); extern const char *php_get_called_class(php::Object &this_);
extern zend_class_entry *php_get_called_ce(php::Object &this_); extern zend_class_entry *php_get_called_ce(php::Object &this_);
extern zend_class_entry *php_switch_scope(php::Object &this_); extern php::Scope php_switch_scope(php::Object &this_);
extern void php_restore_scope(zend_class_entry *ori_scope); extern void php_restore_scope(php::Scope &ori_scope);
static inline php::Variant CALL(int func_id, const php::Str &func_name) { static inline php::Variant CALL(int func_id, const php::Str &func_name) {
return php::call(php_get_func(func_id, func_name)); return php::call(php_get_func(func_id, func_name));

@ -0,0 +1,18 @@
--TEST--
native type
--FILE--
<?php
function main()
{
$a = std::int(100);
var_dump($a);
$b = std::float(100.0);
var_dump($b);
$c = std::bool(true);
var_dump($c);
}
?>
--EXPECT--
float(2.5)
Loading…
Cancel
Save