[AIX] Implement the ifunc attribute. (#153049)

Currently, the AIX linker and loader do not provide a mechanism to
implement ifuncs similar to GNU_ifunc on ELF Linux.
On AIX, we will lower `__attribute__((ifunc("resolver"))` to the llvm
`ifunc` as other platforms do. The llvm `ifunc` in turn will get lowered
at late stages of the optimization pipeline to an AIX-specific
implementation. No special linkage or relocations are needed when
generating assembly/object output.

On AIX, a function `foo` has two symbols associated with it: a function
descriptor (`foo`) residing in the `.data` section, and an entry point
(`.foo`) residing in the `.text` section. The first field of the
descriptor is the address of the entry point. Typically, the address
field in the descriptor is initialized once: statically, at load time
(?), or at runtime if runtime linking is enabled.

Here we would like to use the address field in the descriptor to
implement the `ifunc` semantics. Specifically, the ifunc function will
become a stub that jumps to the entry point in the address field. A
constructor function is linked into every linkage module. The
constructor walks an array of `{descriptor, resolver}` pairs, calling
the resolver and saving the result in the address field in the
descriptor (thus setting `foo`'s descriptor to point to the resolved
version early during program runtime).

Known limitations:
- Due to bug #161576, which affects object generation path, you will
need either `-ffunction-sections` or `-fno-integrated-as` to generate a
correct/linkable object file.
- aliases to ifuncs are not supported, a testcase has been added and
marked XFAIL. I'm planning to address in a follow-up PR because it's not
important enough, IMHO, for this PR
- dead ifuncs in a CU that contains at least one live ifunc, will result
in all ifuncs being kept by the linker. The fix for this is common with
a similar problem we have with PGO. PR #159435 is trying to provide a
mechanism that will allow the ifunc and PGO implementations to avoid the
dead code retention at the link step.
- the resolver must return a function that is in the same DSO as the
ifunc; the compiler will try to detect if this condition is violated and
report it, but it cannot detect it in general. To be safe, all candidate
functions (returned by a particular resolver) must either be static or
have hidden/protected visibility. This is so that the ifunc stub doesn't
have to save and restore the TOC register r2. In future work, this case
will be supported and the requirement will be lifted.

---------

Co-authored-by: Wael Yehia <wyehia@ca.ibm.com>
This commit is contained in:
Wael Yehia 2026-02-03 14:15:16 -05:00 committed by GitHub
parent 9481902eff
commit e1f69ee8e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 966 additions and 56 deletions

View File

@ -6698,6 +6698,13 @@ Not all targets support this attribute:
with an indirect call. The function pointer is initialized by a constructor
that calls the resolver.
- Baremetal target supports it on AVR.
- AIX/XCOFF supports it via a compiler-only solution. An ifunc appears as a
regular function (has an entry point ``.foo[PR]`` and a function descriptor
``foo[DS]``). The entry point is a stub that branches to the function address
in the descriptor, and the descriptor is initialized via a constructor
function (``__init_ifuncs``) that is linked into every shared object and
executable. ``__init_ifuncs`` calls the resolver of each ifunc and stores the
result in the corresponding descriptor.
- Other targets currently do not support this attribute.
}];
}

View File

@ -1578,6 +1578,9 @@ public:
return true;
if (getTriple().getArch() == llvm::Triple::ArchType::avr)
return true;
if (getTriple().isOSAIX())
return getTriple().getOSMajorVersion() == 0 ||
getTriple().getOSVersion() >= VersionTuple(7, 2);
return getTriple().isOSBinFormatELF() &&
((getTriple().isOSLinux() && !getTriple().isMusl()) ||
getTriple().isOSFreeBSD());

View File

@ -147,25 +147,10 @@ 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))
// 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(options::OPT_mxcoff_build_id_EQ)) {

View File

@ -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) {}

View File

@ -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")));

View File

