diff --git a/src/SPC/builder/linux/LinuxBuilder.php b/src/SPC/builder/linux/LinuxBuilder.php index d15816fa..74702b1f 100644 --- a/src/SPC/builder/linux/LinuxBuilder.php +++ b/src/SPC/builder/linux/LinuxBuilder.php @@ -23,6 +23,8 @@ class LinuxBuilder extends UnixBuilderBase /** @var bool Micro patch phar flag */ private bool $phar_patched = false; + private ?PgoManager $pgo = null; + public function __construct(array $options = []) { $this->options = $options; @@ -49,6 +51,8 @@ class LinuxBuilder extends UnixBuilderBase */ public function buildPHP(int $build_target = BUILD_TARGET_NONE): void { + $this->pgo = PgoManager::fromBuilder($this, $build_target); + $cflags = $this->arch_c_flags; f_putenv('CFLAGS=' . $cflags); @@ -134,7 +138,7 @@ class LinuxBuilder extends UnixBuilderBase $this->cleanMake(); - $pgo = PgoManager::active(); + $pgo = $this->pgo; $needsClean = false; $sapiBuilds = [ ['cli', $enableCli, true, fn () => $this->buildCli()], diff --git a/src/SPC/command/BuildPHPCommand.php b/src/SPC/command/BuildPHPCommand.php index 9c10b102..8ae537d0 100644 --- a/src/SPC/command/BuildPHPCommand.php +++ b/src/SPC/command/BuildPHPCommand.php @@ -12,7 +12,6 @@ use SPC\store\SourcePatcher; use SPC\util\DependencyUtil; use SPC\util\GlobalEnvManager; use SPC\util\LicenseDumper; -use SPC\util\PgoManager; use SPC\util\SPCTarget; use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Input\InputArgument; @@ -215,20 +214,6 @@ class BuildPHPCommand extends BuildCommand // clean old modules that may conflict with the new php build FileSystem::removeDir(BUILD_MODULES_PATH); - $pgi = (bool) $this->getOption('pgi'); - $csPgi = (bool) $this->getOption('cs-pgi'); - $pgo = (bool) $this->getOption('pgo'); - if (((int) $pgi + (int) $csPgi + (int) $pgo) > 1) { - $this->output->writeln('--pgi, --cs-pgi, and --pgo are mutually exclusive'); - return static::FAILURE; - } - if ($pgi) { - (new PgoManager())->setupInstrument($rule); - } elseif ($csPgi) { - (new PgoManager())->setupCsInstrument($rule); - } elseif ($pgo) { - (new PgoManager())->setupUse($rule); - } $builder->buildPHP($rule); $builder->testPHP($rule); diff --git a/src/SPC/util/PgoManager.php b/src/SPC/util/PgoManager.php index efb75c0d..3401798c 100644 --- a/src/SPC/util/PgoManager.php +++ b/src/SPC/util/PgoManager.php @@ -43,64 +43,29 @@ class PgoManager private string $mode; - private static ?self $active = null; - - public function __construct() + private function __construct() { $this->profileRoot = BUILD_ROOT_PATH . '/pgo-data'; } - public static function active(): ?self + /** Build a PgoManager for the active --pgi/--cs-pgi/--pgo option, or null if none set. */ + public static function fromBuilder(BuilderBase $builder, int $rule): ?self { - return self::$active; - } - - /** Setup --pgi: build with -fprofile-generate=. */ - public function setupInstrument(int $rule): void - { - $this->validateRule($rule); - FileSystem::removeDir($this->profileRoot); - f_mkdir($this->profileRoot, recursive: true); - foreach ($this->trainableIn($rule) as $sapi) { - f_mkdir($this->rawDir($sapi), recursive: true); + $modes = array_filter(['pgi', 'cs-pgi', 'pgo'], fn ($m) => (bool) $builder->getOption($m)); + if (count($modes) > 1) { + throw new WrongUsageException('--pgi, --cs-pgi, and --pgo are mutually exclusive'); } - $this->mode = self::MODE_INSTRUMENT; - self::$active = $this; - $this->applyShutdownPatches(); - $this->applyForSapi($this->trainableIn($rule)[0]); - logger()->info('pgo --pgi: instrumented build, profraw will land under ' . $this->profileRoot . '//'); - } - - /** Setup --cs-pgi: build with -fprofile-use= -fcs-profile-generate=. 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); + $mode = array_values($modes)[0] ?? null; + if ($mode === null) { + return null; } - $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-/'); - } - - /** Setup --pgo: merge collected .profraw, then build with -fprofile-use=. */ - public function setupUse(int $rule): void - { - $this->validateRule($rule); - if (trim((string) shell_exec('command -v llvm-profdata 2>/dev/null')) === '') { - throw new WrongUsageException('--pgo: llvm-profdata not on PATH'); - } - foreach ($this->trainableIn($rule) as $sapi) { - $this->mergeSapi($sapi); - } - $this->mode = self::MODE_USE; - self::$active = $this; - $this->applyForSapi($this->trainableIn($rule)[0]); + $instance = new self(); + match ($mode) { + 'pgi' => $instance->setupInstrument($rule), + 'cs-pgi' => $instance->setupCsInstrument($rule), + 'pgo' => $instance->setupUse($rule), + }; + return $instance; } /** Patches php-src/libtool to passthrough -fcs-profile-* flags (otherwise dropped during shared lib link). */ @@ -155,6 +120,51 @@ class PgoManager logger()->info("pgo {$this->mode} ({$sapi})"); } + /** Setup --pgi: build with -fprofile-generate=. */ + private function setupInstrument(int $rule): void + { + $this->validateRule($rule); + FileSystem::removeDir($this->profileRoot); + f_mkdir($this->profileRoot, recursive: true); + foreach ($this->trainableIn($rule) as $sapi) { + f_mkdir($this->rawDir($sapi), recursive: true); + } + $this->mode = self::MODE_INSTRUMENT; + $this->applyShutdownPatches(); + $this->applyForSapi($this->trainableIn($rule)[0]); + logger()->info('pgo --pgi: instrumented build, profraw will land under ' . $this->profileRoot . '//'); + } + + /** Setup --cs-pgi: build with -fprofile-use= -fcs-profile-generate=. Requires existing .profdata. */ + private 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; + $this->applyShutdownPatches(); + $this->applyForSapi($this->trainableIn($rule)[0]); + logger()->info('pgo --cs-pgi: cs-instrumented build, cs-profraw under ' . $this->profileRoot . '/cs-/'); + } + + /** Setup --pgo: merge collected .profraw, then build with -fprofile-use=. */ + private function setupUse(int $rule): void + { + $this->validateRule($rule); + if (trim((string) shell_exec('command -v llvm-profdata 2>/dev/null')) === '') { + throw new WrongUsageException('--pgo: llvm-profdata not on PATH'); + } + foreach ($this->trainableIn($rule) as $sapi) { + $this->mergeSapi($sapi); + } + $this->mode = self::MODE_USE; + $this->applyForSapi($this->trainableIn($rule)[0]); + } + /** * Static-embed mode links libphp.a into frankenphp; both end up in one * binary so must share one profdata. Shared-embed mode keeps libphp.so