zigの基本的なクロスコンパイル手法を知る

zig

環境

当方の環境は以下である。
zig version: 0.16.0
Windows 11 + x86_64 + wsl2 + ubuntu24.04 + zsh

クロスコンパイルについて

作ったプログラムはlinuxでもmacでもwindowsでも動いて欲しいものである。一方、開発マシンにはビルド環境がありすぐに実行できるが、特にOSやアーキテクチャの異なる他のマシンに一から構築するのは様々な面においてハードルが高い場合がある。しかし、それぞれの環境に対応した実行ファイルを生成できれば、それを持っていくだけで問題ない。

`zig build-exe`でzigをクロスコンパイルする

zigはクロスコンパイルに力を入れている。以下のように実行する。-targetの書式は <arch><sub>-<os>-<abi>である。abi(Application Binary Interface)はglibcを使用するlinuxならgnu、Microsoft Visual C++を使用するwindowsならmsvcを指定し、macOSであれば一意に決まるので指定無しとする。また、windowsの場合は.pdb(program database)という拡張子のデバッグ情報ファイルも生成される。尚、gnuと指定してもABIがglibc、すなわちGNU製の標準Cライブラリ互換なだけであり、gcc(GNU Compiler Collection)を使用するわけではなく、zigはLLVMバックエンドである。zigの中間表現(IR:Intermediate Representation)からLLVM IRを生成しており、どちらかといえばclangに近い。zigとclangのどちらもLLVM IRを生成し、LLVMが機械語を生成したあと、zigやclangが呼ぶリンカがglibcをリンクさせる。

zig: hello.zig

const std = @import("std");

pub fn main(init: std.process.Init) !void {
    try std.Io.File.stdout().writeStreamingAll(init.io, "hello, world\n");
}

zsh

% #linux
% zig build-exe -target x86_64-linux-gnu --name hello_linux hello.zig
% ls -F
hello.zig  hello_linux*
% ./hello_linux
hello, world
% #windows:.exeと.pdbを生成する
% zig build-exe -target x86_64-windows-msvc --name hello_windows hello.zig
% ls -F
hello.zig  hello_linux*  hello_windows.exe*  hello_windows.pdb
%

powershell

PS> .\hello_windows.exe
hello, world
PS>

自作コマンドをzigで書き直して、linuxだけでなくwindowsでも使えるようにできるにゅんちゅ!

指定できるtargetについて

zig targetsコマンドで全て確認できるが 、主に選択するであろう組み合わせを以下に記載する。

CPUOSABI補足説明
例:x86_64linuxgnuglibc、Linuxは基本的にこれを指定
例:x86_64linuxmusl静的リンク生成時は基本的にこれを指定、musl採用linuxも
例:x86_64WindowsmsvcMicrosoft Visual C++、Windowsは基本的にこれを指定
例:x86_64WindowsgnuMinGW、実行にはWindows標準ライブラリを使用
例:x86_64macosABIは一意に決まる
wasm32freestandingwasm生成時に指定
wasm32wasiwasi生成時に指定

`zig cc`でCをクロスコンパイルする

zigはclang互換のcc機能も有しており、もちろんクロスコンパイルも可能である。一方、windowsターゲットに関しては、zig build-exeはmsvcターゲットにMSVC CRT(C RunTime)互換とWindows SDK(Software Development Kit)互換の独自実装も内蔵しているのに対し、zig ccはMinGW(Minimalist GNU for Windows)のみ内蔵している。WindowsにデフォルトでMinGWは入っていないものの、生成された実行ファイルはWindowsの標準ライブラリを使用するため、実行可能である。

c: main.c

#include <stdio.h>

int main(void){
    printf("hello, world\n");
    return 0;
}

zsh

% #linux
% zig cc -target x86_64-linux-gnu -o c_hello_linux main.c
% ls -F
c_hello_linux*  main.c
% ./c_hello_linux
hello, world
% #windows:.exeと.pdbを生成する
% #zig ccはMSVC CRTとWindows SDKを内蔵していない
% zig cc -target x86_64-windows-msvc -o c_hello_windows.exe main.c
error: unable to provide libc for target 'x86_64-windows.win10...win11_dt-msvc'
info: zig can provide libc for related target x86_64-windows-gnu
% #zig ccはMinGWを内蔵している
% zig cc -target x86_64-windows-gnu -o c_hello_windows.exe main.c
% ls -F
c_hello_linux*  c_hello_windows.exe*  c_hello_windows.pdb  main.c
%

powershell

PS> .\c_hello_windows.exe
hello, world

stage2 backendと呼ばれるzig独自のバックエンド

LLVMは大規模であり重くて遅いこと、LLVMは依存が重くバージョンアップへの対応が必要になること、webassemblyに関してはそこまで最適化が強くないこと等から、zigはstage2 backendと呼ばれるzigで書かれた独自のバックエンドを開発している。LLVMより遥かに高速であり、クロスコンパイルも軽量である。また、最適化に関してはまだLLVMに及ばないが、zig特有の最適化を活かす場合は有効である。一方、デバッグ情報がまだ弱いこと、マイナーなアーキテクチャには対応していないなど、異なる点もあるため目的に応じて使い分けは必要である。stage2 backendを使用したコンパイルは以下のように-fno-llvmオプションを指定して実行する。

zsh

% zig build-exe -fno-llvm -target x86_64-linux-gnu --name hello_linux_nollvm hello.zig
% ./hello_linux_nollvm
hello, world
%

それにしてもコンパイルって凄いにゅんちゅ!考えるの楽しいにゅんちゅ!