Author: Wael Yehia
Date: 2026-03-17T23:15:15-04:00
New Revision: 495c518b96cbfb9ad756860d3715f1afdd7cf496

URL: 
https://github.com/llvm/llvm-project/commit/495c518b96cbfb9ad756860d3715f1afdd7cf496
DIFF: 
https://github.com/llvm/llvm-project/commit/495c518b96cbfb9ad756860d3715f1afdd7cf496.diff

LOG: [FMV][AIX] Implement target_clones (cpu-only) (#177428)

This PR implements Function Multi-versioning on AIX using `__attribute__
((target_clones(<feature-list>)))`.
Initially, we will only support specifying a cpu in the version list. 
Feature strings (such as "altivec" or "isel") on target_clones will be
implemented in a future PR.

Accepted syntax:
```
__attribute__((target_clones(<OPTIONS>)))
```
where `<OPTIONS>` is a comma separated list of strings, each string is
either:
1) the default string `"default"`
2) a cpu string `"cpu=<CPU>"`, where `<CPU>`is a value accepted by the
`-mcpu` flag.
For example, specifying the following on a function
```
__attribute__((target_clones("default", "cpu=power8", "cpu=power9")))
int foo(int x) { return x + 1; }
```
Would generate 3 versions of `foo`: (1) `foo.default`, (2)
`foo.cpu_power8`, and (3) `foo.cpu_power9`,
an IFUNC `foo`, and the resolver function `foo.resolver`, for the IFUNC,
that chooses one of the three versions at runtime.

---------

Co-authored-by: Wael Yehia <[email protected]>

Added: 
    clang/test/CodeGen/PowerPC/attr-target-clones.c
    clang/test/Sema/PowerPC/attr-target-clones.c

Modified: 
    clang/include/clang/Basic/AttrDocs.td
    clang/include/clang/Basic/TargetInfo.h
    clang/include/clang/Sema/SemaPPC.h
    clang/lib/AST/ASTContext.cpp
    clang/lib/Basic/Targets/PPC.cpp
    clang/lib/Basic/Targets/PPC.h
    clang/lib/CodeGen/CodeGenFunction.cpp
    clang/lib/CodeGen/CodeGenFunction.h
    clang/lib/CodeGen/CodeGenModule.cpp
    clang/lib/CodeGen/TargetBuiltins/PPC.cpp
    clang/lib/CodeGen/Targets/PPC.cpp
    clang/lib/Sema/SemaDeclAttr.cpp
    clang/lib/Sema/SemaPPC.cpp
    clang/test/Sema/attr-target.c

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/Basic/AttrDocs.td 
b/clang/include/clang/Basic/AttrDocs.td
index 815ed11ee3b22..b414f23091ce0 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -3294,6 +3294,12 @@ multiversioned function would have if it had been 
declared without the attribute
 For backward compatibility with earlier Clang releases, a function alias with 
an
 ``.ifunc`` suffix is also emitted. The  ``.ifunc`` suffixed symbol is a 
deprecated
 feature and support for it may be removed in the future.
+
+For PowerPC targets, ``target_clones`` is supported on AIX only. Only CPU
+(specified as ``cpu=CPU``) and ``default`` options are allowed. IFUNC is 
supported
+on AIX in Clang, so dispatch is implemented similar to other targets using 
IFUNC.
+An FMV function that is only declared in a translation unit is treated as a
+non-FMV. The resolver and the function clones are given internal linkage.
 }];
 }
 

diff  --git a/clang/include/clang/Basic/TargetInfo.h 
b/clang/include/clang/Basic/TargetInfo.h
index f3a85737f6571..9f7d2a17a0f8a 100644
--- a/clang/include/clang/Basic/TargetInfo.h
+++ b/clang/include/clang/Basic/TargetInfo.h
@@ -1571,7 +1571,7 @@ class TargetInfo : public TransferrableTargetInfo,
   /// which requires support for cpu_supports and cpu_is functionality.
   bool supportsMultiVersioning() const {
     return getTriple().isX86() || getTriple().isAArch64() ||
-           getTriple().isRISCV();
+           getTriple().isRISCV() || getTriple().isOSAIX();
   }
 
   /// Identify whether this target supports IFuncs.

diff  --git a/clang/include/clang/Sema/SemaPPC.h 
b/clang/include/clang/Sema/SemaPPC.h
index f8edecc4fcb7b..9dad80acc1747 100644
--- a/clang/include/clang/Sema/SemaPPC.h
+++ b/clang/include/clang/Sema/SemaPPC.h
@@ -53,6 +53,11 @@ class SemaPPC : public SemaBase {
   // vector double vec_xxpermdi(vector double, vector double, int);
   // vector short vec_xxsldwi(vector short, vector short, int);
   bool BuiltinVSX(CallExpr *TheCall);
+
+  bool checkTargetClonesAttr(const SmallVectorImpl<StringRef> &Params,
+                             const SmallVectorImpl<SourceLocation> &Locs,
+                             SmallVectorImpl<SmallString<64>> &NewParams,
+                             SourceLocation AttrLoc);
 };
 } // namespace clang
 

diff  --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index 41852c960cc46..a8cfdca1cb96d 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -15247,6 +15247,14 @@ void 
ASTContext::getFunctionFeatureMap(llvm::StringMap<bool> &FeatureMap,
                       Target->getTargetOpts().FeaturesAsWritten.begin(),
                       Target->getTargetOpts().FeaturesAsWritten.end());
       Target->initFeatureMap(FeatureMap, getDiagnostics(), TargetCPU, 
Features);
+    } else if (Target->getTriple().isOSAIX()) {
+      std::vector<std::string> Features;
+      StringRef VersionStr = TC->getFeatureStr(GD.getMultiVersionIndex());
+      if (VersionStr.starts_with("cpu="))
+        TargetCPU = VersionStr.drop_front(sizeof("cpu=") - 1);
+      else
+        assert(VersionStr == "default");
+      Target->initFeatureMap(FeatureMap, getDiagnostics(), TargetCPU, 
Features);
     } else {
       std::vector<std::string> Features;
       StringRef VersionStr = TC->getFeatureStr(GD.getMultiVersionIndex());

diff  --git a/clang/lib/Basic/Targets/PPC.cpp b/clang/lib/Basic/Targets/PPC.cpp
index a37a68ad91724..30ea714fbb6f8 100644
--- a/clang/lib/Basic/Targets/PPC.cpp
+++ b/clang/lib/Basic/Targets/PPC.cpp
@@ -678,6 +678,57 @@ void 
PPCTargetInfo::setFeatureEnabled(llvm::StringMap<bool> &Features,
   }
 }
 
