LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。 簡単なReturn IRが変換できるように実装を進めていこう。
実装するのに見ている関数群は以下のようなものだが、まず関数の意味が分からない、というところから始めないといけない。
- LowerFormalArguments(): 入力引数を処理するためのフック。
- LowerReturn() : return値を処理するためのフック。値をreturn用レジスタ(RISC-Vの場合はA0)に挿入する。
CCValAssign : 戻り値が戻り値用のレジスタに格納されたことを表現する。
SmallVector<CCValAssign, 16> RVLocs;
analyzeReturnにより戻り値の解析を行う、らしい。
// Analyze return values.
MYRISCVXCCInfo.analyzeReturn(Outs, Subtarget.abiUsesSoftFloat(),
MF.getFunction().getReturnType());
実体はこちら。
template<typename Ty> void MYRISCVXTargetLowering::MYRISCVXCC:: analyzeReturn(const SmallVectorImpl<Ty> &RetVals, bool IsSoftFloat, const SDNode *CallNode, const Type *RetTy) const { CCAssignFn *Fn; Fn = RetCC_MYRISCVX; for (unsigned I = 0, E = RetVals.size(); I < E; ++I) { MVT VT = RetVals[I].VT; ISD::ArgFlagsTy Flags = RetVals[I].Flags; MVT RegVT = this->getRegVT(VT, RetTy, CallNode, IsSoftFloat); if (Fn(I, VT, RegVT, CCValAssign::Full, Flags, this->CCInfo)) { #ifndef NDEBUG dbgs() << "Call result #" << I << " has unhandled type " << EVT(VT).getEVTString() << '\n'; #endif llvm_unreachable(nullptr); } } }
もう単語の意味から型の意味まで全く分からないのだが、
- MVT = Machine Value Type : 型の種類のようなもの。
if (Fn(I, VT, RegVT, CCValAssign::Full, Flags, this->CCInfo)) {
の意味しているところは、つまるところRetCC_MYRISCVX(I, VT, RegVT, CCValAssign::Full, Flags, this->CCInfo)
を呼び出して、
その型にマッチしていなければFalseを返すということだ。これでReturnする値の型を判定している?
// Copy the result values into the output registers. for (unsigned i = 0; i != RVLocs.size(); ++i) { SDValue Val = OutVals[i]; CCValAssign &VA = RVLocs[i]; assert(VA.isRegLoc() && "Can only return in registers!"); if (RVLocs[i].getValVT() != RVLocs[i].getLocVT()) Val = DAG.getNode(ISD::BITCAST, DL, RVLocs[i].getLocVT(), Val); Chain = DAG.getCopyToReg(Chain, DL, VA.getLocReg(), Val, Flag); // Guarantee that all emitted copies are stuck together with flags. Flag = Chain.getValue(1); RetOps.push_back(DAG.getRegister(VA.getLocReg(), VA.getLocVT())); }
SmallVectorのサイズがなぜ16なのかは分からない。おそらく16個以上の戻り値を返そうとすると(C言語では不可能だがほかの言語だと可能かもしれない)、 エラーを吐いてしまうのではなかろうか。その辺りの実装はどのようになっているのかはよく分からない。 forループの中にprintf()を入れて確認してみるとこのループは1度しか実行されていないようだった。
チュートリアル通り、-O2で最適化したIRで生成すると、A0に戻り値を渡してからRetが実行されているのが分かる。 -O2無しで生成したIRでは、まだクラッシュしてしまう。
./bin/clang -O2 -c -target mips-unknown-linux-gnu ../lbdex/input/ch3.cpp -emit-llvm -o ch3.myriscvx_o2.bc ./bin/llc -march=myriscvx -relocation-model=pic -filetype=asm ch3.myriscvx_o2.bc -o - ... # %bb.0: # %entry addiu $x10, $x0, 0 ret $x1
Small Immediateの挿入
MYRISCVXInstrInfo.tdに以下のパターンを追加しており、これで"return val(val は即値)" のA0レジスタへの代入処理が実行できるようになっている。
//===----------------------------------------------------------------------===// // Arbitrary patterns that map to one or more instructions //===----------------------------------------------------------------------===// // Small immediates def : Pat<(i32 immSExt16:$in), (ADDiu ZERO, imm:$in)>;