FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

RISC-V 64-bit LLVM Backendを試す (7. AsmPrinterの実装)

LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。 前回までで独自アーキテクチャのオプション(MYRISCVX)をllcに表示できるようになった。 しかしまだいろいろ追加していないので、アサーションで落ちるままだ。このあたりをCpu0の実装を見ながら追加していく。

./bin/llc -march=myriscvx -relocation-model=pic -filetype=asm ch3.riscv64.bc

llc: /home/msyksphinz/work/riscv/llvm-riscv-msyksphinz/lib/CodeGen/LLVMTargetMachine.cpp:60: void llvm::LLVMTargetMachine::initAsmInfo(): Assertion `TmpAsmInfo && "MCAsmInfo not initialized. " "Make sure you include the correct TargetSelect.h" "and that InitializeAllTargetMCs() is being invoked!"' failed.
Stack dump:
0.      Program arguments: ./bin/llc -march=myriscvx -relocation-model=pic -filetype=asm ch3.riscv64.bc
#0 0x00007f7786f6e6b3 llvm::sys::PrintStackTrace(llvm::raw_ostream&) /home/msyksphinz/work/riscv/llvm-riscv-msyksphinz/lib/Support/Unix/Signals.inc:490:0
#1 0x00007f7786f6e746 PrintStackTraceSignalHandler(void*) /home/msyksphinz/work/riscv/llvm-riscv-msyksphinz/lib/Support/Unix/Signals.inc:554:0

アサーションのFailを解決するためには、MCAsmInfoを追加する必要があるらしい。 資料を見ながら追加していく。

  • lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXMCAsmInfo.h これは既存のクラスを継承しただけだ。
namespace llvm {
  class Triple;

  class MYRISCVXMCAsmInfo : public MCAsmInfoELF {
    void anchor() override;
  public:
    explicit MYRISCVXMCAsmInfo(const Triple &TheTriple);
  };

} // namespace llvm
  • lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXMCAsmInfo.cpp さらに幾つかのRISC-V用のディレクティブを追加していく。
MYRISCVXMCAsmInfo::MYRISCVXMCAsmInfo(const Triple &TheTriple) {
  if ((TheTriple.getArch() == Triple::myriscvx))
    IsLittleEndian = false; // the default of IsLittleEndian is true

  AlignmentIsInBytes          = false;
  Data16bitsDirective         = "\t.2byte\t";
  Data32bitsDirective         = "\t.4byte\t";
  Data64bitsDirective         = "\t.8byte\t";
  PrivateGlobalPrefix         = "$";
// PrivateLabelPrefix: display $BB for the labels of basic block
  PrivateLabelPrefix          = "$";
  CommentString               = "#";
  ZeroDirective               = "\t.space\t";
  GPRel32Directive            = "\t.gpword\t";
  GPRel64Directive            = "\t.gpdword\t";
  WeakRefDirective            = "\t.weak\t";
  UseAssignmentForEHBegin = true;

  SupportsDebugInformation = true;
  ExceptionsType = ExceptionHandling::DwarfCFI;
  DwarfRegNumForCFI = true;
}

次に、InstPrinterを追加していく。 lib/Target/MYRISCVX/に、InstPrinterディレクトリを追加した。

InstPrinter
├── CMakeLists.txt
├── LLVMBuild.txt
├── MYRISCVXInstPrinter.cpp
└── MYRISCVXInstPrinter.h

InsntPrinterクラスで追加しているのは以下のメソッドになるが、基本的にその名の示す通り命令のプリントを担当しているクラスになる。

  • void printInstruction(const MCInst *MI, raw_ostream &O); : TLBGENで自動的に生成される。
  • static const char *getRegisterName(unsigned RegNo); : レジスタの名前を取得する。tdで定義された名前を返す。
  • void printRegName(raw_ostream &OS, unsigned RegNo) : レジスタ名をプリントする。
  • printInst(const MCInst *MI, raw_ostream &O, StringRef Annot,... 命令をプリントする。printInstructionを呼んでいる。
  • printAliasInstr(const MCInst *MI, raw_ostream &OS) : エイリアス付きの命令をプリントする?
  • printOperand(const MCInst *MI, unsigned OpNo, raw_ostream &O); オペランドのプリント。とりあえずレジスタ名の時と、即値の時で条件が分かれる。
  • void printUnsignedImm(const MCInst *MI, int opNum, raw_ostream &O); 即値のプリント
  • void printMemOperand(const MCInst *MI, int opNum, raw_ostream &O); メモリのオペランドのプリント。0x10(sp)みたいなやつ。
    • この関数は、MYRISCVXInstrInfo.tdで以下のように定義されているのでメモリ操作の時に起動される。
// Address operand
def mem : Operand<i32> {
  let PrintMethod = "printMemOperand";
  let MIOperandInfo = (ops CPURegs, simm16);
  let EncoderMethod = "getMemEncoding";
}
  • void printMemOperandEA(const MCInst *MI, int opNum, raw_ostream &O); メモリのオペランドの印刷。これはいつ使われる?

つぎに、MYRISCVXMCInstLowerを追加して、DAGを生成するクラスを作る。

  • lib/Target/MYRISCVX/MYRISCVXInstLower.h
...
    void Lower(const MachineInstr *MI, MCInst &OutMI) const;
    MCOperand LowerOperand(const MachineOperand& MO, unsigned offset = 0) const;
...

オペランドの種類によってオブジェクトを生成するらしい。

//@LowerOperand {
MCOperand MYRISCVXMCInstLower::LowerOperand(const MachineOperand& MO,
                                        unsigned offset) const {
  MachineOperandType MOTy = MO.getType();

  switch (MOTy) {
  //@2
  default: llvm_unreachable("unknown operand type");
  case MachineOperand::MO_Register:
    // Ignore all implicit register operands.
    if (MO.isImplicit()) break;
    return MCOperand::createReg(MO.getReg());
  case MachineOperand::MO_Immediate:
    return MCOperand::createImm(MO.getImm() + offset);
  case MachineOperand::MO_RegisterMask:
    break;
 }

  return MCOperand();
}

void MYRISCVXMCInstLower::Lower(const MachineInstr *MI, MCInst &OutMI) const {
  OutMI.setOpcode(MI->getOpcode());

  for (unsigned i = 0, e = MI->getNumOperands(); i != e; ++i) {
    const MachineOperand &MO = MI->getOperand(i);
    MCOperand MCOp = LowerOperand(MO);

    if (MCOp.isValid())
      OutMI.addOperand(MCOp);
  }
}

ビルド試行中だが、今日はここまでで時間切れ。。。

ちなみに、tlbgenでどのようなファイルが生成されるかは、この図である程度まとめてある。

https://jonathan2251.github.io/lbd/_images/dyn_reg.png