FPGA開発日記

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

オリジナルLLVMバックエンド実装をまとめる(8. メモリアクセス命令の生成について)

関数のプロローグ・エピローグでは、変数の取り扱いやスタックフレームの扱いなどでメモリアクセスが発生する。ここでは、これらの処理に必要なメモリアクセスの命令を追加する。

RISC-Vには、以下のメモリアクセス命令が定義されている。

f:id:msyksphinz:20190922171417p:plain

さらに、RV64では以下の命令も定義されている。

f:id:msyksphinz:20190922171443p:plain

これらを、MYRISCVXInstrInfo.tdで定義している。

  • llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
/// Load and Store Instructions
///  aligned
def LW  : LoadMemory <0b0000011, 0b010, "lw",  GPR, sextloadi32_a  , 0>;
def SW  : StoreMemory<0b0100011, 0b010, "sw",  GPR, sextloadi32_a  , 0>;
def LB  : LoadMemory <0b0000011, 0b000, "lb",  GPR, sextloadi8_a   , 0>;
def LBU : LoadMemory <0b0000011, 0b100, "lbu", GPR, zextloadi8_a   , 0>;
def SB  : StoreMemory<0b0100011, 0b000, "sb",  GPR, truncstorei8_a , 0>;
def LH  : LoadMemory <0b0000011, 0b001, "lh",  GPR, sextloadi16_a  , 0>;
def LHU : LoadMemory <0b0000011, 0b101, "lhu", GPR, zextloadi16_a  , 0>;
def SH  : StoreMemory<0b0100011, 0b001, "sh",  GPR, truncstorei16_a, 0>;

LoadMemoryStoreMemoryは、メモリアクセスの命令を定義するためのテンプレートクラスだ。

// Memory Load/Store
let canFoldAsLoad = 1 in
class LoadMemory<bits<7> opcode, bits<3> funct3, string instr_asm, RegisterClass RC, PatFrag OpNode,
                 bit Pseudo>:
  MYRISCVX_I<opcode, funct3, (outs RC:$rd), (ins GPR:$rs1, simm12:$simm12),
     !strconcat(instr_asm, "\t$rd, ${simm12}(${rs1})"),
     [], IILoad> {
  let isPseudo = Pseudo;
}

class StoreMemory<bits<7> opcode, bits<3> funct3, string instr_asm, RegisterClass RC, PatFrag OpNode,
                  bit Pseudo>:
  MYRISCVX_S<opcode, funct3, (outs), (ins RC:$rs2, GPR:$rs1, simm12:$simm12),
     !strconcat(instr_asm, "\t$rs2, ${simm12}(${rs1})"),
     [], IIStore> {
  let isPseudo = Pseudo;
}

メモリアクセス命令の生成パタンの登録

メモリアクセス命令を定義したので、今度は命令生成パタンを定義する。メモリアクセスのLLVM IRを使ってメモリアクセスのパタンを用意する。

  • llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
// Load/Store PatFrags.
def load_a          : AlignedLoad<load>;
def sextloadi8_a    : AlignedLoad<sextloadi8>;
def zextloadi8_a    : AlignedLoad<zextloadi8>;
def sextloadi16_a   : AlignedLoad<sextloadi16>;
def zextloadi16_a   : AlignedLoad<zextloadi16>;
def extloadi16_a    : AlignedLoad<extloadi16>;
def sextloadi32_a   : AlignedLoad<sextloadi32>;
def zextloadi32_a   : AlignedLoad<zextloadi32>;
def extloadi32_a    : AlignedLoad<extloadi32>;

def store_a         : AlignedStore<store>;
def truncstorei8_a  : AlignedStore<truncstorei8>;
def truncstorei16_a : AlignedStore<truncstorei16>;
def truncstorei32_a : AlignedStore<truncstorei32>;

AlignedLoad, AlignedStoreはメモリアクセスのパタンをラップするためのクラスだ。

class AlignedLoad<PatFrag Node> :
  PatFrag<(ops node:$ptr), (Node node:$ptr), [{
  LoadSDNode *LD = cast<LoadSDNode>(N);
  return LD->getMemoryVT().getSizeInBits()/8 <= LD->getAlignment();
}]>;

class AlignedStore<PatFrag Node> :
  PatFrag<(ops node:$val, node:$ptr), (Node node:$val, node:$ptr), [{
  StoreSDNode *SD = cast<StoreSDNode>(N);
  return SD->getMemoryVT().getSizeInBits()/8 <= SD->getAlignment();
}]>;

