FPGA開発日記

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

RISC-V 64-bit LLVM Backendを試す (12. EmitPrologueとEmitEpilogueを実装したい)

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

前回までで、簡単なReturn命令は生成されるようになったが、まだ-O2オプションを付加しないとエラーが発生する。

次はCpu0 Backendの資料を見ながら、EmitPrologueとEmitEpilogueを実装していきたい。

jonathan2251.github.io

EmitPrologueとEmitEpilogueは、その名の通り関数の最初のエントリと最後のエントリを生成するものだが、役割としてはスタックポインタとフレームポインタの準備だ。 とりあえずCpu0の資料を読みながらと、RISC-Vの実際の実装を見つつ進めていく。

EmitPrologueのソースコードはCpu0の資料からの引用と、RISC-VのLLVMの実装のミックスだ。 スタックサイズを取得して、それに基づいてスタックの調整を行う。 そのためにadjustReg()という関数を呼び出している。

  • lib/Target/MYRISCVX/MYRISCVXSEFrameLowering.cpp
//@emitPrologue {
void MYRISCVXSEFrameLowering::emitPrologue(MachineFunction &MF,
                                           MachineBasicBlock &MBB) const {
  assert(&MF.front() == &MBB && "Shrink-wrapping not yet supported");
  MachineFrameInfo &MFI        = MF.getFrameInfo();
  auto *MYRISCVXFI = MF.getInfo<MYRISCVXFunctionInfo>();

  const MYRISCVXSEInstrInfo &TII =
    *static_cast<const MYRISCVXSEInstrInfo*>(STI.getInstrInfo());
  const MYRISCVXRegisterInfo &RegInfo =
      *static_cast<const MYRISCVXRegisterInfo *>(STI.getRegisterInfo());
...
  unsigned FPReg = MYRISCVX::S0;
  unsigned SPReg = MYRISCVX::SP;

  // First, compute final stack size.
  uint64_t StackSize = MFI.getStackSize();

  // No need to allocate space on the stack.
  if (StackSize == 0 && !MFI.adjustsStack()) return;

  MachineModuleInfo &MMI = MF.getMMI();
  const MCRegisterInfo *MRI = MMI.getContext().getRegisterInfo();
  MachineLocation DstML, SrcML;

  // Adjust stack.
  adjustReg(MBB, MBBI, dl, SPReg, SPReg, -StackSize, MachineInstr::FrameSetup);

  // emit ".cfi_def_cfa_offset StackSize"
  unsigned CFIIndex = MF.addFrameInst(
      MCCFIInstruction::createDefCfaOffset(nullptr, -StackSize));
  BuildMI(MBB, MBBI, dl, TII.get(TargetOpcode::CFI_INSTRUCTION))
      .addCFIIndex(CFIIndex);

  const std::vector<CalleeSavedInfo> &CSI = MFI.getCalleeSavedInfo();
...
}
//}

adjustRegは以下のように実装している。 中身を完璧に理解しているかというとそんなことは無いのだが、スタックのサイズが12bitよりも小さい範囲で収まる場合には単純にStackのサイズだけSPレジスタの位置をずらす命令を生成している(命令を生成するのはBuildMI()という関数らしい? そうでなければ、scratchRegレジスタを新たに作成し、そこにスタックをずらす量を格納する(TII->movImm32()、そしてBuildMIで加算命令・減算命令を生成してスタックレジスタを更新する、という手法だ。

void MYRISCVXSEFrameLowering::adjustReg(MachineBasicBlock &MBB, MachineBasicBlock::iterator MBBI,
                                        const DebugLoc &DL,
                                        unsigned DestReg, unsigned SrcReg,
                                        int64_t Val, MachineInstr::MIFlag Flag) const {

  MachineRegisterInfo &MRI = MBB.getParent()->getRegInfo();
  const MYRISCVXInstrInfo *TII = STI.getInstrInfo();

  if (DestReg == SrcReg && Val == 0)
    return;

  if (isInt<12>(Val)) {
    BuildMI(MBB, MBBI, DL, TII->get(MYRISCVX::ADDI), DestReg)
        .addReg(SrcReg)
        .addImm(Val)
        .setMIFlag(Flag);
  } else if (isInt<32>(Val)) {
    unsigned Opc = MYRISCVX::ADD;
    bool isSub = Val < 0;
    if (isSub) {
      Val = -Val;
      Opc = MYRISCVX::SUB;
    }

    unsigned ScratchReg = MRI.createVirtualRegister(&MYRISCVX::CPURegsRegClass);
    TII->movImm32(MBB, MBBI, DL, ScratchReg, Val, Flag);
    BuildMI(MBB, MBBI, DL, TII->get(Opc), DestReg)
        .addReg(SrcReg)
        .addReg(ScratchReg, RegState::Kill)
        .setMIFlag(Flag);
  } else {
    report_fatal_error("adjustReg cannot yet handle adjustments >32 bits");
  }
}

次にemitEpilogue()だ。これは上記のemitPrologue()の逆のことをやっている。

void MYRISCVXSEFrameLowering::emitEpilogue(MachineFunction &MF,
                                 MachineBasicBlock &MBB) const {

...
  // Get the number of bytes from FrameInfo
  uint64_t StackSize = MFI.getStackSize();

  if (!StackSize)
    return;

  // Adjust stack.
  adjustReg(MBB, MBBI, dl, SPReg, SPReg, StackSize, MachineInstr::FrameDestroy);

}

スタックサイズを取得し、その分だけスタックポインタを移動させる。emitPrologueのときは-StackSizeでアドレスの低い方にアドレスを伸ばしたが、emitEpilogueではアドレスの高い方にスタックを伸ばす。

という訳でこれらの関数を追加してビルドし、再度単純なReturnだけのcppファイルを食わせてみたのだが、まだエラーが発生する。

Combining: t0: ch = EntryToken
Optimized lowered selection DAG: %bb.0 'main:entry'
SelectionDAG has 9 nodes:
      t0: ch = EntryToken
    t5: ch = store<(store 4 into %ir.retval)> t0, Constant:i32<0>, FrameIndex:i64<0>, undef:i64
  t8: ch,glue = CopyToReg t5, Register:i32 $a0, Constant:i32<4>
  t9: ch = MYRISCVXISD::Ret t8, Register:i32 $a0, t8:1


Legalizing node: t2: i64 = FrameIndex<0>
Analyzing result type: i64
Expand integer result: t2: i64 = FrameIndex<0>

ExpandIntegerResult #0: t2: i64 = FrameIndex<0>

Do not know how to expand the result of this operator!
UNREACHABLE executed at /home/msyksphinz/work/riscv/llvm-myriscvx64/lib/CodeGen/SelectionDAG/LegalizeIntegerTypes.cpp:1369!
Stack dump:
0.      Program arguments: ./bin/llc -march=myriscvx -relocation-model=pic --debug -filetype=asm ch3.4.bc -o -
1.      Running pass 'Function Pass Manager' on module 'ch3.4.bc'.
2.      Running pass 'MYRISCVX DAG->DAG Pattern Instruction Selection' on function '@main'

うーん、これはどうやって解析すればよいのだろう?引き続き見ていく。

ExpandIntegerResult()という関数内で引っかかっているのは分かるが、どのDAGがどの変換で失敗しているのかがまだ理解できていない。 undef:i64などと出ているのも気になるなあ。store命令で何かが足りていないんだろうか?

f:id:msyksphinz:20190117013715p:plain