FPGA開発日記

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

オリジナルLLVMバックエンド実装をまとめる(10. オブジェクトファイルの生成について)

オブジェクトファイルの生成

llcはアセンブリファイルを出力するだけでなく、オブジェクトファイルを出力する機能もある。オブジェクトファイルを出力するためには、llcのオプションで-filetype=asmの代わりに-filetype=objを指定する。

$ ./bin/llc -debug -march=myriscvx32 -filetype=obj simple_main.bc -o -
...
./bin/llc: warning: target does not support generation of this file type!

オブジェクトファイルを出力するためには、第xxx章で実装したLLVMInitializeMYRISCVXTargetMCにさらに機能を追加する。これらの機能は、MYRISCVXMCTargetDescでTargetMCとして登録する。登録したのは以下の4つの機能だ。

  • llvm-myriscvx80/lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXMCTargetDesc.cpp
@@ -109,6 +133,17 @@ extern "C" void LLVMInitializeMYRISCVXTargetMC() {
     // Register the MCInstPrinter.
     TargetRegistry::RegisterMCInstPrinter(*T,
                                              createMYRISCVXMCInstPrinter);
+    // Register the elf streamer.
+    TargetRegistry::RegisterELFStreamer(*T, createMCStreamer);
+
+    // Register the asm target streamer.
+    TargetRegistry::RegisterAsmTargetStreamer(*T, createMYRISCVXAsmTargetStreamer);
+
+    // Register the MC Code Emitter
+    TargetRegistry::RegisterMCCodeEmitter(*T, createMYRISCVXMCCodeEmitter);
+
+    // Register the asm backend.
+    TargetRegistry::RegisterMCAsmBackend(*T, createMYRISCVXAsmBackend);
   }

 }
  • RegisterELFStreamer
  • RegisterAsmTargetStreamer
  • RegisterMCCodeEmitter
  • RegisterMCAsmBackend

RegisterELFStreamer

commit:5b32e6b87bc Add MCStreamer / MYRISCVXAsmTargetStreamer

RegisterELFStreamerはオブジェクトファイル出力のためのStreamerだ。

extern "C" void LLVMInitializeMYRISCVXTargetMC() {
  for (Target *T : {&getTheMYRISCVX32Target(), &getTheMYRISCVX64Target()}) {
...
    // Register the elf streamer.
    TargetRegistry::RegisterELFStreamer(*T, createMCStreamer);
...
  }
}    
// 上記のcreateMCStreamerの実体は同じファイルに定義されている。
static MCStreamer *createMCStreamer(const Triple &T, MCContext &Context,
                                    std::unique_ptr<MCAsmBackend> &&MAB,
                                    std::unique_ptr<MCObjectWriter> &&OW,
                                    std::unique_ptr<MCCodeEmitter> &&Emitter,
                                    bool RelaxAll) {
  return createELFStreamer(Context, std::move(MAB), std::move(OW),
                           std::move(Emitter), RelaxAll);
}
  • llvm-myriscvx80/lib/MC/MCELFStreamer.cpp
MCStreamer *llvm::createELFStreamer(MCContext &Context,
                                    std::unique_ptr<MCAsmBackend> &&MAB,
                                    std::unique_ptr<MCObjectWriter> &&OW,
                                    std::unique_ptr<MCCodeEmitter> &&CE,
                                    bool RelaxAll) {
  MCELFStreamer *S =
      new MCELFStreamer(Context, std::move(MAB), std::move(OW), std::move(CE));
  if (RelaxAll)
    S->getAssembler().setRelaxAll(true);
  return S;
}
  • llvm-myriscvx80/lib/MC/MCELFStreamer.cpp
MCELFStreamer::MCELFStreamer(MCContext &Context,
                             std::unique_ptr<MCAsmBackend> TAB,
                             std::unique_ptr<MCObjectWriter> OW,
                             std::unique_ptr<MCCodeEmitter> Emitter)
    : MCObjectStreamer(Context, std::move(TAB), std::move(OW),
                       std::move(Emitter)) {}

最終的にはMCObjectStreamerというクラスに到達する。

  • llvm-myriscvx80/include/llvm/MC/MCObjectStreamer.h
