HN 日本語サマリー

← 一覧へ戻る
プログラミング

部分的な演算子優先順位

Partial Operator Precedence (ziglang.org)

37 pointsby tosh14 コメント

要約

Zig言語において、演算子の優先順位が曖昧になるケースに対して括弧による明示的な指定が必須となりました。これにより、複雑な式での予期せぬ挙動を防ぎ、プログラマーが覚えるべきルールを減らすことを目指しています。既存コードの移行には、コンパイラのエラーに従うか、`zig fmt --upgrade` コマンドを使用する方法があります。

全文翻訳

Devlog

このページには、Zigのメインブランチへの最近の変更点のキュレーションされたリストが含まれています。

このページには2026年のエントリが含まれています。他の年はDevlogアーカイブページで利用可能です。

2026年10月24日

部分的な演算子優先順位

著者: Ryan Liptak

昨日、Zig言語に関する2番目に古い未解決の提案であったissue #114: require parentheses sometimes to disambiguate confusing operator precedence(2016年2月8日開設)が、コントリビューターCMDRZero氏のおかげで、Zigのマスターブランチに実装されました。

これは、一部の演算子の組み合わせが定義済みの優先順位を持たなくなり、持たないものについてはコンパイラが括弧の使用を要求するようになったことを意味します。

const a = x orelse y == z;
test.zig:1:22: error: ambiguous operator precedence with operators ... orelse ... == ...; use parentheses to disambiguate
const a = x orelse y == z;
                     ^~
test.zig:1:13: note: previous ambiguous operator here
const a = x orelse y == z;
            ^~~~~~
test.zig:1:22: note: in zig 0.17.x, this was evaluated as (... orelse ...) == ...

しかし、一般的なケースは括弧なしで維持できます。

const a = x + y * z;

この変更の背後にある考え方は、プログラマーがZigコードを読み書きする際に覚える必要があるルールの数を減らすことです。例えば、Zigでの経験に関わらず、優先順位のルールを調べずに次の式の評価順序を説明できる可能性は低いでしょう。

a & b + c * d >> e | f orelse g() catch h + i == j or k and l

(Zig 0.17.xの優先順位ルールと同じ評価順序を維持しつつ)現在要求される括弧を追加すると、その式は次のようになります。

((((a & ((b + c * d) >> e)) | f) orelse g()) catch (h + i)) == j or (k and l);

依然として非常に複雑ですが、算術演算子と比較演算子の優先順位ルール以外を知らなくても理解できるものです。しかし、この作り話のような例を除けば、現在括弧が必要になる式は比較的まれです(Zigコードベースの100万行以上のコードのうち、353行が変更を必要としました)。

知っておくべきこと

実際には、今後、定義済みの/曖昧でない優先順位を持つ演算子の組み合わせに関するルールだけを覚えておけば十分です。例えば、算術演算子を含むコードを読んだり書いたりする場合、次の優先順位表だけを覚えておけばよいでしょう。

* / *% *|
+ - +% -% +| -|
== != < > <= >=
and or

そこには驚くようなことは何もありません。この「驚きのなさ」が、括弧が必要か否かの(主観的な)指針となりました。例えば、%は加算/減算演算子に関して曖昧でない優先順位を持ちますが、%を乗算/除算演算子やそれ自身と混在させる場合は括弧が必要です。しかし、これや他の曖昧な演算子の組み合わせは、記憶する必要はありません。将来読むコードにはすでに曖昧さを解消するための括弧が含まれており、書くコードについては、コンパイラが必要なときに教えてくれます。

詳細な説明と、他の演算子に関連する優先順位表については、言語リファレンスの更新された優先順位セクションを参照してください。

既存コードのアップグレード

これには2つのアプローチがあります。

  1. コンパイラによって表示される「曖昧な演算子優先順位」のエラーを1つずつ手動で修正する(これは、お使いのプラットフォームでサポートされている場合、インクリメンタルコンパイルを試す良い機会かもしれません)。
  2. zig fmt --upgrade を使用する(--upgradeフラグはこの目的のために追加されました)。これにより、zig fmt は自動的に括弧を追加し、以前の優先順位ルールの評価順序を維持します(注意:--upgradeフラグは1つのリリースサイクル後に削除されます)。

個人的にはオプション1を推奨します。なぜなら、これらのまれな優先順位シナリオの意図された評価順序を再決定するプロセスが、バグを発見する機会となることがあるからです(その例はこちら)。

オプション2を選択する場合でも、後で差分を確認して、異常な点や予期しない、または意図しない変更がないか確認することをお勧めします。潜在的に一般的な例としては、a orelse b() catch c が以前は (a orelse b()) catch c と評価されていましたが、これを a orelse (b() catch c) に変更すると、結果は同じでも、より理にかなっている可能性があります。また、一部のシナリオでは、zig fmt --upgrade によって冗長な括弧が追加される場合があること(例: a orelse ((b + c) - d) のように b + c の括弧が不要な場合)も注意に値します。そのため、それらを避けるには手動でのクリーンアップが必要になります。

以上です。この変更により、記憶違いによる優先順位ルールのバグがはるかに発生しにくくなり、現在存在する可能性のあるバグにも注意が向けられるようになることを願っています。

実装してくれたCMDRZero氏、および関連する文法変更に協力してくれたIsaac Freund氏に改めて感謝します。

2026年10月20日

Naked Package Metadata

Naked package(ベアパッケージ)とは、ビルドマニフェスト(build.zig.zonファイル)を含まないパッケージのことです。これは通常、Zigエコシステムの外部でサードパーティによって保守されている場合に発生します。

これまでのパッケージハッシュは次のようでした:N-V-__8AAA7lSAA6p7eRmPGVeA9uHmeiJ2DX-ISiPliRJ8Y7。

これは、build.zig.zonファイルがない場合、パッケージハッシュに使用するnameとversionが存在しなかったためです。さらに、これらのパッケージは、フィルタリングなしで取得されたすべてのファイルを含んでいたため、ハッシュが壊れやすくなっていました。これにより、意図しない隠しファイルが含まれてしまい、パッケージハッシュが変更される可能性がありました。あるいは、パッケージに特定のOSで作成できないシンボリックリンクが含まれている可能性もありました。フィルタリングがないと、これらのファイルは、たとえ不要であっても、取得に失敗する原因となりました。

現在、ZigはURLの隣にベアパッケージのversionとpathsを指定できるようになりました。依存関係名もパッケージハッシュに factored in されますが、versionが提供されている場合のみです。これにより、Zigの更新時にパッケージハッシュが突然変更されるのではなく、ユーザーがオプトインできるようになります。

例として、更新されたzlibを示します。

    .dependencies = .{
        .zlib = .{
            .url = "https://github.com/madler/zlib/archive/refs/tags/v1.3.2.tar.gz",
            .hash = "zlib-1.3.2-__8AANXpEADvBXGMSfhczMTZVAA-TkkE4ecoohAR7s3p",
            .version = "1.3.2",
            .paths = .{
                "LICENSE",
                "adler32.c",
                "compress.c",
                "crc32.c",
                "crc32.h",
                "deflate.c",
                "deflate.h",
                "gzclose.c",
                "gzguts.h",
                "gzlib.c",
                "gzread.c",
                "gzwrite.c",
                "infback.c",
                "inffast.c",
                "inffast.h",
                "inffixed.h",
                "inflate.c",
                "inflate.h",
                "inftrees.c",
                "inftrees.h",
                "trees.c",
                "trees.h",
                "uncompr.c",
                "zconf.h",
                "zlib.h",
                "zutil.c",
                "zutil.h",
            },
        },
    },

変更前:

4.3M    zig-pkg/N-V-__8AAI4ANwDM8MWhHTUFLKfvLE_n2NQk6mh0sFDzhTqO

変更後:

1.2M    zig-pkg/zlib-1.3.2-__8AANXpEADvBXGMSfhczMTZVAA-TkkE4ecoohAR7s3p

Pull Request

2026年10月14日

Package Hash Consistency Validation

