既存のC言語による実装をインクリメンタルにZigに移植する
興味深い記事を見つけました。
気に入ったところだけピックアップして紹介するので、ぜひ原文も読んでください。
Zig is an ideal language for incrementally porting existing C code to as I’ll hopefully show.
Zigは既存のCのコードをインクリメンタルに(少しずつ)移植するのに理想的な言語です。これからそれを紹介します。
Mini-RV32ima
Mini-RV32imaはひとつのCのヘッダファイルからなるRISC-Vのエミュレータです。(このエミュレータでLinuxカーネルを起動することが可能です)
私(元のブログの筆者)はRISC-Vのことをあまり知りません。RISC-Vのリファレンスを見るよりもCのコードを一行ずつ忠実にZigに書き直しました。
最初の一歩
zig はCコンパイラとして使うこともできます。最初にやったことは、CのコードをまるごとZigから呼び出すことでした。Cのmain
関数を_main
に変更して、以下のようにしました。
Zig:
const rv32 = @cImport({
@cInclude("mini-rv32ima.h");
}
pub fn main() !void {
rv32.doEverything();
}
C:
void doEverything(void) {
char *argv[] = {
"minirv32", "-f", "Image"
};
_main(3, argv);
}
int _main(int argc, char *argv[]) {
...
}
ZigではビルドシステムをZig自身で記述します。build.zig
はこのようになります。
const rv32ima_flags = [_][]const u8{
"-Wall",
};
const rv32ima_sources = [_][]const u8{
"mini-rv32ima/mini-rv32ima.c",
};
fn createrv32ima(b: *std.build.Builder) *std.build.LibExeObjStep {
const lib = b.addStaticLibrary("rv32ima", null);
lib.addCSourceFiles(&rv32ima_sources, &rv32ima_flags);
lib.addIncludePath("mini-rv32ima");
lib.linkSystemLibraryName("c");
return lib;
}
関数ごとに書き直していく
関数HandleControlStore
はエミュレータが動いたときにMemory mapped I/O への書き込みを行います。addressが0x10000000の場合はそのキャラクタをターミナルに出力します。
C:
uint32_t HandleControlStore( uint32_t addy, uint32_t val ) {
if( addy == 0x10000000 ) { //UART 8250 / 16550 Data Buffer
printf( "%c", val );
fflush( stdout );
}
return 0;
}
Zig:
export fn HandleControlStore(addr:u32, val:u32) callconv(.C) u32 {
if (addr == 0x10000000 ) { //UART 8250 / 16550 Data Buffer
std.debug.print("{c}", .{@intCast(u8, val)});
}
return 0;
}
Zigではexport
とcallconv(.C)
を追加するだけです。
(訳者注: export
をつけるとデフォルトでC ABIになるので、callconv(.C)
は省略可能だと思います。)
(中略)他にもいろいろ書いてありますがスキップします。ぜひ原文をみてください。
WASM
Zigはwasmをターゲットとしてコード出力することができます。全てをZigに移植するまでもなく、Cのコードをwasmにトランスレートしてブラウザの上で動かすことができます。このためには全てのlibcへの依存を除去する必要があります。でもこれは今回はとても簡単でした。すでにI/Oのハンドリングの部分をZigに置き換えていたからです。
Cと同様に、関数をJavaScriptからアクセス可能にするのはシンプルです。
Zig:
export fn tty_read(b:u8) void {
// handle some IO
}
JavaScript:
return WebAssembly.instantiate(bytes, imports).then((results) => {
var tty_read = results.instance.exports.tty_read;
...
});
結論
(DeepLによる翻訳)
Zigを書くのは本当に楽しいです。いくつかの小さなプロジェクトを経て、今ではほとんどの構文を知っているし、必要なときにライブラリ関数がどのように見えるか、きちんと推測できるようになりました。長年のCプログラマーとして、Zigは私の考え方に合っています。Zigには明るい未来があると思います。
Discussion