Index: include/clang/AST/Decl.h =================================================================== --- include/clang/AST/Decl.h +++ include/clang/AST/Decl.h @@ -1719,6 +1719,14 @@ unsigned getParameterIndexLarge() const; }; +enum class MultiVersionType { + None, + Target, + CPUSpecific, + CPUDispatch, + TargetClones +}; + /// Represents a function declaration or definition. /// /// Since a given function can be declared several times in a program, @@ -2226,6 +2234,8 @@ getCanonicalDecl()->FunctionDeclBits.IsMultiVersion = V; } + MultiVersionType getMultiVersionType() const; + /// True if this function is a multiversioned dispatch function as a part of /// the cpu_specific/cpu_dispatch functionality. bool isCPUDispatchMultiVersion() const; Index: include/clang/Basic/Attr.td =================================================================== --- include/clang/Basic/Attr.td +++ include/clang/Basic/Attr.td @@ -2054,6 +2054,38 @@ }]; } +def TargetClones : InheritableAttr { + let Spellings = [GCC<"target_clones">]; + let Args = [VariadicStringArgument<"featuresStrs">]; + let Documentation = [TargetClonesDocs]; + let Subjects = SubjectList<[Function], ErrorDiag>; + let AdditionalMembers = [{ + StringRef getFeatureStr(unsigned Index) const { + return *(featuresStrs_begin() + Index); + } + // 'default' is always moved to the end, so it isn't considered + // when mangling the index. + unsigned getMangledIndex(unsigned Index) const { + if (getFeatureStr(Index) == "default") + return std::count_if(featuresStrs_begin(), featuresStrs_end(), + [](StringRef S) { return S != "default"; }); + + return std::count_if(featuresStrs_begin(), featuresStrs_begin() + Index, + [](StringRef S) { return S != "default"; }); + } + + // True if this is the first of this version to appear in the config string. + // This is used to make sure we don't try to emit this function multiple + // times. + bool isFirstOfVersion(unsigned Index) const { + StringRef FeatureStr(getFeatureStr(Index)); + return 0 == std::count_if( + featuresStrs_begin(), featuresStrs_begin() + Index, + [ FeatureStr ](StringRef S) { return S == FeatureStr; }); + } + }]; +} + def MinVectorWidth : InheritableAttr { let Spellings = [Clang<"min_vector_width">]; let Args = [UnsignedArgument<"VectorWidth">]; Index: include/clang/Basic/AttrDocs.td =================================================================== --- include/clang/Basic/AttrDocs.td +++ include/clang/Basic/AttrDocs.td @@ -1594,6 +1594,40 @@ }]; } +def TargetClonesDocs : Documentation { + let Category = DocCatFunction; + let Content = [{ +Clang supports the ``target_clones("OPTIONS")`` attribute. This attribute may be +attached to a function declaration and causes function multiversioning, where +multiple versions of the function will be emitted with different code +generation options. Additionally, these versions will be resolved at runtime +based on the priority of their attribute options. All ``target_clone`` functions +are considered multiversioned functions. + +All multiversioned functions must contain a ``default`` (fallback) +implementation, otherwise usages of the function are considered invalid. +Additionally, a function may not become multiversioned after its first use. + +The options to ``target_clones`` can either be a target-specific architecture +(specified as ``arch=CPU``), or one of a list of subtarget features. + +Example "subtarget features" from the x86 backend include: "mmx", "sse", "sse4.2", +"avx", "xop" and largely correspond to the machine specific options handled by +the front end. + +The versions can either be listed as a comma-separated sequence of string +literals or as a single string literal containing a comma-separated list of +versions. For compatibility with GCC, the two formats can be mixed. For +example, the following will emit 4 versions of the function: + + .. code-block:: c++ + + __attribute__((target_clones("arch=atom,avx2","arch=ivybridge","default"))) + void foo() {} + +}]; +} + def MinVectorWidthDocs : Documentation { let Category = DocCatFunction; let Content = [{ Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -2517,7 +2517,8 @@ "%0 attribute requires OpenCL version %1%select{| or above}2">; def warn_unsupported_target_attribute : Warning<"%select{unsupported|duplicate}0%select{| architecture}1 '%2' in" - " the 'target' attribute string; 'target' attribute ignored">, + " the '%select{target|target_clones}3' attribute string; " + "'%select{target|target_clones}3' attribute ignored">, InGroup; def err_attribute_unsupported : Error<"%0 attribute is not supported for this target">; @@ -9502,8 +9503,8 @@ def err_multiversion_noproto : Error< "multiversioned function must have a prototype">; def err_multiversion_no_other_attrs : Error< - "attribute '%select{target|cpu_specific|cpu_dispatch}0' multiversioning cannot be combined" - " with other attributes">; + "attribute '%select{target|cpu_specific|cpu_dispatch|target_clones}0' " + "multiversioning cannot be combined with other attributes">; def err_multiversion_diff : Error< "multiversioned function declaration has a different %select{calling convention" "|return type|constexpr specification|inline specification|storage class|" @@ -9529,6 +9530,17 @@ def warn_dispatch_body_ignored : Warning< "body of cpu_dispatch function will be ignored">, InGroup; +def err_target_clone_must_have_default : Error < + "'target_clones' multiversioning requires a default target">; +def err_target_clone_doesnt_match : Error < + "'target_clones' attribute does not match previous declaration">; +def warn_target_clone_mixed_values : ExtWarn < + "mixing 'target_clones' specifier mechanisms is permitted for GCC " + "compatibility; use a comma separated sequence of string literals, " + "or a string literal containing a comma-separated list of versions">, + InGroup>; +def warn_target_clone_duplicate_options : Warning< + "version list contains duplicate entries">, InGroup; // three-way comparison operator diagnostics def err_implied_comparison_category_type_not_found : Error< Index: include/clang/Sema/Sema.h =================================================================== --- include/clang/Sema/Sema.h +++ include/clang/Sema/Sema.h @@ -3376,6 +3376,10 @@ SourceLocation *ArgLocation = nullptr); bool checkSectionName(SourceLocation LiteralLoc, StringRef Str); bool checkTargetAttr(SourceLocation LiteralLoc, StringRef Str); + bool checkTargetClonesAttrString(SourceLocation LiteralLoc, StringRef Str, + const ArgsUnion Arg, bool &HasDefault, + bool &HasCommas, + SmallVectorImpl &Strings); bool checkMSInheritanceAttrOnDefinition( CXXRecordDecl *RD, SourceRange Range, bool BestCase, MSInheritanceAttr::Spelling SemanticSpelling); Index: lib/AST/Decl.cpp =================================================================== --- lib/AST/Decl.cpp +++ lib/AST/Decl.cpp @@ -2952,6 +2952,18 @@ return isMultiVersion() && hasAttr(); } +MultiVersionType FunctionDecl::getMultiVersionType() const { + if (hasAttr()) + return MultiVersionType::Target; + if (hasAttr()) + return MultiVersionType::CPUDispatch; + if (hasAttr()) + return MultiVersionType::CPUSpecific; + if (hasAttr()) + return MultiVersionType::TargetClones; + return MultiVersionType::None; +} + void FunctionDecl::setPreviousDeclaration(FunctionDecl *PrevDecl) { redeclarable_base::setPreviousDecl(PrevDecl); Index: lib/CodeGen/CodeGenModule.h =================================================================== --- lib/CodeGen/CodeGenModule.h +++ lib/CodeGen/CodeGenModule.h @@ -1319,6 +1319,7 @@ void EmitAliasDefinition(GlobalDecl GD); void emitIFuncDefinition(GlobalDecl GD); void emitCPUDispatchDefinition(GlobalDecl GD); + void EmitTargetClonesResolver(GlobalDecl GD); void EmitObjCPropertyImplementations(const ObjCImplementationDecl *D); void EmitObjCIvarInitializations(ObjCImplementationDecl *D); Index: lib/CodeGen/CodeGenModule.cpp =================================================================== --- lib/CodeGen/CodeGenModule.cpp +++ lib/CodeGen/CodeGenModule.cpp @@ -936,6 +936,20 @@ } } +static void AppendTargetClonesMangling(const CodeGenModule &CGM, + const TargetClonesAttr *Attr, + unsigned VersionIndex, + raw_ostream &Out) { + Out << '.'; + StringRef FeatureStr = Attr->getFeatureStr(VersionIndex); + if (FeatureStr.startswith("arch=")) + Out << "arch_" << FeatureStr.substr(sizeof("arch=") - 1); + else + Out << FeatureStr; + + Out << '.' << Attr->getMangledIndex(VersionIndex); +} + static std::string getMangledNameImpl(const CodeGenModule &CGM, GlobalDecl GD, const NamedDecl *ND, bool OmitMultiVersionMangling = false) { @@ -966,12 +980,23 @@ if (const auto *FD = dyn_cast(ND)) if (FD->isMultiVersion() && !OmitMultiVersionMangling) { - if (FD->isCPUDispatchMultiVersion() || FD->isCPUSpecificMultiVersion()) + switch (FD->getMultiVersionType()) { + case MultiVersionType::CPUDispatch: + case MultiVersionType::CPUSpecific: AppendCPUSpecificCPUDispatchMangling(CGM, FD->getAttr(), GD.getMultiVersionIndex(), Out); - else + break; + case MultiVersionType::Target: AppendTargetMangling(CGM, FD->getAttr(), Out); + break; + case MultiVersionType::TargetClones: + AppendTargetClonesMangling(CGM, FD->getAttr(), + GD.getMultiVersionIndex(), Out); + break; + case MultiVersionType::None: + llvm_unreachable("None multiversion type isn't valid here"); + } } return Out.str(); @@ -1386,11 +1411,13 @@ StringRef TargetCPU = getTarget().getTargetOpts().CPU; std::vector Features; const auto *FD = dyn_cast_or_null(GD.getDecl()); - FD = FD ? FD->getMostRecentDecl() : FD; - const auto *TD = FD ? FD->getAttr() : nullptr; - const auto *SD = FD ? FD->getAttr() : nullptr; + FD = FD ? FD->getMostRecentDecl() : nullptr; bool AddedAttr = false; - if (TD || SD) { + // CPUDispatch doesn't get the features list, but all other multiversioning + // does. Additionally, non-multiversion 'target' does. + if (FD && (FD->hasAttr() || + (FD->isMultiVersion() && + FD->getMultiVersionType() != MultiVersionType::CPUDispatch))) { llvm::StringMap FeatureMap; getFunctionFeatureMap(FeatureMap, GD); @@ -1402,7 +1429,7 @@ // While we populated the feature map above, we still need to // get and parse the target attribute so we can get the cpu for // the function. - if (TD) { + if (const auto *TD = FD->getAttr()) { TargetAttr::ParsedTargetAttr ParsedAttr = TD->parse(); if (ParsedAttr.Architecture != "" && getTarget().isValidCPUName(ParsedAttr.Architecture)) @@ -2417,7 +2444,13 @@ auto *Spec = FD->getAttr(); for (unsigned I = 0; I < Spec->cpus_size(); ++I) EmitGlobalFunctionDefinition(GD.getWithMultiVersionIndex(I), nullptr); - // Requires multiple emits. + } else if (FD->getMultiVersionType() == MultiVersionType::TargetClones) { + auto *Attr = FD->getAttr(); + for (unsigned I = 0; I < Attr->featuresStrs_size(); ++I) + if (Attr->isFirstOfVersion(I)) + EmitGlobalFunctionDefinition(GD.getWithMultiVersionIndex(I), nullptr); + EmitTargetClonesResolver( + GD.getWithMultiVersionIndex(Attr->featuresStrs_size())); } else EmitGlobalFunctionDefinition(GD, GV); } @@ -2620,6 +2653,63 @@ CGF.EmitMultiVersionResolver(ResolverFunc, Options); } +void CodeGenModule::EmitTargetClonesResolver(GlobalDecl GD) { + const auto *FD = cast(GD.getDecl()); + assert(FD && "Not a FunctionDecl?"); + const auto *TC = FD->getAttr(); + assert(TC && "Not a target_clones Function?"); + + QualType CanonTy = Context.getCanonicalType(FD->getType()); + llvm::Type *DeclTy = getTypes().ConvertFunctionType(CanonTy, FD); + + if (const auto *CXXFD = dyn_cast(FD)) { + const CGFunctionInfo &FInfo = getTypes().arrangeCXXMethodDeclaration(CXXFD); + DeclTy = getTypes().GetFunctionType(FInfo); + } + + llvm::Function *ResolverFunc; + if (getTarget().supportsIFunc()) { + auto *IFunc = cast(GetOrCreateMultiVersionResolver( + getTarget().supportsIFunc() ? GlobalDecl{} : GD, DeclTy, FD)); + ResolverFunc = cast(IFunc->getResolver()); + } else + ResolverFunc = cast(GetOrCreateMultiVersionResolver( + getTarget().supportsIFunc() ? GlobalDecl{} : GD, DeclTy, FD)); + + SmallVector Options; + for (unsigned VersionIndex = 0; VersionIndex < TC->featuresStrs_size(); + ++VersionIndex) { + if (!TC->isFirstOfVersion(VersionIndex)) + continue; + StringRef Version = TC->getFeatureStr(VersionIndex); + std::string MangledName = + getMangledName(GD.getWithMultiVersionIndex(VersionIndex)); + llvm::Constant *Func = GetGlobalValue(MangledName); + assert(Func && + "Should have already been created before calling resolver emit"); + + StringRef Architecture; + llvm::SmallVector Feature; + + if (Version.startswith("arch=")) + Architecture = Version.drop_front(sizeof("arch=") - 1); + else if (Version != "default") + Feature.push_back(Version); + + Options.emplace_back(cast(Func), Architecture, Feature); + } + + const TargetInfo &TI = getTarget(); + std::stable_sort( + Options.begin(), Options.end(), + [&TI](const CodeGenFunction::MultiVersionResolverOption &LHS, + const CodeGenFunction::MultiVersionResolverOption &RHS) { + return TargetMVPriority(TI, LHS) > TargetMVPriority(TI, RHS); + }); + CodeGenFunction CGF(*this); + CGF.EmitMultiVersionResolver(ResolverFunc, Options); +} + /// If a dispatcher for the specified mangled name is not in the module, create /// and return an llvm Function with the specified type. llvm::Constant *CodeGenModule::GetOrCreateMultiVersionResolver( @@ -2642,7 +2732,7 @@ // Since this is the first time we've created this IFunc, make sure // that we put this multiversioned function into the list to be // replaced later if necessary (target multiversioning only). - if (!FD->isCPUDispatchMultiVersion() && !FD->isCPUSpecificMultiVersion()) + if (FD->getMultiVersionType() == MultiVersionType::Target) MultiVersionFuncs.push_back(GD); if (getTarget().supportsIFunc()) { @@ -5427,6 +5517,16 @@ // the attribute. Target.initFeatureMap(FeatureMap, getDiags(), TargetCPU, ParsedAttr.Features); + } else if (const auto *TC = FD->getAttr()) { + std::vector Features; + StringRef VersionStr = TC->getFeatureStr(GD.getMultiVersionIndex()); + + if (VersionStr.startswith("arch=")) + TargetCPU = VersionStr.drop_front(sizeof("arch=") - 1); + else if (VersionStr != "default") + Features.push_back((StringRef{"+"} + VersionStr).str()); + + Target.initFeatureMap(FeatureMap, getDiags(), TargetCPU, Features); } else if (const auto *SD = FD->getAttr()) { llvm::SmallVector FeaturesTmp; Target.getCPUSpecificCPUDispatchFeatures( Index: lib/Sema/SemaDecl.cpp =================================================================== --- lib/Sema/SemaDecl.cpp +++ lib/Sema/SemaDecl.cpp @@ -9366,20 +9366,6 @@ PrevVD->getType()); } -namespace MultiVersioning { -enum Type { None, Target, CPUSpecific, CPUDispatch}; -} // MultiVersionType - -static MultiVersioning::Type -getMultiVersionType(const FunctionDecl *FD) { - if (FD->hasAttr()) - return MultiVersioning::Target; - if (FD->hasAttr()) - return MultiVersioning::CPUDispatch; - if (FD->hasAttr()) - return MultiVersioning::CPUSpecific; - return MultiVersioning::None; -} /// Check the target attribute of the function for MultiVersion /// validity. /// @@ -9419,7 +9405,7 @@ static bool CheckMultiVersionAdditionalRules(Sema &S, const FunctionDecl *OldFD, const FunctionDecl *NewFD, bool CausesMV, - MultiVersioning::Type MVType) { + MultiVersionType MVType) { enum DoesntSupport { FuncTemplates = 0, VirtFuncs = 1, @@ -9439,9 +9425,12 @@ Linkage = 5 }; - bool IsCPUSpecificCPUDispatchMVType = - MVType == MultiVersioning::CPUDispatch || - MVType == MultiVersioning::CPUSpecific; + unsigned MVTypeForDiag = static_cast(MVType) - 1; + + if (MVType == MultiVersionType::None) { + assert(OldFD->getMultiVersionType() == MultiVersionType::TargetClones); + MVTypeForDiag = static_cast(MultiVersionType::TargetClones) - 1; + } if (OldFD && !OldFD->getType()->getAs()) { S.Diag(OldFD->getLocation(), diag::err_multiversion_noproto); @@ -9462,50 +9451,52 @@ // For now, disallow all other attributes. These should be opt-in, but // an analysis of all of them is a future FIXME. if (CausesMV && OldFD && - std::distance(OldFD->attr_begin(), OldFD->attr_end()) != 1) { + std::distance(OldFD->attr_begin(), OldFD->attr_end()) != + (MVType == MultiVersionType::None ? 0 : 1)) { S.Diag(OldFD->getLocation(), diag::err_multiversion_no_other_attrs) - << IsCPUSpecificCPUDispatchMVType; + << MVTypeForDiag; S.Diag(NewFD->getLocation(), diag::note_multiversioning_caused_here); return true; } - if (std::distance(NewFD->attr_begin(), NewFD->attr_end()) != 1) + if (std::distance(NewFD->attr_begin(), NewFD->attr_end()) != + (MVType == MultiVersionType::None ? 0 : 1)) return S.Diag(NewFD->getLocation(), diag::err_multiversion_no_other_attrs) - << IsCPUSpecificCPUDispatchMVType; + << MVTypeForDiag; if (NewFD->getTemplatedKind() == FunctionDecl::TK_FunctionTemplate) return S.Diag(NewFD->getLocation(), diag::err_multiversion_doesnt_support) - << IsCPUSpecificCPUDispatchMVType << FuncTemplates; + << MVTypeForDiag << FuncTemplates; if (const auto *NewCXXFD = dyn_cast(NewFD)) { if (NewCXXFD->isVirtual()) return S.Diag(NewCXXFD->getLocation(), diag::err_multiversion_doesnt_support) - << IsCPUSpecificCPUDispatchMVType << VirtFuncs; + << MVTypeForDiag << VirtFuncs; if (const auto *NewCXXCtor = dyn_cast(NewFD)) return S.Diag(NewCXXCtor->getLocation(), diag::err_multiversion_doesnt_support) - << IsCPUSpecificCPUDispatchMVType << Constructors; + << MVTypeForDiag << Constructors; if (const auto *NewCXXDtor = dyn_cast(NewFD)) return S.Diag(NewCXXDtor->getLocation(), diag::err_multiversion_doesnt_support) - << IsCPUSpecificCPUDispatchMVType << Destructors; + << MVTypeForDiag << Destructors; } if (NewFD->isDeleted()) return S.Diag(NewFD->getLocation(), diag::err_multiversion_doesnt_support) - << IsCPUSpecificCPUDispatchMVType << DeletedFuncs; + << MVTypeForDiag << DeletedFuncs; if (NewFD->isDefaulted()) return S.Diag(NewFD->getLocation(), diag::err_multiversion_doesnt_support) - << IsCPUSpecificCPUDispatchMVType << DefaultedFuncs; + << MVTypeForDiag << DefaultedFuncs; - if (NewFD->isConstexpr() && (MVType == MultiVersioning::CPUDispatch || - MVType == MultiVersioning::CPUSpecific)) + if (NewFD->isConstexpr() && (MVType == MultiVersionType::CPUDispatch || + MVType == MultiVersionType::CPUSpecific)) return S.Diag(NewFD->getLocation(), diag::err_multiversion_doesnt_support) - << IsCPUSpecificCPUDispatchMVType << ConstexprFuncs; + << MVTypeForDiag << ConstexprFuncs; QualType NewQType = S.getASTContext().getCanonicalType(NewFD->getType()); const auto *NewType = cast(NewQType); @@ -9513,7 +9504,7 @@ if (NewReturnType->isUndeducedType()) return S.Diag(NewFD->getLocation(), diag::err_multiversion_doesnt_support) - << IsCPUSpecificCPUDispatchMVType << DeducedReturn; + << MVTypeForDiag << DeducedReturn; // Only allow transition to MultiVersion if it hasn't been used. if (OldFD && CausesMV && OldFD->isUsed(false)) @@ -9567,19 +9558,19 @@ /// /// Returns true if there was an error, false otherwise. static bool CheckMultiVersionFirstFunction(Sema &S, FunctionDecl *FD, - MultiVersioning::Type MVType, + MultiVersionType MVType, const TargetAttr *TA, const CPUDispatchAttr *CPUDisp, const CPUSpecificAttr *CPUSpec) { - assert(MVType != MultiVersioning::None && + assert(MVType != MultiVersionType::None && "Function lacks multiversion attribute"); // Target only causes MV if it is default, otherwise this is a normal // function. - if (MVType == MultiVersioning::Target && !TA->isDefaultVersion()) + if (MVType == MultiVersionType::Target && !TA->isDefaultVersion()) return false; - if (MVType == MultiVersioning::Target && CheckMultiVersionValue(S, FD)) { + if (MVType == MultiVersionType::Target && CheckMultiVersionValue(S, FD)) { FD->setInvalidDecl(); return true; } @@ -9616,7 +9607,7 @@ } if (CheckMultiVersionAdditionalRules(S, OldFD, NewFD, true, - MultiVersioning::Target)) { + MultiVersionType::Target)) { NewFD->setInvalidDecl(); return true; } @@ -9662,21 +9653,30 @@ return false; } +static bool MultiVersionTypesCompatible(MultiVersionType Old, + MultiVersionType New) { + if (Old == New || Old == MultiVersionType::None || + New == MultiVersionType::None) + return true; + + return (Old == MultiVersionType::CPUDispatch && + New == MultiVersionType::CPUSpecific) || + (Old == MultiVersionType::CPUSpecific && + New == MultiVersionType::CPUDispatch); +} + /// Check the validity of a new function declaration being added to an existing /// multiversioned declaration collection. static bool CheckMultiVersionAdditionalDecl( Sema &S, FunctionDecl *OldFD, FunctionDecl *NewFD, - MultiVersioning::Type NewMVType, const TargetAttr *NewTA, + MultiVersionType NewMVType, const TargetAttr *NewTA, const CPUDispatchAttr *NewCPUDisp, const CPUSpecificAttr *NewCPUSpec, - bool &Redeclaration, NamedDecl *&OldDecl, bool &MergeTypeWithPrevious, - LookupResult &Previous) { + const TargetClonesAttr *NewClones, bool &Redeclaration, NamedDecl *&OldDecl, + bool &MergeTypeWithPrevious, LookupResult &Previous) { - MultiVersioning::Type OldMVType = getMultiVersionType(OldFD); + MultiVersionType OldMVType = OldFD->getMultiVersionType(); // Disallow mixing of multiversioning types. - if ((OldMVType == MultiVersioning::Target && - NewMVType != MultiVersioning::Target) || - (NewMVType == MultiVersioning::Target && - OldMVType != MultiVersioning::Target)) { + if (!MultiVersionTypesCompatible(OldMVType, NewMVType)) { S.Diag(NewFD->getLocation(), diag::err_multiversion_types_mixed); S.Diag(OldFD->getLocation(), diag::note_previous_declaration); NewFD->setInvalidDecl(); @@ -9701,7 +9701,12 @@ if (S.IsOverload(NewFD, CurFD, UseMemberUsingDeclRules)) continue; - if (NewMVType == MultiVersioning::Target) { + switch (NewMVType) { + case MultiVersionType::None: + assert(OldMVType == MultiVersionType::TargetClones && + "Only target_clones can be omitted in subsequent declarations"); + break; + case MultiVersionType::Target: { const auto *CurTA = CurFD->getAttr(); if (CurTA->getFeaturesStr() == NewTA->getFeaturesStr()) { NewFD->setIsMultiVersion(); @@ -9718,13 +9723,36 @@ NewFD->setInvalidDecl(); return true; } - } else { + break; + } + case MultiVersionType::TargetClones: { + const auto *CurClones = CurFD->getAttr(); + Redeclaration = true; + OldDecl = CurFD; + MergeTypeWithPrevious = true; + NewFD->setIsMultiVersion(); + + if (CurClones && NewClones && + (CurClones->featuresStrs_size() != NewClones->featuresStrs_size() || + !std::equal(CurClones->featuresStrs_begin(), + CurClones->featuresStrs_end(), + NewClones->featuresStrs_begin()))) { + S.Diag(NewFD->getLocation(), diag::err_target_clone_doesnt_match); + S.Diag(CurFD->getLocation(), diag::note_previous_declaration); + NewFD->setInvalidDecl(); + return true; + } + + return false; + } + case MultiVersionType::CPUSpecific: + case MultiVersionType::CPUDispatch: { const auto *CurCPUSpec = CurFD->getAttr(); const auto *CurCPUDisp = CurFD->getAttr(); // Handle CPUDispatch/CPUSpecific versions. // Only 1 CPUDispatch function is allowed, this will make it go through // the redeclaration errors. - if (NewMVType == MultiVersioning::CPUDispatch && + if (NewMVType == MultiVersionType::CPUDispatch && CurFD->hasAttr()) { if (CurCPUDisp->cpus_size() == NewCPUDisp->cpus_size() && std::equal( @@ -9745,7 +9773,7 @@ NewFD->setInvalidDecl(); return true; } - if (NewMVType == MultiVersioning::CPUSpecific && CurCPUSpec) { + if (NewMVType == MultiVersionType::CPUSpecific && CurCPUSpec) { if (CurCPUSpec->cpus_size() == NewCPUSpec->cpus_size() && std::equal( @@ -9773,15 +9801,15 @@ } } } - // If the two decls aren't the same MVType, there is no possible error - // condition. + break; + } } } // Else, this is simply a non-redecl case. Checking the 'value' is only // necessary in the Target case, since The CPUSpecific/Dispatch cases are // handled in the attribute adding step. - if (NewMVType == MultiVersioning::Target && + if (NewMVType == MultiVersionType::Target && CheckMultiVersionValue(S, NewFD)) { NewFD->setInvalidDecl(); return true; @@ -9800,7 +9828,6 @@ return false; } - /// Check the validity of a mulitversion function declaration. /// Also sets the multiversion'ness' of the function itself. /// @@ -9814,23 +9841,15 @@ const auto *NewTA = NewFD->getAttr(); const auto *NewCPUDisp = NewFD->getAttr(); const auto *NewCPUSpec = NewFD->getAttr(); + const auto *NewTargetClones = NewFD->getAttr(); - // Mixing Multiversioning types is prohibited. - if ((NewTA && NewCPUDisp) || (NewTA && NewCPUSpec) || - (NewCPUDisp && NewCPUSpec)) { - S.Diag(NewFD->getLocation(), diag::err_multiversion_types_mixed); - NewFD->setInvalidDecl(); - return true; - } - - MultiVersioning::Type MVType = getMultiVersionType(NewFD); + MultiVersionType MVType = NewFD->getMultiVersionType(); // Main isn't allowed to become a multiversion function, however it IS // permitted to have 'main' be marked with the 'target' optimization hint. if (NewFD->isMain()) { - if ((MVType == MultiVersioning::Target && NewTA->isDefaultVersion()) || - MVType == MultiVersioning::CPUDispatch || - MVType == MultiVersioning::CPUSpecific) { + if (MVType != MultiVersionType::None && + !(MVType == MultiVersionType::Target && !NewTA->isDefaultVersion())) { S.Diag(NewFD->getLocation(), diag::err_multiversion_not_allowed_on_main); NewFD->setInvalidDecl(); return true; @@ -9843,7 +9862,7 @@ NewFD->getDeclContext()->getRedeclContext()) { // If there's no previous declaration, AND this isn't attempting to cause // multiversioning, this isn't an error condition. - if (MVType == MultiVersioning::None) + if (MVType == MultiVersionType::None) return false; return CheckMultiVersionFirstFunction(S, NewFD, MVType, NewTA, NewCPUDisp, NewCPUSpec); @@ -9851,36 +9870,50 @@ FunctionDecl *OldFD = OldDecl->getAsFunction(); - if (!OldFD->isMultiVersion() && MVType == MultiVersioning::None) + if (!OldFD->isMultiVersion() && MVType == MultiVersionType::None) return false; - if (OldFD->isMultiVersion() && MVType == MultiVersioning::None) { + // Multiversioned redeclarations aren't allowed to omit the attribute, except + // for target_clones. + if (OldFD->isMultiVersion() && MVType == MultiVersionType::None && + OldFD->getMultiVersionType() != MultiVersionType::TargetClones) { S.Diag(NewFD->getLocation(), diag::err_multiversion_required_in_redecl) - << (getMultiVersionType(OldFD) != MultiVersioning::Target); + << (OldFD->getMultiVersionType() != MultiVersionType::Target); NewFD->setInvalidDecl(); return true; } - // Handle the target potentially causes multiversioning case. - if (!OldFD->isMultiVersion() && MVType == MultiVersioning::Target) - return CheckTargetCausesMultiVersioning(S, OldFD, NewFD, NewTA, - Redeclaration, OldDecl, - MergeTypeWithPrevious, Previous); - // Previous declarations lack CPUDispatch/CPUSpecific. if (!OldFD->isMultiVersion()) { - S.Diag(OldFD->getLocation(), diag::err_multiversion_required_in_redecl) - << 1; - S.Diag(NewFD->getLocation(), diag::note_multiversioning_caused_here); - NewFD->setInvalidDecl(); - return true; + switch (MVType) { + case MultiVersionType::Target: + return CheckTargetCausesMultiVersioning(S, OldFD, NewFD, NewTA, + Redeclaration, OldDecl, + MergeTypeWithPrevious, Previous); + case MultiVersionType::TargetClones: + if (OldFD->isUsed(false)) { + NewFD->setInvalidDecl(); + return S.Diag(NewFD->getLocation(), diag::err_multiversion_after_used); + } + OldFD->setIsMultiVersion(); + break; + case MultiVersionType::CPUDispatch: + case MultiVersionType::CPUSpecific: + NewFD->setInvalidDecl(); + S.Diag(OldFD->getLocation(), diag::err_multiversion_required_in_redecl) + << 1; + return S.Diag(NewFD->getLocation(), + diag::note_multiversioning_caused_here); + case MultiVersionType::None: + break; + } } // At this point, we have a multiversion function decl (in OldFD) AND an // appropriate attribute in the current function decl. Resolve that these are // still compatible with previous declarations. return CheckMultiVersionAdditionalDecl( - S, OldFD, NewFD, MVType, NewTA, NewCPUDisp, NewCPUSpec, Redeclaration, - OldDecl, MergeTypeWithPrevious, Previous); + S, OldFD, NewFD, MVType, NewTA, NewCPUDisp, NewCPUSpec, NewTargetClones, + Redeclaration, OldDecl, MergeTypeWithPrevious, Previous); } /// Perform semantic checking of a new function declaration. Index: lib/Sema/SemaDeclAttr.cpp =================================================================== --- lib/Sema/SemaDeclAttr.cpp +++ lib/Sema/SemaDeclAttr.cpp @@ -1889,6 +1889,11 @@ } static void handleCPUSpecificAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + if (checkAttrMutualExclusion(S, D, AL) || + checkAttrMutualExclusion(S, D, AL) || + checkAttrMutualExclusion(S, D, AL) || + checkAttrMutualExclusion(S, D, AL)) + return; FunctionDecl *FD = cast(D); if (const auto *MD = dyn_cast(D)) { @@ -3028,33 +3033,40 @@ bool Sema::checkTargetAttr(SourceLocation LiteralLoc, StringRef AttrStr) { enum FirstParam { Unsupported, Duplicate }; enum SecondParam { None, Architecture }; + enum ThirdParam { Target, TargetClones }; for (auto Str : {"tune=", "fpmath="}) if (AttrStr.find(Str) != StringRef::npos) return Diag(LiteralLoc, diag::warn_unsupported_target_attribute) - << Unsupported << None << Str; + << Unsupported << None << Str << Target; TargetAttr::ParsedTargetAttr ParsedAttrs = TargetAttr::parse(AttrStr); if (!ParsedAttrs.Architecture.empty() && !Context.getTargetInfo().isValidCPUName(ParsedAttrs.Architecture)) return Diag(LiteralLoc, diag::warn_unsupported_target_attribute) - << Unsupported << Architecture << ParsedAttrs.Architecture; + << Unsupported << Architecture << ParsedAttrs.Architecture << Target; if (ParsedAttrs.DuplicateArchitecture) return Diag(LiteralLoc, diag::warn_unsupported_target_attribute) - << Duplicate << None << "arch="; + << Duplicate << None << "arch=" << Target; for (const auto &Feature : ParsedAttrs.Features) { auto CurFeature = StringRef(Feature).drop_front(); // remove + or -. if (!Context.getTargetInfo().isValidFeatureName(CurFeature)) return Diag(LiteralLoc, diag::warn_unsupported_target_attribute) - << Unsupported << None << CurFeature; + << Unsupported << None << CurFeature << Target; } return false; } static void handleTargetAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + if (checkAttrMutualExclusion(S, D, AL) || + checkAttrMutualExclusion(S, D, AL) || + checkAttrMutualExclusion(S, D, AL) || + checkAttrMutualExclusion(S, D, AL)) + return; + StringRef Str; SourceLocation LiteralLoc; if (!S.checkStringLiteralArgumentAttr(AL, 0, Str, &LiteralLoc) || @@ -3086,6 +3098,98 @@ AL.getAttributeSpellingListIndex())); } +bool Sema::checkTargetClonesAttrString(SourceLocation LiteralLoc, StringRef Str, + const ArgsUnion Arg, bool &HasDefault, + bool &HasCommas, + SmallVectorImpl &Strings) { + enum FirstParam { Unsupported, Duplicate }; + enum SecondParam { None, Architecture }; + enum ThirdParam { Target, TargetClones }; + const StringLiteral *Literal = + cast(Arg.get()->IgnoreParenCasts()); + HasCommas = Str.contains(','); + + // Warn on empty at the beginning of a string. + if (Str.size() == 0) + return Diag(LiteralLoc, diag::warn_unsupported_target_attribute) + << Unsupported << None << "" << TargetClones; + + std::pair Parts = {{}, Str}; + while (!Parts.second.empty()) { + Parts = Parts.second.split(','); + StringRef Cur = Parts.first.trim(); + SourceLocation CurLoc = Literal->getLocationOfByte( + Cur.data() - Literal->getString().data(), getSourceManager(), + getLangOpts(), Context.getTargetInfo()); + + bool DefaultIsDupe = false; + + if (Cur.empty()) + return Diag(CurLoc, diag::warn_unsupported_target_attribute) + << Unsupported << None << "" << TargetClones; + + if (Cur.startswith("arch=")) { + if (!Context.getTargetInfo().isValidCPUName( + Cur.drop_front(sizeof("arch=") - 1))) + return Diag(CurLoc, diag::warn_unsupported_target_attribute) + << Unsupported << Architecture + << Cur.drop_front(sizeof("arch=") - 1) << TargetClones; + } else if (Cur == "default") { + DefaultIsDupe = HasDefault; + HasDefault = true; + } else if (!Context.getTargetInfo().isValidFeatureName(Cur)) + return Diag(CurLoc, diag::warn_unsupported_target_attribute) + << Unsupported << None << Cur << TargetClones; + + if (llvm::find(Strings, Cur) != Strings.end() || DefaultIsDupe) + Diag(CurLoc, diag::warn_target_clone_duplicate_options); + // Note: Add even if there are duplicates, since it changes name mangling. + Strings.push_back(Cur); + } + + if (Str.rtrim().endswith(",")) + return Diag(LiteralLoc, diag::warn_unsupported_target_attribute) + << Unsupported << None << "" << TargetClones; + + return false; +} + +static void handleTargetClonesAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + if (checkAttrMutualExclusion(S, D, AL) || + checkAttrMutualExclusion(S, D, AL) || + checkAttrMutualExclusion(S, D, AL) || + checkAttrMutualExclusion(S, D, AL)) + return; + + SmallVector Strings; + bool HasDefault = false; + bool HasCommas = false; + + for (unsigned I = 0, E = AL.getNumArgs(); I != E; ++I) { + StringRef CurStr; + SourceLocation LiteralLoc; + if (!S.checkStringLiteralArgumentAttr(AL, I, CurStr, &LiteralLoc) || + S.checkTargetClonesAttrString(LiteralLoc, CurStr, AL.getArg(I), + HasDefault, HasCommas, Strings)) + return; + } + + if (HasCommas && AL.getNumArgs() > 1) + S.Diag(AL.getLoc(), diag::warn_target_clone_mixed_values); + + if (!HasDefault) { + S.Diag(AL.getLoc(), diag::err_target_clone_must_have_default); + return; + } + + FunctionDecl *FD = cast(D); + FD->setIsMultiVersion(true); + TargetClonesAttr *NewAttr = ::new (S.Context) + TargetClonesAttr(AL.getRange(), S.Context, Strings.data(), Strings.size(), + AL.getAttributeSpellingListIndex()); + D->addAttr(NewAttr); +} + static void handleCleanupAttr(Sema &S, Decl *D, const ParsedAttr &AL) { Expr *E = AL.getArgAsExpr(0); SourceLocation Loc = E->getExprLoc(); @@ -6371,6 +6475,9 @@ case ParsedAttr::AT_Target: handleTargetAttr(S, D, AL); break; + case ParsedAttr::AT_TargetClones: + handleTargetClonesAttr(S, D, AL); + break; case ParsedAttr::AT_MinVectorWidth: handleMinVectorWidthAttr(S, D, AL); break; Index: test/CodeGen/attr-target-clones.c =================================================================== --- /dev/null +++ test/CodeGen/attr-target-clones.c @@ -0,0 +1,93 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm %s -o - | FileCheck %s --check-prefixes=LINUX,CHECK +// RUN: %clang_cc1 -triple x86_64-windows-pc -emit-llvm %s -o - | FileCheck %s --check-prefixes=WINDOWS,CHECK + +// LINUX: @foo.ifunc = ifunc i32 (), i32 ()* ()* @foo.resolver +// LINUX: @foo_inline.ifunc = ifunc i32 (), i32 ()* ()* @foo_inline.resolver +// LINUX: @foo_decls.ifunc = ifunc void (), void ()* ()* @foo_decls.resolver +// LINUX: @unused.ifunc = ifunc void (), void ()* ()* @unused.resolver + +int __attribute__((target_clones("sse4.2, default"))) foo(void) { return 0; } +// LINUX: define i32 @foo.sse4.2.0() +// LINUX: define i32 @foo.default.1() +// LINUX: define i32 ()* @foo.resolver() +// LINUX: ret i32 ()* @foo.sse4.2.0 +// LINUX: ret i32 ()* @foo.default.1 + +// WINDOWS: define dso_local i32 @foo.sse4.2.0() +// WINDOWS: define dso_local i32 @foo.default.1() +// WINDOWS: define dso_local i32 @foo() +// WINDOWS: musttail call i32 @foo.sse4.2.0 +// WINDOWS: musttail call i32 @foo.default.1 + +int bar() { + // LINUX: define i32 @bar() + // WINDOWS: define dso_local i32 @bar() + return foo(); + // LINUX: call i32 @foo.ifunc() + // WINDOWS: call i32 @foo() +} + +inline int __attribute__((target_clones("arch=sandybridge,default,sse4.2"))) +foo_inline(void) { return 0; } +// LINUX: define linkonce i32 @foo_inline.arch_sandybridge.0() #[[SB:[0-9]+]] +// LINUX: define linkonce i32 @foo_inline.default.2() #[[DEF:[0-9]+]] +// LINUX: define linkonce i32 @foo_inline.sse4.2.1() #[[SSE42:[0-9]+]] +// LINUX: define i32 ()* @foo_inline.resolver() +// LINUX: ret i32 ()* @foo_inline.arch_sandybridge.0 +// LINUX: ret i32 ()* @foo_inline.sse4.2.1 +// LINUX: ret i32 ()* @foo_inline.default.2 + +// WINDOWS: define linkonce_odr dso_local i32 @foo_inline.arch_sandybridge.0() #[[SB:[0-9]+]] +// WINDOWS: define linkonce_odr dso_local i32 @foo_inline.default.2() #[[DEF:[0-9]+]] +// WINDOWS: define linkonce_odr dso_local i32 @foo_inline.sse4.2.1() #[[SSE42:[0-9]+]] +// WINDOWS: define dso_local i32 @foo_inline() +// WINDOWS: musttail call i32 @foo_inline.arch_sandybridge.0 +// WINDOWS: musttail call i32 @foo_inline.sse4.2.1 +// WINDOWS: musttail call i32 @foo_inline.default.2 + +int bar2() { + // LINUX: define i32 @bar2() + // WINDOWS: define dso_local i32 @bar2() + return foo_inline(); + // LINUX: call i32 @foo_inline.ifunc() + // WINDOWS: call i32 @foo_inline() +} + +inline __attribute__((target_clones("default,default ,sse4.2"))) void foo_decls(void) {} +// LINUX: define linkonce void @foo_decls.default.1() +// LINUX: define linkonce void @foo_decls.sse4.2.0() +// LINUX: define void ()* @foo_decls.resolver() +// LINUX: ret void ()* @foo_decls.sse4.2.0 +// LINUX: ret void ()* @foo_decls.default.1 + +// WINDOWS: define linkonce_odr dso_local void @foo_decls.default.1() +// WINDOWS: define linkonce_odr dso_local void @foo_decls.sse4.2.0() +// WINDOWS: define dso_local void @foo_decls() +// WINDOWS: musttail call void @foo_decls.sse4.2.0 +// WINDOWS: musttail call void @foo_decls.default.1 + +void bar3() { + // LINUX: define void @bar3() + // WINDOWS: define dso_local void @bar3() + foo_decls(); + // LINUX: call void @foo_decls.ifunc() + // WINDOWS: call void @foo_decls() +} + +void __attribute__((target_clones("default, arch=ivybridge"))) unused(void) {} +// LINUX: define void @unused.default.1() +// LINUX: define void @unused.arch_ivybridge.0() +// LINUX: define void ()* @unused.resolver() +// LINUX: ret void ()* @unused.arch_ivybridge.0 +// LINUX: ret void ()* @unused.default.1 + +// WINDOWS: define dso_local void @unused.default.1() +// WINDOWS: define dso_local void @unused.arch_ivybridge.0() +// WINDOWS: define dso_local void @unused() +// WINDOWS: musttail call void @unused.arch_ivybridge.0 +// WINDOWS: musttail call void @unused.default.1 + +// CHECK: attributes #[[SSE42]] = +// CHECK-SAME: "target-features"="+mmx,+popcnt,+sse,+sse2,+sse3,+sse4.1,+sse4.2,+ssse3,+x87" +// CHECK: attributes #[[DEF]] = +// CHECK-SAME: "target-features"="+mmx,+sse,+sse2,+x87" 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 129 attributes: +// CHECK: #pragma clang attribute supports 130 attributes: // CHECK-NEXT: AMDGPUFlatWorkGroupSize (SubjectMatchRule_function) // CHECK-NEXT: AMDGPUNumSGPR (SubjectMatchRule_function) // CHECK-NEXT: AMDGPUNumVGPR (SubjectMatchRule_function) @@ -121,6 +121,7 @@ // CHECK-NEXT: SwiftIndirectResult (SubjectMatchRule_variable_is_parameter) // CHECK-NEXT: TLSModel (SubjectMatchRule_variable_is_thread_local) // CHECK-NEXT: Target (SubjectMatchRule_function) +// CHECK-NEXT: TargetClones (SubjectMatchRule_function) // CHECK-NEXT: TestTypestate (SubjectMatchRule_function_is_member) // CHECK-NEXT: TrivialABI (SubjectMatchRule_record) // CHECK-NEXT: VecReturn (SubjectMatchRule_record) Index: test/Sema/attr-cpuspecific.c =================================================================== --- test/Sema/attr-cpuspecific.c +++ test/Sema/attr-cpuspecific.c @@ -82,7 +82,8 @@ // expected-note@-2 {{previous declaration is here}} void __attribute__((cpu_specific(sandybridge))) addtl_attrs2(void); -// expected-error@+2 {{multiversioning attributes cannot be combined}} +// expected-error@+2 {{'cpu_dispatch' and 'cpu_specific' attributes are not compatible}} +// expected-note@+1 {{conflicting attribute is here}} void __attribute((cpu_specific(sandybridge), cpu_dispatch(atom, sandybridge))) combine_attrs(void); Index: test/Sema/attr-target-clones.c =================================================================== --- /dev/null +++ test/Sema/attr-target-clones.c @@ -0,0 +1,87 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu -fsyntax-only -verify %s + +// expected-error@+1 {{'target_clones' multiversioning requires a default target}} +void __attribute__((target_clones("sse4.2", "arch=sandybridge"))) +no_default(void); + +// expected-error@+2 {{'target_clones' and 'target' attributes are not compatible}} +// expected-note@+1 {{conflicting attribute is here}} +void __attribute__((target("sse4.2"), target_clones("arch=sandybridge"))) +ignored_attr(void); +// expected-error@+2 {{'target' and 'target_clones' attributes are not compatible}} +// expected-note@+1 {{conflicting attribute is here}} +void __attribute__((target_clones("arch=sandybridge,default"), target("sse4.2"))) +ignored_attr2(void); + +int redecl(void); +int __attribute__((target_clones("sse4.2", "default"))) redecl(void) { return 1; } + +int __attribute__((target_clones("sse4.2", "default"))) redecl2(void); +int __attribute__((target_clones("sse4.2", "default"))) redecl2(void) { return 1; } + +int __attribute__((target_clones("sse4.2", "default"))) redecl3(void); +int redecl3(void); + +int __attribute__((target_clones("sse4.2", "arch=atom", "default"))) redecl4(void); +// expected-error@+3 {{'target_clones' attribute does not match previous declaration}} +// expected-note@-2 {{previous declaration is here}} +int __attribute__((target_clones("sse4.2", "arch=sandybridge", "default"))) +redecl4(void) { return 1; } + +int __attribute__((target("sse4.2"))) redef2(void) { return 1; } +// expected-error@+2 {{multiversioning attributes cannot be combined}} +// expected-note@-2 {{previous declaration is here}} +int __attribute__((target_clones("sse4.2", "default"))) redef2(void) { return 1; } + +int __attribute__((target_clones("sse4.2,default"))) redef3(void) { return 1; } +// expected-error@+2 {{redefinition of 'redef3'}} +// expected-note@-2 {{previous definition is here}} +int __attribute__((target_clones("sse4.2,default"))) redef3(void) { return 1; } + +int __attribute__((target_clones("sse4.2,default"))) redef4(void) { return 1; } +// expected-error@+2 {{redefinition of 'redef4'}} +// expected-note@-2 {{previous definition is here}} +int __attribute__((target_clones("sse4.2,default"))) redef4(void) { return 1; } + +// Duplicates are allowed, however they alter name mangling. +// expected-warning@+2 {{mixing 'target_clones' specifier mechanisms is permitted}} +// expected-warning@+1 2 {{version list contains duplicate entries}} +int __attribute__((target_clones("arch=atom,arch=atom", "arch=atom,default"))) +dupes(void) { return 1; } + +// expected-warning@+1 {{unsupported '' in the 'target_clones' attribute string;}} +void __attribute__((target_clones(""))) +empty_target_1(void); +// expected-warning@+1 {{unsupported '' in the 'target_clones' attribute string;}} +void __attribute__((target_clones(",default"))) +empty_target_2(void); +// expected-warning@+1 {{unsupported '' in the 'target_clones' attribute string;}} +void __attribute__((target_clones("default,"))) +empty_target_3(void); +// expected-warning@+1 {{unsupported '' in the 'target_clones' attribute string;}} +void __attribute__((target_clones("default, ,avx2"))) +empty_target_4(void); + +// expected-warning@+1 {{unsupported '' in the 'target_clones' attribute string;}} +void __attribute__((target_clones("default,avx2", ""))) +empty_target_5(void); + +// expected-warning@+1 {{version list contains duplicate entries}} +void __attribute__((target_clones("default", "default"))) +dupe_default(void); + +// expected-warning@+1 {{version list contains duplicate entries}} +void __attribute__((target_clones("avx2,avx2,default"))) +dupe_normal(void); + +// expected-error@+2 {{'target_clones' and 'target_clones' attributes are not compatible}} +// expected-note@+1 {{conflicting attribute is here}} +void __attribute__((target_clones("avx2,default"), target_clones("arch=atom,default"))) +dupe_normal2(void); + +int mv_after_use(void); +int useage() { + return mv_after_use(); +} +// expected-error@+1 {{function declaration cannot become a multiversioned function after first usage}} +int __attribute__((target_clones("sse4.2", "default"))) mv_after_use(void) { return 1; }