Skip to content
10 changes: 10 additions & 0 deletions config/pkg/target/llvm-compiler-rt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
llvm-compiler-rt:
type: target
artifact:
source:
type: custom
metadata:
license-files: [LICENSE.TXT]
license: Apache-2.0-with-LLVM-exception
tools:
- zig
16 changes: 16 additions & 0 deletions config/pkg/tool/llvm-tools.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
llvm-tools:
type: tool
artifact:
source:
type: custom
binary: custom
metadata:
license-files: [llvm/LICENSE.TXT]
license: Apache-2.0-with-LLVM-exception
tool:
provides:
- llvm-objcopy
- llvm-strip
- llvm-profdata
install-root: '{pkg_root_path}/llvm-tools'
binary-subdir: bin
39 changes: 39 additions & 0 deletions src/Package/Artifact/llvm_compiler_rt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?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\Artifact\Downloader\Type\GitHubTokenSetupTrait;
use StaticPHP\Attribute\Artifact\CustomSource;
use StaticPHP\Attribute\Artifact\CustomSourceCheckUpdate;

class llvm_compiler_rt
{
use GitHubTokenSetupTrait;

#[CustomSource('llvm-compiler-rt')]
public function downSource(ArtifactDownloader $downloader): DownloadResult
{
$version = zig::getLlvmVersion();
$filename = "compiler-rt-{$version}.src.tar.xz";
$url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-{$version}/{$filename}";
default_shell()->executeCurlDownload(
$url,
DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename,
headers: $this->getGitHubTokenHeaders(),
retries: $downloader->getRetry(),
);
return DownloadResult::archive($filename, ['url' => $url], version: $version);
}

#[CustomSourceCheckUpdate('llvm-compiler-rt')]
public function checkUpdateSource(?string $old_version): CheckUpdateResult
{
$version = zig::getLlvmVersion();
return new CheckUpdateResult(old: $old_version, new: $version, needUpdate: $old_version !== $version);
}
}
57 changes: 57 additions & 0 deletions src/Package/Artifact/llvm_tools.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?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\Artifact\Downloader\Type\GitHubRelease;
use StaticPHP\Artifact\Downloader\Type\GitHubTokenSetupTrait;
use StaticPHP\Attribute\Artifact\CustomBinary;
use StaticPHP\Attribute\Artifact\CustomSource;
use StaticPHP\Attribute\Artifact\CustomSourceCheckUpdate;
use StaticPHP\Runtime\SystemTarget;

class llvm_tools
{
use GitHubTokenSetupTrait;

#[CustomSource('llvm-tools')]
public function downSource(ArtifactDownloader $downloader): DownloadResult
{
$version = zig::getLlvmVersion();
$filename = "llvm-project-{$version}.src.tar.xz";
$url = "https://github.com/llvm/llvm-project/releases/download/llvmorg-{$version}/{$filename}";
default_shell()->executeCurlDownload(
$url,
DOWNLOAD_PATH . DIRECTORY_SEPARATOR . $filename,
headers: $this->getGitHubTokenHeaders(),
retries: $downloader->getRetry(),
);
return DownloadResult::archive($filename, ['url' => $url], version: $version);
}

#[CustomBinary('llvm-tools', [
'linux-x86_64',
'linux-aarch64',
])]
public function downBinary(ArtifactDownloader $downloader): DownloadResult
{
$asset = 'llvm-tools-' . SystemTarget::getTargetArch() . '-linux-musl-' . zig::getLlvmVersion() . '.txz';
return new GitHubRelease()->download('llvm-tools', [
'type' => 'ghrel',
'repo' => 'static-php/hosted',
'match' => preg_quote($asset, '|'),
'extract' => '{pkg_root_path}/llvm-tools',
], $downloader);
}

#[CustomSourceCheckUpdate('llvm-tools')]
public function checkUpdateSource(?string $old_version): CheckUpdateResult
{
$version = zig::getLlvmVersion();
return new CheckUpdateResult(old: $old_version, new: $version, needUpdate: $old_version !== $version);
}
}
36 changes: 25 additions & 11 deletions src/Package/Artifact/zig.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,25 @@
use StaticPHP\Attribute\Artifact\CustomBinary;
use StaticPHP\Attribute\Artifact\CustomBinaryCheckUpdate;
use StaticPHP\Exception\DownloaderException;
use StaticPHP\Package\ToolPackage;
use StaticPHP\Registry\PackageLoader;
use StaticPHP\Runtime\SystemTarget;

