MYRISCVXAsmPrinter
はLLVM IR変換された命令の出力を司る。最終的な詳細の出力は後述するMYRISCVXInstPrinter
クラスが実行するのだが、AsmPrinter
はより高位なアセンブリ出力用のフレームワークとしての役割を果たす。
//@EmitInstruction { //- EmitInstruction() must exists or will have run time error. void MYRISCVXAsmPrinter::EmitInstruction(const MachineInstr *MI) { ... do { if (I->isPseudo()) llvm_unreachable("Pseudo opcode found in EmitInstruction()"); MCInst TmpInst0; MCInstLowering.Lower(&*I, TmpInst0); OutStreamer->EmitInstruction(TmpInst0, getSubtargetInfo()); } while ((++I != E) && I->isInsideBundle()); // Delay slot check } //@EmitInstruction }
printSavedRegsBitmask()
はサポート用の関数だ。関数呼び出しの際などに、どのレジスタを関数側が保存する役割を持っているのか(Callee Saved Registers)をアセンブリファイルに出力する役割を持っている。
void MYRISCVXAsmPrinter::printSavedRegsBitmask(raw_ostream &O) { // CPU and FPU Saved Registers Bitmasks unsigned CPUBitmask = 0; int CPUTopSavedRegOff; ... // Set CPU Bitmask. for (; i != e; ++i) { unsigned Reg = CSI[i].getReg(); unsigned RegNum = TRI->getEncodingValue(Reg); CPUBitmask |= (1 << RegNum); } ... // Print CPUBitmask O << "\t.mask \t"; printHex32(CPUBitmask, O); O << ',' << CPUTopSavedRegOff << '\n'; }
その他にもいろいろ定義すが、すべてアセンブリ出力時のサポート関数だ。
InstPrinterの追加
commit:cd6a10b5d2c] Add MYRISCVX/InstPrinter
命令のプリント、つまりアセンブリ命令をファイルに出力する処理は、基本的にMYRISCVXInstrInfo.td
に記述した命令の定義に基づいて生成される。
LLVMではPrintInstruction()
というメソッドがその役割を担う。
このメソッドはMYRISCVXInstPrinter
クラスに記述されている。
したがって、PrintInstruction()
に関連するメソッドはすべてMYRISCVXInstPrinter
に記述することになる。
build-myriscvx80/lib/Target/MYRISCVX/MYRISCVXGenAsmWriter.inc
/// printInstruction - This method is automatically generated by tablegen /// from the instruction set description. void MYRISCVXInstPrinter::printInstruction(const MCInst *MI, raw_ostream &O) { static const char AsmStrs[] = { ... }
ここでサポートメソッドとして追加しなければならないことは、以下の2つだ。
printOperand()
: オペランドを出力する。printMemOperand()
: メモリオペランドを出力する。メモリオペランドはimm(reg)
の形式で出力するので、printOperand()
を2回呼び出している。llvm-myriscvx80/lib/Target/MYRISCVX/InstPrinter/MYRISCVXInstPrinter.cpp
void MYRISCVXInstPrinter::printOperand(const MCInst *MI, unsigned OpNo, raw_ostream &O) { const MCOperand &Op = MI->getOperand(OpNo); if (Op.isReg()) { printRegName(O, Op.getReg()); return; } if (Op.isImm()) { O << Op.getImm(); return; } assert(Op.isExpr() && "unknown operand kind in printOperand"); Op.getExpr()->print(O, &MAI, true); } void MYRISCVXInstPrinter:: printMemOperand(const MCInst *MI, int opNum, raw_ostream &O) { // Load/Store memory operands -- imm($reg) // If PIC target the target is loaded as the // pattern ld $t9,%call16($gp) printOperand(MI, opNum+1, O); O << "("; printOperand(MI, opNum, O); O << ")"; }
このprintInstruction()
などの命令プリントメソッドとMYRISCVXTargetMachine
との関連付けは、MCTargetDesc/MYRISCVXMCTargetDesc.cpp
で行われている。
実際には、LLVM IRから命令が変換され命令が出力できる状態になると、MYRISCVXAsmPrinter::EmitInstruction()
が呼ばれ、これがOutStreamer.EmitInstruction()
を呼び出す。
このルーチンがオペコードやレジスタ名などをデコードし、これらの情報をMYRISCVXGenInstrInfo.inc
とMYRISCVXGenRegisterInfo.inc
から呼び出してくるわけだ。
llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXAsmPrinter.cpp
//@EmitInstruction { //- EmitInstruction() must exists or will have run time error. void MYRISCVXAsmPrinter::EmitInstruction(const MachineInstr *MI) { //@EmitInstruction body { if (MI->isDebugValue()) { SmallString<128> Str; raw_svector_ostream OS(Str); PrintDebugValueComment(MI, OS); return; } //@print out instruction: // Print out both ordinary instruction and boudle instruction MachineBasicBlock::const_instr_iterator I = MI->getIterator(); MachineBasicBlock::const_instr_iterator E = MI->getParent()->instr_end(); do { if (I->isPseudo()) llvm_unreachable("Pseudo opcode found in EmitInstruction()"); MCInst TmpInst0; MCInstLowering.Lower(&*I, TmpInst0); OutStreamer->EmitInstruction(TmpInst0, getSubtargetInfo()); } while ((++I != E) && I->isInsideBundle()); // Delay slot check } //@EmitInstruction }
llvm-myriscvx80/lib/MC/MCObjectStreamer.cpp
void MCObjectStreamer::EmitInstruction(const MCInst &Inst, const MCSubtargetInfo &STI, bool) { getAssembler().getBackend().handleCodePaddingInstructionBegin(Inst); EmitInstructionImpl(Inst, STI); getAssembler().getBackend().handleCodePaddingInstructionEnd(Inst); }
では、LLVM IRから命令が変換され、実際にアセンブリ命令が出力されるまでの流れを追ってみる。
まず、MCAsmStreamer
内のEmitInstruction()
が呼び出されると、命令が出力される準備が整う。
EmitInstruction()
内でInstPrinter.printInst()
が呼び出され、そこからターゲット固有の命令出力プロセスが流れ始める。
void MCAsmStreamer::EmitInstruction(const MCInst &Inst, const MCSubtargetInfo &STI, bool PrintSchedInfo) { ... if(getTargetStreamer()) getTargetStreamer()->prettyPrintAsm(*InstPrinter, OS, Inst, STI); else InstPrinter->printInst(&Inst, OS, "", STI); // printInstが呼び出される。 ...
まず、MYRISCVXInstPrinter
クラスは大きく分けて2つのファイルに記述してあるところから説明する。
MYRISCVXInstPrinter
クラスは、ソースコードを我々が記述するMYRISCVXInstPrinter.cpp
と、MYRISCVXInstrInfo.td
から自動生成されるMYRISCVXGenAsmWriter.cpp
に分類される。
それぞれ、
llvm-myriscvx80/lib/Target/MYRISCVX/InstrPrinter/MYRISCVXInstrPrinter.cpp
build-myriscvx80/lib/Target/MYRISCVX/MYRISCVXGenAsmWriter.inc
に格納されている。
MYRISCVXInstrPrinter.cpp
内のprintInst
はMYRISCVXGenAsmWriter.inc
に生成されてあるprintInstruction()
を呼び出す。
printInstruction()
内では、命令のフィールドのデコードによっていくつかの共通ルーチンが呼び出される。
例えば、レジスタオペランドの表記であればprintOperand()
が呼び出され、即値に関してはprintUnsigendImm
が呼び出される。
これらは、MYRISCVXInstPrinter.cpp
側で用意しておく必要がある。ハンドコーディングするprintOpreand()
も、場合によってはprintRegName()
が使用するgetRegisterName()
などのMYRISCVXGenAsmWriter.inc
で定義されている自動生成されたルーチンを使用する必要がある。
これらを上手く組み合わせて、命令を生成する。