同じパッケージ(fingerprintに基づく)が依存関係ツリーに現れる場合、セマンティックバージョンが一致すれば、ハッシュは同一でなければなりません。そうでなければ、3つのうちのいずれかが間違っており、メンテナーが対処する必要があります。

  • パッケージ所有権の紛争:誰かが名前やフィンガープリントを変更せずに他の誰かのパッケージをフォークした。
  • パッケージメンテナーが変更後にバージョンを上げ忘れた。
  • 未リリースの変更への依存。

このエラーが発生した場合、プロジェクトメンテナーは、なぜそれらの(2つ以上の)パッケージが矛盾したハッシュ情報を持っているのかを理解し、次にどうするかを決定する必要があります。

例:

andy@bark ~/d/ffmpeg (main)> zig build
error: contradictory hash for package with fingerprint 0x7118c1b07efea3d0 and version 0.0.0: libogg-0.0.0+1.3.7-0KP-flwMAACKAIH-iEs4gbg30ll_FQ-cW50JvLZO8VTg
error: contradictory hash for package with fingerprint 0x7118c1b07efea3d0 and version 0.0.0: libogg-0.0.0+1.3.7-0KP-foYNAAAaj0YJOwRQs7kTN-ivzRMYTjtKzr6Apelj
info: potential cause: package ownership dispute
info: potential cause: maintainer neglected to bump version after making changes
info: potential cause: dependency on unreleased changes
info: hint: use --print-tree to visualize all package versions and hashes

2026年10月13日

Printing Package Tree

zig buildに新しい--print-tree CLIオプションが追加され、次のように表示されます。

andy@bark ~/d/groovebasin (main)> zig build --print-tree
ffmpeg ffmpeg-8.1.1-5-zT7QA_AkUgTOx4ZqOUVZsHSk9iv97BInIiubIM6ifr67
├─ libz zlib-1.3.1-1-ZZQ7ldENAAA7qJjUXP6E6xnRuV-jDL9dyoJFc_eb3zQ6
│  └─ zlib N-V-__8AAB0eQwD-0MdOEBmz7intriBReIsIDNlukNVoNu6o
├─ mbedtls mbedtls-3.6.4-E4NUR2wWAAAUSvVbho9aaU31-e4D8glu9l1VfxKezBkV (not fetched)
├─ openssl openssl-3.3.2-TC9C3Ze3ZABaKR5hc8KRnRPhTClKrKQKqTnXTUYkvmSR (not fetched)
├─ libressl libressl-4.0.0--kqV4KPTAACsSRP8mSIZmqgVa5OZjw6WEgvcftWkLu-c (not fetched)
├─ libmp3lame libmp3lame-3.100.1-7-67wlF2CxEwDWlVOMMLBNPbnDi5kE2OmH0azUHU2Y4DZY
├─ libvorbis libvorbis-0.0.0+1.3.7-1wt9j_QPAADVure94TG4ecBMLD8eIAzWd6ZxVOmBBPCu
│  ├─ libvorbis N-V-__8AAA7lSAA6p7eRmPGVeA9uHmeiJ2DX-ISiPliRJ8Y7
│  └─ libogg libogg-0.0.0+1.3.7-0KP-foYNAAAaj0YJOwRQs7kTN-ivzRMYTjtKzr6Apelj
│     └─ libogg N-V-__8AAFM3CwD6iAA1kggMPR8NHZ8VTb2zvRZ1Mt4BUfq9
├─ libogg libogg-0.0.0+1.3.7-0KP-foYNAAAaj0YJOwRQs7kTN-ivzRMYTjtKzr6Apelj
│  └─ libogg N-V-__8AAFM3CwD6iAA1kggMPR8NHZ8VTb2zvRZ1Mt4BUfq9
└─ nasm nasm-3.0.1-B_NnpQpTAAA25UxvqONakXtmj-a78PGe3xfgyDFgetb0
   ├─ nasm N-V-__8AAEAMKAFrNesgN6ZM9sxj37fq_i7UJe5nKkLmyRJI
   └─ zlib zlib-1.3.1-1-ZZQ7ldENAAA7qJjUXP6E6xnRuV-jDL9dyoJFc_eb3zQ6
      └─ zlib N-V-__8AAB0eQwD-0MdOEBmz7intriBReIsIDNlukNVoNu6o
pulseaudio pulseaudio-16.1.1-10-mk_62DNlNwCDcTJ5iGhTxeCoetuAFKE1FrYNw5AErtX-
StaticHttpFileServer StaticHttpFileServer-1.0.0-j-0gwes2AAByCgpeLBNR2hOaGVsDz9u8I6o54ft10OxU
└─ mime mime-4.0.0-zwmL--0gAACndOVlROQeDnM0chfojNDS8tpohlsBqMNy

ターミナルが検出された場合、色付きで表示され、読みやすくなります。

これはコンフィギュレーターを実行する前に早期終了するため、コンフィギュレーターのコンパイルエラーを引き起こす問題をトラブルシューティングできます。

同様に、--configureが追加されました。これは「設定フェーズ後に終了する」と定義されており、--fetchが以前に行っていた動作と全く同じです。--fetchは「すべての依存関係(遅延ロードされるものを含む)を取得し、設定せずに終了する」に変更されました。

2026年10月12日

More Resilient Help Menu

zig build --helpは、設定が失敗した場合や、build.zigファイルが存在しない場合でも機能するようになりました。

info: initialize build.zig template file with "zig init"
info: see "zig --help" for more options
error: no build.zig file found, in the current directory or any parent directories
Usage: zig build [steps] [options]

Steps:                    (unavailable due to build.zig compilation failure)
Project-Specific Options: (unavailable due to build.zig compilation failure)

System Integration Options:
  --search-prefix [path]       Add a path to look for binaries, libraries, headers
  --sysroot [path]             Set the system root directory (usually /)
...
  Available System Integrations:                Enabled:

   (unavailable due to build.zig compilation failure)

General Options:
  -h, --help                   Print this help to stdout and exit
  -l, --list-steps             Print available steps to stdout and exit
...

2026年8月27日

Pointer Stability for ArrayLists

著者: Robbie Lyman

ポインタ安定性ロックは、2024年にstdのHash Mapコンテナに追加されました。2025年にLeo Emar-Kar氏によって最初に開かれたプルリクエストが、このメモリ安全性を確保する技術をstd.ArrayListにもたらしました。

コードで使用するには、ArrayListによってバックアップされた要素または要素のスライスへのポインタを最初に格納する際にlockPointers()を呼び出し、それらのポインタが不要になったらunlockPointers()を呼び出します。

以下に、やや作り話のような例を示します。2つのArrayListを管理していると仮定します。一方は入力の内容をメモリに保持し、もう一方は関心のあるチャンク(例えば各行)を格納しているとします。このプロセスにバグがあるバージョンを見てみましょう。バグを見つけられますか。

const std = @import("std");

const Context = struct {
    history: std.ArrayList(u8),
    lines: std.ArrayList([]const u8),

    fn parse(ctx: *Context, allocator: std.mem.Allocator, input: []const u8) !void {
        const slice = try ctx.history.addManyAsSlice(allocator, input.len);
        @memcpy(slice, input);
        var it = std.mem.tokenizeScalar(u8, slice, '\n');
        while (it.next()) |line| {
            try ctx.lines.append(allocator, line);
        }
    }
};

バグを見つけましたか?問題は、Context.lines.itemsの要素がContext.history.itemsの場所に依存していることです。しかし、Context.historyが現在の容量を超えて拡張する必要がある場合、この場所は変更される可能性があります。以下にバグの再現例を示します。

test "Context.parse" {
    const input = "I'm first!\n";
    const input_two =
        \But this text
        \is juuuuuuuuuuuuuuuuuuuuuuuuust long enough that it
        \causes a problem!
        \And the problem could be that we segfault!
        \Which is no fun to run into.
    ;
    var ctx: Context = .{ .history = .empty, .lines = .empty };
    const gpa = std.testing.allocator;
    defer ctx.history.deinit(gpa);
    defer ctx.lines.deinit(gpa);
    try ctx.parse(gpa, input);
    try ctx.parse(gpa, input_two);
    try std.testing.expectEqualStrings("I'm first!", ctx.lines.items[0]);
}

