FPGA開発日記

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

LLVMのバックエンドを作るための第一歩 (37. 構造体を値渡しするためのByval属性の引数 2)

f:id:msyksphinz:20190425001356p:plain

ByVal属性の値を関数の引数として受け渡す処理の続き。

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

  • llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
SDValue
MYRISCVXTargetLowering::LowerCall(TargetLowering::CallLoweringInfo &CLI,
                                  SmallVectorImpl<SDValue> &InVals) const {
...
  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()フラグが立っていると処理が開始され、FirstByValRegsLastByValRegsに引数渡しに使用されるレジスタが設定される。copyByValRegsとは逆に、passByValArgsを呼び出して、引数をコピーしている。

  • llvm-myriscvx/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));
    }
...
  // Copy remainder of byval arg to it with 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に対する処理は完了だ。

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

./bin/llc -stats -debug -target-abi=lp32 -march=myriscvx32 -mcpu=simple32 -enable-MYRISCVX-tail-calls=true -relocation-model=static -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   # 関数呼び出し
...
f:id:msyksphinz:20190707001604p:plain
構造体を値渡しで呼んだ場合の引数の処理。ByVal属性が付属される。一部はレジスタ渡し、残りはスタック渡しされる。