Macでclang+OpenMP

特に手を入れてないMacのほぼ最新環境(OS10.9.5, Xcode6.0)でopenmpを使う方法メモ。OS10.9/Xcode6.0の時点ではllvmのバージョンは3.5で、OpenMPにわずかに対応しているように見えますが、以下のような状態で、気にせずに使えるとは言いがたいです。

-  -Xclangオプションが-fopenmpの他に必要([参考](http://stackoverflow.com/questions/26159225/openmp-support-in-xcode-6-clang-3-5))
-  適当なOpenMPのサンプルコードを動かそうとすると、おそらく一番使いたいであろう#pragma omp parallel forが通らない。

# OpenMP対応のclangをビルドする

おそらく次のllvmバージョンアップでほぼ特別対応は不要になると思うのですが、上記環境下でOpenMP使う方法を調べました。llvmのOpenMP用ブランチを落としてきて、clang丸ごと別ビルドを作ることになります。

1.  偉い人が[一発構築用のスクリプト](https://gist.github.com/palmerc/59115c29aa5703f669fc)を作ってくれているので、これをシェルスクリプトとして手元に用意する (名前はmac-clang-omp-setup.shであるとする)。
1.  ターミナルからsudo sh mac-clang-omp-setup.shする。スクリプトを見ると分かるように途中で「gccないんか？clang使う場合はスクリプトいじってね？」と尋ねられるので、yesと答えると、予告通り(?)makeが途中でエラーになり、それでもシェルスクリプトは最後まで到達する。
1.  問題箇所を修正する。私の環境では2カ所あった([参考](http://www.enomosphere.net/2014/07/05/clangでopenmp/))。

    1.  ~/code/libomp_oss/tools/check-tools.plの450行目と451行目 → コメントアウト

        ```perl
        if ( $target_os eq "lin" or $target_os eq "mac" ) {
          # check for gnu tools by default because touch-test.c is compiled with them.
          # push( @versions, [ "GNU C Compiler",     get_gnu_compiler_version( $gnu_compilers->{ $target_os }->{ c   } ) ] );
          # push( @versions, [ "GNU C++ Compiler",   get_gnu_compiler_version( $gnu_compilers->{ $target_os }->{ cpp } ) ] );
```
    2.  ~/code/libomp_oss/src/makefile.mkの433行目の-no-intel-extensionsオプション → やはりコメントアウト

        ```Makefile
        ifeq "$(os)" "mac"
        #    ld-flags += -no-intel-extensions
            ld-flags += -single_module
            ld-flags += -current_version $(VERSION).0 -compatibility_version $(VERSION).0
        endif
      ```

1. この状態でmakeをやり直す(sudo make compiler=clang)。これで最後まで通ると同時に各種PATHの更新が~/.profileに書き込まれる。ただし、~/.bash_profileなどが既に存在している環境では~/.profileが読み込まれないので([参考](http://kadoppe.com/archives/2012/06/bash-cannot-read-profile.html))、必要に応じて~/.profileに書き込まれた設定を移動する。

以上で新しいターミナルからclang2++が読めるようになっていて、例えば以下のようなサンプルプログラムが並列動作することが分かります。

```cpp
#include <omp.h>
#include <stdio.h>

int main(){
	int a[10] = {1,2,3,4,5,6,7,8,9,10};
	int b[10] = {0,0,0,0,0,0,0,0,0,0};

	#pragma omp parallel for
	for(size_t i=0; i<10; ++i){
		b[i] = a[i];

		printf("i=%lu, threadid=%d\n", i, omp_get_thread_num());
	}

	return 0;
}
```

# 不足のinclude pathを追加する
さて上の手順でclang2++にパスが通り普通のOpenMP対応のC++が書けると思いきや、C++標準ヘッダたちの一部が見つからないと怒られる (例えば`#include <cstdint>`が通らない) のでさらに修正が必要です。結論から言うと~/.profileに書かれるべき内容は以下のようになります。もちろんusernameの部分は適宜自分の環境に合わせてください。

```
export PATH=/Users/username/code/llvm/build/Release+Asserts/bin:$PATH
export C_INCLUDE_PATH=/Users/username/code/llvm/include:/Users/username/code/libomp_oss/exports/common/include:$C_INCLUDE_PATH
export CPLUS_INCLUDE_PATH=/Users/username/code/llvm/include:/Users/username/code/libomp_oss/exports/common/include:$CPLUS_INCLUDE_PATH
export CPLUS_INCLUDE_PATH=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../include/c++/v1:$CPLUS_INCLUDE_PATH
export CPLUS_INCLUDE_PATH=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/clang/6.0/include:$CPLUS_INCLUDE_PATH
export CPLUS_INCLUDE_PATH=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include:$CPLUS_INCLUDE_PATH
export CPLUS_INCLUDE_PATH=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include:$CPLUS_INCLUDE_PATH
export CPLUS_INCLUDE_PATH=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/System/Library/Frameworks:$CPLUS_INCLUDE_PATH
export LIBRARY_PATH=/Users/username/code/llvm/build/Release+Asserts/lib:/Users/username/code/libomp_oss/exports/mac_32e/lib.thin:$LIBRARY_PATH
export DYLD_LIBRARY_PATH=/Users/username/code/llvm/build/Release+Asserts/lib:/Users/username/code/libomp_oss/exports/mac_32e/lib.thin:$DYLD_LIBRARY_PATH}
```

環境構築スクリプトmac-clang-omp-setup.shで~/.profileに追記される内容にはおそらく/Applicationで始まるCPLUS_INCLUDE_PATHがありません。なぜこうなっているのか私にはわかりませんでしたが、ともかくコマンドラインから素直にclang2++している限りではヘッダのincludeに失敗しています。

そこで、Macの「普通の方法」でC++をビルドしたときのコマンドライン引数を調べて、その差分を追加することを考えました。私は以下の方法を取って、不足の設定を抽出しました。

1. Xcodeで適当なC++コマンドラインプロジェクトを新規作成する
2. main.cppにコンパイルエラーを起こす適当な修正を加える
3. Issue Navigatorに表示されたエラーメッセージを右クリック->「Reveal in Log」する
4. 出てきてコマンドライン引数(超長い)を見て、足りなさそうな箇所を探す

同じ方法を使えば今後のMacバージョンアップで上記export文が微妙に変わっても正しい状態を維持できると思います。