@ -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:]] {

View File

@ -1,6 +1,7 @@
// RUN: %clang_cc1 -triple x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-apple-macosx -emit-llvm -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple arm64-apple-macosx -emit-llvm -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple powerpc64-ibm-aix-xcoff -emit-llvm -o - %s | FileCheck %s
extern "C" {
__attribute__((used)) static void *resolve_foo() { return 0; }

View File

@ -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

View File

@ -843,6 +843,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

View File

@ -0,0 +1,38 @@
typedef void *Ptr;
typedef struct {
Ptr addr, toc, env;
} Descr;
typedef struct {
Descr *desc;
Ptr (*resolver)();
} IFUNCPair;
#define CONC2(A, B) A##B
#define CONC(A, B) CONC2(A, B)
#define IFUNC_SEC __ifunc_sec
#define IFUNC_SEC_STR "__ifunc_sec"
#define START_SEC CONC(__start_, IFUNC_SEC)
#define STOP_SEC CONC(__stop_, IFUNC_SEC)
// A zero-length entry in section "__ifunc_sec" to satisfy the START_SEC and
// STOP_SEC references in this file, when no user code has any ifuncs.
__attribute__((section(IFUNC_SEC_STR))) static int dummy_ifunc_sec[0];
extern IFUNCPair START_SEC, STOP_SEC;
__attribute__((constructor)) void __init_ifuncs() {
void *volatile ref = &dummy_ifunc_sec; // hack to keep dummy_ifunc_sec alive
// hack to prevent compiler from assuming START_SEC and STOP_SEC
// occupy different addresses.
IFUNCPair *volatile volatile_end = &STOP_SEC;
for (IFUNCPair *pair = &START_SEC, *end = volatile_end; pair != end; 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;
}
}

View File

@ -0,0 +1,23 @@
// We created a constructor function in compiler-rt/lib/builtins/ppc/init_ifuncs.c
// that reads an array contained in a certain named section of the object file.
// The compiler generates extra globals (one per ifunc) in that section.
// This test is to make sure the section name in the builtins library and the
// compiler match by checking the distance between the start and end of the
// section is "2*sizeof(void*)" because there's one ifunc in the entire program.
//
// REQUIRES: target={{.*aix.*}}
// RUN: %clang_builtins %s %librt -fno-integrated-as -o a.out
// RUN: llvm-nm -Xany --numeric-sort a.out | \
// RUN: FileCheck %s %if target-is-powerpc64 %{ --check-prefix=CHECK64 %} \
// RUN: %else %{ --check-prefix=CHECK %}
// CHECK: [[#%x,ADDR:]] W __start___ifunc_sec
// CHECK: [[#ADDR+8]] W __stop___ifunc_sec
// CHECK64: [[#%x,ADDR:]] W __start___ifunc_sec
// CHECK64: [[#ADDR+16]] W __stop___ifunc_sec
static int my_foo() { return 5; }
static void *foo_resolver() { return &my_foo; };
__attribute__((ifunc("foo_resolver"))) int foo();
int main() { return foo(); }

View File

@ -0,0 +1,15 @@
// RUN: %clang_pgogen %s -fno-integrated-as -o %t.out && %t.out
// candidates
__attribute__((visibility("hidden"))) int my_foo() { return 4; }
static int my_foo2() { return 5; }
// resolver
extern int x;
static void *foo_resolver() { return x ? &my_foo : &my_foo2; };
// ifunc
__attribute__((ifunc("foo_resolver"))) int foo();
int x = 1;
int main() { return foo() - 4; }

View File

@ -979,7 +979,7 @@ private:
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;

View File

@ -289,7 +289,7 @@ public:
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;

View File

@ -269,7 +269,7 @@ public:
/// 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;
}

View File

@ -2558,9 +2558,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())
OutStreamer->emitSymbolAttribute(Sym, MCSA_Global);
@ -2905,6 +2902,15 @@ bool AsmPrinter::doFinalization(Module &M) {
// sections after DWARF.
for (const auto &IFunc : M.ifuncs())
emitGlobalIFunc(M, IFunc);
if (TM.getTargetTriple().isOSBinFormatXCOFF() && hasDebugInfo()) {
// Emit section end. This is used to tell the debug line section where the
// end is for a text section if we don't use .loc to represent the debug
// line.
auto *Sec = OutContext.getObjectFileInfo()->getTextSection();
OutStreamer->switchSectionNoPrint(Sec);
MCSymbol *Sym = Sec->getEndSymbol(OutContext);
OutStreamer->emitLabel(Sym);
}
// Finalize debug and EH information.
for (auto &Handler : Handlers)

View File

@ -2392,7 +2392,9 @@ 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 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
@ -2411,6 +2413,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 *>(
@ -2683,7 +2690,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()))) &&
@ -2700,7 +2707,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(),
@ -2714,7 +2721,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(

View File

@ -401,7 +401,7 @@ void LTOModule::addDefinedFunctionSymbol(ModuleSymbolTable::Symbol Sym) {
}
auto *GV = cast<GlobalValue *>(Sym);
assert((isa<Function>(GV) ||
assert((isa<Function>(GV) || isa<GlobalIFunc>(GV) ||
(isa<GlobalAlias>(GV) &&
isa<Function>(cast<GlobalAlias>(GV)->getAliasee()))) &&
"Not function or function alias");
@ -611,6 +611,11 @@ void LTOModule::parseSymbols() {
continue;
}
if (getTargetTriple().isOSBinFormatXCOFF() && isa<GlobalIFunc>(GV)) {
addDefinedFunctionSymbol(Sym);
continue;
}
assert(isa<GlobalAlias>(GV));
if (isa<Function>(cast<GlobalAlias>(GV)->getAliasee()))

View File

@ -42,6 +42,7 @@ add_llvm_target(PowerPCCodeGen
PPCMachineScheduler.cpp
PPCMacroFusion.cpp
PPCMIPeephole.cpp
PPCPrepareIFuncsOnAIX.cpp
PPCRegisterInfo.cpp
PPCSelectionDAGInfo.cpp
PPCSubtarget.cpp

View File

@ -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 &);

View File

@ -48,6 +48,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"
@ -83,6 +84,7 @@
using namespace llvm;
using namespace llvm::XCOFF;
using namespace PatternMatch;
#define DEBUG_TYPE "asmprinter"
@ -101,6 +103,19 @@ 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::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);
// this flag is used for testing only as it might generate bad code.
static cl::opt<bool> IFuncWarnInsteadOfError("test-ifunc-warn-noerror",
cl::init(false), cl::ReallyHidden);
// 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
@ -308,6 +323,8 @@ public:
void emitModuleCommandLines(Module &M) override;
void emitRefMetadata(const GlobalObject *);
void emitGlobalIFunc(Module &M, const GlobalIFunc &GI) override;
};
} // end anonymous namespace
@ -779,6 +796,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.
@ -789,13 +816,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;
@ -2909,8 +2930,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));
// An Ifunc doesn't have a corresponding machine function.
if (MF)
for (const GlobalAlias *Alias : GOAliasMap[&MF->getFunction()])
OutStreamer->emitLabel(getSymbol(Alias));
// Emit function entry point address.
OutStreamer->emitValue(MCSymbolRefExpr::create(CurrentFnSym, OutContext),
@ -2930,11 +2953,14 @@ void PPCAIXAsmPrinter::emitFunctionDescriptor() {
void PPCAIXAsmPrinter::emitFunctionEntryLabel() {
// 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();
const Function *F = &MF->getFunction();
// an ifunc does not have an associated MachineFunction
if (!MF)
return;
const Function *F = &MF->getFunction();
// Emit aliasing label for function entry point label.
for (const GlobalAlias *Alias : GOAliasMap[F])
OutStreamer->emitLabel(
@ -3280,17 +3306,6 @@ void PPCAIXAsmPrinter::emitInstruction(const MachineInstr *MI) {
}
bool PPCAIXAsmPrinter::doFinalization(Module &M) {
// Do streamer related finalization for DWARF.
if (hasDebugInfo()) {
// Emit section end. This is used to tell the debug line section where the
// end is for a text section if we don't use .loc to represent the debug
// line.
auto *Sec = OutContext.getObjectFileInfo()->getTextSection();
OutStreamer->switchSectionNoPrint(Sec);
MCSymbol *Sym = Sec->getEndSymbol(OutContext);
OutStreamer->emitLabel(Sym);
}
for (MCSymbol *Sym : ExtSymSDNodeSymbols)
OutStreamer->emitSymbolAttribute(Sym, MCSA_Extern);
return PPCAsmPrinter::doFinalization(M);
@ -3385,12 +3400,15 @@ void PPCAIXAsmPrinter::emitTTypeReference(const GlobalValue *GV,
void PPCAIXAsmPrinter::emitRefMetadata(const GlobalObject *GO) {
SmallVector<MDNode *> MDs;
GO->getMetadata(LLVMContext::MD_implicit_ref, MDs);
assert(MDs.size() && "Expected asscoiated metadata nodes");
assert(MDs.size() && "Expected !implicit.ref metadata nodes");
for (const MDNode *MD : MDs) {
const ValueAsMetadata *VAM = cast<ValueAsMetadata>(MD->getOperand(0).get());
const GlobalValue *GV = cast<GlobalValue>(VAM->getValue());
MCSymbol *Referenced = TM.getSymbol(GV);
MCSymbol *Referenced =
isa<Function>(GV)
? getObjFileLowering().getFunctionEntryPointSymbol(GV, TM)
: TM.getSymbol(GV);
OutStreamer->emitXCOFFRefDirective(Referenced);
}
}
@ -3426,6 +3444,234 @@ void PPCAIXAsmPrinter::emitModuleCommandLines(Module &M) {
OutStreamer->emitXCOFFCInfoSym(".GCC.command.line", RSOS.str());
}
static bool TOCRestoreNeededForCallToImplementation(const GlobalIFunc &GI) {
enum class IsLocal {
Unknown, // Structure of the llvm::Value is not one of the recognizable
// structures, and so it's unknown if the llvm::Value is the
// address of a local function at runtime.
True, // We can statically prove that all runtime values of the
// llvm::Value is an address of a local function.
False // We can statically prove that one of the runtime values of the
// llvm::Value is the address of a non-local function; it could be
// the case that at runtime the non-local function is never
// selected but we don't care.
};
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) -> IsLocal {
bool Result = F->isDSOLocal();
LLVM_DEBUG(dbgs() << F->getName() << " is "
<< (Result ? "dso_local\n" : "not dso_local\n"));
return Result ? IsLocal::True : IsLocal::False;
};
// 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 IsLocal::Unknown;
auto *I = cast<Instruction>(V);
// return isP9 ? foo_p9 : foo_default;
if (auto *SI = dyn_cast<SelectInst>(I))
return Combine(ValueIsALocalFunc(SI->getTrueValue()),
ValueIsALocalFunc(SI->getFalseValue()));
else if (auto *PN = dyn_cast<PHINode>(I)) {
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;
}
// clang-format off
// @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,
// clang-format on
else if (auto *Op = getPointerOperand(I)) {
while (isa<GEPOperator>(Op))
Op = cast<GEPOperator>(Op)->getPointerOperand();
if (!isa<GlobalVariable>(Op))
return IsLocal::Unknown;
auto *GV = dyn_cast<GlobalVariable>(Op);
if (!GV->hasInitializer() || !isa<ConstantArray>(GV->getInitializer()))
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 IsLocal::Unknown;
};
auto *Resolver = GI.getResolverFunction();
// If the resolver is preemptible then we cannot rely on its implementation.
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 (!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;
}
// 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
* .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..foo_desc(2) # load foo's descriptor address
* lwz 11, 8(12) # load the env pointer (for non-C/C++ functions)
* 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
*/
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]
MCSectionXCOFF *FnDescSec = static_cast<MCSectionXCOFF *>(
getObjFileLowering().getSectionForFunctionDescriptor(&GI, TM));
FnDescSec->setAlignment(Align(IsPPC64 ? 8 : 4));
CurrentFnDescSym = FnDescSec->getQualNameSymbol();
CurrentFnSym = getObjFileLowering().getFunctionEntryPointSymbol(&GI, TM);
// Start codegen:
if (TM.getFunctionSections())
OutStreamer->switchSection(
static_cast<MCSymbolXCOFF *>(CurrentFnSym)->getRepresentedCsect());
else
OutStreamer->switchSection(getObjFileLowering().getTextSection());
if (GI.hasMetadata(LLVMContext::MD_implicit_ref))
emitRefMetadata(&GI);
// generate linkage for foo and .foo
emitLinkage(&GI, CurrentFnDescSym);
emitLinkage(&GI, CurrentFnSym);
// .align 4
Align Alignment(STI->getTargetLowering()->getMinFunctionAlignment());
emitAlignment(Alignment, nullptr);
// generate foo's function descriptor
emitFunctionDescriptor();
emitFunctionEntryLabel();
// generate the code for .foo now:
if (TOCRestoreNeededForCallToImplementation(GI)) {
Twine Msg = "unimplemented: TOC register save/restore needed for ifunc \"" +
Twine(GI.getName()) +
"\", because couldn't prove all candidates "
"are static or hidden/protected visibility definitions";
if (!IFuncWarnInsteadOfError)
reportFatalUsageError(Msg);
else
dbgs() << Msg << "\n";
}
auto FnDescTOCEntryType = getTOCEntryTypeForLinkage(GI.getLinkage());
auto *FnDescTOCEntrySym =
lookUpOrCreateTOCEntry(CurrentFnDescSym, FnDescTOCEntryType);
if (TM.getCodeModel() == CodeModel::Large) {
// addis 12, L..foo_desc@u(2)
// lwz 12, L..foo_desc@l(12)
auto *Exp_U = symbolWithSpecifier(FnDescTOCEntrySym, PPC::S_U);
OutStreamer->emitInstruction(MCInstBuilder(PPC::ADDIS)
.addReg(PPC::X12)
.addReg(PPC::X2)
.addExpr(Exp_U),
*Subtarget);
auto *Exp_L = symbolWithSpecifier(FnDescTOCEntrySym, PPC::S_L);
OutStreamer->emitInstruction(MCInstBuilder(IsPPC64 ? PPC::LD : PPC::LWZ)
.addReg(PPC::X12)
.addExpr(Exp_L)
.addReg(PPC::X12),
*Subtarget);
} else {
// lwz 12, L..foo_desc(2)
auto *Exp = MCSymbolRefExpr::create(FnDescTOCEntrySym, OutContext);
// Exp = getTOCEntryLoadingExprForXCOFF(MOSymbol, Exp, VK);
// TODO: do we need to uncomment this?
OutStreamer->emitInstruction(MCInstBuilder(IsPPC64 ? PPC::LD : PPC::LWZ)
.addReg(PPC::X12)
.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)
.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",

View File

@ -5438,7 +5438,6 @@ 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.");
return getAIXFuncEntryPointSymbolSDNode(GV);
}
return DAG.getTargetGlobalAddress(GV, dl, Callee.getValueType(), 0,

View File

@ -0,0 +1,114 @@
//===-- 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();
}
// 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.
//
// Since the global `__update_foo` is unreferenced, it's liveness needs to be
// associated to the liveness of ifunc `foo`
//
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 = cast<Function>(
M.getOrInsertFunction(IFuncConstructorName, IFuncConstructorFnType)
.getCallee());
for (GlobalIFunc &IFunc : M.ifuncs()) {
NumIFuncs++;
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();
auto *GV = new GlobalVariable(M, IFuncPairType, /*isConstant*/ false,
GlobalValue::PrivateLinkage, nullptr, Name);
GV->setAlignment(DL.getPointerPrefAlignment());
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));
// Liveness of __update_foo is dependent on liveness of ifunc foo.
IFunc.setMetadata(LLVMContext::MD_implicit_ref,
MDNode::get(Ctx, ValueAsMetadata::get(GV)));
// An implicit.ref creates linkage dependency, so make function foo require
// the constructor that calls each ifunc's resolver and saves the result in
// the ifunc's function descriptor.
IFunc.addMetadata(
LLVMContext::MD_implicit_ref,
*MDNode::get(Ctx, ValueAsMetadata::get(IFuncConstructorDecl)));
}
return true;
}

View File

@ -145,6 +145,7 @@ LLVMInitializePowerPCTarget() {
initializeGlobalISel(PR);
initializePPCCTRLoopsPass(PR);
initializePPCDAGToDAGISelLegacyPass(PR);
initializePPCPrepareIFuncsOnAIXPass(PR);
initializePPCLinuxAsmPrinterPass(PR);
initializePPCAIXAsmPrinterPass(PR);
}
@ -438,6 +439,9 @@ void PPCPassConfig::addIRPasses() {
addPass(createLICMPass());
}
if (TM->getTargetTriple().isOSAIX())
addPass(createPPCPrepareIFuncsOnAIXPass());
TargetPassConfig::addIRPasses();
}

View File

@ -0,0 +1,16 @@
; XFAIL: *
; RUN: llc -mtriple=powerpc64-ibm-aix-xcoff %s -o - | FileCheck %s
@foo_alias = alias i32 (...), ptr @my_foo
@foo = ifunc i32 (...), ptr @foo.resolver
define hidden i32 @my_foo() {
entry:
ret i32 4
}
define internal ptr @foo.resolver() {
entry:
ret ptr @my_foo
}

View File

@ -0,0 +1,33 @@
; RUN: llc -mtriple=powerpc64-ibm-aix-xcoff -verify-machineinstrs %s -o - | FileCheck %s
; RUN: llc -mtriple=powerpc-ibm-aix-xcoff -verify-machineinstrs %s -o - | FileCheck %s
; CHECK: .foo:
; CHECK: bctr
; CHECK-NEXT: L..sec_end0:
@foo = ifunc i32 (...), ptr @foo.resolver
define internal ptr @foo.resolver() #0 !dbg !7 {
entry:
ret ptr @my_foo2, !dbg !10
}
; Function Attrs: nounwind
define internal i32 @my_foo2() #1 !dbg !11 {
entry:
ret i32 5, !dbg !12
}
!llvm.dbg.cu = !{!0}
!llvm.module.flags = !{!2, !3}
!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 22.0.0git (git@github.ibm.com:compiler/llvm-project.git e963806df98c6d2e52573efbb8890ec72e5dd745)", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly, splitDebugInlining: false, nameTableKind: None)
!1 = !DIFile(filename: "t.c", directory: "/home/wyehia/Source/scripts.fmv/fmv/ifunc/proto2")
!2 = !{i32 7, !"Dwarf Version", i32 3}
!3 = !{i32 2, !"Debug Info Version", i32 3}
!7 = distinct !DISubprogram(name: "foo_resolver", scope: !1, file: !1, line: 7, type: !8, scopeLine: 7, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0, keyInstructions: true)
!8 = !DISubroutineType(types: !9)
!9 = !{}
!10 = !DILocation(line: 7, scope: !7, atomGroup: 1, atomRank: 1)
!11 = distinct !DISubprogram(name: "my_foo2", scope: !1, file: !1, line: 2, type: !8, scopeLine: 2, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition | DISPFlagOptimized, unit: !0, keyInstructions: true)
!12 = !DILocation(line: 2, scope: !11, atomGroup: 1, atomRank: 1)

