FPGA開発日記

FPGAというより、コンピュータアーキテクチャかもね! カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages

オリジナルLLVMバックエンド実装をまとめる(21. ByVal属性のついた引数を扱うための処理2)

前回の続き。

ByVal属性の引数における呼び出し側の処理

それでは、次に呼び出し側はどのようになっているのだろうか。 呼び出し側でも、ByVal属性のついた引数を一つ一つコピーして関数呼び出され側に渡す必要がある。 関数呼び出し側は、MYRISCVXTargetLowering::LowerCall()を改造するのだった。

  • llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
SDValue
MYRISCVXTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
                                  SmallVectorImpl<SDValue> &InVals) const {
  // Check if it's really possible to do a tail call.
  if (IsTailCall)
    IsTailCall = isEligibleForTailCallOptimization(CCInfo, CLI, MF, ArgLocs);
...
  CCInfo.rewindByValRegsInfo();
...
  for (unsigned i = 0, e = ArgLocs.size(); i != e; ++i) {
...
    if (Flags.isByVal()) {
      unsigned FirstByValReg, LastByValReg;
      unsigned ByValIdx = CCInfo.getInRegsParamsProcessed();

      CCInfo.getInRegsParamInfo(ByValIdx, FirstByValReg, LastByValReg);

      assert(Flags.getByValSize() &&
             "ByVal args of size 0 should have been ignored by front-end.");
      assert(ByValIdx < CCInfo.getInRegsParamsCount());
      assert(!IsTailCall &&
             "Do not tail-call optimize if there is a byval argument.");
      passByValArg(Chain, DL, RegsToPass, MemOpChains, StackPtr, MFI, DAG, Arg,
                   CCInfo, FirstByValReg, LastByValReg, Flags, Subtarget.isLittle(), VA);
      CCInfo.nextInRegsParam();
      continue;
    }
...
    

LowerFormalArgumetsと内容は似ている。isByVal()フラグが立っているとByVal属性の処理が開始され、FirstByValRegsLastByValRegsに引数渡しに使用されるレジスタが設定される。これはHandleByValで処理したものと同様である。

f:id:msyksphinz:20191012021248p:plain
ByVal属性の引数を関数呼び出され側での取り扱い。CallOperands()で解析し、HandleByVal()を呼ぶ。

LowerCall()では、copyByValRegsとは逆に、passByValArgsを呼び出して、引数をコピーしていきます。

  • llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
void MYRISCVXTargetLowering::
passByValArg(SDValue Chain, const SDLoc &DL,
             std::deque< std::pair<unsigned, SDValue> > &RegsToPass,
             SmallVectorImpl<SDValue> &MemOpChains, SDValue StackPtr,
             MachineFrameInfo &MFI, SelectionDAG &DAG, SDValue Arg,
             const CCState &CC, unsigned FirstReg, unsigned LastReg,
             const ISD::ArgFlagsTy &Flags, bool isLittle,
             const CCValAssign &VA) const {
...
      if (NumRegs) {
    const ArrayRef<MCPhysReg> ArgRegs = ABI.GetByValArgRegs();
    bool LeftoverBytes = (NumRegs * RegSizeInBytes > ByValSizeInBytes);
    unsigned I = 0;

    // レジスタに引数の値をコピーしていく。
    for (; I < NumRegs - LeftoverBytes;
         ++I, OffsetInBytes += RegSizeInBytes) {
      // オフセットの計算
      SDValue LoadPtr = DAG.getNode(ISD::ADD, DL, PtrTy, Arg,
                                    DAG.getConstant(OffsetInBytes, DL, PtrTy));
      // 引数レジスタに値をコピーしていく。
      SDValue LoadVal = DAG.getLoad(RegTy, DL, Chain, LoadPtr,
                                    MachinePointerInfo());
      MemOpChains.push_back(LoadVal.getValue(1));
      unsigned ArgReg = ArgRegs[FirstReg + I];
      RegsToPass.push_back(std::make_pair(ArgReg, LoadVal));
    }
...
  // byval属性にもかかわらず引数レジスタに入りきらなかったものについては、memcpyでスタックにコピーしていく。
  unsigned MemCpySize = ByValSizeInBytes - OffsetInBytes;
  SDValue Src = DAG.getNode(ISD::ADD, DL, PtrTy, Arg,
                            DAG.getConstant(OffsetInBytes, DL, PtrTy));
  SDValue Dst = DAG.getNode(ISD::ADD, DL, PtrTy, StackPtr,
                            DAG.getIntPtrConstant(VA.getLocMemOffset(), DL));
  Chain = DAG.getMemcpy(Chain, DL, Dst, Src,
                        DAG.getConstant(MemCpySize, DL, PtrTy),
                        Alignment, /*isVolatile=*/false, /*AlwaysInline=*/false,
                        /*isTailCall=*/false,
                        MachinePointerInfo(), MachinePointerInfo());
  MemOpChains.push_back(Chain);

レジスタに入りきらかなった引数は、なんとmemcpyを呼び出して一気にスタックにコピーする。これで、ByValに対する処理は完了である。

f:id:msyksphinz:20191012021318p:plain
LowerCall()において、ByVal属性の引数を見つけるとpassByValRegs()が呼び出される。

ByVal属性生成の特殊条件の追加

後で説明しますが、ByVal属性の引数が存在しているときは最適化の一つであるTail Call Optimizationが使用できない。このための条件判断のために、MYRISCVXTargetLowering::isEligibleForTailCallOptimization()を追加して、条件判断を行う。

  • llvm-myriscvx80/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
/// isEligibleForTailCallOptimization - Check whether the call is eligible
/// for tail call optimization.
bool MYRISCVXTargetLowering::isEligibleForTailCallOptimization(
    CCState &CCInfo, CallLoweringInfo &CLI, MachineFunction &MF,
    const SmallVector<CCValAssign, 16> &ArgLocs) const {
  auto &Outs = CLI.Outs;

  // Byval parameters hand the function a pointer directly into the stack area
  // we want to reuse during a tail call. Working around this *is* possible
  // but less efficient and uglier in LowerCall.
  for (auto &Arg : Outs)
    if (Arg.Flags.isByVal())
      return false;

  return true;
}

実行結果の確認

それでは、上記のコードをコンパイルしてアセンブリを生成してみる。

./bin/llc -stats -debug -march=myriscvx32 -filetype=asm func_struct_simple.bc -o -
  • func() (関数呼び出され側)
_Z4func1S:
...
# %bb.0:                                # %entry
        addi    x2, x2, -32
        .cfi_def_cfa_offset 32
        addi    x5, x2, 0
        # レジスタ渡しした値をすべてスタックにコピーする。
        addi    x6, x5, 28
        sw      x17, 0(x6)
        addi    x17, x5, 24
        sw      x16, 0(x17)
        addi    x16, x5, 20
        sw      x15, 0(x16)
        addi    x15, x5, 16
        sw      x14, 0(x15)
        addi    x14, x5, 12
        sw      x13, 0(x14)
        addi    x13, x5, 8
        sw      x12, 0(x13)
        ori     x12, x5, 4
        sw      x11, 0(x12)
        addi    x11, zero, 0
        sw      x10, 0(x2)
        addi    x10, x11, 0
...
  • call_func() (関数呼び出し側)
...
        jal     memcpy         # レジスタ渡しができない値をすべてmemcpyでスタックにコピーする。
        addi    x10, x9, 28
        lw      x17, 0(x10)    # レジスタ渡しする値をレジスタにコピー x[7]
        addi    x10, x9, 24
        lw      x16, 0(x10)    # レジスタ渡しする値をレジスタにコピー x[6]
        addi    x10, x9, 20
        lw      x15, 0(x10)    # レジスタ渡しする値をレジスタにコピー x[5]
        addi    x10, x9, 16
        lw      x14, 0(x10)    # レジスタ渡しする値をレジスタにコピー x[4]
        addi    x10, x9, 12
        lw      x13, 0(x10)    # レジスタ渡しする値をレジスタにコピー x[3]
        addi    x10, x9, 8
        lw      x12, 0(x10)    # レジスタ渡しする値をレジスタにコピー x[2]
        ori     x10, x9, 4
        lw      x11, 0(x10)    # レジスタ渡しする値をレジスタにコピー x[1]
        lw      x10, 40(x2)    # レジスタ渡しする値をレジスタにコピー x[0]
        jal     _Z4func1S   # 関数呼び出し
...