その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

zigコードから生成したdynamic libraryおよびstatic libraryをCでコンパイルして使う

TL;DR

zig ccを使うと色々と簡単、特にstatic linkをする場合はハマりにくく楽なので、使える場合は使うと良さそうです。
zig cc自体、gccやclangのdrop-in replacementを目的として出来たCコンパイラなので多くの場合はそのままポンと乗せ替えができそうですが、環境によっては色々な事情もあるでしょうし本記事ではgccを使う方法についても記します。

前提

zig init-libで吐き出されるコード (src/main.zig) を使います。

const std = @import("std");
const testing = std.testing;

export fn add(a: i32, b: i32) i32 {
    return a + b;
}

test "basic add functionality" {
    try testing.expect(add(3, 7) == 10);
}

なお、export modifierが付いていないとライブラリを経由してCから参照することができません。

One of the primary use cases for Zig is exporting a library with the C ABI for other programming languages to call into. The export keyword in front of functions, variables, and types causes them to be part of the library API:
https://ziglang.org/documentation/0.9.1/#Exporting-a-C-Library

Cのコード (main.c) は以下の通り。zigで実装しているadd関数をCコード中で利用します。

#include <stdio.h>
#include <stdint.h>

int32_t add(int32_t a, int32_t b);

int main() {
    printf("Added: %d\n", add(123, 321));
    return 0;
}

zigとCの型の対応については以下のドキュメントを参照すると良いでしょう。
Documentation - The Zig Programming Language

なお試した環境の各種バージョンは以下の通りです。

$ uname -srvmo
Linux 5.15.0-47-generic #51-Ubuntu SMP Thu Aug 11 07:51:15 UTC 2022 x86_64 GNU/Linux
$ zig version
0.9.1
$ gcc --version
gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0
Copyright (C) 2021 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

これは至って普通です。

zigのコードをsoファイルのdynamic libraryにするには、以下のようにzig build-libを実行すると良いです。-targetに何を指定できるのかについてはzig targetsコマンドの結果を参照すると良いでしょう。色々とクロスビルドできて便利ですね。

$ zig build-lib -dynamic -target x86_64-linux-gnu src/main.zig

すると、libmain.soというファイルが出力されるので

$ gcc -L . main.c -lmain

このようにgccdynamic linkすると、

$ LD_LIBRARY_PATH=. ./a.out
Added: 444

このように実行することができます。

なおzig ccを使う場合は"gcc"の部分を"zig cc"に書き替えるだけで動きます:

$ zig cc -L . main.c -lmain

ちなみにzig ccを使ってコンパイルしたバイナリを実行する場合はLD_LIBRARY_PATHを指定する必要が無くなるのですが、gccでも同じことをしたい場合は-Wl,-rpath,.みたいな感じでオプションを指定してRPATHにライブラリの検索パスを追加してあげると良いでしょう *1

static linkする場合はいろいろとzig側にオプションが必要になってきます。

$ zig build-lib -static -fPIC -fcompiler-rt -target x86_64-linux-musl src/main.zig

このようにするとlibmain.aというstatic libraryが生成されます。

今回はstatic linkをするので、glibcの代わりにmuslを使います *2
-fcompiler-rtcompiler-rtのシンボル情報を出力に含めるためのオプションです。これが無いと、static linkをする際にzigランタイムの持つシンボルを解決できず、Cコンパイラコンパイルできなくなります。
そしてmuslを使う場合は-fPICオプションにより位置独立コードとしてライブラリを出力する必要があります *3

そしてgcc側では

$ gcc -L . -static main.c -lmain

としてコンパイルするとstatic linkされた実行形式が得られることとなります *4

なお、gcc (あるいは別のCコンパイラ) の代わりにzig ccを使う場合はbuild-libのオプションは簡略化することができます。

$ zig build-lib -static -target x86_64-linux-musl src/main.zig
$ zig cc -L . -static main.c -lmain
まとめ
  • zigのコードからCのdynamic libraryおよびstatic libraryを作成してCに組み込むことができた
    • Cで書くにはしんどいコードをzigで書いて組み込む、みたいなadaptiveな使い方が比較的簡単にできそう
  • zig ccは便利

*1:パスについては絶対パスで指定してあげたほうが良いような気はしますが

*2:glibcだと主にNSS周りでトラブルになるので。muslはglibcの互換でstatic linkをオフィシャルにサポートしている

*3:-fPIEでも良い

*4:メモ: なぜか `gcc -I . -L . -static -static-libgcc -Wl,-Bstatic main.c -lmain` などとしてやらないと動かない時があった。けれど本文に書いたように `gcc -L . main.c -lmain` で十分なはず。