feat(compiler): 添加性能分析和 objval 函数支持

- 在 Clang、Gcc 和 Msvc 编译后端中添加 PROF_OUTPUT_FILE 宏定义支持
- 实现编译器性能分析模式,通过 --profile 参数启用并自动链接 -lprofiler 库
- 添加 .prof 文件分析功能,支持 php bin/compiler.php app.prof 格式调用 pprof 工具
- 实现 objval() 函数的 AOT 编译支持,用于对象类型转换
- 更新编译器缓存逻辑,启用性能分析时强制重新编译
- 添加 Linux 平台限制检查,非 Linux 系统使用 --profile 参数时显示错误提示
- 创建 objval 函数的单元测试用例
pull/1/head
韩天峰 3 months ago
parent 33ee85c122
commit 1ea7ec0764
  1. 8
      bin/prof.php
  2. 3
      src/Php/Backend/Clang.php
  3. 3
      src/Php/Backend/Gcc.php
  4. 3
      src/Php/Backend/Msvc.php
  5. 25
      src/Php/CompilerBase.php
  6. 10
      src/Php/Translator.php
  7. 33
      src/compiler.php
  8. 42
      tests/aot/class/objval.phpt

@ -1,8 +0,0 @@
#!/usr/bin/env php
<?php
if (count($argv) < 2) {
echo "Usage: $argv[0] elf\n";
exit(1);
}
$elf = $argv[1];
shell_exec("pprof --pdf $elf profile.out > profile.pdf");

@ -410,6 +410,9 @@ class Clang extends CompilerBackend
// 性能分析宏
if (!empty($config['enable_profiler'])) {
$cmd .= ' -DPPROF_ON=1';
if (!empty($config['prof_output'])) {
$cmd .= ' -DPROF_OUTPUT_FILE=\'"' . $config['prof_output'] . '"\'';
}
}
// 用户自定义编译标志

@ -292,6 +292,9 @@ class Gcc extends CompilerBackend
// 性能分析宏
if (!empty($config['enable_profiler'])) {
$cmd .= ' -DPPROF_ON=1';
if (!empty($config['prof_output'])) {
$cmd .= ' -DPROF_OUTPUT_FILE=\'"' . $config['prof_output'] . '"\'';
}
}
// 用户自定义编译标志

@ -379,6 +379,9 @@ class Msvc extends CompilerBackend
// 性能分析宏
if (!empty($config['enable_profiler'])) {
$cmd .= ' /DPPROF_ON=1';
if (!empty($config['prof_output'])) {
$cmd .= ' /DPROF_OUTPUT_FILE=\'"' . $config['prof_output'] . '"\'';
}
}
// 用户自定义编译标志