View File

@ -0,0 +1,89 @@
; RUN: llc -mtriple=powerpc64-ibm-aix-xcoff %s --filetype=obj -o %t.o
; RUN: llvm-objdump -D -r --symbol-description %t.o | FileCheck %s --check-prefixes=CHECK,CHECK-NO-FS
; RUN: llc -mtriple=powerpc64-ibm-aix-xcoff %s --function-sections --filetype=obj -o %t.o
; RUN: llvm-objdump -D -r --symbol-description %t.o | FileCheck %s --check-prefixes=CHECK,CHECK-FS
; RUN: llc -mtriple=powerpc64-ibm-aix-xcoff --function-sections --code-model=large --filetype=obj %s -o %t.o
; RUN: llvm-objdump -D -r --symbol-description %t.o | FileCheck %s --check-prefixes=CHECK-LARGE,CHECK-LARGE-FS
; RUN: llc -mtriple=powerpc64-ibm-aix-xcoff --code-model=large --filetype=obj %s -o %t.o
; RUN: llvm-objdump -D -r --symbol-description %t.o | FileCheck %s --check-prefixes=CHECK-LARGE
; CHECK: Disassembly of section .text:
;;;; R_REF relocations associating .foo to (1) the __init_ifuncs constructor
;;;; and (2) the __update_foo variable.
; CHECK-NO-FS: 0000000000000000: R_REF {{.*}} __ifunc_sec[RW]
; CHECK-NO-FS-NEXT: 0000000000000000: R_REF {{.*}} .__init_ifuncs[PR]
;;;; .foo ifunc stub
; CHECK-NO-FS: .foo:
; CHECK-NO-FS-NEXT: ld 12, 8(2)
; CHECK-NO-FS-NEXT: R_TOC {{.*}} foo[TC]
; CHECK-NO-FS-NEXT: ld 11, 16(12)
; CHECK-NO-FS-NEXT: ld 12, 0(12)
; CHECK-NO-FS-NEXT: mtctr 12
; CHECK-NO-FS-NEXT: bctr
; CHECK-FS: .foo[PR]:
; CHECK-FS-NEXT: ld 12, 8(2)
; CHECK-FS-NEXT: R_REF {{.*}} __ifunc_sec[RW]
; CHECK-FS-NEXT: R_REF {{.*}} .__init_ifuncs[PR]
; CHECK-FS-NEXT: R_TOC {{.*}} foo[TC]
; CHECK-FS-NEXT: ld 11, 16(12)
; CHECK-FS-NEXT: ld 12, 0(12)
; CHECK-FS-NEXT: mtctr 12
; CHECK-FS-NEXT: bctr
; CHECK-LARGE: {{\.foo|\.foo\[PR\]}}:
; CHECK-LARGE-NEXT: addis 12, 2, 0
; CHECK-LARGE-FS-NEXT: R_REF {{.*}} __ifunc_sec[RW]
; CHECK-LARGE-FS-NEXT: R_REF {{.*}} .__init_ifuncs[PR]
; CHECK-LARGE-NEXT: R_TOCU {{.*}} foo[TE]
; CHECK-LARGE-NEXT: ld 12, 8(12)
; CHECK-LARGE-NEXT: R_TOCL {{.*}} foo[TE]
; CHECK-LARGE-NEXT: ld 11, 16(12)
; CHECK-LARGE-NEXT: ld 12, 0(12)
; CHECK-LARGE-NEXT: mtctr 12
; CHECK-LARGE-NEXT: bctr
; CHECK: Disassembly of section .data:
;;;; section __ifunc_sec holding the [foo:foo_resolver] pairs
;;;; @__update_foo = private global { ptr, ptr } { ptr @foo, ptr @foo.resolver }, section "__ifunc_sec", align 8
; CHECK: {{.*}} __ifunc_sec[RW]:
; CHECK-NEXT: 00 00 00 00 <unknown>
; CHECK-NEXT: R_POS {{.*}} foo[DS]
; CHECK-NEXT: {{.*}} <unknown>
; CHECK-NEXT: 00 00 00 00 <unknown>
; CHECK-NEXT: R_POS {{.*}} foo.resolver[DS]
;;;; A function descriptor for foo
; CHECK: {{.*}} foo[DS]:
; CHECK-NEXT: 00 00 00 00 <unknown>
; CHECK-NEXT: R_POS {{.*}} .foo
; CHECK-NEXT: {{.*}} <unknown>
; CHECK-NEXT: 00 00 00 00 <unknown>
; CHECK-NEXT: R_POS {{.*}} TOC[TC0]
;;;; foo's TOC
; CHECK: {{.*}} foo[TC]:
; CHECK-NEXT: 00 00 00 00 <unknown>
; CHECK-NEXT: R_POS {{.*}} foo[DS]
; CHECK-NEXT: {{.*}} <unknown>
; CHECK-LARGE: {{.*}} foo[TE]:
; CHECK-LARGE-NEXT: <unknown>
; CHECK-LARGE-NEXT: R_POS {{.*}} foo[DS]
; CHECK-LARGE-NEXT: <unknown>
@foo = ifunc i32 (...), ptr @foo.resolver
define hidden i32 @my_foo() {
entry:
ret i32 4
}
define internal ptr @foo.resolver() {
entry:
ret ptr @my_foo
}

