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 $BUILD64
BAREMETAL なのに -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