nvccのホスト用コンパイラも意識すべき

nvccはホスト(CPU)側のコードを生成するのはホスト用のコンパイラgccとか)に任せており、これは--compiler-binder <path>で指定できる。

以下nvcc --helpから抜粋。

--compiler-bindir <path>                   (-ccbin)
        Specify the directory in which the host compiler executable resides.  The
        host compiler executable name can be also specified to ensure that the correct
        host compiler is selected.  In addition, driver prefix options ('--input-drive-prefix',
        '--dependency-drive-prefix', or '--drive-prefix') may need to be specified,
        if nvcc is executed in a Cygwin shell or a MinGW shell on Windows.

CPU側のコードをシステムのものではないコンパイラコンパイルしたならこれも変えたほうがいいっぽい。

CMakeを使っているときは、

set_target_properties(project_lib_cuda PROPERTIES
    COMPILE_FLAGS "-ccbin ${CMAKE_CXX_COMPILER}")

のようにする。どうやら勝手に入れてはくれないようなので(もしかしたら知らないフラグがあるのかも)。


何が起きたかを一応書いておく。まず、どこでも使えるロガーをstaticを使って作っていた。かなり簡単にすると以下のような感じ。

template<typename charT, typename traitsT = std::char_traits<charT>>
struct logger
{
    template<tyepname ... Ts>
    static log(Ts&& ... args)
    {
        if(filename.empty())
        {
            throw std::runtime_error("filename is not initialized");
        }
        std::basic_ofstream<charT, traitsT> ofs(filename, std::ios::out | std::ios::app);
        (ofs << ... << std::forward<Ts>(args));
    }
    static std::basic_string<charT, traitsT> filename;
};
template<typename charT, typename traitsT>
std::basic_string<charT, traitsT> logger<charT, traitsT>::filename;

全てをstaticにすることでどこからでもログに書き込める。必要無いのにtemplateにした理由はグローバル変数のODRを回避できるから(C++17を使えるならinline変数を使うのが吉なのでこの実装は時代遅れ)。別スレッドから同時に呼び出すと大変なことになります。

これをCUDAを使うコードのCPU部分と非CUDAな残りのコードの部分で共有して使っていた。一応注意書きしておくと、上のはCUDAカーネル内では動かない。CPUでCUDAカーネルを呼び出す直前とかに呼ぶためのものだ。

今やっているプロジェクトではcudaのコードは別にコンパイルされて最後にリンクされる。ここで、CPU版のコードで初期化したfilenameがcuda部分では初期化されていないというエラーが発生した。それまで普通にログが出ていたのに、CUDAコードに入った瞬間に「ファイル名が与えられていない」と言って落ちたので最初は混乱した。

謎の直感によって-ccbin ${CMAKE_CXX_COMPILER}を足すとこのエラーは収まってちゃんとログが書きだされたので、違うコンパイラを使っていたせいでリンクが上手くいかず、全体で共有されているstatic変数とCUDAコードを含むnvccコンパイルされたオブジェクトの方で異なるアドレスに置かれていたのだろう。

CUDAと非CUDAコードを混ぜて書くときは注意しましょう。