View File

@ -0,0 +1,94 @@
; This testcase is for testing the negative return values of the
; TOCRestoreNeededForCallToImplementation query.
; RUN: llc < %s -mtriple=powerpc64-ibm-aix-xcoff -test-ifunc-warn-noerror -filetype=obj -o /dev/null 2>&1 | FileCheck %s
; CHECK: TOC register save/restore needed for ifunc "foo_ext_decl_ifunc"
; CHECK: TOC register save/restore needed for ifunc "foo_ext_default_decl_ifunc"
; CHECK: TOC register save/restore needed for ifunc "foo_ext_weak_decl_ifunc"
; CHECK: TOC register save/restore needed for ifunc "foo_ext_hidden_weak_decl_ifunc"
; CHECK: TOC register save/restore needed for ifunc "foo_ext_protected_weak_decl_ifunc"
; CHECK: TOC register save/restore needed for ifunc "foo_ext_default_weak_decl_ifunc"
; CHECK: TOC register save/restore needed for ifunc "foo_ext_def_ifunc"
; CHECK: TOC register save/restore needed for ifunc "foo_ext_default_def_ifunc"
; CHECK: TOC register save/restore needed for ifunc "foo_ext_weak_def_ifunc"
; CHECK: TOC register save/restore needed for ifunc "foo_ext_default_weak_def_ifunc"
define void @foo_ext_def() {
entry:
ret void
}
define void @foo_ext_default_def() {
entry:
ret void
}
define weak void @foo_ext_default_weak_def() {
entry:
ret void
}
define weak void @foo_ext_weak_def() {
entry:
ret void
}
declare void @foo_ext_decl(...)
declare void @foo_ext_default_decl(...)
declare extern_weak void @foo_ext_weak_decl(...)
declare extern_weak hidden void @foo_ext_hidden_weak_decl(...)
declare extern_weak protected void @foo_ext_protected_weak_decl(...)
declare extern_weak void @foo_ext_default_weak_decl(...)
define internal ptr @foo_ext_decl_resolver() {
entry:
ret ptr @foo_ext_decl
}
define internal ptr @foo_ext_default_decl_resolver() {
entry:
ret ptr @foo_ext_default_decl
}
define internal ptr @foo_ext_weak_decl_resolver() {
entry:
ret ptr @foo_ext_weak_decl
}
define internal ptr @foo_ext_hidden_weak_decl_resolver() {
entry:
ret ptr @foo_ext_hidden_weak_decl
}
define internal ptr @foo_ext_protected_weak_decl_resolver() {
entry:
ret ptr @foo_ext_protected_weak_decl
}
define internal ptr @foo_ext_default_weak_decl_resolver() {
entry:
ret ptr @foo_ext_default_weak_decl
}
define internal ptr @foo_ext_def_resolver() {
entry:
ret ptr @foo_ext_def
}
define internal ptr @foo_ext_default_def_resolver() {
entry:
ret ptr @foo_ext_default_def
}
define internal ptr @foo_ext_weak_def_resolver() {
entry:
ret ptr @foo_ext_weak_def
}
define internal ptr @foo_ext_default_weak_def_resolver() {
entry:
ret ptr @foo_ext_default_weak_def
}
@foo_ext_decl_ifunc = ifunc i32 (...), ptr @foo_ext_decl_resolver
@foo_ext_default_decl_ifunc = ifunc i32 (...), ptr @foo_ext_default_decl_resolver
@foo_ext_weak_decl_ifunc = ifunc i32 (...), ptr @foo_ext_weak_decl_resolver
@foo_ext_hidden_weak_decl_ifunc = ifunc i32 (...), ptr @foo_ext_hidden_weak_decl_resolver
@foo_ext_protected_weak_decl_ifunc = ifunc i32 (...), ptr @foo_ext_protected_weak_decl_resolver
@foo_ext_default_weak_decl_ifunc = ifunc i32 (...), ptr @foo_ext_default_weak_decl_resolver
@foo_ext_def_ifunc = ifunc i32 (...), ptr @foo_ext_def_resolver
@foo_ext_default_def_ifunc = ifunc i32 (...), ptr @foo_ext_default_def_resolver
@foo_ext_weak_def_ifunc = ifunc i32 (...), ptr @foo_ext_weak_def_resolver
@foo_ext_default_weak_def_ifunc = ifunc i32 (...), ptr @foo_ext_default_weak_def_resolver