このコードをzig testで実行すると、次の出力が得られます(さらに少し追加されます)。

====== expected this output: =========
I'm first!␃

======== instead found this: =========
UUUUUUUUUU␃

======================================
First difference occurs on line 1:
expected:
I'm first!
^ ('\x49')
found:
UUUUUUUUUU
^ ('\x55')
1/1 blah.test.Context.parse...FAIL (TestExpectedEqual)

これはあまり良くないですよね?バグがあることはわかりますが、メモリ問題のデバッグに慣れているか(そしてアロケータの選択によってバグの現れ方が変わるかもしれませんが)、しばらくの間、修正を見つけられないまま迷ってしまうかもしれません。

テストの最初のparse呼び出し後にポインタを格納しているので、この変更を加えるとどうなるでしょうか。

    try ctx.parse(gpa, input);
+   ctx.history.lockPointers();
+   defer ctx.history.unlockPointers();
    try ctx.parse(gpa, input_two);
    try std.testing.expectEqualStrings("I'm first!", ctx.lines.items[0]);

スタックトレースとともにパニックが発生し、ポインタ安定性に関する仮定がどこで違反されたかが示されます。

thread 3023222 panic: reached unreachable code
/Users/robbie/bin/lib/std/debug.zig:442:14: 0x102d2506f in assert (test)
    if (!ok) unreachable; // assertion failure
             ^
/Users/robbie/bin/lib/std/debug.zig:1880:15: 0x102d31ef7 in assertUnlocked (test)
        assert(l.state == .unlocked);
              ^
/Users/robbie/bin/lib/std/array_list.zig:1348:50: 0x102e3ced7 in ensureTotalCapacityPrecise (test)
            self.pointer_stability.assertUnlocked();
                                                 ^
/Users/robbie/bin/lib/std/array_list.zig:1341:51: 0x102e3cdff in ensureTotalCapacity (test)
            return self.ensureTotalCapacityPrecise(gpa, growCapacity(new_capacity));
                                                  ^
/Users/robbie/bin/lib/std/array_list.zig:1237:41: 0x102e4e5c3 in resize (test)
            try self.ensureTotalCapacity(gpa, new_len);
                                        ^
/Users/robbie/bin/lib/std/array_list.zig:1461:28: 0x102e4e40f in addManyAsSlice (test)
            try self.resize(gpa, try addOrOom(self.items.len, n));
                           ^
/Users/robbie/src/advent-of-code/2024/blah.zig:8:51: 0x102e4dc1f in parse (test)
        const ptr = try ctx.history.addManyAsSlice(allocator, input.len);
                                                  ^
/Users/robbie/src/advent-of-code/2024/blah.zig:35:18: 0x102e4e167 in test.Context.parse (test)
    try ctx.parse(gpa, input_two);

素晴らしい、これは大きな助けになります。テストの失敗の原因として、ロジックの問題に加えて、メモリ安全性の問題を疑うことができるようになりました。もちろん、この例はやや作り話ですが、私は実際のコードでこの種のバックイングストレージにstd.ArrayListを使用することがよくあるので、実世界でのユースケースもご自身で見つけられることを願っています。

最後に、微妙な点に注意してください。HashMapとその仲間とは異なり、ArrayListは順序付けられているため、リストの操作は、リスト全体のアロケーション、リサイズ、解放を行わなくても、要素を移動させる可能性があります。例えば、addManyAsSlice(gpa, n)が返すポインタ(スライス)は、orderedRemove()やpop()を呼び出した場合に、リストの最後のn個の要素を指さない可能性があります。このため、orderedRemove()とpop()はアロケーションを発生させませんが、lockPointers()を呼び出した後には上記の同じアサーションを引き起こします。

2026年6月30日

All Package Management Functionality Moved from Compiler to Build System

著者: Andrew Kelley

ユーザーのbuild.zigスクリプトとビルドシステム自体のための別個のプロセスができた今、パッケージ管理ロジックをそこに配置するのが理にかなっています。

これらのサブコマンドをmakerプロセスに移動しました。

  • zig build
  • zig fetch
  • zig init
  • zig libc

これにより、以前はコンパイラ実行可能ファイルに含まれていた機能の大部分が、ソース形式で出荷されるようになりました。

  • パッケージ取得ロジック
  • HTTPクライアントとネットワーキング
  • TLS(トランスポート層セキュリティ)および関連暗号
  • Gitプロトコル
  • xz、gzip、zstd、flate、zip
  • build.zig.zonファイルの解析、検証、およびその他の処理

その結果、この機能はコンパイラを再ビルドせずにパッチできるようになり、ユーザーやコントリビューターが手を加えやすくなりました。

さらに、Zigでのパッケージ管理は、maker実行可能ファイルがReleaseSafeモードでコンパイルされているため、ネットワーキング時に安全チェックが有効になりました。さらに、ネットワーキングとファイルハッシュに使用されるすべての暗号は、ソフトウェア配布時に通常依存するにはまれすぎるCPU命令であっても、ホストで利用可能な特殊なCPU命令を利用できるようになりました。AOTケーキとJITケーキの両方を食べることができます!

この変更の当初の動機は、maker/configurerプロセス分離が--build-runnerオーバーライドフラグに破壊的な変更を加えた後に、ビルドサーバープロトコルを公開することに関連していました。

当初、プロセスツリーは次のようになっていました。

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 --watchプロセスを考えてみましょう。これはファイルを監視し、ソースコードの変更時に再ビルドします。build.zigの変更が検出された場合、またはそのロジックの実行中に観察されたファイルが検出された場合、configurerを再実行する必要があることを意味します。これは、zig buildがパッケージ管理ロジックを繰り返す機会を与えるためにmakerプロセスを終了する必要があることを意味します。

このdevlogエントリで説明されている変更の後、次のようになります。

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

したがって、設定を再実行する必要がある場合、makerプロセスは親プロセスであるため、兄弟プロセスではなく存続できます。今後のビルドサーバーの観点からは、サーバーが終了してクライアントが再接続する必要があるという厄介な状況を回避できます。クライアントに設定変更を通知するだけで済みます。

これはほとんど完全に非破壊的な変更ですが、いくつかの観察可能な違いがあります。

  • Zig実行可能バイナリサイズ:14.1 MiBから13.5 MiBに4%縮小(LLVMなし、ReleaseSmallモード)
  • --maker-optフラグはZIG_DEBUG_MAKER環境変数に置き換えられました。
  • --zig-lib-dirフラグはZIG_LIB_DIR環境変数に置き換えられました。

この変更セットのフォローアップ課題は、Zig 0.17.0をタグ付けするまでの主なブロッカーです。

7月に2つのカンファレンスがあり、講演の準備をする必要があるため、現実的に考えると、これらを完了するのは8月初旬までかかるでしょう。もちろん、貢献は歓迎です。

ZLSチームのTechatrix氏が、ビルドサーバープロトコルについて連絡を取り、協力してくれたことに感謝します。彼らはスポンサーを募集しています。

2026年6月26日

SPIR-V Backend Progress

著者: Ali Cheraghi

カバーすべき点はかなりあります。最近のコンパイラ変更の後、SPIR-Vバックエンドは多くの場所でビットロッドしていましたが、過去数週間かけてより良い状態にしました。

@SpirvType

SPIR-Vには、Zigの型システムでは表現できない型がいくつかあります。新しい@SpirvTypeビルトインが、シェーダー作成における最も古いブロッカーに対処するために導入されました。背景を追うには、#20550、#23326、#35461を参照してください。

