mirror of
https://github.com/crazywhalecc/static-php-cli.git
synced 2026-07-02 14:25:41 +08:00
add cs profiling as optional second step profiling pass
This commit is contained in:
@@ -130,6 +130,7 @@ class LinuxBuilder extends UnixBuilderBase
|
||||
|
||||
$this->emitPatchPoint('before-php-make');
|
||||
SourcePatcher::patchBeforeMake($this);
|
||||
PgoManager::patchBeforeMake($this);
|
||||
|
||||
$this->cleanMake();
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ abstract class UnixBuilderBase extends BuilderBase
|
||||
throw new SPCInternalException("Deploy failed. Cannot find file after copy: {$dst}");
|
||||
}
|
||||
|
||||
if (!$this->getOption('no-strip') && !$this->getOption('pgi')) {
|
||||
if (!$this->getOption('no-strip') && !$this->getOption('pgi') && !$this->getOption('cs-pgi')) {
|
||||
// extract debug info
|
||||
$this->extractDebugInfo($dst);
|
||||
// extra strip
|
||||
|
||||
@@ -51,6 +51,7 @@ class BuildPHPCommand extends BuildCommand
|
||||
$this->addOption('enable-micro-win32', null, null, 'Enable win32 mode for phpmicro (Windows only)');
|
||||
$this->addOption('with-frankenphp-app', null, InputOption::VALUE_REQUIRED, 'Path to a folder to be embedded in FrankenPHP');
|
||||
$this->addOption('pgi', null, null, 'Build instrumented binaries (-fprofile-generate). Run them to collect .profraw files, then re-run with --pgo.');
|
||||
$this->addOption('cs-pgi', null, null, 'Build cs-instrumented binaries (-fprofile-use=<existing.profdata> -fcs-profile-generate). Requires a prior --pgi+--pgo cycle.');
|
||||
$this->addOption('pgo', null, null, 'Build optimised binaries (-fprofile-use) from .profraw collected by a previous --pgi run.');
|
||||
}
|
||||
|
||||
@@ -215,13 +216,16 @@ class BuildPHPCommand extends BuildCommand
|
||||
FileSystem::removeDir(BUILD_MODULES_PATH);
|
||||
|
||||
$pgi = (bool) $this->getOption('pgi');
|
||||
$csPgi = (bool) $this->getOption('cs-pgi');
|
||||
$pgo = (bool) $this->getOption('pgo');
|
||||
if ($pgi && $pgo) {
|
||||
$this->output->writeln('<error>--pgi and --pgo are mutually exclusive</error>');
|
||||
if (((int) $pgi + (int) $csPgi + (int) $pgo) > 1) {
|
||||
$this->output->writeln('<error>--pgi, --cs-pgi, and --pgo are mutually exclusive</error>');
|
||||
return static::FAILURE;
|
||||
}
|
||||
if ($pgi) {
|
||||
(new PgoManager())->setupInstrument($rule);
|
||||
} elseif ($csPgi) {
|
||||
(new PgoManager())->setupCsInstrument($rule);
|
||||
} elseif ($pgo) {
|
||||
(new PgoManager())->setupUse($rule);
|
||||
}
|
||||
|
||||
@@ -45,11 +45,11 @@ NEED_CRT=0 # https://codeberg.org/ziglang/zig/issues/32064
|
||||
for _a in "${PARSED_ARGS[@]}"; do
|
||||
case "$_a" in
|
||||
-c|-S|-E|-M|-MM) IS_LINK=0 ;;
|
||||
-fprofile-generate*|-fprofile-instr-generate*) NEED_PROFILE_RT=1 ;;
|
||||
-fprofile-generate*|-fprofile-instr-generate*|-fcs-profile-generate*) NEED_PROFILE_RT=1 ;;
|
||||
-shared) NEED_CRT=1 ;;
|
||||
esac
|
||||
done
|
||||
[[ "$SPC_COMPILER_EXTRA" == *-fprofile-generate* ]] && NEED_PROFILE_RT=1
|
||||
[[ "$SPC_COMPILER_EXTRA" == *-fprofile-generate* || "$SPC_COMPILER_EXTRA" == *-fcs-profile-generate* ]] && NEED_PROFILE_RT=1
|
||||
if [[ $IS_LINK -eq 1 && $NEED_PROFILE_RT -eq 1 && -f "$SCRIPT_DIR/lib/libclang_rt.profile.a" ]]; then
|
||||
PARSED_ARGS+=("$SCRIPT_DIR/lib/libclang_rt.profile.a" "-Wl,-u,__llvm_profile_runtime")
|
||||
fi
|
||||
|
||||
@@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace SPC\util;
|
||||
|
||||
use SPC\builder\BuilderBase;
|
||||
use SPC\exception\WrongUsageException;
|
||||
use SPC\store\FileSystem;
|
||||
use SPC\store\SourcePatcher;
|
||||
@@ -16,6 +17,8 @@ class PgoManager
|
||||
{
|
||||
public const MODE_INSTRUMENT = 'instrument';
|
||||
|
||||
public const MODE_CS_INSTRUMENT = 'cs-instrument';
|
||||
|
||||
public const MODE_USE = 'use';
|
||||
|
||||
private const TRAINABLE = [
|
||||
@@ -68,6 +71,23 @@ class PgoManager
|
||||
logger()->info('pgo --pgi: instrumented build, profraw will land under ' . $this->profileRoot . '/<sapi>/');
|
||||
}
|
||||
|
||||
/** Setup --cs-pgi: build with -fprofile-use=<sapi.profdata> -fcs-profile-generate=<cs-dir>. Requires existing .profdata. */
|
||||
public function setupCsInstrument(int $rule): void
|
||||
{
|
||||
$this->validateRule($rule);
|
||||
foreach ($this->trainableIn($rule) as $sapi) {
|
||||
if (!is_file($this->profDataFile($sapi))) {
|
||||
throw new WrongUsageException("--cs-pgi: missing {$sapi}.profdata; run --pgi + --pgo first");
|
||||
}
|
||||
f_mkdir($this->csRawDir($sapi), recursive: true);
|
||||
}
|
||||
$this->mode = self::MODE_CS_INSTRUMENT;
|
||||
self::$active = $this;
|
||||
$this->applyShutdownPatches();
|
||||
$this->applyForSapi($this->trainableIn($rule)[0]);
|
||||
logger()->info('pgo --cs-pgi: cs-instrumented build, cs-profraw under ' . $this->profileRoot . '/cs-<sapi>/');
|
||||
}
|
||||
|
||||
/** Setup --pgo: merge collected .profraw, then build with -fprofile-use=<sapi.profdata>. */
|
||||
public function setupUse(int $rule): void
|
||||
{
|
||||
@@ -83,6 +103,29 @@ class PgoManager
|
||||
$this->applyForSapi($this->trainableIn($rule)[0]);
|
||||
}
|
||||
|
||||
/** Patches php-src/libtool to passthrough -fcs-profile-* flags (otherwise dropped during shared lib link). */
|
||||
public static function patchBeforeMake(BuilderBase $builder): void
|
||||
{
|
||||
if (!$builder->getOption('cs-pgi')) {
|
||||
return;
|
||||
}
|
||||
$libtool = SOURCE_PATH . '/php-src/libtool';
|
||||
if (!is_file($libtool)) {
|
||||
return;
|
||||
}
|
||||
$contents = file_get_contents($libtool);
|
||||
if (str_contains($contents, '-fcs-profile-*')) {
|
||||
return;
|
||||
}
|
||||
$patched = str_replace('-fprofile-*|-F*', '-fprofile-*|-fcs-profile-*|-F*', $contents);
|
||||
if ($patched === $contents) {
|
||||
logger()->warning('pgo --cs-pgi: could not patch libtool for -fcs-profile-* passthrough');
|
||||
return;
|
||||
}
|
||||
file_put_contents($libtool, $patched);
|
||||
logger()->info('pgo --cs-pgi: patched libtool for -fcs-profile-* passthrough');
|
||||
}
|
||||
|
||||
public function applyForSapi(string $sapi): void
|
||||
{
|
||||
$sapi = $this->resolveSapi($sapi);
|
||||
@@ -95,12 +138,18 @@ class PgoManager
|
||||
$this->setFlag('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM', '');
|
||||
return;
|
||||
}
|
||||
$flags = $this->mode === self::MODE_INSTRUMENT
|
||||
? '-fprofile-generate=' . $this->rawDir($sapi)
|
||||
: '-fprofile-use=' . $this->profDataFile($sapi)
|
||||
$flags = match ($this->mode) {
|
||||
self::MODE_INSTRUMENT => '-fprofile-generate=' . $this->rawDir($sapi),
|
||||
self::MODE_CS_INSTRUMENT => '-fprofile-use=' . $this->profDataFile($sapi)
|
||||
. ' -fcs-profile-generate=' . $this->csRawDir($sapi)
|
||||
. ' -Wno-error=profile-instr-unprofiled'
|
||||
. ' -Wno-error=profile-instr-out-of-date'
|
||||
. ' -Wno-backend-plugin';
|
||||
. ' -Wno-backend-plugin',
|
||||
default => '-fprofile-use=' . $this->profDataFile($sapi)
|
||||
. ' -Wno-error=profile-instr-unprofiled'
|
||||
. ' -Wno-error=profile-instr-out-of-date'
|
||||
. ' -Wno-backend-plugin',
|
||||
};
|
||||
$this->setFlag('SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS', $flags);
|
||||
$this->setFlag('SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM', $this->ldOnly($flags, $sapi));
|
||||
logger()->info("pgo {$this->mode} ({$sapi})");
|
||||
@@ -129,7 +178,8 @@ class PgoManager
|
||||
private function mergeSapi(string $sapi): void
|
||||
{
|
||||
$raws = glob($this->rawDir($sapi) . '/*.profraw') ?: [];
|
||||
if (empty($raws)) {
|
||||
$csRaws = glob($this->csRawDir($sapi) . '/*.profraw') ?: [];
|
||||
if (empty($raws) && empty($csRaws)) {
|
||||
if ($sapi === 'frankenphp') {
|
||||
logger()->warning('pgo --pgo: no .profraw for frankenphp (cgo glue PGO will be skipped); run --pgi, exercise frankenphp longer, then re-run --pgo to include it');
|
||||
return;
|
||||
@@ -137,7 +187,8 @@ class PgoManager
|
||||
throw new WrongUsageException("--pgo: no .profraw for {$sapi}; run --pgi, exercise the binary, then re-run --pgo");
|
||||
}
|
||||
$out = $this->profDataFile($sapi);
|
||||
$argv = implode(' ', array_map('escapeshellarg', $raws));
|
||||
$inputs = array_merge($raws, $csRaws);
|
||||
$argv = implode(' ', array_map('escapeshellarg', $inputs));
|
||||
shell()->exec('llvm-profdata merge --failure-mode=warn -output=' . escapeshellarg($out) . ' ' . $argv);
|
||||
if (!is_file($out) || filesize($out) === 0) {
|
||||
throw new WrongUsageException("--pgo: empty merge output for {$sapi}");
|
||||
@@ -150,6 +201,11 @@ class PgoManager
|
||||
return $this->profileRoot . '/' . $sapi;
|
||||
}
|
||||
|
||||
private function csRawDir(string $sapi): string
|
||||
{
|
||||
return $this->profileRoot . '/cs-' . $sapi;
|
||||
}
|
||||
|
||||
private function profDataFile(string $sapi): string
|
||||
{
|
||||
return $this->profileRoot . '/' . $sapi . '.profdata';
|
||||
@@ -175,7 +231,7 @@ class PgoManager
|
||||
private function setFlag(string $var, string $append): void
|
||||
{
|
||||
$cur = (string) getenv($var);
|
||||
$cur = preg_replace('/\s*-fprofile-(generate|use)=\S+/', '', $cur) ?? $cur;
|
||||
$cur = preg_replace('/\s*-f(cs-)?profile-(generate|use)=\S+/', '', $cur) ?? $cur;
|
||||
$cur = preg_replace('/\s*-Wno-error=profile-instr-\S+/', '', $cur) ?? $cur;
|
||||
$cur = preg_replace('/\s*-Wno-backend-plugin/', '', $cur) ?? $cur;
|
||||
f_putenv($var . '=' . trim($cur . ' ' . $append));
|
||||
@@ -191,6 +247,7 @@ class PgoManager
|
||||
$patterns = ['/\s*-Wno-error=\S+/', '/\s*-Wno-backend-plugin/'];
|
||||
if ($sapi === 'frankenphp') {
|
||||
$patterns[] = '/\s*-fprofile-use=\S+/';
|
||||
$patterns[] = '/\s*-fcs-profile-generate=\S+/';
|
||||
}
|
||||
return trim(preg_replace($patterns, '', $flags) ?? $flags);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
--- a/main/main.c
|
||||
+++ b/main/main.c
|
||||
@@ -2563,6 +2563,12 @@
|
||||
@@ -2563,6 +2563,9 @@
|
||||
#endif
|
||||
|
||||
zend_observer_shutdown();
|
||||
+
|
||||
+ /* spc-pgo: explicit profile flush so embed/frankenphp callers that exit
|
||||
+ * via SYS_exit_group (skipping libc atexit) still get .profraw written.
|
||||
+ * Weak symbol → no-op in non-PGO builds. */
|
||||
+ { extern int __llvm_profile_write_file(void) __attribute__((weak));
|
||||
+ if (__llvm_profile_write_file) __llvm_profile_write_file(); }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user