C言語の可変長引数では、例えば以下のような記述が可能となる。
vararg.cpp
#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(10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); return a; }
C言語の文法としては少し特殊な形をしているかもしれないが、フロントエンドによって一度IRの形式になってしまえば、あとはレジスタを割り当てて命令を生成するだけなのでそこまで難しくはなさそうだ。ただし、この可変長引数をサポートするためには、DAGによってさらに別の種類のノードをサポートする必要がある。
まずは、可変長引数のC言語プログラムから生成されたLLVM IRを見てみる。
./bin/clang vararg.cpp -emit-llvm -o vararg.bc ./bin/llvm-dis vararg.bc -o -
define dso_local i32 @_Z11test_varargv() #0 { entry: ... %call = call i32 (i32, ...) @_Z5sum_iiz(i32 10, i32 0, i32 1, i32 2, i32 3, i32 4, i32 5, i32 6, i32 7, i32 8, i32 9) ...
define dso_local i32 @_Z5sum_iiz(i32 %amount, ...) #0 { entry: ... %arraydecay = getelementptr inbounds [1 x %struct.__va_list_tag], [1 x %struct.__va_list_tag]* %vl, i32 0, i32 0 %arraydecay1 = bitcast %struct.__va_list_tag* %arraydecay to i8* // va_start()を呼ぶ call void @llvm.va_start(i8* %arraydecay1) ... vaarg.in_reg: ; preds = %for.body ... for.end: ; preds = %for.cond // va_end()を呼ぶ call void @llvm.va_end(i8* %arraydecay34) ret i32 %11 }
in_reg
の場合と、in_mem
の場合が挙げられている。for.body
から進んでいき、%gp_offset
が40よりも小さい場合、in_reg
に飛んでいき、そうでない場合はin_mem
に飛んでいくようだ。
このコードの意図をはっきりと説明した文章は見つけられないが、考えられるのは、Callee側が受け入れることのできる引数が可変長だと、コンパイル時にCalleeが使用するスタックの量をあらかじめ算出すことができない、こうすると面倒なので、40バイトまでの可変引数までならスタック経由で渡せるようにする、それ以降はグローバル領域を確保して受け渡す、というようにしていると思われる。
SelectionDAGの生成
VAARG
, VACOPY
, VAEND
の3つは最適化の最中に生成されないように抑制する。つまり、MYRISCVXTargetLowering
にて、setOperationInAction()
で対象となるノードの生成を抑制する。
llvm-myriscvx/lib/Target/MYRISCVX/MYRISCVXISelLowering.cpp
MYRISCVXTargetLowering::MYRISCVXTargetLowering(const MYRISCVXTargetMachine &TM, const MYRISCVXSubtarget &STI) ... // For Var Arguments setOperationAction(ISD::VASTART, MVT::Other, Custom); // Support va_arg(): variable numbers (not fixed numbers) of arguments // (parameters) for function all setOperationAction(ISD::VAARG, MVT::Other, Expand); setOperationAction(ISD::VACOPY, MVT::Other, Expand); setOperationAction(ISD::VAEND, MVT::Other, Expand); ...
VASTART
: 可変長引数の処理を開始する。VAARG
: 可変長引数から実際の値を取り出す。VACOPY
VAEND
: 可変長引数の処理を終了する。
VASTART
だけはカスタム実装に設定したので、lowerVASTART()
のみ実装する。
SDValue MYRISCVXTargetLowering:: LowerOperation(SDValue Op, SelectionDAG &DAG) const { switch (Op.getOpcode()) { case ISD::SELECT : return lowerSELECT(Op, DAG); case ISD::GlobalAddress : return lowerGlobalAddress(Op, DAG); case ISD::VASTART : return lowerVASTART(Op, DAG); } return SDValue(); }
lowerVASTART
では、
SDValue MYRISCVXTargetLowering::lowerVASTART(SDValue Op, SelectionDAG &DAG) const { MachineFunction &MF = DAG.getMachineFunction(); MYRISCVXFunctionInfo *FuncInfo = MF.getInfo<MYRISCVXFunctionInfo>(); SDLoc DL = SDLoc(Op); SDValue FI = DAG.getFrameIndex(FuncInfo->getVarArgsFrameIndex(), getPointerTy(MF.getDataLayout())); // 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()
つまり可変長引数を持つ関数が呼び出されたときの引数の処理について、可変長引数であれば専用のルーチンを呼び出す。writeVarArgRegs
を呼び出す。
SDValue MYRISCVXTargetLowering::LowerFormalArguments (SDValue Chain, CallingConv::ID CallConv, bool IsVarArg, const SmallVectorImpl<ISD::InputArg> &Ins, const SDLoc &DL, SelectionDAG &DAG, SmallVectorImpl<SDValue> &InVals) const { ... // 可変長引数を処理する場合は、特殊なwriteVarArgRegsに飛ぶ。 if (IsVarArg) writeVarArgRegs(OutChains, Chain, DL, DAG, CCInfo); return Chain; }
このwriteVarArgRegs
は、ABIがLP32
の時にレジスタ渡しをしたデータをスタックに戻す。せっかくレジスタ渡しをしたのにどうしてスタックに戻すんだ?と思われるかもしれないが、それ以降の処理は一律してfor文の処理で統一するため、レジスタ渡し、メモリロードの2種類でアセンブリを生成したくない、という意図があると思われる。
/* for文の前に、レジスタ渡しした可変長引数の要素をスタックに置いておく。 */ for (i = 0; i < amount; i++) { /* valの取得はスタックから一律に受け取る */ val = va_arg(vl, int); sum += val; }
void MYRISCVXTargetLowering::writeVarArgRegs(std::vector<SDValue> &OutChains, SDValue Chain, const SDLoc &DL, SelectionDAG &DAG, CCState &State) const { ArrayRef<MCPhysReg> ArgRegs = ABI.GetVarArgRegs(); unsigned Idx = State.getFirstUnallocated(ArgRegs); unsigned RegSizeInBytes = Subtarget.getGPRSizeInBytes(); MVT RegTy = MVT::getIntegerVT(RegSizeInBytes * 8); ... /* 引数渡しに使用しているレジスタを1つ1つ退避していく */ for (unsigned I = Idx; I < ArgRegs.size(); ++I, VaArgOffset += RegSizeInBytes) { unsigned Reg = addLiveIn(MF, ArgRegs[I], RC); SDValue ArgValue = DAG.getCopyFromReg(Chain, DL, Reg, RegTy); FI = MFI.CreateFixedObject(RegSizeInBytes, VaArgOffset, true); SDValue PtrOff = DAG.getFrameIndex(FI, getPointerTy(DAG.getDataLayout())); /* Store命令を生成 */ SDValue Store = DAG.getStore(Chain, DL, ArgValue, PtrOff, MachinePointerInfo()); cast<StoreSDNode>(Store.getNode())->getMemOperand()->setValue( (Value *)nullptr); OutChains.push_back(Store); } }