diff --git a/src/Php/AstNodeType.php b/src/Php/AstNodeType.php index d7399224..e7d27598 100644 --- a/src/Php/AstNodeType.php +++ b/src/Php/AstNodeType.php @@ -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; diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index bc5e0a55..99e1a7b0 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -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(); + 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); + foreach ($possibleFunctionNames as $nativeFunc) { + if (str_contains($nativeFunc, '\\')) { + $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)) { - 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 $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[] = $tmpVar; + break; } - $argList[] = '{' . implode(', ', $list_vargs) . '}'; - 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,8 +4030,11 @@ class CompilerBase extends \PhpAot\Core\Translator if (isset($this->classMethodOverride[$fullMethodName]) and $this->classMethodOverride[$fullMethodName]) { return false; } - if ($nativeFunc and $this->hasNativeFunction($nativeFunc)) { - return $nativeFunc; + if ($nativeFunc) { + $this->checkNativeFunction($nativeFunc); + if ($this->hasNativeFunction($nativeFunc)) { + return $nativeFunc; + } } return false; } @@ -3985,7 +4042,6 @@ class CompilerBase extends \PhpAot\Core\Translator 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 . ';'; } } diff --git a/src/Php/Generator/ClosureGenerator.php b/src/Php/Generator/ClosureGenerator.php index 4faad75d..a77afa90 100644 --- a/src/Php/Generator/ClosureGenerator.php +++ b/src/Php/Generator/ClosureGenerator.php @@ -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++ 函数将使用 & 捕获所有闭包变量 */ diff --git a/src/Php/Translator.php b/src/Php/Translator.php index f9e4a96a..cb541876 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -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) { diff --git a/src/cpp/main.cc b/src/cpp/main.cc index 0b81b33e..6b045de7 100644 --- a/src/cpp/main.cc +++ b/src/cpp/main.cc @@ -2,6 +2,7 @@ #include #endif +#include #include #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) { diff --git a/src/cpp/php_aot_helper.h b/src/cpp/php_aot_helper.h index 37083e27..f04f0998 100644 --- a/src/cpp/php_aot_helper.h +++ b/src/cpp/php_aot_helper.h @@ -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)); diff --git a/tests/aot/native-type.phpt b/tests/aot/native-type.phpt new file mode 100644 index 00000000..afa92536 --- /dev/null +++ b/tests/aot/native-type.phpt @@ -0,0 +1,18 @@ +--TEST-- +native type +--FILE-- + +--EXPECT-- +float(2.5)