View File

@ -0,0 +1,77 @@
; This testcase is for testing the positive return values of the
; TOCRestoreNeededForCallToImplementation query, specifically the type of
; functions that are considered DSO local on AIX.
; RUN: llc < %s -mtriple=powerpc64-ibm-aix-xcoff --function-sections -filetype=obj -o /dev/null -debug-only=asmprinter 2>&1 | FileCheck %s
; RUN: llc < %s -mtriple=powerpc-ibm-aix-xcoff --function-sections -filetype=obj -o /dev/null -debug-only=asmprinter 2>&1 | FileCheck %s
; RUN: llc < %s -mtriple=powerpc64-ibm-aix-xcoff -ifunc-local-if-proven=1 -o /dev/null -debug-only=asmprinter 2>&1 | FileCheck %s
; CHECK: foo_ext_hidden_decl is dso_local
; CHECK: foo_ext_hidden_def is dso_local
; CHECK: foo_ext_hidden_weak_def is dso_local
; CHECK: foo_ext_protected_decl is dso_local
; CHECK: foo_ext_protected_def is dso_local
; CHECK: foo_ext_protected_weak_def is dso_local
; CHECK: foo_static is dso_local
; The following decls/defs are dso_local in the IR, and it matches the behaviour on AIX in practice
; (1) a hidden/protected declaration should have a matching definition in the same shared object,
; with the matching visibility. So the definition is not interposable due to hidden/protected.
; (2) a hidden/protected definition is not interposable.
; (3) attribute weak has no effect on interposition, and if a strong definition in the same shared object
; exists, then it's a user error to have that definition have conflicting visibility.
; In practice, on AIX the linker will silently pick the strong definition and keep it's visibility
; ignoring what's on the weak definition.
; On Linux, both ld and lld pick the strong definition but give it the most restrictive visibility based
; on the candidates available (so a weak hidden and a strong default will yield a hidden strong)
;
declare hidden void @foo_ext_hidden_decl(...) ; (1)
declare protected void @foo_ext_protected_decl(...) ; (1)
define hidden void @foo_ext_hidden_def() { ; (2)
entry:
ret void
}
define protected void @foo_ext_protected_def() { ; (2)
entry:
ret void
}
define weak hidden void @foo_ext_hidden_weak_def() { ; (3)
entry:
ret void
}
define weak protected void @foo_ext_protected_weak_def() { ; (3)
entry:
ret void
}
define internal void @foo_static() {
entry:
ret void
}
@foo = ifunc void (...), ptr @resolve_foo
@x = global i32 0, align 4
@switch.table.bar = private unnamed_addr constant [6 x ptr] [ptr @foo_ext_hidden_decl, ptr @foo_ext_hidden_def, ptr @foo_ext_hidden_weak_def, ptr @foo_ext_protected_decl, ptr @foo_ext_protected_def, ptr @foo_ext_protected_weak_def], align 4
define internal nonnull ptr @resolve_foo() {
entry:
%x = load i32, ptr @x, align 4
%0 = icmp ult i32 %x, 6
br i1 %0, label %switch.lookup, label %return
switch.lookup: ; preds = %entry
%switch.gep = getelementptr inbounds nuw ptr, ptr @switch.table.bar, i32 %x
%switch.load = load ptr, ptr %switch.gep, align 4
br label %return
return: ; preds = %entry, %switch.lookup
%retval.0 = phi ptr [ %switch.load, %switch.lookup ], [ @foo_static, %entry ]
ret ptr %retval.0
}

