forked from OSchip/llvm-project
MS ABI: Pass 'sret' as the second parameter of instance methods
Summary: MSVC always passes 'sret' after 'this', unlike GCC. This required changing a number of places in Clang that assumed the sret parameter was always first in LLVM IR. This fixes win64 MSVC ABI compatibility for methods returning structs. Reviewers: rsmith, majnemer Subscribers: cfe-commits Differential Revision: http://reviews.llvm.org/D3618 llvm-svn: 208458
This commit is contained in:
parent
46e1ecdecc
commit
37abaca3c2
|
@ -82,9 +82,10 @@ private:
|
|||
};
|
||||
Kind TheKind;
|
||||
bool PaddingInReg : 1;
|
||||
bool InAllocaSRet : 1; // isInAlloca
|
||||
bool InAllocaSRet : 1; // isInAlloca()
|
||||
bool IndirectByVal : 1; // isIndirect()
|
||||
bool IndirectRealign : 1; // isIndirect()
|
||||
bool SRetAfterThis : 1; // isIndirect()
|
||||
bool InReg : 1; // isDirect() || isExtend() || isIndirect()
|
||||
|
||||
ABIArgInfo(Kind K)
|
||||
|
@ -129,6 +130,7 @@ public:
|
|||
AI.setIndirectAlign(Alignment);
|
||||
AI.setIndirectByVal(ByVal);
|
||||
AI.setIndirectRealign(Realign);
|
||||
AI.setSRetAfterThis(false);
|
||||
AI.setPaddingType(Padding);
|
||||
return AI;
|
||||
}
|
||||
|
@ -233,6 +235,15 @@ public:
|
|||
IndirectRealign = IR;
|
||||
}
|
||||
|
||||
bool isSRetAfterThis() const {
|
||||
assert(isIndirect() && "Invalid kind!");
|
||||
return SRetAfterThis;
|
||||
}
|
||||
void setSRetAfterThis(bool AfterThis) {
|
||||
assert(isIndirect() && "Invalid kind!");
|
||||
SRetAfterThis = AfterThis;
|
||||
}
|
||||
|
||||
unsigned getInAllocaFieldIndex() const {
|
||||
assert(isInAlloca() && "Invalid kind!");
|
||||
return AllocaFieldIndex;
|
||||
|
|
|
@ -114,6 +114,10 @@ public:
|
|||
/// Returns how an argument of the given record type should be passed.
|
||||
virtual RecordArgABI getRecordArgABI(const CXXRecordDecl *RD) const = 0;
|
||||
|
||||
/// Returns true if the implicit 'sret' parameter comes after the implicit
|
||||
/// 'this' parameter of C++ instance methods.
|
||||
virtual bool isSRetParameterAfterThis() const { return false; }
|
||||
|
||||
/// Find the LLVM type used to represent the given member pointer
|
||||
/// type.
|
||||
virtual llvm::Type *
|
||||
|
|
|
@ -940,6 +940,7 @@ CodeGenTypes::GetFunctionType(const CGFunctionInfo &FI) {
|
|||
bool Inserted = FunctionsBeingProcessed.insert(&FI); (void)Inserted;
|
||||
assert(Inserted && "Recursively being processed?");
|
||||
|
||||
bool SwapThisWithSRet = false;
|
||||
SmallVector<llvm::Type*, 8> argTypes;
|
||||
llvm::Type *resultType = 0;
|
||||
|
||||
|
@ -973,6 +974,8 @@ CodeGenTypes::GetFunctionType(const CGFunctionInfo &FI) {
|
|||
llvm::Type *ty = ConvertType(ret);
|
||||
unsigned addressSpace = Context.getTargetAddressSpace(ret);
|
||||
argTypes.push_back(llvm::PointerType::get(ty, addressSpace));
|
||||
|
||||
SwapThisWithSRet = retAI.isSRetAfterThis();
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1035,6 +1038,9 @@ CodeGenTypes::GetFunctionType(const CGFunctionInfo &FI) {
|
|||
if (llvm::StructType *ArgStruct = FI.getArgStruct())
|
||||
argTypes.push_back(ArgStruct->getPointerTo());
|
||||
|
||||
if (SwapThisWithSRet)
|
||||
std::swap(argTypes[0], argTypes[1]);
|
||||
|
||||
bool Erased = FunctionsBeingProcessed.erase(&FI); (void)Erased;
|
||||
assert(Erased && "Not in set?");
|
||||
|
||||
|
@ -1149,6 +1155,7 @@ void CodeGenModule::ConstructAttributeList(const CGFunctionInfo &FI,
|
|||
|
||||
QualType RetTy = FI.getReturnType();
|
||||
unsigned Index = 1;
|
||||
bool SwapThisWithSRet = false;
|
||||
const ABIArgInfo &RetAI = FI.getReturnInfo();
|
||||
switch (RetAI.getKind()) {
|
||||
case ABIArgInfo::Extend:
|
||||
|
@ -1176,10 +1183,12 @@ void CodeGenModule::ConstructAttributeList(const CGFunctionInfo &FI,
|
|||
SRETAttrs.addAttribute(llvm::Attribute::StructRet);
|
||||
if (RetAI.getInReg())
|
||||
SRETAttrs.addAttribute(llvm::Attribute::InReg);
|
||||
PAL.push_back(llvm::
|
||||
AttributeSet::get(getLLVMContext(), Index, SRETAttrs));
|
||||
SwapThisWithSRet = RetAI.isSRetAfterThis();
|
||||
PAL.push_back(llvm::AttributeSet::get(
|
||||
getLLVMContext(), SwapThisWithSRet ? 2 : Index, SRETAttrs));
|
||||
|
||||
++Index;
|
||||
if (!SwapThisWithSRet)
|
||||
++Index;
|
||||
// sret disables readnone and readonly
|
||||
FuncAttrs.removeAttribute(llvm::Attribute::ReadOnly)
|
||||
.removeAttribute(llvm::Attribute::ReadNone);
|
||||
|
@ -1201,6 +1210,11 @@ void CodeGenModule::ConstructAttributeList(const CGFunctionInfo &FI,
|
|||
const ABIArgInfo &AI = I.info;
|
||||
llvm::AttrBuilder Attrs;
|
||||
|
||||
// Skip over the sret parameter when it comes second. We already handled it
|
||||
// above.
|
||||
if (Index == 2 && SwapThisWithSRet)
|
||||
++Index;
|
||||
|
||||
if (AI.getPaddingType()) {
|
||||
if (AI.getPaddingInReg())
|
||||
PAL.push_back(llvm::AttributeSet::get(getLLVMContext(), Index,
|
||||
|
@ -1344,13 +1358,20 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI,
|
|||
assert(ArgStruct->getType() == FI.getArgStruct()->getPointerTo());
|
||||
}
|
||||
|
||||
// Name the struct return argument.
|
||||
if (CGM.ReturnTypeUsesSRet(FI)) {
|
||||
// Name the struct return parameter, which can come first or second.
|
||||
const ABIArgInfo &RetAI = FI.getReturnInfo();
|
||||
bool SwapThisWithSRet = false;
|
||||
if (RetAI.isIndirect()) {
|
||||
SwapThisWithSRet = RetAI.isSRetAfterThis();
|
||||
if (SwapThisWithSRet)
|
||||
++AI;
|
||||
AI->setName("agg.result");
|
||||
AI->addAttr(llvm::AttributeSet::get(getLLVMContext(),
|
||||
AI->getArgNo() + 1,
|
||||
AI->addAttr(llvm::AttributeSet::get(getLLVMContext(), AI->getArgNo() + 1,
|
||||
llvm::Attribute::NoAlias));
|
||||
++AI;
|
||||
if (SwapThisWithSRet)
|
||||
--AI; // Go back to the beginning for 'this'.
|
||||
else
|
||||
++AI; // Skip the sret parameter.
|
||||
}
|
||||
|
||||
// Track if we received the parameter as a pointer (indirect, byval, or
|
||||
|
@ -1580,6 +1601,9 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI,
|
|||
}
|
||||
|
||||
++AI;
|
||||
|
||||
if (ArgNo == 1 && SwapThisWithSRet)
|
||||
++AI; // Skip the sret parameter.
|
||||
}
|
||||
|
||||
if (FI.usesInAlloca())
|
||||
|
@ -1822,13 +1846,15 @@ void CodeGenFunction::EmitFunctionEpilog(const CGFunctionInfo &FI,
|
|||
break;
|
||||
|
||||
case ABIArgInfo::Indirect: {
|
||||
auto AI = CurFn->arg_begin();
|
||||
if (RetAI.isSRetAfterThis())
|
||||
++AI;
|
||||
switch (getEvaluationKind(RetTy)) {
|
||||
case TEK_Complex: {
|
||||
ComplexPairTy RT =
|
||||
EmitLoadOfComplex(MakeNaturalAlignAddrLValue(ReturnValue, RetTy),
|
||||
EndLoc);
|
||||
EmitStoreOfComplex(RT,
|
||||
MakeNaturalAlignAddrLValue(CurFn->arg_begin(), RetTy),
|
||||
EmitStoreOfComplex(RT, MakeNaturalAlignAddrLValue(AI, RetTy),
|
||||
/*isInit*/ true);
|
||||
break;
|
||||
}
|
||||
|
@ -1837,7 +1863,7 @@ void CodeGenFunction::EmitFunctionEpilog(const CGFunctionInfo &FI,
|
|||
break;
|
||||
case TEK_Scalar:
|
||||
EmitStoreOfScalar(Builder.CreateLoad(ReturnValue),
|
||||
MakeNaturalAlignAddrLValue(CurFn->arg_begin(), RetTy),
|
||||
MakeNaturalAlignAddrLValue(AI, RetTy),
|
||||
/*isInit*/ true);
|
||||
break;
|
||||
}
|
||||
|
@ -2600,13 +2626,19 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
|
|||
// If the call returns a temporary with struct return, create a temporary
|
||||
// alloca to hold the result, unless one is given to us.
|
||||
llvm::Value *SRetPtr = 0;
|
||||
if (CGM.ReturnTypeUsesSRet(CallInfo) || RetAI.isInAlloca()) {
|
||||
bool SwapThisWithSRet = false;
|
||||
if (RetAI.isIndirect() || RetAI.isInAlloca()) {
|
||||
SRetPtr = ReturnValue.getValue();
|
||||
if (!SRetPtr)
|
||||
SRetPtr = CreateMemTemp(RetTy);
|
||||
if (CGM.ReturnTypeUsesSRet(CallInfo)) {
|
||||
if (RetAI.isIndirect()) {
|
||||
Args.push_back(SRetPtr);
|
||||
SwapThisWithSRet = RetAI.isSRetAfterThis();
|
||||
if (SwapThisWithSRet)
|
||||
IRArgNo = 1;
|
||||
checkArgMatches(SRetPtr, IRArgNo, IRFuncTy);
|
||||
if (SwapThisWithSRet)
|
||||
IRArgNo = 0;
|
||||
} else {
|
||||
llvm::Value *Addr =
|
||||
Builder.CreateStructGEP(ArgMemory, RetAI.getInAllocaFieldIndex());
|
||||
|
@ -2622,6 +2654,10 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
|
|||
const ABIArgInfo &ArgInfo = info_it->info;
|
||||
RValue RV = I->RV;
|
||||
|
||||
// Skip 'sret' if it came second.
|
||||
if (IRArgNo == 1 && SwapThisWithSRet)
|
||||
++IRArgNo;
|
||||
|
||||
CharUnits TypeAlign = getContext().getTypeAlignInChars(I->Ty);
|
||||
|
||||
// Insert a padding argument to ensure proper alignment.
|
||||
|
@ -2811,6 +2847,9 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
|
|||
}
|
||||
}
|
||||
|
||||
if (SwapThisWithSRet)
|
||||
std::swap(Args[0], Args[1]);
|
||||
|
||||
if (ArgMemory) {
|
||||
llvm::Value *Arg = ArgMemory;
|
||||
llvm::Type *LastParamTy =
|
||||
|
|
|
@ -622,7 +622,10 @@ void CodeGenFunction::StartFunction(GlobalDecl GD,
|
|||
!hasScalarEvaluationKind(CurFnInfo->getReturnType())) {
|
||||
// Indirect aggregate return; emit returned value directly into sret slot.
|
||||
// This reduces code size, and affects correctness in C++.
|
||||
ReturnValue = CurFn->arg_begin();
|
||||
auto AI = CurFn->arg_begin();
|
||||
if (CurFnInfo->getReturnInfo().isSRetAfterThis())
|
||||
++AI;
|
||||
ReturnValue = AI;
|
||||
} else if (CurFnInfo->getReturnInfo().getKind() == ABIArgInfo::InAlloca &&
|
||||
!hasScalarEvaluationKind(CurFnInfo->getReturnType())) {
|
||||
// Load the sret pointer from the argument struct and return into that.
|
||||
|
|
|
@ -46,6 +46,8 @@ public:
|
|||
|
||||
RecordArgABI getRecordArgABI(const CXXRecordDecl *RD) const override;
|
||||
|
||||
bool isSRetParameterAfterThis() const override { return true; }
|
||||
|
||||
StringRef GetPureVirtualCallName() override { return "_purecall"; }
|
||||
// No known support for deleted functions in MSVC yet, so this choice is
|
||||
// arbitrary.
|
||||
|
|
|
@ -992,13 +992,13 @@ void X86_32ABIInfo::computeInfo(CGFunctionInfo &FI) const {
|
|||
FI.getReturnInfo() =
|
||||
classifyReturnType(FI.getReturnType(), State, FI.isInstanceMethod());
|
||||
|
||||
// On win32, use the x86_cdeclmethodcc convention for cdecl methods that use
|
||||
// sret. This convention swaps the order of the first two parameters behind
|
||||
// the scenes to match MSVC.
|
||||
// On win32, swap the order of the first two parameters for instance methods
|
||||
// which are sret behind the scenes to match MSVC.
|
||||
if (IsWin32StructABI && FI.isInstanceMethod() &&
|
||||
FI.getCallingConvention() == llvm::CallingConv::C &&
|
||||
FI.getReturnInfo().isIndirect())
|
||||
FI.setEffectiveCallingConvention(llvm::CallingConv::X86_CDeclMethod);
|
||||
FI.getReturnInfo().isIndirect()) {
|
||||
assert(FI.arg_size() >= 1 && "instance method should have this");
|
||||
FI.getReturnInfo().setSRetAfterThis(true);
|
||||
}
|
||||
|
||||
bool UsedInAlloca = false;
|
||||
for (auto &I : FI.arguments()) {
|
||||
|
@ -2768,6 +2768,14 @@ void WinX86_64ABIInfo::computeInfo(CGFunctionInfo &FI) const {
|
|||
QualType RetTy = FI.getReturnType();
|
||||
FI.getReturnInfo() = classify(RetTy, true);
|
||||
|
||||
// On win64, swap the order of the first two parameters for instance methods
|
||||
// which are sret behind the scenes to match MSVC.
|
||||
if (FI.getReturnInfo().isIndirect() && FI.isInstanceMethod() &&
|
||||
getCXXABI().isSRetParameterAfterThis()) {
|
||||
assert(FI.arg_size() >= 1 && "instance method should have this");
|
||||
FI.getReturnInfo().setSRetAfterThis(true);
|
||||
}
|
||||
|
||||
for (auto &I : FI.arguments())
|
||||
I.info = classify(I.type, false);
|
||||
}
|
||||
|
|
|
@ -19,9 +19,9 @@ S C::variadic_sret(const char *f, ...) { return S(); }
|
|||
S C::cdecl_sret() { return S(); }
|
||||
S C::byval_and_sret(S a) { return S(); }
|
||||
|
||||
// CHECK: define x86_cdeclmethodcc void @"\01?variadic_sret@C@@QAA?AUS@@PBDZZ"(%struct.S* noalias sret %agg.result, %struct.C* %this, i8* %f, ...)
|
||||
// CHECK: define x86_cdeclmethodcc void @"\01?cdecl_sret@C@@QAA?AUS@@XZ"(%struct.S* noalias sret %agg.result, %struct.C* %this)
|
||||
// CHECK: define x86_cdeclmethodcc void @"\01?byval_and_sret@C@@QAA?AUS@@U2@@Z"(%struct.S* noalias sret %agg.result, %struct.C* %this, %struct.S* byval align 4 %a)
|
||||
// CHECK: define void @"\01?variadic_sret@C@@QAA?AUS@@PBDZZ"(%struct.C* %this, %struct.S* noalias sret %agg.result, i8* %f, ...)
|
||||
// CHECK: define void @"\01?cdecl_sret@C@@QAA?AUS@@XZ"(%struct.C* %this, %struct.S* noalias sret %agg.result)
|
||||
// CHECK: define void @"\01?byval_and_sret@C@@QAA?AUS@@U2@@Z"(%struct.C* %this, %struct.S* noalias sret %agg.result, %struct.S* byval align 4 %a)
|
||||
|
||||
int main() {
|
||||
C c;
|
||||
|
@ -30,6 +30,6 @@ int main() {
|
|||
c.byval_and_sret(S());
|
||||
}
|
||||
// CHECK-LABEL: define i32 @main()
|
||||
// CHECK: call x86_cdeclmethodcc void {{.*}} @"\01?variadic_sret@C@@QAA?AUS@@PBDZZ"
|
||||
// CHECK: call x86_cdeclmethodcc void @"\01?cdecl_sret@C@@QAA?AUS@@XZ"
|
||||
// CHECK: call x86_cdeclmethodcc void @"\01?byval_and_sret@C@@QAA?AUS@@U2@@Z"
|
||||
// CHECK: call void {{.*}} @"\01?variadic_sret@C@@QAA?AUS@@PBDZZ"
|
||||
// CHECK: call void @"\01?cdecl_sret@C@@QAA?AUS@@XZ"
|
||||
// CHECK: call void @"\01?byval_and_sret@C@@QAA?AUS@@U2@@Z"
|
||||
|
|
|
@ -201,19 +201,19 @@ class Class {
|
|||
public:
|
||||
Small thiscall_method_small() { return Small(); }
|
||||
// LINUX: define {{.*}} void @_ZN5Class21thiscall_method_smallEv(%struct.Small* noalias sret %agg.result, %class.Class* %this)
|
||||
// WIN32: define {{.*}} x86_thiscallcc void @"\01?thiscall_method_small@Class@@QAE?AUSmall@@XZ"(%struct.Small* noalias sret %agg.result, %class.Class* %this)
|
||||
// WIN32: define {{.*}} x86_thiscallcc void @"\01?thiscall_method_small@Class@@QAE?AUSmall@@XZ"(%class.Class* %this, %struct.Small* noalias sret %agg.result)
|
||||
|
||||
SmallWithCtor thiscall_method_small_with_ctor() { return SmallWithCtor(); }
|
||||
// LINUX: define {{.*}} void @_ZN5Class31thiscall_method_small_with_ctorEv(%struct.SmallWithCtor* noalias sret %agg.result, %class.Class* %this)
|
||||
// WIN32: define {{.*}} x86_thiscallcc void @"\01?thiscall_method_small_with_ctor@Class@@QAE?AUSmallWithCtor@@XZ"(%struct.SmallWithCtor* noalias sret %agg.result, %class.Class* %this)
|
||||
// WIN32: define {{.*}} x86_thiscallcc void @"\01?thiscall_method_small_with_ctor@Class@@QAE?AUSmallWithCtor@@XZ"(%class.Class* %this, %struct.SmallWithCtor* noalias sret %agg.result)
|
||||
|
||||
Small __cdecl cdecl_method_small() { return Small(); }
|
||||
// LINUX: define {{.*}} void @_ZN5Class18cdecl_method_smallEv(%struct.Small* noalias sret %agg.result, %class.Class* %this)
|
||||
// WIN32: define {{.*}} void @"\01?cdecl_method_small@Class@@QAA?AUSmall@@XZ"(%struct.Small* noalias sret %agg.result, %class.Class* %this)
|
||||
// WIN32: define {{.*}} void @"\01?cdecl_method_small@Class@@QAA?AUSmall@@XZ"(%class.Class* %this, %struct.Small* noalias sret %agg.result)
|
||||
|
||||
Big __cdecl cdecl_method_big() { return Big(); }
|
||||
// LINUX: define {{.*}} void @_ZN5Class16cdecl_method_bigEv(%struct.Big* noalias sret %agg.result, %class.Class* %this)
|
||||
// WIN32: define {{.*}} void @"\01?cdecl_method_big@Class@@QAA?AUBig@@XZ"(%struct.Big* noalias sret %agg.result, %class.Class* %this)
|
||||
// WIN32: define {{.*}} void @"\01?cdecl_method_big@Class@@QAA?AUBig@@XZ"(%class.Class* %this, %struct.Big* noalias sret %agg.result)
|
||||
|
||||
void thiscall_method_arg(Empty s) {}
|
||||
// LINUX: define {{.*}} void @_ZN5Class19thiscall_method_argE5Empty(%class.Class* %this)
|
||||
|
|
|
@ -34,14 +34,14 @@ void f() {
|
|||
// CHECK32-LABEL: define void @"\01?f@@YAXXZ"()
|
||||
// CHECK32: store i8* bitcast (void (%struct.C*)* @"\01??_9C@@$BA@AE" to i8*), i8** %ptr
|
||||
// CHECK32: store i8* bitcast (i32 (%struct.C*, i32, double)* @"\01??_9C@@$B3AE" to i8*), i8** %ptr2
|
||||
// CHECK32: store i8* bitcast (void (%struct.S*, %struct.C*, i32)* @"\01??_9C@@$B7AE" to i8*), i8** %ptr3
|
||||
// CHECK32: store i8* bitcast (void (%struct.C*, %struct.S*, i32)* @"\01??_9C@@$B7AE" to i8*), i8** %ptr3
|
||||
// CHECK32: store i8* bitcast (void (%"struct.(anonymous namespace)::D"*)* @"\01??_9D@?A@@$BA@AE" to i8*), i8** %ptr4
|
||||
// CHECK32: }
|
||||
//
|
||||
// CHECK64-LABEL: define void @"\01?f@@YAXXZ"()
|
||||
// CHECK64: store i8* bitcast (void (%struct.C*)* @"\01??_9C@@$BA@AA" to i8*), i8** %ptr
|
||||
// CHECK64: store i8* bitcast (i32 (%struct.C*, i32, double)* @"\01??_9C@@$B7AA" to i8*), i8** %ptr2
|
||||
// CHECK64: store i8* bitcast (void (%struct.S*, %struct.C*, i32)* @"\01??_9C@@$BBA@AA" to i8*), i8** %ptr3
|
||||
// CHECK64: store i8* bitcast (void (%struct.C*, %struct.S*, i32)* @"\01??_9C@@$BBA@AA" to i8*), i8** %ptr3
|
||||
// CHECK64: store i8* bitcast (void (%"struct.(anonymous namespace)::D"*)* @"\01??_9D@?A@@$BA@AA" to i8*), i8** %ptr
|
||||
// CHECK64: }
|
||||
}
|
||||
|
@ -78,17 +78,17 @@ void f() {
|
|||
// CHECK64: }
|
||||
|
||||
// Thunk for calling the 3rd virtual function in C, taking an int parameter, returning a struct.
|
||||
// CHECK32-LABEL: define linkonce_odr x86_thiscallcc void @"\01??_9C@@$B7AE"(%struct.S* noalias sret %agg.result, %struct.C* %this, i32) unnamed_addr
|
||||
// CHECK32: [[VPTR:%.*]] = getelementptr inbounds void (%struct.S*, %struct.C*, i32)** %{{.*}}, i64 2
|
||||
// CHECK32: [[CALLEE:%.*]] = load void (%struct.S*, %struct.C*, i32)** [[VPTR]]
|
||||
// CHECK32: call x86_thiscallcc void [[CALLEE]](%struct.S* sret %agg.result, %struct.C* %{{.*}}, i32 %{{.*}})
|
||||
// CHECK32-LABEL: define linkonce_odr x86_thiscallcc void @"\01??_9C@@$B7AE"(%struct.C* %this, %struct.S* noalias sret %agg.result, i32) unnamed_addr
|
||||
// CHECK32: [[VPTR:%.*]] = getelementptr inbounds void (%struct.C*, %struct.S*, i32)** %{{.*}}, i64 2
|
||||
// CHECK32: [[CALLEE:%.*]] = load void (%struct.C*, %struct.S*, i32)** [[VPTR]]
|
||||
// CHECK32: call x86_thiscallcc void [[CALLEE]](%struct.C* %{{.*}}, %struct.S* sret %agg.result, i32 %{{.*}})
|
||||
// CHECK32: ret void
|
||||
// CHECK32: }
|
||||
//
|
||||
// CHECK64-LABEL: define linkonce_odr void @"\01??_9C@@$BBA@AA"(%struct.S* noalias sret %agg.result, %struct.C* %this, i32) unnamed_addr
|
||||
// CHECK64: [[VPTR:%.*]] = getelementptr inbounds void (%struct.S*, %struct.C*, i32)** %{{.*}}, i64 2
|
||||
// CHECK64: [[CALLEE:%.*]] = load void (%struct.S*, %struct.C*, i32)** [[VPTR]]
|
||||
// CHECK64: call void [[CALLEE]](%struct.S* sret %agg.result, %struct.C* %{{.*}}, i32 %{{.*}})
|
||||
// CHECK64-LABEL: define linkonce_odr void @"\01??_9C@@$BBA@AA"(%struct.C* %this, %struct.S* noalias sret %agg.result, i32) unnamed_addr
|
||||
// CHECK64: [[VPTR:%.*]] = getelementptr inbounds void (%struct.C*, %struct.S*, i32)** %{{.*}}, i64 2
|
||||
// CHECK64: [[CALLEE:%.*]] = load void (%struct.C*, %struct.S*, i32)** [[VPTR]]
|
||||
// CHECK64: call void [[CALLEE]](%struct.C* %{{.*}}, %struct.S* sret %agg.result, i32 %{{.*}})
|
||||
// CHECK64: ret void
|
||||
// CHECK64: }
|
||||
|
||||
|
|
Loading…
Reference in New Issue