2021年04月14日
GCC無しでClangクロスCコンパイラ(RISCV64)をビルドして自動ベクトル化を試す
GCC の場合、ホストの C++ コンパイラのみ(通常は GCC)で、Binutils と GCC のソースコードから完全な C/C++ クロスコンパイラをビルドすることが可能です。
これまで Clang は GCC に依存しており、LLVM プロジェクトのソースコードのみで完全なクロスコンパイラをビルドすることはできない(そのため、GCC のヘッダやライブラリをそのまま使うしかない)という認識でした。この誤解は、CMake が完全な(a.out を生成可能な)C/C++ コンパイラを要求するので、Clang のランタイムライブラリである compiler-rt を Clang 自身でビルドする方法がわからなかったためです。一つでも GCC でライブラリをビルドしてしまうと、そのライブラリは GCC のヘッダに依存することになるので、他のライブラリも全て同じ GCC でビルドしなければなりません。
しかし最近いろいろ調べていて、実はそれが可能であることがわかりました。
ただし、以下の制限があります。(この記事では RISC-V をターゲットとします。他のターゲットでは以下の制限は無い可能性があります。)
これまで Clang は GCC に依存しており、LLVM プロジェクトのソースコードのみで完全なクロスコンパイラをビルドすることはできない(そのため、GCC のヘッダやライブラリをそのまま使うしかない)という認識でした。この誤解は、CMake が完全な(a.out を生成可能な)C/C++ コンパイラを要求するので、Clang のランタイムライブラリである compiler-rt を Clang 自身でビルドする方法がわからなかったためです。一つでも GCC でライブラリをビルドしてしまうと、そのライブラリは GCC のヘッダに依存することになるので、他のライブラリも全て同じ GCC でビルドしなければなりません。
しかし最近いろいろ調べていて、実はそれが可能であることがわかりました。
ただし、以下の制限があります。(この記事では RISC-V をターゲットとします。他のターゲットでは以下の制限は無い可能性があります。)
- LLVM のリンカ LLD は RISC-V のデフォルトである -mrelax オプション(Linker optimization/relaxation)のサポートが完全ではないなど、様々な問題があるため、リンカのみ Binutils の GNU ld を使用します。
- LLVM の C++ ランタイム実装 libc++abi と C++ ライブラリ libc++ が newlib ではビルドできないようなので、今回は C コンパイラのみビルドします。(libc++abi の実装に使用される C++ 例外の実装 libunwind はビルド可能ですが、C の場合は不要なので今回は割愛します。)
- RISC-V 命令セットシミュレータの spike と一緒に使用する pk カーネルが clang ではビルドできないようなので、spike/pk は今回はビルドせず、以前の環境で GCC を使ってビルドしたものを使用します。(OVPsim の無償版は現在 V 拡張をサポートしていない問題があります。)
ポイントは、compiler-rt などをビルドする際に -DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY を指定するということです。これによりターゲットの C/C++ コンパイラのチェックをスキップすることができるので、不完全な Clang コンパイラで付属ライブラリをビルドすることが可能となります。
また、compiler-rt が stdlib.h を要求するので先に newlib をビルドする、builtins 以外のライブラリは C++ を使用するので全て disable にする、なども重要です。
環境変数の設定を多用するので、ビルドは以下のシェルスクリプトで行います。RISC-V の Vector 拡張(RVV)はまだ 1.0 にならず、なんと 0.9 の次は 0.10 になってしまいました。いまだに Clang のオプションは v0p10 -menable-experimental-extensions と、バージョン指定のままです。
Clang のビルド環境については以下の記事を参照してください。
http://blog.kmckk.com/archives/5715073.html
今回は簡単のため spike/pk で動けば良しとしましたが、実際に組み込み開発に使用する場合、RISC-V のデフォルトは medlow メモリモデルで 0x80000000 以下のアドレスにしかバイナリを配置できない制限があるため、ライブラリを -mcmodel=medany 付きでビルドする必要があるかもしれません。
現在の Clang は、以下のように普通の C プログラムを RVV 命令で自動ベクトル化できるようになってきていて、かなりすごいです。
また、compiler-rt が stdlib.h を要求するので先に newlib をビルドする、builtins 以外のライブラリは C++ を使用するので全て disable にする、なども重要です。
環境変数の設定を多用するので、ビルドは以下のシェルスクリプトで行います。RISC-V の Vector 拡張(RVV)はまだ 1.0 にならず、なんと 0.9 の次は 0.10 になってしまいました。いまだに Clang のオプションは v0p10 -menable-experimental-extensions と、バージョン指定のままです。
Clang のビルド環境については以下の記事を参照してください。
http://blog.kmckk.com/archives/5715073.html
set -x -e [ -z $TOP ] && export TOP=$PWD/riscv64 [ -z $PARA ] && export PARA=-j32 INSTALL64=$TOP/install64 BUILD64=$TOP/build64 export PATH=$INSTALL64/bin/:$PATH export ARCH64="rv64gcv" export CLANG_ARCH64="${ARCH64}0p10 -menable-experimental-extensions" export ABI64=lp64d export TARGET64=riscv64-unknown-elf export CLANG_CFLAGS64="--target=${TARGET64} -march=${CLANG_ARCH64} -mabi=${ABI64}" SRC=$PWD/src mkdir -p $SRC cd $SRC git clone --depth 1 -b rvv-1.0.x-zfh https://github.com/riscv/riscv-binutils-gdb.git wget ftp://sourceware.org/pub/newlib/newlib-4.1.0.tar.gz tar xvf newlib-4.1.0.tar.gz git clone --depth 1 https://github.com/llvm/llvm-project.git mkdir -p $INSTALL64 $BUILD64 cd $BUILD64 mkdir binutils cd binutils/ $SRC/riscv-binutils-gdb/configure --prefix=$INSTALL64 --target=$TARGET64 --enable-lto --disable-werror --disable-shared --disable-nls --with-sysroot=yes make $PARA && make install cd $BUILD64 mkdir clang cd clang/ cmake -DLLVM_ENABLE_PROJECTS="clang;lld" \ -G "Unix Makefiles" $SRC/llvm-project/llvm \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=$INSTALL64 \ -DLLVM_TARGETS_TO_BUILD="RISCV" \ -DLLVM_DEFAULT_TARGET_TRIPLE=$TARGET64 \ -DLLVM_BUILD_EXAMPLES=OFF \ -DLLVM_INCLUDE_EXAMPLES=OFF \ -DBUILD_SHARED_LIBS=OFF \ -DLLVM_OPTIMIZED_TABLEGEN=ON \ -DLLVM_ENABLE_LIBXML2=OFF \ -DCLANG_DEFAULT_RTLIB=compiler-rt \ -DCLANG_DEFAULT_UNWINDLIB=libunwind \ -DCLANG_DEFAULT_CXX_STDLIB=libc++ make $PARA && make install cp bin/* $INSTALL64/bin cd $BUILD64 ARGSTR='"$@"' echo "${INSTALL64}/bin/clang ${CLANG_CFLAGS64} -Wno-unused-command-line-argument ${ARGSTR}" > $INSTALL64/bin/riscv64-unknown-elf-clang echo "${INSTALL64}/bin/clang++ ${CLANG_CFLAGS64} -Wno-unused-command-line-argument ${ARGSTR}" > $INSTALL64/bin/riscv64-unknown-elf-clang++ chmod +x $INSTALL64/bin/$TARGET64-clang chmod +x $INSTALL64/bin/$TARGET64-clang++ # stdlib.h is required to build compiler-rt, so we need to build newlib first. mkdir newlib cd newlib export CFLAGS_FOR_TARGET=" -g -gdwarf-3 -gstrict-dwarf -O2 -ffunction-sections -fdata-sections " export CC_FOR_TARGET=$TARGET64-clang export AS_FOR_TARGET=$TARGET64-clang export LD_FOR_TARGET=lld export CXX_FOR_TARGET=$TARGET64-clang++ export AR_FOR_TARGET=llvm-ar export NM_FOR_TARGET=llvm-nm export RANLIB_FOR_TARGET=llvm-ranlib export OBJCOPY_FOR_TARGET=llvm-objcopy export OBJDUMP_FOR_TARGET=llvm-objdump export READELF_FOR_TARGET=llvm-readelf export STRIP_FOR_TARGET=llvm-strip export LIPO_FOR_TARGET=llvm-lipo export DLLTOOL_FOR_TARGET=llvm-dlltool $SRC/newlib-4.1.0/configure --prefix=$INSTALL64 --target=$TARGET64 --disable-multilib --disable-nls make $PARA all && make install cd $BUILD64 mkdir libcompiler_rt cd libcompiler_rt export CC="${INSTALL64}/bin/${TARGET64}-clang" export CXX="${INSTALL64}/bin/${TARGET64}-clang++" export AR="${INSTALL64}/bin/llvm-ar" export NM="${INSTALL64}/bin/llvm-nm" export RANLIB="${INSTALL64}/bin/llvm-ranlib" export OBJCOPY="${INSTALL64}/bin/llvm-objcopy" export LLVM_CONFIG="${INSTALL64}/bin/llvm-config" TARGET_CFLAGS="" TARGET_CXXFLAGS="${TARGET_CFLAGS}" TARGET_LDFLAGS="" LLVM_VERSION=`$CC -dumpversion` LLVM_RESOURCEDIR=/lib/clang/$LLVM_VERSION cmake -G "Unix Makefiles" $SRC/llvm-project/compiler-rt \ -DCMAKE_INSTALL_PREFIX=$INSTALL64/$LLVM_RESOURCEDIR/ \ -DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \ -DCMAKE_CROSSCOMPILING=True \ -DCMAKE_SYSTEM_NAME=Linux \ -DCMAKE_BUILD_TYPE=Release \ -DCOMPILER_RT_BUILD_BUILTINS=ON \ -DCOMPILER_RT_BUILD_SANITIZERS=OFF \ -DCOMPILER_RT_BUILD_XRAY=OFF \ -DCOMPILER_RT_BUILD_LIBFUZZER=OFF \ -DCOMPILER_RT_BUILD_PROFILE=OFF \ -DCOMPILER_RT_BUILD_MEMPROF=OFF \ -DCOMPILER_RT_BUILD_XRAY_NO_PREINIT=OFF \ -DCOMPILER_RT_SANITIZERS_TO_BUILD=none \ -DCMAKE_C_COMPILER=$CC \ -DCMAKE_CXX_COMPILER=$CXX \ -DCMAKE_AR=$AR \ -DCMAKE_NM=$NM \ -DCMAKE_RANLIB=$RANLIB \ -DLLVM_CONFIG_PATH=$LLVM_CONFIG \ -DCMAKE_C_COMPILER_TARGET=$TARGET64 \ -DCOMPILER_RT_DEFAULT_TARGET_ONLY=ON \ -DCMAKE_C_FLAGS="${TARGET_CFLAGS}" \ -DCMAKE_CXX_FLAGS="${TARGET_CXXFLAGS}" \ -DCMAKE_EXE_LINKER_FLAGS="${TARGET_LDFLAGS}" \ -DCOMPILER_RT_BAREMETAL_BUILD=ON \ -DCOMPILER_RT_OS_DIR="" make $PARA && make install cd $BUILD64BAREMETAL なのに -DCMAKE_SYSTEM_NAME=Linux を指定して、OS_DIR が空指定(これを指定しないと、一段深い BareMetal サブディレクトリにインストールされてしまいます)など、若干気持ち悪い所がありますが、これは現状 Clang のコンパイラドライバが Linux を前提としているため、crtbegin/end を要求し、BareMetal ではない PATH のライブラリを参照するためです。Linux 以外を指定して compiler-rt をビルドすると crtbegin/end がビルドされないようです。(現状の RISCV の BAREMETAL は crt0 も要求しないようです。今回は newlib の crt0.o をそのまま使用したかったので、このような少々トリッキーな設定になっています。)
今回は簡単のため spike/pk で動けば良しとしましたが、実際に組み込み開発に使用する場合、RISC-V のデフォルトは medlow メモリモデルで 0x80000000 以下のアドレスにしかバイナリを配置できない制限があるため、ライブラリを -mcmodel=medany 付きでビルドする必要があるかもしれません。
現在の Clang は、以下のように普通の C プログラムを RVV 命令で自動ベクトル化できるようになってきていて、かなりすごいです。
$ cat test.c #include <stdlib.h> #include <stdio.h> int sum(int *a, int len) { int sum = 0; for (int i = 0; i < len; i++) { sum += a[i]; } return sum; } #define ARRAY_SIZE 1024 int array[ARRAY_SIZE]; int main() { srand(1234); for (int i = 0; i < ARRAY_SIZE; i++) array[i] = rand(); int x = sum(array, ARRAY_SIZE); printf("sum = %d\n", x); return 0; } $ ./riscv64/install64/bin/riscv64-unknown-elf-clang -O3 -mllvm -riscv-v-vector-bits-min=256 -save-temps test.c $ cat test.s .text .attribute 4, 16 .attribute 5, "rv64i2p0_m2p0_a2p0_f2p0_d2p0_c2p0_v0p10_zvamo0p10_zvlsseg0p10" .file "test.c" .globl sum # -- Begin function sum .p2align 1 .type sum,@function sum: # @sum # %bb.0: blez a1, .LBB0_3 # %bb.1: # %.lr.ph.preheader slli a2, a1, 32 addi a3, zero, 8 srli a2, a2, 32 bgeu a1, a3, .LBB0_4 # %bb.2: mv a3, zero mv a1, zero j .LBB0_7 .LBB0_3: mv a0, zero ret .LBB0_4: # %vector.ph andi a3, a2, -8 vsetivli a1, 8, e32,m1,ta,mu vmv.v.i v25, 0 mv a1, a3 mv a4, a0 .LBB0_5: # %vector.body # =>This Inner Loop Header: Depth=1 vsetivli a5, 8, e32,m1,ta,mu vle32.v v26, (a4) vadd.vv v25, v26, v25 addi a1, a1, -8 addi a4, a4, 32 bnez a1, .LBB0_5 # %bb.6: # %middle.block vsetvli a1, zero, e32,m1,ta,mu vmv.v.i v26, 0 vsetivli a1, 8, e32,m1,ta,mu vredsum.vs v25, v25, v26 vmv.x.s a1, v25 beq a3, a2, .LBB0_9 .LBB0_7: # %.lr.ph.preheader12 slli a4, a3, 2 add a0, a0, a4 sub a2, a2, a3 .LBB0_8: # %.lr.ph # =>This Inner Loop Header: Depth=1 lw a3, 0(a0) addw a1, a1, a3 addi a2, a2, -1 addi a0, a0, 4 bnez a2, .LBB0_8 .LBB0_9: # %._crit_edge mv a0, a1 ret .Lfunc_end0: .size sum, .Lfunc_end0-sum # -- End function .globl main # -- Begin function main .p2align 1 .type main,@function main: # @main # %bb.0: addi sp, sp, -32 sd ra, 24(sp) # 8-byte Folded Spill sd s0, 16(sp) # 8-byte Folded Spill sd s1, 8(sp) # 8-byte Folded Spill addi a0, zero, 1234 call srand lui a0, %hi(array) addi s0, a0, %lo(array) addi s1, zero, 1024 .LBB1_1: # =>This Inner Loop Header: Depth=1 call rand sw a0, 0(s0) addi s1, s1, -1 addi s0, s0, 4 bnez s1, .LBB1_1 # %bb.2: # %vector.body.preheader lui a0, %hi(array) addi a0, a0, %lo(array) vsetivli a1, 8, e32,m1,ta,mu vmv.v.i v25, 0 addi a1, zero, 1024 .LBB1_3: # %vector.body # =>This Inner Loop Header: Depth=1 vsetivli a2, 8, e32,m1,ta,mu vle32.v v26, (a0) vadd.vv v25, v26, v25 addi a1, a1, -8 addi a0, a0, 32 bnez a1, .LBB1_3 # %bb.4: # %middle.block vsetvli a0, zero, e32,m1,ta,mu vmv.v.i v26, 0 vsetivli a0, 8, e32,m1,ta,mu vredsum.vs v25, v25, v26 vmv.x.s a1, v25 lui a0, %hi(.L.str) addi a0, a0, %lo(.L.str) call printf mv a0, zero ld s1, 8(sp) # 8-byte Folded Reload ld s0, 16(sp) # 8-byte Folded Reload ld ra, 24(sp) # 8-byte Folded Reload addi sp, sp, 32 ret .Lfunc_end1: .size main, .Lfunc_end1-main # -- End function .type array,@object # @array .bss .globl array .p2align 2 array: .zero 4096 .size array, 4096 .type .L.str,@object # @.str .section .rodata.str1.1,"aMS",@progbits,1 .L.str: .asciz "sum = %d\n" .size .L.str, 10 .ident "clang version 13.0.0 (https://github.com/llvm/llvm-project.git 91b6ef64db55084b33295e640258c926acedcb1a)" .section ".note.GNU-stack","",@progbits .addrsig $ ../../tool/RVV_GCC/install/riscv64-unknown-elf/bin/spike ../../tool/RVV_GCC/install/riscv64-unknown-elf/bin/pk a.out bbl loader sum = 16823942