非常にシンプルな関数をどのようにして命令に変換するかについて考える。
int main() { return 0; }
./bin/clang -c -O2 ../myriscvx-tests/tests/simple_main.cpp -emit-llvm ./bin/llvm-dis simple_main.bc -o -
define dso_local i32 @main() local_unnamed_addr #0 { entry: ret i32 0 }
ここで共通の処理として登場するのは、関数の呼び出し時の処理と、関数から戻るときの処理だ。
関数の呼び出し処理とは、つまり引数の処理だ。関数コールを行った側から引数を正しく受け取り、関数内で使えるようにする。このための処理はLowerFormalArguments()
に実装する。
一方で、関数から戻る処理は、戻り値を所定の場所に格納し、戻り場所までジャンプする。このための処理はLowerReturn
に実装する(図[fig:LowerFormalArguments/LowerReturn])
//@LowerFormalArguments { /// LowerFormalArguments - transform physical registers into virtual registers /// and generate load operations for arguments places on the stack. SDValue MYRISCVXTargetLowering::LowerFormalArguments(SDValue Chain, CallingConv::ID CallConv, bool IsVarArg, const SmallVectorImpl<ISD::InputArg> &Ins, const SDLoc &DL, SelectionDAG &DAG, SmallVectorImpl<SDValue> &InVals) const { return Chain; } // @LowerFormalArguments } //===----------------------------------------------------------------------===// //@ Return Value Calling Convention Implementation //===----------------------------------------------------------------------===// SDValue MYRISCVXTargetLowering::LowerReturn(SDValue Chain, CallingConv::ID CallConv, bool IsVarArg, const SmallVectorImpl<ISD::OutputArg> &Outs, const SmallVectorImpl<SDValue> &OutVals, const SDLoc &DL, SelectionDAG &DAG) const { return DAG.getNode(MYRISCVXISD::Ret, DL, MVT::Other, Chain, DAG.getRegister(MYRISCVX::RA, Subtarget.getXLenVT())); }
とりあえず空っぽだ。LowerFormalArguments
に関しては何もせず、LowerReturn
に関しては、Ret
ノード命令を使ってSelectionDAGへと変換していることが分かる。
つまり、LLVM IRのretが呼び出されると、このLowerReturn()
が実行され、MYRISCVXISD::Ret
ノードを生成する(MYRISCVXISD::Ret
はMYRISCVXInstrInfo.td
で定義したノードだ)。
このMYRISCVXISD::Ret
をどうするかというと、MYRISCVXInstrInfo.td
に以下のルールを追加してRetRA
ノードに変換する。
llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
let isReturn=1, isTerminator=1, hasDelaySlot=0, isBarrier=1, hasCtrlDep=1 in def RetRA : MYRISCVXPseudo<(outs), (ins), "", [(MYRISCVXRet)]>;
MYRISCVXPseudo
はその名の通り、具体的な命令を生成せずに、等価なノードを生成するだけだ。
この等価なRetRA
ノードをどうするかというと、さらにLLVMにあらかじめ組み込まれているexpandPostRAPsudo()
というメソッドを拡張して、RetRA
ノードに遭遇した場合は等価なret命令(つまりJALR x0, ra, 0
)に変換するという訳だ。
llvm-myriscvx80/include/llvm/CodeGen/TargetInstrInfo.h
/// This function is called for all pseudo instructions /// that remain after register allocation. Many pseudo instructions are /// created to help register allocation. This is the place to convert them /// into real instructions. The target can edit MI in place, or it can insert /// new instructions and erase MI. The function should return true if /// anything was changed. virtual bool expandPostRAPseudo(MachineInstr &MI) const { return false; }
llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXInstrInfo.cpp
//@expandPostRAPseudo /// Expand Pseudo instructions into real backend instructions bool MYRISCVXInstrInfo::expandPostRAPseudo(MachineInstr &MI) const { //@expandPostRAPseudo-body MachineBasicBlock &MBB = *MI.getParent(); switch (MI.getDesc().getOpcode()) { default: return false; case MYRISCVX::RetRA: expandRetRA(MBB, MI); break; } MBB.erase(MI); return true; } void MYRISCVXInstrInfo::expandRetRA(MachineBasicBlock &MBB, MachineBasicBlock::iterator I) const { BuildMI(MBB, I, I->getDebugLoc(), get(MYRISCVX::JALR)) .addReg(MYRISCVX::ZERO).addReg(MYRISCVX::RA).addImm(0); }
これで、以下の命令列が生成されるようになる(Calling Conventionを実装していないのでまだ戻り値を生成できていない)。
main: .cfi_startproc .frame $x2,0,$x1 .mask 0x00000000,0 .set noreorder .set nomacro discovered a new reachable node %bb.0 # %bb.0: # %entry ret