const Sampler = @SpirvType(.sampler);
const Image = @SpirvType(.{ .image = .{ .usage = .{ .sampled = u32 }, .format = .unknown, .dim = ."2d", .depth = .unknown, .arrayed = false, .multisampled = false, .access = .unknown } });
const SampledImage = @SpirvType(.{ .sampled_image = Image });
const RuntimeArray = @SpirvType(.{ .runtime_array = u32 });
const sampled_image = @extern(*addrspace(.constant) const SampledImage, .{ .name = "sampled_image", .decoration = .{ .descriptor = .{ .set = 0, .binding = 1 } } });

Execution Mode on the Calling Convention

実行モード情報(ワークグループサイズ、フラグメントの原点など)は、インラインアセンブリOpExecutionModeを介して発行されるのではなく、呼び出し規約によって伝達されるようになりました。古いstd.gpu.executionMode()ヘルパーは削除され、SPIR-Vアセンブラは手動のOpExecutionMode命令を拒否します。メッシュシェーディングパイプラインのために、spirv_taskとspirv_meshという2つの新しい呼び出し規約も追加されました。

export fn vert() callconv(.spirv_vertex) void {}
export fn frag() callconv(.{ .spirv_fragment = .{ .depth_assumption = .greater } }) void {}
export fn comp() callconv(.{ .spirv_kernel = .{ .x = 8, .y = 8, .z = 1 } }) void {}
export fn task() callconv(.{ .spirv_task = .{ .x = 1, .y = 1, .z = 1 } }) void {}
export fn mesh() callconv(.{ .spirv_mesh = .{ .stage_output = .output_lines, .max_primitives = 1, .max_vertices = 2 } }) void {}

Capabilities and Extensions from CPU Features

以前は、機能(Capabilities)と拡張機能(Extensions)は、コード生成またはインラインアセンブリを介してアドホックに発行されていました。これらは現在、他のターゲットと同様にCPU機能セットによって完全に駆動されており、依存関係チェーンはSPIRV-Headersから抽出されています(現時点では外部ベンダーを除く)。アセンブラは、OpCapabilityまたはOpExtensionを直接発行しようとする試みを拒否するようになりました。

Multi-Threaded Codegen

初日から、SPIR-Vバックエンドはコード生成をリンカー スレッド内でシングルスレッドで実行していました。各コード生成ジョブは、他のすべてのセルフホストバックエンドと同様にMir値を生成し、コンパイラのスレッドプールでスケジュールされるようになりました。

同じ変更により、以前のリファクタリング中に削除されていた2つのISelパスが復活しました:dedup_types(同等の型命令をマージする)とprune_unused(最終モジュールからデッドコードを削除する)。これらは、コード生成がシングルスレッドだった頃に削除されていました。

Object File Linking

.spvファイルはオブジェクトファイルとして認識されるようになりました。複数の.zigファイル(または外部.spvオブジェクト)をコンパイルし、SPIR-Vリンカーにそれらを単一モジュールにステッチさせることができます。

また、この過程で数十のバグが修正され、spirv64-vulkanターゲットでの合計合格テストの割合が約10%増加しました(現在49%)。std.gpuはstd.spirvにリネームされ、SPIR-Vバックエンドは1ヶ月前よりも実質的に有用になりましたが、まだ道のりは長いです。SPIR-Vでは多くのテストケースがスキップされたままです。とはいえ、Zigをシェーダーやコンピュートカーネルに使用することを検討しているなら、今が試すのに良い時期です。バグレポートはCodebergで大歓迎です。ハッピーハッキング!

2026年6月25日

New @bitCast Semantics and LLVM Backend Improvements

著者: Matthew Lugg

(かなり長いdevlogになります、申し訳ありません—今回は少しやりすぎました!)

数週間前、長らく計画されていたLLVMバックエンドの改善を実装するブランチの作業を開始しました。これが雪だるま式に大きくなり、いくつかの言語提案が実装されました。それらについてお話しできることを嬉しく思います。

LLVM Backend Integer Lowering

Zigは常に、任意のビット幅の整数型(例:u4、i13、u40)を直接LLVM IRのビット整数型(i4、i13、i40)に低レベル化してきました。しかし、この低レベル化は最適ではないことが以前から知られていました。なぜなら、LLVMのメモリでのこれらの型の表現に関するドキュメント化されたセマンティクスは、オプティマイザにとって不必要に制限的だからです。さらに重要なのは、ClangがこのようなLLVM IRを生成しないため、LLVMのこれらのコードパスは適切にテストされておらず、実際にはサポートが不十分であることです。過去数年間、単純な最適化が見逃されるケースや、単純な誤コンパイルさえも数多く観察されてきました。

そのため、PRの当初の目標は、これらのビット整数型をSSA形式で値を操作する場合にのみ使用し、メモリに格納する際にはABIサイズの型(i8、i16、i32など)にゼロ拡張または符号拡張することでした。これは、ClangがCの_BitInt(N)を低レベル化する方法と一致するため、十分にサポートされるはずです。

その変更は実際にはかなり簡単でしたが、1つの問題に遭遇し、それが深みにはまりました。

The Problem with @bitCast

@bitCastは興味深いビルトインです。過去には、それは以下の操作シーケンスと同等であると定義されていました。

  • オペランド値へのポインタを取得する。
  • それを宛先型のポインタにキャストする。
  • そのポインタからロードする。

言い換えれば、それは基本的にメモリバイトの再解釈のためのシンタックスシュガーでした。しかし、時間が経つにつれて、この定義から逸脱しました。例えば、@bitCastを使用して[3]u8をu24に再解釈することが許可されるようになりました。これは、ほとんどのターゲットで@sizeOf(u24)が@sizeOf([3]u8)よりも大きい場合、上記の定義は不正な動作を呼び出すためです。

これまでは、LLVMバックエンドはこの未定義のセマンティクスを@bitCastビルトインに対して実装していました。しかし、その定義はメモリの再解釈に関わるため、整数型をメモリに格納する方法を変更したことは、@bitCastの実装に影響を与え、コンパイラテストスイートでのクラッシュを引き起こす不正な動作を導入しました。

この問題に対する最も簡単な解決策は、古い動作を近似的に一致させるためのロジックをLLVMバックエンドに実装することだったでしょう。しかし、私はより良い解決策を選択しました。新しい@bitCastの定義を実装することです。

Redefining @bitCast

2024年、Jacob Young氏は、@bitCastの問題を解決するために、新しいセマンティクスのセットを正確に定義することを目的とした言語提案#19755を作成しました。この提案は提出 shortly 後に受理され、実際、それが詳述するセマンティクスはすでにセルフホストのx86_64バックエンドによって実装されています!そのため、LLVMバックエンドの問題を解決するために、古い@bitCastセマンティクスを一致させる必要はありませんでした。代わりに、新しいセマンティクスをすべてで実装する良い機会だと考えました。

ちなみに、もう1つの利点は、コンパイラのLegalizeパスを利用できることです。これは、低レベル化が難しい操作をより単純な操作に書き換えるため、コンパイラバックエンドはそれらの単純な操作のみをサポートすればよくなります。Legalizeはすでに、セルフホストのx86_64バックエンドによって使用される機能を持っており、複雑な@bitCast操作をより単純なものに変換していました。そして、それは他のコンパイラバックエンド(主にLLVMおよびCバックエンド)を支援するために簡単に適応させることができました。ただし、それらが新しいセマンティクスを実装している場合に限ります。

いずれにせよ、私が取り組んだのは、コンパイラ全体にこれらの新しいセマンティクスを実装することでした(これは元のクエストよりも困難でした)。これには、LLVMおよびCバックエンドだけでなく、comptime実行も含まれます。結局のところ、Zigは@bitCastを含め、ほとんどの操作をcomptimeで実行できます!新しいセマンティクスは古いものとは大きく異なるため(後述)、標準ライブラリ、コンパイラ、およびサポートライブラリ(例:compiler_rt)全体での@bitCastの使用箇所も監査する必要がありました。しかし、数回の比較的スムーズなCI障害修正の後、私のPRをグリーンにし、昨日マスターにマージしました(その過程で多くの問題をクローズしました!)。

The New @bitCast Semantics