@ -2883,6 +2883,7 @@ class CompilerBase extends \PhpAot\Core\Translator
'is_zts' => $this->isPhpZts,
'build_mode' => $this->buildMode,
'enable_profiler' => $this->enableProfiler,
'prof_output' => $this->targetName . '.prof',
'suppressed_warnings' => Constants::MSVC_SUPPRESSED_WARNINGS ?? [],
'cxxflags' => $this->cxxFlags,
];
@ -2920,10 +2921,16 @@ class CompilerBase extends \PhpAot\Core\Translator
protected function getLinkCommandOptions(): array
{
$ldflags = $this->ldflags;
if ($this->enableProfiler) {
$ldflags .= ' -lprofiler';
}
$options = [
'library_paths' => $this->getLibraryPaths(),
'libraries' => $this->getLibraries(),
'ldflags' => $this->ldflags,
'ldflags' => $ldflags,
'debug' => $this->debug,
'no_console' => $this->noConsole,
'build_mode' => $this->buildMode,
@ -3408,6 +3415,9 @@ class CompilerBase extends \PhpAot\Core\Translator
if (in_array($name, Constants::UNSUPPORTED_FUNCTIONS)) {
$this->fatalError($expr, 'Unsupported function: `' . $name . '`');
}
if ($name === 'objval') {
return $this->genObjvalCall($expr);
}
$nativeFn = $this->findNativeFunction($name);
if ($nativeFn) {
$expr->setAttribute('nativeCall', $nativeFn);
@ -5277,6 +5287,19 @@ class CompilerBase extends \PhpAot\Core\Translator
return $left . ' = &' . $tmpVar;
}
protected function genObjvalCall(Expr\FuncCall $expr): string
{
if (count($expr->args) !== 2) {
$this->fatalError($expr, 'objval() requires exactly 2 arguments');
}
$receiver = $this->parseExpr($expr->args[0]->value);
$className = $this->resolveClassNameArg($expr->args[1]->value);
if ($className === '') {
$this->fatalError($expr, 'The second parameter of objval() only supports string literals or `ClassName::class` constant');
}
return 'php::toObject(' . $receiver . ', ' . $this->getClassEntryPtr($className) . ', true)';
}
protected function genToObjectCall(Expr\MethodCall $expr, string $receiver): string
{
if (empty($expr->args)) {

@ -113,7 +113,7 @@ class Translator extends Preprocessor
$climate->bold('OPTIONS:');
$climate->tab()->out('-O <level> Optimization level (0-3, default: 0)');
$climate->tab()->out('-p, --profile Enable performance profiling');
$climate->tab()->out('--profile Enable performance profiling (adds -lprofiler, forces recompile)');
$climate->tab()->out('-d, --debug Enable debug mode (auto-disable optimizations, add debug symbols)');
$climate->tab()->out('--cxx-std <version> C++ standard version (c++17, c++20, etc.)');
$climate->tab()->out('-o, --output <file> Output binary name (default: input basename)');
@ -177,8 +177,12 @@ class Translator extends Preprocessor
$this->noLiteralStrings = true;
}
// 启用性能分析
// 启用性能分析(需强制重编译 misc 文件以确保 PPROF_ON 宏生效,仅 Linux 支持)
if ($this->climate->arguments->defined('profile')) {
if (!$this->isLinux()) {
$this->climate->error('--profile is only supported on Linux (requires gperftools)');
exit(1);
}
$this->enableProfiler = true;
}
@ -694,7 +698,7 @@ CODE;
*/
public function hasMiscObjectFileCache(string $cppFile): bool
{
if ($this->climate->arguments->defined('force')) {
if ($this->climate->arguments->defined('force') || $this->enableProfiler) {
return false;
}

@ -7,6 +7,12 @@ function main(int $argc, array $argv): void
define("ROOT_PATH", getcwd());
}
// .prof 文件分析模式:php bin/compiler.php app.prof
if ($argc >= 2 && str_ends_with($argv[1], '.prof')) {
profileAnalyze($argc, $argv);
return;
}
require_once ROOT_PATH . '/vendor/autoload.php';
global $translator;
@ -25,3 +31,30 @@ function main(int $argc, array $argv): void
$translator->run($binaryFile); // never returns
}
}
function profileAnalyze(int $argc, array $argv): void
{
$profFile = $argv[1];
if (!file_exists($profFile)) {
fwrite(STDERR, "Profile file not found: {$profFile}\n");
exit(1);
}
// 从 prof 文件名推导二进制文件名(app.prof → app)
$binary = basename($profFile, '.prof');
if (!file_exists($binary) && file_exists('./' . $binary)) {
$binary = './' . $binary;
}
if (!file_exists($binary)) {
fwrite(STDERR, "Binary not found: {$binary} (expected from prof file name)\n");
fwrite(STDERR, "Usage: php bin/compiler.php <binary>.prof\n");
exit(1);
}
$cmd = 'pprof --web ' . escapeshellarg($binary) . ' ' . escapeshellarg($profFile);
fwrite(STDERR, "Running: {$cmd}\n");
passthru($cmd, $exitCode);
exit($exitCode);
}

@ -0,0 +1,42 @@
--TEST--
objval
--FILE--
<?php
class TestEvent
{
public int $x = 0;
public int $y = 0;
public string $action = '';
public function __construct(int $x, int $y, string $action)
{
$this->x = $x;
$this->y = $y;
$this->action = $action;
}
public function getX(): int {
return $this->x;
}
}
function wrapObjval($ev): TestEvent
{
return objval($ev, TestEvent::class);
}
function main() {
$ev = new TestEvent(42, 0, 'base');
$ev2 = wrapObjval($ev);
var_dump($ev2->x);
var_dump($ev2->getX());
echo "done\n";
}
?>
--EXPECT--
int(42)
int(42)
done
Loading…
Cancel
Save