View File

@ -0,0 +1,75 @@
; RUN: llc -mtriple=powerpc64-ibm-aix-xcoff %s -o - | FileCheck %s --check-prefixes=COMMON,NO-FUNCSECT -DALIGN=3 -DPTR_SIZE=8 -DLOAD=ld -DOFF=16
; RUN: llc -mtriple=powerpc-ibm-aix-xcoff %s -o - | FileCheck %s --check-prefixes=COMMON,NO-FUNCSECT -DALIGN=2 -DPTR_SIZE=4 -DLOAD=lwz -DOFF=8
; RUN: llc -mtriple=powerpc64-ibm-aix-xcoff --function-sections %s -o - | FileCheck %s --check-prefixes=COMMON,FUNCSECT -DALIGN=3 -DPTR_SIZE=8 -DLOAD=ld -DOFF=16
; RUN: llc -mtriple=powerpc-ibm-aix-xcoff --function-sections %s -o - | FileCheck %s --check-prefixes=COMMON,FUNCSECT -DALIGN=2 -DPTR_SIZE=4 -DLOAD=lwz -DOFF=8
; RUN: llc -mtriple=powerpc64-ibm-aix-xcoff --function-sections --code-model=large %s -o - | FileCheck %s --check-prefixes=LARGE -DALIGN=3 -DPTR_SIZE=8 -DLOAD=ld -DOFF=16
; RUN: llc -mtriple=powerpc-ibm-aix-xcoff --function-sections --code-model=large %s -o - | FileCheck %s --check-prefixes=LARGE -DALIGN=2 -DPTR_SIZE=4 -DLOAD=lwz -DOFF=8
;;;; section __ifunc_sec holding the [foo:foo_resolver] pairs
; COMMON: .csect __ifunc_sec[RW],2
; COMMON-NEXT: .align [[ALIGN]]
; COMMON-NEXT: L..__update_foo:
; COMMON-NEXT: .vbyte [[PTR_SIZE]], foo[DS]
; COMMON-NEXT: .vbyte [[PTR_SIZE]], foo.resolver[DS]
;;;; forward declare the __init_ifuncs constructor
; COMMON-NEXT: .extern .__init_ifuncs[PR]
;;;; declare foo[DS] and .foo[PR]
; FUNCSECT-NEXT: .csect .foo[PR],5
; FUNCSECT-NEXT: .ref L..__update_foo
; FUNCSECT-NEXT: .ref .__init_ifuncs[PR]
; FUNCSECT-NEXT: .globl foo[DS]
; FUNCSECT-NEXT: .globl .foo[PR]
; FUNCSECT-NEXT: .align 2
; NO-FUNCSECT-NEXT: .csect ..text..[PR],5
; NO-FUNCSECT-NEXT: .ref L..__update_foo
; NO-FUNCSECT-NEXT: .ref .__init_ifuncs[PR]
; NO-FUNCSECT-NEXT: .globl foo[DS]
; NO-FUNCSECT-NEXT: .globl .foo
; NO-FUNCSECT-NEXT: .align 2
;;;; define foo's descriptor
; COMMON-NEXT: .csect foo[DS],[[ALIGN]]
; FUNCSECT-NEXT: .vbyte [[PTR_SIZE]], .foo[PR]
; NO-FUNCSECT-NEXT: .vbyte [[PTR_SIZE]], .foo
; COMMON-NEXT: .vbyte [[PTR_SIZE]], TOC[TC0]
; COMMON-NEXT: .vbyte [[PTR_SIZE]], 0
;;;; emit foo's body
; FUNCSECT-NEXT: .csect .foo[PR],5
; NO-FUNCSECT-NEXT: .csect ..text..[PR],5
; NO-FUNCSECT-NEXT: .foo:
; COMMON-NEXT: [[LOAD]] 12, [[FOO_TOC:.*]](2)
; COMMON-NEXT: [[LOAD]] 11, [[OFF]](12)
; COMMON-NEXT: [[LOAD]] 12, 0(12)
; COMMON-NEXT: mtctr 12
; COMMON-NEXT: bctr
; -mcmodel=large:
; LARGE: .csect .foo[PR],5
; LARGE: addis 12, [[FOO_TOC:.*]]@u(2)
; LARGE-NEXT: [[LOAD]] 12, [[FOO_TOC]]@l(12)
; LARGE-NEXT: [[LOAD]] 11, [[OFF]](12)
; LARGE-NEXT: [[LOAD]] 12, 0(12)
;;;; foo's TOC entry
; COMMON: [[FOO_TOC]]:
; COMMON-NEXT: .tc foo[TC],foo[DS]
; LARGE: [[FOO_TOC]]:
; LARGE-NEXT: .tc foo[TE],foo[DS]
@foo = ifunc i32 (...), ptr @foo.resolver
define hidden i32 @my_foo() {
entry:
ret i32 4
}
define internal ptr @foo.resolver() {
entry:
ret ptr @my_foo
}