すべての背景を終えたので、ついに新しい@bitCastの動作を実際に説明する時が来ました。以前のようにメモリバイトの再解釈に基づいていたのではなく、ビルトインは現在、型を論理的に表すビットに基づいています。

@bitCastをサポートするすべての型には、「論理ビットレイアウト」があります。これは、型を順序付けられたビットのシーケンスとして表現したものです。例えば、u5は5つの論理ビットで構成され、それらを最下位ビットから最上位ビットへと順序付けます。[2]u5は10個の論理ビットで構成されます。最初の要素の5ビット、次に2番目の要素の5ビットです。@bitCastの新しい定義は、ある型の論理ビットを別の型の論理ビットとして再解釈することです。

最も単純な例は、符号なし整数(例えばu8)を取り、同じサイズの符号付き整数(この場合はi8)に変換することです。この操作は期待どおりに機能します。ビットは変更されず、最上位ビットを符号ビットとして再解釈するだけです。整数型とpacked struct/packed union型との間の@bitCastのセマンティクスも変更されません。

新しいセマンティクスと古いセマンティクスが異なるのは、集約型(配列とベクトル)が関わる場合です。

例えば、[2]u8をu16にビットキャストすることを考えてみましょう。古いセマンティクスでは、この操作の結果はターゲットエンディアンに依存していました。ビッグエンディアンターゲットでは、最初の配列要素は最上位8ビットになり、リトルエンディアンターゲットでは、最初の配列要素は最下位8ビットになりました。新しいセマンティクスでは、論理ビット表現(エンディアンに依存しない)のみを考慮するため、操作はすべてのターゲットで同じように動作します。最初の配列要素は最下位8ビットになります。一般的なルールとして、新しいセマンティクスはリトルエンディアンターゲットでの古いセマンティクスの動作と一致する傾向があります。

この定義により、[2]u3を@Vector(3, u2)に変換するような、より奇妙な操作も可能になります。

test "bitcast [2]u3 to @Vector(3, u2)" {
    const arr: [2]u3 = .{ 0b001, 0b011 };
    const vec: @Vector(3, u2) = @bitCast(arr);

    // arr[0]の最下位ビットから始まるarrのすべてのビットを連結して論理ビットシーケンスを見つけ、
    // それからそれを2ビットチャンクで読み取って結果のベクトル値vecの要素を取得します。
    //
    //     arr[0]         arr[1]
    //     0b001          0b011
    // -------------  -------------
    //  1    0    0    1    1    0
    // --------  --------  --------
    //   0b01      0b10      0b01
    //  vec[0]    vec[1]    vec[2]

    try expect(vec[0] == 0b01);
    try expect(vec[1] == 0b10);
    try expect(vec[2] == 0b01);
}
const expect = @import("std").testing.expect;

この種の操作はほとんどの場合あまり役に立ちませんが、必要であれば利用できます!例えば、整数を個々のビットのベクトルに分解して操作したい場合、それは@Vector(n, u1)への@bitCastで行えるようになりました。

