[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