Hello,
We've recently started using ifuncs in the x86(_64) FreeBSD kernel.
Currently lld will emit a PLT entry for each ifunc, so ifunc calls are
more expensive that those of regular functions. In our kernel, this
overhead isn't really necessary: if lld instead emits PC-relative
relocations for each ifunc call site, where each relocation references
a symbol of type GNU_IFUNC, then during boot we can resolve each
call site and apply the relocation before mapping the kernel text
read-only. Then, ifunc calls have the same overhead as regular function
calls.
To implement this optimization, I wrote an lld patch to add
"-z ifunc-noplt". When this option is specified, lld does not create
PLT entries for ifuncs and instead passes the existing PC-relative
relocation through to the output file. The patch is below; I tested it
with lld 7.0 and the patch applied without modifications to the sources
in trunk.
I'm wondering if such an option would be acceptable in upstream lld, and
whether anyone had comments on my implementation. The patch is lacking
tests, and I had some questions:
- How should "-z ifunc-noplt" interact with "-z text"? Should the
invoker be required to additionally specify "-z notext"?
- Could "-z ifunc-noplt" be subsumed by a more general mechanism which
tells lld not to apply constant relocations and instead pass them
through to the output file? I could imagine using such mechanism
to make it possible to dynamically enable retpoline at boot time.
It could also be useful for implementing static DTrace trace points.
Thanks,
-Mark
diff --git a/ELF/Config.h b/ELF/Config.h
index 5dc7f5321..b5a3d3266 100644
--- a/ELF/Config.h
+++ b/ELF/Config.h
@@ -182,6 +182,7 @@ struct Configuration {
bool ZCopyreloc;
bool ZExecstack;
bool ZHazardplt;
+ bool ZIfuncnoplt;
bool ZInitfirst;
bool ZKeepTextSectionPrefix;
bool ZNodelete;
diff --git a/ELF/Driver.cpp b/ELF/Driver.cpp
index aced1edca..e7896cedf 100644
--- a/ELF/Driver.cpp
+++ b/ELF/Driver.cpp
@@ -340,7 +340,8 @@ static bool getZFlag(opt::InputArgList &Args, StringRef K1, StringRef K2,
static bool isKnown(StringRef S) {
return S == "combreloc" || S == "copyreloc" || S == "defs" ||
- S == "execstack" || S == "hazardplt" || S == "initfirst" ||
+ S == "execstack" || S == "hazardplt" || S == "ifunc-noplt" ||
+ S == "initfirst" ||
S == "keep-text-section-prefix" || S == "lazy" || S == "muldefs" ||
S == "nocombreloc" || S == "nocopyreloc" || S == "nodelete" ||
S == "nodlopen" || S == "noexecstack" ||
@@ -834,6 +835,7 @@ void LinkerDriver::readConfigs(opt::InputArgList &Args) {
Config->ZCopyreloc = getZFlag(Args, "copyreloc", "nocopyreloc", true);
Config->ZExecstack = getZFlag(Args, "execstack", "noexecstack", false);
Config->ZHazardplt = hasZOption(Args, "hazardplt");
+ Config->ZIfuncnoplt = hasZOption(Args, "ifunc-noplt");
Config->ZInitfirst = hasZOption(Args, "initfirst");
Config->ZKeepTextSectionPrefix = getZFlag(
Args, "keep-text-section-prefix", "nokeep-text-section-prefix", false);
diff --git a/ELF/Relocations.cpp b/ELF/Relocations.cpp
index 8f60aa3d2..a54d87e43 100644
--- a/ELF/Relocations.cpp
+++ b/ELF/Relocations.cpp
@@ -361,6 +361,10 @@ static bool isStaticLinkTimeConstant(RelExpr E, RelType Type, const Symbol &Sym,
R_TLSLD_HINT>(E))
return true;
+ // The computation involves output from the ifunc resolver.
+ if (Sym.isGnuIFunc() && Config->ZIfuncnoplt)
+ return false;