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 をターゲットとします。他のターゲットでは以下の制限は無い可能性があります。)
  • 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
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


kmckk at 17:35コメント(0)Clang | 若槻 

コメントする

名前
 
  絵文字
 
 
記事検索
最新コメント
アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

QRコード
QRコード