+ParsedTargetAttr PPCTargetInfo::parseTargetAttr(StringRef Features) const {
+  ParsedTargetAttr Ret;
+  if (Features == "default")
+    return Ret;
+  SmallVector<StringRef, 1> AttrFeatures;
+  Features.split(AttrFeatures, ",");
+
+  // Grab the various features and prepend a "+" to turn on the feature to
+  // the backend and add them to our existing set of features.
+  for (auto &Feature : AttrFeatures) {
+    // Go ahead and trim whitespace rather than either erroring or
+    // accepting it weirdly.
+    Feature = Feature.trim();
+
+    if (Feature.starts_with("cpu=")) {
+      if (!Ret.CPU.empty())
+        Ret.Duplicate = "cpu=";
+      else
+        Ret.CPU = Feature.split("=").second.trim();
+    } else if (Feature.starts_with("tune=")) {
+      if (!Ret.Tune.empty())
+        Ret.Duplicate = "tune=";
+      else
+        Ret.Tune = Feature.split("=").second.trim();
+    } else if (Feature.starts_with("no-"))
+      Ret.Features.push_back("-" + Feature.split("-").second.str());
+    else
+      Ret.Features.push_back("+" + Feature.str());
+  }
+  return Ret;
+}
+
+llvm::APInt PPCTargetInfo::getFMVPriority(ArrayRef<StringRef> Features) const {
+  if (Features.empty())
+    return llvm::APInt(32, 0);
+  assert(Features.size() == 1 && "one feature/cpu per clone on PowerPC");
+  ParsedTargetAttr ParsedAttr = parseTargetAttr(Features[0]);
+  if (!ParsedAttr.CPU.empty()) {
+    int Priority = llvm::StringSwitch<int>(ParsedAttr.CPU)
+                       .Case("pwr7", 1)
+                       .Case("pwr8", 2)
+                       .Case("pwr9", 3)
+                       .Case("pwr10", 4)
+                       .Case("pwr11", 5)
+                       .Default(0);
+    return llvm::APInt(32, Priority);
+  }
+  assert(false && "unimplemented");
+  return llvm::APInt(32, 0);
+}
+
 // Make sure that registers are added in the correct array index which should 
