feat(debug): 添加内存泄漏检测脚本

- 实现了随机数生成函数 gen_random
- 添加了堆排序算法实现 heapsort 和 heapsort_r
- 集成了高精度时间测量功能 gethrtime
- 创建了性能测试框架包括 start_test 和 end_test 函数
- 实现了测试结果显示和统计功能 total
- 添加了主函数 main 来执行完整的性能测试流程
pull/1/head
韩天峰 2 months ago
parent 0b815d8d1e
commit e996dc8b43
  1. 3
      src/Php/CompilerBase.php
  2. 837
      src/Php/Optimizer/FuncCallOptimizer.php
  3. 6
      src/Php/Translator.php

@ -227,7 +227,6 @@ class CompilerBase extends \PhpAot\Core\Translator
];
protected array $localHeaders = [];
protected array $internalFunctions = [];
protected array $funcSymbols = [];
protected array $internalConstants = [];
/**
@ -4861,7 +4860,7 @@ class CompilerBase extends \PhpAot\Core\Translator
foreach ($expr->parts as $part) {
$list[] = $this->identifierToStr($part);
}
return 'php::call(' . $this->funcSymbols['shell_exec'] . ', { php::concat({' . implode(', ', $list) . '}) })';
return 'php::std::shell_exec(php::concat({' . implode(', ', $list) . '}))';
}
protected function parseGoto(Node\Stmt\Goto_ $v): string

@ -8,230 +8,691 @@
namespace PhpAot\Php\Optimizer;
use PhpAot\Php\Reflection;
use PhpParser\Node;
/**
* Config-driven optimizer for built-in function calls.
*
* Used as a trait by CompilerBase. All method calls are direct (no __call/reflection).
*/
trait FuncCallOptimizer
{
protected function genFuncGetArgs(string $name, Node\Expr\FuncCall $expr): string
private const string A_V = 'v';
private const string A_S = 's';
private const string A_I = 'i';
private const string A_F = 'f';
private const string A_B = 'b';
private const string A_R = 'R';
private const string A_OPT = '?';
private const int FOLD_STRING_LEN = 1;
private const int FOLD_STRING_CASE = 2;
private const int FOLD_CMP2 = 3;
private const int FOLD_CMP3 = 4;
private const int FOLD_COUNT_LITERAL = 5;
private const int FOLD_KNOWN_CLASS = 6;
private const int FOLD_KNOWN_CONSTANT = 7;
private const int FOLD_SSA_TYPE = 8;
/** @var array<string,string|array>|null */
private ?array $_funcCallConfig = null;
/** @var array<string,array> Cache for auto-detected arg reflection info */
private array $_autoArgTypes = [];
// =========================================================================
// Config
// =========================================================================
private function getFuncCallConfig(): array
{
$this->warningUndefinedBehavior($expr);
$funcDef = $this->functionDef;
$list = [];
foreach ($funcDef->argInfoList as $i => $argInfo) {
if ($argInfo->variadic) {
$tmpVar = $this->addTmpVar(self::TYPE_ARRAY);
$this->context->beforeStmtLines[] = $this->genArray($list) . ';';
$this->context->beforeStmtLines[] = $tmpVar . '.merge(' . $argInfo->name . ');';
return $tmpVar;
}
$list[] = $argInfo->name;
if ($this->_funcCallConfig !== null) {
return $this->_funcCallConfig;
}
return $this->genArray($list);
return $this->_funcCallConfig = $this->buildFuncCallConfig();
}
protected function genFuncGetArg(string $name, Node\Expr\FuncCall $expr)
private function buildFuncCallConfig(): array
{
$this->warningUndefinedBehavior($expr);
$position = $expr->args[0]->value;
if ($this->isScalarInt($position)) {
$funcDef = $this->functionDef;
$posInt = intval($position->value);
foreach ($funcDef->argInfoList as $i => $argInfo) {
if ($argInfo->variadic) {
return $argInfo->name . '.offsetGet(' . ($posInt - $i) . ')';
}
if ($i == $posInt) {
return $argInfo->name;
}
$simple = [
'urlencode', 'urldecode', 'rawurlencode', 'rawurldecode',
'base64_encode', 'method_exists', 'property_exists',
'implode', 'str_replace', 'array_column', 'array_reverse',
'array_sum', 'array_product', 'array_key_first', 'array_key_last',
'array_combine', 'array_flip', 'array_intersect', 'array_values',
'version_compare', 'gettype',
'is_array', 'is_string', 'is_object', 'is_resource',
'is_scalar', 'is_numeric', 'is_callable', 'is_countable', 'is_iterable',
'array_is_list', 'is_dir', 'is_file', 'realpath', 'time',
'parse_url', 'base64_decode',
'in_array', 'array_search', 'array_unique', 'array_filter', 'array_reduce',
'date', 'strtotime', 'md5', 'print_r',
'strstr', 'strripos', 'strrpos', 'is_a', 'is_subclass_of',
'sort', 'rsort', 'asort', 'arsort', 'ksort',
'array_pop', 'array_shift', 'reset', 'end',
'microtime', 'hrtime', 'uniqid',
'dirname', 'basename',
];
$extra = [
// Aliases (PHP function name → C++ target name)
'join' => 'implode',
'str_ireplace' => 'str_replace',
'stristr' => 'strstr',
'strlen' => ['constFold' => self::FOLD_STRING_LEN],
'ord' => [],
'ucfirst' => [],
'lcfirst' => [],
'strtolower' => ['constFold' => self::FOLD_STRING_CASE],
'strtoupper' => ['constFold' => self::FOLD_STRING_CASE],
'crc32' => [],
'chr' => [],
'strcmp' => ['constFold' => self::FOLD_CMP2],
'strcasecmp' => ['constFold' => self::FOLD_CMP2],
'str_starts_with' => [],
'str_ends_with' => [],
'str_contains' => [],
'strtr' => [],
'strncmp' => ['constFold' => self::FOLD_CMP3],
'strncasecmp' => ['constFold' => self::FOLD_CMP3],
'htmlspecialchars' => [],
'htmlentities' => [],
'htmlspecialchars_decode' => [],
'html_entity_decode' => [],
'strip_tags' => [],
'explode' => [],
'strpos' => [],
'stripos' => [],
'substr' => [],
'str_repeat' => [],
'str_pad' => [],
'array_slice' => [],
'array_chunk' => [],
'array_fill' => [],
// Variadic
'array_diff' => ['variadic' => true],
'array_merge' => ['variadic' => true],
'array_merge_recursive' => ['variadic' => true],
// Compile-time fold with defaults
'class_exists' => ['constFold' => self::FOLD_KNOWN_CLASS, 'defaults' => [1 => 'true']],
'interface_exists' => ['defaults' => [1 => 'true']],
'trait_exists' => ['defaults' => [1 => 'true']],
'enum_exists' => ['defaults' => [1 => 'true']],
'defined' => ['constFold' => self::FOLD_KNOWN_CONSTANT],
// Big* dispatch
'abs' => ['bigDispatch' => [
self::TYPE_BIGINT => 'php::BigInt::abs',
self::TYPE_BIGFLOAT => 'php::BigFloat::abs',
self::TYPE_DECIMAL => 'php::Decimal::abs',
]],
'pow' => ['bigDispatch' => [
self::TYPE_BIGINT => 'php::BigInt::pow',
self::TYPE_DECIMAL => 'php::Decimal::pow',
]],
'sqrt' => ['bigDispatch' => [
self::TYPE_BIGINT => 'php::BigInt::sqrt',
self::TYPE_DECIMAL => 'php::Decimal::sqrt',
self::TYPE_BIGFLOAT => 'php::BigFloat::sqrt',
]],
'floor' => ['bigDispatch' => [
self::TYPE_DECIMAL => 'php::Decimal::floor',
'fallback' => 'php::std::floor',
]],
'ceil' => ['bigDispatch' => [
self::TYPE_DECIMAL => 'php::Decimal::ceil',
'fallback' => 'php::std::ceil',
]],
// Type conversions
'strval' => ['conversion' => self::A_S],
'intval' => ['conversion' => self::A_I],
'floatval' => ['conversion' => self::A_F],
'boolval' => ['conversion' => self::A_B],
// SSA compile-time type checks
'is_int' => ['constFold' => self::FOLD_SSA_TYPE, 'constFoldExtra' => self::TYPE_INT],
'is_float' => ['constFold' => self::FOLD_SSA_TYPE, 'constFoldExtra' => self::TYPE_FLOAT],
'is_bool' => ['constFold' => self::FOLD_SSA_TYPE, 'constFoldExtra' => self::TYPE_BOOL],
// Custom handlers
'is_null' => ['handler' => 'genIsNull'],
'get_class' => ['handler' => 'genGetClassOptimized'],
'get_parent_class' => ['handler' => 'genGetParentClass'],
'function_exists' => ['handler' => 'genFunctionExistsOptimized'],
'func_get_arg' => ['handler' => 'genFuncGetArgOptimized'],
'func_get_args' => ['handler' => 'genFuncGetArgsOptimized'],
'func_num_args' => ['handler' => 'genFuncNumArgsOptimized'],
'compact' => ['handler' => 'genCompactOptimized'],
'max' => ['handler' => 'genMaxMin'],
'min' => ['handler' => 'genMaxMin'],
'array_keys' => ['handler' => 'genArrayKeys'],
'array_key_exists' => ['handler' => 'genArrayKeyExists'],
'round' => ['handler' => 'genRound'],
'count' => ['handler' => 'genCount'],
'define' => ['handler' => 'genDefine'],
];
$config = $extra;
foreach ($simple as $name) {
if (!isset($config[$name])) {
$config[$name] = [];
}
$this->fatalError($expr, 'wrong parameter position `' . $posInt . '`');
} else {
$this->fatalError($expr, 'func_get_arg() only support scalar int');
}
return $config;
}
protected function isValidDefineName(string $name): bool
// =========================================================================
// Main entry point
// =========================================================================
protected function parseFuncCallWithOptimizer(string $name, Node\Expr\FuncCall $expr): string|false
{
return preg_match('/^(?!\d)[\p{L}_][\p{L}\p{N}_]*$/u', $name) === 1;
$config = $this->getFuncCallConfig()[$name] ?? null;
if ($config === null) {
return false;
}
if (is_string($config)) {
$targetName = $config;
$config = ['target' => $targetName];
$name = $targetName;
}
if (isset($config['handler'])) {
return $this->{$config['handler']}($name, $expr, $config);
}
if (isset($config['bigDispatch'])) {
return $this->dispatchBigType($expr, $config['bigDispatch']);
}
if (isset($config['conversion'])) {
return $this->dispatchConversion($expr, $config['conversion']);
}
return $this->dispatchFuncCall($name, $expr, $config);
}
protected function parseFuncCallWithOptimizer(string $name, Node\Expr\FuncCall $expr): string|false
// =========================================================================
// Generic dispatcher
// =========================================================================
private function dispatchFuncCall(string $name, Node\Expr\FuncCall $expr, array $config): string|false
{
$getArg = function ($i) use ($expr) {
return $this->parseIdentifier($expr->args[$i]->value);
};
if (count($expr->args) == 1) {
switch ($name) {
case 'intval':
return $this->convertIntExpr($this->parseExpr($expr->args[0]->value));
case 'floatval':
return $this->convertFloatExpr($this->parseExpr($expr->args[0]->value));
case 'boolval':
return $this->convertBoolExpr($this->parseExpr($expr->args[0]->value));
case 'strval':
$arg = $expr->args[0]->value;
$type = $this->detectTypeOfExpr($arg);
$parsed = $this->parseExpr($arg);
if ($type === self::TYPE_BIGINT) {
return 'php::BigInt::toString(' . $parsed . ')';
}
if ($type === self::TYPE_BIGFLOAT) {
return 'php::BigFloat::toString(' . $parsed . ')';
}
if ($type === self::TYPE_DECIMAL) {
return 'php::Decimal::toString(' . $parsed . ')';
}
return $this->convertStringExpr($parsed);
default:
break;
}
} elseif (count($expr->args) == 2) {
switch ($name) {
case 'define':
$arg1 = $expr->args[0]->value;
if ($this->isScalarString($arg1) and !$this->isValidDefineName($arg1->value)) {
$this->fatalError($expr, 'Invalid define name `' . $arg1->value . '`');
}
break;
default:
break;
}
$target = $config['target'] ?? null;
if ($target === null) {
$target = 'php::std::' . $name;
} elseif (!str_starts_with($target, 'php::')) {
$target = 'php::std::' . $target;
}
if ($name === 'abs') {
$type = $this->detectTypeOfExpr($expr->args[0]->value);
if ($type === self::TYPE_BIGINT) {
return 'php::BigInt::abs(' . $this->parseExpr($expr->args[0]->value) . ')';
}
if ($type === self::TYPE_BIGFLOAT) {
return 'php::BigFloat::abs(' . $this->parseExpr($expr->args[0]->value) . ')';
}
if ($type === self::TYPE_DECIMAL) {
return 'php::Decimal::abs(' . $this->parseExpr($expr->args[0]->value) . ')';
}
return 'php::math::abs(' . $getArg(0) . ')';
$refInfo = $this->getArgReflectionInfo($name);
$argTypeStr = $config['args'] ?? ($refInfo['args'] ?? '');
$defaults = $config['defaults'] ?? [];
if (!empty($config['variadic']) || ($refInfo['variadic'] ?? false)) {
return $this->genVariadicCall($target, $expr);
}
if ($name === 'pow') {
$type = $this->detectTypeOfExpr($expr->args[0]->value);
if ($type === self::TYPE_BIGINT) {
return 'php::BigInt::pow(' . $this->parseExpr($expr->args[0]->value) . ', ' . $this->parseExpr($expr->args[1]->value) . ')';
}
if ($type === self::TYPE_DECIMAL) {
return 'php::Decimal::pow(' . $this->parseExpr($expr->args[0]->value) . ', ' . $this->parseExpr($expr->args[1]->value) . ')';
if (isset($config['constFold'])) {
$folded = $this->tryConstFold($config['constFold'], $config['constFoldExtra'] ?? null, $expr);
if ($folded !== false) {
return $folded;
}
return 'php::math::pow(' . $getArg(0) . ', ' . $getArg(1) . ')';
}
if ($name === 'sqrt') {
$type = $this->detectTypeOfExpr($expr->args[0]->value);
if ($type === self::TYPE_BIGINT) {
return 'php::BigInt::sqrt(' . $this->parseExpr($expr->args[0]->value) . ')';
}
if ($type === self::TYPE_DECIMAL) {
return 'php::Decimal::sqrt(' . $this->parseExpr($expr->args[0]->value) . ')';
$args = $this->buildArgList($expr, $argTypeStr, $defaults);
return $target . '(' . implode(', ', $args) . ')';
}
// =========================================================================
// Auto-detect argument types from PHP reflection
// =========================================================================
private function getArgReflectionInfo(string $funcName): array
{
if (isset($this->_autoArgTypes[$funcName])) {
return $this->_autoArgTypes[$funcName];
}
$ref = Reflection::getFunction($funcName);
if (!$ref) {
return $this->_autoArgTypes[$funcName] = ['args' => '', 'variadic' => false];
}
$types = [];
$variadic = false;
foreach ($ref->getParameters() as $param) {
if ($param->isVariadic()) {
$variadic = true;
continue;
}
if ($type === self::TYPE_BIGFLOAT) {
return 'php::BigFloat::sqrt(' . $this->parseExpr($expr->args[0]->value) . ')';
$char = $this->phpParamToArgChar($param);
if ($param->isOptional()) {
$char = self::A_OPT . $char;
}
$types[] = $char;
}
if ($name === 'floor') {
$type = $this->detectTypeOfExpr($expr->args[0]->value);
if ($type === self::TYPE_DECIMAL) {
return 'php::Decimal::floor(' . $this->parseExpr($expr->args[0]->value) . ')';
}
return $this->_autoArgTypes[$funcName] = ['args' => implode('_', $types), 'variadic' => $variadic];
}
private function phpParamToArgChar(\ReflectionParameter $param): string
{
if ($param->isPassedByReference()) {
return self::A_R;
}
if ($name === 'ceil') {
$type = $this->detectTypeOfExpr($expr->args[0]->value);
if ($type === self::TYPE_DECIMAL) {
return 'php::Decimal::ceil(' . $this->parseExpr($expr->args[0]->value) . ')';
$type = $param->getType();
if ($type instanceof \ReflectionNamedType) {
return match ($type->getName()) {
'string' => self::A_S,
'int' => self::A_I,
'float' => self::A_F,
'bool' => self::A_B,
default => self::A_V,
};
}
return self::A_V;
}
// =========================================================================
// Arg helpers
// =========================================================================
private function getArg(Node\Expr\FuncCall $expr, int $i): string
{
return $this->parseIdentifier($expr->args[$i]->value);
}
private function getRefArg(Node\Expr\FuncCall $expr, int $i): string
{
$arg = $expr->args[$i]->value;
if ($this->isArrayDimFetch($arg) and $this->isVarExpr($arg->var)) {
$array = $this->parseIdentifier($arg->var);
if ($arg->dim !== null) {
$tmpRef = $this->genTmpVarName();
$this->context->beforeStmtLines[] = 'auto&& ' . $tmpRef . ' = ' . $array . '.item(' . $this->identifierToStr($arg->dim) . ', true);';
return $tmpRef;
}
}
if ($name === 'round') {
$type = $this->detectTypeOfExpr($expr->args[0]->value);
if ($type === self::TYPE_DECIMAL) {
$arg0 = $this->parseExpr($expr->args[0]->value);
if (count($expr->args) >= 2) {
return 'php::Decimal::round(' . $arg0 . ', ' . $this->parseExpr($expr->args[1]->value) . ')';
if ($this->isPropertyFetch($arg) and $this->isVarExpr($arg->var)) {
$obj = $this->parseIdentifier($arg->var);
$tmpRef = $this->genTmpVarName();
$this->context->beforeStmtLines[] = 'auto&& ' . $tmpRef . ' = ' . $obj . '.attr(' . $this->identifierToStr($arg->name) . ', true);';
return $tmpRef;
}
return $this->getArg($expr, $i);
}
private function resolveArg(Node\Expr\FuncCall $expr, int $index, string $type): string
{
$base = ($type[0] ?? '') === self::A_OPT ? substr($type, 1) : $type;
$raw = ($base === self::A_R) ? $this->getRefArg($expr, $index) : $this->getArg($expr, $index);
return match ($base) {
self::A_S => $this->convertStringExpr($raw),
self::A_I => $this->convertIntExpr($raw),
self::A_F => $this->convertFloatExpr($raw),
self::A_B => $this->convertBoolExpr($raw),
default => $raw,
};
}
private function buildArgList(Node\Expr\FuncCall $expr, string $argTypeStr, array $defaults = []): array
{
if ($argTypeStr === '') {
return [];
}
$types = explode('_', $argTypeStr);
$argCount = count($expr->args);
$args = [];
foreach ($types as $i => $type) {
$optional = ($type[0] ?? '') === self::A_OPT;
if ($optional && $argCount <= $i) {
if (isset($defaults[$i])) {
$args[] = $defaults[$i];
}
return 'php::Decimal::round(' . $arg0 . ')';
continue;
}
$args[] = $this->resolveArg($expr, $i, $type);
}
if ($name === 'strlen') {
if ($expr->args[0]->value instanceof Node\Scalar\String_) {
return strlen($expr->args[0]->value->value) . $this->getPlatform()->getIntegerLiteralSuffix();
}
return 'php::fn::strlen(' . $getArg(0) . ')';
return $args;
}
// =========================================================================
// Variadic, conversion, Big* dispatch
// =========================================================================
private function genVariadicCall(string $target, Node\Expr\FuncCall $expr): string
{
$args = [];
foreach ($expr->args as $arg) {
$args[] = $this->parseExpr($arg->value);
}
if ($name === 'ord') {
return 'php::fn::ord(' . $getArg(0) . ')';
return $target . '({' . implode(', ', $args) . '})';
}
private function dispatchConversion(Node\Expr\FuncCall $expr, string $convType): string
{
$arg = $expr->args[0]->value;
$type = $this->detectTypeOfExpr($arg);
$parsed = $this->parseExpr($arg);
if ($convType === self::A_S) {
return match ($type) {
self::TYPE_BIGINT => 'php::BigInt::toString(' . $parsed . ')',
self::TYPE_BIGFLOAT => 'php::BigFloat::toString(' . $parsed . ')',
self::TYPE_DECIMAL => 'php::Decimal::toString(' . $parsed . ')',
default => $this->convertStringExpr($parsed),
};
}
if ($name === 'chr') {
return 'php::fn::chr(' . $this->convertIntExpr($getArg(0)) . ')';
return match ($convType) {
self::A_I => $this->convertIntExpr($parsed),
self::A_F => $this->convertFloatExpr($parsed),
self::A_B => $this->convertBoolExpr($parsed),
default => $parsed,
};
}
private function dispatchBigType(Node\Expr\FuncCall $expr, array $dispatch): string|false
{
$type = $this->detectTypeOfExpr($expr->args[0]->value);
$target = $dispatch[$type] ?? $dispatch['fallback'] ?? null;
if (!$target) {
return false;
}
if ($name === 'array_key_exists') {
return $getArg(1) . '.offsetExists(' . $getArg(0) . ')';
$args = [$this->parseExpr($expr->args[0]->value)];
if (count($expr->args) >= 2) {
$args[] = $this->parseExpr($expr->args[1]->value);
}
if ($name === 'func_get_arg') {
return $this->genFuncGetArg($name, $expr);
return $target . '(' . implode(', ', $args) . ')';
}
// =========================================================================
// Constant folding
// =========================================================================
private function tryConstFold(int $rule, mixed $extra, Node\Expr\FuncCall $expr): string|false
{
return match ($rule) {
self::FOLD_STRING_LEN => $this->doFoldStringLen($expr),
self::FOLD_STRING_CASE => $this->doFoldStringCase($expr),
self::FOLD_CMP2 => $this->doFoldCmp2($expr),
self::FOLD_CMP3 => $this->doFoldCmp3($expr),
self::FOLD_COUNT_LITERAL => $this->doFoldCountLiteral($expr),
self::FOLD_KNOWN_CLASS => $this->doFoldKnownClass($expr),
self::FOLD_KNOWN_CONSTANT => $this->doFoldKnownConstant($expr),
self::FOLD_SSA_TYPE => $this->doFoldSsaType($expr, $extra),
default => false,
};
}
private function doFoldStringLen(Node\Expr\FuncCall $expr): string|false
{
$arg = $expr->args[0]->value;
return ($arg instanceof Node\Scalar\String_)
? strlen($arg->value) . $this->getPlatform()->getIntegerLiteralSuffix()
: false;
}
private function doFoldStringCase(Node\Expr\FuncCall $expr): string|false
{
$arg = $expr->args[0]->value;
if (!$this->isScalarString($arg)) {
return false;
}
if ($name === 'func_get_args') {
return $this->genFuncGetArgs($name, $expr);
$func = $expr->name instanceof Node\Name ? $expr->name->toLowerString() : '';
$val = $func === 'strtoupper' ? strtoupper($arg->value) : strtolower($arg->value);
return $this->getLiteralString($val);
}
private function doFoldCmp2(Node\Expr\FuncCall $expr): string|false
{
$a0 = $expr->args[0]->value;
$a1 = $expr->args[1]->value;
if (!$this->isScalarString($a0) || !$this->isScalarString($a1)) {
return false;
}
if ($name === 'func_num_args') {
return $this->genFuncNumArgs($name, $expr);
$func = $expr->name instanceof Node\Name ? $expr->name->toLowerString() : '';
$result = $func === 'strcasecmp'
? strcasecmp($a0->value, $a1->value)
: strcmp($a0->value, $a1->value);
return $result . $this->getPlatform()->getIntegerLiteralSuffix();
}
private function doFoldCmp3(Node\Expr\FuncCall $expr): string|false
{
$a0 = $expr->args[0]->value;
$a1 = $expr->args[1]->value;
$a2 = $expr->args[2]->value;
if (!$this->isScalarString($a0) || !$this->isScalarString($a1) || !$this->isScalarInt($a2)) {
return false;
}
if ($name === 'function_exists') {
return $this->genFunctionExists($name, $expr);
$func = $expr->name instanceof Node\Name ? $expr->name->toLowerString() : '';
$result = $func === 'strncasecmp'
? strncasecmp($a0->value, $a1->value, (int) $a2->value)
: strncmp($a0->value, $a1->value, (int) $a2->value);
return $result . $this->getPlatform()->getIntegerLiteralSuffix();
}
private function doFoldCountLiteral(Node\Expr\FuncCall $expr): string|false
{
if (count($expr->args) !== 1 || !($expr->args[0] instanceof Node\Arg)) {
return false;
}
if ($name === 'class_exists') {
$className = $expr->args[0]->value;
if ($this->isScalarString($className) and $this->hasClass($className->value)) {
return 'true';
}
$arg = $expr->args[0]->value;
if ($arg instanceof Node\Expr\Array_) {
return count($arg->items) . $this->getPlatform()->getIntegerLiteralSuffix();
}
if ($name === 'compact') {
return $this->genCompact($expr);
return $this->genStdContainerCount($arg);
}
private function doFoldKnownClass(Node\Expr\FuncCall $expr): string|false
{
$cn = $expr->args[0]->value;
return ($this->isScalarString($cn) && $this->hasClass($cn->value)) ? 'true' : false;
}
private function doFoldKnownConstant(Node\Expr\FuncCall $expr): string|false
{
$cn = $expr->args[0]->value;
return ($this->isScalarString($cn) && $this->hasConstant($cn->value)) ? 'true' : false;
}
private function doFoldSsaType(Node\Expr\FuncCall $expr, mixed $expectType): string|false
{
if (count($expr->args) !== 1 || !($expr->args[0] instanceof Node\Arg)) {
return false;
}
if ($name === 'get_class') {
return $this->genGetClass($expr);
return ($this->detectTypeOfExpr($expr->args[0]->value) === $expectType) ? 'true' : false;
}
// =========================================================================
// Custom handlers
// =========================================================================
private function genIsNull(string $n, Node\Expr\FuncCall $e, array $c): string
{
return $this->parseIdentifier($e->args[0]->value) . '.isNull()';
}
private function genGetClassOptimized(string $n, Node\Expr\FuncCall $e, array $c): string
{
$obj = $e->args[0]->value;
if ($this->isVarExpr($obj) && $this->isTypedObject($obj->name)) {
return $this->getLiteralString($this->getObjectType($obj->name));
}
// 除了 Node\Arg 之外,可能是 VariadicPlaceholder 占位符 (...)
if (count($expr->args) === 1 && $expr->args[0] instanceof Node\Arg) {
$arg = $expr->args[0]->value;
$type = $this->detectTypeOfExpr($arg);
// is_* compile-time elimination when SSA-narrowed
if ($name === 'is_int' && $type === self::TYPE_INT) {
return 'true';
}
if ($name === 'is_float' && $type === self::TYPE_FLOAT) {
return 'true';
}
if ($name === 'is_bool' && $type === self::TYPE_BOOL) {
return 'true';
return 'php::std::get_class(' . $this->parseIdentifier($obj) . ')';
}
private function genGetParentClass(string $n, Node\Expr\FuncCall $e, array $c): string
{
if (count($e->args) === 0) {
if ($this->classDef && $this->classDef->extends) {
return $this->getLiteralString($this->classDef->extends);
}
if ($name === 'is_null') {
return $this->parseIdentifier($arg) . '.isNull()';
return 'false';
}
$arg = $e->args[0]->value;
if ($this->isScalarString($arg)) {
$cls = $this->getClass($arg->value);
if ($cls && $cls->extends) return $this->getLiteralString($cls->extends);
if ($cls && !$cls->extends) return 'false';
}
return 'php::std::get_parent_class(' . $this->parseIdentifier($arg) . ')';
}
private function genFunctionExistsOptimized(string $n, Node\Expr\FuncCall $e, array $c): string
{
return $this->genFunctionExists($n, $e);
}
private function genFuncGetArgOptimized(string $n, Node\Expr\FuncCall $e, array $c): string
{
return $this->genFuncGetArg($n, $e);
}
private function genFuncGetArgsOptimized(string $n, Node\Expr\FuncCall $e, array $c): string
{
return $this->genFuncGetArgs($n, $e);
}
private function genFuncNumArgsOptimized(string $n, Node\Expr\FuncCall $e, array $c): string
{
return $this->genFuncNumArgs($n, $e);
}
private function genCompactOptimized(string $n, Node\Expr\FuncCall $e, array $c): string
{
return $this->genCompactOrig($e);
}
private function genMaxMin(string $n, Node\Expr\FuncCall $e, array $c): string
{
$target = 'php::std::' . $n;
if (count($e->args) == 1) {
return $target . '(' . $this->getArg($e, 0) . ')';
}
$a = [];
foreach ($e->args as $arg) {
$a[] = $this->parseExpr($arg->value);
}
return $target . '(php::Array{' . implode(', ', $a) . '})';
}
private function genArrayKeys(string $n, Node\Expr\FuncCall $e, array $c): string
{
$cnt = count($e->args);
if ($cnt >= 3) {
return 'php::std::array_keys_filter(' . $this->getArg($e, 0) . ', ' . $this->getArg($e, 1) . ', ' . $this->getArg($e, 2) . ')';
}
if ($cnt >= 2) {
return 'php::std::array_keys_filter(' . $this->getArg($e, 0) . ', ' . $this->getArg($e, 1) . ', false)';
}
return 'php::std::array_keys(' . $this->getArg($e, 0) . ')';
}
private function genArrayKeyExists(string $n, Node\Expr\FuncCall $e, array $c): string
{
return $this->getArg($e, 1) . '.offsetExists(' . $this->getArg($e, 0) . ')';
}
private function genRound(string $n, Node\Expr\FuncCall $e, array $c): string
{
$type = $this->detectTypeOfExpr($e->args[0]->value);
if ($type === self::TYPE_DECIMAL) {
$a0 = $this->parseExpr($e->args[0]->value);
if (count($e->args) >= 2) {
return 'php::Decimal::round(' . $a0 . ', ' . $this->parseExpr($e->args[1]->value) . ')';
}
// Compile-time count() on literal arrays
if ($name === 'count' && $arg instanceof Node\Expr\Array_) {
$itemCount = count($arg->items);
return $itemCount . $this->getPlatform()->getIntegerLiteralSuffix();
return 'php::Decimal::round(' . $a0 . ')';
}
$args = count($e->args);
if ($args >= 3) {
return 'php::std::round(' . $this->getArg($e, 0) . ', ' . $this->convertIntExpr($this->getArg($e, 1)) . ', ' . $this->convertIntExpr($this->getArg($e, 2)) . ')';
}
if ($args >= 2) {
return 'php::std::round(' . $this->getArg($e, 0) . ', ' . $this->convertIntExpr($this->getArg($e, 1)) . ')';
}
return 'php::std::round(' . $this->getArg($e, 0) . ')';
}
private function genCount(string $n, Node\Expr\FuncCall $e, array $c): string
{
$folded = $this->doFoldCountLiteral($e);
if ($folded !== false) return $folded;
if (count($e->args) >= 2) {
return 'php::std::count(' . $this->getArg($e, 0) . ', ' . $this->convertIntExpr($this->getArg($e, 1)) . ')';
}
return 'php::std::count(' . $this->getArg($e, 0) . ')';
}
private function genDefine(string $n, Node\Expr\FuncCall $e, array $c): string|false
{
$arg = $e->args[0]->value;
if ($this->isScalarString($arg) && !$this->isValidDefineName($arg->value)) {
$this->fatalError($e, 'Invalid define name `' . $arg->value . '`');
}
$args = count($e->args) >= 3 ? 3 : 2;
if ($args == 3) {
return 'php::std::define(' . $this->getArg($e, 0) . ', ' . $this->getArg($e, 1) . ', ' . $this->getArg($e, 2) . ')';
}
return 'php::std::define(' . $this->getArg($e, 0) . ', ' . $this->getArg($e, 1) . ')';
}
// =========================================================================
// Legacy helpers
// =========================================================================
private function genFuncGetArgs(string $name, Node\Expr\FuncCall $expr): string
{
$this->warningUndefinedBehavior($expr);
$funcDef = $this->functionDef;
$list = [];
foreach ($funcDef->argInfoList as $i => $argInfo) {
if ($argInfo->variadic) {
$tmpVar = $this->addTmpVar(self::TYPE_ARRAY);
$this->context->beforeStmtLines[] = $this->genArray($list) . ';';
$this->context->beforeStmtLines[] = $tmpVar . '.merge(' . $argInfo->name . ');';
return $tmpVar;
}
// Compile-time string operations on literals
if ($this->isScalarString($arg)) {
$val = $arg->value;
switch ($name) {
case 'strtoupper':
return $this->getLiteralString(strtoupper($val));
case 'strtolower':
return $this->getLiteralString(strtolower($val));
case 'trim':
return $this->getLiteralString(trim($val));
$list[] = $argInfo->name;
}
return $this->genArray($list);
}
protected function genFuncGetArg(string $name, Node\Expr\FuncCall $expr)
{
$this->warningUndefinedBehavior($expr);
$position = $expr->args[0]->value;
if ($this->isScalarInt($position)) {
$funcDef = $this->functionDef;
$posInt = intval($position->value);
foreach ($funcDef->argInfoList as $i => $argInfo) {
if ($argInfo->variadic) {
return $argInfo->name . '.offsetGet(' . ($posInt - $i) . ')';
}
if ($i == $posInt) {
return $argInfo->name;
}
}
$this->fatalError($expr, 'wrong parameter position `' . $posInt . '`');
} else {
$this->fatalError($expr, 'func_get_arg() only support scalar int');
}
return false;
}
protected function genFuncNumArgs(string $name, Node\Expr\FuncCall $expr): string
private function genFuncNumArgs(string $name, Node\Expr\FuncCall $expr): string
{
$this->warningUndefinedBehavior($expr);
$funcDef = $this->functionDef;
@ -252,25 +713,24 @@ trait FuncCallOptimizer
return 'true';
}
$funcName = $this->getLiteralString($nameLower);
return 'php::fn::function_exists(' . $funcName . ', true)';
return 'php::std::function_exists(' . $funcName . ')';
}
return 'php::fn::function_exists(' . $this->parseIdentifier($funcName) . ')';
return 'php::std::function_exists(' . $this->parseIdentifier($funcName) . ')';
}
protected function genGetClass(Node\Expr\FuncCall $expr): string
private function genGetClass(Node\Expr\FuncCall $expr): string
{
$object = $expr->args[0]->value;
if ($this->isVarExpr($object) and $this->isTypedObject($object->name)) {
return $this->getLiteralString($this->getObjectType($object->name));
}
return 'php::fn::get_class(' . $this->parseIdentifier($object) . ')';
return 'php::std::get_class(' . $this->parseIdentifier($object) . ')';
}
protected function genCompact(Node\Expr\FuncCall $expr): string
private function genCompactOrig(Node\Expr\FuncCall $expr): string
{
$list = [];
$this->indentLevel++;
foreach ($expr->args as $arg) {
if (!$this->isScalarString($arg->value)) {
$this->fatalError($expr, 'The argument of compact function can only be literal string');
@ -284,10 +744,19 @@ trait FuncCallOptimizer
}
$key = $this->getLiteralString($var);
$list[] = $this->getIndent() . '{ ' . $key . '.str(), ' . $var . ' }';
$cVar = $this->escapeVarName($var);
$list[] = '{' . $key . ', php::Var(' . $cVar . ')}';
}
$this->indentLevel--;
return $this->genArray($list);
return 'php::Array{' . implode(', ', $list) . '}';
}
// =========================================================================
// Utility
// =========================================================================
protected function isValidDefineName(string $name): bool
{
return preg_match('/^(?!\d)[\p{L}_][\p{L}\p{N}_]*$/u', $name) === 1;
}
}

@ -398,9 +398,7 @@ class Translator extends Preprocessor
public function prepare(string $path): array
{
// 预先添加一些函数符号,在编译阶段部分语法会转为函数调用
$this->funcSymbols['shell_exec'] = $this->getFuncPtr('shell_exec');
$this->funcSymbols['define'] = $this->getFuncPtr('define');
// shell_exec 和 define 已通过 php::std:: 直接调用,无需动态符号表
// 根据平台检查库文件(仅在构建二进制文件时需要)
if ($this->isBuildModeBin()) {
@ -683,7 +681,7 @@ CODE;
$code .= '// register constants' . PHP_EOL;
foreach ($this->constants as $name => $const) {
$code .= "{$name} = {$const->value};\n";
$code .= 'php::call(' . $this->funcSymbols['define'] . ', { ' . $this->genCharPtr($const->name, true) . ', ' . $name . ' });' . PHP_EOL;
$code .= 'php::std::define(' . $this->genCharPtr($const->name, true) . ', ' . $name . ');' . PHP_EOL;
}
$code .= '// global vars ' . PHP_EOL;
foreach ($this->globalVars as $name => $type) {

Loading…
Cancel
Save