これらすべてを行いながら、私はさらに2つの小さな受理済み提案を実装しました。ここでは詳しく説明しませんが、興味があればイシューを参照してください。

  • ベクトルポインタへの/からの@bitCastの禁止(#18936)
  • 列挙型での@bitCastの許可(#35602の一部)

もちろん、これらのセマンティクスの変更はすべて0.17.0リリースノートで説明されます(ここでは私が管理したよりも簡潔になることを願っています!)、および推奨される移行手順が概説されます。

LLVM Backend Performance

最後に、このブランチの当初の動機である、LLVMバックエンドが非ABI整数型をどのように低レベル化するかを変更することが、見逃されていた最適化を回復させるのに実証済みで成功したことを言及しておきたいと思います。実際、Zigコンパイラ自体(内部で重度に任意のビット幅整数を使用していなくても!)は、より良い最適化により約5%のパフォーマンス向上が見られました。これは、0.17.0でマイナーな実行時パフォーマンス向上が期待できることを意味します!

読んでくれてありがとう、これが皆さんの何人かに興味深いものであったことを願っています。ハッピーハッキング!

2026年5月30日

ELF Linker Improvements

著者: Matthew Lugg

過去数週間、Zig 0.16.0でデビューした新しいELFリンカーに取り組んできました。0.16.0リリース時点では、このリンカー実装はかなり初期段階であり、外部ライブラリ(libcさえも)なしでZigのみのコードのリンクしかサポートしていませんでした。そのため、デフォルトでは無効になっていました(-fnew-linkerで有効にできます)。しかし、最初のリリース以降、かなりの進歩がありました!

これは素晴らしいマイルストーンです。私の最新のPRの時点で、新しいELFリンカーは、LLVMおよびLLDライブラリを有効にしてセルフホストZigコンパイラをビルドできるようになりました。このタスクには、多くの内部機能が必要です。

[mlugg@nebula master]$
# 新しいリンカーを使用してZigコンパイラをビルドする:
[mlugg@nebula master]$
zig build -Dno-lib -Dnew-linker -Denable-llvm
[mlugg@nebula master]$
# そのコンパイラを使用して、LLVMとLLDで何かをビルドする:
[mlugg@nebula master]$
./zig-out/bin/zig build-exe ~/hello.zig -fllvm -flld
[mlugg@nebula master]$
./hello
Hello, World!
[mlugg@nebula master]$

もちろん、ELFリンカーは必ずしも最もエキサイティングなものではありません。そのため、この新しいリンカーのヘッドライン機能は、高速なインクリメンタルコンパイルのサポートです。最近の強化により、(x86_64 Linux上で)外部ライブラリ、Cソースなどをリンクしながらインクリメンタルリビルドを実行できるようになりました。パフォーマンスオーバーヘッドは一切ありません!Andrewのテトリスクローンで試している様子をクリップで紹介します。

[動画]

Andrewのテトリスクローンに対するいくつかの些細な変更が、それぞれ約30ミリ秒でビルドされています。

また、高速なインクリメンタルリビルドは、Zigコンパイラ自体でもうまく機能します。

[mlugg@nebula master]$
zig build -Dno-lib -Denable-llvm -fincremental --watch
Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 36s

Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 244ms

Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 228ms

Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 288ms

Build Summary: 4/4 steps succeeded
install success
└─ install zig success
   └─ compile exe zig Debug native success 283ms

このリンカー実装で現在最も欠けている機能は、Zigコード用のDWARFデバッグ情報をまだ生成できないことです。これは間違いなく私の次の優先事項です。しかし、そのサポートがなくても、インスタントリビルドがどれほど役立つかは驚くべきことです。例えば、多くのプリントデバッグを行っている状況では特にそうです。

Zigのマスターブランチを使用しており、x86_64 Linuxを使用している方は、以前プロジェクトで動作しなかった場合でも、新しいELFリンカーでインクリメンタルコンパイルを試してみてください!多くのコードベースがすでにうまく動作し、プロジェクトをミリ秒単位でリビルドできる能力を解き放つことができると予想しています。もちろん、バグに遭遇した場合は、問題を報告してください。

そして、タグ付きリリースを使用している方は心配しないでください。Andrewが前回のdevlogで言及したように、Zig 0.17.0が間もなくリリースされるので、これも試せるようになるまでそう長くはかかりません!

2026年5月26日

Build System Reworked

著者: Andrew Kelley

大きなブランチがマージされました:makerプロセスとconfigurerプロセスを分離

このdevlogエントリは、基本的に今後のリリースノートのプレビューですが、新しい機能をテストし、Zigプロジェクトを今後導くフィードバックを提供したい方への事前通知として機能します。

以前は、build.zigファイルとビルドシステム実装はすべて、1つの肥大化したプロセスにコンパイルされていました(Debugモード)。build.zigロジックがメモリ内にビルドグラフを構築し終えた後、「ビルドランナー」コードがそれを実行していました。

現在、build.zigファイルは小さなプロセス(「configurer」)にコンパイルされます(debugモード)。このロジックがメモリ内にビルドグラフを構築し終えた後、それはバイナリ設定ファイルにシリアライズされます。親のzig buildプロセスはこのファイルを認識し、次回のためにキャッシュします。その間、ビルドグラフ実行プロセス(「maker」)を非同期にコンパイルします(releaseモード)。設定ファイルが利用可能になり、makerプロセスがコンパイルを完了すると、makerプロセスが実行され、設定ファイルが渡されます。makerプロセスは、グローバルキャッシュのおかげで、zig versionごとに一度だけコンパイルすればよくなります。makerプロセスは、シリアライズされた設定ファイルに含まれるビルドグラフを実行します。

この変更の主な動機は、3つの方法でzig buildを高速化することでした。

  1. 変更ごとに、ビルドシステム全体ではなく、ユーザーのbuild.zigロジックのみがコンパイルされます。これは、--watch、--fuzz、--webuiが導入された今、より価値が高まっています。ビルドシステムは、zig buildの時間が長くなることなく、より多くの機能を持つことができます。

  2. ビルドシステムは、何も変更されないことがわかっている場合、build.zigロジックの実行を完全にスキップできるようになりました。例えば、zig buildコマンドラインに-freference-traceを追加すると、前回と同じ設定を使用して、build.zigロジックの冗長な実行が回避されます。

  3. ビルドグラフを実際に実行するプロセスが、最適化を有効にしてコンパイルされるようになりました。

ポイント2と3を実証するために、zig build --helpを実行する前後の違いを示します。

Benchmark 1 (34 runs): master/zig build -h
  measurement          mean ± σ            min … max           outliers         delta
  wall_time           150ms ± 5.52ms     145ms …  165ms          4 (12%)        0%
  peak_rss           84.8MB ±  275KB    84.2MB … 85.1MB          0 ( 0%)        0%
  cpu_cycles          593M  ± 4.01M      588M  …  608M           2 ( 6%)        0%
  instructions        995M  ± 52.5K      995M  …  995M           0 ( 0%)        0%
  cache_references   25.8M  ±  165K     25.4M  … 26.1M           0 ( 0%)        0%
  cache_misses        651K  ± 20.1K      619K  …  697K           0 ( 0%)        0%
  branch_misses       918K  ± 7.44K      906K  …  935K           0 ( 0%)        0%
Benchmark 2 (348 runs): branch/zig build -h
  measurement          mean ± σ            min … max           outliers         delta
  wall_time          14.3ms ±  744us    13.2ms … 23.3ms          8 ( 2%)        ⚡- 90.4% ±  0.4%
  peak_rss           78.5MB ±  562KB    77.1MB … 81.4MB          7 ( 2%)        ⚡-  7.4% ±  0.2%
  cpu_cycles         24.1M  ±  821K     22.8M  … 27.1M           3 ( 1%)        ⚡- 95.9% ±  0.1%
  instructions       43.7M  ± 23.8K     43.7M  … 43.8M          56 (16%)        ⚡- 95.6% ±  0.0%
  cache_references   1.46M  ± 14.6K     1.40M  … 1.50M          19 ( 5%)        ⚡- 94.3% ±  0.1%
  cache_misses        142K  ± 4.87K      127K  …  157K           2 ( 1%)        ⚡- 78.1% ±  0.4%
  branch_misses       126K  ± 1.37K      120K  …  129K          12 ( 3%)        ⚡- 86.3% ±  0.1%

これは劇的です。なぜなら、以前はbuild.zigロジックが各zig buildコマンドで実行されていましたが、現在はビルドシステムがキャッシュされたシリアライズされた設定を使用しているからです。

パフォーマンス以外では、ZLSのようなサードパーティ製ツールが、ビルドランナーのフォークを維持するのではなく、シリアライズされた設定ファイルを消費することから恩恵を受けると予想しています。

この変更はZigビルドシステムの内部メカニズムを大幅に再構築しますが、PRで指摘されている例外を除いて、APIの観点からはほとんど非破壊的です。

ほとんどの人にとって、これはおそらく主要な破壊的変更でしょう。

if (b.args) |args| {
    run_cmd.addArgs(args);
}

⬇️

run_cmd.addPassthruArgs();

これはビルドスクリプトから機能性を削除します。なぜなら、それらはもはやそれらの引数を観測できなくなるからです。その代わりに、これらの引数を変更した場合、ビルドスクリプトをソースから再ビルドする必要がなくなることを意味します。

Zigの方向性に影響を与えたい人は、プロジェクトを開発バージョンにアップグレードしてこれらの変更を試す良い機会です。数週間以内に0.17.0をリリースする予定です。しかし、時間がない場合、そして0.17.0がビルドを壊したことに気づいた場合でも、心配しないでください。0.17.1タグの修正を入れる機会はたくさんあります。

2026年4月8日

Incremental compilation with LLVM

著者: Matthew Lugg

先月type resolution changesをマージした後、個人的なプロジェクトに時間を費やしていましたが、最近LLVMコード生成バックエンドのいくつかの改善を行う時間を見つけました。これには、さまざまな目標を持ついくつかの異なる強化が含まれていましたが、ユーザーにとって便利な変更の1つは、LLVMバックエンドでインクリメンタルコンパイルを機能させることができたことです。

残念ながら、これは恐ろしいLLVM Emit Objectの時間を短縮することはできません。その時間は完全にLLVMに依存します。しかし、インクリメンタルコンパイルが役立つのは、実際のZigコンパイラコードでの時間を最小限に抑えることです。これは、コードにコンパイルエラーがある場合(そのため「LLVM Emit Object」がスキップされる)、通常は非常に迅速にエラーが得られることを意味します。(もちろん、ビルドが成功した場合でもわずかに高速化されます。)

このサポートは現在マスターブランチビルドで利用可能であり、0.16.0リリース(まもなくタグ付けされます)に含まれます。

まだ試していない方、特にZigのマスターブランチを使用している方は、zig buildに-fincremental --watchを渡してインクリメンタルコンパイルを試してみてください!Zigコアチームは1年間ワークフローでインクリメンタルコンパイルの恩恵を受けており、ユーザーからも良いフィードバックを聞いています。この機能は現在比較的安定しており、人々は数秒ではなくミリ秒で最新のコンパイルエラーを得るだけで、どれだけ時間を節約できるかに驚くことがよくあります。

個人的にはLLVMバックエンドでインクリメンタルコンパイルをあまり使用していませんが、CIのすべてのインクリメンタルテストカバレッジはLLVMバックエンドで有効になっており、ユーザーから肯定的なフィードバックを得ているため、試す価値は間違いなくあります。いつものように、インクリメンタルコンパイルでバグに遭遇した場合は、報告してください!

ありがとう、そしてこれが役に立つことを願っています :)

2026年3月10日

Type resolution redesign, with language changes to taste

著者: Matthew Lugg

本日、2〜3ヶ月(あるいはそれ以上)の作業を経て、30,000行のPRをマージしました。このブランチの目標は、Zigコンパイラの内部型解決ロジックを、より論理的でわかりやすい設計に再構築することでした。これは個人的には非常にエキサイティングな変更です。なぜなら、コンパイラの内部構造を整理することができたからです。しかし、ユーザーにとって便利な変更もいくつかあり、興味を引くかもしれません!

まず、Zigコンパイラは型のフィールドの解析に怠惰になりました。型が初期化されない場合、Zigはその型が「どのように見えるか」を気にする必要はありません。これは、型が名前空間としても機能する場合に重要です。これは現代のZigでは一般的なパターンです。例えば、std.Io.Writerを使用する場合、コンパイラにstd.Ioのコードを大量に引き込ませたくないでしょう!以下に簡単な例を示します。

const Foo = struct {
    bad_field: @compileError("i am an evil field, muahaha"),
    const something = 123;
};
comptime {
    _ = Foo.something; // `Foo`は名前空間としてのみ使用される
}

以前は、このコードはコンパイルエラーを発生させていました。しかし現在では、Zigは@compileError呼び出しを実際に見ないため、問題なくコンパイルされます。

もう1つの改善点は、「依存関係ループ」のエクスペリエンスです。Zigで依存関係ループのコンパイルエラーに遭遇したことがある人なら誰でも、エラーメッセージが全く役に立たないことを知っています。しかし、それは変わりました!もし(以前よりも発生しにくくなっていますが)遭遇した場合、依存関係ループがどこから来ているのかを正確に伝える詳細なエラーメッセージが表示されます。見てみましょう。

const Foo = struct { inner: Bar };
const Bar = struct { x: u32 align(@alignOf(Foo)) };
comptime {
    _ = @as(Foo, undefined);
}
$ zig build-obj repro.zig
error: dependency loop with length 2
    repro.zig:1:29: note: type 'repro.Foo' depends on type 'repro.Bar' for field declared here
    const Foo = struct { inner: Bar };
                                ^~~
    repro.zig:2:44: note: type 'repro.Bar' depends on type 'repro.Foo' for alignment query here
    const Bar = struct { x: u32 align(@alignOf(Foo)) };
                                               ^~~
    note: eliminate any one of these dependencies to break the loop

もちろん、依存関係ループはこれよりもはるかに複雑になる可能性がありますが、私がテストしたすべてのケースで、エラーメッセージには何が起こっているのかを簡単に把握できる十分な情報が含まれていました。

さらに、このPRはZigコンパイラの「インクリメンタルコンパイル」機能に大きな改善をもたらしました。要するに、多くの既知のバグが修正されましたが、特に「過剰解析」の問題(インクリメンタル更新が、時には大幅に、必要以上に多くの作業を行ってしまうこと)は、ついにほぼ排除されたはずです。これにより、多くの場合、インクリメンタルコンパイルが大幅に高速化されます!まだ試していない場合は、インクリメンタルコンパイルを試すことを検討してください。これは本当に素晴らしい開発体験です。これは間違いなく、この変更の主な動機となった最もエキサイティングな改善です。

このPRにはさらに多くの変更が含まれています。数十のバグ修正、いくつかの小さな言語変更(ほとんどがかなりニッチなもの)、およびコンパイラパフォーマンスの改善です。ここにすべてをリストするには多すぎますが、詳細を知りたい場合は、CodebergのPRを参照してください。もちろん、バグに遭遇した場合は、問題を報告してください。ハッピーハッキング!

2026年2月13日

io_uring and Grand Central Dispatch std.Io implementations landed

著者: Andrew Kelley

0.16.0リリースサイクルの終盤に近づくにつれて、Jacobはstd.Io.Eventedを最新のAPI変更に対応させるために精力的に取り組んできました。

これら両方とも、ユーザースペーススタックスイッチング(「ファイバー」、「スタックフルコルーチン」、または「グリーン スレッド」とも呼ばれます)に基づいています。

これらは現在、std.Io.Eventedを使用してアプリケーションを構築することで試すことができます。ただし、信頼性高く堅牢に使用できるようになる前に、重要なフォローアップ作業があるため、実験的と見なされるべきです。

これらの注意点を念頭に置くと、ZigコードでI/O実装を簡単に切り替えられる「約束の地」に確かに到達しているようです。

const std = @import("std");

pub fn main(init: std.process.Init.Minimal) !void {
    var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
    const gpa = debug_allocator.allocator();

    var threaded: std.Io.Threaded = .init(gpa, .{
        .argv0 = .init(init.args),
        .environ = init.environ,
    });
    defer threaded.deinit();
    const io = threaded.io();

    return app(io);
}

fn app(io: std.Io) !void {
    try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
}
$ strace ./hello_threaded
execve("./hello_threaded", ["./hello_threaded"], 0x7ffc1da88b20 /* 98 vars */) = 0
mmap(NULL, 262207, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f583f338000
arch_prctl(ARCH_SET_FS, 0x7f583f378018) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
prlimit64(0, RLIMIT_STACK, {rlim_cur=16384*1024, rlim_max=RLIM64_INFINITY}, NULL) = 0
sigaltstack({ss_sp=0x7f583f338000, ss_flags=0, ss_size=262144}, NULL) = 0
sched_getaffinity(0, 128, [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31]) = 8
rt_sigaction(SIGIO, {sa_handler=0x1019d90, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x10328c0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
rt_sigaction(SIGPIPE, {sa_handler=0x1019d90, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x10328c0}, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=0}, 8) = 0
writev(1, [{iov_base="Hello, World!\n", iov_len=14}], 1Hello, World!\n)
 = 14
rt_sigaction(SIGIO, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x10328c0}, NULL, 8) = 0
rt_sigaction(SIGPIPE, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x10328c0}, NULL, 8) = 0
exit_group(0)                           = ?
+++ exited with 0 +++

I/O実装のみを切り替える:

const std = @import("std");

pub fn main(init: std.process.Init.Minimal) !void {
    var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
    const gpa = debug_allocator.allocator();

    var evented: std.Io.Evented = undefined;
    try evented.init(gpa, .{
        .argv0 = .init(init.args),
        .environ = init.environ,
        .backing_allocator_needs_mutex = false,
    });
    defer evented.deinit();
    const io = evented.io();

    return app(io);
}

fn app(io: std.Io) !void {
    try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
}
execve("./hello_evented", ["./hello_evented"], 0x7fff368894f0 /* 98 vars */) = 0
mmap(NULL, 262215, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f70a4c28000
arch_prctl(ARCH_SET_FS, 0x7f70a4c68020) = 0
prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}) = 0
prlimit64(0, RLIMIT_STACK, {rlim_cur=16384*1024, rlim_max=RLIM64_INFINITY}, NULL) = 0
sigaltstack({ss_sp=0x7f70a4c28008, ss_flags=0, ss_size=262144}, NULL) = 0
sched_getaffinity(0, 128, [0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31]) = 8
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f70a4c27000
mmap(0x7f70a4c28000, 548864, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f70a4ba1000
io_uring_setup(64, {flags=IORING_SETUP_COOP_TASKRUN|IORING_SETUP_SINGLE_ISSUER, sq_thread_cpu=0, sq_thread_idle=1000, sq_entries=64, cq_entries=128, features=IORING_FEAT_SINGLE_MMAP|IORING_FEAT_NODROP|IORING_FEAT_SUBMIT_STABLE|IORING_FEAT_RW_CUR_POS|IORING_FEAT_CUR_PERSONALITY|IORING_FEAT_FAST_POLL|IORING_FEAT_POLL_32BITS|IORING_FEAT_SQPOLL_NONFIXED|IORING_FEAT_EXT_ARG|IORING_FEAT_NATIVE_WORKERS|IORING_FEAT_RSRC_TAGS|IORING_FEAT_CQE_SKIP|IORING_FEAT_LINKED_FILE|IORING_FEAT_REG_REG_RING|IORING_FEAT_RECVSEND_BUNDLE|IORING_FEAT_MIN_TIMEOUT|IORING_FEAT_RW_ATTR|IORING_FEAT_NO_IOWAIT, sq_off={head=0, tail=4, ring_mask=16, ring_entries=24, flags=36, dropped=32, array=2112, user_addr=0}, cq_off={head=8, tail=12, ring_mask=20, ring_entries=28, overflow=44, cqes=64, flags=40, user_addr=0}}) = 3
mmap(NULL, 2368, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_POPULATE, 3, 0) = 0x7f70a4ba0000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_POPULATE, 3, 0x10000000) = 0x7f70a4b9f000
io_uring_enter(3, 1, 1, IORING_ENTER_GETEVENTS, NULL, 8Hello, World!\n)
 = 1
