FPGA開発日記

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

オリジナルLLVM Backendを追加しよう (14. RISC-V命令フィールド登録の見直し)

LLVMにはすでにRISC-Vのバックエンドサポートが追加されている。しかし、勉強のために独自のRISC-V実装をLLVMに追加している。

Chapter-3のサイズの大きなスタックフレームの処理に関して、どうにも解決できずに悩んでいる。 とりあえず最初から全体的に見直すために、いろいろ見直す場所もあるので最初から作り直している。

せっかくなのでMYRISCVX(LLVMに追加している独自RISC-V実装)の命令フィールドの定義も作り直すことにした。

ディレクトリ構成

$ tree lib/Target/MYRISCVX
lib/Target/MYRISCVX
├── CMakeLists.txt
├── LLVMBuild.txt
├── MCTargetDesc
│   ├── CMakeLists.txt
│   ├── LLVMBuild.txt
│   └── MYRISCVXMCTargetDesc.cpp
├── MYRISCVX.h
├── MYRISCVXInstrInfo.td
├── MYRISCVXOther.td
├── MYRISCVXRegisterInfo.td
├── MYRISCVXTargetMachine.cpp
└── TargetInfo
    ├── CMakeLists.txt
    ├── LLVMBuild.txt
    └── MYRISCVXTargetInfo.cpp

MYRISCVXInstrInfo.tdの見直し

MYRISCXVはRISC-V命令セットと全く同一で作るつもりなので、別に変な加工をする必要はない。なるべく自力で作ってみるというだけである。

まず、RISC-V命令セットの命令タイプは大きく分けて以下の4種類に分けられる。

f:id:msyksphinz:20190129235828p:plain
RISC-Vの4種類の命令タイプ

R, I, S, Uの4種類が定義されており、それぞれにクラスを定義すれば良さそうだ。

def Pseudo : Format<0>;
def FrmR   : Format<1>;
def FrmI   : Format<2>;
def FrmS   : Format<3>;
def FrmU   : Format<4>; // Instruction w/ a custom format

まずはベースとなるMYRISCVXInstクラスを定義する。

// Generic MYRISCVX Format
class MYRISCVXInst<dag outs, dag ins, string asmstr, list<dag> pattern,
                   InstrItinClass itin, Format f>: Instruction
{
  // Inst and Size: for tablegen(... -gen-emitter) and
  // tablegen(... -gen-disassembler) in CMakeLists.txt
  field bits<32> Inst;
  Format Form = f;

  let Namespace = "MYRISCVX";

  let Size = 4;

  bits<7> Opcode = 0;

  // Bottom 7 bits are the 'opcode' field
  let Inst{6-0} = Opcode;

...
}

次に、R型の命令を定義する。Opcodeの部分はすでにフィールドに割り付け済みなので、それ以外を作っていく。

class FR<bits<7> opcode, bits<7> funct7, bits<3> funct3,
         dag outs, dag ins, string asmstr,
         list<dag> pattern, InstrItinClass itin>:
      MYRISCVXInst<outs, ins, asmstr, pattern, itin, FrmR>
{
  bits<5>  rs2;
  bits<5>  rs1;
  bits<5>  rd;

  let Opcode = opcode;

  let Inst{31-25} = funct7;
  let Inst{24-20} = rs2;
  let Inst{19-15} = rs1;
  let Inst{14-12} = funct3;
  let Inst{11-7}  = rd;
}

こんな感じでいいのだろうか。次にI型、S型を作る。

class FI<bits<7> opcode, bits<3> funct3,
         dag outs, dag ins, string asmstr, list<dag> pattern,
         InstrItinClass itin>: MYRISCVXInst<outs, ins, asmstr, pattern, itin, FrmI>
{
  bits<5>  rs1;
  bits<5>  rd;
  bits<12> imm12;

  let Opcode = opcode;

  let Inst{31-20} = imm12;
  let Inst{19-15} = rs1;
  let Inst{14-12} = funct3;
  let Inst{11-7}  = rd;
}
class FS<bits<7> opcode, bits<3> funct3,
         dag outs, dag ins, string asmstr, list<dag> pattern,
         InstrItinClass itin>: MYRISCVXInst<outs, ins, asmstr, pattern, itin, FrmS>
{
  bits<5>  rs1;
  bits<5>  rs2;
  bits<12> imm12;

  let Opcode = opcode;

  let Inst{31-25} = imm12{11-5};
  let Inst{19-15} = rs1;
  let Inst{24-20} = rs2;
  let Inst{14-12} = funct3;
  let Inst{11-7}  = imm12{4-0};
}
f:id:msyksphinz:20190129235914p:plain
MYRISCVXの命令フィールドクラスの構造

ここに、各種命令を登録していった。

class LoadM<bits<7> opcode, bits<3> funct3, string instr_asm, PatFrag OpNode, RegisterClass RC,
            Operand MemOpnd, bit Pseudo>:
  FI<opcode, funct3, (outs RC:$ra), (ins MemOpnd:$addr),
     !strconcat(instr_asm, "\t$ra, $addr"),
     [(set RC:$ra, (OpNode addr:$addr))], IILoad> {
  let isPseudo = Pseudo;
}


class StoreM<bits<7> opcode, bits<3> funct3, string instr_asm, PatFrag OpNode, RegisterClass RC,
             Operand MemOpnd, bit Pseudo>:
  FI<opcode, funct3, (outs), (ins RC:$ra, MemOpnd:$addr),
     !strconcat(instr_asm, "\t$ra, $addr"),
     [(OpNode RC:$ra, addr:$addr)], IIStore> {
  let isPseudo = Pseudo;
}


//@ 32-bit load.
multiclass LoadM32<bits<7> opcode, bits<3> funct3, string instr_asm, PatFrag OpNode,
                   bit Pseudo = 0> {
  def #NAME# : LoadM<opcode, funct3, instr_asm, OpNode, GPR, mem, Pseudo>;
}

// 32-bit store.
multiclass StoreM32<bits<7> opcode, bits<3> funct3, string instr_asm, PatFrag OpNode,
                    bit Pseudo = 0> {
  def #NAME# : StoreM<opcode, funct3, instr_asm, OpNode, GPR, mem, Pseudo>;
}


//@JumpFR {
let isBranch=1, isTerminator=1, isBarrier=1, imm12=0, hasDelaySlot = 0,
isIndirectBranch = 1 in
class JumpFR<bits<7> opcode, bits<3> funct3, string instr_asm, RegisterClass RC>:
  FI<opcode, funct3, (outs), (ins RC:$ra),
     !strconcat(instr_asm, "\t$ra"), [(brind RC:$ra)], IIBranch> {
  let rs1   = 1;  // RA
  let imm12 = 0;
}
//@JumpFR }

defm LW : LoadM32 <0b0000011, 0b010, "lw", load_a >;
defm SW : StoreM32<0b0100011, 0b010, "sw", store_a>;

/// Arithmetic Instructions (ALU Immediate)
// IR "add" defined in include/llvm/Target/TargetSelectionDAG.td, line 315 (def add).
def ADDI : ArithLogicI<0b0010011, 0b000, "addi", add, simm12, immSExt12, GPR>;

/// Arithmetic Instructions (3-Operand, R-Type)
def JALR : JumpFR<0b1100111, 0b000, "jalr", GPR>;
def : InstAlias<"jr $rs1",      (JALR ZERO, GPR:$rs1, 0)>;
def RET : RetBase<GPR>;

/// No operation
def : InstAlias<"nop",  (ADDI ZERO, ZERO, 0)>;