View File

@ -0,0 +1,32 @@
; RUN: opt -ppc-prep-ifunc-aix -mtriple=powerpc64-ibm-aix-xcoff %s -S | FileCheck %s -DALIGN=8
; RUN: opt -ppc-prep-ifunc-aix -mtriple=powerpc-ibm-aix-xcoff %s -S | FileCheck %s -DALIGN=4
; CHECK: @__update_foo = private global { ptr, ptr } { ptr @foo, ptr @foo.resolver }, section "__ifunc_sec", align [[ALIGN]]
; CHECK: @__update_bar = private global { ptr, ptr } { ptr @bar, ptr @bar.resolver }, section "__ifunc_sec", align [[ALIGN]]
; CHECK: @foo = ifunc i32 (...), ptr @foo.resolver, !implicit.ref ![[#UPDATE_FOO:]], !implicit.ref ![[#INIT_IFUNC:]]
; CHECK: @bar = ifunc void (i32, i1), ptr @bar.resolver, !implicit.ref ![[#UPDATE_BAR:]], !implicit.ref ![[#INIT_IFUNC]]
; CHECK: declare void @__init_ifuncs()
; CHECK: ![[#UPDATE_FOO]] = !{ptr @__update_foo}
; CHECK: ![[#INIT_IFUNC]] = !{ptr @__init_ifuncs}
; 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
}

View File

@ -0,0 +1,18 @@
; RUN: llvm-as < %s > %t
; RUN: llvm-lto -list-symbols-only %t | FileCheck %s
; CHECK: foo { function defined default }
target triple = "powerpc-ibm-aix7.2.0.0"
@foo = ifunc i32 (...), ptr @foo.resolver
define internal ptr @foo.resolver() {
entry:
ret ptr @my_foo2
}
define internal i32 @my_foo2() {
entry:
ret i32 5
}

View File

@ -90,6 +90,7 @@ static_library("LLVMPowerPCCodeGen") {
"PPCMachineScheduler.cpp",
"PPCMacroFusion.cpp",
"PPCPreEmitPeephole.cpp",
"PPCPrepareIFuncsOnAIX.cpp",
"PPCReduceCRLogicals.cpp",
"PPCRegisterInfo.cpp",
"PPCSelectionDAGInfo.cpp",