Zig パッケージ管理とビルドシステムの再構築

Zig パッケージ管理とビルドシステムの再構築

Zig はパッケージ管理ロジックをコンパイラ実行ファイルから切り離し、ビルドシステム("maker" プロセス)へ移行しました。このアーキテクチャの変更により、コンパイラを再ビルドせずにパッケージ管理タスクをパッチでき、ネットワーク操作に ReleaseSafe モードを使用できるようになり、全体的なセキュリティとパフォーマンスが向上します。

パッケージ管理がビルドシステムへ移行

Zig はパッケージ管理の責任をコンパイラからビルドシステムへシフトしました。これにより、zig buildzig fetchzig initzig libc といったサブコマンドは、コンパイラ実行ファイルではなく maker プロセスによって処理されます。

その結果、以前はコンパイラにバンドルされていたいくつかのコンポーネントがソース形式で配布されるようになりました。含まれるものは次のとおりです。

  • パッケージ取得ロジック
  • HTTP クライアントとネットワークスタック
  • TLS(Transport Layer Security)および関連暗号化
  • Git プロトコル実装
  • 圧縮ライブラリ(xz、gzip、zstd、flate、zip)
  • build.zig.zon のパースと検証

この分離により、ビルドシステムは暗号化やファイルハッシュに必要なホスト固有の CPU 命令を利用でき、一般的なコンパイラ配布に含めるにはあまりにも希少なものでも使用可能になります。また、Zig 実行バイナリのサイズが約 4%(LLVM なし、ReleaseSmall 設定で 14.1 MiB から 13.5 MiB)削減されます。

プロセスアーキテクチャの進化

この変更をサポートするため、Zig はプロセスツリーを進化させ、設定の再実行時にビルドシステムが親プロセスであり続けるようにしました。

従来のアーキテクチャ:

zig build (the zig compiler + package manager)
└─ builder (the user's build.zig logic + build system implementation)

中間アーキテクチャ:

zig build (the zig compiler + package manager)
├─ configurer (the user's build.zig logic)
└─ maker (build system)

現在のアーキテクチャ:

zig build (the zig compiler)
└─ maker (build system + package manager)
   └─ configurer (the user's build.zig logic)

makerconfigurer の親になることで、configurer を再実行する必要がある場合(例: zig build --watch セッション中)でも maker プロセスは存続でき、サーバーが終了してクライアントが再接続する必要がなくなります。これは ZLS(Zig Language Server)を解放するためのビルドサーバープロトコル公開への重要なステップです。

ビルドシステムのパフォーマンスと再構築

Zig は「configurer」(ユーザーの build.zig ロジックを実行)と「maker」(ビルドグラフを実行)を分離しました。configurer はデバッグモードでコンパイルされ、maker はリリースモードでコンパイルされます。

この再構築により、主に次の 3 つのパフォーマンス向上が得られます。

  1. コンパイル時間の短縮: 変更があったときにコンパイルされるのはユーザーの build.zig ロジックだけで、ビルドシステム全体は再コンパイルされません。
  2. 設定のキャッシュ: 変更が検出されなければ、ビルドシステムはシリアライズされたバイナリ設定ファイルを使用して build.zig ロジックの再実行を完全にスキップできます。
  3. 実行の最適化: ビルドグラフを実行するプロセスは、最適化が有効な状態でコンパイルされます。

ベンチマークでは、シンプルなコマンドの実行時間が劇的に短縮されました。例として zig build -h は 150 ms から 14.3 ms へと減少し、約 90% の改善が見られます。

LLVM バックエンドと @bitCast のセマンティクス

Zig は LLVM バックエンドを更新し、非 ABI 整数型の低減方法を改善しました。従来は任意ビット幅整数が直接 LLVM IR の bit‑int 型に低減されており、最適化が失われたり誤コンパイルが起きやすい状況でした。現在、Zig はこれらをメモリに格納する際に ABI サイズの型へゼロ拡張または符号拡張し、Clang の _BitInt(N) の挙動に合わせています。

@bitCast の再定義

メモリ上のバイト再解釈ではなく、型の 論理ビットレイアウト に基づくように @bitCast が再定義されました。

  • 論理ビットレイアウト: 型は最下位ビットから最上位ビットへ順に並んだビット列として表現されます。例: u5 は 5 論理ビット、[2]u5 は 10 論理ビットです。
  • エンディアン非依存: 操作が論理的になるため、@bitCast はすべてのターゲットで同一に振る舞います。たとえば [2]u8u16 にビットキャストすると、常に最初の配列要素が下位 8 ビットとなり、エンディアンに関係なく結果は同じです。

この変更により、コンパイラの Legalize パスが複雑な @bitCast を LLVM および C バックエンド向けに単純化された形に書き換えることが可能になります。

ELF リンカとインクリメンタルコンパイル

Zig は新しい ELF リンカ(-fnew-linker で有効化)を導入し、x86_64 Linux 上で高速インクリメンタルコンパイルをサポートします。このリンカは外部ライブラリや C ソースをリンクしながらインクリメンタル再ビルドを可能にし、追加のパフォーマンスオーバーヘッドなしで 30 ms 程度で再ビルドが完了するケースもあります。

SPIR‑V バックエンドの進捗

SPIR‑V バックエンド向けに重要な更新が加えられました。

  • @SpirvType: Zig の標準型システムでは表現できない SPIR‑V 型(サンプラーやイメージなど)を記述できる新ビルトイン。
  • 呼び出し規約: 実行モード情報(ワークグループサイズ等)はインラインアセンブリではなく呼び出し規約(例: callconv(.spirv_kernel))で伝搬されます。
  • マルチスレッドコード生成: コード生成は Mir 値を生成し、コンパイラのスレッドプール上でスケジュールされ、シングルスレッド実行から脱却しました。
  • オブジェクトファイルリンク: .spv ファイルがオブジェクトファイルとして認識され、複数の .zig ファイルや外部 .spv オブジェクトを単一モジュールにリンクできるようになりました。

標準ライブラリと OS 連携

Windows ネイティブ API の優先

Zig は Win32(kernel32.dll)ラッパーよりもネイティブ NT API の使用を推進しています。これにより不要なヒープ割り当てが減り、冗長な失敗モードが排除され、バイナリ肥大化が抑制されます。たとえば、エントロピー取得(ランダムバイト)やファイル I/O(NtReadFile/NtWriteFile)で kernel32.dll をバイパスし、キャンセル対応やリソース管理が改善されました。

zig libc

Zig は libc 実装に含まれるベンダー提供の C ソースを段階的に Zig 標準ライブラリのラッパーへ置き換えています。これにより C 言語への依存が減り、コンパイル速度とバイナリサイズが向上します。これらの libc 関数は Zig Compilation Unit(ZCU)を共有し、LTO(Link‑Time Optimization)に相当する境界を越えた最適化が可能になります。

イベント駆動 I/O

実験的実装として io_uring(Linux)と Grand Central Dispatch(GCD、macOS)が std.Io.Evented に導入されました。これらはユーザースペースのスタックスイッチング(ファイバー/グリーンスレッド)を利用し、アプリケーションロジックを変更せずに I/O 実装を容易に入れ替えられるようにします。

コミュニティの見解

アーキテクチャ変更は技術的根拠が高く評価されている一方で、ユーザー体験(UX)に関して懸念を示す声もあります。あるコントリビュータは、コンパイラから @cImport を削除しビルドシステムへ移行したことは「開発上の健全性が UX より優先された」取引だと指摘しています。

"Zig、Go、Python の開発者は『ラジエータ液を燃料タンクから取り除いた』と発表し、支持者はそれが言語にとって良いことだと歓声を上げる。一方で私は『最初になぜラジエータ液を燃料タンクに入れたのか』と疑問に思っている。"

Sources