前回の続き。
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属性の処理が開始され、FirstByValRegs
とLastByValRegs
に引数渡しに使用されるレジスタが設定される。これは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
に対する処理は完了である。
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 # 関数呼び出し ...