背景
- レイトレーシングや機械学習など, aarch64(arm64) linux のプログラムを書いていたり, ライブラリをビルドしていたりする
- 市販で入手しやすい Native 環境(e.g. Raspberry Pi 4, Android スマホ + termux や, Jetson AGX とか. だいたい aarch64 4 コア + 8 GB mem な環境) だとビルドが遅かったり, 並列ビルドすると out-of-memory になりつらいので, つよつよ host PC(Threadripper とか)で cross-compile でやりたい
- 特に embree-aarch64 で ARM のビルドだと, 同等の性能の x86 の 5~10 倍はコンパイル遅いような現象が出て辛い
- AWS とかでつよつよ ARM インスタンス借りる手もあるが, レイトレーシングなど画面に出したりなどのアプリだとめんどいし, またコンパイルうまく行かずにソースコード直したり原因突き止めたりとかで時間消費しても課金されるのでおつらい.
- gcc だと aarch64-***-gcc みたいにコマンドが別れていて, cross-compile 設定がやりやすいが, clang だと基本 triple 指定なので cmake 記述がめんどい
- ネットにある情報(as of Jul 2020)だと, arm 32bit を想定しているのばかりで, aarch64 環境向けの設定がまったくない.
情報
Stackoverflow を参考にして, toolchain file を作って対応していきます.
方法
ターゲットは Raspberry Pi(64bit) や Jetson AGX(aarch64 Ubuntu 18.04)などを想定します.
ホスト環境は
- cmake 3.17
- Ubuntu 18.04(x64)
- clang 10(apt や, prebuilt で入るもの)
を仮定します. ターゲットとホストで同じ Ubuntu バージョンの環境(glibc, libstdc++ のバージョンが同じ環境)を使っているものとします.
cmake および clang は, あまり古すぎると aarch64 cross-compile 対応が未成熟でうまくいかないかもです.
libc, libstdc++ など
clang のパッケージ自体には libc(glibc) など含まれていないので(llvm-libc が開発中の模様ではある), gcc の libc, libstdc++(C++ STL)を流用します.
これは gcc のパッケージを入れるのが手っ取り早いと思います.
Ubuntu 18.04 には幸いにも aarch64 cross-compile 用 gcc パッケージがあります.
$ sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
$ sudo apt install g++-multilib
$ sudo apt install libstdc++-8-dev-arm64-cross
(g++-multilib
を入れれば, gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
は不要かも?)
/usr/aarch64-linux-gnu
に inc, lib が配置されます.
/usr/lib/gcc-cross/aarch64-linux-gnu/
にもクロスコンパイル用のなにか(crtbegin.o
などがあるのでランタイム周り?)が配置されます.
libstdc++-8-dev-arm64-cross
は aarch64 用の libstdc++ が入ります.
入れないと, コンパイル時に bits/c++-config.h
が見つからないエラーがでます。
(/usr/aarch64-linux-gnu/include/c++/8/aarch64-linux-gnu/bits/c++config.h
ファイルができているのを確認しましょう)
今回は 8
を使いましたが, Ubuntu のバージョンなどに応じて変更する必要があるかもです
(また, apt には pic
版もありました. コンパイルうまくいかなかったら pic
版を試すのもいいかも)
最小限のクロスコンパイル
まずは, 最小限の構成で, clang を用い C++ コードが aarch64 にクロスコンパイルできるかをチェックしましょう.
triple
clang の場合, --target
でコンパイル先のアーキテクチャを指定することでクロスコンパイルします.
clang --target=aarch64-linux-gnu
linker も, clang
コマンドを使います.
sysroot, gnu-toolchain
クロスコンパイルでは, コンパイル先の環境のヘッダやライブラリなどの検索パスを設定する必要があります.
--sysroot
でシステムヘッダ(?)のパスをしています. 今回は --sysroot=/usr/aarch64-linux-gnu
になります.
別途 --gnu-toochain
で glibc, libstdc++ 関連(?)のパスを設定します.
実際にはここで指定したパスに triple(aarch64-linux-gnu
) が付与されたりして検索されるようなので, --gnu-toolchain=/usr
になります.
内部的では -L... -lgcc_s ...
などに展開されます.
複数 aarch64 gcc 環境(version 7.5, 8 がインストールされているなど)がある場合, よろしくバージョンを選択してくれるようです.
また, --gnu-toolchain
の代わりに -B
flag を使うでも OK そうです: https://gcc.gnu.org/onlinedocs/gcc/Directory-Options.html
URL は gcc のヘルプですが, clang でもサポートされています.
pthread
-lpthread
でもいけるかもですが, Linux(posix) の推奨(?)である -pthread
を使います.
C++11 thread
など使う場合は現状 pthread 必須ですから(Posix 環境の場合), だいたい現在のナウでヤングな C++11 or later コードでは -pthread
必須と考えておいたほうがいいでしょう.
LLD linker
bfd(/usr/lib/gcc-cross/aarch64-linux-gnu/8/../../../../aarch64-linux-gnu/bin/ld
) だと, pthread 関連で,
/usr/lib/gcc-cross/aarch64-linux-gnu/8/../../../../aarch64-linux-gnu/bin/ld: cannot find /usr/aarch64-linux-gnu/lib/libpthread.so.0 inside /usr/aarch64-linux-gnu
/usr/lib/gcc-cross/aarch64-linux-gnu/8/../../../../aarch64-linux-gnu/bin/ld: cannot find /usr/aarch64-linux-gnu/lib/libpthread_nonshared.a inside /usr/aarch64-linux-gnu
とエラーがでてしまいます(どうも内部でのパス探索がうまくいっていないっぽい?) gold
の場合もだめでした.
-fuse-ld=lld
として, LLD リンカーを使いましょう
(apt で clang を入れている場合は, sudo apt install lld-10
などとして別途インストールが必要)
Minimal sample
#include <thread>
#include <iostream>
void fn(void) {
std::cout << "bora\n";
}
int main(int argc, char **argv)
{
std::thread th(fn);
th.join();
return EXIT_SUCCESS;
}
/mnt/data/local/clang+llvm-10.0.0-x86_64-linux-gnu-ubuntu-18.04/bin/clang++ -v \
--target=aarch64-linux-gnu \
-std=c++14 \
--gcc-toolchain=/usr \
--sysroot=/usr/aarch64-linux-gnu \
main.cc \
-fuse-ld=lld -pthread
Voala!
できたバイナリを aarch64 linux マシンにコピって動けば成功です!
あとはこの設定をうまく cmake で行えばよさそうです.
うまく行かない場合
-v -H
オプションなどで, 以下のようなヘッダのサーチパスやインクルード履歴など見れますのでがんばってデバッグしてみましょう.
#include "..." search starts here:
#include <...> search starts here:
/usr/lib/gcc-cross/aarch64-linux-gnu/8/../../../../aarch64-linux-gnu/include/c++/8
/usr/lib/gcc-cross/aarch64-linux-gnu/8/../../../../aarch64-linux-gnu/include/c++/8/aarch64-linux-gnu
/usr/lib/gcc-cross/aarch64-linux-gnu/8/../../../../aarch64-linux-gnu/include/c++/8/backward
/usr/lib/llvm-9/lib/clang/9.0.0/include
/usr/aarch64-linux-gnu/include
End of search list.
. /usr/lib/gcc-cross/aarch64-linux-gnu/8/../../../../aarch64-linux-gnu/include/c++/8/cstddef
.. /usr/lib/gcc-cross/aarch64-linux-gnu/8/../../../../aarch64-linux-gnu/include/c++/8/aarch64-linux-gnu/bits/c++config.h
... /usr/lib/gcc-cross/aarch64-linux-gnu/8/../../../../aarch64-linux-gnu/include/c++/8/aarch64-linux-gnu/bits/os_defines.h
.... /usr/aarch64-linux-gnu/include/features.h
..... /usr/aarch64-linux-gnu/include/stdc-predef.h
..... /usr/aarch64-linux-gnu/include/sys/cdefs.h
...... /usr/aarch64-linux-gnu/include/bits/wordsize.h
...... /usr/aarch64-linux-gnu/include/bits/long-double.h
..... /usr/aarch64-linux-gnu/include/gnu/stubs.h
...... /usr/aarch64-linux-gnu/include/bits/wordsize.h
...... /usr/aarch64-linux-gnu/include/gnu/stubs-lp64.h
... /usr/lib/gcc-cross/aarch64-linux-gnu/8/../../../../aarch64-linux-gnu/include/c++/8/aarch64-linux-gnu/bits/cpu_defines.h
.. /usr/lib/llvm-9/lib/clang/9.0.0/include/stddef.h
... /usr/lib/llvm-9/lib/clang/9.0.0/include/__stddef_max_align_t.h
bits/c++config.h
が見つからないエラー?
しかし、環境によっては(たとえば GitHub Actions での Ubuntu 18.04 ビルド環境), --sysroot
や -B
(--gcc-toolchain
)hではうまく aarch64 クロスコンパイル環境のヘッダを探索してくれないケースがあります.
たとえば, x86 のヘッダなどが優先されてしまい, bits/c++config.h
が見つからないエラーが出てしまう.
その場合は, -I
で直接指定するしかなさそうです(Clang cross compile で推奨の方法っぽい. -isystem
でもいいかも)
-nostdinc++
(C++ ヘッダを標準パス?から探索しない)も追加しておくとよいでしょう.
-nostdinc++ -I/path/to/c++ -I/path/to/c++/aarch64-linux-gnu/
など.
Cmake 設定
を参考にして toolchain ファイルを作ります.
# Based on https://stackoverflow.com/questions/54539682/how-to-set-up-cmake-to-cross-compile-with-clang-for-arm-embedded-on-windows
set(CMAKE_CROSSCOMPILING TRUE)
SET(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)
if(DEFINED ENV{GCC_ARM_TOOLCHAIN})
set(GCC_ARM_TOOLCHAIN $ENV{GCC_ARM_TOOLCHAIN})
else()
# Assume `/usr/aarch64-linux-gnu/` exists
# (e.g. though `sudo apt install gcc-aarch64-linux-gnu` on Ubuntu)
set(GCC_ARM_TOOLCHAIN "/usr")
endif()
# Clang target triple
SET(TARGET_TRIPLE aarch64-linux-gnu)
# specify the cross compiler
# TODO(LTE): Read clang path from environment variable
SET(CMAKE_C_COMPILER_TARGET ${TARGET_TRIPLE})
SET(CMAKE_C_COMPILER /mnt/data/local/clang+llvm-10.0.0-x86_64-linux-gnu-ubuntu-18.04/bin/clang)
SET(CMAKE_CXX_COMPILER_TARGET ${TARGET_TRIPLE})
SET(CMAKE_CXX_COMPILER /mnt/data/local/clang+llvm-10.0.0-x86_64-linux-gnu-ubuntu-18.04/bin/clang++)
SET(CMAKE_ASM_COMPILER_TARGET ${TARGET_TRIPLE})
SET(CMAKE_ASM_COMPILER /mnt/data/local/clang+llvm-10.0.0-x86_64-linux-gnu-ubuntu-18.04/bin/clang++)
# Don't run the linker on compiler check
set(CMAKE_TRY_COMPILE_TARGET_TYPE STATIC_LIBRARY)
# or we can use `--gcc-toolchain` for clang
# this flag is required to find libc(glibc) headers, crtbegin.o, etc
set(CMAKE_C_FLAGS_INIT " -B${GCC_ARM_TOOLCHAIN}")
set(CMAKE_CXX_FLAGS_INIT " -B${GCC_ARM_TOOLCHAIN} ")
# C/C++ toolchain
set(GCC_ARM_SYSROOT "${GCC_ARM_TOOLCHAIN}/${TARGET_TRIPLE}")
set(CMAKE_SYSROOT ${GCC_ARM_SYSROOT})
#set(CMAKE_FIND_ROOT_PATH ${GCC_ARM_SYSROOT})
# Search for programs in the build host directories
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
# For libraries and headers in the target directories
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
-
-B
(or--gcc-toolchain
)のパスはCMAKE_C_FLAGS_INIT
,CMAKE_CXX_FLAGS_INIT
に指定する - linker には clang を使う
lld 設定は, CMakeLists.txt あたりに, CMAKE_LINK_FLAGS
に -fuse-ld=lld
を使記述します(Toolchain cmake で記載してもいいかも)
set(CMAKE_CROSSCOMPILING TRUE)
pthread
-B
and --sysroot
がきちんと設定されていれば, find_package(Threads)
で pthread 見つかります.
ただ, cross-compiling 環境で, THREADS_PREFER_PTHREAD_FLAG
On にして, find_package(Threads) すると, CMAKE_THREAD_LIBS_INIT
にはなにもセットされないバグがあるようです(cmake 3.17.3 で確認)
work around として, -pthread
を明示的に linker flag などに設定しましょう.
cross compile のモードかどうかは, CMAKE_CROSSCOMPILING
変数で判定できます.
IF (CMAKE_CROSSCOMPILING)
LIST(APPEND CMAKE_LINK_FLAGS "-pthread ")
ENDIF()
など.
ただ, cross compile モードの場合, UNIX
フラグは立たないようです.
Windows への cross compile のケースなどと区別したい場合,
CMAKE_SYSTEM_PROCESSOR
や, toolchain file で設定した変数(e.g. TARGET_TRIPLE
)を export して(MARK_AS_ADVANCED
?), 外側の CmakeLists.txt でチェックできるようにするとよさそうです.
実例
embree-aarch64 を参照ください.
(現在 branch にあるが, いずれ master
にマージ予定)
まとめ
めんどいです. pthread や glibc(libc) 周りでいろいろワナがありますので注意です.
TODO
-
できたバイナリファイルの aarch64 実機へのコピーを楽にする方法を探す
- rsync で watch and 同期?
- aarch64 実機のストレージを NFS, CIFS(samba) マウントでもよいが, 10 GbE 以上でないとネットワーク速度律速になりそう
-
libc++(
-stdlib=libc++
)でのクロスコンパイルを試す - llvm-libc が出てきたら, llvm-libc でいけるか試す.
- 優秀な aarch64 + C/C++ 開発若人さまが, clang + cmake で aarch64 クロスコンパイル環境を整えることで, 人類史上最速で究極の aarch64 + C/C++ 開発若人さまへと昇華なされるスキームを確立する旅に出たい