[clang] [compiler-rt] [llvm] [AIX] Implement the ifunc attribute. (PR #153049)

Wael Yehia via llvm-commits llvm-commits at lists.llvm.org
Tue Oct 7 16:06:27 PDT 2025


https://github.com/w2yehia updated https://github.com/llvm/llvm-project/pull/153049

>From c562d407468236c0ee31cd32949e7f6a363a798f Mon Sep 17 00:00:00 2001
From: Wael Yehia <wyehia at ca.ibm.com>
Date: Tue, 9 Sep 2025 19:27:03 +0000
Subject: [PATCH 01/16] accept ifunc attribute on AIX

---
 clang/include/clang/Basic/TargetInfo.h        |   2 +
 llvm/include/llvm/CodeGen/AsmPrinter.h        |   2 +-
 .../CodeGen/TargetLoweringObjectFileImpl.h    |   2 +-
 .../llvm/Target/TargetLoweringObjectFile.h    |   2 +-
 llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp    |   2 -
 .../CodeGen/TargetLoweringObjectFileImpl.cpp  |  16 +-
 llvm/lib/Target/PowerPC/CMakeLists.txt        |   1 +
 llvm/lib/Target/PowerPC/PPC.h                 |   2 +
 llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp     | 227 +++++++++++++++++-
 .../Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp  | 123 ++++++++++
 llvm/lib/Target/PowerPC/PPCTargetMachine.cpp  |   4 +
 .../llvm/lib/Target/PowerPC/BUILD.gn          |   1 +
 12 files changed, 366 insertions(+), 18 deletions(-)
 create mode 100644 llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp

diff --git a/clang/include/clang/Basic/TargetInfo.h b/clang/include/clang/Basic/TargetInfo.h
index e5c5ada3b0858..d04df9664d44f 100644
--- a/clang/include/clang/Basic/TargetInfo.h
+++ b/clang/include/clang/Basic/TargetInfo.h
@@ -1547,6 +1547,8 @@ class TargetInfo : public TransferrableTargetInfo,
       return true;
     if (getTriple().getArch() == llvm::Triple::ArchType::avr)
       return true;
+    if (getTriple().isOSAIX())
+      return true;
     return getTriple().isOSBinFormatELF() &&
            ((getTriple().isOSLinux() && !getTriple().isMusl()) ||
             getTriple().isOSFreeBSD());
diff --git a/llvm/include/llvm/CodeGen/AsmPrinter.h b/llvm/include/llvm/CodeGen/AsmPrinter.h
index 71317619098ad..7ca68ee8003d6 100644
--- a/llvm/include/llvm/CodeGen/AsmPrinter.h
+++ b/llvm/include/llvm/CodeGen/AsmPrinter.h
@@ -984,7 +984,7 @@ class LLVM_ABI AsmPrinter : public MachineFunctionPass {
   virtual void emitModuleCommandLines(Module &M);
 
   GCMetadataPrinter *getOrCreateGCPrinter(GCStrategy &S);
-  void emitGlobalIFunc(Module &M, const GlobalIFunc &GI);
+  virtual void emitGlobalIFunc(Module &M, const GlobalIFunc &GI);
 
   /// This method decides whether the specified basic block requires a label.
   bool shouldEmitLabelForBasicBlock(const MachineBasicBlock &MBB) const;
diff --git a/llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h b/llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h
index fe450b3c1a3a2..fb04228205df0 100644
--- a/llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h
+++ b/llvm/include/llvm/CodeGen/TargetLoweringObjectFileImpl.h
@@ -289,7 +289,7 @@ class TargetLoweringObjectFileXCOFF : public TargetLoweringObjectFile {
   static XCOFF::StorageClass getStorageClassForGlobal(const GlobalValue *GV);
 
   MCSection *
-  getSectionForFunctionDescriptor(const Function *F,
+  getSectionForFunctionDescriptor(const GlobalObject *F,
                                   const TargetMachine &TM) const override;
   MCSection *getSectionForTOCEntry(const MCSymbol *Sym,
                                    const TargetMachine &TM) const override;
diff --git a/llvm/include/llvm/Target/TargetLoweringObjectFile.h b/llvm/include/llvm/Target/TargetLoweringObjectFile.h
index 4d6cbc5540131..1266cf615b903 100644
--- a/llvm/include/llvm/Target/TargetLoweringObjectFile.h
+++ b/llvm/include/llvm/Target/TargetLoweringObjectFile.h
@@ -269,7 +269,7 @@ class LLVM_ABI TargetLoweringObjectFile : public MCObjectFileInfo {
   /// On targets that use separate function descriptor symbols, return a section
   /// for the descriptor given its symbol. Use only with defined functions.
   virtual MCSection *
-  getSectionForFunctionDescriptor(const Function *F,
+  getSectionForFunctionDescriptor(const GlobalObject *F,
                                   const TargetMachine &TM) const {
     return nullptr;
   }
diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
index cd14a4f57f760..6a89b6403d651 100644
--- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
@@ -2426,8 +2426,6 @@ void AsmPrinter::emitGlobalAlias(const Module &M, const GlobalAlias &GA) {
 }
 
 void AsmPrinter::emitGlobalIFunc(Module &M, const GlobalIFunc &GI) {
-  assert(!TM.getTargetTriple().isOSBinFormatXCOFF() &&
-         "IFunc is not supported on AIX.");
 
   auto EmitLinkage = [&](MCSymbol *Sym) {
     if (GI.hasExternalLinkage() || !MAI->getWeakRefDirective())
diff --git a/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp b/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
index ae681b9aebdfb..eb62e573637bd 100644
--- a/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
+++ b/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
@@ -2391,7 +2391,8 @@ MCSymbol *
 TargetLoweringObjectFileXCOFF::getTargetSymbol(const GlobalValue *GV,
                                                const TargetMachine &TM) const {
   // We always use a qualname symbol for a GV that represents
-  // a declaration, a function descriptor, or a common symbol.
+  // a declaration, a function descriptor, or a common symbol. An IFunc is
+  // lowered as a function, so it has an entry point and a descriptor.
   // If a GV represents a GlobalVariable and -fdata-sections is enabled, we
   // also return a qualname so that a label symbol could be avoided.
   // It is inherently ambiguous when the GO represents the address of a
@@ -2410,6 +2411,11 @@ TargetLoweringObjectFileXCOFF::getTargetSymbol(const GlobalValue *GV,
                    SectionForGlobal(GVar, SectionKind::getData(), TM))
             ->getQualNameSymbol();
 
+    if (isa<GlobalIFunc>(GO))
+      return static_cast<const MCSectionXCOFF *>(
+                 getSectionForFunctionDescriptor(GO, TM))
+          ->getQualNameSymbol();
+
     SectionKind GOKind = getKindForGlobal(GO, TM);
     if (GOKind.isText())
       return static_cast<const MCSectionXCOFF *>(
@@ -2682,7 +2688,7 @@ TargetLoweringObjectFileXCOFF::getStorageClassForGlobal(const GlobalValue *GV) {
 
 MCSymbol *TargetLoweringObjectFileXCOFF::getFunctionEntryPointSymbol(
     const GlobalValue *Func, const TargetMachine &TM) const {
-  assert((isa<Function>(Func) ||
+  assert((isa<Function>(Func) || isa<GlobalIFunc>(Func) ||
           (isa<GlobalAlias>(Func) &&
            isa_and_nonnull<Function>(
                cast<GlobalAlias>(Func)->getAliaseeObject()))) &&
@@ -2699,7 +2705,7 @@ MCSymbol *TargetLoweringObjectFileXCOFF::getFunctionEntryPointSymbol(
   // undefined symbols gets treated as csect with XTY_ER property.
   if (((TM.getFunctionSections() && !Func->hasSection()) ||
        Func->isDeclarationForLinker()) &&
-      isa<Function>(Func)) {
+      (isa<Function>(Func) || isa<GlobalIFunc>(Func))) {
     return getContext()
         .getXCOFFSection(
             NameStr, SectionKind::getText(),
@@ -2713,7 +2719,9 @@ MCSymbol *TargetLoweringObjectFileXCOFF::getFunctionEntryPointSymbol(
 }
 
 MCSection *TargetLoweringObjectFileXCOFF::getSectionForFunctionDescriptor(
-    const Function *F, const TargetMachine &TM) const {
+    const GlobalObject *F, const TargetMachine &TM) const {
+  assert((isa<Function>(F) || isa<GlobalIFunc>(F)) &&
+         "F must be a function or ifunc object.");
   SmallString<128> NameStr;
   getNameWithPrefix(NameStr, F, TM);
   return getContext().getXCOFFSection(
diff --git a/llvm/lib/Target/PowerPC/CMakeLists.txt b/llvm/lib/Target/PowerPC/CMakeLists.txt
index 2182039e0eef8..6c2037c6a89de 100644
--- a/llvm/lib/Target/PowerPC/CMakeLists.txt
+++ b/llvm/lib/Target/PowerPC/CMakeLists.txt
@@ -41,6 +41,7 @@ add_llvm_target(PowerPCCodeGen
   PPCMachineScheduler.cpp
   PPCMacroFusion.cpp
   PPCMIPeephole.cpp
+  PPCPrepareIFuncsOnAIX.cpp
   PPCRegisterInfo.cpp
   PPCSelectionDAGInfo.cpp
   PPCSubtarget.cpp
diff --git a/llvm/lib/Target/PowerPC/PPC.h b/llvm/lib/Target/PowerPC/PPC.h
index a7cd5cde16b4f..88a54ac33f5a9 100644
--- a/llvm/lib/Target/PowerPC/PPC.h
+++ b/llvm/lib/Target/PowerPC/PPC.h
@@ -53,6 +53,7 @@ class ModulePass;
   FunctionPass *createPPCPreEmitPeepholePass();
   FunctionPass *createPPCExpandAtomicPseudoPass();
   FunctionPass *createPPCCTRLoopsPass();
+  ModulePass *createPPCPrepareIFuncsOnAIXPass();
   void LowerPPCMachineInstrToMCInst(const MachineInstr *MI, MCInst &OutMI,
                                     AsmPrinter &AP);
   bool LowerPPCMachineOperandToMCOperand(const MachineOperand &MO,
@@ -78,6 +79,7 @@ class ModulePass;
   void initializePPCExpandAtomicPseudoPass(PassRegistry &);
   void initializePPCCTRLoopsPass(PassRegistry &);
   void initializePPCDAGToDAGISelLegacyPass(PassRegistry &);
+  void initializePPCPrepareIFuncsOnAIXPass(PassRegistry &);
   void initializePPCLinuxAsmPrinterPass(PassRegistry &);
   void initializePPCAIXAsmPrinterPass(PassRegistry &);
 
diff --git a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
index 023fd147535ec..03a72fc1e8ef0 100644
--- a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
+++ b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
@@ -100,6 +100,13 @@ static cl::opt<bool> EnableSSPCanaryBitInTB(
     "aix-ssp-tb-bit", cl::init(false),
     cl::desc("Enable Passing SSP Canary info in Trackback on AIX"), cl::Hidden);
 
+static cl::list<std::string> IFuncLocal(
+    "ifunc-local",
+    llvm::cl::desc("a comma separated list of ifunc function names that are "
+                   "guaranteed to resolve to a module-local function. "
+                   "-ifunc-local=1 will apply to all ifuncs in the CU."),
+    cl::CommaSeparated, cl::Hidden);
+
 // Specialize DenseMapInfo to allow
 // std::pair<const MCSymbol *, PPCMCExpr::Specifier> in DenseMap.
 // This specialization is needed here because that type is used as keys in the
@@ -305,6 +312,8 @@ class PPCAIXAsmPrinter : public PPCAsmPrinter {
   void emitTTypeReference(const GlobalValue *GV, unsigned Encoding) override;
 
   void emitModuleCommandLines(Module &M) override;
+
+  void emitGlobalIFunc(Module &M, const GlobalIFunc &GI) override;
 };
 
 } // end anonymous namespace
@@ -768,6 +777,16 @@ static MCSymbol *getMCSymbolForTOCPseudoMO(const MachineOperand &MO,
   }
 }
 
+static PPCAsmPrinter::TOCEntryType
+getTOCEntryTypeForLinkage(GlobalValue::LinkageTypes Linkage) {
+  if (Linkage == GlobalValue::ExternalLinkage ||
+      Linkage == GlobalValue::AvailableExternallyLinkage ||
+      Linkage == GlobalValue::ExternalWeakLinkage)
+    return PPCAsmPrinter::TOCType_GlobalExternal;
+
+  return PPCAsmPrinter::TOCType_GlobalInternal;
+}
+
 static PPCAsmPrinter::TOCEntryType
 getTOCEntryTypeForMO(const MachineOperand &MO) {
   // Use the target flags to determine if this MO is Thread Local.
@@ -778,13 +797,7 @@ getTOCEntryTypeForMO(const MachineOperand &MO) {
   switch (MO.getType()) {
   case MachineOperand::MO_GlobalAddress: {
     const GlobalValue *GlobalV = MO.getGlobal();
-    GlobalValue::LinkageTypes Linkage = GlobalV->getLinkage();
-    if (Linkage == GlobalValue::ExternalLinkage ||
-        Linkage == GlobalValue::AvailableExternallyLinkage ||
-        Linkage == GlobalValue::ExternalWeakLinkage)
-      return PPCAsmPrinter::TOCType_GlobalExternal;
-
-    return PPCAsmPrinter::TOCType_GlobalInternal;
+    return getTOCEntryTypeForLinkage(GlobalV->getLinkage());
   }
   case MachineOperand::MO_ConstantPoolIndex:
     return PPCAsmPrinter::TOCType_ConstantPool;
@@ -2865,8 +2878,10 @@ void PPCAIXAsmPrinter::emitFunctionDescriptor() {
       static_cast<MCSymbolXCOFF *>(CurrentFnDescSym)->getRepresentedCsect());
 
   // Emit aliasing label for function descriptor csect.
-  for (const GlobalAlias *Alias : GOAliasMap[&MF->getFunction()])
-    OutStreamer->emitLabel(getSymbol(Alias));
+  if (MF) // TODO MF is unset when processing an ifunc, handle it better than
+          // this.
+    for (const GlobalAlias *Alias : GOAliasMap[&MF->getFunction()])
+      OutStreamer->emitLabel(getSymbol(Alias));
 
   // Emit function entry point address.
   OutStreamer->emitValue(MCSymbolRefExpr::create(CurrentFnSym, OutContext),
@@ -2884,6 +2899,12 @@ void PPCAIXAsmPrinter::emitFunctionDescriptor() {
 }
 
 void PPCAIXAsmPrinter::emitFunctionEntryLabel() {
+  if (!MF) { // TODO: MF is unset when processing an ifunc, handle it better.
+    if (!TM.getFunctionSections())
+      PPCAsmPrinter::emitFunctionEntryLabel();
+    return;
+  }
+
   // For functions without user defined section, it's not necessary to emit the
   // label when we have individual function in its own csect.
   if (!TM.getFunctionSections() || MF->getFunction().hasSection())
@@ -3363,6 +3384,194 @@ void PPCAIXAsmPrinter::emitModuleCommandLines(Module &M) {
   OutStreamer->emitXCOFFCInfoSym(".GCC.command.line", RSOS.str());
 }
 
+static bool TOCRestoreNeeded(const GlobalIFunc &GI) {
+  auto IsLocalFunc = [&](const Value *V) {
+    if (!isa<Function>(V))
+      return false;
+    auto *F = cast<Function>(V);
+
+    // static functions are local
+    if (F->getLinkage() == GlobalValue::InternalLinkage)
+      return true;
+    // for now, declarations we treat as potentially non-local
+    if (F->isDeclarationForLinker())
+      return false;
+    // hidden visibility definitions cannot be preempted, so treat as local.
+    if (F->getVisibility() == GlobalValue::HiddenVisibility)
+      return true;
+
+    return false;
+  };
+
+  if (!IFuncLocal.empty()) {
+    ArrayRef<std::string> List = IFuncLocal;
+    // special case of -ifunc-local=1
+    if (List.size() == 1 && List[0].compare("1") == 0)
+      return false;
+    StringRef IFuncName = GI.getName();
+    if (any_of(List, [&](const std::string &Element) {
+          return Element.size() == IFuncName.size() &&
+                 Element.compare(IFuncName.data()) == 0;
+        }))
+      return false;
+  }
+
+  // if one of the return values of the resolver function is not a
+  // local function, then we have to conservatively do a TOC save/restore.
+  auto *Resolver = GI.getResolverFunction();
+  for (auto &BB : *Resolver) {
+    if (auto *Ret = dyn_cast<ReturnInst>(BB.getTerminator())) {
+      Value *RV = Ret->getReturnValue();
+      assert(RV);
+      // return &foo_p9;
+      if (auto *F = dyn_cast<Function>(RV)) {
+        if (!IsLocalFunc(F))
+          return true;
+      } else if (auto *I = dyn_cast<Instruction>(RV)) {
+        // return isP9 ? foo_p9 : foo_default;
+        if (auto *SI = dyn_cast<SelectInst>(I)) {
+          if (!IsLocalFunc(SI->getTrueValue()) ||
+              !IsLocalFunc(SI->getFalseValue()))
+            return true;
+        } else if (auto *PN = dyn_cast<PHINode>(I)) {
+          for (unsigned i = 1, e = PN->getNumIncomingValues(); i != e; ++i)
+            if (!IsLocalFunc(PN->getIncomingValue(i)))
+              return true;
+        } else
+          return true;
+      } else
+        return true;
+    }
+  }
+  // all return values where local functions, so no TOC save/restore needed.
+  return false;
+}
+/*
+ *   .csect .foo[PR],5
+ *   .globl  foo[DS]
+ *   .globl  .foo[PR]
+ *   .lglobl ifunc_sec.foo[RW]
+ *   .align  4
+ *   .csect foo[DS],2
+ *   .vbyte  4, .foo[PR]
+ *   .vbyte  4, TOC[TC0]
+ *   .vbyte  4, 0
+ *   .csect .foo[PR],5
+ *   .ref ifunc_sec.foo[RW]
+ *   lwz 12, L..C3(2)
+ *   lwz 12, 0(12)
+ *   mtctr 12
+ *   bctr
+ *                                              # -- End function
+ */
+void PPCAIXAsmPrinter::emitGlobalIFunc(Module &M, const GlobalIFunc &GI) {
+  // Set the Subtarget to that of the resolver.
+  const TargetSubtargetInfo *STI =
+      TM.getSubtargetImpl(*GI.getResolverFunction());
+  bool IsPPC64 = static_cast<const PPCSubtarget *>(STI)->isPPC64();
+
+  // Create syms and sections that are part of the ifunc implementation:
+  //  - Function descriptor symbol foo[RW]
+  //  - Function entry symbol .foo[PR]
+  //  - ifunc_sec variable (that registers the ifunc's descriptor and resolver)
+  MCSectionXCOFF *FnDescSec = static_cast<MCSectionXCOFF *>(
+      getObjFileLowering().getSectionForFunctionDescriptor(&GI, TM));
+  FnDescSec->setAlignment(Align(IsPPC64 ? 8 : 4));
+
+  CurrentFnDescSym = FnDescSec->getQualNameSymbol();
+
+  CurrentFnSym = getObjFileLowering().getFunctionEntryPointSymbol(&GI, TM);
+
+  MCSymbol *IFuncUpdateSym = nullptr;
+  if (MDNode *MD = GI.getMetadata(LLVMContext::MD_associated)) {
+    const ValueAsMetadata *VAM = cast<ValueAsMetadata>(MD->getOperand(0).get());
+    const GlobalVariable *IFuncUpdateGV = cast<GlobalVariable>(VAM->getValue());
+    IFuncUpdateSym = getSymbol(IFuncUpdateGV);
+  }
+
+  // Start codegen:
+  if (TM.getFunctionSections())
+    OutStreamer->switchSection(
+        static_cast<MCSymbolXCOFF *>(CurrentFnSym)->getRepresentedCsect());
+  else
+    OutStreamer->switchSection(getObjFileLowering().getTextSection());
+
+  // generate linkage for foo and .foo
+  emitLinkage(&GI, CurrentFnDescSym);
+  emitLinkage(&GI, CurrentFnSym);
+
+  // declare the "ifunc_sec.foo[RW]" as an internal symbol
+  if (IFuncUpdateSym)
+    OutStreamer->emitXCOFFSymbolLinkageWithVisibility(
+        IFuncUpdateSym, MCSA_LGlobal, MCSA_Invalid);
+
+  // .align 4
+  Align Alignment(STI->getTargetLowering()->getMinFunctionAlignment());
+  emitAlignment(Alignment, nullptr);
+
+  // generate foo's function descriptor
+  emitFunctionDescriptor();
+
+  emitFunctionEntryLabel();
+
+  // back to .foo[PR]
+  // .ref ifunc_sec.foo[RW]
+  if (IFuncUpdateSym)
+    OutStreamer->emitXCOFFRefDirective(IFuncUpdateSym);
+
+  // vvvvvv TEMPORARY: TO BE REMOVED AFTER upstream PR 151569 lands vvvvv
+  // .ref .__init_ifuncs[PR]
+  if (MDNode *MD = GI.getMetadata(LLVMContext::MD_associated)) {
+    const ValueAsMetadata *VAM = cast<ValueAsMetadata>(MD->getOperand(0).get());
+    const GlobalVariable *IFuncUpdateGV = cast<GlobalVariable>(VAM->getValue());
+    MD = IFuncUpdateGV->getMetadata(LLVMContext::MD_associated);
+    if (MD) {
+      const ValueAsMetadata *VAM =
+          cast<ValueAsMetadata>(MD->getOperand(0).get());
+      const Function *InitIFuncDecl = cast<Function>(VAM->getValue());
+      OutStreamer->emitXCOFFRefDirective(
+          getObjFileLowering().getFunctionEntryPointSymbol(InitIFuncDecl, TM));
+    }
+  }
+  // ^^^^^^ TEMPORARY ^^^^^
+
+  // generate the code for .foo now:
+  if (TOCRestoreNeeded(GI)) {
+    reportFatalUsageError(
+        "unimplmented: TOC register save/restore needed for function " +
+        Twine(GI.getName()) +
+        ", check if -mllvm -ifunc-local=... applies to your case");
+    return;
+  }
+
+  //  lwz 12, L..C3(2)
+  auto FnDescTOCEntryType = getTOCEntryTypeForLinkage(GI.getLinkage());
+  auto *FnDescTOCEntrySym =
+      lookUpOrCreateTOCEntry(CurrentFnDescSym, FnDescTOCEntryType);
+  auto *Exp = MCSymbolRefExpr::create(FnDescTOCEntrySym, OutContext);
+  // Exp = getTOCEntryLoadingExprForXCOFF(MOSymbol, Exp, VK);// TODO: need this?
+  // need this uncommented
+  OutStreamer->emitInstruction(MCInstBuilder(IsPPC64 ? PPC::LD : PPC::LWZ)
+                                   .addReg(PPC::X12)
+                                   .addExpr(Exp)
+                                   .addReg(PPC::X2),
+                               *Subtarget);
+
+  //  lwz 12, 0(12)
+  OutStreamer->emitInstruction(MCInstBuilder(IsPPC64 ? PPC::LD : PPC::LWZ)
+                                   .addReg(PPC::X12)
+                                   .addImm(0)
+                                   .addReg(PPC::X12),
+                               *Subtarget);
+  //  mtctr 12
+  OutStreamer->emitInstruction(
+      MCInstBuilder(IsPPC64 ? PPC::MTCTR8 : PPC::MTCTR).addReg(PPC::X12),
+      *Subtarget);
+  //  bctr
+  OutStreamer->emitInstruction(MCInstBuilder(IsPPC64 ? PPC::BCTR8 : PPC::BCTR),
+                               *Subtarget);
+}
+
 char PPCAIXAsmPrinter::ID = 0;
 
 INITIALIZE_PASS(PPCAIXAsmPrinter, "ppc-aix-asm-printer",
diff --git a/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp b/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp
new file mode 100644
index 0000000000000..098412554721c
--- /dev/null
+++ b/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp
@@ -0,0 +1,123 @@
+//===-- PPCPrepareIFuncsOnAIX.cpp - Prepare for ifunc lowering in codegen ===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This pass generates...
+//
+//===----------------------------------------------------------------------===//
+
+#include "PPC.h"
+#include "PPCSubtarget.h"
+#include "PPCTargetMachine.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/Statistic.h"
+#include "llvm/Analysis/TargetTransformInfo.h"
+#include "llvm/CodeGen/TargetPassConfig.h"
+#include "llvm/IR/Instructions.h"
+#include "llvm/IR/Module.h"
+#include <cassert>
+
+using namespace llvm;
+
+#define DEBUG_TYPE "ppc-prep-ifunc-aix"
+
+STATISTIC(NumIFuncs, "Number of IFuncs prepared");
+
+namespace {
+class PPCPrepareIFuncsOnAIX : public ModulePass {
+public:
+  static char ID;
+
+  PPCPrepareIFuncsOnAIX() : ModulePass(ID) {}
+
+  bool runOnModule(Module &M) override;
+
+  StringRef getPassName() const override {
+    return "PPC Prepare for AIX IFunc lowering";
+  }
+};
+} // namespace
+
+char PPCPrepareIFuncsOnAIX::ID = 0;
+
+INITIALIZE_PASS(PPCPrepareIFuncsOnAIX, DEBUG_TYPE,
+                "PPC Prepare for AIX IFunc lowering", false, false)
+
+ModulePass *llvm::createPPCPrepareIFuncsOnAIXPass() {
+  return new PPCPrepareIFuncsOnAIX();
+}
+
+// @foo = ifunc i32 (), ptr @foo_resolver, !associated !0
+// define ptr @foo_resolver() {
+//  ...
+//
+// %struct.IFUNC_PAIR = type { ptr, ptr }
+// @update_foo = internal global %struct.IFUNC_PAIR { ptr @foo, ptr
+// @foo_resolver }, section "ifunc_sec", align 8, !associated !1 declare void
+// @__init_ifuncs(...)
+//
+// !0 = !{ptr @update_foo}
+// !1 = !{ptr @__init_ifuncs}
+bool PPCPrepareIFuncsOnAIX::runOnModule(Module &M) {
+  if (M.ifuncs().empty())
+    return false;
+
+  const DataLayout &DL = M.getDataLayout();
+  LLVMContext &Ctx = M.getContext();
+  auto *PtrTy = PointerType::getUnqual(Ctx);
+  StringRef IFuncUpdatePrefix = "__update_";
+  StringRef IFuncUpdateSectionName = "ifunc_sec";
+  StructType *IFuncPairType = StructType::get(PtrTy, PtrTy);
+
+  StringRef IFuncConstructorName = "__init_ifuncs";
+  auto *IFuncConstructorFnType =
+      FunctionType::get(Type::getVoidTy(Ctx), {}, /*isVarArg=*/false);
+  auto *IFuncConstructorDecl =
+      Function::Create(IFuncConstructorFnType, GlobalValue::ExternalLinkage,
+                       IFuncConstructorName, M);
+
+  for (GlobalIFunc &IFunc : M.ifuncs()) {
+    LLVM_DEBUG(dbgs() << "doing ifunc " << IFunc.getName() << "\n");
+    // @update_foo = internal global { ptr @foo, ptr @foo_resolver }, section
+    // "ifunc_sec", align 8
+    std::string Name = (Twine(IFuncUpdatePrefix) + IFunc.getName()).str();
+    auto *GV = new GlobalVariable(M, IFuncPairType, /*isConstant*/ false,
+                                  GlobalValue::PrivateLinkage, nullptr, Name);
+    GV->setAlignment(DL.getPointerPrefAlignment());
+    GV->setVisibility(GlobalValue::DefaultVisibility);
+    GV->setSection(IFuncUpdateSectionName);
+
+    // Note that on AIX, the address of a function is the address of it's
+    // function descriptor, which is what these two values end up being
+    // in assembly.
+    Constant *InitVals[] = {&IFunc, IFunc.getResolver()};
+    GV->setInitializer(ConstantStruct::get(IFuncPairType, InitVals));
+
+    // Associate liveness of function foo with the liveness of update_foo.
+    IFunc.setMetadata(LLVMContext::MD_associated,
+                      MDNode::get(Ctx, ValueAsMetadata::get(GV)));
+    // Make function foo depend on the constructor that calls each ifunc's
+    // resolver and updaTes the ifunc's function descriptor with the result.
+    // Note: technically, we can associate both the update_foo variable and
+    // the constructor function to function foo, but only one MD_associated
+    // is allowed on an llvm::Value, so associate the constructor to update_foo
+    // here.
+    GV->setMetadata(
+        LLVMContext::MD_associated,
+        MDNode::get(Ctx, ValueAsMetadata::get(IFuncConstructorDecl)));
+
+    MDNode *MD = GV->getMetadata(LLVMContext::MD_associated);
+    assert(MD);
+    LLVM_DEBUG(MD->dump());
+    MD = IFunc.getMetadata(LLVMContext::MD_associated);
+    assert(MD);
+    LLVM_DEBUG(MD->dump());
+  }
+  LLVM_DEBUG(M.dump());
+
+  return true;
+}
diff --git a/llvm/lib/Target/PowerPC/PPCTargetMachine.cpp b/llvm/lib/Target/PowerPC/PPCTargetMachine.cpp
index 000d29610678f..a4c279db57135 100644
--- a/llvm/lib/Target/PowerPC/PPCTargetMachine.cpp
+++ b/llvm/lib/Target/PowerPC/PPCTargetMachine.cpp
@@ -145,6 +145,7 @@ LLVMInitializePowerPCTarget() {
   initializeGlobalISel(PR);
   initializePPCCTRLoopsPass(PR);
   initializePPCDAGToDAGISelLegacyPass(PR);
+  initializePPCPrepareIFuncsOnAIXPass(PR);
   initializePPCLinuxAsmPrinterPass(PR);
   initializePPCAIXAsmPrinterPass(PR);
 }
@@ -437,6 +438,9 @@ void PPCPassConfig::addIRPasses() {
     addPass(createLICMPass());
   }
 
+  if (TM->getTargetTriple().isOSAIX())
+    addPass(createPPCPrepareIFuncsOnAIXPass());
+
   TargetPassConfig::addIRPasses();
 }
 
diff --git a/llvm/utils/gn/secondary/llvm/lib/Target/PowerPC/BUILD.gn b/llvm/utils/gn/secondary/llvm/lib/Target/PowerPC/BUILD.gn
index 2bce96859f8bc..cff10732bd1ed 100644
--- a/llvm/utils/gn/secondary/llvm/lib/Target/PowerPC/BUILD.gn
+++ b/llvm/utils/gn/secondary/llvm/lib/Target/PowerPC/BUILD.gn
@@ -83,6 +83,7 @@ static_library("LLVMPowerPCCodeGen") {
     "PPCMachineScheduler.cpp",
     "PPCMacroFusion.cpp",
     "PPCPreEmitPeephole.cpp",
+    "PPCPrepareIFuncsOnAIX.cpp",
     "PPCReduceCRLogicals.cpp",
     "PPCRegisterInfo.cpp",
     "PPCSelectionDAGInfo.cpp",

>From 7defcf42bd365f44e62c9cd827fc2e4fa35a1efc Mon Sep 17 00:00:00 2001
From: Wael Yehia <wyehia at ca.ibm.com>
Date: Tue, 9 Sep 2025 20:30:47 +0000
Subject: [PATCH 02/16] enable tests

---
 clang/test/CodeGen/attr-ifunc.c            | 2 ++
 clang/test/CodeGen/attr-ifunc.cpp          | 2 ++
 clang/test/CodeGen/ifunc.c                 | 4 +++-
 clang/test/SemaCXX/ifunc-has-attribute.cpp | 1 +
 4 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/clang/test/CodeGen/attr-ifunc.c b/clang/test/CodeGen/attr-ifunc.c
index c9e70b17a8302..55d1866c17a69 100644
--- a/clang/test/CodeGen/attr-ifunc.c
+++ b/clang/test/CodeGen/attr-ifunc.c
@@ -4,6 +4,8 @@
 // RUN: %clang_cc1 -triple x86_64-apple-macosx -verify -emit-llvm-only %s
 // RUN: %clang_cc1 -triple aarch64-none-linux-gnu -verify -emit-llvm-only %s
 // RUN: %clang_cc1 -triple aarch64-pc-windows-msvcu -verify -emit-llvm-only %s
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix-xcoff -verify -emit-llvm-only %s
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix-xcoff -verify -emit-llvm-only -DCHECK_ALIASES %s
 
 #if defined(_WIN32) && !defined(__aarch64__)
 void foo(void) {}
diff --git a/clang/test/CodeGen/attr-ifunc.cpp b/clang/test/CodeGen/attr-ifunc.cpp
index 9e6cd7312122d..601fad94530bd 100644
--- a/clang/test/CodeGen/attr-ifunc.cpp
+++ b/clang/test/CodeGen/attr-ifunc.cpp
@@ -1,9 +1,11 @@
 // RUN: %clang_cc1 -triple x86_64-linux -verify -emit-llvm-only %s
 // RUN: %clang_cc1 -triple x86_64-apple-macosx -verify -emit-llvm-only %s
 // RUN: %clang_cc1 -triple arm64-apple-macosx -verify -emit-llvm-only %s
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix-xcoff -verify -emit-llvm-only %s
 // RUN: not %clang_cc1 -triple x86_64-linux -emit-llvm-only -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
 // RUN: not %clang_cc1 -triple x86_64-apple-macosx -emit-llvm-only -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
 // RUN: not %clang_cc1 -triple arm64-apple-macosx -emit-llvm-only -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
+// RUN: not %clang_cc1 -triple powerpc64-ibm-aix-xcoff -emit-llvm-only -fdiagnostics-parseable-fixits %s 2>&1 | FileCheck %s
 
 void *f1_ifunc(void) { return nullptr; }
 void f1(void) __attribute__((ifunc("f1_ifunc")));
diff --git a/clang/test/CodeGen/ifunc.c b/clang/test/CodeGen/ifunc.c
index 7d21f742e8676..c346f81947cde 100644
--- a/clang/test/CodeGen/ifunc.c
+++ b/clang/test/CodeGen/ifunc.c
@@ -16,6 +16,8 @@
 // RUN: %clang_cc1 -triple aarch64-none-linux-gnu -O2 -emit-llvm -o - %s | FileCheck %s
 // RUN: %clang_cc1 -triple aarch64-none-linux-gnu -fsanitize=thread -O2 -emit-llvm -o - %s | FileCheck %s --check-prefix=SAN
 // RUN: %clang_cc1 -triple aarch64-none-linux-gnu -fsanitize=address -O2 -emit-llvm -o - %s | FileCheck %s --check-prefix=SAN
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix-xcoff -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -triple powerpc64-ibm-aix-xcoff -O2 -emit-llvm -o - %s | FileCheck %s
 
 
 /// The ifunc is emitted before its resolver.
@@ -65,7 +67,7 @@ extern void hoo(int) __attribute__ ((ifunc("hoo_ifunc")));
 // AVR: @goo = ifunc void (), ptr addrspace(1) @goo_ifunc
 // AVR: @hoo = ifunc void (i16), ptr addrspace(1) @hoo_ifunc
 
-// CHECK: call i32 @foo(i32
+// CHECK: call {{(signext )?}}i32 @foo(i32
 // CHECK: call void @goo()
 
 // SAN: define {{(dso_local )?}}noalias {{(noundef )?}}ptr @goo_ifunc() #[[#GOO_IFUNC:]] {
diff --git a/clang/test/SemaCXX/ifunc-has-attribute.cpp b/clang/test/SemaCXX/ifunc-has-attribute.cpp
index 242f3b621745f..913bc40ffee44 100644
--- a/clang/test/SemaCXX/ifunc-has-attribute.cpp
+++ b/clang/test/SemaCXX/ifunc-has-attribute.cpp
@@ -2,6 +2,7 @@
 // RUN: %clang_cc1 -emit-llvm-only -triple x86_64-apple-macosx -verify %s -DSUPPORTED=1
 // RUN: %clang_cc1 -emit-llvm-only -triple arm64-apple-macosx -verify %s -DSUPPORTED=1
 // RUN: %clang_cc1 -emit-llvm-only -triple x86_64-pc-win32 -verify %s -DNOT_SUPPORTED=1
+// RUN: %clang_cc1 -emit-llvm-only -triple powerpc64-ibm-aix-xcoff -verify %s -DSUPPORTED=1
 
 // expected-no-diagnostics
 

>From 916b94ef2c3ca3d4a5c03d1ed1cd945389ffb5d3 Mon Sep 17 00:00:00 2001
From: Wael Yehia <wyehia at ca.ibm.com>
Date: Wed, 10 Sep 2025 03:14:54 +0000
Subject: [PATCH 03/16] Add ppc/init_ifuncs.c to the builtins library and pass
 the linker option -bdbg:namedsects:ss by default on AIX

---
 clang/lib/Driver/ToolChains/AIX.cpp        | 23 +++-------------------
 compiler-rt/lib/builtins/CMakeLists.txt    | 10 ++++++++++
 compiler-rt/lib/builtins/ppc/init_ifuncs.c | 14 +++++++++++++
 3 files changed, 27 insertions(+), 20 deletions(-)
 create mode 100644 compiler-rt/lib/builtins/ppc/init_ifuncs.c

diff --git a/clang/lib/Driver/ToolChains/AIX.cpp b/clang/lib/Driver/ToolChains/AIX.cpp
index 066b59305fe3f..8111179d3de92 100644
--- a/clang/lib/Driver/ToolChains/AIX.cpp
+++ b/clang/lib/Driver/ToolChains/AIX.cpp
@@ -146,26 +146,9 @@ void aix::Linker::ConstructJob(Compilation &C, const JobAction &JA,
     CmdArgs.push_back("-bforceimprw");
   }
 
-  // PGO instrumentation generates symbols belonging to special sections, and
-  // the linker needs to place all symbols in a particular section together in
-  // memory; the AIX linker does that under an option.
-  if (Args.hasFlag(options::OPT_fprofile_arcs, options::OPT_fno_profile_arcs,
-                    false) ||
-       Args.hasFlag(options::OPT_fprofile_generate,
-                    options::OPT_fno_profile_generate, false) ||
-       Args.hasFlag(options::OPT_fprofile_generate_EQ,
-                    options::OPT_fno_profile_generate, false) ||
-       Args.hasFlag(options::OPT_fprofile_instr_generate,
-                    options::OPT_fno_profile_instr_generate, false) ||
-       Args.hasFlag(options::OPT_fprofile_instr_generate_EQ,
-                    options::OPT_fno_profile_instr_generate, false) ||
-       Args.hasFlag(options::OPT_fcs_profile_generate,
-                    options::OPT_fno_profile_generate, false) ||
-       Args.hasFlag(options::OPT_fcs_profile_generate_EQ,
-                    options::OPT_fno_profile_generate, false) ||
-       Args.hasArg(options::OPT_fcreate_profile) ||
-       Args.hasArg(options::OPT_coverage))
-    CmdArgs.push_back("-bdbg:namedsects:ss");
+  // ifunc support, which is ON by default, generates named sections.
+  CmdArgs.push_back("-bdbg:namedsects:ss");
+
 
   if (Arg *A =
           Args.getLastArg(clang::driver::options::OPT_mxcoff_build_id_EQ)) {
diff --git a/compiler-rt/lib/builtins/CMakeLists.txt b/compiler-rt/lib/builtins/CMakeLists.txt
index 0d7fc65cfd3e9..414e82bd372fe 100644
--- a/compiler-rt/lib/builtins/CMakeLists.txt
+++ b/compiler-rt/lib/builtins/CMakeLists.txt
@@ -789,6 +789,16 @@ if (NOT OS_NAME MATCHES "AIX")
     ${powerpc64_SOURCES}
   )
 endif()
+if (OS_NAME MATCHES "AIX")
+  set(powerpc_SOURCES
+    ppc/init_ifuncs.c
+    ${powerpc_SOURCES}
+  )
+  set(powerpc64_SOURCES
+    ppc/init_ifuncs.c
+    ${powerpc64_SOURCES}
+  )
+endif()
 set(powerpc64le_SOURCES ${powerpc64_SOURCES})
 
 set(riscv_SOURCES
diff --git a/compiler-rt/lib/builtins/ppc/init_ifuncs.c b/compiler-rt/lib/builtins/ppc/init_ifuncs.c
new file mode 100644
index 0000000000000..0f743c2ad05d4
--- /dev/null
+++ b/compiler-rt/lib/builtins/ppc/init_ifuncs.c
@@ -0,0 +1,14 @@
+typedef void* Ptr;
+typedef struct { Ptr addr, toc, env; } Descr;
+typedef struct { Descr* desc; Ptr (*resolver)(); } IFUNC_PAIR;
+
+extern IFUNC_PAIR __start_ifunc_sec, __stop_ifunc_sec;
+
+__attribute__((constructor))
+void __init_ifuncs() {
+  for (IFUNC_PAIR *pair = &__start_ifunc_sec;
+       pair != &__stop_ifunc_sec;
+       pair++)
+    pair->desc->addr = ((Descr*)(pair->resolver()))->addr;
+}
+

>From 5a1b3ceba6cfa8cee7f592db3aa52d7bbe7369a8 Mon Sep 17 00:00:00 2001
From: Wael Yehia <wyehia at ca.ibm.com>
Date: Wed, 10 Sep 2025 14:25:15 +0000
Subject: [PATCH 04/16] create a zero-length entry in the ifunc_sec section to
 satisfy the start/stop symbol references when user code does not use ifuncs

---
 compiler-rt/lib/builtins/ppc/init_ifuncs.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/compiler-rt/lib/builtins/ppc/init_ifuncs.c b/compiler-rt/lib/builtins/ppc/init_ifuncs.c
index 0f743c2ad05d4..b48e80fabc84e 100644
--- a/compiler-rt/lib/builtins/ppc/init_ifuncs.c
+++ b/compiler-rt/lib/builtins/ppc/init_ifuncs.c
@@ -2,12 +2,20 @@ typedef void* Ptr;
 typedef struct { Ptr addr, toc, env; } Descr;
 typedef struct { Descr* desc; Ptr (*resolver)(); } IFUNC_PAIR;
 
+// A zero-length entry in section "ifunc_sec" to satisfy the __start_ifunc_sec
+// and __stop_ifunc_sec references in this file, when no user code has any.
+__attribute__((section("ifunc_sec"))) static int dummy_ifunc_sec[0];
+
 extern IFUNC_PAIR __start_ifunc_sec, __stop_ifunc_sec;
 
 __attribute__((constructor))
 void __init_ifuncs() {
-  for (IFUNC_PAIR *pair = &__start_ifunc_sec;
-       pair != &__stop_ifunc_sec;
+  void *volatile ref = &dummy_ifunc_sec; // hack to keep dummy_ifunc_sec alive
+
+  // hack to prevent compiler from assuming __start_ifunc_sec and
+  // __stop_ifunc_sec occupy different addresses.
+  IFUNC_PAIR *volatile volatile_end = &__stop_ifunc_sec;
+  for (IFUNC_PAIR *pair = &__start_ifunc_sec, *end = volatile_end; pair != end;
        pair++)
     pair->desc->addr = ((Descr*)(pair->resolver()))->addr;
 }

>From 36f1e88530b37b1fe56fe4a4696e2877e328ded0 Mon Sep 17 00:00:00 2001
From: Wael Yehia <wyehia at ca.ibm.com>
Date: Thu, 18 Sep 2025 03:00:07 +0000
Subject: [PATCH 05/16] use -bdbg:namedsects:ss on AIX 7.2 and above

also pass -bdbg:namedsects:ss if AIX version is unknown
---
 clang/include/clang/Basic/TargetInfo.h            | 3 ++-
 clang/lib/Driver/ToolChains/AIX.cpp               | 8 +++++---
 llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp | 9 +--------
 3 files changed, 8 insertions(+), 12 deletions(-)

diff --git a/clang/include/clang/Basic/TargetInfo.h b/clang/include/clang/Basic/TargetInfo.h
index d04df9664d44f..a3cf0885b8ae2 100644
--- a/clang/include/clang/Basic/TargetInfo.h
+++ b/clang/include/clang/Basic/TargetInfo.h
@@ -1548,7 +1548,8 @@ class TargetInfo : public TransferrableTargetInfo,
     if (getTriple().getArch() == llvm::Triple::ArchType::avr)
       return true;
     if (getTriple().isOSAIX())
-      return true;
+      return getTriple().getOSMajorVersion() == 0 ||
+             getTriple().getOSVersion() >= VersionTuple(7, 2);
     return getTriple().isOSBinFormatELF() &&
            ((getTriple().isOSLinux() && !getTriple().isMusl()) ||
             getTriple().isOSFreeBSD());
diff --git a/clang/lib/Driver/ToolChains/AIX.cpp b/clang/lib/Driver/ToolChains/AIX.cpp
index 8111179d3de92..c7448cf13fe3c 100644
--- a/clang/lib/Driver/ToolChains/AIX.cpp
+++ b/clang/lib/Driver/ToolChains/AIX.cpp
@@ -146,9 +146,11 @@ void aix::Linker::ConstructJob(Compilation &C, const JobAction &JA,
     CmdArgs.push_back("-bforceimprw");
   }
 
-  // ifunc support, which is ON by default, generates named sections.
-  CmdArgs.push_back("-bdbg:namedsects:ss");
-
+  // PGO and ifunc support depends on the named sections linker feature that is
+  // available on AIX 7.2 TL5 SP5 onwards.
+  if (ToolChain.getTriple().getOSMajorVersion() == 0 ||
+      ToolChain.getTriple().getOSVersion() >= VersionTuple(7, 2))
+    CmdArgs.push_back("-bdbg:namedsects:ss");
 
   if (Arg *A =
           Args.getLastArg(clang::driver::options::OPT_mxcoff_build_id_EQ)) {
diff --git a/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp b/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp
index 098412554721c..9fc5f53ad677e 100644
--- a/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp
+++ b/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp
@@ -81,6 +81,7 @@ bool PPCPrepareIFuncsOnAIX::runOnModule(Module &M) {
                        IFuncConstructorName, M);
 
   for (GlobalIFunc &IFunc : M.ifuncs()) {
+    NumIFuncs++;
     LLVM_DEBUG(dbgs() << "doing ifunc " << IFunc.getName() << "\n");
     // @update_foo = internal global { ptr @foo, ptr @foo_resolver }, section
     // "ifunc_sec", align 8
@@ -109,15 +110,7 @@ bool PPCPrepareIFuncsOnAIX::runOnModule(Module &M) {
     GV->setMetadata(
         LLVMContext::MD_associated,
         MDNode::get(Ctx, ValueAsMetadata::get(IFuncConstructorDecl)));
-
-    MDNode *MD = GV->getMetadata(LLVMContext::MD_associated);
-    assert(MD);
-    LLVM_DEBUG(MD->dump());
-    MD = IFunc.getMetadata(LLVMContext::MD_associated);
-    assert(MD);
-    LLVM_DEBUG(MD->dump());
   }
-  LLVM_DEBUG(M.dump());
 
   return true;
 }

>From 27da809e57e8fd2d9c706643544c3bc9fa6c0572 Mon Sep 17 00:00:00 2001
From: Wael Yehia <wyehia at ca.ibm.com>
Date: Fri, 19 Sep 2025 14:31:28 -0400
Subject: [PATCH 06/16] Address code review comments Load R11 in the ifunc stub

---
 llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp     | 26 ++++++++++++-------
 .../Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp  |  5 ++--
 2 files changed, 19 insertions(+), 12 deletions(-)

diff --git a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
index 03a72fc1e8ef0..2bd0c2da7bfe8 100644
--- a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
+++ b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
@@ -3384,7 +3384,8 @@ void PPCAIXAsmPrinter::emitModuleCommandLines(Module &M) {
   OutStreamer->emitXCOFFCInfoSym(".GCC.command.line", RSOS.str());
 }
 
-static bool TOCRestoreNeeded(const GlobalIFunc &GI) {
+
+static bool TOCRestoreNeededForCallToImplementation(const GlobalIFunc &GI) {
   auto IsLocalFunc = [&](const Value *V) {
     if (!isa<Function>(V))
       return false;
@@ -3458,10 +3459,11 @@ static bool TOCRestoreNeeded(const GlobalIFunc &GI) {
  *   .vbyte  4, 0
  *   .csect .foo[PR],5
  *   .ref ifunc_sec.foo[RW]
- *   lwz 12, L..C3(2)
- *   lwz 12, 0(12)
+ *   ld 12, L..foo_desc(2)                   # load foo's descriptor address
+ *   ld 11, 16(12)                           # load the env pointer if target might be a non-C/C++ function, otherwise this load is omitted
+ *   ld 12, 0(12)                            # load foo.addr
  *   mtctr 12
- *   bctr
+ *   bctr                                    # branch to CR without setting LR so that callee returns to the caller of .foo
  *                                              # -- End function
  */
 void PPCAIXAsmPrinter::emitGlobalIFunc(Module &M, const GlobalIFunc &GI) {
@@ -3536,15 +3538,16 @@ void PPCAIXAsmPrinter::emitGlobalIFunc(Module &M, const GlobalIFunc &GI) {
   // ^^^^^^ TEMPORARY ^^^^^
 
   // generate the code for .foo now:
-  if (TOCRestoreNeeded(GI)) {
+  if (TOCRestoreNeededForCallToImplementation(GI)) {
     reportFatalUsageError(
-        "unimplmented: TOC register save/restore needed for function " +
+        "unimplemented: TOC register save/restore needed for function " +
         Twine(GI.getName()) +
-        ", check if -mllvm -ifunc-local=... applies to your case");
+        ", because couldn't prove all candidates are static or hidden/protected"
+        " visibility definitions");
     return;
   }
 
-  //  lwz 12, L..C3(2)
+  //  lwz 12, L..foo_desc(2)
   auto FnDescTOCEntryType = getTOCEntryTypeForLinkage(GI.getLinkage());
   auto *FnDescTOCEntrySym =
       lookUpOrCreateTOCEntry(CurrentFnDescSym, FnDescTOCEntryType);
@@ -3556,7 +3559,12 @@ void PPCAIXAsmPrinter::emitGlobalIFunc(Module &M, const GlobalIFunc &GI) {
                                    .addExpr(Exp)
                                    .addReg(PPC::X2),
                                *Subtarget);
-
+  //  lwz 11, 8(12)
+  OutStreamer->emitInstruction(MCInstBuilder(IsPPC64 ? PPC::LD : PPC::LWZ)
+                                   .addReg(PPC::X11)
+                                   .addImm(IsPPC64 ? 16 : 8)
+                                   .addReg(PPC::X12),
+                               *Subtarget);
   //  lwz 12, 0(12)
   OutStreamer->emitInstruction(MCInstBuilder(IsPPC64 ? PPC::LD : PPC::LWZ)
                                    .addReg(PPC::X12)
diff --git a/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp b/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp
index 9fc5f53ad677e..d1a1f436328b3 100644
--- a/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp
+++ b/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp
@@ -83,13 +83,12 @@ bool PPCPrepareIFuncsOnAIX::runOnModule(Module &M) {
   for (GlobalIFunc &IFunc : M.ifuncs()) {
     NumIFuncs++;
     LLVM_DEBUG(dbgs() << "doing ifunc " << IFunc.getName() << "\n");
-    // @update_foo = internal global { ptr @foo, ptr @foo_resolver }, section
-    // "ifunc_sec", align 8
+    // @__update_foo = private global { ptr @foo, ptr @foo_resolver },
+    //   section "ifunc_sec"
     std::string Name = (Twine(IFuncUpdatePrefix) + IFunc.getName()).str();
     auto *GV = new GlobalVariable(M, IFuncPairType, /*isConstant*/ false,
                                   GlobalValue::PrivateLinkage, nullptr, Name);
     GV->setAlignment(DL.getPointerPrefAlignment());
-    GV->setVisibility(GlobalValue::DefaultVisibility);
     GV->setSection(IFuncUpdateSectionName);
 
     // Note that on AIX, the address of a function is the address of it's

>From 896b5433e5c8ffc9d651cab00803a8b3be1ff889 Mon Sep 17 00:00:00 2001
From: Wael Yehia <wyehia at ca.ibm.com>
Date: Sun, 21 Sep 2025 23:12:50 -0400
Subject: [PATCH 07/16] comment changes and logic updates in
 TOCRestoreNeededForCallToImplementation

---
 llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp     | 26 ++++++++++++-------
 .../Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp  | 19 +++++++-------
 2 files changed, 26 insertions(+), 19 deletions(-)

diff --git a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
index 2bd0c2da7bfe8..8a9bfedef2db1 100644
--- a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
+++ b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
@@ -3391,14 +3391,17 @@ static bool TOCRestoreNeededForCallToImplementation(const GlobalIFunc &GI) {
       return false;
     auto *F = cast<Function>(V);
 
-    // static functions are local
+    // Static functions are local
     if (F->getLinkage() == GlobalValue::InternalLinkage)
       return true;
-    // for now, declarations we treat as potentially non-local
+    // We treat declarations as non-local because the visibility attribute
+    // on a declaration might not match the definition, and AIX linker
+    // ignores the visibility on a reference.
     if (F->isDeclarationForLinker())
       return false;
-    // hidden visibility definitions cannot be preempted, so treat as local.
-    if (F->getVisibility() == GlobalValue::HiddenVisibility)
+    // hidden or protected visibility definitions cannot be preempted.
+    if (F->getVisibility() == GlobalValue::HiddenVisibility ||
+        F->getVisibility() == GlobalValue::ProtectedVisibility)
       return true;
 
     return false;
@@ -3417,9 +3420,12 @@ static bool TOCRestoreNeededForCallToImplementation(const GlobalIFunc &GI) {
       return false;
   }
 
-  // if one of the return values of the resolver function is not a
-  // local function, then we have to conservatively do a TOC save/restore.
   auto *Resolver = GI.getResolverFunction();
+  // If the resolver is preemptible then we cannot rely on its implementation.
+  if (!isLocalFunc(Resolver))
+    return true;
+  // If one of the return values of the resolver function is not a
+  // local function, then we have to conservatively do a TOC save/restore.
   for (auto &BB : *Resolver) {
     if (auto *Ret = dyn_cast<ReturnInst>(BB.getTerminator())) {
       Value *RV = Ret->getReturnValue();
@@ -3459,12 +3465,12 @@ static bool TOCRestoreNeededForCallToImplementation(const GlobalIFunc &GI) {
  *   .vbyte  4, 0
  *   .csect .foo[PR],5
  *   .ref ifunc_sec.foo[RW]
- *   ld 12, L..foo_desc(2)                   # load foo's descriptor address
- *   ld 11, 16(12)                           # load the env pointer if target might be a non-C/C++ function, otherwise this load is omitted
- *   ld 12, 0(12)                            # load foo.addr
+ *   lwz 12, L..foo_desc(2)                  # load foo's descriptor address
+ *   lwz 11, 8(12)                           # load the env pointer if target might be a non-C/C++ function
+ *   lwz 12, 0(12)                           # load foo.addr
  *   mtctr 12
  *   bctr                                    # branch to CR without setting LR so that callee returns to the caller of .foo
- *                                              # -- End function
+ *                                           # -- End function
  */
 void PPCAIXAsmPrinter::emitGlobalIFunc(Module &M, const GlobalIFunc &GI) {
   // Set the Subtarget to that of the resolver.
diff --git a/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp b/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp
index d1a1f436328b3..7f0586799e585 100644
--- a/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp
+++ b/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp
@@ -51,17 +51,18 @@ ModulePass *llvm::createPPCPrepareIFuncsOnAIXPass() {
   return new PPCPrepareIFuncsOnAIX();
 }
 
-// @foo = ifunc i32 (), ptr @foo_resolver, !associated !0
-// define ptr @foo_resolver() {
-//  ...
+// For each ifunc `foo` with a resolver `foo_resolver`, create a global variable
+// `__update_foo` in the `ifunc_sec` section, representing the pair:
+//   { ptr @foo, ptr @foo_resolver }
+// The compiler arranges for the constructor function `__init_ifuncs` to be
+// included on the link step. The constructor walks the `ifunc_sec` section,
+// calling the resolver function and storing the result in foo's descriptor.
+// On AIX, the address of a function is the address of its descriptor, so the
+// constructor accesses foo's descriptor from the first field of the pair.
 //
-// %struct.IFUNC_PAIR = type { ptr, ptr }
-// @update_foo = internal global %struct.IFUNC_PAIR { ptr @foo, ptr
-// @foo_resolver }, section "ifunc_sec", align 8, !associated !1 declare void
-// @__init_ifuncs(...)
+// Since the global `__update_foo` is unreferenced, it's liveness needs to be
+// associated to the liveness of ifunc `foo`
 //
-// !0 = !{ptr @update_foo}
-// !1 = !{ptr @__init_ifuncs}
 bool PPCPrepareIFuncsOnAIX::runOnModule(Module &M) {
   if (M.ifuncs().empty())
     return false;

>From 13868e89dfdff2d0e2189340bfe73f65a9f20aa7 Mon Sep 17 00:00:00 2001
From: Wael Yehia <wyehia at ca.ibm.com>
Date: Mon, 22 Sep 2025 17:48:34 -0400
Subject: [PATCH 08/16] refactor TOCRestoreNeeded

---
 llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp | 66 ++++++++++++++---------
 1 file changed, 42 insertions(+), 24 deletions(-)

diff --git a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
index 8a9bfedef2db1..2bab48314e9f5 100644
--- a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
+++ b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
@@ -47,6 +47,7 @@
 #include "llvm/IR/GlobalValue.h"
 #include "llvm/IR/GlobalVariable.h"
 #include "llvm/IR/Module.h"
+#include "llvm/IR/PatternMatch.h"
 #include "llvm/MC/MCAsmInfo.h"
 #include "llvm/MC/MCContext.h"
 #include "llvm/MC/MCDirectives.h"
@@ -82,6 +83,7 @@
 
 using namespace llvm;
 using namespace llvm::XCOFF;
+using namespace PatternMatch;
 
 #define DEBUG_TYPE "asmprinter"
 
@@ -3384,13 +3386,9 @@ void PPCAIXAsmPrinter::emitModuleCommandLines(Module &M) {
   OutStreamer->emitXCOFFCInfoSym(".GCC.command.line", RSOS.str());
 }
 
-
 static bool TOCRestoreNeededForCallToImplementation(const GlobalIFunc &GI) {
-  auto IsLocalFunc = [&](const Value *V) {
-    if (!isa<Function>(V))
-      return false;
-    auto *F = cast<Function>(V);
-
+  // Query if the given function is local to the load module.
+  auto IsLocalFunc = [](const Function *F) {
     // Static functions are local
     if (F->getLinkage() == GlobalValue::InternalLinkage)
       return true;
@@ -3406,6 +3404,42 @@ static bool TOCRestoreNeededForCallToImplementation(const GlobalIFunc &GI) {
 
     return false;
   };
+  // Recursive walker that checks if all possible runtime values of the given
+  // llvm::Value are addresses of local functions.
+  std::function<bool(const Value*)> ValueIsALocalFunc = [&IsLocalFunc, &ValueIsALocalFunc](const Value *V) -> bool{
+    if (auto *F = dyn_cast<Function>(V))
+      return IsLocalFunc(F);
+    if (!isa<Instruction>(V))
+      return false;
+
+    Value *Op;
+    auto *I = cast<Instruction>(V);
+    // return isP9 ? foo_p9 : foo_default;
+    if (auto *SI = dyn_cast<SelectInst>(I))
+      return ValueIsALocalFunc(SI->getTrueValue()) && ValueIsALocalFunc(SI->getFalseValue());
+    else if (auto *PN = dyn_cast<PHINode>(I)) {
+      for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i)
+        if (!ValueIsALocalFunc(PN->getIncomingValue(i)))
+          return false;
+      return true;
+    }
+    // @switch.table.resolve_foo = private unnamed_addr constant [3 x ptr] [ptr @foo_static, ptr @foo_hidden, ptr @foo_protected]
+    // %switch.gep = getelementptr inbounds nuw ptr, ptr @switch.table, i64 %2
+    // V = load ptr, ptr %switch.gep,
+    else if (match(I, m_Load(m_GEP(m_Value(Op), m_Value())))) {
+      if (!isa<GlobalVariable>(Op))
+        return false;
+      auto *GV = dyn_cast<GlobalVariable>(Op);
+      if (!GV->hasInitializer() || !isa<ConstantArray>(GV->getInitializer()))
+        return false;
+      auto *Initializer = cast<ConstantArray>(GV->getInitializer());
+      for (unsigned Idx = 0, End = Initializer->getNumOperands(); Idx != End; ++Idx)
+        if (!ValueIsALocalFunc(Initializer->getOperand(Idx)))
+          return false;
+      return true;
+    }
+    return false;
+  };
 
   if (!IFuncLocal.empty()) {
     ArrayRef<std::string> List = IFuncLocal;
@@ -3422,7 +3456,7 @@ static bool TOCRestoreNeededForCallToImplementation(const GlobalIFunc &GI) {
 
   auto *Resolver = GI.getResolverFunction();
   // If the resolver is preemptible then we cannot rely on its implementation.
-  if (!isLocalFunc(Resolver))
+  if (!IsLocalFunc(Resolver))
     return true;
   // If one of the return values of the resolver function is not a
   // local function, then we have to conservatively do a TOC save/restore.
@@ -3430,23 +3464,7 @@ static bool TOCRestoreNeededForCallToImplementation(const GlobalIFunc &GI) {
     if (auto *Ret = dyn_cast<ReturnInst>(BB.getTerminator())) {
       Value *RV = Ret->getReturnValue();
       assert(RV);
-      // return &foo_p9;
-      if (auto *F = dyn_cast<Function>(RV)) {
-        if (!IsLocalFunc(F))
-          return true;
-      } else if (auto *I = dyn_cast<Instruction>(RV)) {
-        // return isP9 ? foo_p9 : foo_default;
-        if (auto *SI = dyn_cast<SelectInst>(I)) {
-          if (!IsLocalFunc(SI->getTrueValue()) ||
-              !IsLocalFunc(SI->getFalseValue()))
-            return true;
-        } else if (auto *PN = dyn_cast<PHINode>(I)) {
-          for (unsigned i = 1, e = PN->getNumIncomingValues(); i != e; ++i)
-            if (!IsLocalFunc(PN->getIncomingValue(i)))
-              return true;
-        } else
-          return true;
-      } else
+      if (!ValueIsALocalFunc(RV))
         return true;
     }
   }

>From 204ea53c3fbf4df1bb5787c075785b353f47fc28 Mon Sep 17 00:00:00 2001
From: Wael Yehia <wyehia at ca.ibm.com>
Date: Mon, 22 Sep 2025 21:03:40 -0400
Subject: [PATCH 09/16] simplify IsLocalFunc by using
 isStrongDefinitionForLinker() && isDSOLocal()

---
 llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp | 15 +--------------
 1 file changed, 1 insertion(+), 14 deletions(-)

diff --git a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
index 2bab48314e9f5..2003c62c2af11 100644
--- a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
+++ b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
@@ -3389,20 +3389,7 @@ void PPCAIXAsmPrinter::emitModuleCommandLines(Module &M) {
 static bool TOCRestoreNeededForCallToImplementation(const GlobalIFunc &GI) {
   // Query if the given function is local to the load module.
   auto IsLocalFunc = [](const Function *F) {
-    // Static functions are local
-    if (F->getLinkage() == GlobalValue::InternalLinkage)
-      return true;
-    // We treat declarations as non-local because the visibility attribute
-    // on a declaration might not match the definition, and AIX linker
-    // ignores the visibility on a reference.
-    if (F->isDeclarationForLinker())
-      return false;
-    // hidden or protected visibility definitions cannot be preempted.
-    if (F->getVisibility() == GlobalValue::HiddenVisibility ||
-        F->getVisibility() == GlobalValue::ProtectedVisibility)
-      return true;
-
-    return false;
+    return F->isStrongDefinitionForLinker() && F->isDSOLocal();
   };
   // Recursive walker that checks if all possible runtime values of the given
   // llvm::Value are addresses of local functions.

>From 8f81cc6d3726865c46520931ed84bbe4a971536e Mon Sep 17 00:00:00 2001
From: Wael Yehia <wyehia at ca.ibm.com>
Date: Tue, 23 Sep 2025 11:08:24 -0400
Subject: [PATCH 10/16] improve switch table detection

---
 llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
index 2003c62c2af11..0401ad3f11551 100644
--- a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
+++ b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
@@ -3389,17 +3389,18 @@ void PPCAIXAsmPrinter::emitModuleCommandLines(Module &M) {
 static bool TOCRestoreNeededForCallToImplementation(const GlobalIFunc &GI) {
   // Query if the given function is local to the load module.
   auto IsLocalFunc = [](const Function *F) {
-    return F->isStrongDefinitionForLinker() && F->isDSOLocal();
+    bool Result = F->isStrongDefinitionForLinker() && F->isDSOLocal();
+    LLVM_DEBUG(dbgs() << F->getName() << " is " << (Result ? "local\n" : "not local\n"));
+    return Result;
   };
   // Recursive walker that checks if all possible runtime values of the given
   // llvm::Value are addresses of local functions.
-  std::function<bool(const Value*)> ValueIsALocalFunc = [&IsLocalFunc, &ValueIsALocalFunc](const Value *V) -> bool{
+  std::function<bool(const Value*)> ValueIsALocalFunc = [&IsLocalFunc, &ValueIsALocalFunc](const Value *V) -> bool {
     if (auto *F = dyn_cast<Function>(V))
       return IsLocalFunc(F);
     if (!isa<Instruction>(V))
       return false;
 
-    Value *Op;
     auto *I = cast<Instruction>(V);
     // return isP9 ? foo_p9 : foo_default;
     if (auto *SI = dyn_cast<SelectInst>(I))
@@ -3413,7 +3414,10 @@ static bool TOCRestoreNeededForCallToImplementation(const GlobalIFunc &GI) {
     // @switch.table.resolve_foo = private unnamed_addr constant [3 x ptr] [ptr @foo_static, ptr @foo_hidden, ptr @foo_protected]
     // %switch.gep = getelementptr inbounds nuw ptr, ptr @switch.table, i64 %2
     // V = load ptr, ptr %switch.gep,
-    else if (match(I, m_Load(m_GEP(m_Value(Op), m_Value())))) {
+    else if (auto *Op = getPointerOperand(I)) {
+      while (isa<GEPOperator>(Op))
+        Op = cast<GEPOperator>(Op)->getPointerOperand();
+
       if (!isa<GlobalVariable>(Op))
         return false;
       auto *GV = dyn_cast<GlobalVariable>(Op);
@@ -3451,8 +3455,10 @@ static bool TOCRestoreNeededForCallToImplementation(const GlobalIFunc &GI) {
     if (auto *Ret = dyn_cast<ReturnInst>(BB.getTerminator())) {
       Value *RV = Ret->getReturnValue();
       assert(RV);
-      if (!ValueIsALocalFunc(RV))
+      if (!ValueIsALocalFunc(RV)) {
+        LLVM_DEBUG(dbgs() << "return value " << RV->getName() << " is not a local function\n");
         return true;
+      }
     }
   }
   // all return values where local functions, so no TOC save/restore needed.

>From 4956ea22541e5ac9ab785704846f4ceba934ef56 Mon Sep 17 00:00:00 2001
From: Wael Yehia <wyehia at ca.ibm.com>
Date: Tue, 23 Sep 2025 17:26:23 +0000
Subject: [PATCH 11/16] remove assert in transformCallee()

---
 llvm/lib/Target/PowerPC/PPCISelLowering.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Target/PowerPC/PPCISelLowering.cpp b/llvm/lib/Target/PowerPC/PPCISelLowering.cpp
index 2907303874de5..ce40a1098037b 100644
--- a/llvm/lib/Target/PowerPC/PPCISelLowering.cpp
+++ b/llvm/lib/Target/PowerPC/PPCISelLowering.cpp
@@ -5591,7 +5591,7 @@ static SDValue transformCallee(const SDValue &Callee, SelectionDAG &DAG,
     const GlobalValue *GV = cast<GlobalAddressSDNode>(Callee)->getGlobal();
 
     if (Subtarget.isAIXABI()) {
-      assert(!isa<GlobalIFunc>(GV) && "IFunc is not supported on AIX.");
+      // TODO: convert ifunc to indirect call
       return getAIXFuncEntryPointSymbolSDNode(GV);
     }
     return DAG.getTargetGlobalAddress(GV, dl, Callee.getValueType(), 0,

>From 28f128c297a19ac6025059c7d097ff6cb9a90e52 Mon Sep 17 00:00:00 2001
From: Wael Yehia <wyehia at ca.ibm.com>
Date: Tue, 30 Sep 2025 12:18:12 -0400
Subject: [PATCH 12/16] init_ifuncs.c: copy entire descriptor

---
 compiler-rt/lib/builtins/ppc/init_ifuncs.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/compiler-rt/lib/builtins/ppc/init_ifuncs.c b/compiler-rt/lib/builtins/ppc/init_ifuncs.c
index b48e80fabc84e..b3b8f7fab5b5b 100644
--- a/compiler-rt/lib/builtins/ppc/init_ifuncs.c
+++ b/compiler-rt/lib/builtins/ppc/init_ifuncs.c
@@ -16,7 +16,13 @@ void __init_ifuncs() {
   // __stop_ifunc_sec occupy different addresses.
   IFUNC_PAIR *volatile volatile_end = &__stop_ifunc_sec;
   for (IFUNC_PAIR *pair = &__start_ifunc_sec, *end = volatile_end; pair != end;
-       pair++)
-    pair->desc->addr = ((Descr*)(pair->resolver()))->addr;
+       pair++) {
+    // Call the resolver and copy the entire descriptor because:
+    //  - the resolved function might be in another DSO, so copy the TOC address
+    //  - we might be linking with objects from a language that uses the
+    //    enviroment pointer, so copy it too.
+    Descr *result = (Descr*)pair->resolver();
+    *(pair->desc) = *result;
+  }
 }
 

>From 62b6e6fa5b459fc3e51425f8ffcf33a119b7fb42 Mon Sep 17 00:00:00 2001
From: Wael Yehia <wyehia at ca.ibm.com>
Date: Tue, 30 Sep 2025 19:21:26 -0400
Subject: [PATCH 13/16] code review

---
 llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp | 13 ++++---------
 1 file changed, 4 insertions(+), 9 deletions(-)

diff --git a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
index 0401ad3f11551..a0eaa607f1864 100644
--- a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
+++ b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
@@ -2880,8 +2880,7 @@ void PPCAIXAsmPrinter::emitFunctionDescriptor() {
       static_cast<MCSymbolXCOFF *>(CurrentFnDescSym)->getRepresentedCsect());
 
   // Emit aliasing label for function descriptor csect.
-  if (MF) // TODO MF is unset when processing an ifunc, handle it better than
-          // this.
+  if (MF)
     for (const GlobalAlias *Alias : GOAliasMap[&MF->getFunction()])
       OutStreamer->emitLabel(getSymbol(Alias));
 
@@ -2901,17 +2900,13 @@ void PPCAIXAsmPrinter::emitFunctionDescriptor() {
 }
 
 void PPCAIXAsmPrinter::emitFunctionEntryLabel() {
-  if (!MF) { // TODO: MF is unset when processing an ifunc, handle it better.
-    if (!TM.getFunctionSections())
-      PPCAsmPrinter::emitFunctionEntryLabel();
-    return;
-  }
-
   // For functions without user defined section, it's not necessary to emit the
   // label when we have individual function in its own csect.
-  if (!TM.getFunctionSections() || MF->getFunction().hasSection())
+  if (!TM.getFunctionSections() || (MF && MF->getFunction().hasSection()))
     PPCAsmPrinter::emitFunctionEntryLabel();
 
+  if (!MF)
+    return;
   // Emit aliasing label for function entry point label.
   for (const GlobalAlias *Alias : GOAliasMap[&MF->getFunction()])
     OutStreamer->emitLabel(

>From 9b7b7963125267be3b1a5d2f87a3ec1bb893b10f Mon Sep 17 00:00:00 2001
From: Wael Yehia <wyehia at ca.ibm.com>
Date: Tue, 30 Sep 2025 21:40:07 -0400
Subject: [PATCH 14/16] refactor TOCRestoreNeededForCallToImplementation based
 on code review

---
 llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp | 114 ++++++++++++----------
 1 file changed, 65 insertions(+), 49 deletions(-)

diff --git a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
index a0eaa607f1864..9b5ead576424e 100644
--- a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
+++ b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
@@ -102,12 +102,14 @@ static cl::opt<bool> EnableSSPCanaryBitInTB(
     "aix-ssp-tb-bit", cl::init(false),
     cl::desc("Enable Passing SSP Canary info in Trackback on AIX"), cl::Hidden);
 
-static cl::list<std::string> IFuncLocal(
-    "ifunc-local",
-    llvm::cl::desc("a comma separated list of ifunc function names that are "
-                   "guaranteed to resolve to a module-local function. "
-                   "-ifunc-local=1 will apply to all ifuncs in the CU."),
-    cl::CommaSeparated, cl::Hidden);
+static cl::opt<bool> IFuncLocalIfProven(
+    "ifunc-local-if-proven", cl::init(false),
+    cl::desc("During ifunc lowering, the compiler assumes the resolver returns "
+             "dso-local functions and bails out if non-local functions are "
+             "detected; this flag flips the assumption: resolver returns "
+             "preemptible functions unless the compiler can prove all paths "
+             "return local functions."),
+    cl::Hidden);
 
 // Specialize DenseMapInfo to allow
 // std::pair<const MCSymbol *, PPCMCExpr::Specifier> in DenseMap.
@@ -3382,29 +3384,48 @@ void PPCAIXAsmPrinter::emitModuleCommandLines(Module &M) {
 }
 
 static bool TOCRestoreNeededForCallToImplementation(const GlobalIFunc &GI) {
+  enum class IsLocal { Unknown, True, False };
+  auto Combine = [](IsLocal LHS, IsLocal RHS) -> IsLocal {
+    if (LHS == IsLocal::False || RHS == IsLocal::False)
+      return IsLocal::False;
+    if (LHS == IsLocal::True && RHS == IsLocal::True)
+      return IsLocal::True;
+    return IsLocal::Unknown;
+  };
+
   // Query if the given function is local to the load module.
-  auto IsLocalFunc = [](const Function *F) {
+  auto IsLocalFunc = [](const Function *F) -> IsLocal {
     bool Result = F->isStrongDefinitionForLinker() && F->isDSOLocal();
     LLVM_DEBUG(dbgs() << F->getName() << " is " << (Result ? "local\n" : "not local\n"));
-    return Result;
+    return Result ? IsLocal::True : IsLocal::False;
   };
-  // Recursive walker that checks if all possible runtime values of the given
-  // llvm::Value are addresses of local functions.
-  std::function<bool(const Value*)> ValueIsALocalFunc = [&IsLocalFunc, &ValueIsALocalFunc](const Value *V) -> bool {
+
+  // Recursive walker that visits certain patterns that make up the given Value,
+  // and returns
+  //  - false if at least one non-local function was seen,
+  //  - otherwise, return unknown if some unrecognizable pattern was seen,
+  //  - otherwise, return true (which means only recognizable patterns were seen
+  //    and all possible values are local functions).
+  std::function<IsLocal(const Value *)> ValueIsALocalFunc =
+      [&IsLocalFunc, &Combine, &ValueIsALocalFunc](const Value *V) -> IsLocal {
     if (auto *F = dyn_cast<Function>(V))
       return IsLocalFunc(F);
     if (!isa<Instruction>(V))
-      return false;
+      return IsLocal::Unknown;
 
     auto *I = cast<Instruction>(V);
     // return isP9 ? foo_p9 : foo_default;
     if (auto *SI = dyn_cast<SelectInst>(I))
-      return ValueIsALocalFunc(SI->getTrueValue()) && ValueIsALocalFunc(SI->getFalseValue());
+      return Combine(ValueIsALocalFunc(SI->getTrueValue()),
+                     ValueIsALocalFunc(SI->getFalseValue()));
     else if (auto *PN = dyn_cast<PHINode>(I)) {
-      for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i)
-        if (!ValueIsALocalFunc(PN->getIncomingValue(i)))
-          return false;
-      return true;
+      IsLocal Res = IsLocal::True;
+      for (unsigned i = 0, e = PN->getNumIncomingValues(); i != e; ++i) {
+        Res = Combine(Res, ValueIsALocalFunc(PN->getIncomingValue(i)));
+        if (Res == IsLocal::False)
+          return Res;
+      }
+      return Res;
     }
     // @switch.table.resolve_foo = private unnamed_addr constant [3 x ptr] [ptr @foo_static, ptr @foo_hidden, ptr @foo_protected]
     // %switch.gep = getelementptr inbounds nuw ptr, ptr @switch.table, i64 %2
@@ -3414,50 +3435,45 @@ static bool TOCRestoreNeededForCallToImplementation(const GlobalIFunc &GI) {
         Op = cast<GEPOperator>(Op)->getPointerOperand();
 
       if (!isa<GlobalVariable>(Op))
-        return false;
+        return IsLocal::Unknown;
       auto *GV = dyn_cast<GlobalVariable>(Op);
       if (!GV->hasInitializer() || !isa<ConstantArray>(GV->getInitializer()))
-        return false;
-      auto *Initializer = cast<ConstantArray>(GV->getInitializer());
-      for (unsigned Idx = 0, End = Initializer->getNumOperands(); Idx != End; ++Idx)
-        if (!ValueIsALocalFunc(Initializer->getOperand(Idx)))
-          return false;
-      return true;
+        return IsLocal::Unknown;
+      auto *Init = cast<ConstantArray>(GV->getInitializer());
+      IsLocal Res = IsLocal::True;
+      for (unsigned Idx = 0, End = Init->getNumOperands(); Idx != End; ++Idx) {
+        Res = Combine(Res, ValueIsALocalFunc(Init->getOperand(Idx)));
+        if (Res == IsLocal::False)
+          return Res;
+      }
+      return Res;
     }
-    return false;
+    return IsLocal::Unknown;
   };
 
-  if (!IFuncLocal.empty()) {
-    ArrayRef<std::string> List = IFuncLocal;
-    // special case of -ifunc-local=1
-    if (List.size() == 1 && List[0].compare("1") == 0)
-      return false;
-    StringRef IFuncName = GI.getName();
-    if (any_of(List, [&](const std::string &Element) {
-          return Element.size() == IFuncName.size() &&
-                 Element.compare(IFuncName.data()) == 0;
-        }))
-      return false;
-  }
-
   auto *Resolver = GI.getResolverFunction();
   // If the resolver is preemptible then we cannot rely on its implementation.
-  if (!IsLocalFunc(Resolver))
+  if (IsLocalFunc(Resolver) == IsLocal::False && IFuncLocalIfProven)
     return true;
+
   // If one of the return values of the resolver function is not a
   // local function, then we have to conservatively do a TOC save/restore.
+  IsLocal Res = IsLocal::True;
   for (auto &BB : *Resolver) {
-    if (auto *Ret = dyn_cast<ReturnInst>(BB.getTerminator())) {
-      Value *RV = Ret->getReturnValue();
-      assert(RV);
-      if (!ValueIsALocalFunc(RV)) {
-        LLVM_DEBUG(dbgs() << "return value " << RV->getName() << " is not a local function\n");
-        return true;
-      }
-    }
+    if (!isa<ReturnInst>(BB.getTerminator()))
+      continue;
+    auto *Ret = cast<ReturnInst>(BB.getTerminator());
+    Value *RV = Ret->getReturnValue();
+    assert(RV);
+    Res = Combine(Res, ValueIsALocalFunc(RV));
+    if (Res == IsLocal::False)
+      break;
   }
-  // all return values where local functions, so no TOC save/restore needed.
-  return false;
+  // no TOC save/restore needed if either all functions were local or we're
+  // being optimistic and no preemptible functions were seen.
+  if (Res == IsLocal::True || (Res == IsLocal::Unknown && !IFuncLocalIfProven))
+    return false;
+  return true;
 }
 /*
  *   .csect .foo[PR],5

>From 4c7462d91f0c796faabe934dbdf5c6bc930bafa9 Mon Sep 17 00:00:00 2001
From: Wael Yehia <wyehia at ca.ibm.com>
Date: Tue, 7 Oct 2025 18:42:27 -0400
Subject: [PATCH 15/16] Sean's second code review

---
 llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp        | 3 ++-
 llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp | 3 ++-
 llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp         | 2 ++
 llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp | 4 ++--
 4 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
index 6a89b6403d651..514486867d580 100644
--- a/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/AsmPrinter.cpp
@@ -2426,7 +2426,8 @@ void AsmPrinter::emitGlobalAlias(const Module &M, const GlobalAlias &GA) {
 }
 
 void AsmPrinter::emitGlobalIFunc(Module &M, const GlobalIFunc &GI) {
-
+  assert(!TM.getTargetTriple().isOSBinFormatXCOFF() &&
+         "AIX has non-default implementation.");
   auto EmitLinkage = [&](MCSymbol *Sym) {
     if (GI.hasExternalLinkage() || !MAI->getWeakRefDirective())
       OutStreamer->emitSymbolAttribute(Sym, MCSA_Global);
diff --git a/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp b/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
index eb62e573637bd..cf2d2237ada37 100644
--- a/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
+++ b/llvm/lib/CodeGen/TargetLoweringObjectFileImpl.cpp
@@ -2392,7 +2392,8 @@ TargetLoweringObjectFileXCOFF::getTargetSymbol(const GlobalValue *GV,
                                                const TargetMachine &TM) const {
   // We always use a qualname symbol for a GV that represents
   // a declaration, a function descriptor, or a common symbol. An IFunc is
-  // lowered as a function, so it has an entry point and a descriptor.
+  // lowered as a special trampoline function which has an entry point and a
+  // descriptor.
   // If a GV represents a GlobalVariable and -fdata-sections is enabled, we
   // also return a qualname so that a label symbol could be avoided.
   // It is inherently ambiguous when the GO represents the address of a
diff --git a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
index 9b5ead576424e..c0c8f6c65cc2d 100644
--- a/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
+++ b/llvm/lib/Target/PowerPC/PPCAsmPrinter.cpp
@@ -2882,6 +2882,7 @@ void PPCAIXAsmPrinter::emitFunctionDescriptor() {
       static_cast<MCSymbolXCOFF *>(CurrentFnDescSym)->getRepresentedCsect());
 
   // Emit aliasing label for function descriptor csect.
+  // An Ifunc doesn't have a corresponding machine function.
   if (MF)
     for (const GlobalAlias *Alias : GOAliasMap[&MF->getFunction()])
       OutStreamer->emitLabel(getSymbol(Alias));
@@ -2907,6 +2908,7 @@ void PPCAIXAsmPrinter::emitFunctionEntryLabel() {
   if (!TM.getFunctionSections() || (MF && MF->getFunction().hasSection()))
     PPCAsmPrinter::emitFunctionEntryLabel();
 
+  // an ifunc does not have an associated MachineFunction
   if (!MF)
     return;
   // Emit aliasing label for function entry point label.
diff --git a/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp b/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp
index 7f0586799e585..ef65ab6a2dda5 100644
--- a/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp
+++ b/llvm/lib/Target/PowerPC/PPCPrepareIFuncsOnAIX.cpp
@@ -71,7 +71,7 @@ bool PPCPrepareIFuncsOnAIX::runOnModule(Module &M) {
   LLVMContext &Ctx = M.getContext();
   auto *PtrTy = PointerType::getUnqual(Ctx);
   StringRef IFuncUpdatePrefix = "__update_";
-  StringRef IFuncUpdateSectionName = "ifunc_sec";
+  StringRef IFuncUpdateSectionName = "__ifunc_sec";
   StructType *IFuncPairType = StructType::get(PtrTy, PtrTy);
 
   StringRef IFuncConstructorName = "__init_ifuncs";
@@ -83,7 +83,7 @@ bool PPCPrepareIFuncsOnAIX::runOnModule(Module &M) {
 
   for (GlobalIFunc &IFunc : M.ifuncs()) {
     NumIFuncs++;
-    LLVM_DEBUG(dbgs() << "doing ifunc " << IFunc.getName() << "\n");
+    LLVM_DEBUG(dbgs() << "expanding ifunc " << IFunc.getName() << "\n");
     // @__update_foo = private global { ptr @foo, ptr @foo_resolver },
     //   section "ifunc_sec"
     std::string Name = (Twine(IFuncUpdatePrefix) + IFunc.getName()).str();

>From d4533952024cb08cf9ad4970c805bef6be3c3ed8 Mon Sep 17 00:00:00 2001
From: Wael Yehia <wyehia at ca.ibm.com>
Date: Thu, 2 Oct 2025 11:57:50 -0400
Subject: [PATCH 16/16] ifunc test 1

---
 llvm/test/CodeGen/PowerPC/ifunc-prepare.ll | 34 ++++++++++++++++++++++
 1 file changed, 34 insertions(+)
 create mode 100644 llvm/test/CodeGen/PowerPC/ifunc-prepare.ll

diff --git a/llvm/test/CodeGen/PowerPC/ifunc-prepare.ll b/llvm/test/CodeGen/PowerPC/ifunc-prepare.ll
new file mode 100644
index 0000000000000..02bb804d399e3
--- /dev/null
+++ b/llvm/test/CodeGen/PowerPC/ifunc-prepare.ll
@@ -0,0 +1,34 @@
+; RUN: opt -ppc-prep-ifunc-aix -mtriple=powerpc64-ibm-aix-xcoff %s -S | FileCheck %s --check-prefixes=CHECK,CHECK64
+; RUN: opt -ppc-prep-ifunc-aix -mtriple=powerpc-ibm-aix-xcoff %s -S | FileCheck %s --check-prefixes=CHECK,CHECK32
+
+; CHECK64: @__update_foo = private global { ptr, ptr } { ptr @foo, ptr @foo.resolver }, section "__ifunc_sec", align 8, !associated ![[#INIT_IFUNC:]]
+; CHECK64: @__update_bar = private global { ptr, ptr } { ptr @bar, ptr @bar.resolver }, section "__ifunc_sec", align 8, !associated ![[#INIT_IFUNC]]
+; CHECK32: @__update_foo = private global { ptr, ptr } { ptr @foo, ptr @foo.resolver }, section "__ifunc_sec", align 4, !associated ![[#INIT_IFUNC:]]
+; CHECK32: @__update_bar = private global { ptr, ptr } { ptr @bar, ptr @bar.resolver }, section "__ifunc_sec", align 4, !associated ![[#INIT_IFUNC]]
+; CHECK: @foo = ifunc i32 (...), ptr @foo.resolver, !associated ![[#UPDATE_FOO:]]
+; CHECK: @bar = ifunc void (i32, i1), ptr @bar.resolver, !associated ![[#UPDATE_BAR:]]
+; CHECK: declare void @__init_ifuncs()
+; CHECK: ![[#INIT_IFUNC]] = !{ptr @__init_ifuncs}
+; CHECK: ![[#UPDATE_FOO]] = !{ptr @__update_foo}
+; CHECK: ![[#UPDATE_BAR]] = !{ptr @__update_bar}
+
+ at foo = ifunc i32 (...), ptr @foo.resolver
+ at bar = ifunc void (i32, i1), ptr @bar.resolver
+
+define hidden signext i32 @my_foo() {
+entry:
+  ret i32 4
+}
+
+define internal ptr @foo.resolver() {
+entry:
+  ret ptr @my_foo
+}
+
+declare void @my_bar(i32, i1)
+
+define ptr @bar.resolver() {
+entry:
+  ret ptr @my_bar
+}
+



More information about the llvm-commits mailing list