This is an archive of the discontinued LLVM Phabricator instance.

[ThinLTO] Mark callers of local ifunc not eligible for import
ClosedPublic

Authored by MaskRay on Aug 27 2023, 9:24 PM.

Details

Summary

Fix https://github.com/llvm/llvm-project/issues/58740
The target_clones attribute results in ifunc on eligible targets
(Linux glibc/Android or FreeBSD). If the function has internal linkage,
we will get an internal linkage ifunc.

__attribute__((target_clones("popcnt", "default")))
static int foo(int n) { return __builtin_popcount(n); }
int use(int n) { return foo(n); }

@foo.ifunc = internal ifunc i32 (i32), ptr @foo.resolver
define internal nonnull ptr @foo.resolver() comdat {
; local linkage comdat is another issue that should be fixed
  ...
  select i1 %.not, ptr @foo.default.1, ptr @foo.popcnt.0
  ...
}
define internal i32 @foo.default.1(i32 noundef %n)

ifuncs are not included in module summaries, so LTO doesn't know the
local linkage foo.default.1 referenced by foo.resolver
should be promoted. If a caller of foo (e.g. use) is imported,
the local linkage foo.resolver will be cloned as a definition
(IRLinker::shouldLink), leading to linker errors.

ld.lld: error: undefined hidden symbol: foo.default.1.llvm.8017227050314953235
>>> referenced by bar.c
>>>               lto.tmp:(foo.ifunc)

As a simple fix, just mark use as not eligible for import. Non-local
linkage ifuncs do not have the problem, because they are not imported,
and not cloned when a caller is imported.


https://reviews.llvm.org/D82745 contains a more involved fix, though the
original bug it intended to fix
(https://github.com/llvm/llvm-project/issues/45833) now works.

Note: importing ifunc is tricky.
If we import an ifunc, we need to make sure the resolver and the
implementation are in the translation unit, as required by
https://sourceware.org/glibc/wiki/GNU_IFUNC

Requirement (a): Resolver must be defined in the same translation unit as the implementations.

This is infeasible if the implementation is changed to
available_externally.

In addition, the imported ifunc may be referenced by two translation
units. This doesn't work with PowerPC32 -msecure-plt
(https://maskray.me/blog/2021-01-18-gnu-indirect-function).
At the very least, every referencing translation unit needs one extra
IRELATIVE dynamic relocation.

At least for the local linkage ifunc case, it doesn't have much use
outside of target_clones, as a global pointer is usually a better
replacement
(https://maskray.me/blog/2021-01-18-gnu-indirect-function#alternative).

I think ifuncs just have too many pitfalls to design more IR features
around it to optimize them.

Diff Detail

Event Timeline

MaskRay created this revision.Aug 27 2023, 9:24 PM
Herald added a project: Restricted Project. · View Herald TranscriptAug 27 2023, 9:24 PM
MaskRay requested review of this revision.Aug 27 2023, 9:24 PM
Herald added a project: Restricted Project. · View Herald TranscriptAug 27 2023, 9:24 PM
yota9 added a comment.Aug 28 2023, 2:47 AM

Hello! Just wanna to add that if you have interest in my patch - fill free to commander the change, modify and submit it as your own patch, I have absolutely no objections on it. Although ifuncs are quite rare so I think not importing such functions is good-enough solution. Thanks! :)

tejohnson accepted this revision.Aug 28 2023, 2:01 PM

lgtm except one comment in the test needs clarification.

llvm/test/ThinLTO/X86/ifunc-import.ll
14

This seems to be specifically about @foo.ifunc, not @foo.ifunc2 which is not internal and for which we can import a reference.

This revision is now accepted and ready to land.Aug 28 2023, 2:01 PM
MaskRay marked an inline comment as done.Aug 28 2023, 3:16 PM

Hello! Just wanna to add that if you have interest in my patch - fill free to commander the change, modify and submit it as your own patch, I have absolutely no objections on it. Although ifuncs are quite rare so I think not importing such functions is good-enough solution. Thanks! :)

Thank you for the offer! My current thought is that we probably don't want to analyze GlobalIFunc. Unlike GlobalAlias whose aliasee is easy to analyze, a GlobalIFunc may have multiple implementations and the implementations are selected by a resolver.
It may be too much for the compiler to analyze... We have done a few special cases like D129009 and D150262. With this patch, it seems that we are good enough for known cases...

My concern with a more powerful GlobalIndirectSummary is when we do future optimizations on GlobalAlias, we'd be tempted to think whether that works with GlobalIFunc. Since ifunc semantics are so special, there will be ongoing cognitive complexity...

llvm/test/ThinLTO/X86/ifunc-import.ll
14

Thanks for noticing the inaccurate comment. Fixed.

MaskRay updated this revision to Diff 554073.Aug 28 2023, 3:17 PM
MaskRay marked an inline comment as done.

clarify a comment

This revision was landed with ongoing or failed builds.Aug 29 2023, 12:26 PM
This revision was automatically updated to reflect the committed changes.