/// Streaming object file generation interface.
///
/// This class provides an implementation of the MCStreamer interface which is
/// suitable for use with the assembler backend. Specific object file formats
/// are expected to subclass this interface to implement directives specific
/// to that file format or custom semantics expected by the object writer
/// implementation.
class MCObjectStreamer : public MCStreamer {
  std::unique_ptr<MCAssembler> Assembler;
  MCSection::iterator CurInsertionPoint;
  bool EmitEHFrame;
  bool EmitDebugFrame;
  SmallVector<MCSymbol *, 2> PendingLabels;
...

TargetRegistry::RegisterELFStreamer()を呼び出した時点では、createMCStreamerは関数ポインタが登録されるだけで、引数は何もつけられていない。 この時点では、関数ポインタのみを登録して必要な時に引数を設定して呼び出すのだ。では、具体的にどのような引数が付けられるかというと

  • MCAsmBackend : バックエンドに関する基本的な情報
  • MCObjectWriter : オブジェクトの出力を行うためのWriter
  • MCCodeEmitter : コードつまり命令をエンコーディングするための情報

を引数として受け取ります。MCObjectStreamerが持つMCAssemblerがこれらの情報を利用して命令を出力する。

f:id:msyksphinz:20190926013825p:plain
createMCStreamer

RegisterAsmTargetStreamer

RegisterAsmTargetStreamerは、アセンブリ命令出力のためのStreamerだ。

  • llvm-myriscvx80/lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXMCTargetDesc.cpp
extern "C" void LLVMInitializeMYRISCVXTargetMC() {
  for (Target *T : {&getTheMYRISCVX32Target(), &getTheMYRISCVX64Target()}) {
...
    // Register the asm target streamer.
    TargetRegistry::RegisterAsmTargetStreamer(*T, createMYRISCVXAsmTargetStreamer);
...
  }
}    

// 上記のcreateMYRISCVXAsmTargetStreamerは同じファイル内の以下のコード。MYRISCVXTargetAsmStreamerを生成する。
static MCTargetStreamer *createMYRISCVXAsmTargetStreamer(MCStreamer &S,
                                                         formatted_raw_ostream &OS,
                                                         MCInstPrinter *InstPrint,
                                                         bool isVerboseAsm) {
  return new MYRISCVXTargetAsmStreamer(S, OS);
}
  • llvm-myriscvx80/lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXTargetStreamer.cpp
// MYRISCVXTargetAsmStreamerは以下の2つのコンストラクタを経由してMCTargetStreamerが生成される。
MYRISCVXTargetStreamer::MYRISCVXTargetStreamer(MCStreamer &S)
    : MCTargetStreamer(S) {
}

MYRISCVXTargetAsmStreamer::MYRISCVXTargetAsmStreamer(MCStreamer &S,
                                                     formatted_raw_ostream &OS)
    : MYRISCVXTargetStreamer(S), OS(OS) {}

MCTargetStreamerをベースとするわけだが、アセンブリファイルの出力に必要なプリント処理を行う。createMYRISCVXAsmTargetStreamer()ではMCInstPrintを引数に取り、命令の出力を行う。

f:id:msyksphinz:20190926013850p:plain
createMYRISCVXAsmTargetStreamer

RegisterMCCodeEmitter

commit:3815fb8ce30 Implement MCCodeEmitter

  • llvm-myriscvx80/lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXMCCodeEmitter.cpp
MCCodeEmitter *createMYRISCVXMCCodeEmitter(const MCInstrInfo &MCII,
                                           const MCRegisterInfo &MRI,
                                           MCContext &Ctx) {
  return new MYRISCVXMCCodeEmitter(MCII, Ctx, true);
}
  • llvm-myriscvx80/lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXMCCodeEmitter.h
  MYRISCVXMCCodeEmitter(const MCInstrInfo &mcii, MCContext &Ctx_, bool IsLittle)
      : MCII(mcii), Ctx(Ctx_), IsLittleEndian(IsLittle) {}

実体は、MCCodeEmitterというクラスを継承している。MCCodeEmitterは、命令をエンコーディングするためのクラスだ。

  • llvm-myriscvx80/lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXMCCodeEmitter.h
class MYRISCVXMCCodeEmitter : public MCCodeEmitter {
  MYRISCVXMCCodeEmitter(const MYRISCVXMCCodeEmitter &) = delete;
  void operator=(const MYRISCVXMCCodeEmitter &) = delete;
  const MCInstrInfo &MCII;
  MCContext &Ctx;
  bool IsLittleEndian;

...
  • llvm-myriscvx80/include/llvm/MC/MCCodeEmitter.h
/// MCCodeEmitter - Generic instruction encoding interface.
class MCCodeEmitter {
protected: // Can only create subclasses.
  MCCodeEmitter();
...

createMYRISCVXMCCodeEmitter()ではMCInstrInfoMCRegisterInfoクラスを引数に取っている。実際にはMCRegisterInfoは使用しないのだが、MCInstrInfoクラスの情報を用いて命令の生成を行う。

f:id:msyksphinz:20190926013940p:plain
createMYRISCVXMCCodeEmitter

RegisterMCAsmBackend

commit:53b739059f9 Implement MYRISCVXAsmBackend