be
 // the DWARF number for PPC registers.
 const char *const PPCTargetInfo::GCCRegNames[] = {

diff  --git a/clang/lib/Basic/Targets/PPC.h b/clang/lib/Basic/Targets/PPC.h
index 664c9e15d8d18..6f90ff1f5d57c 100644
--- a/clang/lib/Basic/Targets/PPC.h
+++ b/clang/lib/Basic/Targets/PPC.h
@@ -199,6 +199,10 @@ class LLVM_LIBRARY_VISIBILITY PPCTargetInfo : public 
TargetInfo {
 
   bool supportsTargetAttributeTune() const override { return true; }
 
+  ParsedTargetAttr parseTargetAttr(StringRef Str) const override;
+
+  llvm::APInt getFMVPriority(ArrayRef<StringRef> Features) const override;
+
   ArrayRef<const char *> getGCCRegNames() const override;
 
   ArrayRef<TargetInfo::GCCRegAlias> getGCCRegAliases() const override;

diff  --git a/clang/lib/CodeGen/CodeGenFunction.cpp 
b/clang/lib/CodeGen/CodeGenFunction.cpp
index fb0d5e450f9d6..81b293fe5fe89 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -45,6 +45,7 @@
 #include "llvm/IR/Instruction.h"
 #include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Intrinsics.h"
+#include "llvm/IR/IntrinsicsPowerPC.h"
 #include "llvm/IR/MDBuilder.h"
 #include "llvm/Support/CRC.h"
 #include "llvm/Support/SipHash.h"
@@ -3075,10 +3076,84 @@ void CodeGenFunction::EmitMultiVersionResolver(
   case llvm::Triple::riscv64be:
     EmitRISCVMultiVersionResolver(Resolver, Options);
     return;
-
+  case llvm::Triple::ppc:
+  case llvm::Triple::ppc64:
+    if (getContext().getTargetInfo().getTriple().isOSAIX()) {
+      EmitPPCAIXMultiVersionResolver(Resolver, Options);
+      return;
+    }
+    [[fallthrough]];
   default:
-    assert(false && "Only implemented for x86, AArch64 and RISC-V targets");
+    assert(false &&
+           "Only implemented for x86, AArch64, RISC-V, and PowerPC AIX");
+  }
+}
+
+/**
+ * define internal ptr @foo.resolver() {
+ * entry:
+ *   %is_version_1 = __builtin_cpu_supports(version_1)
+ *   br i1 %1, label %if.version_1, label %if.else_2
+ *
+ * if.version_1:
+ *   ret ptr @foo.version_1
+ *
+ * if.else_2:
+ *   %is_version_2 = __builtin_cpu_supports(version_2)
+ * ...
+ * if.else:                                          ; preds = %entry
+ *   ret ptr @foo.default
+ * }
+ */
+void CodeGenFunction::EmitPPCAIXMultiVersionResolver(
+    llvm::Function *Resolver, ArrayRef<FMVResolverOption> Options) {
+
+  // entry:
+  llvm::BasicBlock *CurBlock = createBasicBlock("entry", Resolver);
+
+  SmallVector<std::pair<llvm::Value *, llvm::BasicBlock *>, 3> PhiArgs;
+  for (const FMVResolverOption &RO : Options) {
+    Builder.SetInsertPoint(CurBlock);
+    // The 'default' or 'generic' case.
+    if (!RO.Architecture && RO.Features.empty()) {
+      // if.else:
+      //   ret ptr @foo.default
+      assert(&RO == Options.end() - 1 &&
+             "Default or Generic case must be last");
+      Builder.CreateRet(RO.Function);
+      return;
+    }
+    // if.else_n:
+    //   %is_version_n = __builtin_cpu_supports(version_n)
+    //   br i1 %is_version_n, label %if.version_n, label %if.else_n+1
+    //
+    // if.version_n:
+    //   ret ptr @foo_version_n
+    assert(RO.Features.size() == 1 &&
+           "for now one feature requirement per version");
+
+    assert(RO.Features[0].starts_with("cpu="));
+    StringRef CPU = RO.Features[0].split("=").second.trim();
+    StringRef Feature = llvm::StringSwitch<StringRef>(CPU)
+                            .Case("pwr7", "arch_2_06")
+                            .Case("pwr8", "arch_2_07")
+                            .Case("pwr9", "arch_3_00")
+                            .Case("pwr10", "arch_3_1")
+                            .Case("pwr11", "arch_3_1")
+                            .Default("error");
+
+    llvm::Value *Condition = EmitPPCBuiltinCpu(
+        Builtin::BI__builtin_cpu_supports, Builder.getInt1Ty(), Feature);
+
+    llvm::BasicBlock *ThenBlock = createBasicBlock("if.version", Resolver);
+    CurBlock = createBasicBlock("if.else", Resolver);
+    Builder.CreateCondBr(Condition, ThenBlock, CurBlock);
+
+    Builder.SetInsertPoint(ThenBlock);
+    Builder.CreateRet(RO.Function);
   }
+
+  llvm_unreachable("Default case missing");
 }
 
 void CodeGenFunction::EmitRISCVMultiVersionResolver(

diff  --git a/clang/lib/CodeGen/CodeGenFunction.h 
b/clang/lib/CodeGen/CodeGenFunction.h
index 9771b89b55aae..85c058ba237ee 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -4912,6 +4912,8 @@ class CodeGenFunction : public CodeGenTypeCache {
 
   llvm::Value *BuildVector(ArrayRef<llvm::Value *> Ops);
   llvm::Value *EmitX86BuiltinExpr(unsigned BuiltinID, const CallExpr *E);
+  llvm::Value *EmitPPCBuiltinCpu(unsigned BuiltinID, llvm::Type *ReturnType,
+                                 StringRef CPUStr);
   llvm::Value *EmitPPCBuiltinExpr(unsigned BuiltinID, const CallExpr *E);
   llvm::Value *EmitAMDGPUBuiltinExpr(unsigned BuiltinID, const CallExpr *E);
   llvm::Value *EmitHLSLBuiltinExpr(unsigned BuiltinID, const CallExpr *E,
@@ -5578,6 +5580,8 @@ class CodeGenFunction : public CodeGenTypeCache {
                                        ArrayRef<FMVResolverOption> Options);
   void EmitRISCVMultiVersionResolver(llvm::Function *Resolver,
                                      ArrayRef<FMVResolverOption> Options);
+  void EmitPPCAIXMultiVersionResolver(llvm::Function *Resolver,
+                                      ArrayRef<FMVResolverOption> Options);
 
   Address EmitAddressOfPFPField(Address RecordPtr, const PFPField &Field);
   Address EmitAddressOfPFPField(Address RecordPtr, Address FieldPtr,

diff  --git a/clang/lib/CodeGen/CodeGenModule.cpp 
b/clang/lib/CodeGen/CodeGenModule.cpp
index 43af159305953..daaa846bf42bc 100644
--- a/clang/lib/CodeGen/CodeGenModule.cpp
+++ b/clang/lib/CodeGen/CodeGenModule.cpp
@@ -3060,11 +3060,13 @@ bool 
CodeGenModule::GetCPUAndFeaturesAttributes(GlobalDecl GD,
 
     // Now add the target-cpu and target-features to the function.
     // 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) {
-      ParsedTargetAttr ParsedAttr =
-          Target.parseTargetAttr(TD->getFeaturesStr());
+    // get and parse the target/target_clones attribute so we can
+    // get the cpu for the function.
+    StringRef FeatureStr = TD ? TD->getFeaturesStr() : StringRef();
+    if (TC && (getTriple().isOSAIX() || getTriple().isX86()))
+      FeatureStr = TC->getFeatureStr(GD.getMultiVersionIndex());
+    if (!FeatureStr.empty()) {
+      ParsedTargetAttr ParsedAttr = Target.parseTargetAttr(FeatureStr);
       if (!ParsedAttr.CPU.empty() &&
           getTarget().isValidCPUName(ParsedAttr.CPU)) {
         TargetCPU = ParsedAttr.CPU;
@@ -4769,7 +4771,7 @@ getFMVPriority(const TargetInfo &TI,
 static llvm::GlobalValue::LinkageTypes
 getMultiversionLinkage(CodeGenModule &CGM, GlobalDecl GD) {
   const FunctionDecl *FD = cast<FunctionDecl>(GD.getDecl());
-  if (FD->getFormalLinkage() == Linkage::Internal)
+  if (FD->getFormalLinkage() == Linkage::Internal || CGM.getTriple().isOSAIX())
     return llvm::GlobalValue::InternalLinkage;
   return llvm::GlobalValue::WeakODRLinkage;
 }
@@ -4803,7 +4805,7 @@ void CodeGenModule::emitMultiVersionFunctions() {
     // For AArch64, a resolver is only emitted if a function marked with
     // target_version("default")) or target_clones("default") is defined
     // in this TU. For other architectures it is always emitted.
-    bool ShouldEmitResolver = !getTarget().getTriple().isAArch64();
+    bool ShouldEmitResolver = !getTriple().isAArch64();
     SmallVector<CodeGenFunction::FMVResolverOption, 10> Options;
     llvm::DenseMap<llvm::Function *, const FunctionDecl *> DeclMap;
 
@@ -4855,7 +4857,8 @@ void CodeGenModule::emitMultiVersionFunctions() {
     if (auto *IFunc = dyn_cast<llvm::GlobalIFunc>(ResolverConstant)) {
       ResolverConstant = IFunc->getResolver();
       if (FD->isTargetClonesMultiVersion() &&
-          !getTarget().getTriple().isAArch64()) {
+          !getTarget().getTriple().isAArch64() &&
+          !getTarget().getTriple().isOSAIX()) {
         std::string MangledName = getMangledNameImpl(
             *this, GD, FD, /*OmitMultiVersionMangling=*/true);
         if (!GetGlobalValue(MangledName + ".ifunc")) {
@@ -5155,9 +5158,14 @@ llvm::Constant 
*CodeGenModule::GetOrCreateMultiVersionResolver(GlobalDecl GD) {
     llvm::Constant *Resolver = GetOrCreateLLVMFunction(
         MangledName + ".resolver", ResolverType, GlobalDecl{},
         /*ForVTable=*/false);
-    llvm::GlobalIFunc *GIF =
-        llvm::GlobalIFunc::create(DeclTy, AS, getMultiversionLinkage(*this, 
GD),
-                                  "", Resolver, &getModule());
+
+    // on AIX, the FMV is ignored on a declaration, and so we don't need the
+    // ifunc, which is only generated on FMV definitions, to be weak.
+    auto Linkage = getTriple().isOSAIX() ? getFunctionLinkage(GD)
+                                         : getMultiversionLinkage(*this, GD);
+
+    llvm::GlobalIFunc *GIF = llvm::GlobalIFunc::create(DeclTy, AS, Linkage, "",
+                                                       Resolver, &getModule());
     GIF->setName(ResolverName);
     SetCommonAttributes(FD, GIF);
     if (ResolverGV)
@@ -5176,6 +5184,7 @@ llvm::Constant 
*CodeGenModule::GetOrCreateMultiVersionResolver(GlobalDecl GD) {
 void CodeGenModule::setMultiVersionResolverAttributes(llvm::Function *Resolver,
                                                       GlobalDecl GD) {
   const NamedDecl *D = dyn_cast_or_null<NamedDecl>(GD.getDecl());
+
   Resolver->setLinkage(getMultiversionLinkage(*this, GD));
 
   // Function body has to be emitted before calling setGlobalVisibility
@@ -5255,6 +5264,15 @@ llvm::Constant *CodeGenModule::GetOrCreateLLVMFunction(
           AddDeferredMultiVersionResolverToEmit(GD);
           NameWithoutMultiVersionMangling = getMangledNameImpl(
               *this, GD, FD, /*OmitMultiVersionMangling=*/true);
+        }
+        // On AIX, a declared (but not defined) FMV shall be treated like a
+        // regular non-FMV function. If a definition is later seen, then
+        // GetOrCreateMultiVersionResolver will get called (when processing 
said
+        // definition) which will replace the IR declaration we're creating 
here
+        // with the FMV ifunc (see replaceDeclarationWith).
+        else if (getTriple().isOSAIX() && !FD->isDefined()) {
+          NameWithoutMultiVersionMangling = getMangledNameImpl(
+              *this, GD, FD, /*OmitMultiVersionMangling=*/true);
         } else
           return GetOrCreateMultiVersionResolver(GD);
       }
@@ -6713,6 +6731,9 @@ void 
CodeGenModule::EmitGlobalFunctionDefinition(GlobalDecl GD,
   auto *Fn = cast<llvm::Function>(GV);
   setFunctionLinkage(GD, Fn);
 
+  if (getTriple().isOSAIX() && D->isTargetClonesMultiVersion())
+    Fn->setLinkage(llvm::GlobalValue::InternalLinkage);
+
   // FIXME: this is redundant with part of setFunctionDefinitionAttributes
   setGVProperties(Fn, GD);
 

diff  --git a/clang/lib/CodeGen/TargetBuiltins/PPC.cpp 
b/clang/lib/CodeGen/TargetBuiltins/PPC.cpp
index 01926878085e0..721071308c251 100644
--- a/clang/lib/CodeGen/TargetBuiltins/PPC.cpp
+++ b/clang/lib/CodeGen/TargetBuiltins/PPC.cpp
@@ -70,31 +70,21 @@ static llvm::Value 
*emitPPCLoadReserveIntrinsic(CodeGenFunction &CGF,
   return CI;
 }
 
-Value *CodeGenFunction::EmitPPCBuiltinExpr(unsigned BuiltinID,
-                                           const CallExpr *E) {
-  // Do not emit the builtin arguments in the arguments of a function call,
-  // because the evaluation order of function arguments is not specified in 
C++.
-  // This is important when testing to ensure the arguments are emitted in the
-  // same order every time. Eg:
-  // Instead of:
-  //   return Builder.CreateFDiv(EmitScalarExpr(E->getArg(0)),
-  //                             EmitScalarExpr(E->getArg(1)), "swdiv");
-  // Use:
-  //   Value *Op0 = EmitScalarExpr(E->getArg(0));
-  //   Value *Op1 = EmitScalarExpr(E->getArg(1));
-  //   return Builder.CreateFDiv(Op0, Op1, "swdiv")
-
-  Intrinsic::ID ID = Intrinsic::not_intrinsic;
+Value *CodeGenFunction::EmitPPCBuiltinCpu(unsigned BuiltinID,
+                                          llvm::Type *ReturnType,
+                                          StringRef CPUStr) {
+  assert(BuiltinID == Builtin::BI__builtin_cpu_is ||
+         BuiltinID == Builtin::BI__builtin_cpu_supports);
 
 #include "llvm/TargetParser/PPCTargetParser.def"
   auto GenAIXPPCBuiltinCpuExpr = [&](unsigned SupportMethod, unsigned FieldIdx,
                                      unsigned Mask, CmpInst::Predicate CompOp,
                                      unsigned OpValue) -> Value * {
     if (SupportMethod == BUILTIN_PPC_FALSE)
-      return llvm::ConstantInt::getFalse(ConvertType(E->getType()));
+      return llvm::ConstantInt::getFalse(ReturnType);
 
     if (SupportMethod == BUILTIN_PPC_TRUE)
-      return llvm::ConstantInt::getTrue(ConvertType(E->getType()));
+      return llvm::ConstantInt::getTrue(ReturnType);
 
     assert(SupportMethod <= SYS_CALL && "Invalid value for SupportMethod.");
 
@@ -137,12 +127,7 @@ Value *CodeGenFunction::EmitPPCBuiltinExpr(unsigned 
BuiltinID,
         ConstantInt::get(IsValueType64Bit ? Int64Ty : Int32Ty, OpValue));
   };
 
-  switch (BuiltinID) {
-  default: return nullptr;
-
-  case Builtin::BI__builtin_cpu_is: {
-    const Expr *CPUExpr = E->getArg(0)->IgnoreParenCasts();
-    StringRef CPUStr = cast<clang::StringLiteral>(CPUExpr)->getString();
+  if (BuiltinID == Builtin::BI__builtin_cpu_is) {
     llvm::Triple Triple = getTarget().getTriple();
 
     typedef std::tuple<unsigned, unsigned, unsigned, unsigned> CPUInfo;
@@ -170,7 +155,7 @@ Value *CodeGenFunction::EmitPPCBuiltinExpr(unsigned 
BuiltinID,
            "Invalid CPU name. Missed by SemaChecking?");
 
     if (LinuxSupportMethod == BUILTIN_PPC_FALSE)
-      return llvm::ConstantInt::getFalse(ConvertType(E->getType()));
+      return llvm::ConstantInt::getFalse(ReturnType);
 
     Value *Op0 = llvm::ConstantInt::get(Int32Ty, PPC_FAWORD_CPUID);
     llvm::Function *F = CGM.getIntrinsic(Intrinsic::ppc_fixed_addr_ld);
@@ -178,47 +163,71 @@ Value *CodeGenFunction::EmitPPCBuiltinExpr(unsigned 
BuiltinID,
     return Builder.CreateICmpEQ(TheCall,
                                 llvm::ConstantInt::get(Int32Ty, LinuxIDValue));
   }
-  case Builtin::BI__builtin_cpu_supports: {
-    llvm::Triple Triple = getTarget().getTriple();
-    const Expr *CPUExpr = E->getArg(0)->IgnoreParenCasts();
-    StringRef CPUStr = cast<clang::StringLiteral>(CPUExpr)->getString();
-    if (Triple.isOSAIX()) {
-      typedef std::tuple<unsigned, unsigned, unsigned, CmpInst::Predicate,
-                         unsigned>
-          CPUSupportType;
-      auto [SupportMethod, FieldIdx, Mask, CompOp, Value] =
-          static_cast<CPUSupportType>(StringSwitch<CPUSupportType>(CPUStr)
+  // else BuiltinID == Builtin::BI__builtin_cpu_supports
+  llvm::Triple Triple = getTarget().getTriple();
+  if (Triple.isOSAIX()) {
+    typedef std::tuple<unsigned, unsigned, unsigned, CmpInst::Predicate,
+                       unsigned>
+        CPUSupportType;
+    auto [SupportMethod, FieldIdx, Mask, CompOp, Value] =
+        static_cast<CPUSupportType>(
+            StringSwitch<CPUSupportType>(CPUStr)
 #define PPC_AIX_FEATURE(NAME, DESC, SUPPORT_METHOD, INDEX, MASK, COMP_OP,      
\
                         VALUE)                                                 
\
   .Case(NAME, {SUPPORT_METHOD, INDEX, MASK, COMP_OP, VALUE})
 #include "llvm/TargetParser/PPCTargetParser.def"
-                                          .Default({BUILTIN_PPC_FALSE, 0, 0,
-                                                    CmpInst::Predicate(), 0}));
-      return GenAIXPPCBuiltinCpuExpr(SupportMethod, FieldIdx, Mask, CompOp,
-                                     Value);
-    }
+                .Default({BUILTIN_PPC_FALSE, 0, 0, CmpInst::Predicate(), 0}));
+    return GenAIXPPCBuiltinCpuExpr(SupportMethod, FieldIdx, Mask, CompOp,
+                                   Value);
+  }
 
-    assert(Triple.isOSLinux() &&
-           "__builtin_cpu_supports() is only supported for AIX and Linux.");
-    auto [FeatureWord, BitMask] =
-        StringSwitch<std::pair<unsigned, unsigned>>(CPUStr)
+  assert(Triple.isOSLinux() &&
+         "__builtin_cpu_supports() is only supported for AIX and Linux.");
+  auto [FeatureWord, BitMask] =
+      StringSwitch<std::pair<unsigned, unsigned>>(CPUStr)
 #define PPC_LNX_FEATURE(Name, Description, EnumName, Bitmask, FA_WORD)         
\
   .Case(Name, {FA_WORD, Bitmask})
 #include "llvm/TargetParser/PPCTargetParser.def"
-            .Default({0, 0});
-    if (!BitMask)
-      return Builder.getFalse();
-    Value *Op0 = llvm::ConstantInt::get(Int32Ty, FeatureWord);
-    llvm::Function *F = CGM.getIntrinsic(Intrinsic::ppc_fixed_addr_ld);
-    Value *TheCall = Builder.CreateCall(F, {Op0}, "cpu_supports");
-    Value *Mask =
-        Builder.CreateAnd(TheCall, llvm::ConstantInt::get(Int32Ty, BitMask));
-    return Builder.CreateICmpNE(Mask, llvm::Constant::getNullValue(Int32Ty));
+          .Default({0, 0});
+  if (!BitMask)
+    return Builder.getFalse();
+  Value *Op0 = llvm::ConstantInt::get(Int32Ty, FeatureWord);
+  llvm::Function *F = CGM.getIntrinsic(Intrinsic::ppc_fixed_addr_ld);
+  Value *TheCall = Builder.CreateCall(F, {Op0}, "cpu_supports");
+  Value *Mask =
+      Builder.CreateAnd(TheCall, llvm::ConstantInt::get(Int32Ty, BitMask));
+  return Builder.CreateICmpNE(Mask, llvm::Constant::getNullValue(Int32Ty));
 #undef PPC_FAWORD_HWCAP
 #undef PPC_FAWORD_HWCAP2
 #undef PPC_FAWORD_CPUID
-  }
+}
+
+Value *CodeGenFunction::EmitPPCBuiltinExpr(unsigned BuiltinID,
+                                           const CallExpr *E) {
+  // Do not emit the builtin arguments in the arguments of a function call,
+  // because the evaluation order of function arguments is not specified in 
C++.
+  // This is important when testing to ensure the arguments are emitted in the
+  // same order every time. Eg:
+  // Instead of:
+  //   return Builder.CreateFDiv(EmitScalarExpr(E->getArg(0)),
+  //                             EmitScalarExpr(E->getArg(1)), "swdiv");
+  // Use:
+  //   Value *Op0 = EmitScalarExpr(E->getArg(0));
+  //   Value *Op1 = EmitScalarExpr(E->getArg(1));
+  //   return Builder.CreateFDiv(Op0, Op1, "swdiv")
 
+  Intrinsic::ID ID = Intrinsic::not_intrinsic;
+
+  switch (BuiltinID) {
+  default:
+    return nullptr;
+
+  case Builtin::BI__builtin_cpu_is:
+  case Builtin::BI__builtin_cpu_supports: {
+    const Expr *CPUExpr = E->getArg(0)->IgnoreParenCasts();
+    StringRef CPUStr = cast<clang::StringLiteral>(CPUExpr)->getString();
+    return EmitPPCBuiltinCpu(BuiltinID, ConvertType(E->getType()), CPUStr);
+  }
   // __builtin_ppc_get_timebase is GCC 4.8+'s PowerPC-specific name for what we
   // call __builtin_readcyclecounter.
   case PPC::BI__builtin_ppc_get_timebase:

diff  --git a/clang/lib/CodeGen/Targets/PPC.cpp 
b/clang/lib/CodeGen/Targets/PPC.cpp
index 35e7655646ade..ab069bfbd1b51 100644
--- a/clang/lib/CodeGen/Targets/PPC.cpp
+++ b/clang/lib/CodeGen/Targets/PPC.cpp
@@ -128,8 +128,38 @@ class AIXABIInfo : public ABIInfo {
 
   RValue EmitVAArg(CodeGenFunction &CGF, Address VAListAddr, QualType Ty,
                    AggValueSlot Slot) const override;
+
+  using ABIInfo::appendAttributeMangling;
+  void appendAttributeMangling(TargetClonesAttr *Attr, unsigned Index,
+                               raw_ostream &Out) const override;
+  void appendAttributeMangling(StringRef AttrStr,
+                               raw_ostream &Out) const override;
 };
 
+void AIXABIInfo::appendAttributeMangling(TargetClonesAttr *Attr, unsigned 
Index,
+                                         raw_ostream &Out) const {
+  appendAttributeMangling(Attr->getFeatureStr(Index), Out);
+}
+
+void AIXABIInfo::appendAttributeMangling(StringRef AttrStr,
+                                         raw_ostream &Out) const {
+  if (AttrStr == "default") {
+    Out << ".default";
+    return;
+  }
+
+  const TargetInfo &TI = CGT.getTarget();
+  ParsedTargetAttr Info = TI.parseTargetAttr(AttrStr);
+
+  if (!Info.CPU.empty()) {
+    assert(Info.Features.empty() && "cannot have both a CPU and a feature");
+    Out << ".cpu_" << Info.CPU;
+    return;
+  }
+
+  assert(0 && "specifying target features on an FMV is unsupported on AIX");
+}
+
 class AIXTargetCodeGenInfo : public TargetCodeGenInfo {
   const bool Is64Bit;
 

diff  --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index 8da694d09e4b4..8c2577e78b231 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -55,6 +55,7 @@
 #include "clang/Sema/SemaObjC.h"
 #include "clang/Sema/SemaOpenCL.h"
 #include "clang/Sema/SemaOpenMP.h"
+#include "clang/Sema/SemaPPC.h"
 #include "clang/Sema/SemaRISCV.h"
 #include "clang/Sema/SemaSYCL.h"
 #include "clang/Sema/SemaSwift.h"
@@ -3631,6 +3632,10 @@ static void handleTargetClonesAttr(Sema &S, Decl *D, 
const ParsedAttr &AL) {
     if (S.X86().checkTargetClonesAttr(Params, Locations, NewParams,
                                       AL.getLoc()))
       return;
+  } else if (S.Context.getTargetInfo().getTriple().isOSAIX()) {
+    if (S.PPC().checkTargetClonesAttr(Params, Locations, NewParams,
+                                      AL.getLoc()))
+      return;
   }
   Params.clear();
   for (auto &SmallStr : NewParams)

diff  --git a/clang/lib/Sema/SemaPPC.cpp b/clang/lib/Sema/SemaPPC.cpp
index f48c975ff1106..57c880c81ac9e 100644
--- a/clang/lib/Sema/SemaPPC.cpp
+++ b/clang/lib/Sema/SemaPPC.cpp
@@ -22,6 +22,7 @@
 #include "clang/Basic/TargetInfo.h"
 #include "clang/Sema/Sema.h"
 #include "llvm/ADT/APSInt.h"
+#include "llvm/TargetParser/PPCTargetParser.h"
 
 namespace clang {
 
@@ -573,4 +574,72 @@ bool SemaPPC::BuiltinVSX(CallExpr *TheCall) {
   return false;
 }
 
+bool SemaPPC::checkTargetClonesAttr(const SmallVectorImpl<StringRef> &Params,
+                                    const SmallVectorImpl<SourceLocation> 
&Locs,
+                                    SmallVectorImpl<SmallString<64>> 
&NewParams,
+                                    SourceLocation AttrLoc) {
+  using namespace DiagAttrParams;
+
+  assert(Params.size() == Locs.size() &&
+         "Mismatch between number of string parameters and locations");
+
+  auto &TargetInfo = getASTContext().getTargetInfo();
+  bool HasDefault = false;
+  bool HasComma = false;
+  for (unsigned I = 0, E = Params.size(); I < E; ++I) {
+    const StringRef Param = Params[I].trim();
+    const SourceLocation &Loc = Locs[I];
+
+    if (Param.empty() || Param.ends_with(','))
+      return Diag(Loc, diag::warn_unsupported_target_attribute)
+             << Unsupported << None << "" << TargetClones;
+
+    if (Param.contains(','))
+      HasComma = true;
+
+    StringRef LHS;
+    StringRef RHS = Param;
+    do {
+      std::tie(LHS, RHS) = RHS.split(',');
+      LHS = LHS.trim();
+      const SourceLocation &CurLoc =
+          Loc.getLocWithOffset(LHS.data() - Param.data());
+
+      if (LHS.starts_with("cpu=")) {
+        StringRef CPUStr = LHS.drop_front(sizeof("cpu=") - 1);
+        if (!TargetInfo.isValidCPUName(CPUStr))
+          return Diag(CurLoc, diag::warn_unsupported_target_attribute)
+                 << Unknown << CPU << CPUStr << TargetClones;
+        else if (!TargetInfo.validateCpuIs(CPUStr))
+          return Diag(CurLoc, diag::warn_unsupported_target_attribute)
+                 << Unsupported << CPU << CPUStr << TargetClones;
+      } else if (LHS == "default") {
+        HasDefault = true;
+      } else {
+        // it's a feature string, but not supported yet.
+        return Diag(CurLoc, diag::warn_unsupported_target_attribute)
+               << Unsupported << None << LHS << TargetClones;
+      }
+      SmallString<64> CPU;
+      if (LHS.starts_with("cpu=")) {
+        CPU.append("cpu=");
+        CPU.append(
+            llvm::PPC::normalizeCPUName(LHS.drop_front(sizeof("cpu=") - 1)));
+        LHS = CPU.str();
+      }
+      if (llvm::is_contained(NewParams, LHS)) {
+        Diag(CurLoc, diag::warn_target_clone_duplicate_options);
+        continue;
+      }
+      NewParams.push_back(LHS);
+    } while (!RHS.empty());
+  }
+  if (HasComma && Params.size() > 1)
+    Diag(Locs[0], diag::warn_target_clone_mixed_values);
+
+  if (!HasDefault)
+    return Diag(AttrLoc, diag::err_target_clone_must_have_default);
+
+  return false;
+}
 } // namespace clang

diff  --git a/clang/test/CodeGen/PowerPC/attr-target-clones.c 
b/clang/test/CodeGen/PowerPC/attr-target-clones.c
new file mode 100644
index 0000000000000..c8cc9f2204b65
--- /dev/null
+++ b/clang/test/CodeGen/PowerPC/attr-target-clones.c
@@ -0,0 +1,142 @@
+// RUN: %clang_cc1 -triple powerpc-ibm-aix-xcoff -target-cpu pwr7 -emit-llvm 
%s -o - | FileCheck %s
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix-xcoff -target-cpu pwr7 -emit-llvm 
%s -o - | FileCheck %s
+
+// CHECK: @internal = internal ifunc i32 (), ptr @internal.resolver
+// CHECK: @foo = ifunc i32 (), ptr @foo.resolver
+// CHECK: @foo_dupes = ifunc void (), ptr @foo_dupes.resolver
+// CHECK: @unused = ifunc void (), ptr @unused.resolver
+// CHECK: @foo_inline = linkonce ifunc i32 (), ptr @foo_inline.resolver
+// CHECK: @foo_ref_then_def = ifunc i32 (), ptr @foo_ref_then_def.resolver
+// CHECK: @foo_priority = ifunc i32 (i32), ptr @foo_priority.resolver
+// CHEECK: @isa_level = ifunc i32 (i32), ptr @isa_level.resolver
+
+
+static int __attribute__((target_clones("cpu=power10, default"))) 
internal(void) { return 0; }
+int use(void) { return internal(); }
+// CHECK: define internal ptr @internal.resolver() 
+
+// test all supported cpus
+int __attribute__((target_clones("cpu=power10, cpu=power11, cpu=pwr9, 
cpu=pwr7, cpu=power8, default"))) foo(void) { return 0; }
+// CHECK: define internal {{.*}}i32 @foo.cpu_pwr10() #[[#ATTR_P10:]]
+// CHECK: define internal {{.*}}i32 @foo.cpu_pwr11() #[[#ATTR_P11:]]
+// CHECK: define internal {{.*}}i32 @foo.cpu_pwr9() #[[#ATTR_P9:]]
+// CHECK: define internal {{.*}}i32 @foo.cpu_pwr7() #[[#ATTR_P7:]]
+// CHECK: define internal {{.*}}i32 @foo.cpu_pwr8() #[[#ATTR_P8:]]
+// CHECK: define internal {{.*}}i32 @foo.default() #[[#ATTR_P7:]]
+// CHECK: define internal ptr @foo.resolver()
+// CHECK: ret ptr @foo.cpu_pwr11
+// CHECK: ret ptr @foo.cpu_pwr10
+// CHECK: ret ptr @foo.cpu_pwr9
+// CHECK: ret ptr @foo.cpu_pwr8
+// CHECK: ret ptr @foo.cpu_pwr7
+// CHECK: ret ptr @foo.default
+
+__attribute__((target_clones("default,default ,cpu=pwr8"))) void 
foo_dupes(void) {}
+// CHECK: define internal void @foo_dupes.default() #[[#ATTR_P7]]
+// CHECK: define internal void @foo_dupes.cpu_pwr8() #[[#ATTR_P8:]]
+// CHECK: define internal ptr @foo_dupes.resolver()
+// CHECK: ret ptr @foo_dupes.cpu_pwr8
+// CHECK: ret ptr @foo_dupes.default
+
+void bar2(void) {
+  // CHECK: define {{.*}}void @bar2()
+  foo_dupes();
+  // CHECK: call void @foo_dupes()
+}
+
+int bar(void) {
+  // CHECK: define {{.*}}i32 @bar()
+  return foo();
+  // CHECK: call {{.*}}i32 @foo()
+}
+
+void __attribute__((target_clones("default, cpu=pwr9"))) unused(void) {}
+// CHECK: define internal void @unused.default() #[[#ATTR_P7]]
+// CHECK: define internal void @unused.cpu_pwr9() #[[#ATTR_P9:]]
+// CHECK: define internal ptr @unused.resolver()
+// CHECK: ret ptr @unused.cpu_pwr9
+// CHECK: ret ptr @unused.default
+
+int __attribute__((target_clones("cpu=power10, default"))) inherited(void);
+int inherited(void) { return 0; }
+// CHECK: define internal {{.*}}i32 @inherited.cpu_pwr10() #[[#ATTR_P10]]
+// CHECK: define internal {{.*}}i32 @inherited.default() #[[#ATTR_P7]]
+// CHECK: define internal ptr @inherited.resolver()
+// CHECK: ret ptr @inherited.cpu_pwr10
+// CHECK: ret ptr @inherited.default
+
+
+int test_inherited(void) {
+  // CHECK: define {{.*}}i32 @test_inherited()
+  return inherited();
+  // CHECK: call {{.*}}i32 @inherited()
+}
+
+inline int __attribute__((target_clones("default,cpu=pwr8")))
+foo_inline(void) { return 0; }
+int __attribute__((target_clones("cpu=pwr7,default")))
+foo_ref_then_def(void);
+
+int bar3(void) {
+  // CHECK: define {{.*}}i32 @bar3()
+  return foo_inline() + foo_ref_then_def();
+  // CHECK: call {{.*}}i32 @foo_inline()
+  // CHECK: call {{.*}}i32 @foo_ref_then_def()
+}
+
+// CHECK: define internal ptr @foo_inline.resolver()
+// CHECK: ret ptr @foo_inline.cpu_pwr8
+// CHECK: ret ptr @foo_inline.default
+
+int __attribute__((target_clones("cpu=pwr7,default")))
+foo_ref_then_def(void){ return 0; }
+// CHECK: define internal ptr @foo_ref_then_def.resolver()
+// CHECK: ret ptr @foo_ref_then_def.cpu_pwr7
+// CHECK: ret ptr @foo_ref_then_def.default
+
+int __attribute__((target_clones("default", "cpu=pwr8")))
+foo_unused_no_defn(void);
+// CHECK-NOT: foo_unused_no_defn
+
+int __attribute__((target_clones("default", "cpu=pwr9")))
+foo_used_no_defn(void);
+
+int test_foo_used_no_defn(void) {
+  // CHECK: define {{.*}}i32 @test_foo_used_no_defn()
+  return foo_used_no_defn();
+  // CHECK: call {{.*}}i32 @foo_used_no_defn()
+}
+// CHECK: declare {{.*}}i32 @foo_used_no_defn()
+
+// Test that the CPU conditions are checked from the most to the least
+// restrictive (highest to lowest CPU). Also test the codegen for the
+// conditions
+int __attribute__((target_clones("cpu=pwr10", "cpu=pwr7", "cpu=pwr9", 
"default", "cpu=pwr8")))
+foo_priority(int x) { return x & (x - 1); }
+// CHECK: define internal ptr @foo_priority.resolver()
+// CHECK-NEXT: entry
+//   if (__builtin_cpu_supports("arch_3_1")) return &foo_priority.cpu_pwr10;
+// CHECK-NEXT: %[[#L1:]] = load i32, ptr getelementptr inbounds nuw (i8, ptr 
@_system_configuration, {{i32|i64}} 4)
+// CHECK-NEXT: icmp uge i32 %[[#L1]], 262144
+// CHECK: ret ptr @foo_priority.cpu_pwr10
+//   if (__builtin_cpu_supports("arch_3_00")) return &foo_priority.cpu_pwr9;
+// CHECK: %[[#L2:]] = load i32, ptr getelementptr inbounds nuw (i8, ptr 
@_system_configuration, {{i32|i64}} 4)
+// CHECK-NEXT: icmp uge i32 %[[#L2]], 131072
+// CHECK: ret ptr @foo_priority.cpu_pwr9
+//   if (__builtin_cpu_supports("arch_2_07")) return &foo_priority.cpu_pwr8;
+// CHECK: %[[#L3:]] = load i32, ptr getelementptr inbounds nuw (i8, ptr 
@_system_configuration, {{i32|i64}} 4)
+// CHECK-NEXT: icmp uge i32 %[[#L3]], 65536
+// CHECK: ret ptr @foo_priority.cpu_pwr8
+//   if (__builtin_cpu_supports("arch_2_06")) return &foo_priority.cpu_pwr8;
+// CHECK: %[[#L4:]] = load i32, ptr getelementptr inbounds nuw (i8, ptr 
@_system_configuration, {{i32|i64}} 4)
+// CHECK-NEXT: icmp uge i32 %[[#L4]], 32768
+// CHECK: ret ptr @foo_priority.cpu_pwr7
+// CHECK: ret ptr @foo_priority.default
+
+
+// CHECK: attributes #[[#ATTR_P7]] = {{.*}} "target-cpu"="pwr7"
+// CHECK: attributes #[[#ATTR_P10]] = {{.*}} "target-cpu"="pwr10"
+// CHECK: attributes #[[#ATTR_P11]] = {{.*}} "target-cpu"="pwr11"
+// CHECK: attributes #[[#ATTR_P9]] = {{.*}} "target-cpu"="pwr9"
+// CHECK: attributes #[[#ATTR_P8]] = {{.*}} "target-cpu"="pwr8"
+

diff  --git a/clang/test/Sema/PowerPC/attr-target-clones.c 
b/clang/test/Sema/PowerPC/attr-target-clones.c
new file mode 100644
index 0000000000000..96acc974320b0
--- /dev/null
+++ b/clang/test/Sema/PowerPC/attr-target-clones.c
@@ -0,0 +1,132 @@
+// RUN: %clang_cc1 -triple powerpc-ibm-aix-xcoff  -fsyntax-only -verify %s
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix-xcoff  -fsyntax-only -verify %s
+
+// expected-error@+1 {{'target_clones' multiversioning requires a default 
target}}
+void __attribute__((target_clones("cpu=pwr7")))
+no_default(void);
+
+// expected-error@+2 {{'target_clones' and 'target' attributes are not 
compatible}}
+// expected-note@+1 {{conflicting attribute is here}}
+void __attribute__((target("cpu=pwr7"), target_clones("cpu=pwr8")))
+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("default", "cpu=pwr8"), target("cpu=pwr7")))
+ignored_attr2(void);
+
+int __attribute__((target_clones("cpu=pwr9", "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("cpu=pwr7", "default")))
+redecl4(void) { return 1; }
+
+int __attribute__((target_clones("cpu=pwr7", "default"))) redecl7(void);
+// expected-error@+2 {{multiversioning attributes cannot be combined}}
+// expected-note@-2 {{previous declaration is here}}
+int __attribute__((target("cpu=pwr8"))) redecl7(void) { return 1; }
+
+int __attribute__((target("cpu=pwr9"))) redef2(void) { return 1; }
+// expected-error@+2 {{multiversioning attributes cannot be combined}}
+// expected-note@-2 {{previous declaration is here}}
+int __attribute__((target_clones("cpu=pwr7", "default"))) redef2(void) { 
return 1; }
+
+int __attribute__((target_clones("cpu=pwr9,default"))) redef3(void) { return 
1; }
+// expected-error@+2 {{redefinition of 'redef3'}}
+// expected-note@-2 {{previous definition is here}}
+int __attribute__((target_clones("cpu=pwr9,default"))) redef3(void) { return 
1; }
+
+// Duplicates are allowed
+// expected-warning@+2 {{mixing 'target_clones' specifier mechanisms is 
permitted for GCC compatibility}}
+// expected-warning@+1 2 {{version list contains duplicate entries}}
+int __attribute__((target_clones("cpu=pwr9,cpu=power9", "cpu=power9, 
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, ,cpu=pwr7")))
+empty_target_4(void);
+
+// expected-warning@+1 {{unsupported '' in the 'target_clones' attribute 
string;}}
+void __attribute__((target_clones("default,cpu=pwr7", "")))
+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("cpu=pwr9,cpu=power9,default")))
+dupe_normal(void);
+
+// expected-error@+2 {{attribute 'target_clones' cannot appear more than once 
on a declaration}}
+// expected-note@+1 {{conflicting attribute is here}}
+void __attribute__((target_clones("cpu=pwr7,default"), 
target_clones("cpu=pwr8,default")))
+dupe_normal2(void);
+
+int mv_after_use(void);
+int useage(void) {
+  return mv_after_use();
+}
+// expected-error@+1 {{function declaration cannot become a multiversioned 
function after first usage}}
+int __attribute__((target_clones("cpu=pwr9", "default"))) mv_after_use(void) { 
return 1; }
+
+void bad_overload1(void) __attribute__((target_clones("cpu=pwr8", "default")));
+// expected-error@+2 {{conflicting types for 'bad_overload1'}}
+// expected-note@-2 {{previous declaration is here}}
+void bad_overload1(int p) {}
+
+void bad_overload2(int p) {}
+// expected-error@+2 {{conflicting types for 'bad_overload2'}}
+// expected-note@-2 {{previous definition is here}}
+void bad_overload2(void) __attribute__((target_clones("cpu=pwr8", "default")));
+
+void bad_overload3(void) __attribute__((target_clones("cpu=pwr8", "default")));
+// expected-error@+2 {{conflicting types for 'bad_overload3'}}
+// expected-note@-2 {{previous declaration is here}}
+void bad_overload3(int) __attribute__((target_clones("cpu=pwr8", "default")));
+
+
+void good_overload1(void) __attribute__((target_clones("cpu=pwr7", 
"cpu=power10", "default")));
+void __attribute__((__overloadable__)) good_overload1(int p) {}
+
+// expected-error@+1 {{attribute 'target_clones' multiversioning cannot be 
combined with attribute 'overloadable'}}
+void __attribute__((__overloadable__)) good_overload2(void) 
__attribute__((target_clones("cpu=pwr7", "default")));
+void good_overload2(int p) {}
+// expected-error@+1 {{attribute 'target_clones' multiversioning cannot be 
combined with attribute 'overloadable'}}
+void __attribute__((__overloadable__)) good_overload3(void) 
__attribute__((target_clones("cpu=pwr7", "default")));
+// expected-error@+1 {{attribute 'target_clones' multiversioning cannot be 
combined with attribute 'overloadable'}}
+void __attribute__((__overloadable__)) good_overload3(int) 
__attribute__((target_clones("cpu=pwr7", "default")));
+
+void good_overload4(void) __attribute__((target_clones("cpu=pwr7", 
"default")));
+// expected-error@+1 {{attribute 'target_clones' multiversioning cannot be 
combined with attribute 'overloadable'}}
+void __attribute__((__overloadable__)) good_overload4(int) 
__attribute__((target_clones("cpu=pwr7", "default")));
+
+// expected-error@+1 {{attribute 'target_clones' multiversioning cannot be 
combined with attribute 'overloadable'}}
+void __attribute__((__overloadable__)) good_overload5(void) 
__attribute__((target_clones("cpu=pwr7", "default")));
+void good_overload5(int) __attribute__((target_clones("cpu=pwr7", "default")));
+
+
+void good_isa_level(int) __attribute__((target_clones("default", "cpu=pwr7", 
"cpu=pwr8", "cpu=pwr9", "cpu=pwr10")));
+
+// expected-warning@+1 {{unknown CPU 'bad-cpu' in the 'target_clones' 
attribute string; 'target_clones' attribute ignored}}
+void bad_cpu(int) __attribute__((target_clones("default", "cpu=bad-cpu")));
+
+// expected-warning@+1 {{unsupported CPU 'pwr3' in the 'target_clones' 
attribute string; 'target_clones' attribute ignored}}
+void bad_cpu(int) __attribute__((target_clones("default", "cpu=pwr3")));
+
+// expected-error@+1 {{'target_clones' multiversioning requires a default 
target}}
+void __attribute__((target_clones()))
+gh173684_empty_attribute_args(void);
+
+// expected-error@+1 {{'target_clones' multiversioning requires a default 
target}}
+void __attribute__((target_clones))
+gh173684_empty_attribute_args_2(void);

