FPGA開発日記

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

LLVMのバックエンドを作るための第一歩 (36. 末尾関数呼び出し最適化を実装する2)

f:id:msyksphinz:20190425001356p:plain

最後に、アセンブリの生成です。TAILCALLTAILCALL_Rは疑似命令なのでそのままではアセンブリ命令を出力することができない。これを置き換える処理は、tdファイルからすでに生成されている。MYRISCVXGenMCPseudoLowering.incを見てみみる。

  • build-myriscvx/lib/Target/MYRISCVX/MYRISCVXGenMCPseudoLowering.inc
bool MYRISCVXAsmPrinter::
emitPseudoExpansionLowering(MCStreamer &OutStreamer,
                            const MachineInstr *MI) {
  switch (MI->getOpcode()) {
    default: return false;
    case MYRISCVX::TAILCALL: {
      MCInst TmpInst;
      MCOperand MCOp;
      TmpInst.setOpcode(MYRISCVX::JAL);
      // Operand: target
      lowerOperand(MI->getOperand(0), MCOp);
      TmpInst.addOperand(MCOp);
      EmitToStreamer(OutStreamer, TmpInst);
      break;
    }
    case MYRISCVX::TAILCALL_R: {
      MCInst TmpInst;
      MCOperand MCOp;
      TmpInst.setOpcode(MYRISCVX::JALR);
      // Operand: rs1
      lowerOperand(MI->getOperand(0), MCOp);
      TmpInst.addOperand(MCOp);
      EmitToStreamer(OutStreamer, TmpInst);
      break;
    }
  }
  return true;
}

TAILCALLではJAL命令に置き換え、TAILCALL_RノードではJALR命令に置き換えるシーケンスが生成されていることが分かる。なぜこれが生成されたかというと、これらのノードにはPseudoInstExpansionが追加されており、自動的に展開することができるようになっていたからです。

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXAsmPrinter.cpp
//- EmitInstruction() must exists or will have run time error.
void MYRISCVXAsmPrinter::EmitInstruction(const MachineInstr *MI) {
...
  do {
    // Do any auto-generated pseudo lowerings.
    if (emitPseudoExpansionLowering(*OutStreamer, &*I))
      continue;
... 

ここまでで、末尾関数呼び出し最適化を適用した場合とそうでない場合についてアセンブリ命令を生成してみる。

# 末尾関数呼び出し最適化を適用した場合
./bin/llc -stats -debug -target-abi=lp64 -march=myriscvx32 -mcpu=simple32 -enable-MYRISCVX-tail-calls=true -relocation-model=static -filetype=asm result/tailcall_-O1_-march=myriscvx32_-mcpu=simple32_static_lp64_-enable-MYRISCVX-tail-calls=true/tailcall.bc -o -
# 末尾関数呼び出し最適化を適用しない場合
./bin/llc -stats -debug -target-abi=lp64 -march=myriscvx32 -mcpu=simple32 -enable-MYRISCVX-tail-calls=false -relocation-model=static -filetype=asm result/tailcall_-O1_-march=myriscvx32_-mcpu=simple32_static_lp64_-enable-MYRISCVX-tail-calls=false/tailcall.bc -o -

- 末尾関数呼び出し最適化を適用した場合の生成されたアセンブリ命令

​```asm
// 関数呼び出され側 (Callee)
_Z14tail_call_funcii:
...
# %bb.0:                                # %entry
    add x10, x11, x10
    ret x1

// 関数呼び出し側 (Caller)
_Z14tail_call_mainiiii:
...
# %bb.0:                                # %entry
    sub x10, x10, x11
    mul x11, x13, x12
    jal _Z14tail_call_funcii
...
  • 末尾関数呼び出し最適化を適用しない場合の生成されたアセンブリ命令
// 関数呼び出され側 (Callee)
_Z14tail_call_funcii:
...
# %bb.0:                                # %entry
    add x10, x11, x10
    ret x1

_Z14tail_call_mainiiii:
...
# %bb.0:                                # %entry
    addi    x2, x2, -8
    .cfi_def_cfa_offset 8
    sw  x2, 4(x2)               # 4-byte Folded Spill
    .cfi_offset 2, -4
    sub x10, x10, x11
    mul x11, x13, x12
    jal _Z14tail_call_funcii
    lw  x2, 4(x2)               # 4-byte Folded Reload
    addi    x2, x2, 8
    ret x1

末尾関数呼び出し最適化を適用しない場合場合は、_Z14tail_call_funcii呼び出し前にスタックの調整が行われていることが確認できる。一方で、末尾関数呼び出し最適化を適用した場合はスタックの調整コードが省略されている。これにより、関数呼び出しの場合のスタックフレームの消費を抑え、動作を高速化することができる。

f:id:msyksphinz:20190704233655p:plain