Index: include/clang/AST/ASTContext.h =================================================================== --- include/clang/AST/ASTContext.h +++ include/clang/AST/ASTContext.h @@ -1179,6 +1179,10 @@ const FunctionProtoType::ExceptionSpecInfo &ESI, bool AsWritten = false); + /// Determine whether a type is a class that should be detructed in the + /// callee function. + bool isParamDestroyedInCallee(QualType T) const; + /// \brief Return the uniqued reference to the type for a complex /// number with the specified element type. QualType getComplexType(QualType T) const; Index: include/clang/AST/Decl.h =================================================================== --- include/clang/AST/Decl.h +++ include/clang/AST/Decl.h @@ -1731,6 +1731,12 @@ unsigned HasWrittenPrototype : 1; unsigned IsDeleted : 1; unsigned IsTrivial : 1; // sunk from CXXMethodDecl + + /// This flag indicates whether this function is trivial for the purpose of + /// calls. This is meaningful only when this function is a copy/move + /// constructor or a destructor. + unsigned IsTrivialForCall : 1; + unsigned IsDefaulted : 1; // sunk from CXXMethoDecl unsigned IsExplicitlyDefaulted : 1; //sunk from CXXMethodDecl unsigned HasImplicitReturnZero : 1; @@ -1845,7 +1851,8 @@ IsInline(isInlineSpecified), IsInlineSpecified(isInlineSpecified), IsExplicitSpecified(false), IsVirtualAsWritten(false), IsPure(false), HasInheritedPrototype(false), HasWrittenPrototype(true), - IsDeleted(false), IsTrivial(false), IsDefaulted(false), + IsDeleted(false), IsTrivial(false), IsTrivialForCall(false), + IsDefaulted(false), IsExplicitlyDefaulted(false), HasImplicitReturnZero(false), IsLateTemplateParsed(false), IsConstexpr(isConstexprSpecified), InstantiationIsPending(false), UsesSEHTry(false), HasSkippedBody(false), @@ -2010,6 +2017,9 @@ bool isTrivial() const { return IsTrivial; } void setTrivial(bool IT) { IsTrivial = IT; } + bool isTrivialForCall() const { return IsTrivialForCall; } + void setTrivialForCall(bool IT) { IsTrivialForCall = IT; } + /// Whether this function is defaulted per C++0x. Only valid for /// special member functions. bool isDefaulted() const { return IsDefaulted; } Index: include/clang/AST/DeclCXX.h =================================================================== --- include/clang/AST/DeclCXX.h +++ include/clang/AST/DeclCXX.h @@ -437,6 +437,11 @@ /// which have been declared but not yet defined. unsigned HasTrivialSpecialMembers : 6; + /// These bits keep track of the triviality of special functions for the + /// purpose of calls. Only the bits corresponding to SMF_CopyConstructor, + /// SMF_MoveConstructor, and SMF_Destructor are meaningful here. + unsigned HasTrivialSpecialMembersForCall : 6; + /// \brief The declared special members of this class which are known to be /// non-trivial. /// @@ -445,6 +450,12 @@ /// members which have not yet been declared. unsigned DeclaredNonTrivialSpecialMembers : 6; + /// These bits keep track of the declared special members that are + /// non-trivial for the purpose of calls. + /// Only the bits corresponding to SMF_CopyConstructor, + /// SMF_MoveConstructor, and SMF_Destructor are meaningful here. + unsigned DeclaredNonTrivialSpecialMembersForCall : 6; + /// \brief True when this class has a destructor with no semantic effect. unsigned HasIrrelevantDestructor : 1; @@ -1349,6 +1360,10 @@ return data().HasTrivialSpecialMembers & SMF_CopyConstructor; } + bool hasTrivialCopyConstructorForCall() const { + return data().HasTrivialSpecialMembersForCall & SMF_CopyConstructor; + } + /// \brief Determine whether this class has a non-trivial copy constructor /// (C++ [class.copy]p6, C++11 [class.copy]p12) bool hasNonTrivialCopyConstructor() const { @@ -1356,6 +1371,12 @@ !hasTrivialCopyConstructor(); } + bool hasNonTrivialCopyConstructorForCall() const { + return (data().DeclaredNonTrivialSpecialMembersForCall & + SMF_CopyConstructor) || + !hasTrivialCopyConstructorForCall(); + } + /// \brief Determine whether this class has a trivial move constructor /// (C++11 [class.copy]p12) bool hasTrivialMoveConstructor() const { @@ -1363,6 +1384,11 @@ (data().HasTrivialSpecialMembers & SMF_MoveConstructor); } + bool hasTrivialMoveConstructorForCall() const { + return hasMoveConstructor() && + (data().HasTrivialSpecialMembersForCall & SMF_MoveConstructor); + } + /// \brief Determine whether this class has a non-trivial move constructor /// (C++11 [class.copy]p12) bool hasNonTrivialMoveConstructor() const { @@ -1371,6 +1397,13 @@ !(data().HasTrivialSpecialMembers & SMF_MoveConstructor)); } + bool hasNonTrivialMoveConstructorForCall() const { + return (data().DeclaredNonTrivialSpecialMembersForCall & + SMF_MoveConstructor) || + (needsImplicitMoveConstructor() && + !(data().HasTrivialSpecialMembersForCall & SMF_MoveConstructor)); + } + /// \brief Determine whether this class has a trivial copy assignment operator /// (C++ [class.copy]p11, C++11 [class.copy]p25) bool hasTrivialCopyAssignment() const { @@ -1405,12 +1438,25 @@ return data().HasTrivialSpecialMembers & SMF_Destructor; } + bool hasTrivialDestructorForCall() const { + return data().HasTrivialSpecialMembersForCall & SMF_Destructor; + } + /// \brief Determine whether this class has a non-trivial destructor /// (C++ [class.dtor]p3) bool hasNonTrivialDestructor() const { return !(data().HasTrivialSpecialMembers & SMF_Destructor); } + bool hasNonTrivialDestructorForCall() const { + return !(data().HasTrivialSpecialMembersForCall & SMF_Destructor); + } + + void setHasTrivialSpecialMemberForCall() { + data().HasTrivialSpecialMembersForCall = + (SMF_CopyConstructor | SMF_MoveConstructor | SMF_Destructor); + } + /// \brief Determine whether declaring a const variable with this type is ok /// per core issue 253. bool allowConstDefaultInit() const { @@ -1440,6 +1486,13 @@ data().CanPassInRegisters = CanPass; } + /// 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 { @@ -1797,6 +1850,8 @@ /// member function is now complete. void finishedDefaultedOrDeletedMember(CXXMethodDecl *MD); + void setTrivialForCallFlags(CXXMethodDecl *MD); + /// \brief Indicates that the definition of this class is now complete. void completeDefinition() override; Index: include/clang/AST/Type.h =================================================================== --- include/clang/AST/Type.h +++ include/clang/AST/Type.h @@ -808,6 +808,11 @@ /// 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: include/clang/Basic/Attr.td =================================================================== --- include/clang/Basic/Attr.td +++ include/clang/Basic/Attr.td @@ -1159,6 +1159,13 @@ let Documentation = [LayoutVersionDocs]; } +def TrivialABI : InheritableAttr { + let Spellings = [Clang<"trivial_abi">]; + let Subjects = SubjectList<[CXXRecord]>; + let Documentation = [TrivialABIDocs]; + let LangOpts = [CPlusPlus]; +} + def MaxFieldAlignment : InheritableAttr { // This attribute has no spellings as it is only ever created implicitly. let Spellings = []; Index: include/clang/Basic/AttrDocs.td =================================================================== --- include/clang/Basic/AttrDocs.td +++ include/clang/Basic/AttrDocs.td @@ -2242,6 +2242,48 @@ }]; } +def TrivialABIDocs : Documentation { + let Category = DocCatVariable; + let Content = [{ +The ``trivial_abi`` attribute can be applied to a C++ class, struct, or union. +It instructs the compiler to pass and return the type using the C ABI for the +underlying type when the type would otherwise be considered non-trivial for the +purpose of calls. +A class annotated with `trivial_abi` can have non-trivial destructors or copy/move constructors without automatically becoming non-trivial for the purposes of calls. For example: + + .. code-block:: c++ + + // A is trivial for the purposes of calls because `trivial_abi` makes the + // user-provided special functions trivial. + struct __attribute__((trivial_abi)) A { + ~A(); + A(const A &); + A(A &&); + int x; + }; + + // B's destructor and copy/move constructor are considered trivial for the + // purpose of calls because A is trivial. + struct B { + A a; + }; + +If a type is trivial for the purposes of calls, has a non-trivial destructor, +and is passed as an argument by value, the convention is that the callee will +destroy the object before returning. + +Attribute ``trivial_abi`` has no effect in the following cases: + +- The class directly declares a virtual base or virtual methods. +- The class has a base class that is non-trivial for the purposes of calls. +- The class has a non-static data member whose type is non-trivial for the +purposes of calls, which includes: + - classes that are non-trivial for the purposes of calls + - __weak-qualified types in Objective-C++ + - arrays of any of the above + }]; +} + def MSInheritanceDocs : Documentation { let Category = DocCatType; let Heading = "__single_inhertiance, __multiple_inheritance, __virtual_inheritance"; Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -2881,6 +2881,9 @@ def err_invalid_attribute_on_virtual_function : Error< "%0 attribute cannot be applied to virtual functions">; +def ext_cannot_use_trivial_abi : ExtWarn< + "'trivial_abi' cannot be applied to %0">, InGroup; + // Availability attribute def warn_availability_unknown_platform : Warning< "unknown platform %0 in availability macro">, InGroup; Index: include/clang/Sema/Sema.h =================================================================== --- include/clang/Sema/Sema.h +++ include/clang/Sema/Sema.h @@ -2236,7 +2236,17 @@ bool CheckNontrivialField(FieldDecl *FD); void DiagnoseNontrivial(const CXXRecordDecl *Record, CXXSpecialMember CSM); + + enum TrivialABIHandling { + /// The triviality of a method unaffected by "trivial_abi". + TAH_IgnoreTrivialABI, + + /// The triviality of a method affected by "trivial_abi". + TAH_ConsiderTrivialABI + }; + bool SpecialMemberIsTrivial(CXXMethodDecl *MD, CXXSpecialMember CSM, + TrivialABIHandling TAH = TAH_IgnoreTrivialABI, bool Diagnose = false); CXXSpecialMember getSpecialMember(const CXXMethodDecl *MD); void ActOnLastBitfield(SourceLocation DeclStart, @@ -5796,6 +5806,11 @@ SourceLocation BaseLoc); void CheckCompletedCXXClass(CXXRecordDecl *Record); + + /// Check that the C++ class annoated with "trivial_abi" satisfies all the + /// conditions that are needed for the attribute to have an effect. + void checkIllFormedTrivialABIStruct(CXXRecordDecl &RD); + void ActOnFinishCXXMemberSpecification(Scope* S, SourceLocation RLoc, Decl *TagDecl, SourceLocation LBrac, Index: lib/AST/ASTContext.cpp =================================================================== --- lib/AST/ASTContext.cpp +++ lib/AST/ASTContext.cpp @@ -2640,6 +2640,11 @@ } } +bool ASTContext::isParamDestroyedInCallee(QualType T) const { + return getTargetInfo().getCXXABI().areArgsDestroyedLeftToRightInCallee() || + T.hasTrivialABIOverride(); +} + /// getComplexType - Return the uniqued reference to the type for a complex /// number with the specified element type. QualType ASTContext::getComplexType(QualType T) const { Index: lib/AST/DeclCXX.cpp =================================================================== --- lib/AST/DeclCXX.cpp +++ lib/AST/DeclCXX.cpp @@ -88,7 +88,9 @@ DefaultedMoveConstructorIsDeleted(false), DefaultedMoveAssignmentIsDeleted(false), DefaultedDestructorIsDeleted(false), HasTrivialSpecialMembers(SMF_All), - DeclaredNonTrivialSpecialMembers(0), HasIrrelevantDestructor(true), + HasTrivialSpecialMembersForCall(SMF_All), + DeclaredNonTrivialSpecialMembers(0), + DeclaredNonTrivialSpecialMembersForCall(0), HasIrrelevantDestructor(true), HasConstexprNonCopyMoveConstructor(false), HasDefaultedDefaultConstructor(false), CanPassInRegisters(true), @@ -281,6 +283,7 @@ // operator for a class X] is trivial [...] if: // -- class X has [...] no virtual base classes data().HasTrivialSpecialMembers &= SMF_Destructor; + data().HasTrivialSpecialMembersForCall &= SMF_Destructor; // C++0x [class]p7: // A standard-layout class is a class that: [...] @@ -314,6 +317,10 @@ // subobject is trivial, and if (!BaseClassDecl->hasTrivialCopyConstructor()) data().HasTrivialSpecialMembers &= ~SMF_CopyConstructor; + + if (!BaseClassDecl->hasTrivialCopyConstructorForCall()) + data().HasTrivialSpecialMembersForCall &= ~SMF_CopyConstructor; + // If the base class doesn't have a simple move constructor, we'll eagerly // declare it and perform overload resolution to determine which function // it actually calls. If it does have a simple move constructor, this @@ -321,6 +328,9 @@ if (!BaseClassDecl->hasTrivialMoveConstructor()) data().HasTrivialSpecialMembers &= ~SMF_MoveConstructor; + if (!BaseClassDecl->hasTrivialMoveConstructorForCall()) + data().HasTrivialSpecialMembersForCall &= ~SMF_MoveConstructor; + // C++0x [class.copy]p27: // A copy/move assignment operator for class X is trivial if [...] // [...] @@ -357,6 +367,9 @@ if (!BaseClassDecl->hasTrivialDestructor()) data().HasTrivialSpecialMembers &= ~SMF_Destructor; + if (!BaseClassDecl->hasTrivialDestructorForCall()) + data().HasTrivialSpecialMembersForCall &= ~SMF_Destructor; + if (!BaseClassDecl->hasIrrelevantDestructor()) data().HasIrrelevantDestructor = false; @@ -539,6 +552,7 @@ // assignment operator for a class X] is trivial [...] if: // -- class X has no virtual functions [...] data().HasTrivialSpecialMembers &= SMF_Destructor; + data().HasTrivialSpecialMembersForCall &= SMF_Destructor; // C++0x [class]p7: // A standard-layout class is a class that: [...] @@ -623,8 +637,10 @@ // C++11 [class.dtor]p5: // A destructor is trivial if [...] the destructor is not virtual. - if (DD->isVirtual()) + if (DD->isVirtual()) { data().HasTrivialSpecialMembers &= ~SMF_Destructor; + data().HasTrivialSpecialMembersForCall &= ~SMF_Destructor; + } } // Handle member functions. @@ -670,16 +686,30 @@ // If this is the first declaration of a special member, we no longer have // an implicit trivial special member. data().HasTrivialSpecialMembers &= - data().DeclaredSpecialMembers | ~SMKind; + data().DeclaredSpecialMembers | ~SMKind; + data().HasTrivialSpecialMembersForCall &= + data().DeclaredSpecialMembers | ~SMKind; if (!Method->isImplicit() && !Method->isUserProvided()) { // This method is user-declared but not user-provided. We can't work out // whether it's trivial yet (not until we get to the end of the class). // We'll handle this method in finishedDefaultedOrDeletedMember. - } else if (Method->isTrivial()) + } else if (Method->isTrivial()) { data().HasTrivialSpecialMembers |= SMKind; - else + data().HasTrivialSpecialMembersForCall |= SMKind; + } else if (Method->isTrivialForCall()) { + data().HasTrivialSpecialMembersForCall |= SMKind; data().DeclaredNonTrivialSpecialMembers |= SMKind; + } else { + data().DeclaredNonTrivialSpecialMembers |= SMKind; + // If this is a user-provided function, do not set + // DeclaredNonTrivialSpecialMembersForCall here since we don't know + // yet whether the method would be considered non-trivial for the + // purpose of calls (attribute "trivial_abi" can be dropped from the + // class later, which can change the special method's triviality). + if (!Method->isUserProvided()) + data().DeclaredNonTrivialSpecialMembersForCall |= SMKind; + } // Note when we have declared a declared special member, and suppress the // implicit declaration of this special member. @@ -772,6 +802,7 @@ struct DefinitionData &Data = data(); Data.PlainOldData = false; Data.HasTrivialSpecialMembers = 0; + Data.HasTrivialSpecialMembersForCall = 0; Data.HasIrrelevantDestructor = false; } else if (!Context.getLangOpts().ObjCAutoRefCount) { setHasObjectMember(true); @@ -899,12 +930,19 @@ // member is trivial; if (!FieldRec->hasTrivialCopyConstructor()) data().HasTrivialSpecialMembers &= ~SMF_CopyConstructor; + + if (!FieldRec->hasTrivialCopyConstructorForCall()) + data().HasTrivialSpecialMembersForCall &= ~SMF_CopyConstructor; + // If the field doesn't have a simple move constructor, we'll eagerly // declare the move constructor for this class and we'll decide whether // it's trivial then. if (!FieldRec->hasTrivialMoveConstructor()) data().HasTrivialSpecialMembers &= ~SMF_MoveConstructor; + if (!FieldRec->hasTrivialMoveConstructorForCall()) + data().HasTrivialSpecialMembersForCall &= ~SMF_MoveConstructor; + // C++0x [class.copy]p27: // A copy/move assignment operator for class X is trivial if [...] // [...] @@ -921,6 +959,8 @@ if (!FieldRec->hasTrivialDestructor()) data().HasTrivialSpecialMembers &= ~SMF_Destructor; + if (!FieldRec->hasTrivialDestructorForCall()) + data().HasTrivialSpecialMembersForCall &= ~SMF_Destructor; if (!FieldRec->hasIrrelevantDestructor()) data().HasIrrelevantDestructor = false; if (FieldRec->hasObjectMember()) @@ -1103,6 +1143,23 @@ data().DeclaredNonTrivialSpecialMembers |= SMKind; } +void CXXRecordDecl::setTrivialForCallFlags(CXXMethodDecl *D) { + unsigned SMKind = 0; + + if (CXXConstructorDecl *Constructor = dyn_cast(D)) { + if (Constructor->isCopyConstructor()) + SMKind = SMF_CopyConstructor; + else if (Constructor->isMoveConstructor()) + SMKind = SMF_MoveConstructor; + } else if (isa(D)) + SMKind = SMF_Destructor; + + if (D->isTrivialForCall()) + data().HasTrivialSpecialMembersForCall |= SMKind; + else + data().DeclaredNonTrivialSpecialMembersForCall |= SMKind; +} + bool CXXRecordDecl::isCLike() const { if (getTagKind() == TTK_Class || getTagKind() == TTK_Interface || !TemplateOrInstantiation.isNull()) Index: lib/AST/Type.cpp =================================================================== --- lib/AST/Type.cpp +++ lib/AST/Type.cpp @@ -2201,6 +2201,12 @@ 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: lib/CodeGen/CGCall.cpp =================================================================== --- lib/CodeGen/CGCall.cpp +++ lib/CodeGen/CGCall.cpp @@ -3144,7 +3144,6 @@ static void deactivateArgCleanupsBeforeCall(CodeGenFunction &CGF, const CallArgList &CallArgs) { - assert(CGF.getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()); ArrayRef Cleanups = CallArgs.getCleanupsToDeactivate(); // Iterate in reverse to increase the likelihood of popping the cleanup. @@ -3501,8 +3500,7 @@ // In the Microsoft C++ ABI, aggregate arguments are destructed by the callee. // 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().areArgsDestroyedLeftToRightInCallee()) { + if (HasAggregateEvalKind && getContext().isParamDestroyedInCallee(type)) { // If we're using inalloca, use the argument memory. Otherwise, use a // temporary. AggValueSlot Slot; @@ -3514,7 +3512,8 @@ const CXXRecordDecl *RD = type->getAsCXXRecordDecl(); bool DestroyedInCallee = RD && RD->hasNonTrivialDestructor() && - CGM.getCXXABI().getRecordArgABI(RD) != CGCXXABI::RAA_Default; + (CGM.getCXXABI().getRecordArgABI(RD) != CGCXXABI::RAA_Default || + RD->hasTrivialABIOverride()); if (DestroyedInCallee) Slot.setExternallyDestructed(); Index: lib/CodeGen/CGDecl.cpp =================================================================== --- lib/CodeGen/CGDecl.cpp +++ lib/CodeGen/CGDecl.cpp @@ -1868,7 +1868,7 @@ // Don't push a cleanup in a thunk for a method that will also emit a // cleanup. if (!IsScalar && !CurFuncIsThunk && - getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) { + getContext().isParamDestroyedInCallee(Ty)) { const CXXRecordDecl *RD = Ty->getAsCXXRecordDecl(); if (RD && RD->hasNonTrivialDestructor()) pushDestroy(QualType::DK_cxx_destructor, DeclPtr, Ty); Index: lib/CodeGen/ItaniumCXXABI.cpp =================================================================== --- lib/CodeGen/ItaniumCXXABI.cpp +++ lib/CodeGen/ItaniumCXXABI.cpp @@ -68,8 +68,8 @@ if (CGM.getCodeGenOpts().getClangABICompat() <= CodeGenOptions::ClangABI::Ver4 || CGM.getTriple().getOS() == llvm::Triple::PS4) - return RD->hasNonTrivialDestructor() || - RD->hasNonTrivialCopyConstructor(); + return RD->hasNonTrivialDestructorForCall() || + RD->hasNonTrivialCopyConstructorForCall(); return !canCopyArgument(RD); } Index: lib/CodeGen/MicrosoftCXXABI.cpp =================================================================== --- lib/CodeGen/MicrosoftCXXABI.cpp +++ lib/CodeGen/MicrosoftCXXABI.cpp @@ -820,19 +820,8 @@ return RAA_Default; case llvm::Triple::x86_64: - // 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 (RD->hasNonTrivialDestructor() && - getContext().getTypeSize(RD->getTypeForDecl()) > 64) - return RAA_Indirect; + 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. @@ -841,23 +830,49 @@ // passed in registers, so long as they *also* have a trivial copy ctor, // which is non-conforming. if (RD->needsImplicitCopyConstructor()) { - // If the copy ctor has not yet been declared, we can read its triviality - // off the AST. - if (!RD->defaultedCopyConstructorIsDeleted() && - RD->hasTrivialCopyConstructor()) - return RAA_Default; + if (!RD->defaultedCopyConstructorIsDeleted()) { + if (RD->hasTrivialCopyConstructor()) + CopyCtorIsTrivial = true; + if (RD->hasTrivialCopyConstructorForCall()) + CopyCtorIsTrivialForCall = true; + } } else { - // Otherwise, we need to find the copy constructor(s) and ask. for (const CXXConstructorDecl *CD : RD->ctors()) { - if (CD->isCopyConstructor()) { - // We had at least one nondeleted trivial copy ctor. Return directly. - if (!CD->isDeleted() && CD->isTrivial()) - return RAA_Default; + if (CD->isCopyConstructor() && !CD->isDeleted()) { + if (CD->isTrivial()) + CopyCtorIsTrivial = true; + if (CD->isTrivialForCall()) + CopyCtorIsTrivialForCall = true; } } } - // We have no trivial, non-deleted copy constructor. + 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; } Index: lib/Sema/SemaChecking.cpp =================================================================== --- lib/Sema/SemaChecking.cpp +++ lib/Sema/SemaChecking.cpp @@ -10814,23 +10814,18 @@ // information is added for it. diagnoseArrayStarInParamType(*this, PType, Param->getLocation()); - // 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. However, we don't perform any direct access check - // on the dtor. - if (getLangOpts().CPlusPlus && Context.getTargetInfo() - .getCXXABI() - .areArgsDestroyedLeftToRightInCallee()) { - if (!Param->isInvalidDecl()) { - if (const RecordType *RT = Param->getType()->getAs()) { - CXXRecordDecl *ClassDecl = cast(RT->getDecl()); - if (!ClassDecl->isInvalidDecl() && - !ClassDecl->hasIrrelevantDestructor() && - !ClassDecl->isDependentContext()) { - CXXDestructorDecl *Destructor = LookupDestructor(ClassDecl); - MarkFunctionReferenced(Param->getLocation(), Destructor); - DiagnoseUseOfDecl(Destructor, Param->getLocation()); - } + // If the parameter is a c++ class type and it has to be destructed in the + // callee function, declare the destructor so that it can be called by the + // callee function. Do not perfom any direct access check on the dtor here. + if (!Param->isInvalidDecl()) { + if (CXXRecordDecl *ClassDecl = Param->getType()->getAsCXXRecordDecl()) { + if (!ClassDecl->isInvalidDecl() && + !ClassDecl->hasIrrelevantDestructor() && + !ClassDecl->isDependentContext() && + Context.isParamDestroyedInCallee(Param->getType())) { + CXXDestructorDecl *Destructor = LookupDestructor(ClassDecl); + MarkFunctionReferenced(Param->getLocation(), Destructor); + DiagnoseUseOfDecl(Destructor, Param->getLocation()); } } } Index: lib/Sema/SemaDeclAttr.cpp =================================================================== --- lib/Sema/SemaDeclAttr.cpp +++ lib/Sema/SemaDeclAttr.cpp @@ -6436,6 +6436,9 @@ case AttributeList::AT_LayoutVersion: handleLayoutVersion(S, D, Attr); break; + case AttributeList::AT_TrivialABI: + handleSimpleAttribute(S, D, Attr); + break; case AttributeList::AT_MSNoVTable: handleSimpleAttribute(S, D, Attr); break; Index: lib/Sema/SemaDeclCXX.cpp =================================================================== --- lib/Sema/SemaDeclCXX.cpp +++ lib/Sema/SemaDeclCXX.cpp @@ -5780,20 +5780,20 @@ if (D->needsImplicitCopyConstructor() && !D->defaultedCopyConstructorIsDeleted()) { - if (!D->hasTrivialCopyConstructor()) + if (!D->hasTrivialCopyConstructorForCall()) return false; HasNonDeletedCopyOrMove = true; } if (S.getLangOpts().CPlusPlus11 && D->needsImplicitMoveConstructor() && !D->defaultedMoveConstructorIsDeleted()) { - if (!D->hasTrivialMoveConstructor()) + if (!D->hasTrivialMoveConstructorForCall()) return false; HasNonDeletedCopyOrMove = true; } if (D->needsImplicitDestructor() && !D->defaultedDestructorIsDeleted() && - !D->hasTrivialDestructor()) + !D->hasTrivialDestructorForCall()) return false; for (const CXXMethodDecl *MD : D->methods()) { @@ -5806,7 +5806,7 @@ else if (!isa(MD)) continue; - if (!MD->isTrivial()) + if (!MD->isTrivialForCall()) return false; } @@ -5890,6 +5890,13 @@ } } + // Set HasTrivialSpecialMemberForCall if the record has attribute + // "trivial_abi". + bool HasTrivialABI = Record->hasAttr(); + + if (HasTrivialABI) + Record->setHasTrivialSpecialMemberForCall(); + bool HasMethodWithOverrideControl = false, HasOverridingMethodWithoutOverrideControl = false; if (!Record->isDependentType()) { @@ -5912,12 +5919,23 @@ if (!M->isImplicit() && !M->isUserProvided()) { if (CSM != CXXInvalid) { M->setTrivial(SpecialMemberIsTrivial(M, CSM)); - // Inform the class that we've finished declaring this member. Record->finishedDefaultedOrDeletedMember(M); + M->setTrivialForCall( + HasTrivialABI || + SpecialMemberIsTrivial(M, CSM, TAH_ConsiderTrivialABI)); + Record->setTrivialForCallFlags(M); } } + // Set triviality for the purpose of calls if this is a user-provided + // copy/move constructor or destructor. + if ((CSM == CXXCopyConstructor || CSM == CXXMoveConstructor || + CSM == CXXDestructor) && M->isUserProvided()) { + M->setTrivialForCall(HasTrivialABI); + Record->setTrivialForCallFlags(M); + } + if (!M->isInvalidDecl() && M->isExplicitlyDefaulted() && M->hasAttr()) { if (getLangOpts().isCompatibleWithMSVC(LangOptions::MSVC2015) && @@ -7029,9 +7047,14 @@ /// /// If \p Selected is not \c NULL, \c *Selected will be filled in with the /// member that was most likely to be intended to be trivial, if any. +/// +/// If \p ForCall is true, look at CXXRecord::HasTrivialSpecialMembersForCall to +/// determine whether the special member is trivial. static bool findTrivialSpecialMember(Sema &S, CXXRecordDecl *RD, Sema::CXXSpecialMember CSM, unsigned Quals, - bool ConstRHS, CXXMethodDecl **Selected) { + bool ConstRHS, + Sema::TrivialABIHandling TAH, + CXXMethodDecl **Selected) { if (Selected) *Selected = nullptr; @@ -7072,7 +7095,9 @@ // C++11 [class.dtor]p5: // A destructor is trivial if: // - all the direct [subobjects] have trivial destructors - if (RD->hasTrivialDestructor()) + if (RD->hasTrivialDestructor() || + (TAH == Sema::TAH_ConsiderTrivialABI && + RD->hasTrivialDestructorForCall())) return true; if (Selected) { @@ -7087,7 +7112,9 @@ // C++11 [class.copy]p12: // A copy constructor is trivial if: // - the constructor selected to copy each direct [subobject] is trivial - if (RD->hasTrivialCopyConstructor()) { + if (RD->hasTrivialCopyConstructor() || + (TAH == Sema::TAH_ConsiderTrivialABI && + RD->hasTrivialCopyConstructorForCall())) { if (Quals == Qualifiers::Const) // We must either select the trivial copy constructor or reach an // ambiguity; no need to actually perform overload resolution. @@ -7140,6 +7167,10 @@ // not supposed to! if (Selected) *Selected = SMOR.getMethod(); + + if (TAH == Sema::TAH_ConsiderTrivialABI && + (CSM == Sema::CXXCopyConstructor || CSM == Sema::CXXMoveConstructor)) + return SMOR.getMethod()->isTrivialForCall(); return SMOR.getMethod()->isTrivial(); } @@ -7178,14 +7209,14 @@ QualType SubType, bool ConstRHS, Sema::CXXSpecialMember CSM, TrivialSubobjectKind Kind, - bool Diagnose) { + Sema::TrivialABIHandling TAH, bool Diagnose) { CXXRecordDecl *SubRD = SubType->getAsCXXRecordDecl(); if (!SubRD) return true; CXXMethodDecl *Selected; if (findTrivialSpecialMember(S, SubRD, CSM, SubType.getCVRQualifiers(), - ConstRHS, Diagnose ? &Selected : nullptr)) + ConstRHS, TAH, Diagnose ? &Selected : nullptr)) return true; if (Diagnose) { @@ -7215,7 +7246,8 @@ << Kind << SubType.getUnqualifiedType() << CSM; // Explain why the defaulted or deleted special member isn't trivial. - S.SpecialMemberIsTrivial(Selected, CSM, Diagnose); + S.SpecialMemberIsTrivial(Selected, CSM, Sema::TAH_IgnoreTrivialABI, + Diagnose); } } @@ -7226,7 +7258,9 @@ /// trivial. static bool checkTrivialClassMembers(Sema &S, CXXRecordDecl *RD, Sema::CXXSpecialMember CSM, - bool ConstArg, bool Diagnose) { + bool ConstArg, + Sema::TrivialABIHandling TAH, + bool Diagnose) { for (const auto *FI : RD->fields()) { if (FI->isInvalidDecl() || FI->isUnnamedBitfield()) continue; @@ -7236,7 +7270,7 @@ // Pretend anonymous struct or union members are members of this class. if (FI->isAnonymousStructOrUnion()) { if (!checkTrivialClassMembers(S, FieldType->getAsCXXRecordDecl(), - CSM, ConstArg, Diagnose)) + CSM, ConstArg, TAH, Diagnose)) return false; continue; } @@ -7264,7 +7298,7 @@ bool ConstRHS = ConstArg && !FI->isMutable(); if (!checkTrivialSubobjectCall(S, FI->getLocation(), FieldType, ConstRHS, - CSM, TSK_Field, Diagnose)) + CSM, TSK_Field, TAH, Diagnose)) return false; } @@ -7278,14 +7312,15 @@ bool ConstArg = (CSM == CXXCopyConstructor || CSM == CXXCopyAssignment); checkTrivialSubobjectCall(*this, RD->getLocation(), Ty, ConstArg, CSM, - TSK_CompleteObject, /*Diagnose*/true); + TSK_CompleteObject, TAH_IgnoreTrivialABI, + /*Diagnose*/true); } /// Determine whether a defaulted or deleted special member function is trivial, /// as specified in C++11 [class.ctor]p5, C++11 [class.copy]p12, /// C++11 [class.copy]p25, and C++11 [class.dtor]p5. bool Sema::SpecialMemberIsTrivial(CXXMethodDecl *MD, CXXSpecialMember CSM, - bool Diagnose) { + TrivialABIHandling TAH, bool Diagnose) { assert(!MD->isUserProvided() && CSM != CXXInvalid && "not special enough"); CXXRecordDecl *RD = MD->getParent(); @@ -7362,7 +7397,7 @@ // destructors] for (const auto &BI : RD->bases()) if (!checkTrivialSubobjectCall(*this, BI.getLocStart(), BI.getType(), - ConstArg, CSM, TSK_BaseClass, Diagnose)) + ConstArg, CSM, TSK_BaseClass, TAH, Diagnose)) return false; // C++11 [class.ctor]p5, C++11 [class.dtor]p5: @@ -7377,7 +7412,7 @@ // -- for all of the non-static data members of its class that are of class // type (or array thereof), each such class has a trivial [default // constructor or destructor] - if (!checkTrivialClassMembers(*this, RD, CSM, ConstArg, Diagnose)) + if (!checkTrivialClassMembers(*this, RD, CSM, ConstArg, TAH, Diagnose)) return false; // C++11 [class.dtor]p5: @@ -7559,6 +7594,50 @@ } } +void Sema::checkIllFormedTrivialABIStruct(CXXRecordDecl &RD) { + auto PrintDiagAndRemoveAttr = [&]() { + // No diagnostics if this is a template instantiation. + if (!isTemplateInstantiation(RD.getTemplateSpecializationKind())) + Diag(RD.getAttr()->getLocation(), + diag::ext_cannot_use_trivial_abi) << &RD; + RD.dropAttr(); + }; + + // Ill-formed if the struct has virtual functions. + if (RD.isPolymorphic()) { + PrintDiagAndRemoveAttr(); + return; + } + + for (const auto &B : RD.bases()) { + // Ill-formed if the base class is non-trivial for the purpose of calls or a + // virtual base. + if ((!B.getType()->isDependentType() && + !B.getType()->getAsCXXRecordDecl()->canPassInRegisters()) || + B.isVirtual()) { + PrintDiagAndRemoveAttr(); + return; + } + } + + for (const auto *FD : RD.fields()) { + // Ill-formed if the field is an ObjectiveC pointer or of a type that is + // non-trivial for the purpose of calls. + QualType FT = FD->getType(); + if (FT.getObjCLifetime() == Qualifiers::OCL_Weak) { + PrintDiagAndRemoveAttr(); + return; + } + + if (const auto *RT = FT->getBaseElementTypeUnsafe()->getAs()) + if (!RT->isDependentType() && + !cast(RT->getDecl())->canPassInRegisters()) { + PrintDiagAndRemoveAttr(); + return; + } + } +} + void Sema::ActOnFinishCXXMemberSpecification(Scope* S, SourceLocation RLoc, Decl *TagDecl, SourceLocation LBrac, @@ -7577,12 +7656,17 @@ l->getName(); } + // See if trivial_abi has to be dropped. + auto *RD = dyn_cast(TagDecl); + if (RD && RD->hasAttr()) + checkIllFormedTrivialABIStruct(*RD); + ActOnFields(S, RLoc, TagDecl, llvm::makeArrayRef( // strict aliasing violation! reinterpret_cast(FieldCollector->getCurFields()), FieldCollector->getCurNumFields()), LBrac, RBrac, AttrList); - CheckCompletedCXXClass(dyn_cast_or_null(TagDecl)); + CheckCompletedCXXClass(RD); } /// AddImplicitlyDeclaredMembersToClass - Adds any implicitly-declared @@ -10732,6 +10816,8 @@ // We don't need to use SpecialMemberIsTrivial here; triviality for // destructors is easy to compute. Destructor->setTrivial(ClassDecl->hasTrivialDestructor()); + Destructor->setTrivialForCall(ClassDecl->hasAttr() || + ClassDecl->hasTrivialDestructorForCall()); // Note that we have declared this destructor. ++ASTContext::NumImplicitDestructorsDeclared; @@ -12035,9 +12121,16 @@ CopyConstructor->setParams(FromParam); CopyConstructor->setTrivial( - ClassDecl->needsOverloadResolutionForCopyConstructor() - ? SpecialMemberIsTrivial(CopyConstructor, CXXCopyConstructor) - : ClassDecl->hasTrivialCopyConstructor()); + ClassDecl->needsOverloadResolutionForCopyConstructor() + ? SpecialMemberIsTrivial(CopyConstructor, CXXCopyConstructor) + : ClassDecl->hasTrivialCopyConstructor()); + + CopyConstructor->setTrivialForCall( + ClassDecl->hasAttr() || + (ClassDecl->needsOverloadResolutionForCopyConstructor() + ? SpecialMemberIsTrivial(CopyConstructor, CXXCopyConstructor, + TAH_ConsiderTrivialABI) + : ClassDecl->hasTrivialCopyConstructorForCall())); // Note that we have declared this constructor. ++ASTContext::NumImplicitCopyConstructorsDeclared; @@ -12158,9 +12251,16 @@ MoveConstructor->setParams(FromParam); MoveConstructor->setTrivial( - ClassDecl->needsOverloadResolutionForMoveConstructor() - ? SpecialMemberIsTrivial(MoveConstructor, CXXMoveConstructor) - : ClassDecl->hasTrivialMoveConstructor()); + ClassDecl->needsOverloadResolutionForMoveConstructor() + ? SpecialMemberIsTrivial(MoveConstructor, CXXMoveConstructor) + : ClassDecl->hasTrivialMoveConstructor()); + + MoveConstructor->setTrivialForCall( + ClassDecl->hasAttr() || + (ClassDecl->needsOverloadResolutionForMoveConstructor() + ? SpecialMemberIsTrivial(MoveConstructor, CXXMoveConstructor, + TAH_ConsiderTrivialABI) + : ClassDecl->hasTrivialMoveConstructorForCall())); // Note that we have declared this constructor. ++ASTContext::NumImplicitMoveConstructorsDeclared; Index: lib/Sema/SemaTemplateInstantiate.cpp =================================================================== --- lib/Sema/SemaTemplateInstantiate.cpp +++ lib/Sema/SemaTemplateInstantiate.cpp @@ -2108,6 +2108,10 @@ } } + // See if trivial_abi has to be dropped. + if (Instantiation && Instantiation->hasAttr()) + checkIllFormedTrivialABIStruct(*Instantiation); + // Finish checking fields. ActOnFields(nullptr, Instantiation->getLocation(), Instantiation, Fields, SourceLocation(), SourceLocation(), nullptr); Index: lib/Sema/SemaType.cpp =================================================================== --- lib/Sema/SemaType.cpp +++ lib/Sema/SemaType.cpp @@ -7794,7 +7794,8 @@ diag::note_non_literal_user_provided_dtor : diag::note_non_literal_nontrivial_dtor) << RD; if (!Dtor->isUserProvided()) - SpecialMemberIsTrivial(Dtor, CXXDestructor, /*Diagnose*/true); + SpecialMemberIsTrivial(Dtor, CXXDestructor, TAH_IgnoreTrivialABI, + /*Diagnose*/true); } return true; Index: lib/Serialization/ASTReaderDecl.cpp =================================================================== --- lib/Serialization/ASTReaderDecl.cpp +++ lib/Serialization/ASTReaderDecl.cpp @@ -786,6 +786,7 @@ FD->HasWrittenPrototype = Record.readInt(); FD->IsDeleted = Record.readInt(); FD->IsTrivial = Record.readInt(); + FD->IsTrivialForCall = Record.readInt(); FD->IsDefaulted = Record.readInt(); FD->IsExplicitlyDefaulted = Record.readInt(); FD->HasImplicitReturnZero = Record.readInt(); @@ -1577,7 +1578,9 @@ Data.DefaultedMoveAssignmentIsDeleted = Record.readInt(); Data.DefaultedDestructorIsDeleted = Record.readInt(); Data.HasTrivialSpecialMembers = Record.readInt(); + Data.HasTrivialSpecialMembersForCall = Record.readInt(); Data.DeclaredNonTrivialSpecialMembers = Record.readInt(); + Data.DeclaredNonTrivialSpecialMembersForCall = Record.readInt(); Data.HasIrrelevantDestructor = Record.readInt(); Data.HasConstexprNonCopyMoveConstructor = Record.readInt(); Data.HasDefaultedDefaultConstructor = Record.readInt(); @@ -1715,7 +1718,9 @@ MATCH_FIELD(DefaultedMoveAssignmentIsDeleted) MATCH_FIELD(DefaultedDestructorIsDeleted) OR_FIELD(HasTrivialSpecialMembers) + OR_FIELD(HasTrivialSpecialMembersForCall) OR_FIELD(DeclaredNonTrivialSpecialMembers) + OR_FIELD(DeclaredNonTrivialSpecialMembersForCall) MATCH_FIELD(HasIrrelevantDestructor) OR_FIELD(HasConstexprNonCopyMoveConstructor) OR_FIELD(HasDefaultedDefaultConstructor) Index: lib/Serialization/ASTWriter.cpp =================================================================== --- lib/Serialization/ASTWriter.cpp +++ lib/Serialization/ASTWriter.cpp @@ -6009,7 +6009,9 @@ Record->push_back(Data.DefaultedMoveAssignmentIsDeleted); Record->push_back(Data.DefaultedDestructorIsDeleted); Record->push_back(Data.HasTrivialSpecialMembers); + Record->push_back(Data.HasTrivialSpecialMembersForCall); Record->push_back(Data.DeclaredNonTrivialSpecialMembers); + Record->push_back(Data.DeclaredNonTrivialSpecialMembersForCall); Record->push_back(Data.HasIrrelevantDestructor); Record->push_back(Data.HasConstexprNonCopyMoveConstructor); Record->push_back(Data.HasDefaultedDefaultConstructor); Index: lib/Serialization/ASTWriterDecl.cpp =================================================================== --- lib/Serialization/ASTWriterDecl.cpp +++ lib/Serialization/ASTWriterDecl.cpp @@ -528,6 +528,7 @@ Record.push_back(D->HasWrittenPrototype); Record.push_back(D->IsDeleted); Record.push_back(D->IsTrivial); + Record.push_back(D->IsTrivialForCall); Record.push_back(D->IsDefaulted); Record.push_back(D->IsExplicitlyDefaulted); Record.push_back(D->HasImplicitReturnZero); @@ -2067,6 +2068,7 @@ Abv->Add(BitCodeAbbrevOp(1)); // HasWrittenProto Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // Deleted Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // Trivial + Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // TrivialForCall Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // Defaulted Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // ExplicitlyDefaulted Abv->Add(BitCodeAbbrevOp(BitCodeAbbrevOp::Fixed, 1)); // ImplicitReturnZero Index: test/CodeGenCXX/trivial_abi.cpp =================================================================== --- test/CodeGenCXX/trivial_abi.cpp +++ test/CodeGenCXX/trivial_abi.cpp @@ -0,0 +1,239 @@ +// RUN: %clang_cc1 -triple arm64-apple-ios11 -std=c++11 -fcxx-exceptions -fexceptions -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -triple arm64-apple-ios11 -std=c++11 -fcxx-exceptions -fexceptions -fclang-abi-compat=4.0 -emit-llvm -o - %s | FileCheck %s + +// CHECK: %[[STRUCT_SMALL:.*]] = type { i32* } +// CHECK: %[[STRUCT_LARGE:.*]] = type { i32*, [128 x i32] } +// CHECK: %[[STRUCT_TRIVIAL:.*]] = type { i32 } +// CHECK: %[[STRUCT_NONTRIVIAL:.*]] = type { i32 } + +struct __attribute__((trivial_abi)) Small { + int *p; + Small(); + ~Small(); + Small(const Small &) noexcept; + Small &operator=(const Small &); +}; + +struct __attribute__((trivial_abi)) Large { + int *p; + int a[128]; + Large(); + ~Large(); + Large(const Large &) noexcept; + Large &operator=(const Large &); +}; + +struct Trivial { + int a; +}; + +struct NonTrivial { + NonTrivial(); + ~NonTrivial(); + int a; +}; + +struct HasTrivial { + Small s; + Trivial m; +}; + +struct HasNonTrivial { + Small s; + NonTrivial m; +}; + +// CHECK: define void @_Z14testParamSmall5Small(i64 %[[A_COERCE:.*]]) +// CHECK: %[[A:.*]] = alloca %[[STRUCT_SMALL]], align 8 +// CHECK: %[[COERCE_DIVE:.*]] = getelementptr inbounds %[[STRUCT_SMALL]], %[[STRUCT_SMALL]]* %[[A]], i32 0, i32 0 +// CHECK: %[[COERCE_VAL_IP:.*]] = inttoptr i64 %[[A_COERCE]] to i32* +// CHECK: store i32* %[[COERCE_VAL_IP]], i32** %[[COERCE_DIVE]], align 8 +// CHECK: %[[CALL:.*]] = call %[[STRUCT_SMALL]]* @_ZN5SmallD1Ev(%[[STRUCT_SMALL]]* %[[A]]) +// CHECK: ret void +// CHECK: } + +void testParamSmall(Small a) noexcept { +} + +// CHECK: define i64 @_Z15testReturnSmallv() +// CHECK: %[[RETVAL:.*]] = alloca %[[STRUCT_SMALL:.*]], align 8 +// CHECK: %[[CALL:.*]] = call %[[STRUCT_SMALL]]* @_ZN5SmallC1Ev(%[[STRUCT_SMALL]]* %[[RETVAL]]) +// CHECK: %[[COERCE_DIVE:.*]] = getelementptr inbounds %[[STRUCT_SMALL]], %[[STRUCT_SMALL]]* %[[RETVAL]], i32 0, i32 0 +// CHECK: %[[V0:.*]] = load i32*, i32** %[[COERCE_DIVE]], align 8 +// CHECK: %[[COERCE_VAL_PI:.*]] = ptrtoint i32* %[[V0]] to i64 +// CHECK: ret i64 %[[COERCE_VAL_PI]] +// CHECK: } + +Small testReturnSmall() { + Small t; + return t; +} + +// CHECK: define void @_Z14testCallSmall0v() +// CHECK: %[[T:.*]] = alloca %[[STRUCT_SMALL:.*]], align 8 +// CHECK: %[[AGG_TMP:.*]] = alloca %[[STRUCT_SMALL]], align 8 +// CHECK: %[[CALL:.*]] = call %[[STRUCT_SMALL]]* @_ZN5SmallC1Ev(%[[STRUCT_SMALL]]* %[[T]]) +// CHECK: %[[CALL1:.*]] = call %[[STRUCT_SMALL]]* @_ZN5SmallC1ERKS_(%[[STRUCT_SMALL]]* %[[AGG_TMP]], %[[STRUCT_SMALL]]* dereferenceable(8) %[[T]]) +// CHECK: %[[COERCE_DIVE:.*]] = getelementptr inbounds %[[STRUCT_SMALL]], %[[STRUCT_SMALL]]* %[[AGG_TMP]], i32 0, i32 0 +// CHECK: %[[V0:.*]] = load i32*, i32** %[[COERCE_DIVE]], align 8 +// CHECK: %[[COERCE_VAL_PI:.*]] = ptrtoint i32* %[[V0]] to i64 +// CHECK: call void @_Z14testParamSmall5Small(i64 %[[COERCE_VAL_PI]]) +// CHECK: %[[CALL2:.*]] = call %[[STRUCT_SMALL]]* @_ZN5SmallD1Ev(%[[STRUCT_SMALL]]* %[[T]]) +// CHECK: ret void +// CHECK: } + +void testCallSmall0() { + Small t; + testParamSmall(t); +} + +// CHECK: define void @_Z14testCallSmall1v() +// CHECK: %[[AGG_TMP:.*]] = alloca %[[STRUCT_SMALL:.*]], align 8 +// CHECK: %[[CALL:.*]] = call i64 @_Z15testReturnSmallv() +// CHECK: %[[COERCE_DIVE:.*]] = getelementptr inbounds %[[STRUCT_SMALL]], %[[STRUCT_SMALL]]* %[[AGG_TMP]], i32 0, i32 0 +// CHECK: %[[COERCE_VAL_IP:.*]] = inttoptr i64 %[[CALL]] to i32* +// CHECK: store i32* %[[COERCE_VAL_IP]], i32** %[[COERCE_DIVE]], align 8 +// CHECK: %[[COERCE_DIVE1:.*]] = getelementptr inbounds %[[STRUCT_SMALL]], %[[STRUCT_SMALL]]* %[[AGG_TMP]], i32 0, i32 0 +// CHECK: %[[V0:.*]] = load i32*, i32** %[[COERCE_DIVE1]], align 8 +// CHECK: %[[COERCE_VAL_PI:.*]] = ptrtoint i32* %[[V0]] to i64 +// CHECK: call void @_Z14testParamSmall5Small(i64 %[[COERCE_VAL_PI]]) +// CHECK: ret void +// CHECK: } + +void testCallSmall1() { + testParamSmall(testReturnSmall()); +} + +// CHECK: define void @_Z16testIgnoredSmallv() +// CHECK: %[[AGG_TMP_ENSURED:.*]] = alloca %[[STRUCT_SMALL:.*]], align 8 +// CHECK: %[[CALL:.*]] = call i64 @_Z15testReturnSmallv() +// CHECK: %[[COERCE_DIVE:.*]] = getelementptr inbounds %[[STRUCT_SMALL]], %[[STRUCT_SMALL]]* %[[AGG_TMP_ENSURED]], i32 0, i32 0 +// CHECK: %[[COERCE_VAL_IP:.*]] = inttoptr i64 %[[CALL]] to i32* +// CHECK: store i32* %[[COERCE_VAL_IP]], i32** %[[COERCE_DIVE]], align 8 +// CHECK: %[[CALL1:.*]] = call %[[STRUCT_SMALL]]* @_ZN5SmallD1Ev(%[[STRUCT_SMALL]]* %[[AGG_TMP_ENSURED]]) +// CHECK: ret void +// CHECK: } + +void testIgnoredSmall() { + testReturnSmall(); +} + +// CHECK: define void @_Z14testParamLarge5Large(%[[STRUCT_LARGE:.*]]* %[[A:.*]]) +// CHECK: %[[CALL:.*]] = call %[[STRUCT_LARGE]]* @_ZN5LargeD1Ev(%[[STRUCT_LARGE]]* %[[A]]) +// CHECK: ret void +// CHECK: } + +void testParamLarge(Large a) noexcept { +} + +// CHECK: define void @_Z15testReturnLargev(%[[STRUCT_LARGE:.*]]* noalias sret %[[AGG_RESULT:.*]]) +// CHECK: %[[CALL:.*]] = call %[[STRUCT_LARGE]]* @_ZN5LargeC1Ev(%[[STRUCT_LARGE]]* %[[AGG_RESULT]]) +// CHECK: ret void +// CHECK: } + +Large testReturnLarge() { + Large t; + return t; +} + +// CHECK: define void @_Z14testCallLarge0v() +// CHECK: %[[T:.*]] = alloca %[[STRUCT_LARGE:.*]], align 8 +// CHECK: %[[AGG_TMP:.*]] = alloca %[[STRUCT_LARGE]], align 8 +// CHECK: %[[CALL:.*]] = call %[[STRUCT_LARGE]]* @_ZN5LargeC1Ev(%[[STRUCT_LARGE]]* %[[T]]) +// CHECK: %[[CALL1:.*]] = call %[[STRUCT_LARGE]]* @_ZN5LargeC1ERKS_(%[[STRUCT_LARGE]]* %[[AGG_TMP]], %[[STRUCT_LARGE]]* dereferenceable(520) %[[T]]) +// CHECK: call void @_Z14testParamLarge5Large(%[[STRUCT_LARGE]]* %[[AGG_TMP]]) +// CHECK: %[[CALL2:.*]] = call %[[STRUCT_LARGE]]* @_ZN5LargeD1Ev(%[[STRUCT_LARGE]]* %[[T]]) +// CHECK: ret void +// CHECK: } + +void testCallLarge0() { + Large t; + testParamLarge(t); +} + +// CHECK: define void @_Z14testCallLarge1v() +// CHECK: %[[AGG_TMP:.*]] = alloca %[[STRUCT_LARGE:.*]], align 8 +// CHECK: call void @_Z15testReturnLargev(%[[STRUCT_LARGE]]* sret %[[AGG_TMP]]) +// CHECK: call void @_Z14testParamLarge5Large(%[[STRUCT_LARGE]]* %[[AGG_TMP]]) +// CHECK: ret void +// CHECK: } + +void testCallLarge1() { + testParamLarge(testReturnLarge()); +} + +// CHECK: define void @_Z16testIgnoredLargev() +// CHECK: %[[AGG_TMP_ENSURED:.*]] = alloca %[[STRUCT_LARGE:.*]], align 8 +// CHECK: call void @_Z15testReturnLargev(%[[STRUCT_LARGE]]* sret %[[AGG_TMP_ENSURED]]) +// CHECK: %[[CALL:.*]] = call %[[STRUCT_LARGE]]* @_ZN5LargeD1Ev(%[[STRUCT_LARGE]]* %[[AGG_TMP_ENSURED]]) +// CHECK: ret void +// CHECK: } + +void testIgnoredLarge() { + testReturnLarge(); +} + +// CHECK: define i64 @_Z20testReturnHasTrivialv() +// CHECK: %[[RETVAL:.*]] = alloca %[[STRUCT_TRIVIAL:.*]], align 4 +// CHECK: %[[COERCE_DIVE:.*]] = getelementptr inbounds %[[STRUCT_TRIVIAL]], %[[STRUCT_TRIVIAL]]* %[[RETVAL]], i32 0, i32 0 +// CHECK: %[[V0:.*]] = load i32, i32* %[[COERCE_DIVE]], align 4 +// CHECK: %[[COERCE_VAL_II:.*]] = zext i32 %[[V0]] to i64 +// CHECK: ret i64 %[[COERCE_VAL_II]] +// CHECK: } + +Trivial testReturnHasTrivial() { + Trivial t; + return t; +} + +// CHECK: define void @_Z23testReturnHasNonTrivialv(%[[STRUCT_NONTRIVIAL:.*]]* noalias sret %[[AGG_RESULT:.*]]) +// CHECK: %[[CALL:.*]] = call %[[STRUCT_NONTRIVIAL]]* @_ZN10NonTrivialC1Ev(%[[STRUCT_NONTRIVIAL]]* %[[AGG_RESULT]]) +// CHECK: ret void +// CHECK: } + +NonTrivial testReturnHasNonTrivial() { + NonTrivial t; + return t; +} + +// CHECK: define void @_Z18testExceptionSmallv() +// CHECK: %[[AGG_TMP:.*]] = alloca %[[STRUCT_SMALL]], align 8 +// CHECK: %[[AGG_TMP1:.*]] = alloca %[[STRUCT_SMALL]], align 8 +// CHECK: call %[[STRUCT_SMALL]]* @_ZN5SmallC1Ev(%[[STRUCT_SMALL]]* %[[AGG_TMP]]) +// CHECK: invoke %[[STRUCT_SMALL]]* @_ZN5SmallC1Ev(%[[STRUCT_SMALL]]* %[[AGG_TMP1]]) + +// CHECK: call void @_Z20calleeExceptionSmall5SmallS_(i64 %{{.*}}, i64 %{{.*}}) +// CHECK-NEXT: ret void + +// CHECK: landingpad { i8*, i32 } +// CHECK: call %[[STRUCT_SMALL]]* @_ZN5SmallD1Ev(%[[STRUCT_SMALL]]* %[[AGG_TMP]]) +// CHECK: br + +// CHECK: resume { i8*, i32 } + +void calleeExceptionSmall(Small, Small); + +void testExceptionSmall() { + calleeExceptionSmall(Small(), Small()); +} + +// CHECK: define void @_Z18testExceptionLargev() +// CHECK: %[[AGG_TMP:.*]] = alloca %[[STRUCT_LARGE]], align 8 +// CHECK: %[[AGG_TMP1:.*]] = alloca %[[STRUCT_LARGE]], align 8 +// CHECK: call %[[STRUCT_LARGE]]* @_ZN5LargeC1Ev(%[[STRUCT_LARGE]]* %[[AGG_TMP]]) +// CHECK: invoke %[[STRUCT_LARGE]]* @_ZN5LargeC1Ev(%[[STRUCT_LARGE]]* %[[AGG_TMP1]]) + +// CHECK: call void @_Z20calleeExceptionLarge5LargeS_(%[[STRUCT_LARGE]]* %[[AGG_TMP]], %[[STRUCT_LARGE]]* %[[AGG_TMP1]]) +// CHECK-NEXT: ret void + +// CHECK: landingpad { i8*, i32 } +// CHECK: call %[[STRUCT_LARGE]]* @_ZN5LargeD1Ev(%[[STRUCT_LARGE]]* %[[AGG_TMP]]) +// CHECK: br + +// CHECK: resume { i8*, i32 } + +void calleeExceptionLarge(Large, Large); + +void testExceptionLarge() { + calleeExceptionLarge(Large(), Large()); +} Index: test/CodeGenObjCXX/trivial_abi.mm =================================================================== --- test/CodeGenObjCXX/trivial_abi.mm +++ test/CodeGenObjCXX/trivial_abi.mm @@ -0,0 +1,104 @@ +// 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: entry: +// 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) { +} Index: test/Misc/pragma-attribute-supported-attributes-list.test =================================================================== --- test/Misc/pragma-attribute-supported-attributes-list.test +++ test/Misc/pragma-attribute-supported-attributes-list.test @@ -2,7 +2,7 @@ // The number of supported attributes should never go down! -// CHECK: #pragma clang attribute supports 66 attributes: +// CHECK: #pragma clang attribute supports 67 attributes: // CHECK-NEXT: AMDGPUFlatWorkGroupSize (SubjectMatchRule_function) // CHECK-NEXT: AMDGPUNumSGPR (SubjectMatchRule_function) // CHECK-NEXT: AMDGPUNumVGPR (SubjectMatchRule_function) @@ -66,6 +66,7 @@ // CHECK-NEXT: TLSModel (SubjectMatchRule_variable_is_thread_local) // CHECK-NEXT: Target (SubjectMatchRule_function) // CHECK-NEXT: TestTypestate (SubjectMatchRule_function_is_member) +// CHECK-NEXT: TrivialABI (SubjectMatchRule_record) // CHECK-NEXT: WarnUnusedResult (SubjectMatchRule_objc_method, SubjectMatchRule_enum, SubjectMatchRule_record, SubjectMatchRule_hasType_functionType) // CHECK-NEXT: XRayInstrument (SubjectMatchRule_function, SubjectMatchRule_objc_method) // CHECK-NEXT: XRayLogArgs (SubjectMatchRule_function, SubjectMatchRule_objc_method) Index: test/SemaObjCXX/attr-trivial-abi.mm =================================================================== --- test/SemaObjCXX/attr-trivial-abi.mm +++ test/SemaObjCXX/attr-trivial-abi.mm @@ -0,0 +1,93 @@ +// RUN: %clang_cc1 -std=c++11 -fobjc-runtime-has-weak -fobjc-weak -fobjc-arc -fsyntax-only -verify %s + +void __attribute__((trivial_abi)) foo(); // expected-warning {{'trivial_abi' attribute only applies to classes}} + +struct [[clang::trivial_abi]] S0 { + int a; +}; + +struct __attribute__((trivial_abi)) S1 { + int a; +}; + +struct __attribute__((trivial_abi)) S2 { // expected-warning {{'trivial_abi' cannot be applied to 'S2'}} + __weak id a; +}; + +struct __attribute__((trivial_abi)) S3 { // expected-warning {{'trivial_abi' cannot be applied to 'S3'}} + virtual void m(); +}; + +struct S4 { + int a; +}; + +struct __attribute__((trivial_abi)) S5 : public virtual S4 { // expected-warning {{'trivial_abi' cannot be applied to 'S5'}} +}; + +struct __attribute__((trivial_abi)) S9 : public S4 { +}; + +struct S6 { + __weak id a; +}; + +struct __attribute__((trivial_abi)) S12 { // expected-warning {{'trivial_abi' cannot be applied to 'S12'}} + __weak id a; +}; + +struct __attribute__((trivial_abi)) S13 { // expected-warning {{'trivial_abi' cannot be applied to 'S13'}} + __weak id a[2]; +}; + +struct __attribute__((trivial_abi)) S7 { // expected-warning {{'trivial_abi' cannot be applied to 'S7'}} + S6 a; +}; + +struct __attribute__((trivial_abi)) S11 { // expected-warning {{'trivial_abi' cannot be applied to 'S11'}} + S6 a[2]; +}; + +struct __attribute__((trivial_abi(1))) S8 { // expected-error {{'trivial_abi' attribute takes no arguments}} + int a; +}; + +// Do not warn when 'trivial_abi' is used to annotate a template class. +template +struct __attribute__((trivial_abi)) S10 { + T p; +}; + +S10 p1; +S10<__weak id> p2; + +template<> +struct __attribute__((trivial_abi)) S10 { // expected-warning {{'trivial_abi' cannot be applied to 'S10'}} + __weak id a; +}; + +template +struct S14 { + T a; + __weak id b; +}; + +template +struct __attribute__((trivial_abi)) S15 : S14 { +}; + +S15 s15; + +template +struct __attribute__((trivial_abi)) S16 { + S14 a; +}; + +S16 s16; + +template +struct __attribute__((trivial_abi)) S17 { // expected-warning {{'trivial_abi' cannot be applied to 'S17'}} + __weak id a; +}; + +S17 s17;