D言語

【D言語】ビルドがハングアップした時の処方箋

TL;DR

dmdのリリースビルドがハングアップする場合、関数のインライン化に失敗していることが原因として考えられる。

コンパイルが止まってしまうモジュールで呼び出されている関数に、pragma(inline, false)をつけてインライン化を禁止すれば解決するかもしれない。

なお、コンパイルの停止個所を割り出すには、dmdの-vオプションが使える。

はじめに

筆者はゲーム制作にD言語を使っている。
今回の問題が起こった環境およびツールのバージョンは以下のとおり。

  • Windows10 64bit
  • dmd v2.100.0
  • dub v1.29.0

いきさつ

事のはじまり

ついこないだ、半年ぶりにゲーム制作の進捗動画をアップしようと思い、それに先立ってリリースビルドを実行したのだが、そのとき困ったことが起こった。

ビルドが長すぎる。いつまで経っても完了しないのだ。

長らくデバッグビルドでのテストプレイしかしておらず、リリースビルドは久しぶりだったので、最初のうちは「まあこんなもんか」と思っていたが、何分待てども応答がない。

テンプレート地獄の魔境みたいなコードならいざ知らず、コンパイルの速いD言語で、筆者の素直なコードにこんなに時間がかかるわけがない。

どんなふうにハングアップするか

なにか困ったときに我々パンピーにできることといえば、タスクマネージャを開くことくらいだ。

メモリやCPU使用率を監視しながら、もう一度リリースビルドを走らせてみる。

すると、メモリ使用量の増加が、あるところでピタッと完全に止まってしまうことが分かった。

CPU使用率はというと、高い値のまま張り付いている。

ああ、これは完全に死んでますね…

何故止まるか調べたい

さて弱った。こんなことは今まであったことがない。

コードにバグがあるのかと、あっちを書き換えこっちに手を加え、絶望した面持ちで原因のあぶり出しをしていたが、ここでふとあることを思い出す。

-vオプションがあるじゃないか!

dmdの-vオプション

-vというのはdmdのオプションのひとつで、コンパイルの過程を詳細に出力してくれるものだ。

この出力がつっかえたところに何か問題があるはず。

というわけで、dub.jsondflags-vを追加する。

dflagsというのは、dubがコンパイラ(dmd)に引き渡すコンパイラオプションの配列である。

// こんな感じ
"dflags": [
    "-v"
]

あとは、先ほどまでと同じようにdub build -b=releaseでリリースビルドを実行する。

すると、コンソールにこんな風に膨大なログがずらーっと出力される。

code xxx
function game.aaa.bbb.xxx.this
function game.aaa.bbb.xxx.foo
function game.aaa.bbb.xxx.bar
function game.aaa.bbb.xxx.baz
...

あとは、つっかえるのを待つ。

原因個所が判明

果たして作戦は成功した。

あるモジュールのコンパイルに差し掛かった段階で、コンソールの出力が停止する。
メモリ使用量が増加しなくなるタイミングとも一致している。これだ!

具体的な処理内容とかは(書いても誰の役にも立たないので)省くが、ある構造体のリストを生成する関数でハングアップしているらしい。

/// メッセージウインドウ情報の構造体のリストを生成する。
Message[] createMessageList(int messageID) @safe
{
    switch (messageID)
    {
        case 0:
            return
            [
                createMessage(/* メッセージを生成する引数 */),
                createMessage(/* メッセージを生成する引数 */),
                createMessage(/* メッセージを生成する引数 */),
                ...
            ];

        case 1:
            return
            [
                createMessage(/* メッセージを生成する引数 */),
                createMessage(/* メッセージを生成する引数 */),
                createMessage(/* メッセージを生成する引数 */),
                ...
            ];

        // case文が延々と続く...
    }
}

渡されたIDで分岐して、構造体のリストを生成して返している。

では何故ハングアップするのか

※一応、ここからは多分に推測も含まれることを断っておきたい。

問題の関数を色々いじってみたところ、case文を減らせばハングアップしないことがわかった。

もちろん、百個かそこらのcase文ごときでコンパイラが音を上げるとは思えない。

リリースビルド特有の最適化が絡んでいそうだと予想し、上述のcreateMessageに対してpragma(inline, false)を付与してインライン化を禁止したところ、解決に至ったわけだ。
(これは勘だったとしか言いようがない。ここから更にハマらなかったのは、運がよかった。)

dmdのインライン化の基準はよくわからないが、このcreateMessageという関数が、ほぼ構造体を生成して返すだけのわりと単純な実装だったので、インライン化可能と判断されたのだろう。

しかし、その呼び出し個所がいかんせん膨大で、関数自体も引数が多く、なおかつ、戻り値がcase文の中で動的配列にダイレクトに格納されるという特殊な事情も相まって、今回のハングアップを引き起こしてしまったのだろうと推測している。

環境の問題なのか、コンパイラのバグなのか、根本的な原因はよくわかっていない。

ただまあ、今後、似たようなコードを書く際には留意しておきたいところだ。

補足

ちなみに、メモリ不足が一因なのではないかと考え、-lowmemオプションを付けて試してみたが、こちらは効果がなかった。

おわりに

D言語の記事を書くのは、こっちのサイトに越してきてからはこれが初めてか。

せっせと書いてて、「この記事、需要あるんか…?」とか正直思わんでもなかったけど、D言語の、しかも日本語の記事ってほんとに少ないから、ここはひとつ、枯れ木も山の賑わいということで仕上げてみた。

しかし、dmdの-vオプションを思い出せたのは本当にラッキーだった。

以前、オプション一覧をサラッと眺めておいてよかった…

まぐれでしか突破できない障壁がぶっちゃけまだまだ多いと感じるD言語かいわいなので、ちょっとしたことでも日本語の情報が増えるといいなとか思う入梅の候なのでした。