class zig
{
public static function getLlvmVersion(): string
{
$zig = PackageLoader::getPackage('zig');
if (!$zig instanceof ToolPackage || !$zig->isInstalled()) {
throw new DownloaderException('zig is not installed, run `bin/spc doctor` first');
}
[$code, $out] = shell()->execWithResult(escapeshellarg($zig->getBinary('zig')) . ' cc --version', false);
if ($code !== 0 || !preg_match('/clang version (\d+\.\d+\.\d+)/', implode("\n", $out), $match)) {
throw new DownloaderException('Could not determine the clang version shipped by zig');
}
return $match[1];
}

#[CustomBinary('zig', [
'linux-x86_64',
'linux-aarch64',
Expand Down Expand Up @@ -102,21 +117,20 @@ public function checkUpdateBinary(?string $old_version, ArtifactDownloader $down
])]
public function postExtractZig(string $target_path): void
{
$files = ['zig', 'zig-cc', 'zig-c++', 'zig-ar', 'zig-ld.lld', 'zig-ranlib', 'zig-objcopy'];
$all_exist = true;
foreach ($files as $file) {
if (!file_exists("{$target_path}/{$file}")) {
$all_exist = false;
break;
}
}
if ($all_exist) {
return;
}
self::writeWrappers($target_path);
}

