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

- 新增 isMethodCall 和 isStaticCall 方法用于检测方法调用类型
- 实现 genScopeSwitchCode 方法用于生成作用域切换代码
- 移除 lastNativeCall 属性并优化相关逻辑
- 添加 defaultNativeType 属性控制原生类型使用
- 移除 resetExpr 方法简化表达式解析
- 使用 empty 替代 isset 检查 nativeFunctions
- 重构函数体解析逻辑并添加 Skip 异常处理
- 支持 std::int、std::float、std::bool 原生类型转换
- 添加 checkNativeFunction 方法处理函数声明检查
- 使用 getNativeType 方法统一类型获取逻辑
- 重构参数解析逻辑支持可变参数展开
- 更新 C++ 辅助函数支持作用域管理
- 添加原生类型测试用例
pull/1/head
韩天峰 5 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;
}
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
{
return $expr instanceof Node\Scalar;

@ -198,7 +198,6 @@ class CompilerBase extends \PhpAot\Core\Translator
protected ?ClassDef $classDef = null;
protected ?MethodDef $methodDef = null;
protected ?InterfaceDef $interfaceDef = null;
protected ?FunctionDef $lastNativeCall = null;
protected array $superGlobalVars = [
'_GET' => self::TYPE_ARRAY,
'_POST' => self::TYPE_ARRAY,
@ -235,6 +234,7 @@ class CompilerBase extends \PhpAot\Core\Translator
protected array $afterStmtLines = [];
protected bool $inLoop = false;
protected bool $inClosure = false;
protected bool $defaultNativeType = false;
/**
* 赋值表达式的左值,写操作,右值为读操作.
@ -303,7 +303,6 @@ class CompilerBase extends \PhpAot\Core\Translator
public function parseExpr(mixed $expr)
{
$this->resetExpr();
$type = $expr->getType();
$this->writeLog('Line ' . $this->getLine($expr) . ': ' . $type);
if ($expr->getLine() === $this->debugLine) {
@ -614,11 +613,6 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->namespace = '';
}
protected function resetExpr(): void
{
$this->lastNativeCall = null;
}
protected function getFunctionName(FunctionLike $v): string
{
return $this->getNativeName($this->parseIdentifier($v->name), $this->namespace, $this->class);
@ -826,7 +820,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->resetFunction();
$this->function = $this->parseIdentifier($v->name);
$name = $this->getFunctionName($v);
if (isset($this->nativeFunctions[$name])) {
if (!empty($this->nativeFunctions[$name])) {
$this->functionDef = $this->nativeFunctions[$name];
} else {
$this->nativeFunctions[$name] = $this->parseFunctionDecl($v);
@ -853,19 +847,18 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->addArgument($argInfo->name, $argInfo->type);
}
$stmts = '';
if ($v->stmts) {
$this->indentLevel++;
try {
$stmts = $this->parseStmts($v->stmts);
} catch (Skip $e) {
$stmts = '';
}
if (!$this->isReturnStmtInLastLine($v->stmts)) {
$stmts .= $this->genReturnCode();
}
} catch (Skip) {
$this->climate->cyan('Skip function ' . $name);
}
$this->indentLevel--;
} else {
$stmts = '';
}
$functionDeclCode = $this->getReturnType() . ' ' . self::PREFIX . $name . '(';
@ -1315,11 +1308,33 @@ class CompilerBase extends \PhpAot\Core\Translator
if (!$this->hasVar($var)) {
$this->addLocalVar($var, $type);
}
return $var . ' = ' . $this->parseIdentifier($right->args[0]->value);
} else {
$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)) {
@ -1566,6 +1581,18 @@ class CompilerBase extends \PhpAot\Core\Translator
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
{
if (!$this->hasNativeClass($class)) {
@ -1628,14 +1655,14 @@ class CompilerBase extends \PhpAot\Core\Translator
switch ($exprType) {
case 'Expr_Cast_Int':
case 'Scalar_Int':
return self::TYPE_INT;
return $this->getNativeType(self::TYPE_INT);
case 'Expr_Cast_Float':
case 'Expr_Cast_Double':
case 'Scalar_Float':
return self::TYPE_FLOAT;
return $this->getNativeType(self::TYPE_FLOAT);
case 'Expr_Cast_Bool':
case 'Scalar_Bool':
return self::TYPE_BOOL;
return $this->getNativeType(self::TYPE_BOOL);
case 'Expr_Array':
return self::TYPE_ARRAY;
case 'Expr_BinaryOp_Plus':
@ -2150,20 +2177,13 @@ class CompilerBase extends \PhpAot\Core\Translator
}
}
foreach ($possibleFunctionNames as $name) {
if (str_contains($name, '\\')) {
$name = $this->escapeNamespace($name);
}
// 在预处理阶段检测到函数声明,但是未定义,说明在当前文件,但是顺序错误
// 跳过,稍后再处理
if (isset($this->functionDeclInFile[$name])
and $this->functionDeclInFile[$name] === $this->file
and !$this->hasNativeFunction($name)) {
$this->redoAfterDeclare[$name] = true;
throw new Skip();
foreach ($possibleFunctionNames as $nativeFunc) {
if (str_contains($nativeFunc, '\\')) {
$nativeFunc = $this->escapeNamespace($nativeFunc);
}
if ($this->hasNativeFunction($name)) {
return $name;
$this->checkNativeFunction($nativeFunc);
if ($this->hasNativeFunction($nativeFunc)) {
return $nativeFunc;
}
}
@ -2189,6 +2209,7 @@ class CompilerBase extends \PhpAot\Core\Translator
}
$nativeFn = $this->findNativeFunction($name);
if ($nativeFn) {
$expr->setAttribute('nativeCall', $nativeFn);
return self::PREFIX . $nativeFn . '(' . $this->parseNativeCallArgs($expr->args, $nativeFn) . ')';
}
$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
{
$argList = [];
$functionDef = $this->nativeFunctions[$nativeFunc];
$this->lastNativeCall = $functionDef;
$args = [];
$hasNamedArg = false;
// 对命名参数进行重排
@ -2251,13 +2276,29 @@ class CompilerBase extends \PhpAot\Core\Translator
foreach ($args as $i => $arg) {
$argInfo = $this->getArgInfo($arg, $nativeFunc, $i);
if ($argInfo->variadic) {
$vargs = array_slice($args, $i);
$list_vargs = [];
foreach ($vargs as $varg) {
$list_vargs[] = $this->getTypeConvertedArg($varg, $argInfo);
$argsSlice = array_slice($args, $i);
if (count($argsSlice) === 1 and $argsSlice[0]->unpack) {
if ($this->isVarExpr($arg->value) ) {
$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;
}
} else {
$argList[] = $this->getTypeConvertedArg($arg, $argInfo);
}
@ -3071,6 +3112,18 @@ class CompilerBase extends \PhpAot\Core\Translator
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
{
$name = $this->parseIdentifier($expr->name);
@ -3078,15 +3131,14 @@ class CompilerBase extends \PhpAot\Core\Translator
return $this->getConstantType($name);
}
if ($name === 'true') {
return self::TYPE_BOOL;
return $this->getNativeType(self::TYPE_BOOL);
}
if ($name === 'false') {
return self::TYPE_BOOL;
return $this->getNativeType(self::TYPE_BOOL);
}
if ($name === 'NAN' or $name === 'INF') {
return self::TYPE_FLOAT;
return $this->getNativeType(self::TYPE_FLOAT);
}
return self::TYPE_VAR;
}
@ -3445,6 +3497,7 @@ class CompilerBase extends \PhpAot\Core\Translator
$this->beforeStmtLines[] = '// Method Call: ' . $object . '->' . $this->parseIdentifier($expr->name) . '()';
$nativeFunc = $this->findNativeMethod($expr, $object, $this->parseIdentifier($expr->name));
if ($nativeFunc) {
$expr->setAttribute('nativeCall', $nativeFunc);
return $this->parseNativeMethodCall($object, $nativeFunc, $expr->args);
}
}
@ -3547,6 +3600,7 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($nativeFunc) {
try {
$args = $this->parseNativeCallArgs($expr->args, $nativeFunc);
$expr->setAttribute('nativeCall', $nativeFunc);
} catch (PlaceHolder) {
return $this->genPlaceHolder($this->genArray($callScope));
}
@ -3619,16 +3673,16 @@ class CompilerBase extends \PhpAot\Core\Translator
if ($classDef->hasProperty($property)) {
$propertyDef = $classDef->getProperty($property);
if ($propertyDef->isPublic()) {
return $this->getPropertyOffset($classDef->getNamespacedName(), $property);
return $this->getPropertyOffset($classDef->getNamespacedName(false), $property);
}
if ($propertyDef->isProtected()) {
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}`");
} else {
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}`");
}
@ -3976,16 +4030,18 @@ class CompilerBase extends \PhpAot\Core\Translator
if (isset($this->classMethodOverride[$fullMethodName]) and $this->classMethodOverride[$fullMethodName]) {
return false;
}
if ($nativeFunc and $this->hasNativeFunction($nativeFunc)) {
if ($nativeFunc) {
$this->checkNativeFunction($nativeFunc);
if ($this->hasNativeFunction($nativeFunc)) {
return $nativeFunc;
}
}
return false;
}
protected function parseNativeMethodCall(string $object, string $nativeFunc, array $args): string
{
if (count($args) === 0) {
$this->lastNativeCall = $this->nativeFunctions[$nativeFunc];
return self::PREFIX . $nativeFunc . '(' . $object . ')';
}
return self::PREFIX . $nativeFunc . '(' . $object . ', ' . $this->parseNativeCallArgs($args, $nativeFunc) . ')';
@ -4077,7 +4133,8 @@ class CompilerBase extends \PhpAot\Core\Translator
$beforeCode = '';
}
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 . ';';
}
}

@ -12,6 +12,14 @@ use PhpParser\NodeAbstract;
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++ 函数将使用 & 捕获所有闭包变量
*/

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

@ -2,6 +2,7 @@
#include <gperftools/profiler.h>
#endif
#include <phpx.h>
#include <php_aot_helper.h>
#include "sapi/embed/php_embed.h"
#include "ps_title.h"
@ -44,15 +45,16 @@ static zend_execute_data *get_frame() {
return frame;
}
zend_class_entry *php_switch_scope(php::Object &this_) {
auto frame = get_frame();
auto ori_scope = frame->func->common.scope;
frame->func->common.scope = php_get_called_ce(this_);
return ori_scope;
php::Scope php_switch_scope(php::Object &this_) {
php::Scope scope;
scope.frame = get_frame();
scope.ce = scope.frame->func->common.scope;
scope.frame->func->common.scope = php_get_called_ce(this_);
return scope;
}
void php_restore_scope(zend_class_entry *ori_scope) {
get_frame()->func->common.scope = ori_scope;
void php_restore_scope(php::Scope &ori_scope) {
ori_scope.frame->func->common.scope = ori_scope.ce;
}
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 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 zend_class_entry *php_get_called_ce(php::Object &this_);
extern zend_class_entry *php_switch_scope(php::Object &this_);
extern void php_restore_scope(zend_class_entry *ori_scope);
extern php::Scope php_switch_scope(php::Object &this_);
extern void php_restore_scope(php::Scope &ori_scope);
static inline php::Variant CALL(int func_id, const php::Str &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