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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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} + +@foo = ifunc i32 (...), ptr @foo.resolver +@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 +} + _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