public static function writeWrappers(string $target_path): void
{
$script_path = ROOT_DIR . '/src/globals/scripts/zig-cc.sh';
$script_content = file_get_contents($script_path);

$files = ['zig-cc', 'zig-c++', 'zig-ar', 'zig-ld.lld', 'zig-ranlib', 'zig-objcopy'];
$current = @file_get_contents("{$target_path}/zig-cc");
if ($current === $script_content && array_all($files, fn ($file) => file_exists("{$target_path}/{$file}"))) {
return;
}

file_put_contents("{$target_path}/zig-cc", $script_content);
chmod("{$target_path}/zig-cc", 0755);

Expand Down
139 changes: 139 additions & 0 deletions src/Package/Target/llvm_compiler_rt.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
<?php

declare(strict_types=1);

namespace Package\Target;

use StaticPHP\Attribute\Package\BuildFor;
use StaticPHP\Attribute\Package\Target;
use StaticPHP\Exception\BuildFailureException;
use StaticPHP\Package\TargetPackage;
use StaticPHP\Package\ToolPackage;
use StaticPHP\Registry\PackageLoader;
use StaticPHP\Runtime\SystemTarget;
use StaticPHP\Util\FileSystem;

/**
* Builds the three compiler-rt pieces zig ships without, into zig/lib/{triple}:
*
* - libclang_rt.profile.a the PGO instrumentation runtime. Without it
* -fprofile-instr-generate still links, but writes no .profraw.
* https://codeberg.org/ziglang/zig/issues/32066
* - clang_rt.crtbegin/crtend.o __dso_handle, needed when linking shared libraries.
* https://codeberg.org/ziglang/zig/issues/32064
* - libclang_rt.cpu_model.a __cpu_model and __cpu_indicator_init, the globals
* __builtin_cpu_supports() and __builtin_cpu_init() resolve against.
*
* zig-cc.sh links whichever of them a given command needs, from SPC_COMPILER_RT_DIR.
*/
#[Target('llvm-compiler-rt')]
class llvm_compiler_rt extends TargetPackage
{
private const array SKIP_PLATFORMS = ['PlatformAIX', 'PlatformDarwin', 'PlatformFuchsia', 'PlatformOther', 'PlatformWindows', 'WindowsMMap'];

public static function outputs(): array
{
return ['libclang_rt.profile.a', 'libclang_rt.cpu_model.a', 'clang_rt.crtbegin.o', 'clang_rt.crtend.o'];
}

public static function outputDir(): string
{
return getenv('SPC_COMPILER_RT_DIR')
?: throw new BuildFailureException('llvm-compiler-rt is only built under the zig toolchain (SPC_COMPILER_RT_DIR is unset)');
}

public function isInstalled(): bool
{
return self::isBuilt();
}

public static function isBuilt(): bool
{
$dir = getenv('SPC_COMPILER_RT_DIR');
return $dir !== false && $dir !== ''
&& array_all(self::outputs(), fn ($file) => file_exists("{$dir}/{$file}"));
}

#[BuildFor('Linux')]
public function build(): void
{
$source = $this->getSourceRoot();
$out = self::outputDir();
$triple = SystemTarget::getCanonicalTriple();
FileSystem::createDir($out);

$this->buildProfileRuntime($source, "{$out}/libclang_rt.profile.a", $triple);
$this->buildCrtObjects($source, "{$out}/clang_rt.crtbegin.o", "{$out}/clang_rt.crtend.o", $triple);
$this->buildCpuModel($source, "{$out}/libclang_rt.cpu_model.a", $triple);
}

private function buildProfileRuntime(string $source, string $lib, string $triple): void
{
$dir = "{$source}/lib/profile";
if (!is_dir($dir)) {
throw new BuildFailureException("llvm-compiler-rt: missing profile sources at {$dir}");
}
$sources = array_filter(
[...glob("{$dir}/*.c") ?: [], ...glob("{$dir}/*.cpp") ?: []],
fn ($file) => !array_any(self::SKIP_PLATFORMS, fn ($skip) => str_contains($file, "/{$skip}")),
);

$obj_dir = "{$source}/obj-profile-{$triple}";
FileSystem::resetDir($obj_dir);
$cflags = "-target {$triple} -c -O2 -fPIC -fvisibility=hidden -I" . escapeshellarg("{$source}/include")
. ' -DCOMPILER_RT_HAS_ATOMICS=1 -DCOMPILER_RT_HAS_FCNTL_LCK=1 -DCOMPILER_RT_HAS_UNAME=1';
shell()->cd($obj_dir)
->exec($this->zig('cc') . " {$cflags} " . implode(' ', array_map('escapeshellarg', $sources)))
->exec($this->zig('ar') . ' rcs ' . escapeshellarg($lib) . ' *.o');
}

private function buildCrtObjects(string $source, string $begin, string $end, string $triple): void
{
$cflags = "-target {$triple} -c -O2 -fPIC -fvisibility=hidden -DCRT_HAS_INITFINI_ARRAY";
foreach ([['crtbegin.c', $begin], ['crtend.c', $end]] as [$name, $dst]) {
$src = "{$source}/lib/builtins/{$name}";
if (!is_file($src)) {
throw new BuildFailureException("llvm-compiler-rt: missing {$src}");
}
shell()->exec($this->zig('cc') . " {$cflags} -o " . escapeshellarg($dst) . ' ' . escapeshellarg($src));
}
}

private function buildCpuModel(string $source, string $lib, string $triple): void
{
$builtins = "{$source}/lib/builtins";
$arch = explode('-', $triple)[0];
$family = match (true) {
in_array($arch, ['x86_64', 'amd64', 'i386', 'i686', 'x86'], true) => 'x86',
in_array($arch, ['aarch64', 'arm64'], true) => 'aarch64',
str_starts_with($arch, 'riscv') => 'riscv',
default => null,
};
if ($family === null) {
logger()->debug("llvm-compiler-rt: no cpu_model implementation for {$triple}, skipping");
return;
}
[$src, $includes] = is_dir("{$builtins}/cpu_model")
? ["{$builtins}/cpu_model/{$family}.c", '-I' . escapeshellarg($builtins) . ' -I' . escapeshellarg("{$builtins}/cpu_model")]
: ["{$builtins}/cpu_model.c", '-I' . escapeshellarg($builtins)];
if (!is_file($src)) {
throw new BuildFailureException("llvm-compiler-rt: missing cpu_model source {$src}");
}

$obj_dir = "{$source}/obj-cpu-model-{$triple}";
FileSystem::resetDir($obj_dir);
$obj = "{$obj_dir}/cpu_model.o";
shell()
->exec($this->zig('cc') . " -target {$triple} -c -O2 -fPIC {$includes} -o " . escapeshellarg($obj) . ' ' . escapeshellarg($src))
->exec($this->zig('ar') . ' rcs ' . escapeshellarg($lib) . ' ' . escapeshellarg($obj));
}

private function zig(string $subcommand): string
{
$zig = PackageLoader::getPackage('zig');
if (!$zig instanceof ToolPackage) {
throw new BuildFailureException('llvm-compiler-rt requires the zig tool package');
}
return escapeshellarg($zig->getBinary('zig')) . ' ' . $subcommand;
}
}
Loading
Loading