2026-05-18 10:55:39 +08:00
< ? php
declare ( strict_types = 1 );
namespace Package\Artifact ;
use StaticPHP\Artifact\ArtifactDownloader ;
use StaticPHP\Artifact\Downloader\DownloadResult ;
use StaticPHP\Artifact\Downloader\Type\CheckUpdateResult ;
use StaticPHP\Attribute\Artifact\AfterBinaryExtract ;
use StaticPHP\Attribute\Artifact\CustomBinary ;
use StaticPHP\Attribute\Artifact\CustomBinaryCheckUpdate ;
use StaticPHP\Exception\DownloaderException ;
2026-05-19 20:01:50 +07:00
use StaticPHP\Runtime\SystemTarget ;
2026-05-18 10:55:39 +08:00
2026-05-19 20:01:50 +07:00
class llvm_compiler_rt
2026-05-18 10:55:39 +08:00
{
2026-05-19 20:01:50 +07:00
#[CustomBinary('llvm-compiler-rt', [
2026-05-18 10:55:39 +08:00
'linux-x86_64' ,
'linux-aarch64' ,
])]
public function downBinary ( ArtifactDownloader $downloader ) : DownloadResult
{
$llvmVersion = $this -> detectZigLlvmVersion ()
? ? throw new DownloaderException ( 'Could not detect bundled clang version from zig cc --version; ensure zig is installed' );
$tarball = " compiler-rt- { $llvmVersion } .src.tar.xz " ;
$url = " https://github.com/llvm/llvm-project/releases/download/llvmorg- { $llvmVersion } / { $tarball } " ;
$tarballPath = DOWNLOAD_PATH . '/' . $tarball ;
default_shell () -> executeCurlDownload ( $url , $tarballPath , retries : $downloader -> getRetry ());
2026-05-19 20:01:50 +07:00
return DownloadResult :: archive ( $tarball , [ 'url' => $url , 'version' => $llvmVersion ], extract : '{pkg_root_path}/llvm-compiler-rt' , verified : false , version : $llvmVersion );
2026-05-18 10:55:39 +08:00
}
2026-05-19 20:01:50 +07:00
#[CustomBinaryCheckUpdate('llvm-compiler-rt', [
2026-05-18 10:55:39 +08:00
'linux-x86_64' ,
'linux-aarch64' ,
])]
public function checkUpdateBinary ( ? string $old_version , ArtifactDownloader $downloader ) : CheckUpdateResult
{
$llvmVersion = $this -> detectZigLlvmVersion ()
? ? throw new DownloaderException ( 'Could not detect bundled clang version from zig cc --version; ensure zig is installed' );
return new CheckUpdateResult (
old : $old_version ,
new : $llvmVersion ,
needUpdate : $old_version === null || $llvmVersion !== $old_version ,
);
}
2026-05-19 20:01:50 +07:00
#[AfterBinaryExtract('llvm-compiler-rt', [
2026-05-18 10:55:39 +08:00
'linux-x86_64' ,
'linux-aarch64' ,
])]
public function postExtract ( string $target_path ) : void
{
2026-05-19 20:01:50 +07:00
$this -> buildForCurrentTarget ( $target_path );
}
public function buildForCurrentTarget ( ? string $sourceDir = null ) : bool
{
$sourceDir ? ? = PKG_ROOT_PATH . '/llvm-compiler-rt' ;
$triple = SystemTarget :: getCanonicalTriple ();
$libDir = PKG_ROOT_PATH . '/zig/lib/' . $triple ;
if ( $this -> isBuilt ( $libDir )) {
return true ;
}
if ( ! is_dir ( $sourceDir )) {
logger () -> warning ( " [llvm-compiler-rt] source dir missing at { $sourceDir } ; cannot build runtime bits for { $triple } " );
return false ;
}
$llvmVersion = $this -> detectZigLlvmVersion ();
if ( $llvmVersion === null ) {
logger () -> warning ( '[llvm-compiler-rt] could not detect bundled clang version; skipping runtime bit build (PGO + shared libs without __dso_handle will fail to link)' );
return false ;
}
logger () -> info ( " Building llvm compiler-rt bits for { $triple } (LLVM { $llvmVersion } ) " );
2026-05-18 10:55:39 +08:00
$zig = PKG_ROOT_PATH . '/zig/zig' ;
2026-05-19 20:01:50 +07:00
f_mkdir ( $libDir , recursive : true );
2026-05-18 10:55:39 +08:00
$profileLib = " { $libDir } /libclang_rt.profile.a " ;
$crtBegin = " { $libDir } /clang_rt.crtbegin.o " ;
$crtEnd = " { $libDir } /clang_rt.crtend.o " ;
if ( ! file_exists ( $profileLib )) {
2026-05-19 20:01:50 +07:00
$this -> buildProfileRuntime ( $zig , $sourceDir , $profileLib , $triple );
2026-05-18 10:55:39 +08:00
}
if ( ! file_exists ( $crtBegin ) || ! file_exists ( $crtEnd )) {
2026-05-19 20:01:50 +07:00
$this -> buildCrtObjects ( $zig , $sourceDir , $crtBegin , $crtEnd , $triple );
2026-05-18 10:55:39 +08:00
}
2026-05-19 20:01:50 +07:00
return $this -> isBuilt ( $libDir );
2026-05-18 10:55:39 +08:00
}
2026-05-19 20:01:50 +07:00
public function isBuilt ( string $libDir ) : bool
{
return file_exists ( " { $libDir } /libclang_rt.profile.a " )
&& file_exists ( " { $libDir } /clang_rt.crtbegin.o " )
&& file_exists ( " { $libDir } /clang_rt.crtend.o " );
}
private function buildProfileRuntime ( string $zig , string $srcRoot , string $libPath , string $triple ) : void
2026-05-18 10:55:39 +08:00
{
$profileSrc = " { $srcRoot } /lib/profile " ;
$profileInc = " { $srcRoot } /include " ;
if ( ! is_dir ( $profileSrc )) {
2026-05-19 20:01:50 +07:00
logger () -> warning ( " [llvm-compiler-rt] profile src dir missing at { $profileSrc } — PGO will not work " );
2026-05-18 10:55:39 +08:00
return ;
}
$sources = array_merge (
glob ( " { $profileSrc } /*.c " ) ? : [],
glob ( " { $profileSrc } /*.cpp " ) ? : []
);
// Keep Linux-only compilation units; the others bring in OS-specific headers
// we can't satisfy without their respective SDKs.
$skip = [ '/PlatformAIX' , '/PlatformDarwin' , '/PlatformFuchsia' , '/PlatformOther' , '/PlatformWindows' , '/WindowsMMap' ];
$sources = array_filter ( $sources , function ( $f ) use ( $skip ) {
foreach ( $skip as $s ) {
if ( str_contains ( $f , $s )) {
return false ;
}
}
return true ;
});
2026-05-19 20:01:50 +07:00
$objDir = " { $srcRoot } /obj-profile- { $triple } " ;
2026-05-18 10:55:39 +08:00
f_mkdir ( $objDir , recursive : true );
2026-05-19 20:01:50 +07:00
$cflags = " -target { $triple } -c -O2 -fPIC -fvisibility=hidden " .
2026-05-18 10:55:39 +08:00
'-I' . escapeshellarg ( $profileInc ) . ' ' .
'-DCOMPILER_RT_HAS_ATOMICS=1 -DCOMPILER_RT_HAS_FCNTL_LCK=1 -DCOMPILER_RT_HAS_UNAME=1' ;
$objs = [];
foreach ( $sources as $src ) {
$obj = $objDir . '/' . pathinfo ( $src , PATHINFO_FILENAME ) . '.o' ;
$cmd = escapeshellarg ( $zig ) . ' cc ' . $cflags . ' -o ' . escapeshellarg ( $obj ) . ' ' . escapeshellarg ( $src ) . ' 2>&1' ;
if ( ! $this -> runZigCmd ( $cmd , $obj , " failed to compile { $src } " )) {
return ;
}
$objs [] = $obj ;
}
$arCmd = escapeshellarg ( $zig ) . ' ar rcs ' . escapeshellarg ( $libPath ) . ' ' . implode ( ' ' , array_map ( 'escapeshellarg' , $objs )) . ' 2>&1' ;
if ( ! $this -> runZigCmd ( $arCmd , $libPath , 'zig ar failed' )) {
return ;
}
2026-05-19 20:01:50 +07:00
logger () -> info ( '[llvm-compiler-rt] libclang_rt.profile.a installed (' . filesize ( $libPath ) . ' bytes)' );
2026-05-18 10:55:39 +08:00
}
2026-05-19 20:01:50 +07:00
private function buildCrtObjects ( string $zig , string $srcRoot , string $crtBegin , string $crtEnd , string $triple ) : void
2026-05-18 10:55:39 +08:00
{
$beginSrc = " { $srcRoot } /lib/builtins/crtbegin.c " ;
$endSrc = " { $srcRoot } /lib/builtins/crtend.c " ;
if ( ! is_file ( $beginSrc ) || ! is_file ( $endSrc )) {
2026-05-19 20:01:50 +07:00
logger () -> error ( " [llvm-compiler-rt] crtbegin/crtend source missing under { $srcRoot } /lib/builtins — shared libs will lack __dso_handle " );
2026-05-18 10:55:39 +08:00
return ;
}
2026-05-19 20:01:50 +07:00
$cflags = " -target { $triple } -c -O2 -fPIC -fvisibility=hidden -DCRT_HAS_INITFINI_ARRAY " ;
2026-05-18 10:55:39 +08:00
foreach ([[ $beginSrc , $crtBegin ], [ $endSrc , $crtEnd ]] as [ $src , $dst ]) {
$cmd = escapeshellarg ( $zig ) . ' cc ' . $cflags . ' -o ' . escapeshellarg ( $dst ) . ' ' . escapeshellarg ( $src ) . ' 2>&1' ;
if ( ! $this -> runZigCmd ( $cmd , $dst , " failed to compile { $src } " )) {
return ;
}
}
2026-05-19 20:01:50 +07:00
logger () -> info ( '[llvm-compiler-rt] clang_rt.crtbegin.o + clang_rt.crtend.o installed (' . filesize ( $crtBegin ) . ' + ' . filesize ( $crtEnd ) . ' bytes)' );
2026-05-18 10:55:39 +08:00
}
private function runZigCmd ( string $cmd , string $dst , string $errPrefix ) : bool
{
exec ( $cmd , $out , $rc );
if ( $rc !== 0 || ! is_file ( $dst )) {
2026-05-19 20:01:50 +07:00
logger () -> warning ( " [llvm-compiler-rt] { $errPrefix } : " . implode ( " \n " , $out ));
2026-05-18 10:55:39 +08:00
return false ;
}
return true ;
}
private function detectZigLlvmVersion () : ? string
{
$zig = PKG_ROOT_PATH . '/zig/zig' ;
if ( ! is_file ( $zig )) {
return null ;
}
$verLine = trim (( string ) shell_exec ( escapeshellarg ( $zig ) . ' cc --version 2>/dev/null' ));
if ( ! preg_match ( '/clang version (\d+\.\d+\.\d+)/' , $verLine , $m )) {
return null ;
}
return $m [ 1 ];
}
}