diff  --git a/clang/test/Sema/attr-target.c b/clang/test/Sema/attr-target.c
index 65ece3c27d299..ddf6654632187 100644
--- a/clang/test/Sema/attr-target.c
+++ b/clang/test/Sema/attr-target.c
@@ -75,15 +75,13 @@ int __attribute__((target("tune=pwr8"))) baz(void) { return 
4; }
 //expected-warning@+1 {{unsupported 'fpmath=' in the 'target' attribute 
string; 'target' attribute ignored}}
 int __attribute__((target("fpmath=387"))) walrus(void) { return 4; }
 //expected-warning@+1 {{unknown CPU 'hiss' in the 'target' attribute string; 
'target' attribute ignored}}
-int __attribute__((target("float128,arch=hiss"))) meow(void) {  return 4; }
+int __attribute__((target("float128,cpu=hiss"))) meow(void) {  return 4; }
 // no warning, same as saying 'nothing'.
-int __attribute__((target("arch="))) turtle(void) { return 4; }
+int __attribute__((target("cpu="))) turtle(void) { return 4; }
 //expected-warning@+1 {{unknown CPU 'hiss' in the 'target' attribute string; 
'target' attribute ignored}}
-int __attribute__((target("arch=hiss,arch=woof"))) pine_tree(void) { return 4; 
}
-//expected-warning@+1 {{duplicate 'arch=' in the 'target' attribute string; 
'target' attribute ignored}}
-int __attribute__((target("arch=pwr9,arch=pwr10"))) oak_tree(void) { return 4; 
}
-//expected-warning@+1 {{unsupported 'branch-protection' in the 'target' 
attribute string; 'target' attribute ignored}}
-int __attribute__((target("branch-protection=none"))) birch_tree(void) { 
return 5; }
+int __attribute__((target("cpu=hiss,cpu=woof"))) pine_tree(void) { return 4; }
+//expected-warning@+1 {{duplicate 'cpu=' in the 'target' attribute string; 
'target' attribute ignored}}
+int __attribute__((target("cpu=pwr9,cpu=pwr10"))) oak_tree(void) { return 4; }
 //expected-warning@+1 {{unknown tune CPU 'hiss' in the 'target' attribute 
string; 'target' attribute ignored}}
 int __attribute__((target("tune=hiss,tune=woof"))) apple_tree(void) { return 
4; }
 


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to