FPGA開発日記

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

オリジナルLLVM Backendを追加しよう (25. 可変引数・動的スタック割り当て)

https://cdn-ak.f.st-hatena.com/images/fotolife/m/msyksphinz/20181123/20181123225150.png

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

jonathan2251.github.io

第9章の後半では、可変長引数と、動的スタックの割り当てを実装する。

可変長引数のサポート

可変長引数は、例えば以下のようなコードをどのようにコンパイルするか、という問題だ。

#include <stdarg.h>

int sum_i(int amount, ...)
{
  int i = 0;
  int val = 0;
  int sum = 0;

  va_list vl;
  va_start(vl, amount);
  for (i = 0; i < amount; i++)
  {
    val = va_arg(vl, int);
    sum += val;
  }
  va_end(vl);

  return sum;
}

int test_vararg()
{
  int a = sum_i(6, 0, 1, 2, 3, 4, 5);

  return a;
}

この場合、まず行うことは引数からamountを取得し、引数の全体サイズを取得し、引数の各値をメモリからロードし、加算を行うという処理になる。 引数の数は6つのなので、スタックに6つの値が積み上げられ、関数に飛ぶ。

// int test_vararg() 内
  addiu $2, $zero, 5
  st  $2, 24($sp)
  addiu $2, $zero, 4
  st  $2, 20($sp)
  addiu $2, $zero, 3
  st  $2, 16($sp)
  addiu $2, $zero, 2
  st  $2, 12($sp)
  addiu $2, $zero, 1
  st  $2, 8($sp)
  addiu $2, $zero, 0
  st  $2, 4($sp)
  addiu $2, $zero, 6
  st  $2, 0($sp)
  • `lib/Target/MYRISCVX/Cpu0ISelLowering.cpp
SDValue MYRISCVXTargetLowering::lowerVASTART(SDValue Op, SelectionDAG &DAG) const {
...
  // vastart just stores the address of the VarArgsFrameIndex slot into the
  // memory location argument.
  const Value *SV = cast<SrcValueSDNode>(Op.getOperand(2))->getValue();
  return DAG.getStore(Op.getOperand(0), DL, FI, Op.getOperand(1),
                      MachinePointerInfo(SV));
}
//@LowerFormalArguments {
/// LowerFormalArguments - transform physical registers into virtual registers
/// and generate load operations for arguments places on the stack.
SDValue
MYRISCVXTargetLowering::LowerFormalArguments(SDValue Chain,
...
  if (IsVarArg)
    writeVarArgRegs(OutChains, MYRISCVXCCInfo, Chain, DL, DAG);
...
void MYRISCVXTargetLowering::writeVarArgRegs(std::vector<SDValue> &OutChains,
                                         const MYRISCVXCC &CC, SDValue Chain,
...
  // Copy the integer registers that have not been used for argument passing
  // to the argument register save area. For O32, the save area is allocated
  // in the caller's stack frame, while for N32/64, it is allocated in the
  // callee's stack frame.
  for (unsigned I = Idx; I < NumRegs; ++I, VaArgOffset += RegSize) {
    unsigned Reg = addLiveIn(MF, ArgRegs[I], RC);
    SDValue ArgValue = DAG.getCopyFromReg(Chain, DL, Reg, RegTy);
    FI = MFI.CreateFixedObject(RegSize, VaArgOffset, true);
    SDValue PtrOff = DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout()));
    SDValue Store = DAG.getStore(Chain, DL, ArgValue, PtrOff,
                                 MachinePointerInfo());
    cast<StoreSDNode>(Store.getNode())->getMemOperand()->setValue(
        (Value *)nullptr);
    OutChains.push_back(Store);
  }

動的スタック割り当てのサポート

C言語ではほとんど使わないらしい。allocaという関数は初めて知った。スタック上にメモリを動的に取る関数らしい。

  • ch9_3_alloc.cpp
int weight_sum(int x1, int x2, int x3, int x4, int x5, int x6)
{
//  int *b = (int*)alloca(sizeof(int) * 1 * x1);
  int* b = (int*)__builtin_alloca(sizeof(int) * 1 * x1);
  int *a = b;
  *b = x3;

  int weight = sum(3*x1, x2, x3, x4, 2*x5, x6);

  return (weight + (*a));
}

これをclangでIRとして出力すると、以下のようになっていた。なるほど、allocというIRが呼ばれるのか。

; Function Attrs: noinline nounwind optnone
define dso_local i32 @_Z10weight_sumiiiiii(i32 signext %x1, i32 signext %x2, i32 signext %x3, i32 signext %x4, i32 signext %x5, i32 signext %x6) #0 {
entry:
  %x1.addr = alloca i32, align 4
  %x2.addr = alloca i32, align 4
  %x3.addr = alloca i32, align 4
  %x4.addr = alloca i32, align 4
...

基本的には、adjustStackPtrを使ってこれらのスタックを処理する関数を生成するようだ。 上記のC言語では、コンパイル時にallocで確保されるスタックのサイズを特定することができない。 そこで、スタックのサイズを示しているx1のサイズだけスタックポインタを移動し、その領域を確保するようにしている。 その際にallocが呼ばれる前のspの場所をfpに保存しておき、関数から戻るときは、spの値をfpから書き戻す。

// Eliminate ADJCALLSTACKDOWN, ADJCALLSTACKUP pseudo instructions
MachineBasicBlock::iterator MYRISCVXFrameLowering::
eliminateCallFramePseudoInstr(MachineFunction &MF, MachineBasicBlock &MBB,
                              MachineBasicBlock::iterator I) const {
  unsigned SP = MYRISCVX::SP;
  if (!hasReservedCallFrame(MF)) {
    int64_t Amount = I->getOperand(0).getImm();
    if (I->getOpcode() == MYRISCVX::ADJCALLSTACKDOWN)
      Amount = -Amount;

    // const MYRISCVXSEInstrInfo &TII =
    //     *static_cast<const MYRISCVXSEInstrInfo*>(STI.getInstrInfo());

    STI.getInstrInfo()->adjustStackPtr(SP, Amount, MBB, I);
  }

  return MBB.erase(I);
f:id:msyksphinz:20190403234557p:plain

もう一つの方法は、allocよりも上の領域へのアクセスはfpを使い、allocよりも下の領域はspを使ってアクセスする、というものらしい。 この方式のほうが、spだけを使うよりも処理を高速化することができる、ということだ。

f:id:msyksphinz:20190403234809p:plain