- 将 Clang 和 GCC 后端重构为继承自新的 GccLikeBackend 基类 - 移除重复的编译和链接命令构建逻辑 - 添加平台特定的钩子方法用于扩展功能 - 在 FunctionContext 中添加 SSA 构建器属性 - 统一编译器和链接器命令的初始化方式 - 优化 Windows 平台兼容性处理逻辑pull/3/head
parent
d49743a0fe
commit
7c2192ff54
9 changed files with 615 additions and 1195 deletions
@ -0,0 +1,355 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Php\Backend; |
||||
|
||||
use PhpAot\Php\Platform\PlatformBase; |
||||
|
||||
/** |
||||
* GCC/Clang 共享后端基类 |
||||
* 包含 Unix-like 编译器(GCC、Clang)的通用命令行构建逻辑。 |
||||
* 子类只需覆盖平台差异的钩子方法。 |
||||
*/ |
||||
abstract class GccLikeBackend extends CompilerBackend |
||||
{ |
||||
protected string $compilerCommand; |
||||
protected ?string $linkerCommand; |
||||
|
||||
public function __construct(PlatformBase $platform, string $compilerCommand, ?string $linkerCommand = null) |
||||
{ |
||||
parent::__construct($platform); |
||||
$this->compilerCommand = $compilerCommand; |
||||
$this->linkerCommand = $linkerCommand; |
||||
} |
||||
|
||||
public function getCompilerCommand(): string |
||||
{ |
||||
return $this->compilerCommand; |
||||
} |
||||
|
||||
// ──── 钩子方法(子类覆盖点) ──── |
||||
|
||||
/** 编译器特定的前缀标志(如 MSVC 兼容模式) */ |
||||
protected function getCompilerPrefixFlags(): string |
||||
{ |
||||
return ''; |
||||
} |
||||
|
||||
/** 链接器输出标志(-o vs /OUT:) */ |
||||
protected function getLinkerOutputFlag(): string |
||||
{ |
||||
return '-o'; |
||||
} |
||||
|
||||
/** 格式化 sanitizer 标志 */ |
||||
protected function formatSanitizerFlag(string $sanitizer): string |
||||
{ |
||||
return match ($sanitizer) { |
||||
'address', 'addr' => '-fsanitize=address', |
||||
'undefined', 'undef' => '-fsanitize=undefined', |
||||
default => '-fsanitize=' . $sanitizer, |
||||
}; |
||||
} |
||||
|
||||
/** 添加 PIC 标志 */ |
||||
protected function addPICFlag(array $config, string &$cmd): void |
||||
{ |
||||
if ((!empty($config['build_mode']) && $config['build_mode'] === 'ext') || !empty($config['pic'])) { |
||||
$cmd .= ' -fPIC'; |
||||
} |
||||
} |
||||
|
||||
/** 添加平台特定的链接选项 */ |
||||
protected function addPlatformLinkFlags(array $config, string &$cmd): void |
||||
{ |
||||
if ((!empty($config['build_mode']) && $config['build_mode'] === 'ext') || !empty($config['shared'])) { |
||||
$cmd .= ' ' . $this->platform->getSharedLinkFlag(); |
||||
|
||||
if ($this->platform instanceof \PhpAot\Php\Platform\Macos && !empty($config['install_name'])) { |
||||
$cmd .= ' ' . $this->platform->getCurrentInstallNameOption($config['install_name']); |
||||
} |
||||
} |
||||
|
||||
if (!empty($config['rpath'])) { |
||||
foreach ($config['rpath'] as $path) { |
||||
$cmd .= ' -Wl,-rpath,' . escapeshellarg($path); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** 添加平台特定的完整链接选项 */ |
||||
protected function addPlatformFullLinkFlags(array $options, string &$cmd): void |
||||
{ |
||||
if (!empty($options['shared'])) { |
||||
$cmd .= ' ' . $this->platform->getSharedLinkFlag(); |
||||
} |
||||
|
||||
if (!empty($options['rpath'])) { |
||||
$cmd .= ' ' . $this->platform->getRpathOptions($options['rpath']); |
||||
} |
||||
} |
||||
|
||||
// ──── 抽象方法实现 ──── |
||||
|
||||
public function compileFile( |
||||
string $sourceFile, |
||||
string $outputFile, |
||||
array $includePaths = [], |
||||
array $defines = [], |
||||
array $flags = [] |
||||
): string { |
||||
$cmd = $this->getCompilerCommand(); |
||||
$cmd .= $this->getCompilerPrefixFlags(); |
||||
$cmd .= ' -c ' . escapeshellarg($sourceFile); |
||||
$cmd .= ' -o ' . escapeshellarg($outputFile); |
||||
|
||||
if (!empty($includePaths)) { |
||||
$cmd .= ' ' . $this->formatIncludePaths($includePaths); |
||||
} |
||||
|
||||
foreach ($defines as $define) { |
||||
$cmd .= ' -D' . $define; |
||||
} |
||||
|
||||
if (!empty($flags)) { |
||||
$cmd .= ' ' . implode(' ', $flags); |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function linkObjects( |
||||
array $objectFiles, |
||||
string $outputFile, |
||||
array $libraryPaths = [], |
||||
array $libraries = [], |
||||
array $flags = [] |
||||
): string { |
||||
$cmd = $this->getLinkerCommand(); |
||||
$cmd .= ' ' . implode(' ', array_map('escapeshellarg', $objectFiles)); |
||||
$cmd .= ' ' . $this->getLinkerOutputFlag() . ' ' . escapeshellarg($outputFile); |
||||
|
||||
if (!empty($libraryPaths)) { |
||||
$cmd .= ' ' . $this->formatLibraryPaths($libraryPaths); |
||||
} |
||||
|
||||
if (!empty($libraries)) { |
||||
$cmd .= ' ' . $this->formatLibraries($libraries); |
||||
} |
||||
|
||||
if (!empty($flags)) { |
||||
$cmd .= ' ' . implode(' ', $flags); |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function buildCompileCommand(string $sourceFile, string $outputFile, array $options = []): string |
||||
{ |
||||
$cmd = $this->getCompilerCommand(); |
||||
$cmd .= $this->getCompilerPrefixFlags(); |
||||
$cmd .= ' -c'; |
||||
$cmd .= ' ' . escapeshellarg($sourceFile); |
||||
$cmd .= ' -o ' . escapeshellarg($outputFile); |
||||
|
||||
if (!empty($options['include_paths'])) { |
||||
$cmd .= ' ' . $this->formatIncludePaths($options['include_paths']); |
||||
} |
||||
|
||||
$cmd .= $this->buildCompileOptions($options); |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function buildCCompileCommand(string $sourceFile, string $outputFile, array $options = []): string |
||||
{ |
||||
$cmd = $this->getCompilerCommand(); |
||||
$cmd .= $this->getCompilerPrefixFlags(); |
||||
$cmd .= ' -c'; |
||||
$cmd .= ' -x c'; |
||||
$cmd .= ' ' . escapeshellarg($sourceFile); |
||||
$cmd .= ' -o ' . escapeshellarg($outputFile); |
||||
|
||||
if (!empty($options['include_paths'])) { |
||||
$cmd .= ' ' . $this->formatIncludePaths($options['include_paths']); |
||||
} |
||||
|
||||
$optimizeLevel = $options['optimize'] ?? 0; |
||||
if (!empty($options['debug'])) { |
||||
$cmd .= ' -O0 -g'; |
||||
} else { |
||||
$cmd .= ' -O' . $optimizeLevel; |
||||
} |
||||
|
||||
$cmd .= ' -Wall'; |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function buildNativeCompileCommand(string $sourceFile, string $outputFile, array $options = [], string $language = ''): string |
||||
{ |
||||
$cmd = $this->getCompilerCommand(); |
||||
$cmd .= $this->getCompilerPrefixFlags(); |
||||
$cmd .= ' -c'; |
||||
if ($language !== '') { |
||||
$cmd .= ' -x ' . $language; |
||||
} |
||||
$cmd .= ' ' . escapeshellarg($sourceFile); |
||||
$cmd .= ' -o ' . escapeshellarg($outputFile); |
||||
|
||||
if (!empty($options['include_paths'])) { |
||||
$cmd .= ' ' . $this->formatIncludePaths($options['include_paths']); |
||||
} |
||||
|
||||
$cmd .= $this->buildCompileOptions($options); |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function buildLinkCommand(array $objectFiles, string $outputFile, array $options = []): string |
||||
{ |
||||
$cmd = $this->getLinkerCommand(); |
||||
$cmd .= ' ' . implode(' ', array_map('escapeshellarg', $objectFiles)); |
||||
$cmd .= ' ' . $this->getLinkerOutputFlag() . ' ' . escapeshellarg($outputFile); |
||||
|
||||
if (!empty($options['library_paths'])) { |
||||
$cmd .= ' ' . $this->formatLibraryPaths($options['library_paths']); |
||||
} |
||||
|
||||
if (!empty($options['ldflags'])) { |
||||
$cmd .= ' ' . $options['ldflags']; |
||||
} |
||||
|
||||
$cmd .= $this->buildLinkOptions($options); |
||||
|
||||
if (!empty($options['libraries'])) { |
||||
$cmd .= ' ' . $this->formatLibraries($options['libraries']); |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function buildCompileOptions(array $config = []): string |
||||
{ |
||||
$cmd = ''; |
||||
|
||||
$cmd .= $this->getCompilerPrefixFlags(); |
||||
|
||||
if (!empty($config['sanitize'])) { |
||||
$cmd .= ' ' . $this->formatSanitizerFlag($config['sanitize']); |
||||
} |
||||
|
||||
if (!empty($config['debug'])) { |
||||
$cmd .= ' -O0 -g'; |
||||
} else { |
||||
$optimizeLevel = $config['optimize'] ?? 2; |
||||
$cmd .= ' -O' . $optimizeLevel; |
||||
} |
||||
|
||||
$cmd .= ' -Wall'; |
||||
|
||||
if (!empty($config['cpp_std'])) { |
||||
$cmd .= ' -std=' . $config['cpp_std']; |
||||
} |
||||
|
||||
if (!empty($config['march'])) { |
||||
$cmd .= ' -march=' . $config['march']; |
||||
} |
||||
|
||||
if (!empty($config['target_platform'])) { |
||||
$cmd .= ' --target=' . $config['target_platform']; |
||||
} |
||||
|
||||
$this->addPICFlag($config, $cmd); |
||||
|
||||
if (!empty($config['enable_profiler'])) { |
||||
$cmd .= ' -DPPROF_ON=1'; |
||||
if (!empty($config['prof_output'])) { |
||||
$cmd .= ' -DPROF_OUTPUT_FILE=\'"' . $config['prof_output'] . '"\''; |
||||
} |
||||
} |
||||
|
||||
if (!empty($config['cxxflags'])) { |
||||
$cmd .= ' ' . $config['cxxflags']; |
||||
} |
||||
|
||||
if (!empty($config['user_defines'])) { |
||||
foreach ($config['user_defines'] as $define) { |
||||
$cmd .= ' -D' . $define; |
||||
} |
||||
} |
||||
|
||||
if (!empty($config['lto'])) { |
||||
$cmd .= ' -flto'; |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function buildLinkOptions(array $config = []): string |
||||
{ |
||||
$cmd = ''; |
||||
|
||||
$this->addPlatformLinkFlags($config, $cmd); |
||||
|
||||
if (!empty($config['sanitize'])) { |
||||
$cmd .= ' ' . $this->formatSanitizerFlag($config['sanitize']); |
||||
} |
||||
|
||||
if (!empty($config['target_platform'])) { |
||||
$cmd .= ' --target=' . $config['target_platform']; |
||||
} |
||||
|
||||
if (!empty($config['lto'])) { |
||||
$cmd .= ' -flto'; |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function buildFullCompileOptions(array $options = []): string |
||||
{ |
||||
$cmd = ''; |
||||
|
||||
$cmd .= $this->getCompilerPrefixFlags(); |
||||
|
||||
if (!empty($options['debug'])) { |
||||
$cmd .= ' -O0 -g'; |
||||
} else { |
||||
$optimizeLevel = $options['optimize'] ?? 2; |
||||
$cmd .= ' -O' . $optimizeLevel; |
||||
} |
||||
|
||||
$cmd .= ' -Wall'; |
||||
|
||||
if (!empty($options['cpp_std'])) { |
||||
$cmd .= ' -std=' . $options['cpp_std']; |
||||
} |
||||
|
||||
if (!empty($options['march'])) { |
||||
$cmd .= ' -march=' . $options['march']; |
||||
} |
||||
|
||||
if (!empty($options['sanitize'])) { |
||||
$cmd .= ' ' . $this->formatSanitizerFlag($options['sanitize']); |
||||
} |
||||
|
||||
if (!empty($options['pic'])) { |
||||
$cmd .= ' -fPIC'; |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
|
||||
public function buildFullLinkOptions(array $options = []): string |
||||
{ |
||||
$cmd = ''; |
||||
|
||||
$this->addPlatformFullLinkFlags($options, $cmd); |
||||
|
||||
if (!empty($options['sanitize'])) { |
||||
$cmd .= ' -fsanitize=' . $options['sanitize']; |
||||
} |
||||
|
||||
return $cmd; |
||||
} |
||||
} |
||||
@ -0,0 +1,211 @@ |
||||
<?php |
||||
|
||||
namespace PhpAot\Php\Platform; |
||||
|
||||
/** |
||||
* Unix-like 平台基类(Linux, macOS) |
||||
* 包含 GCC/Clang 通用标志语法的共享实现 |
||||
*/ |
||||
abstract class UnixPlatform extends PlatformBase |
||||
{ |
||||
public function getIncludeFlags(array $includePaths): string |
||||
{ |
||||
if (empty($includePaths)) { |
||||
return ''; |
||||
} |
||||
|
||||
$flags = []; |
||||
foreach ($includePaths as $path) { |
||||
$flags[] = '-I' . escapeshellarg($path); |
||||
} |
||||
|
||||
return implode(' ', $flags); |
||||
} |
||||
|
||||
public function getLibraryPathFlags(array $libraryPaths): string |
||||
{ |
||||
if (empty($libraryPaths)) { |
||||
return ''; |
||||
} |
||||
|
||||
$flags = []; |
||||
foreach ($libraryPaths as $path) { |
||||
$flags[] = '-L' . escapeshellarg($path); |
||||
} |
||||
|
||||
return implode(' ', $flags); |
||||
} |
||||
|
||||
public function getLibraryFlags(array $libraries): string |
||||
{ |
||||
if (empty($libraries)) { |
||||
return ''; |
||||
} |
||||
|
||||
$ext = ltrim($this->getSharedLibraryExtension(), '.'); |
||||
$flags = []; |
||||
foreach ($libraries as $lib) { |
||||
$libName = basename($lib); |
||||
if (str_starts_with($libName, 'lib')) { |
||||
$libName = substr($libName, 3); |
||||
} |
||||
$libName = preg_replace('/\.(a|' . $ext . ')$/', '', $libName); |
||||
|
||||
$flags[] = '-l' . $libName; |
||||
} |
||||
|
||||
return implode(' ', $flags); |
||||
} |
||||
|
||||
public function getObjectExtension(): string |
||||
{ |
||||
return '.o'; |
||||
} |
||||
|
||||
public function getExecutableExtension(): string |
||||
{ |
||||
return ''; |
||||
} |
||||
|
||||
public function getPathSeparator(): string |
||||
{ |
||||
return '/'; |
||||
} |
||||
|
||||
public function getPhpDir(): string |
||||
{ |
||||
$phpDir = getenv('PHP_HOME'); |
||||
if ($phpDir && is_dir($phpDir)) { |
||||
return rtrim($phpDir, '\/'); |
||||
} |
||||
|
||||
$phpDir = shell_exec('php-config --prefix 2>/dev/null'); |
||||
if (!empty($phpDir)) { |
||||
return trim($phpDir); |
||||
} |
||||
|
||||
$phpExe = trim(shell_exec('which php 2>/dev/null')); |
||||
if ($phpExe && file_exists($phpExe)) { |
||||
$phpDir = dirname(dirname($phpExe)); |
||||
if (is_dir($phpDir)) { |
||||
return $phpDir; |
||||
} |
||||
} |
||||
|
||||
throw new \RuntimeException('The `php-config` is not found. Please install PHP development package or set PHP_HOME environment variable'); |
||||
} |
||||
|
||||
/** |
||||
* 获取 RPATH 选项 |
||||
*/ |
||||
public function getRpathOptions(array $paths): string |
||||
{ |
||||
if (empty($paths)) { |
||||
return ''; |
||||
} |
||||
|
||||
$rpaths = []; |
||||
foreach ($paths as $path) { |
||||
$rpaths[] = '-Wl,-rpath,' . escapeshellarg($path); |
||||
} |
||||
|
||||
return implode(' ', $rpaths); |
||||
} |
||||
|
||||
/** |
||||
* 获取 PIC 选项 |
||||
*/ |
||||
public function getPicFlag(): string |
||||
{ |
||||
return '-fPIC'; |
||||
} |
||||
|
||||
/** |
||||
* 构建 PHP 包含路径(使用 php-config 动态获取) |
||||
*/ |
||||
public function buildPhpIncludePaths(string $phpDir): array |
||||
{ |
||||
$phpConfigPath = $this->findPhpConfig($phpDir); |
||||
if ($phpConfigPath) { |
||||
$includes = shell_exec("{$phpConfigPath} --includes 2>/dev/null"); |
||||
if ($includes) { |
||||
preg_match_all('/-I([^\s]+)/', $includes, $matches); |
||||
if (!empty($matches[1])) { |
||||
return array_filter($matches[1], 'is_dir'); |
||||
} |
||||
} |
||||
} |
||||
|
||||
$paths = [ |
||||
$phpDir . '/include/php', |
||||
$phpDir . '/include/php/main', |
||||
$phpDir . '/include/php/TSRM', |
||||
$phpDir . '/include/php/Zend', |
||||
$phpDir . '/include/php/ext', |
||||
]; |
||||
|
||||
return array_filter($paths, 'is_dir'); |
||||
} |
||||
|
||||
/** |
||||
* 查找 php-config 可执行文件 |
||||
*/ |
||||
protected function findPhpConfig(string $phpDir): ?string |
||||
{ |
||||
$candidate = $phpDir . '/bin/php-config'; |
||||
if (is_executable($candidate)) { |
||||
return $candidate; |
||||
} |
||||
|
||||
$whichResult = trim(shell_exec('which php-config 2>/dev/null')); |
||||
if ($whichResult && is_executable($whichResult)) { |
||||
return $whichResult; |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
/** |
||||
* 构建 PHP 库路径 |
||||
*/ |
||||
public function buildPhpLibPaths(string $phpDir): array |
||||
{ |
||||
$paths = []; |
||||
|
||||
$libPath = $phpDir . '/lib'; |
||||
if (is_dir($libPath)) { |
||||
$paths[] = $libPath; |
||||
} |
||||
|
||||
return $paths; |
||||
} |
||||
|
||||
/** |
||||
* 检测 PHP 库文件 |
||||
*/ |
||||
public function detectPhpLibs(string $phpDir): array |
||||
{ |
||||
$libPath = $phpDir . '/lib'; |
||||
|
||||
if (!is_dir($libPath)) { |
||||
throw new \RuntimeException("PHP lib directory not found: {$libPath}"); |
||||
} |
||||
|
||||
$ext = ltrim($this->getSharedLibraryExtension(), '.'); |
||||
$embedLib = $libPath . '/libphp.' . $ext; |
||||
$staticLib = $libPath . '/libphp.a'; |
||||
|
||||
$hasEmbed = file_exists($embedLib); |
||||
$hasStatic = file_exists($staticLib); |
||||
|
||||
if (!$hasEmbed && !$hasStatic) { |
||||
throw new \RuntimeException("Neither libphp.{$ext} nor libphp.a found"); |
||||
} |
||||
|
||||
return [ |
||||
'embed' => $hasEmbed ? $embedLib : null, |
||||
'static' => $hasStatic ? $staticLib : null, |
||||
'is_shared' => $hasEmbed, |
||||
]; |
||||
} |
||||
} |
||||
Loading…
Reference in new issue