From d4abf6549efb81da6ee9053af8bc7918d0a3b192 Mon Sep 17 00:00:00 2001 From: Reid Kleckner Date: Wed, 4 Dec 2013 19:23:12 +0000 Subject: [PATCH] [ms-cxxabi] Construct and destroy call arguments in the correct order Summary: MSVC destroys arguments in the callee from left to right. Because C++ objects have to be destroyed in the reverse order of construction, Clang has to construct arguments from right to left and destroy arguments from left to right. This patch fixes the ordering by reversing the order of evaluation of all call arguments under the MS C++ ABI. Fixes PR18035. Reviewers: rsmith Differential Revision: http://llvm-reviews.chandlerc.com/D2275 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@196402 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/Basic/TargetCXXABI.h | 4 +- lib/CodeGen/CGCall.cpp | 66 +++++++++++-- lib/CodeGen/CGClass.cpp | 25 +---- lib/CodeGen/CGDecl.cpp | 2 +- lib/CodeGen/CGExprCXX.cpp | 31 +----- lib/CodeGen/CodeGenFunction.h | 96 ++++++++++--------- lib/Sema/SemaChecking.cpp | 5 +- test/CodeGen/tbaa-ms-abi.cpp | 4 +- test/CodeGenCXX/microsoft-abi-arg-order.cpp | 41 ++++++++ .../microsoft-abi-arc-param-order.mm | 20 ++++ 10 files changed, 188 insertions(+), 106 deletions(-) create mode 100644 test/CodeGenCXX/microsoft-abi-arg-order.cpp create mode 100644 test/CodeGenObjCXX/microsoft-abi-arc-param-order.mm diff --git a/include/clang/Basic/TargetCXXABI.h b/include/clang/Basic/TargetCXXABI.h index 1590cca623..9ef3274b6e 100644 --- a/include/clang/Basic/TargetCXXABI.h +++ b/include/clang/Basic/TargetCXXABI.h @@ -135,14 +135,14 @@ public: return !isMicrosoft(); } - /// Are temporary objects passed by value to a call destroyed by the callee? + /// Are arguments to a call destroyed left to right in the callee? /// This is a fundamental language change, since it implies that objects /// passed by value do *not* live to the end of the full expression. /// Temporaries passed to a function taking a const reference live to the end /// of the full expression as usual. Both the caller and the callee must /// have access to the destructor, while only the caller needs the /// destructor if this is false. - bool isArgumentDestroyedByCallee() const { + bool areArgsDestroyedLeftToRightInCallee() const { return isMicrosoft(); } diff --git a/lib/CodeGen/CGCall.cpp b/lib/CodeGen/CGCall.cpp index 22f2467021..726e808ed0 100644 --- a/lib/CodeGen/CGCall.cpp +++ b/lib/CodeGen/CGCall.cpp @@ -1246,6 +1246,12 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI, ++AI; } + // Create a pointer value for every parameter declaration. This usually + // entails copying one or more LLVM IR arguments into an alloca. Don't push + // any cleanups or do anything that might unwind. We do that separately, so + // we can push the cleanups in the correct order for the ABI. + SmallVector ArgVals; + ArgVals.reserve(Args.size()); assert(FI.arg_size() == Args.size() && "Mismatch between function signature & arguments."); unsigned ArgNo = 1; @@ -1299,7 +1305,7 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI, if (isPromoted) V = emitArgumentDemotion(*this, Arg, V); } - EmitParmDecl(*Arg, V, ArgNo); + ArgVals.push_back(V); break; } @@ -1340,7 +1346,7 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI, if (V->getType() != LTy) V = Builder.CreateBitCast(V, LTy); - EmitParmDecl(*Arg, V, ArgNo); + ArgVals.push_back(V); break; } @@ -1413,7 +1419,7 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI, if (isPromoted) V = emitArgumentDemotion(*this, Arg, V); } - EmitParmDecl(*Arg, V, ArgNo); + ArgVals.push_back(V); continue; // Skip ++AI increment, already done. } @@ -1426,7 +1432,7 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI, Alloca->setAlignment(Align.getQuantity()); LValue LV = MakeAddrLValue(Alloca, Ty, Align); llvm::Function::arg_iterator End = ExpandTypeFromArgs(Ty, LV, AI); - EmitParmDecl(*Arg, Alloca, ArgNo); + ArgVals.push_back(Alloca); // Name the arguments used in expansion and increment AI. unsigned Index = 0; @@ -1438,10 +1444,9 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI, case ABIArgInfo::Ignore: // Initialize the local variable appropriately. if (!hasScalarEvaluationKind(Ty)) - EmitParmDecl(*Arg, CreateMemTemp(Ty), ArgNo); + ArgVals.push_back(CreateMemTemp(Ty)); else - EmitParmDecl(*Arg, llvm::UndefValue::get(ConvertType(Arg->getType())), - ArgNo); + ArgVals.push_back(llvm::UndefValue::get(ConvertType(Arg->getType()))); // Skip increment, no matching LLVM parameter. continue; @@ -1450,6 +1455,14 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI, ++AI; } assert(AI == Fn->arg_end() && "Argument mismatch!"); + + if (getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) { + for (int I = Args.size() - 1; I >= 0; --I) + EmitParmDecl(*Args[I], ArgVals[I], I + 1); + } else { + for (unsigned I = 0, E = Args.size(); I != E; ++I) + EmitParmDecl(*Args[I], ArgVals[I], I + 1); + } } static void eraseUnusedBitCasts(llvm::Instruction *insn) { @@ -1859,7 +1872,7 @@ static void emitWritebacks(CodeGenFunction &CGF, static void deactivateArgCleanupsBeforeCall(CodeGenFunction &CGF, const CallArgList &CallArgs) { - assert(CGF.getTarget().getCXXABI().isArgumentDestroyedByCallee()); + assert(CGF.getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()); ArrayRef Cleanups = CallArgs.getCleanupsToDeactivate(); // Iterate in reverse to increase the likelihood of popping the cleanup. @@ -2004,6 +2017,41 @@ static void emitWritebackArg(CodeGenFunction &CGF, CallArgList &args, args.add(RValue::get(finalArgument), CRE->getType()); } +void CodeGenFunction::EmitCallArgs(CallArgList &Args, + ArrayRef ArgTypes, + CallExpr::const_arg_iterator ArgBeg, + CallExpr::const_arg_iterator ArgEnd, + bool ForceColumnInfo) { + CGDebugInfo *DI = getDebugInfo(); + SourceLocation CallLoc; + if (DI) CallLoc = DI->getLocation(); + + // We *have* to evaluate arguments from right to left in the MS C++ ABI, + // because arguments are destroyed left to right in the callee. + if (CGM.getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) { + size_t CallArgsStart = Args.size(); + for (int I = ArgTypes.size() - 1; I >= 0; --I) { + CallExpr::const_arg_iterator Arg = ArgBeg + I; + EmitCallArg(Args, *Arg, ArgTypes[I]); + // Restore the debug location. + if (DI) DI->EmitLocation(Builder, CallLoc, ForceColumnInfo); + } + + // Un-reverse the arguments we just evaluated so they match up with the LLVM + // IR function. + std::reverse(Args.begin() + CallArgsStart, Args.end()); + return; + } + + for (unsigned I = 0, E = ArgTypes.size(); I != E; ++I) { + CallExpr::const_arg_iterator Arg = ArgBeg + I; + assert(Arg != ArgEnd); + EmitCallArg(Args, *Arg, ArgTypes[I]); + // Restore the debug location. + if (DI) DI->EmitLocation(Builder, CallLoc, ForceColumnInfo); + } +} + void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E, QualType type) { if (const ObjCIndirectCopyRestoreExpr *CRE @@ -2027,7 +2075,7 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E, // However, we still have to push an EH-only cleanup in case we unwind before // we make it to the call. if (HasAggregateEvalKind && - CGM.getTarget().getCXXABI().isArgumentDestroyedByCallee()) { + CGM.getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) { const CXXRecordDecl *RD = type->getAsCXXRecordDecl(); if (RD && RD->hasNonTrivialDestructor()) { AggValueSlot Slot = CreateAggTemp(type, "agg.arg.tmp"); diff --git a/lib/CodeGen/CGClass.cpp b/lib/CodeGen/CGClass.cpp index 27aefe482f..d503d332e7 100644 --- a/lib/CodeGen/CGClass.cpp +++ b/lib/CodeGen/CGClass.cpp @@ -1703,38 +1703,23 @@ CodeGenFunction::EmitSynthesizedCXXCopyCtorCall(const CXXConstructorDecl *D, assert(D->isInstance() && "Trying to emit a member call expr on a static method!"); - const FunctionProtoType *FPT = D->getType()->getAs(); + const FunctionProtoType *FPT = D->getType()->castAs(); CallArgList Args; // Push the this ptr. Args.add(RValue::get(This), D->getThisType(getContext())); - // Push the src ptr. QualType QT = *(FPT->arg_type_begin()); llvm::Type *t = CGM.getTypes().ConvertType(QT); Src = Builder.CreateBitCast(Src, t); Args.add(RValue::get(Src), QT); - + // Skip over first argument (Src). - ++ArgBeg; - CallExpr::const_arg_iterator Arg = ArgBeg; - for (FunctionProtoType::arg_type_iterator I = FPT->arg_type_begin()+1, - E = FPT->arg_type_end(); I != E; ++I, ++Arg) { - assert(Arg != ArgEnd && "Running over edge of argument list!"); - EmitCallArg(Args, *Arg, *I); - } - // Either we've emitted all the call args, or we have a call to a - // variadic function. - assert((Arg == ArgEnd || FPT->isVariadic()) && - "Extra arguments in non-variadic function!"); - // If we still have any arguments, emit them using the type of the argument. - for (; Arg != ArgEnd; ++Arg) { - QualType ArgType = Arg->getType(); - EmitCallArg(Args, *Arg, ArgType); - } - + EmitCallArgs(Args, FPT->isVariadic(), FPT->arg_type_begin() + 1, + FPT->arg_type_end(), ArgBeg + 1, ArgEnd); + EmitCall(CGM.getTypes().arrangeCXXMethodCall(Args, FPT, RequiredArgs::All), Callee, ReturnValueSlot(), Args, D); } diff --git a/lib/CodeGen/CGDecl.cpp b/lib/CodeGen/CGDecl.cpp index 66d6b33eb6..374cd026b8 100644 --- a/lib/CodeGen/CGDecl.cpp +++ b/lib/CodeGen/CGDecl.cpp @@ -1647,7 +1647,7 @@ void CodeGenFunction::EmitParmDecl(const VarDecl &D, llvm::Value *Arg, DeclPtr = Arg; // Push a destructor cleanup for this parameter if the ABI requires it. if (HasNonScalarEvalKind && - getTarget().getCXXABI().isArgumentDestroyedByCallee()) { + getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) { if (const CXXRecordDecl *RD = Ty->getAsCXXRecordDecl()) { if (RD->hasNonTrivialDestructor()) pushDestroy(QualType::DK_cxx_destructor, DeclPtr, Ty); diff --git a/lib/CodeGen/CGExprCXX.cpp b/lib/CodeGen/CGExprCXX.cpp index a748620ee7..d4e1e33d73 100644 --- a/lib/CodeGen/CGExprCXX.cpp +++ b/lib/CodeGen/CGExprCXX.cpp @@ -1129,35 +1129,12 @@ llvm::Value *CodeGenFunction::EmitCXXNewExpr(const CXXNewExpr *E) { allocatorArgs.add(RValue::get(allocSize), sizeType); - // Emit the rest of the arguments. - // FIXME: Ideally, this should just use EmitCallArgs. - CXXNewExpr::const_arg_iterator placementArg = E->placement_arg_begin(); - - // First, use the types from the function type. // We start at 1 here because the first argument (the allocation size) // has already been emitted. - for (unsigned i = 1, e = allocatorType->getNumArgs(); i != e; - ++i, ++placementArg) { - QualType argType = allocatorType->getArgType(i); - - assert(getContext().hasSameUnqualifiedType(argType.getNonReferenceType(), - placementArg->getType()) && - "type mismatch in call argument!"); - - EmitCallArg(allocatorArgs, *placementArg, argType); - } - - // Either we've emitted all the call args, or we have a call to a - // variadic function. - assert((placementArg == E->placement_arg_end() || - allocatorType->isVariadic()) && - "Extra arguments to non-variadic function!"); - - // If we still have any arguments, emit them using the type of the argument. - for (CXXNewExpr::const_arg_iterator placementArgsEnd = E->placement_arg_end(); - placementArg != placementArgsEnd; ++placementArg) { - EmitCallArg(allocatorArgs, *placementArg, placementArg->getType()); - } + EmitCallArgs(allocatorArgs, allocatorType->isVariadic(), + allocatorType->arg_type_begin() + 1, + allocatorType->arg_type_end(), E->placement_arg_begin(), + E->placement_arg_end()); // Emit the allocation call. If the allocator is a global placement // operator, just "inline" it directly. diff --git a/lib/CodeGen/CodeGenFunction.h b/lib/CodeGen/CodeGenFunction.h index db291e3b1d..2ddb1b9460 100644 --- a/lib/CodeGen/CodeGenFunction.h +++ b/lib/CodeGen/CodeGenFunction.h @@ -2493,68 +2493,78 @@ private: SourceLocation Loc); /// EmitCallArgs - Emit call arguments for a function. - /// The CallArgTypeInfo parameter is used for iterating over the known - /// argument types of the function being called. - template - void EmitCallArgs(CallArgList& Args, const T* CallArgTypeInfo, + template + void EmitCallArgs(CallArgList &Args, const T *CallArgTypeInfo, CallExpr::const_arg_iterator ArgBeg, CallExpr::const_arg_iterator ArgEnd, bool ForceColumnInfo = false) { - CGDebugInfo *DI = getDebugInfo(); - SourceLocation CallLoc; - if (DI) CallLoc = DI->getLocation(); + if (CallArgTypeInfo) { + EmitCallArgs(Args, CallArgTypeInfo->isVariadic(), + CallArgTypeInfo->arg_type_begin(), + CallArgTypeInfo->arg_type_end(), ArgBeg, ArgEnd, + ForceColumnInfo); + } else { + // T::arg_type_iterator might not have a default ctor. + const QualType *NoIter = 0; + EmitCallArgs(Args, /*AllowExtraArguments=*/true, NoIter, NoIter, ArgBeg, + ArgEnd, ForceColumnInfo); + } + } + template + void EmitCallArgs(CallArgList& Args, + bool AllowExtraArguments, + ArgTypeIterator ArgTypeBeg, + ArgTypeIterator ArgTypeEnd, + CallExpr::const_arg_iterator ArgBeg, + CallExpr::const_arg_iterator ArgEnd, + bool ForceColumnInfo = false) { + SmallVector ArgTypes; CallExpr::const_arg_iterator Arg = ArgBeg; // First, use the argument types that the type info knows about - if (CallArgTypeInfo) { - for (typename T::arg_type_iterator I = CallArgTypeInfo->arg_type_begin(), - E = CallArgTypeInfo->arg_type_end(); I != E; ++I, ++Arg) { - assert(Arg != ArgEnd && "Running over edge of argument list!"); - QualType ArgType = *I; + for (ArgTypeIterator I = ArgTypeBeg, E = ArgTypeEnd; I != E; ++I, ++Arg) { + assert(Arg != ArgEnd && "Running over edge of argument list!"); #ifndef NDEBUG - QualType ActualArgType = Arg->getType(); - if (ArgType->isPointerType() && ActualArgType->isPointerType()) { - QualType ActualBaseType = + QualType ArgType = *I; + QualType ActualArgType = Arg->getType(); + if (ArgType->isPointerType() && ActualArgType->isPointerType()) { + QualType ActualBaseType = ActualArgType->getAs()->getPointeeType(); - QualType ArgBaseType = + QualType ArgBaseType = ArgType->getAs()->getPointeeType(); - if (ArgBaseType->isVariableArrayType()) { - if (const VariableArrayType *VAT = - getContext().getAsVariableArrayType(ActualBaseType)) { - if (!VAT->getSizeExpr()) - ActualArgType = ArgType; - } + if (ArgBaseType->isVariableArrayType()) { + if (const VariableArrayType *VAT = + getContext().getAsVariableArrayType(ActualBaseType)) { + if (!VAT->getSizeExpr()) + ActualArgType = ArgType; } } - assert(getContext().getCanonicalType(ArgType.getNonReferenceType()). - getTypePtr() == - getContext().getCanonicalType(ActualArgType).getTypePtr() && - "type mismatch in call argument!"); -#endif - EmitCallArg(Args, *Arg, ArgType); - - // Each argument expression could modify the debug - // location. Restore it. - if (DI) DI->EmitLocation(Builder, CallLoc, ForceColumnInfo); } - - // Either we've emitted all the call args, or we have a call to a - // variadic function. - assert((Arg == ArgEnd || CallArgTypeInfo->isVariadic()) && - "Extra arguments in non-variadic function!"); - + assert(getContext().getCanonicalType(ArgType.getNonReferenceType()). + getTypePtr() == + getContext().getCanonicalType(ActualArgType).getTypePtr() && + "type mismatch in call argument!"); +#endif + ArgTypes.push_back(*I); } + // Either we've emitted all the call args, or we have a call to variadic + // function or some other call that allows extra arguments. + assert((Arg == ArgEnd || AllowExtraArguments) && + "Extra arguments in non-variadic function!"); + // If we still have any arguments, emit them using the type of the argument. - for (; Arg != ArgEnd; ++Arg) { - EmitCallArg(Args, *Arg, Arg->getType()); + for (; Arg != ArgEnd; ++Arg) + ArgTypes.push_back(Arg->getType()); - // Restore the debug location. - if (DI) DI->EmitLocation(Builder, CallLoc, ForceColumnInfo); - } + EmitCallArgs(Args, ArgTypes, ArgBeg, ArgEnd, ForceColumnInfo); } + void EmitCallArgs(CallArgList &Args, ArrayRef ArgTypes, + CallExpr::const_arg_iterator ArgBeg, + CallExpr::const_arg_iterator ArgEnd, bool ForceColumnInfo); + const TargetCodeGenInfo &getTargetHooks() const { return CGM.getTargetCodeGenInfo(); } diff --git a/lib/Sema/SemaChecking.cpp b/lib/Sema/SemaChecking.cpp index 0b95c48d4f..9e711c6332 100644 --- a/lib/Sema/SemaChecking.cpp +++ b/lib/Sema/SemaChecking.cpp @@ -6190,8 +6190,9 @@ bool Sema::CheckParmsForFunctionDef(ParmVarDecl *const *P, // MSVC destroys objects passed by value in the callee. Therefore a // function definition which takes such a parameter must be able to call the // object's destructor. - if (getLangOpts().CPlusPlus && - Context.getTargetInfo().getCXXABI().isArgumentDestroyedByCallee()) { + if (getLangOpts().CPlusPlus && Context.getTargetInfo() + .getCXXABI() + .areArgsDestroyedLeftToRightInCallee()) { if (const RecordType *RT = Param->getType()->getAs()) FinalizeVarWithDestructor(Param, RT); } diff --git a/test/CodeGen/tbaa-ms-abi.cpp b/test/CodeGen/tbaa-ms-abi.cpp index 67390b1a8a..9908ac06d9 100644 --- a/test/CodeGen/tbaa-ms-abi.cpp +++ b/test/CodeGen/tbaa-ms-abi.cpp @@ -16,7 +16,7 @@ StructB::StructB() { // CHECK: store i32 42, i32* {{.*}}, !tbaa [[TAG_A_i32:!.*]] } -// CHECK: [[TYPE_CHAR:!.*]] = metadata !{metadata !"omnipotent char", metadata -// CHECK: [[TYPE_INT:!.*]] = metadata !{metadata !"int", metadata [[TYPE_CHAR]], i64 0} +// CHECK: [[TYPE_INT:!.*]] = metadata !{metadata !"int", metadata [[TYPE_CHAR:!.*]], i64 0} +// CHECK: [[TYPE_CHAR]] = metadata !{metadata !"omnipotent char", metadata // CHECK: [[TAG_A_i32]] = metadata !{metadata [[TYPE_A:!.*]], metadata [[TYPE_INT]], i64 0} // CHECK: [[TYPE_A]] = metadata !{metadata !"?AUStructA@@", metadata [[TYPE_INT]], i64 0} diff --git a/test/CodeGenCXX/microsoft-abi-arg-order.cpp b/test/CodeGenCXX/microsoft-abi-arg-order.cpp new file mode 100644 index 0000000000..4f96f2a1bf --- /dev/null +++ b/test/CodeGenCXX/microsoft-abi-arg-order.cpp @@ -0,0 +1,41 @@ +// RUN: %clang_cc1 -mconstructor-aliases -std=c++11 -fexceptions -emit-llvm %s -o - -cxx-abi microsoft -triple=i386-pc-win32 | FileCheck %s + +struct A { + A(int a); + ~A(); + int a; +}; + +void foo(A a, A b, A c) { +} + +// Order of destruction should be left to right. +// +// CHECK-LABEL: define void @"\01?foo@@YAXUA@@00@Z" +// CHECK: ({{.*}} %[[a:.*]], {{.*}} %[[b:.*]], {{.*}} %[[c:.*]]) +// CHECK: call x86_thiscallcc void @"\01??1A@@QAE@XZ"(%struct.A* %[[a]]) +// CHECK: call x86_thiscallcc void @"\01??1A@@QAE@XZ"(%struct.A* %[[b]]) +// CHECK: call x86_thiscallcc void @"\01??1A@@QAE@XZ"(%struct.A* %[[c]]) +// CHECK: ret void + + +void call_foo() { + foo(A(1), A(2), A(3)); +} + +// Order of evaluation should be right to left, and we should clean up the right +// things as we unwind. +// +// CHECK-LABEL: define void @"\01?call_foo@@YAXXZ"() +// CHECK: call x86_thiscallcc %struct.A* @"\01??0A@@QAE@H@Z"(%struct.A* %[[arg3:.*]], i32 3) +// CHECK: invoke x86_thiscallcc %struct.A* @"\01??0A@@QAE@H@Z"(%struct.A* %[[arg2:.*]], i32 2) +// CHECK: invoke x86_thiscallcc %struct.A* @"\01??0A@@QAE@H@Z"(%struct.A* %[[arg1:.*]], i32 1) +// CHECK: call void @"\01?foo@@YAXUA@@00@Z"({{.*}} %[[arg1]], {{.*}} %[[arg2]], {{.*}} %[[arg3]]) +// CHECK: ret void +// +// lpad2: +// CHECK: call x86_thiscallcc void @"\01??1A@@QAE@XZ"(%struct.A* %[[arg2]]) +// CHECK: br label +// +// ehcleanup: +// CHECK: call x86_thiscallcc void @"\01??1A@@QAE@XZ"(%struct.A* %[[arg3]]) diff --git a/test/CodeGenObjCXX/microsoft-abi-arc-param-order.mm b/test/CodeGenObjCXX/microsoft-abi-arc-param-order.mm new file mode 100644 index 0000000000..91fd47ac6a --- /dev/null +++ b/test/CodeGenObjCXX/microsoft-abi-arc-param-order.mm @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -cxx-abi microsoft -mconstructor-aliases -fobjc-arc -triple i686-pc-win32 -emit-llvm -o - %s | FileCheck %s + +struct A { + A(); + A(const A &); + ~A(); + int a; +}; + +// Verify that we destruct things from left to right in the MS C++ ABI: a, b, c, d. +// +// CHECK-LABEL: define void @"\01?test_arc_order@@YAXUA@@PAAAPAUobjc_object@@01@Z" +// CHECK: ({{.*}} %[[a:.*]], {{.*}}, {{.*}} %[[c:.*]], {{.*}}) +void test_arc_order(A a, id __attribute__((ns_consumed)) b , A c, id __attribute__((ns_consumed)) d) { + // CHECK: call x86_thiscallcc void @"\01??1A@@QAE@XZ"(%struct.A* %[[a]]) + // CHECK: call void @objc_storeStrong(i8** %{{.*}}, i8* null) + // CHECK: call x86_thiscallcc void @"\01??1A@@QAE@XZ"(%struct.A* %[[c]]) + // CHECK: call void @objc_storeStrong(i8** %{{.*}}, i8* null) + // CHECK: ret void +} -- 2.50.1