FPGA開発日記

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

RISC-V 64-bit LLVM Backendを試す (5. 基本的なファイルの追加とビルド)

LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。 まだ現時点ではビルドにすら成功していないが、徐々に修正しながら必要なものを追加してビルドしている。

ビルドを通すためには、基本的なファイルを追加しする必要がある。少しずつファイルを追加しながらビルドを進めていく。

  • 命令追加のためのファイル群:
    • MYRISCVXInstrFormats.td
    • MYRISCVXInstrInfo.cpp
    • MYRISCVXInstrInfo.h
    • MYRISCVXInstrInfo.td
    • MYRISCVXSEInstrInfo.cpp
    • MYRISCVXSEInstrInfo.h
  • レジスタ追加のためのファイル群:
    • MYRISCVXRegisterInfo.cpp
    • MYRISCVXRegisterInfo.h
    • MYRISCVXRegisterInfo.td
    • MYRISCVXSERegisterInfo.cpp
    • MYRISCVXSERegisterInfo.h
  • 関数フレームのためのファイル群:
    • MYRISCVXFrameLowering.cpp
    • MYRISCVXFrameLowering.h
    • MYRISCVXSEFrameLowering.cpp
    • MYRISCVXSEFrameLowering.h
  • 命令選択のためのファイル群:
    • MYRISCVXISelLowering.cpp
    • MYRISCVXISelLowering.h
    • MYRISCVXSEISelLowering.cpp
    • MYRISCVXSEISelLowering.h

大量にMYRISCVXSExxxというファイルを作ったのだが、どうやらSEというのは標準の32bitアーキテクチャのことを示すらしい。 あれ、64bitを前提に作ってしまったが間違っていたかな。例えば、MIPSだとMips16, MipsSE(=32-bitクラス), Mips64のクラスが生成されるそうだ。

あと、td(Target Description)ファイルを作ることによって結構なファイルが自動生成される。とりあえず今のところ分かるのはこんな感じかな。 ビルドすると以下の図のようなイメージでファイルが生成される。

f:id:msyksphinz:20190107012713p:plain
tdファイルとそれに基づいて自動生成されるファイル群(.incファイル)

ABIの追加

RISC-VのABIについては資料を参考にしながら進めていく。RISC-Vには関数の引数として使えるレジスタがなんと8個も存在する。

f:id:msyksphinz:20190107004435p:plain
RISC-Vには、x10-x17までの8個のレジスタが引数として使用できる。
  • lib/Target/MYRISCVX/MYRISCVXCallingConv.td
def RetCC_MYRISCVXEABI : CallingConv<[
  // i32 are returned in registers A0, A1, A2, A3, A4, A5, A6, A7
  CCIfType<[i32], CCAssignToReg<[A0, A1, A2, A3, A4, A5, A6, A7]>>
]>;

ABIについてはCpu0のものを参考にした。Cpu0の資料には以下のように書いてある。とりあえずO32をベースにして改造することにした。

Function call — Tutorial: Creating an LLVM Backend for the Cpu0 Architecture

Mentioned in last section, option llc -cpu0-s32-calls=true uses S32 calling convention which passes all arguements at registers while option llc -cpu0-s32-calls=false uses O32 pass first two arguments at registers and other arguments at stack. The result as follows, S32は全ての引数をスタック経由で渡すが、O32は最初の2つのレジスタレジスタで渡しての残りはスタック経由で渡す。

  • ADJCALLSTACKDOWN / ADJCALLSTACKUPの追加。これはどのアーキテクチャでも追加してあるらしい。

Function call — Tutorial: Creating an LLVM Backend for the Cpu0 Architecture

Pseudo hook instruction ADJCALLSTACKDOWN and ADJCALLSTACKUP DAG.getCALLSEQ_START() and DAG.getCALLSEQ_END() are set before and after the “for loop”, respectively, they insert CALLSEQ_START, CALLSEQ_END, and translate them into pseudo machine instructions !ADJCALLSTACKDOWN, !ADJCALLSTACKUP later according Cpu0InstrInfo.td definition as follows.

  • lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
let Defs = [SP], Uses = [SP] in {
def ADJCALLSTACKDOWN : MYRISCVXPseudo<(outs), (ins i32imm:$amt1, i32imm:$amt2),
                                  "!ADJCALLSTACKDOWN $amt1",
                                  [(callseq_start timm:$amt1, timm:$amt2)]>;
def ADJCALLSTACKUP   : MYRISCVXPseudo<(outs), (ins i32imm:$amt1, i32imm:$amt2),
                                  "!ADJCALLSTACKUP $amt1",
                                  [(callseq_end timm:$amt1, timm:$amt2)]>;
} // Defs = [SP], Uses = [SP]

callseq_startとcallseq_endは以下のようになっている。これも、Cpu0から取ってきた。

  • lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
def SDT_MYRISCVXCallSeqStart : SDCallSeqStart<[SDTCisVT<0, i32>]>;
def SDT_MYRISCVXCallSeqEnd   : SDCallSeqEnd<[SDTCisVT<0, i32>, SDTCisVT<1, i32>]>;
...
// These are target-independent nodes, but have target-specific formats.
def callseq_start : SDNode<"ISD::CALLSEQ_START", SDT_MYRISCVXCallSeqStart,
                           [SDNPHasChain, SDNPOutGlue]>;
def callseq_end   : SDNode<"ISD::CALLSEQ_END", SDT_MYRISCVXCallSeqEnd,
                           [SDNPHasChain, SDNPOptInGlue, SDNPOutGlue]>;
  • MYRISCVXISelLowering.cpp / MYRISCVXSEISelLowering.cpp を追加した。まだほとんど空である。
    • このファイルはMYRISCVXにおけるLLVMのコードをDAGに変換するための関数群らしい。
  • MYRISCVXSERegisterInfo.h を追加した。まだほとんど空である。

    • MYRISCVXのレジスタ情報を含んでいる?
    • getCalleeSavedRegs()
    • getCallPreservedMask()
    • getReservedRegs()
    • eliminateFrameIndex()
    • requiresRegisterScavenging()
    • trackLivenessAfterRegAlloc()
    • getFrameRegister()
  • MYRISCVXInstrInfo.cpp / MYRISCVXSEInstrInfo.cpp

    • MYRISCVXの命令情報を含んでいる?
    • create()
    • getRegisterInfo()
    • GetInstSizeInBytes()
    • createMYRISCVXSEInstrInfo()
  • MYRISCVSEInstrInfo.h

  class MYRISCVXSEInstrInfo : public MYRISCVXInstrInfo {
    const MYRISCVXSERegisterInfo RI;

 public:
    explicit MYRISCVXSEInstrInfo(const MYRISCVXSubtarget &STI);

    const MYRISCVXRegisterInfo &getRegisterInfo() const override;


  };
  • MYRISCVSERegisterInfo.h
  class MYRISCVXSERegisterInfo : public MYRISCVXRegisterInfo {
 public:
    MYRISCVXSERegisterInfo(const MYRISCVXSubtarget &Subtarget);

    const TargetRegisterClass *intRegClass(unsigned Size) const override;
  };
  • MYRISCVXFrameLowering.cpp / MYRISCVXSEFrameLowering.cpp を追加した。まだ殆ど空である。
    • これは関数のフレームを処理するためのファイルかな?
    • create()
    • hasFP()
    • eliminateCallFramePseudoInstr()
    • createMYRISCVXSEFrameLowering()

と、とりあえず見よう見まねで最低限のファイルだけを追加してなんとかビルドが通るまでになった。 あとは、動きを確認しながら機能を追加していこう。