diff --git a/include/clang/Basic/TargetCXXABI.h b/include/clang/Basic/TargetCXXABI.h index 1590cca62337b80e0be81616cb431875bb2818db..9ef3274b6e15b0f4df3d91a031993694ed2652db 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 22f2467021e1cc07dc6bd8cb859646f38929292c..726e808ed08aec0fb82b303df7d67d3b3afaf3a6 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<llvm::Value *, 16> 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<CallArgList::CallArgCleanup> 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<QualType> 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 27aefe482f296144a911012739c64f6a75915850..d503d332e7d3ffa6f68814512135916760ff4d37 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<FunctionProtoType>(); + const FunctionProtoType *FPT = D->getType()->castAs<FunctionProtoType>(); 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 66d6b33eb6f007361af1367acaf0a9ad522e948f..374cd026b85759bfa9f1666cc445568d9e686e58 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 a748620ee7fd7e490c7c61fc2a9039dbbee71ff8..d4e1e33d734c18c3c36d10c5eae1c8cce80f8224 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 db291e3b1ddd143085011ad511328677fe3d02bf..2ddb1b9460d112627ef6a874c2d2021172983477 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<typename T> - void EmitCallArgs(CallArgList& Args, const T* CallArgTypeInfo, + template <typename T> + 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<typename ArgTypeIterator> + void EmitCallArgs(CallArgList& Args, + bool AllowExtraArguments, + ArgTypeIterator ArgTypeBeg, + ArgTypeIterator ArgTypeEnd, + CallExpr::const_arg_iterator ArgBeg, + CallExpr::const_arg_iterator ArgEnd, + bool ForceColumnInfo = false) { + SmallVector<QualType, 16> 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<PointerType>()->getPointeeType(); - QualType ArgBaseType = + QualType ArgBaseType = ArgType->getAs<PointerType>()->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<QualType> 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 0b95c48d4f8e7afa9d9ce571a4ad2ca77a39af05..9e711c63321551aaa478ea790048cd5cf447bb60 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<RecordType>()) FinalizeVarWithDestructor(Param, RT); } diff --git a/test/CodeGen/tbaa-ms-abi.cpp b/test/CodeGen/tbaa-ms-abi.cpp index 67390b1a8a5494f41d4cb46d0f9b8de8a0ba8fd2..9908ac06d98ff8576561a93b72276f0945f94c52 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 0000000000000000000000000000000000000000..4f96f2a1bf18ad1cb7a0464b838ae4267bfda3c5 --- /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 0000000000000000000000000000000000000000..91fd47ac6a7bccb905603bf80a8503820b8344a6 --- /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 +}