Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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