LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。
C++のサポート
LLVMでC++特殊な文法をサポートするためには、ポリフォーフィズムをサポートする必要がある。例えば、以下のようなプログラムを考える。
lbdex/input/ch12_inherit.cpp
class CPolygon { // _ZTVN10__cxxabiv117__class_type_infoE for parent class protected: int width, height; public: void set_values (int a, int b) { width=a; height=b; } virtual int area (void) { return 0;}; void printarea (void) { printf("%d\n", this->area()); } }; class CRectangle: public CPolygon { public: int area (void) { return (width * height); } }; class CTriangle: public CPolygon { public: int area (void) { return (width * height / 2); } }; class CAngle: public CPolygon { public: int area (void) { return (width * height / 4); } }; int test_cpp_polymorphism() { CRectangle poly1; CTriangle poly2; CAngle poly3; CPolygon * ppoly1 = &poly1; CPolygon * ppoly2 = &poly2; CPolygon * ppoly3 = &poly3; ppoly1->set_values (4,5); ppoly2->set_values (4,5); ppoly3->set_values (4,5); ppoly1->printarea(); ppoly2->printarea(); ppoly3->printarea();
上記のC++コードでは、ベースクラスCPolygon
に対して3つの派生クラスCRectangle, CTriangle, CAngle
が定義されている。
この時、print_area()
を呼び出した場合、それぞれの派生クラスで呼び出されるarea()
メソッドが異なり、異なる結果が得られる。
C++のサポートでは、このような文法を考慮する必要がある。
./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch12_inherit.cpp -emit-llvm -o - | ./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=asm -o -
また、C++では、try - catch
のような例外動作も考慮する必要がある。
lbdex/input/ch12_eh.cpp
class Ex1 {}; void throw_exception(int a, int b) { Ex1 ex1; if (a > b) { throw ex1; } } int test_try_catch() { try { throw_exception(2, 1); } catch(...) { return 1; } return 0; }
このプログラムでは例外を処理するために、try - catch
の構文を使っている。これをLLVMでコンパイルすると、
./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch12_eh.cpp -emit-llvm -o - | ./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=asm -o -
Selecting: t4: i32,ch = load<(dereferenceable load 4 from %ir.retval)> t0, FrameIndex:i32<0>, undef:i32 Selecting: t5: i32 = Register $a0 Selecting: t0: ch = EntryToken llc: /home/msyksphinz/work/llvm/llvm-myriscvx/lib/CodeGen/LiveVariables.cpp:133: void llvm::LiveVariables::HandleVirtRegUse(unsigned int, llvm::MachineBasicBlock*, llvm::MachineInstr&): Assertion `MRI->getVRegDef(reg) && "Register use before def!"' failed. Stack dump: 0. Program arguments: ./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=asm -o - 1. Running pass 'Function Pass Manager' on module '<stdin>'. 2. Running pass 'Live Variable Analysis' on function '@_Z14test_try_catchv' ^C
llcが正しくIRを処理することができなかった。LLVM IRの逆アセンブルを生成して確認してみる。
./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch12_eh.cpp -emit-llvm -o - | ./bin/llvm-dis
; Function Attrs: noinline optnone define dso_local void @_Z15throw_exceptionii(i32 signext %a, i32 signext %b) #0 { entry: %a.addr = alloca i32, align 4 %b.addr = alloca i32, align 4 %ex1 = alloca %class.Ex1, align 1 store i32 %a, i32* %a.addr, align 4 store i32 %b, i32* %b.addr, align 4 %0 = load i32, i32* %a.addr, align 4 %1 = load i32, i32* %b.addr, align 4 %cmp = icmp sgt i32 %0, %1 br i1 %cmp, label %if.then, label %if.end if.then: ; preds = %entry %exception = call i8* @__cxa_allocate_exception(i32 1) #1 %2 = bitcast i8* %exception to %class.Ex1* call void @__cxa_throw(i8* %exception, i8* bitcast ({ i8*, i8* }* @_ZTI3Ex1 to i8*), i8* null) #2 unreachable if.end: ; preds = %entry ret void }
そこで、以下のコードを追加した。
lib/Target/MYRISCVX/MYRISCVXISelLowering.h
/// 例外アドレスが格納されるレジスタを指定する。今回はA0レジスタ unsigned getExceptionPointerRegister(const Constant *PersonalityFn) const override { return MYRISCVX::A0; } /// 例外要因が格納されるレジスタを指定する。今回はA1レジスタ unsigned getExceptionSelectorRegister(const Constant *PersonalityFn) const override { return MYRISCVX::A1; }
./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch12_eh.cpp -emit-llvm -o - | ./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=asm -o - | less
ビルドできた。test_try_catch()
を確認する。
_Z14test_try_catchv: $tmp3: .set $func_begin0, ($tmp3) .cfi_startproc .cfi_personality 0, __gxx_personality_v0 .cfi_lsda 0, $exception0 .frame $x8,24,$x1 .mask 0x00000100,-4 .set noreorder .cpload $t9 .set nomacro # %bb.0: # %entry lui x10, %hi(_gp_disp) addi x10, x10, %lo(_gp_disp) addi x2, x2, -24 .cfi_def_cfa_offset 24 sw x8, 20(x2) # 4-byte Folded Spill .cfi_offset 8, -4 move x8, x2 .cfi_def_cfa_register 8 .cprestore 8 $tmp0: lw x3, %call16(_Z15throw_exceptionii)(x3) addi x10, zero, 2 addi x11, zero, 1 jalr x3 lw x3, 8(x2) $tmp1: jal $BB1_1 $BB1_1: # %invoke.cont jal $BB1_4 $BB1_2: # %lpad $tmp2: lw x3, 8(x2) sw x30, 12(x2) sw x31, 8(x2) jal $BB1_3
スレッドローカルストレージのサポート
スレッドローカルストレージとは、C++11からサポートされているスレッド固有の変数で、スレッド毎の静的記憶領域に変数が書き込まれる。
static
に対してthread_local
キーワードの場合はスレッド固有の領域に変数が用意される。
lbdex/input/ch12_thread_var.cpp
__thread int a = 0; thread_local int b = 0; // need option -std=c++11 int test_thread_var() { a = 2; return a; } int test_thread_var_2() { b = 3; return b; }
上記をコンパイルしてみると、以下のようなエラーが発生してしまった。GlobalTLSAddress
のLLVM IRを追加する必要がありそうだ。
./bin/clang -target mips-unknown-linux-gnu -c ../lbdex/input/ch12_thread_var.cpp -emit-llvm -o - | ./bin/llc -march=myriscvx32 -relocation-model=pic -filetype=asm -o - | less
Selecting: t9: ch = MYRISCVXISD::Ret t8, Register:i32 $a0, t8:1 Selecting: t8: ch,glue = CopyToReg t5, Register:i32 $a0, t6 Selecting: t6: i32,ch = load<(dereferenceable load 4 from @a)> t5, GlobalTLSAddress:i32<i32* @a> 0, undef:i32 Selecting: t5: ch = store<(store 4 into @a)> t0, Constant:i32<2>, GlobalTLSAddress:i32<i32* @a> 0, undef:i32 Selecting: t7: i32 = Register $a0 Selecting: t2: i32 = GlobalTLSAddress<i32* @a> 0 LLVM ERROR: Cannot select: t2: i32 = GlobalTLSAddress<i32* @a> 0 In function: _Z15test_thread_varv
MYRISCVXAsmParser.cpp : evaluateRelocExpr()
MYRISCVXMCExpr.cpp : printImpl()
を改造する必要がある。追加しなければならないLLVM IRは、tlsgd, tlsldm, dtp_hi, dtp_lo, gottp, tp_hi, tp_lo
です。
lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXAsmBackend.cpp
//@getFixupKindInfo { const MCFixupKindInfo &MYRISCVXAsmBackend:: getFixupKindInfo(MCFixupKind Kind) const { const static MCFixupKindInfo Infos[MYRISCVX::NumTargetFixupKinds] = { ... { "fixup_MYRISCVX_TLSGD" , 0, 16, 0 }, { "fixup_MYRISCVX_GOTTP" , 0, 16, 0 }, { "fixup_MYRISCVX_TP_HI" , 0, 16, 0 }, { "fixup_MYRISCVX_TP_LO" , 0, 16, 0 }, { "fixup_MYRISCVX_TLSLDM" , 0, 16, 0 }, { "fixup_MYRISCVX_DTP_HI" , 0, 16, 0 }, { "fixup_MYRISCVX_DTP_LO" , 0, 16, 0 }
lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXBaseInfo.h
namespace MYRISCVXII { /// Target Operand Flag enum. enum TOF { //===------------------------------------------------------------------===// // MYRISCVX Specific MachineOperand flags. ... // MO_TLSGD : モジュールIDとTSLブロックオフセットが存在するグローバルオフセットテーブルのオフセットを示している。 // グローバル動的TLS向け MO_TLSGD, // MO_TLSLDM : モジュールIDとTSLブロックオフセットが存在するグローバルオフセットテーブルのオフセットを示している。 // ローカル動的TLS向け MO_TLSLDM, MO_DTP_HI, MO_DTP_LO, // MO_GOTTPREL : スレッドポインタのオフセットを示す // 初期動作TLS向け MO_GOTTPREL, // MO_TPREL_HI/LO : スレッドポインタからのオフセットのHIとLO領域を示す。 // ローカル動作TLS向け MO_TP_HI, MO_TP_LO, }; // enum TOF {
さらに、target descriptionファイルに以下を追加します。
// TlsGd node is used to handle General Dynamic TLS def MYRISCVXTlsGd : SDNode<"MYRISCVXISD::TlsGd", SDTIntUnaryOp>; // TpHi and TpLo nodes are used to handle Local Exec TLS def MYRISCVXTpHi : SDNode<"MYRISCVXISD::TpHi", SDTIntUnaryOp>; def MYRISCVXTpLo : SDNode<"MYRISCVXISD::TpLo", SDTIntUnaryOp>; def : Pat<(MYRISCVXHi tglobaltlsaddr:$in), (LUi tglobaltlsaddr:$in)>; def : Pat<(MYRISCVXLo tglobaltlsaddr:$in), (ORi ZERO, tglobaltlsaddr:$in)>; def : Pat<(add CPURegs:$hi, (MYRISCVXLo tglobaltlsaddr:$lo)), (ORi CPURegs:$hi, tglobaltlsaddr:$lo)>; def : WrapperPat<tglobaltlsaddr, ORi, CPURegs>;
これで最終的にスレッドローカルストレージを含むソースコードがコンパイルできるようになった。LLVMがスレッドローカルストレージのアドレスを生成する手順を以下にまとめた。
IR:load i32* @a, align 4;
→(add Cpu0ISD::Hi Cpu0ISD::Lo);
→ori $2, $zero, %tp_lo(a); lui $3, %tp_hi(a); addu $3, $3, $2;
IR: ret i32* @b;
→%0=(add Cpu0ISD::Hi Cpu0ISD::Lo);...
→ori $2, $zero, %tp_lo(a); lui $3, %tp_hi(a); addu $3, $3, $2;