Index: cfe/trunk/include/clang/AST/Decl.h =================================================================== --- cfe/trunk/include/clang/AST/Decl.h +++ cfe/trunk/include/clang/AST/Decl.h @@ -3559,6 +3559,11 @@ /// pass an object of this class. bool CanPassInRegisters : 1; + /// Indicates whether this struct is destroyed in the callee. This flag is + /// meaningless when Microsoft ABI is used since parameters are always + /// destroyed in the callee. + bool ParamDestroyedInCallee : 1; + protected: RecordDecl(Kind DK, TagKind TK, const ASTContext &C, DeclContext *DC, SourceLocation StartLoc, SourceLocation IdLoc, @@ -3654,6 +3659,14 @@ CanPassInRegisters = CanPass; } + bool isParamDestroyedInCallee() const { + return ParamDestroyedInCallee; + } + + void setParamDestroyedInCallee(bool V) { + ParamDestroyedInCallee = V; + } + /// \brief Determines whether this declaration represents the /// injected class name. /// Index: cfe/trunk/include/clang/AST/DeclCXX.h =================================================================== --- cfe/trunk/include/clang/AST/DeclCXX.h +++ cfe/trunk/include/clang/AST/DeclCXX.h @@ -1468,13 +1468,6 @@ return data().HasIrrelevantDestructor; } - /// Determine whether the triviality for the purpose of calls for this class - /// is overridden to be trivial because this class or the type of one of its - /// subobjects has attribute "trivial_abi". - bool hasTrivialABIOverride() const { - return canPassInRegisters() && hasNonTrivialDestructor(); - } - /// \brief Determine whether this class has a non-literal or/ volatile type /// non-static data member or base class. bool hasNonLiteralTypeFieldsOrBases() const { Index: cfe/trunk/include/clang/AST/Type.h =================================================================== --- cfe/trunk/include/clang/AST/Type.h +++ cfe/trunk/include/clang/AST/Type.h @@ -808,11 +808,6 @@ /// Return true if this is a trivially copyable type (C++0x [basic.types]p9) bool isTriviallyCopyableType(const ASTContext &Context) const; - /// Determine whether this is a class whose triviality for the purpose of - /// calls is overridden to be trivial because the class or the type of one of - /// its subobjects has attribute "trivial_abi". - bool hasTrivialABIOverride() const; - // Don't promise in the API that anything besides 'const' can be // easily added. Index: cfe/trunk/include/clang/Basic/LangOptions.h =================================================================== --- cfe/trunk/include/clang/Basic/LangOptions.h +++ cfe/trunk/include/clang/Basic/LangOptions.h @@ -102,6 +102,23 @@ MSVC2015 = 19 }; + /// Clang versions with different platform ABI conformance. + enum class ClangABI { + /// Attempt to be ABI-compatible with code generated by Clang 3.8.x + /// (SVN r257626). This causes <1 x long long> to be passed in an + /// integer register instead of an SSE register on x64_64. + Ver3_8, + + /// Attempt to be ABI-compatible with code generated by Clang 4.0.x + /// (SVN r291814). This causes move operations to be ignored when + /// determining whether a class type can be passed or returned directly. + Ver4, + + /// Conform to the underlying platform's C and C++ ABIs as closely + /// as we can. + Latest + }; + enum FPContractModeKind { // Form fused FP ops only where result will not be affected. FPC_Off, Index: cfe/trunk/include/clang/Basic/LangOptions.def =================================================================== --- cfe/trunk/include/clang/Basic/LangOptions.def +++ cfe/trunk/include/clang/Basic/LangOptions.def @@ -284,6 +284,10 @@ BENIGN_LANGOPT(AllowEditorPlaceholders, 1, 0, "allow editor placeholders in source") +ENUM_LANGOPT(ClangABICompat, ClangABI, 4, ClangABI::Latest, + "version of Clang that we should attempt to be ABI-compatible " + "with") + #undef LANGOPT #undef COMPATIBLE_LANGOPT #undef BENIGN_LANGOPT Index: cfe/trunk/include/clang/Basic/TargetInfo.h =================================================================== --- cfe/trunk/include/clang/Basic/TargetInfo.h +++ cfe/trunk/include/clang/Basic/TargetInfo.h @@ -1053,6 +1053,14 @@ } } + enum CallingConvKind { + CCK_Default, + CCK_ClangABI4OrPS4, + CCK_MicrosoftX86_64 + }; + + virtual CallingConvKind getCallingConvKind(bool ClangABICompat4) const; + /// Controls if __builtin_longjmp / __builtin_setjmp can be lowered to /// llvm.eh.sjlj.longjmp / llvm.eh.sjlj.setjmp. virtual bool hasSjLjLowering() const { Index: cfe/trunk/include/clang/Frontend/CodeGenOptions.def =================================================================== --- cfe/trunk/include/clang/Frontend/CodeGenOptions.def +++ cfe/trunk/include/clang/Frontend/CodeGenOptions.def @@ -138,9 +138,6 @@ CODEGENOPT(OmitLeafFramePointer , 1, 0) ///< Set when -momit-leaf-frame-pointer is ///< enabled. -/// A version of Clang that we should attempt to be ABI-compatible with. -ENUM_CODEGENOPT(ClangABICompat, ClangABI, 4, ClangABI::Latest) - VALUE_CODEGENOPT(OptimizationLevel, 2, 0) ///< The -O[0-3] option specified. VALUE_CODEGENOPT(OptimizeSize, 2, 0) ///< If -Os (==1) or -Oz (==2) is specified. Index: cfe/trunk/lib/AST/ASTContext.cpp =================================================================== --- cfe/trunk/lib/AST/ASTContext.cpp +++ cfe/trunk/lib/AST/ASTContext.cpp @@ -2643,9 +2643,11 @@ } bool ASTContext::isParamDestroyedInCallee(QualType T) const { - return getTargetInfo().getCXXABI().areArgsDestroyedLeftToRightInCallee() || - T.hasTrivialABIOverride() || - T.isDestructedType() == QualType::DK_nontrivial_c_struct; + if (getTargetInfo().getCXXABI().areArgsDestroyedLeftToRightInCallee()) + return true; + if (const auto *RT = T->getBaseElementTypeUnsafe()->getAs()) + return RT->getDecl()->isParamDestroyedInCallee(); + return false; } /// getComplexType - Return the uniqued reference to the type for a complex Index: cfe/trunk/lib/AST/Decl.cpp =================================================================== --- cfe/trunk/lib/AST/Decl.cpp +++ cfe/trunk/lib/AST/Decl.cpp @@ -3951,7 +3951,7 @@ LoadedFieldsFromExternalStorage(false), NonTrivialToPrimitiveDefaultInitialize(false), NonTrivialToPrimitiveCopy(false), NonTrivialToPrimitiveDestroy(false), - CanPassInRegisters(true) { + CanPassInRegisters(true), ParamDestroyedInCallee(false) { assert(classof(static_cast(this)) && "Invalid Kind!"); } Index: cfe/trunk/lib/AST/DeclCXX.cpp =================================================================== --- cfe/trunk/lib/AST/DeclCXX.cpp +++ cfe/trunk/lib/AST/DeclCXX.cpp @@ -801,7 +801,17 @@ struct DefinitionData &Data = data(); Data.PlainOldData = false; Data.HasTrivialSpecialMembers = 0; - Data.HasTrivialSpecialMembersForCall = 0; + + // __strong or __weak fields do not make special functions non-trivial + // for the purpose of calls. + Qualifiers::ObjCLifetime LT = T.getQualifiers().getObjCLifetime(); + if (LT != Qualifiers::OCL_Strong && LT != Qualifiers::OCL_Weak) + data().HasTrivialSpecialMembersForCall = 0; + + // Structs with __weak fields should never be passed directly. + if (LT == Qualifiers::OCL_Weak) + setCanPassInRegisters(false); + Data.HasIrrelevantDestructor = false; } else if (!Context.getLangOpts().ObjCAutoRefCount) { setHasObjectMember(true); Index: cfe/trunk/lib/AST/Type.cpp =================================================================== --- cfe/trunk/lib/AST/Type.cpp +++ cfe/trunk/lib/AST/Type.cpp @@ -2195,12 +2195,6 @@ return false; } -bool QualType::hasTrivialABIOverride() const { - if (const auto *RD = getTypePtr()->getAsCXXRecordDecl()) - return RD->hasTrivialABIOverride(); - return false; -} - bool QualType::isNonWeakInMRRWithObjCWeak(const ASTContext &Context) const { return !Context.getLangOpts().ObjCAutoRefCount && Context.getLangOpts().ObjCWeak && Index: cfe/trunk/lib/Basic/TargetInfo.cpp =================================================================== --- cfe/trunk/lib/Basic/TargetInfo.cpp +++ cfe/trunk/lib/Basic/TargetInfo.cpp @@ -357,6 +357,14 @@ return true; } +TargetInfo::CallingConvKind +TargetInfo::getCallingConvKind(bool ClangABICompat4) const { + if (getCXXABI() != TargetCXXABI::Microsoft && + (ClangABICompat4 || getTriple().getOS() == llvm::Triple::PS4)) + return CCK_ClangABI4OrPS4; + return CCK_Default; +} + LangAS TargetInfo::getOpenCLTypeAddrSpace(OpenCLTypeKind TK) const { switch (TK) { case OCLTK_Image: Index: cfe/trunk/lib/Basic/Targets/X86.h =================================================================== --- cfe/trunk/lib/Basic/Targets/X86.h +++ cfe/trunk/lib/Basic/Targets/X86.h @@ -728,6 +728,11 @@ Builder.defineMacro("_M_X64", "100"); Builder.defineMacro("_M_AMD64", "100"); } + + TargetInfo::CallingConvKind + getCallingConvKind(bool ClangABICompat4) const override { + return CCK_MicrosoftX86_64; + } }; // x86-64 MinGW target Index: cfe/trunk/lib/CodeGen/CGCall.cpp =================================================================== --- cfe/trunk/lib/CodeGen/CGCall.cpp +++ cfe/trunk/lib/CodeGen/CGCall.cpp @@ -3540,24 +3540,13 @@ else Slot = CreateAggTemp(type, "agg.tmp"); - bool DestroyedInCallee = true, NeedsEHCleanup = true; - if (const auto *RD = type->getAsCXXRecordDecl()) { - DestroyedInCallee = - RD && RD->hasNonTrivialDestructor() && - (CGM.getCXXABI().getRecordArgABI(RD) != CGCXXABI::RAA_Default || - RD->hasTrivialABIOverride()); - } else { - NeedsEHCleanup = needsEHCleanup(type.isDestructedType()); - } - - if (DestroyedInCallee) - Slot.setExternallyDestructed(); + Slot.setExternallyDestructed(); EmitAggExpr(E, Slot); RValue RV = Slot.asRValue(); args.add(RV, type); - if (DestroyedInCallee && NeedsEHCleanup) { + if (type->getAsCXXRecordDecl() || needsEHCleanup(type.isDestructedType())) { // Create a no-op GEP between the placeholder and the cleanup so we can // RAUW it successfully. It also serves as a marker of the first // instruction where the cleanup is active. Index: cfe/trunk/lib/CodeGen/ItaniumCXXABI.cpp =================================================================== --- cfe/trunk/lib/CodeGen/ItaniumCXXABI.cpp +++ cfe/trunk/lib/CodeGen/ItaniumCXXABI.cpp @@ -63,13 +63,6 @@ bool classifyReturnType(CGFunctionInfo &FI) const override; bool passClassIndirect(const CXXRecordDecl *RD) const { - // Clang <= 4 used the pre-C++11 rule, which ignores move operations. - // The PS4 platform ABI follows the behavior of Clang 3.2. - if (CGM.getCodeGenOpts().getClangABICompat() <= - CodeGenOptions::ClangABI::Ver4 || - CGM.getTriple().getOS() == llvm::Triple::PS4) - return RD->hasNonTrivialDestructorForCall() || - RD->hasNonTrivialCopyConstructorForCall(); return !canCopyArgument(RD); } Index: cfe/trunk/lib/CodeGen/MicrosoftCXXABI.cpp =================================================================== --- cfe/trunk/lib/CodeGen/MicrosoftCXXABI.cpp +++ cfe/trunk/lib/CodeGen/MicrosoftCXXABI.cpp @@ -829,60 +829,7 @@ return RAA_Default; case llvm::Triple::x86_64: - bool CopyCtorIsTrivial = false, CopyCtorIsTrivialForCall = false; - bool DtorIsTrivialForCall = false; - - // If a class has at least one non-deleted, trivial copy constructor, it - // is passed according to the C ABI. Otherwise, it is passed indirectly. - // - // Note: This permits classes with non-trivial copy or move ctors to be - // passed in registers, so long as they *also* have a trivial copy ctor, - // which is non-conforming. - if (RD->needsImplicitCopyConstructor()) { - if (!RD->defaultedCopyConstructorIsDeleted()) { - if (RD->hasTrivialCopyConstructor()) - CopyCtorIsTrivial = true; - if (RD->hasTrivialCopyConstructorForCall()) - CopyCtorIsTrivialForCall = true; - } - } else { - for (const CXXConstructorDecl *CD : RD->ctors()) { - if (CD->isCopyConstructor() && !CD->isDeleted()) { - if (CD->isTrivial()) - CopyCtorIsTrivial = true; - if (CD->isTrivialForCall()) - CopyCtorIsTrivialForCall = true; - } - } - } - - if (RD->needsImplicitDestructor()) { - if (!RD->defaultedDestructorIsDeleted() && - RD->hasTrivialDestructorForCall()) - DtorIsTrivialForCall = true; - } else if (const auto *D = RD->getDestructor()) { - if (!D->isDeleted() && D->isTrivialForCall()) - DtorIsTrivialForCall = true; - } - - // If the copy ctor and dtor are both trivial-for-calls, pass direct. - if (CopyCtorIsTrivialForCall && DtorIsTrivialForCall) - return RAA_Default; - - // If a class has a destructor, we'd really like to pass it indirectly - // because it allows us to elide copies. Unfortunately, MSVC makes that - // impossible for small types, which it will pass in a single register or - // stack slot. Most objects with dtors are large-ish, so handle that early. - // We can't call out all large objects as being indirect because there are - // multiple x64 calling conventions and the C++ ABI code shouldn't dictate - // how we pass large POD types. - - // Note: This permits small classes with nontrivial destructors to be - // passed in registers, which is non-conforming. - if (CopyCtorIsTrivial && - getContext().getTypeSize(RD->getTypeForDecl()) <= 64) - return RAA_Default; - return RAA_Indirect; + return !canCopyArgument(RD) ? RAA_Indirect : RAA_Default; } llvm_unreachable("invalid enum"); Index: cfe/trunk/lib/CodeGen/TargetInfo.cpp =================================================================== --- cfe/trunk/lib/CodeGen/TargetInfo.cpp +++ cfe/trunk/lib/CodeGen/TargetInfo.cpp @@ -2131,8 +2131,8 @@ /// classify it as INTEGER (for compatibility with older clang compilers). bool classifyIntegerMMXAsSSE() const { // Clang <= 3.8 did not do this. - if (getCodeGenOpts().getClangABICompat() <= - CodeGenOptions::ClangABI::Ver3_8) + if (getContext().getLangOpts().getClangABICompat() <= + LangOptions::ClangABI::Ver3_8) return false; const llvm::Triple &Triple = getTarget().getTriple(); Index: cfe/trunk/lib/Frontend/CompilerInvocation.cpp =================================================================== --- cfe/trunk/lib/Frontend/CompilerInvocation.cpp +++ cfe/trunk/lib/Frontend/CompilerInvocation.cpp @@ -633,33 +633,6 @@ if (!Opts.ProfileInstrumentUsePath.empty()) setPGOUseInstrumentor(Opts, Opts.ProfileInstrumentUsePath); - if (Arg *A = Args.getLastArg(OPT_fclang_abi_compat_EQ)) { - Opts.setClangABICompat(CodeGenOptions::ClangABI::Latest); - - StringRef Ver = A->getValue(); - std::pair VerParts = Ver.split('.'); - unsigned Major, Minor = 0; - - // Check the version number is valid: either 3.x (0 <= x <= 9) or - // y or y.0 (4 <= y <= current version). - if (!VerParts.first.startswith("0") && - !VerParts.first.getAsInteger(10, Major) && - 3 <= Major && Major <= CLANG_VERSION_MAJOR && - (Major == 3 ? VerParts.second.size() == 1 && - !VerParts.second.getAsInteger(10, Minor) - : VerParts.first.size() == Ver.size() || - VerParts.second == "0")) { - // Got a valid version number. - if (Major == 3 && Minor <= 8) - Opts.setClangABICompat(CodeGenOptions::ClangABI::Ver3_8); - else if (Major <= 4) - Opts.setClangABICompat(CodeGenOptions::ClangABI::Ver4); - } else if (Ver != "latest") { - Diags.Report(diag::err_drv_invalid_value) - << A->getAsString(Args) << A->getValue(); - } - } - Opts.CoverageMapping = Args.hasFlag(OPT_fcoverage_mapping, OPT_fno_coverage_mapping, false); Opts.DumpCoverageMapping = Args.hasArg(OPT_dump_coverage_mapping); @@ -2670,6 +2643,33 @@ // -fallow-editor-placeholders Opts.AllowEditorPlaceholders = Args.hasArg(OPT_fallow_editor_placeholders); + + if (Arg *A = Args.getLastArg(OPT_fclang_abi_compat_EQ)) { + Opts.setClangABICompat(LangOptions::ClangABI::Latest); + + StringRef Ver = A->getValue(); + std::pair VerParts = Ver.split('.'); + unsigned Major, Minor = 0; + + // Check the version number is valid: either 3.x (0 <= x <= 9) or + // y or y.0 (4 <= y <= current version). + if (!VerParts.first.startswith("0") && + !VerParts.first.getAsInteger(10, Major) && + 3 <= Major && Major <= CLANG_VERSION_MAJOR && + (Major == 3 ? VerParts.second.size() == 1 && + !VerParts.second.getAsInteger(10, Minor) + : VerParts.first.size() == Ver.size() || + VerParts.second == "0")) { + // Got a valid version number. + if (Major == 3 && Minor <= 8) + Opts.setClangABICompat(LangOptions::ClangABI::Ver3_8); + else if (Major <= 4) + Opts.setClangABICompat(LangOptions::ClangABI::Ver4); + } else if (Ver != "latest") { + Diags.Report(diag::err_drv_invalid_value) + << A->getAsString(Args) << A->getValue(); + } + } } static bool isStrictlyPreprocessorAction(frontend::ActionKind Action) { Index: cfe/trunk/lib/Sema/SemaDecl.cpp =================================================================== --- cfe/trunk/lib/Sema/SemaDecl.cpp +++ cfe/trunk/lib/Sema/SemaDecl.cpp @@ -15461,8 +15461,10 @@ QualType::PrimitiveCopyKind PCK = FT.isNonTrivialToPrimitiveCopy(); if (PCK != QualType::PCK_Trivial && PCK != QualType::PCK_VolatileTrivial) Record->setNonTrivialToPrimitiveCopy(true); - if (FT.isDestructedType()) + if (FT.isDestructedType()) { Record->setNonTrivialToPrimitiveDestroy(true); + Record->setParamDestroyedInCallee(true); + } if (!FT.canPassInRegisters()) Record->setCanPassInRegisters(false); } Index: cfe/trunk/lib/Sema/SemaDeclCXX.cpp =================================================================== --- cfe/trunk/lib/Sema/SemaDeclCXX.cpp +++ cfe/trunk/lib/Sema/SemaDeclCXX.cpp @@ -5791,12 +5791,21 @@ } } -/// Determine whether a type is permitted to be passed or returned in -/// registers, per C++ [class.temporary]p3. -static bool computeCanPassInRegisters(Sema &S, CXXRecordDecl *D) { +/// Determine whether a type would be destructed in the callee if it had a +/// non-trivial destructor. The rules here are based on C++ [class.temporary]p3, +/// which determines whether a struct can be passed to or returned from +/// functions in registers. +static bool paramCanBeDestroyedInCallee(Sema &S, CXXRecordDecl *D, + TargetInfo::CallingConvKind CCK) { if (D->isDependentType() || D->isInvalidDecl()) return false; + // Clang <= 4 used the pre-C++11 rule, which ignores move operations. + // The PS4 platform ABI follows the behavior of Clang 3.2. + if (CCK == TargetInfo::CCK_ClangABI4OrPS4) + return !D->hasNonTrivialDestructorForCall() && + !D->hasNonTrivialCopyConstructorForCall(); + // Per C++ [class.temporary]p3, the relevant condition is: // each copy constructor, move constructor, and destructor of X is // either trivial or deleted, and X has at least one non-deleted copy @@ -5838,6 +5847,77 @@ return HasNonDeletedCopyOrMove; } +static bool computeCanPassInRegister(bool DestroyedInCallee, + const CXXRecordDecl *RD, + TargetInfo::CallingConvKind CCK, + Sema &S) { + if (RD->isDependentType() || RD->isInvalidDecl()) + return true; + + // The param cannot be passed in registers if CanPassInRegisters is already + // set to false. + if (!RD->canPassInRegisters()) + return false; + + if (CCK != TargetInfo::CCK_MicrosoftX86_64) + return DestroyedInCallee; + + bool CopyCtorIsTrivial = false, CopyCtorIsTrivialForCall = false; + bool DtorIsTrivialForCall = false; + + // If a class has at least one non-deleted, trivial copy constructor, it + // is passed according to the C ABI. Otherwise, it is passed indirectly. + // + // Note: This permits classes with non-trivial copy or move ctors to be + // passed in registers, so long as they *also* have a trivial copy ctor, + // which is non-conforming. + if (RD->needsImplicitCopyConstructor()) { + if (!RD->defaultedCopyConstructorIsDeleted()) { + if (RD->hasTrivialCopyConstructor()) + CopyCtorIsTrivial = true; + if (RD->hasTrivialCopyConstructorForCall()) + CopyCtorIsTrivialForCall = true; + } + } else { + for (const CXXConstructorDecl *CD : RD->ctors()) { + if (CD->isCopyConstructor() && !CD->isDeleted()) { + if (CD->isTrivial()) + CopyCtorIsTrivial = true; + if (CD->isTrivialForCall()) + CopyCtorIsTrivialForCall = true; + } + } + } + + if (RD->needsImplicitDestructor()) { + if (!RD->defaultedDestructorIsDeleted() && + RD->hasTrivialDestructorForCall()) + DtorIsTrivialForCall = true; + } else if (const auto *D = RD->getDestructor()) { + if (!D->isDeleted() && D->isTrivialForCall()) + DtorIsTrivialForCall = true; + } + + // If the copy ctor and dtor are both trivial-for-calls, pass direct. + if (CopyCtorIsTrivialForCall && DtorIsTrivialForCall) + return true; + + // If a class has a destructor, we'd really like to pass it indirectly + // because it allows us to elide copies. Unfortunately, MSVC makes that + // impossible for small types, which it will pass in a single register or + // stack slot. Most objects with dtors are large-ish, so handle that early. + // We can't call out all large objects as being indirect because there are + // multiple x64 calling conventions and the C++ ABI code shouldn't dictate + // how we pass large POD types. + + // Note: This permits small classes with nontrivial destructors to be + // passed in registers, which is non-conforming. + if (CopyCtorIsTrivial && + S.getASTContext().getTypeSize(RD->getTypeForDecl()) <= 64) + return true; + return false; +} + /// \brief Perform semantic checks on a class definition that has been /// completing, introducing implicitly-declared members, checking for /// abstract types, etc. @@ -6001,7 +6081,17 @@ checkClassLevelDLLAttribute(Record); - Record->setCanPassInRegisters(computeCanPassInRegisters(*this, Record)); + bool ClangABICompat4 = + Context.getLangOpts().getClangABICompat() <= LangOptions::ClangABI::Ver4; + TargetInfo::CallingConvKind CCK = + Context.getTargetInfo().getCallingConvKind(ClangABICompat4); + bool DestroyedInCallee = paramCanBeDestroyedInCallee(*this, Record, CCK); + + if (Record->hasNonTrivialDestructor()) + Record->setParamDestroyedInCallee(DestroyedInCallee); + + Record->setCanPassInRegisters( + computeCanPassInRegister(DestroyedInCallee, Record, CCK, *this)); } /// Look up the special member function that would be called by a special Index: cfe/trunk/lib/Serialization/ASTReaderDecl.cpp =================================================================== --- cfe/trunk/lib/Serialization/ASTReaderDecl.cpp +++ cfe/trunk/lib/Serialization/ASTReaderDecl.cpp @@ -743,6 +743,7 @@ RD->setNonTrivialToPrimitiveCopy(Record.readInt()); RD->setNonTrivialToPrimitiveDestroy(Record.readInt()); RD->setCanPassInRegisters(Record.readInt()); + RD->setParamDestroyedInCallee(Record.readInt()); return Redecl; } @@ -4109,6 +4110,7 @@ OldDD && (OldDD->Definition != RD || !Reader.PendingFakeDefinitionData.count(OldDD)); RD->setCanPassInRegisters(Record.readInt()); + RD->setParamDestroyedInCallee(Record.readInt()); ReadCXXRecordDefinition(RD, /*Update*/true); // Visible update is handled separately. Index: cfe/trunk/lib/Serialization/ASTWriter.cpp =================================================================== --- cfe/trunk/lib/Serialization/ASTWriter.cpp +++ cfe/trunk/lib/Serialization/ASTWriter.cpp @@ -5194,6 +5194,7 @@ auto *RD = cast(D); UpdatedDeclContexts.insert(RD->getPrimaryContext()); Record.push_back(RD->canPassInRegisters()); + Record.push_back(RD->isParamDestroyedInCallee()); Record.AddCXXDefinitionData(RD); Record.AddOffset(WriteDeclContextLexicalBlock( *Context, const_cast(RD))); Index: cfe/trunk/lib/Serialization/ASTWriterDecl.cpp =================================================================== --- cfe/trunk/lib/Serialization/ASTWriterDecl.cpp +++ cfe/trunk/lib/Serialization/ASTWriterDecl.cpp @@ -470,6 +470,7 @@ Record.push_back(D->isNonTrivialToPrimitiveCopy()); Record.push_back(D->isNonTrivialToPrimitiveDestroy()); Record.push_back(D->canPassInRegisters()); + Record.push_back(D->isParamDestroyedInCallee()); if (D->getDeclContext() == D->getLexicalDeclContext() && !D->hasAttrs() && @@ -1912,6 +1913,8 @@ // isNonTrivialToPrimitiveDestroy Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // canPassInRegisters + // isParamDestroyedInCallee + Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // DC Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::VBR, 6)); // LexicalOffset Index: cfe/trunk/test/CodeGenCXX/microsoft-abi-sret-and-byval.cpp =================================================================== --- cfe/trunk/test/CodeGenCXX/microsoft-abi-sret-and-byval.cpp +++ cfe/trunk/test/CodeGenCXX/microsoft-abi-sret-and-byval.cpp @@ -172,12 +172,9 @@ void call_small_arg_with_dtor() { small_arg_with_dtor(SmallWithDtor()); } -// The temporary is copied, so it's destroyed in the caller as well as the -// callee. // WIN64-LABEL: define dso_local void @"?call_small_arg_with_dtor@@YAXXZ"() // WIN64: call %struct.SmallWithDtor* @"??0SmallWithDtor@@QEAA@XZ" // WIN64: call void @"?small_arg_with_dtor@@YAXUSmallWithDtor@@@Z"(i32 %{{.*}}) -// WIN64: call void @"??1SmallWithDtor@@QEAA@XZ" // WIN64: ret void // Test that references aren't destroyed in the callee. Index: cfe/trunk/test/CodeGenObjCXX/arc-special-member-functions.mm =================================================================== --- cfe/trunk/test/CodeGenObjCXX/arc-special-member-functions.mm +++ cfe/trunk/test/CodeGenObjCXX/arc-special-member-functions.mm @@ -31,6 +31,8 @@ void test_ObjCMember_copy_assign(ObjCMember m1, ObjCMember m2) { // CHECK: {{call.*_ZN10ObjCMemberaSERKS_}} m1 = m2; + // CHECK-NEXT: call void @_ZN10ObjCMemberD1Ev( + // CHECK-NEXT: call void @_ZN10ObjCMemberD1Ev( // CHECK-NEXT: ret void } @@ -58,6 +60,8 @@ void test_ObjCArrayMember_copy_assign(ObjCArrayMember m1, ObjCArrayMember m2) { // CHECK: {{call.*@_ZN15ObjCArrayMemberaSERKS_}} m1 = m2; + // CHECK-NEXT: call void @_ZN15ObjCArrayMemberD1Ev( + // CHECK-NEXT: call void @_ZN15ObjCArrayMemberD1Ev( // CHECK-NEXT: ret void } @@ -79,7 +83,8 @@ void test_ObjCBlockMember_copy_construct_destruct(ObjCBlockMember m1) { // CHECK: call void @_ZN15ObjCBlockMemberC1ERKS_ ObjCBlockMember m2 = m1; - // CHECK-NEXT: call void @_ZN15ObjCBlockMemberD1Ev + // CHECK-NEXT: call void @_ZN15ObjCBlockMemberD1Ev( + // CHECK-NEXT: call void @_ZN15ObjCBlockMemberD1Ev( // CHECK-NEXT: ret void } @@ -87,6 +92,8 @@ void test_ObjCBlockMember_copy_assign(ObjCBlockMember m1, ObjCBlockMember m2) { // CHECK: {{call.*_ZN15ObjCBlockMemberaSERKS_}} m1 = m2; + // CHECK-NEXT: call void @_ZN15ObjCBlockMemberD1Ev( + // CHECK-NEXT: call void @_ZN15ObjCBlockMemberD1Ev( // CHECK-NEXT: ret void } Index: cfe/trunk/test/CodeGenObjCXX/objc-struct-cxx-abi.mm =================================================================== --- cfe/trunk/test/CodeGenObjCXX/objc-struct-cxx-abi.mm +++ cfe/trunk/test/CodeGenObjCXX/objc-struct-cxx-abi.mm @@ -0,0 +1,151 @@ +// RUN: %clang_cc1 -triple arm64-apple-ios11 -std=c++11 -fobjc-arc -fobjc-weak -fobjc-runtime-has-weak -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple arm64-apple-ios11 -std=c++11 -fobjc-arc -fobjc-weak -fobjc-runtime-has-weak -fclang-abi-compat=4.0 -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple arm64-apple-ios11 -std=c++11 -fobjc-arc -fobjc-weak -fobjc-runtime-has-weak -emit-llvm -o - -DTRIVIALABI %s | FileCheck %s +// RUN: %clang_cc1 -triple arm64-apple-ios11 -std=c++11 -fobjc-arc -fobjc-weak -fobjc-runtime-has-weak -fclang-abi-compat=4.0 -emit-llvm -o - -DTRIVIALABI %s | FileCheck %s + +// Check that structs consisting solely of __strong or __weak pointer fields are +// destructed in the callee function and structs consisting solely of __strong +// pointer fields are passed directly. + +// CHECK: %[[STRUCT_STRONGWEAK:.*]] = type { i8*, i8* } +// CHECK: %[[STRUCT_STRONG:.*]] = type { i8* } +// CHECK: %[[STRUCT_S:.*]] = type { i8* } +// CHECK: %[[STRUCT_CONTAINSNONTRIVIAL:.*]] = type { %{{.*}}, i8* } + +#ifdef TRIVIALABI +struct __attribute__((trivial_abi)) StrongWeak { +#else +struct StrongWeak { +#endif + id fstrong; + __weak id fweak; +}; + +#ifdef TRIVIALABI +struct __attribute__((trivial_abi)) Strong { +#else +struct Strong { +#endif + id fstrong; +}; + +template +#ifdef TRIVIALABI +struct __attribute__((trivial_abi)) S { +#else +struct S { +#endif + T a; +}; + +struct NonTrivial { + NonTrivial(); + NonTrivial(const NonTrivial &); + ~NonTrivial(); + int *a; +}; + +// This struct is not passed directly nor destructed in the callee because f0 +// has type NonTrivial. +struct ContainsNonTrivial { + NonTrivial f0; + id f1; +}; + +// CHECK: define void @_Z19testParamStrongWeak10StrongWeak(%[[STRUCT_STRONGWEAK]]* %{{.*}}) +// CHECK: call %struct.StrongWeak* @_ZN10StrongWeakD1Ev( +// CHECK-NEXT: ret void + +void testParamStrongWeak(StrongWeak a) { +} + +// CHECK: define void @_Z18testCallStrongWeakP10StrongWeak(%[[STRUCT_STRONGWEAK]]* %[[A:.*]]) +// CHECK: %[[A_ADDR:.*]] = alloca %[[STRUCT_STRONGWEAK]]*, align 8 +// CHECK: %[[AGG_TMP:.*]] = alloca %[[STRUCT_STRONGWEAK]], align 8 +// CHECK: store %[[STRUCT_STRONGWEAK]]* %[[A]], %[[STRUCT_STRONGWEAK]]** %[[A_ADDR]], align 8 +// CHECK: %[[V0:.*]] = load %[[STRUCT_STRONGWEAK]]*, %[[STRUCT_STRONGWEAK]]** %[[A_ADDR]], align 8 +// CHECK: %[[CALL:.*]] = call %[[STRUCT_STRONGWEAK]]* @_ZN10StrongWeakC1ERKS_(%[[STRUCT_STRONGWEAK]]* %[[AGG_TMP]], %[[STRUCT_STRONGWEAK]]* dereferenceable(16) %[[V0]]) +// CHECK: call void @_Z19testParamStrongWeak10StrongWeak(%[[STRUCT_STRONGWEAK]]* %[[AGG_TMP]]) +// CHECK-NOT: call +// CHECK: ret void + +void testCallStrongWeak(StrongWeak *a) { + testParamStrongWeak(*a); +} + +// CHECK: define void @_Z20testReturnStrongWeakP10StrongWeak(%[[STRUCT_STRONGWEAK:.*]]* noalias sret %[[AGG_RESULT:.*]], %[[STRUCT_STRONGWEAK]]* %[[A:.*]]) +// CHECK: %[[A_ADDR:.*]] = alloca %[[STRUCT_STRONGWEAK]]*, align 8 +// CHECK: store %[[STRUCT_STRONGWEAK]]* %[[A]], %[[STRUCT_STRONGWEAK]]** %[[A_ADDR]], align 8 +// CHECK: %[[V0:.*]] = load %[[STRUCT_STRONGWEAK]]*, %[[STRUCT_STRONGWEAK]]** %[[A_ADDR]], align 8 +// CHECK: %[[CALL:.*]] = call %[[STRUCT_STRONGWEAK]]* @_ZN10StrongWeakC1ERKS_(%[[STRUCT_STRONGWEAK]]* %[[AGG_RESULT]], %[[STRUCT_STRONGWEAK]]* dereferenceable(16) %[[V0]]) +// CHECK: ret void + +StrongWeak testReturnStrongWeak(StrongWeak *a) { + return *a; +} + +// CHECK: define void @_Z15testParamStrong6Strong(i64 %[[A_COERCE:.*]]) +// CHECK: %[[A:.*]] = alloca %[[STRUCT_STRONG]], align 8 +// CHECK: %[[COERCE_DIVE:.*]] = getelementptr inbounds %[[STRUCT_STRONG]], %[[STRUCT_STRONG]]* %[[A]], i32 0, i32 0 +// CHECK: %[[COERCE_VAL_IP:.*]] = inttoptr i64 %[[A_COERCE]] to i8* +// CHECK: store i8* %[[COERCE_VAL_IP]], i8** %[[COERCE_DIVE]], align 8 +// CHECK: %[[CALL:.*]] = call %[[STRUCT_STRONG]]* @_ZN6StrongD1Ev(%[[STRUCT_STRONG]]* %[[A]]) +// CHECK: ret void + +// CHECK: define linkonce_odr %[[STRUCT_STRONG]]* @_ZN6StrongD1Ev( + +void testParamStrong(Strong a) { +} + +// CHECK: define void @_Z14testCallStrongP6Strong(%[[STRUCT_STRONG]]* %[[A:.*]]) +// CHECK: %[[A_ADDR:.*]] = alloca %[[STRUCT_STRONG]]*, align 8 +// CHECK: %[[AGG_TMP:.*]] = alloca %[[STRUCT_STRONG]], align 8 +// CHECK: store %[[STRUCT_STRONG]]* %[[A]], %[[STRUCT_STRONG]]** %[[A_ADDR]], align 8 +// CHECK: %[[V0:.*]] = load %[[STRUCT_STRONG]]*, %[[STRUCT_STRONG]]** %[[A_ADDR]], align 8 +// CHECK: %[[CALL:.*]] = call %[[STRUCT_STRONG]]* @_ZN6StrongC1ERKS_(%[[STRUCT_STRONG]]* %[[AGG_TMP]], %[[STRUCT_STRONG]]* dereferenceable(8) %[[V0]]) +// CHECK: %[[COERCE_DIVE:.*]] = getelementptr inbounds %[[STRUCT_STRONG]], %[[STRUCT_STRONG]]* %[[AGG_TMP]], i32 0, i32 0 +// CHECK: %[[V1:.*]] = load i8*, i8** %[[COERCE_DIVE]], align 8 +// CHECK: %[[COERCE_VAL_PI:.*]] = ptrtoint i8* %[[V1]] to i64 +// CHECK: call void @_Z15testParamStrong6Strong(i64 %[[COERCE_VAL_PI]]) +// CHECK: ret void + +void testCallStrong(Strong *a) { + testParamStrong(*a); +} + +// CHECK: define i64 @_Z16testReturnStrongP6Strong(%[[STRUCT_STRONG]]* %[[A:.*]]) +// CHECK: %[[RETVAL:.*]] = alloca %[[STRUCT_STRONG]], align 8 +// CHECK: %[[A_ADDR:.*]] = alloca %[[STRUCT_STRONG]]*, align 8 +// CHECK: store %[[STRUCT_STRONG]]* %[[A]], %[[STRUCT_STRONG]]** %[[A_ADDR]], align 8 +// CHECK: %[[V0:.*]] = load %[[STRUCT_STRONG]]*, %[[STRUCT_STRONG]]** %[[A_ADDR]], align 8 +// CHECK: %[[CALL:.*]] = call %[[STRUCT_STRONG]]* @_ZN6StrongC1ERKS_(%[[STRUCT_STRONG]]* %[[RETVAL]], %[[STRUCT_STRONG]]* dereferenceable(8) %[[V0]]) +// CHECK: %[[COERCE_DIVE:.*]] = getelementptr inbounds %[[STRUCT_STRONG]], %[[STRUCT_STRONG]]* %[[RETVAL]], i32 0, i32 0 +// CHECK: %[[V1:.*]] = load i8*, i8** %[[COERCE_DIVE]], align 8 +// CHECK: %[[COERCE_VAL_PI:.*]] = ptrtoint i8* %[[V1]] to i64 +// CHECK: ret i64 %[[COERCE_VAL_PI]] + +Strong testReturnStrong(Strong *a) { + return *a; +} + +// CHECK: define void @_Z21testParamWeakTemplate1SIU6__weakP11objc_objectE(%[[STRUCT_S]]* %{{.*}}) +// CHECK: call %struct.S* @_ZN1SIU6__weakP11objc_objectED1Ev( +// CHECK-NEXT: ret void + +void testParamWeakTemplate(S<__weak id> a) { +} + +// CHECK: define void @_Z27testParamContainsNonTrivial18ContainsNonTrivial(%[[STRUCT_CONTAINSNONTRIVIAL]]* %{{.*}}) +// CHECK-NOT: call +// CHECK: ret void + +void testParamContainsNonTrivial(ContainsNonTrivial a) { +} + +// CHECK: define void @_Z26testCallContainsNonTrivialP18ContainsNonTrivial( +// CHECK: call void @_Z27testParamContainsNonTrivial18ContainsNonTrivial(%[[STRUCT_CONTAINSNONTRIVIAL]]* %{{.*}}) +// CHECK: call %struct.ContainsNonTrivial* @_ZN18ContainsNonTrivialD1Ev(%[[STRUCT_CONTAINSNONTRIVIAL]]* %{{.*}}) + +void testCallContainsNonTrivial(ContainsNonTrivial *a) { + testParamContainsNonTrivial(*a); +} Index: cfe/trunk/test/CodeGenObjCXX/property-dot-copy-elision.mm =================================================================== --- cfe/trunk/test/CodeGenObjCXX/property-dot-copy-elision.mm +++ cfe/trunk/test/CodeGenObjCXX/property-dot-copy-elision.mm @@ -1,11 +1,13 @@ // RUN: %clang_cc1 -triple x86_64-apple-darwin10 -emit-llvm -std=c++1z -fobjc-arc -o - %s | FileCheck %s struct S0 { + ~S0(); id f; }; struct S1 { S1(); + ~S1(); S1(S0); id f; }; Index: cfe/trunk/test/CodeGenObjCXX/trivial_abi.mm =================================================================== --- cfe/trunk/test/CodeGenObjCXX/trivial_abi.mm +++ cfe/trunk/test/CodeGenObjCXX/trivial_abi.mm @@ -1,103 +0,0 @@ -// RUN: %clang_cc1 -triple arm64-apple-ios11 -std=c++11 -fobjc-arc -fobjc-weak -fobjc-runtime-has-weak -emit-llvm -o - %s | FileCheck %s -// RUN: %clang_cc1 -triple arm64-apple-ios11 -std=c++11 -fobjc-arc -fobjc-weak -fobjc-runtime-has-weak -fclang-abi-compat=4.0 -emit-llvm -o - %s | FileCheck %s - -// CHECK: %[[STRUCT_STRONGWEAK:.*]] = type { i8*, i8* } -// CHECK: %[[STRUCT_STRONG:.*]] = type { i8* } -// CHECK: %[[STRUCT_S:.*]] = type { i8* } - -struct __attribute__((trivial_abi)) StrongWeak { - id fstrong; - __weak id fweak; -}; - -struct __attribute__((trivial_abi)) Strong { - id fstrong; -}; - -template -struct __attribute__((trivial_abi)) S { - T a; -}; - -// CHECK: define void @_Z19testParamStrongWeak10StrongWeak(%[[STRUCT_STRONGWEAK]]* %{{.*}}) -// CHECK-NOT: call -// CHECK: ret void - -void testParamStrongWeak(StrongWeak a) { -} - -// CHECK: define void @_Z18testCallStrongWeakP10StrongWeak(%[[STRUCT_STRONGWEAK]]* %[[A:.*]]) -// CHECK: %[[A_ADDR:.*]] = alloca %[[STRUCT_STRONGWEAK]]*, align 8 -// CHECK: %[[AGG_TMP:.*]] = alloca %[[STRUCT_STRONGWEAK]], align 8 -// CHECK: store %[[STRUCT_STRONGWEAK]]* %[[A]], %[[STRUCT_STRONGWEAK]]** %[[A_ADDR]], align 8 -// CHECK: %[[V0:.*]] = load %[[STRUCT_STRONGWEAK]]*, %[[STRUCT_STRONGWEAK]]** %[[A_ADDR]], align 8 -// CHECK: %[[CALL:.*]] = call %[[STRUCT_STRONGWEAK]]* @_ZN10StrongWeakC1ERKS_(%[[STRUCT_STRONGWEAK]]* %[[AGG_TMP]], %[[STRUCT_STRONGWEAK]]* dereferenceable(16) %[[V0]]) -// CHECK: call void @_Z19testParamStrongWeak10StrongWeak(%[[STRUCT_STRONGWEAK]]* %[[AGG_TMP]]) -// CHECK: %[[CALL1:.*]] = call %[[STRUCT_STRONGWEAK]]* @_ZN10StrongWeakD1Ev(%[[STRUCT_STRONGWEAK]]* %[[AGG_TMP]]) -// CHECK: ret void - -void testCallStrongWeak(StrongWeak *a) { - testParamStrongWeak(*a); -} - -// CHECK: define void @_Z20testReturnStrongWeakP10StrongWeak(%[[STRUCT_STRONGWEAK:.*]]* noalias sret %[[AGG_RESULT:.*]], %[[STRUCT_STRONGWEAK]]* %[[A:.*]]) -// CHECK: %[[A_ADDR:.*]] = alloca %[[STRUCT_STRONGWEAK]]*, align 8 -// CHECK: store %[[STRUCT_STRONGWEAK]]* %[[A]], %[[STRUCT_STRONGWEAK]]** %[[A_ADDR]], align 8 -// CHECK: %[[V0:.*]] = load %[[STRUCT_STRONGWEAK]]*, %[[STRUCT_STRONGWEAK]]** %[[A_ADDR]], align 8 -// CHECK: %[[CALL:.*]] = call %[[STRUCT_STRONGWEAK]]* @_ZN10StrongWeakC1ERKS_(%[[STRUCT_STRONGWEAK]]* %[[AGG_RESULT]], %[[STRUCT_STRONGWEAK]]* dereferenceable(16) %[[V0]]) -// CHECK: ret void - -StrongWeak testReturnStrongWeak(StrongWeak *a) { - return *a; -} - -// CHECK: define void @_Z15testParamStrong6Strong(i64 %[[A_COERCE:.*]]) -// CHECK: %[[A:.*]] = alloca %[[STRUCT_STRONG]], align 8 -// CHECK: %[[COERCE_DIVE:.*]] = getelementptr inbounds %[[STRUCT_STRONG]], %[[STRUCT_STRONG]]* %[[A]], i32 0, i32 0 -// CHECK: %[[COERCE_VAL_IP:.*]] = inttoptr i64 %[[A_COERCE]] to i8* -// CHECK: store i8* %[[COERCE_VAL_IP]], i8** %[[COERCE_DIVE]], align 8 -// CHECK: %[[CALL:.*]] = call %[[STRUCT_STRONG]]* @_ZN6StrongD1Ev(%[[STRUCT_STRONG]]* %[[A]]) -// CHECK: ret void - -// CHECK: define linkonce_odr %[[STRUCT_STRONG]]* @_ZN6StrongD1Ev( - -void testParamStrong(Strong a) { -} - -// CHECK: define void @_Z14testCallStrongP6Strong(%[[STRUCT_STRONG]]* %[[A:.*]]) -// CHECK: %[[A_ADDR:.*]] = alloca %[[STRUCT_STRONG]]*, align 8 -// CHECK: %[[AGG_TMP:.*]] = alloca %[[STRUCT_STRONG]], align 8 -// CHECK: store %[[STRUCT_STRONG]]* %[[A]], %[[STRUCT_STRONG]]** %[[A_ADDR]], align 8 -// CHECK: %[[V0:.*]] = load %[[STRUCT_STRONG]]*, %[[STRUCT_STRONG]]** %[[A_ADDR]], align 8 -// CHECK: %[[CALL:.*]] = call %[[STRUCT_STRONG]]* @_ZN6StrongC1ERKS_(%[[STRUCT_STRONG]]* %[[AGG_TMP]], %[[STRUCT_STRONG]]* dereferenceable(8) %[[V0]]) -// CHECK: %[[COERCE_DIVE:.*]] = getelementptr inbounds %[[STRUCT_STRONG]], %[[STRUCT_STRONG]]* %[[AGG_TMP]], i32 0, i32 0 -// CHECK: %[[V1:.*]] = load i8*, i8** %[[COERCE_DIVE]], align 8 -// CHECK: %[[COERCE_VAL_PI:.*]] = ptrtoint i8* %[[V1]] to i64 -// CHECK: call void @_Z15testParamStrong6Strong(i64 %[[COERCE_VAL_PI]]) -// CHECK: ret void - -void testCallStrong(Strong *a) { - testParamStrong(*a); -} - -// CHECK: define i64 @_Z16testReturnStrongP6Strong(%[[STRUCT_STRONG]]* %[[A:.*]]) -// CHECK: %[[RETVAL:.*]] = alloca %[[STRUCT_STRONG]], align 8 -// CHECK: %[[A_ADDR:.*]] = alloca %[[STRUCT_STRONG]]*, align 8 -// CHECK: store %[[STRUCT_STRONG]]* %[[A]], %[[STRUCT_STRONG]]** %[[A_ADDR]], align 8 -// CHECK: %[[V0:.*]] = load %[[STRUCT_STRONG]]*, %[[STRUCT_STRONG]]** %[[A_ADDR]], align 8 -// CHECK: %[[CALL:.*]] = call %[[STRUCT_STRONG]]* @_ZN6StrongC1ERKS_(%[[STRUCT_STRONG]]* %[[RETVAL]], %[[STRUCT_STRONG]]* dereferenceable(8) %[[V0]]) -// CHECK: %[[COERCE_DIVE:.*]] = getelementptr inbounds %[[STRUCT_STRONG]], %[[STRUCT_STRONG]]* %[[RETVAL]], i32 0, i32 0 -// CHECK: %[[V1:.*]] = load i8*, i8** %[[COERCE_DIVE]], align 8 -// CHECK: %[[COERCE_VAL_PI:.*]] = ptrtoint i8* %[[V1]] to i64 -// CHECK: ret i64 %[[COERCE_VAL_PI]] - -Strong testReturnStrong(Strong *a) { - return *a; -} - -// CHECK: define void @_Z21testParamWeakTemplate1SIU6__weakP11objc_objectE(%[[STRUCT_S]]* %{{.*}}) -// CHECK-NOT: call -// CHECK: ret void - -void testParamWeakTemplate(S<__weak id> a) { -}