io_uring_enter(3, 1, 1, IORING_ENTER_GETEVENTS, NULL, 8) = 1
munmap(0x7f70a4b9f000, 4096)            = 0
munmap(0x7f70a4ba0000, 2368)            = 0
close(3)                                = 0
munmap(0x7f70a4ba1000, 548864)          = 0
exit_group(0)                           = ?
+++ exited with 0 +++

ここで重要なのは、app関数がこれらの2つのスニペットで同一であるということです。

Hello Worldを超えて、Zigコンパイラ自体もstd.Io.Eventedを使用して問題なく動作します。io_uringとGCDの両方で、ただし前述のように、そうした場合のパフォーマンス低下はまだ診断されていません。

ハッピーハッキング、

Andrew

2026年2月6日

Two Package Management Workflow Enhancements

著者: Andrew Kelley

依存関係を持つZigプロジェクトがある場合、皆さんが知りたいと思うであろう2つの大きな変更が加えられました。

取得されたパッケージは、プロジェクトのルートにあるzig-pkgディレクトリ(build.zigファイルの隣)にローカルに格納されるようになりました。

例えば、zig buildを実行した後のaweboからの結果をいくつか示します。

$ du -sh zig-pkg/*
13M    freetype-2.14.1-alzUkTyBqgBwke4Jsot997WYSpl207Ij9oO-2QOvGrOi
20K    opus-0.0.2-vuF-cMAkAADVsm707MYCtPmqmRs0gzg84Sz0qGbb5E3w
4.3M   pulseaudio-16.1.1-9-mk_62MZkNwBaFwiZ7ZVrYRIf_3dTqqJR5PbMRCJzSuLw
5.2M   uucode-0.1.0-ZZjBPvtWUACf5dqD_f9I37VGFsN24436CuceC5pTJ25n
728K   vaxis-0.5.1-BWNV_AxECQCj3p4Hcv4U3Yo1WMUJ7Z2FUj0UkpuJGxQQ

このディレクトリをプロジェクトローカルのソース管理無視ファイル(例:.gitignore)に追加することを強く推奨します。しかし、.zig-cacheの外にあるため、すべての依存関係を含む自己完結型のソースtarballを配布する可能性があり、これによりオフラインでビルドしたり、アーカイブ目的で使用したりできます。

一方、依存関係の追加のコピーがグローバルにキャッシュされます。pathsフィルタに基づいて未使用のファイルをすべてフィルタリングした後、コンテンツは再圧縮されます。

$ du -sh ~/.cache/zig/p/*
2.4M    freetype-2.14.1-alzUkTyBqgBwke4Jsot997WYSpl207Ij9oO-2QOvGrOi.tar.gz
4.0K    opus-0.0.2-vuF-cMAkAADVsm707MYCtPmqmRs0gzg84Sz0qGbb5E3w.tar.gz
636K    pulseaudio-16.1.1-9-mk_62MZkNwBaFwiZ7ZVrYRIf_3dTqqJR5PbMRCJzSuLw.tar.gz
880K    uucode-0.1.0-ZZjBPvtWUACf5dqD_f9I37VGFsN24436CuceC5pTJ25n.tar.gz
120K    vaxis-0.5.1-BWNV_BFECQBbXeTeFd48uTJRjD5a-KD6kPuKanzzVB01.tar.gz

この変更の動機は、いじくり回しやすくすることです。これらのファイルを編集して、何が起こるか見てみてください。パッケージディレクトリをgit cloneに置き換えてみてください。依存関係全体をgrepしてみてください。IDEをzig-pkgディレクトリに基づいて自動補完するように設定してください。依存関係ツリーにbaobabを実行する。さらに、グローバルキャッシュに圧縮ファイルがあることで、コンピューター間でそのキャッシュデータを共有しやすくなります。将来的には、ピアツーピアのトレントによる依存関係ツリーのサポートが計画されています。パッケージを正規形式に再圧縮することで、ピアが最小限の帯域幅でZigパッケージを共有できるようになります。このアイデアは、ネットワーク障害に対する回復力と、人気投票を同時に提供するため、気に入っています。シーダー数に基づいてどのオープンソースパッケージが人気があるかを見つけてください!

2番目の変更は、zig buildに--forkフラグが追加されたことです。

振り返ってみると、それは非常に明白で、最初からなぜ思いつかなかったのかわかりません。これは次のようになります。

zig build --fork=[path]

これはプロジェクトオーバーライドオプションです。ソースチェックアウトへのパスを指定すると、依存関係ツリー全体でそのプロジェクトに一致するすべてのパッケージがオーバーライドされます。

パッケージコンテンツハッシュに名前とフィンガープリントが含まれているという事実は、パッケージが取得される前に解決されることを保証します。

これは、完全に別のディレクトリにある1つ以上のフォークを一時的に使用する簡単な方法です。すべてが機能するまで依存関係ツリー全体を反復処理でき、同時にフォークプロジェクトの開発環境とソース管理を快適に使用できます。

CLIフラグであるという事実は、それを適切に一時的なものにしています。フラグを削除するとすぐに、元のクリーンな取得済み依存関係ツリーに戻ります。

プロジェクトが一致しない場合、混乱を防ぐためにエラーが発生します。

$ zig build --fork=/home/andy/dev/mime
error: fork /home/andy/dev/mime matched no mime packages
$ 

プロジェクトが一致する場合、フォークを使用していることを示すリマインダーが表示され、混乱を防ぎます。

$ zig build --fork=/home/andy/dev/dvui
info: fork /home/andy/dev/dvui matched 1 (dvui) packages
...

この機能は、エコシステムの破損に対処するワークフローを強化することを目的としています。すでに少し試しましたが、非常に使いやすいと感じました。新しいワークフローは次のようになります。

  1. エコシステムの破損によりソースからのビルドに失敗する。
  2. --forkを使用してプロジェクトが再び機能するまで調整する。この間、実際のアップストリームソース管理、テストスイート、zig build test --watch -fincrementalなどを利用できます。
  3. ここで新しいオプションがあります:自己中心的になり、自分の作業を続けるか、パッチをアップストリームに送信するか。

…そして、アップストリームが修正のマージに時間がかかると予想しない限り、build.zig.zonをフォークに変更するステップをスキップできるでしょう。

2026年2月3日

Bypassing Kernel32.dll for Fun and Nonprofit

著者: Andrew Kelley

Windowsオペレーティングシステムは、カーネルで何かを行うための広範なABIサーフェスを提供します。しかし、すべてのABIが平等に作られているわけではありません。Casey Muratoriが彼の講義The Only Unbreakable Lawで指摘しているように、ソフトウェア開発チームの組織構造は、彼らが生成するソフトウェアの構造に直接影響を与えます。

Windows上のDLLは階層に整理されており、一部のAPIは低レベルAPIのハイレベルラッパーです。例えば、kernel32.dllの関数を呼び出すたびに、最終的には実際の作業はntdll.dllによって行われます。ProcMon.exeを使用してスタックトレースを調べると、これを直接確認できます。

経験的に学んだことは、ntdll APIは一般的によく設計されており、合理的で強力ですが、kernel32ラッパーは不要なヒープ割り当て、追加の障害モード、意図しないCPU使用率、および肥大化を導入するということです。

これが、Zig標準ライブラリのポリシーがPrefer the Native API over Win32である理由です。まだ完全ではありませんが、最近大きな進歩を遂げました。2つの例を挙げます。

Example 1: Entropy

公式ドキュメントによると、Windowsにはランダムバイトを取得するための簡単な方法がありません。

Chromium、boringssl、Firefox、Rustを含む多くのプロジェクトは、Windows 8より前のバージョンで動作したため、advapi32.dllからSystemFunction036を呼び出します。

残念ながら、Windows 8以降、この関数を最初に呼び出すと、bcryptprimitives.dllが動的にロードされ、ProcessPrngが呼び出されます。DLLのロードに失敗した場合(例えば、システム負荷が高い場合など、Zig CIで何度か観測されています)、void戻り値の関数からエラー38が返されます(この関数は失敗しないと文書化されています)。

ProcessPrngが最初に行うことは、少量の固定バイト数をヒープに割り当てることです。これが失敗した場合、BOOLでNO_MEMORYが返されます(文書化された動作は失敗せず、常にTRUEを返すことです)。

bcryptprimitives.dllは、ロードするたびにテストスイートを実行しているようです。

ProcessPrngが実際に行っていることは、`