これらのパタンを使用して、各アクセスサイズのメモリアクセス命令とメモリアクセスパタンを関連付ける。 メモリアクセスパタンは以下の4種類を用意する。 それぞれ、命令を生成するパタンを登録していくのですが、メモリアクセス命令の種類は多いので、すべてをベタ書きで定義していくとずいぶんと行数を消費してしまう。 ここは、メモリアクセスの定義をさらにテンプレートクラスで囲んで、もっと簡単に生成パタンを定義できるようにする。

  • llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
// Memory Acccess
multiclass LoadPattern<PatFrag LoadOp, MYRISCVXInst Inst> {
  def : Pat<(XLenVT (LoadOp GPR:$rs1))                          , (Inst GPR:$rs1, 0)           >;
  def : Pat<(XLenVT (LoadOp addr_fi:$rs1))                      , (Inst addr_fi:$rs1, 0)       >;
  def : Pat<(XLenVT (LoadOp (add GPR:$rs1, simm12:$simm12)))    , (Inst GPR:$rs1, $simm12)     >;
  def : Pat<(XLenVT (LoadOp (add addr_fi:$rs1, simm12:$simm12))), (Inst addr_fi:$rs1, $simm12) >;
}

multiclass StorePattern<PatFrag StoreOp, MYRISCVXInst Inst> {
  def : Pat<(StoreOp GPR:$rs2, GPR:$rs1)                          , (Inst GPR:$rs2, GPR:$rs1, 0)                         >;
  def : Pat<(StoreOp GPR:$rs2, addr_fi:$rs1)                      , (Inst GPR:$rs2, addr_fi:$rs1, 0)                     >;
  def : Pat<(StoreOp GPR:$rs2, (add GPR:$rs1, simm12:$simm12))    , (Inst GPR:$rs2, GPR:$rs1, simm12:$simm12)            >;
  def : Pat<(StoreOp GPR:$rs2, (add addr_fi:$rs1, simm12:$simm12)), (Inst GPR:$rs2, addr_fi:$rs1, simm12:$simm12)        >;
}

multiclassというのは、そのクラス内に複数の定義を記述できるクラスだ。その機能を利用して、複数のメモリアクセステンプレートを一気に定義できるようにしている。

  • Pat<(XLenVT (LoadOp GPR:$rs1)), (Inst GPR:$rs1, 0)>
    • 汎用レジスタrs1で指定されたメモリアドレスの要素をロードする。実命令になる場合は、ロード命令のオフセットを0にすることで実現できる。
  • Pat<(XLenVT (LoadOp addr_fi:$rs1)), (Inst addr_fi:$rs1, 0)>
    • 汎用レジスタrs1で指定された関数フレームアドレスの要素をロードする。実命令になる場合は、ロード命令のオフセットを0にすることで実現できる。
  • Pat<(XLenVT (LoadOp (add GPR:$rs1, simm12:$simm12))), (Inst GPR:$rs1, $simm12) >;
    • 汎用レジスタrs1と即値simm12の加算で表現されるメモリアドレスの要素をロードする。実命令になる場合は、ロード命令の汎用レジスタと即値オフセットを使用する。
  • Pat<(StoreOp GPR:$rs2, (add addr_fi:$rs1, simm12:$simm12)), (Inst GPR:$rs2, addr_fi:$rs1, simm12:$simm12)>
    • 汎用レジスタrs1と即値simm12の加算で表現される関数フレームアドレスの要素をロードする。実命令になる場合は、ロード命令の汎用レジスタと即値オフセットを使用する。

それぞれの命令に応じて、対応するノードを関連付けていく。

  • llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXInstrInfo.td
defm : LoadPattern<sextloadi8  , LB>;
defm : LoadPattern<extloadi8   , LB>;
defm : LoadPattern<sextloadi16 , LH>;
defm : LoadPattern<extloadi16  , LH>;
defm : LoadPattern<load        , LW>, Requires<[IsRV32]>;
defm : LoadPattern<zextloadi8  , LBU>;
defm : LoadPattern<zextloadi16 , LHU>;

defm : StorePattern<truncstorei8  , SB>;
defm : StorePattern<truncstorei16 , SH>;
defm : StorePattern<store         , SW>, Requires<[IsRV32]>;
図[fig:load_inst_pattern]:メモリロード命令をmulticlassを使って様々なバリエーションを定義する。
f:id:msyksphinz:20190922171509p:plain
メモリロード命令をmulticlassを使って様々なバリエーションを定義する。
f:id:msyksphinz:20190922171546p:plain
メモリストア命令をmulticlassを使って様々なバリエーションを定義する。