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()
フラグが立っていると処理が開始され、FirstByValRegs
とLastByValRegs
に引数渡しに使用されるレジスタが設定される。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 # 関数呼び出し ...