  • llvm-myriscvx80/lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXMCTargetDesc.cpp
    // Register the asm backend.
    TargetRegistry::RegisterMCAsmBackend(*T, createMYRISCVXAsmBackend);

MYRISCVXMCAsmBackendくらすは、MCAsmBackendを継承しているクラスだ。

  • llvm-myriscvx80/lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXAsmBackend.cpp
// MCAsmBackend
MCAsmBackend *llvm::createMYRISCVXAsmBackend(const Target &T,
                                             const MCSubtargetInfo &STI,
                                             const MCRegisterInfo &MRI,
                                             const MCTargetOptions &Options) {
  return new MYRISCVXAsmBackend(T, MRI, STI.getTargetTriple(), STI.getCPU());
}
  • llvm-myriscvx80/lib/Target/MYRISCVX/MCTargetDesc/MYRISCVXAsmBackend.h
    MYRISCVXAsmBackend(const Target &T,
                       const MCRegisterInfo &MRI,
                       const Triple &TT,
                       StringRef CPU)
        : MCAsmBackend(TT.isLittleEndian() ? support::little : support::big),
          TheTriple(TT) {}
  • llvm-myriscvx80/include/llvm/MC/MCAsmBackend.h
/// Generic interface to target specific assembler backends.
class MCAsmBackend {
  std::unique_ptr<MCCodePadder> CodePadder;

...

このMCAsmBackendは抽象的なクラスで、いくつかのメソッドは実装されていないので継承したMYRISCVXAsmBackend側で実装しなければならない。具体的には、以下のメソッドをMYRISCVXAsmBackendで実装する必要がある。

  createObjectTargetWriter() const = 0;
  /// Get the number of target specific fixup kinds.
  virtual unsigned getNumFixupKinds() const = 0;
  /// Apply the \p Value for given \p Fixup into the provided data fragment, at
  /// the offset specified by the fixup and following the fixup kind as
  /// appropriate. Errors (such as an out of range fixup value) should be
  /// reported via \p Ctx.
  /// The  \p STI is present only for fragments of type MCRelaxableFragment and
  /// MCDataFragment with hasInstructions() == true.
  virtual void applyFixup(const MCAssembler &Asm, const MCFixup &Fixup,
                          const MCValue &Target, MutableArrayRef<char> Data,
                          uint64_t Value, bool IsResolved,
                          const MCSubtargetInfo *STI) const = 0;
  /// Check whether the given instruction may need relaxation.
  ///
  /// \param Inst - The instruction to test.
  /// \param STI - The MCSubtargetInfo in effect when the instruction was
  /// encoded.
  virtual bool mayNeedRelaxation(const MCInst &Inst,
                                 const MCSubtargetInfo &STI) const = 0;
  /// Simple predicate for targets where !Resolved implies requiring relaxation
  virtual bool fixupNeedsRelaxation(const MCFixup &Fixup, uint64_t Value,
                                    const MCRelaxableFragment *DF,
                                    const MCAsmLayout &Layout) const = 0;
  /// Relax the instruction in the given fragment to the next wider instruction.
  ///
  /// \param Inst The instruction to relax, which may be the same as the
  /// output.
  /// \param STI the subtarget information for the associated instruction.
  /// \param [out] Res On return, the relaxed instruction.
  virtual void relaxInstruction(const MCInst &Inst, const MCSubtargetInfo &STI,
                                MCInst &Res) const = 0;
  /// Write an (optimal) nop sequence of Count bytes to the given output. If the
  /// target cannot generate such a sequence, it should return an error.
  ///
  /// \return - True on success.
  virtual bool writeNopData(raw_ostream &OS, uint64_t Count) const = 0;

それぞれ、MYRISCVXAsmBackend.h, MYRISCVXAsmBackend.cppに実体を記述する。命令のRelaxなどに関する処理は、今回は実装していない。

f:id:msyksphinz:20190926014021p:plain
MYRISCVXAsmBackend