[llvm] [AArch64] Extend -fno-plt to SelectionDAG calls (PR #206659)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Jun 29 23:58:31 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-debuginfo
@llvm/pr-subscribers-backend-aarch64
Author: lfeng14
<details>
<summary>Changes</summary>
This fixes AArch64 SelectionDAG calls not honoring `-fno-plt`.
In the workload reported in #<!-- -->126176, Clang still emitted many PLT calls for
preemptible external function calls even when built with `-fno-plt`. As a
result, hot call paths retained unnecessary PLT indirection, leading to a
measurable performance regression.
With this patch, the workload improves by more than 16%. The number of PLT
entries in `libglobal_api.so`, measured with:
```sh
objdump -d libglobal_api.so | grep "@<!-- -->plt>:" | wc -l
```
drops from 25258 to 2274 after the fix. For comparison, GCC produces only 56
PLT entries for the same workload. Top-down analysis also shows the frontend
bottleneck decreases by about 5%.
The root cause is that AArch64 `classifyGlobalFunctionReference` only selected
GOT-based calls for functions marked `NonLazyBind`. Consequently, regular
external function calls lowered through SelectionDAG could still use PLT calls
even when `-fno-plt` set the `RtLibUseGOT` module flag.
This patch extends the same classification path to honor the `RtLibUseGOT`
module flag while preserving the existing `shouldAssumeDSOLocal` and
local-linkage exclusions. As a result, the default behavior is unchanged, and
calls known to be non-preemptible are not forced through the GOT. GOT-based
calls are selected only when the user explicitly requests `-fno-plt`.
The patch also adds TLI entries for `_Unwind_Resume`,
`__cxa_allocate_exception`, and `__cxa_begin_catch`, and marks
`_Unwind_Resume` as `nonlazybind` when `DwarfEHPrepare` creates it under
`RtLibUseGOT`.
Fixes #<!-- -->126176.
Testing:
* `ninja -C build-fno-plt-min -j1 llc FileCheck llvm-tli-checker AnalysisTests`
* `build-fno-plt-min/bin/llvm-lit -sv llvm/test/CodeGen/AArch64/fno-plt.ll`
* `build-fno-plt-min/unittests/Analysis/AnalysisTests --gtest_filter=TargetLibraryInfoTest.ValidProto`
---
Full diff: https://github.com/llvm/llvm-project/pull/206659.diff
6 Files Affected:
- (modified) llvm/include/llvm/Analysis/TargetLibraryInfo.td (+9)
- (modified) llvm/lib/CodeGen/DwarfEHPrepare.cpp (+6)
- (modified) llvm/lib/Target/AArch64/AArch64Subtarget.cpp (+5-1)
- (added) llvm/test/CodeGen/AArch64/fno-plt.ll (+80)
- (modified) llvm/test/tools/llvm-tli-checker/ps4-tli-check.yaml (+7-4)
- (modified) llvm/unittests/Analysis/TargetLibraryInfoTest.cpp (+3)
``````````diff
diff --git a/llvm/include/llvm/Analysis/TargetLibraryInfo.td b/llvm/include/llvm/Analysis/TargetLibraryInfo.td
index 6b17e70b87f55..0d4dc2ee84c6b 100644
--- a/llvm/include/llvm/Analysis/TargetLibraryInfo.td
+++ b/llvm/include/llvm/Analysis/TargetLibraryInfo.td
@@ -370,6 +370,15 @@ def cxa_guard_acquire : TargetLibCall<"__cxa_guard_acquire", Int, [Ptr]>;
/// void __cxa_guard_release(guard_t *guard);
def cxa_guard_release : TargetLibCall<"__cxa_guard_release", Void, [Ptr]>;
+/// void *_Unwind_Resume(_Unwind_Exception *);
+def unwind_resume : TargetLibCall<"_Unwind_Resume", Void, [Ptr]>;
+
+/// void *__cxa_allocate_exception(size_t thrown_size);
+def cxa_allocate_exception : TargetLibCall<"__cxa_allocate_exception", Ptr, [SizeT]>;
+
+/// void *__cxa_begin_catch(void *exception);
+def cxa_begin_catch : TargetLibCall<"__cxa_begin_catch", Ptr, [Ptr]>;
+
/// double __exp10_finite(double x);
def exp10_finite : TargetLibCall<"__exp10_finite", Dbl, [Dbl]>;
diff --git a/llvm/lib/CodeGen/DwarfEHPrepare.cpp b/llvm/lib/CodeGen/DwarfEHPrepare.cpp
index a4544a61561e5..467b6341dc507 100644
--- a/llvm/lib/CodeGen/DwarfEHPrepare.cpp
+++ b/llvm/lib/CodeGen/DwarfEHPrepare.cpp
@@ -236,6 +236,12 @@ bool DwarfEHPrepare::InsertUnwindResumeCalls() {
}
RewindFunction = F.getParent()->getOrInsertFunction(RewindName, FTy);
+ // When -fno-plt is enabled, mark _Unwind_Resume as non-lazy-bind to ensure
+ // it goes through GOT instead of PLT.
+ if (F.getParent()->getRtLibUseGOT())
+ if (Function *RewindFn = dyn_cast<Function>(RewindFunction.getCallee()))
+ RewindFn->addFnAttr(Attribute::NonLazyBind);
+
// Create the basic block where the _Unwind_Resume call will live.
if (ResumesLeft == 1) {
// Instead of creating a new BB and PHI node, just append the call to
diff --git a/llvm/lib/Target/AArch64/AArch64Subtarget.cpp b/llvm/lib/Target/AArch64/AArch64Subtarget.cpp
index 9ee3d0cc9e0ea..2e60d4d5d4256 100644
--- a/llvm/lib/Target/AArch64/AArch64Subtarget.cpp
+++ b/llvm/lib/Target/AArch64/AArch64Subtarget.cpp
@@ -483,9 +483,13 @@ unsigned AArch64Subtarget::classifyGlobalFunctionReference(
return AArch64II::MO_GOT;
// NonLazyBind goes via GOT unless we know it's available locally.
+ // When -fno-plt is enabled (getRtLibUseGOT), all external function calls
+ // go through GOT, matching the X86 backend behavior.
auto *F = dyn_cast<Function>(GV);
if ((!isTargetMachO() || MachOUseNonLazyBind) && F &&
- F->hasFnAttribute(Attribute::NonLazyBind) && !TM.shouldAssumeDSOLocal(GV))
+ (F->hasFnAttribute(Attribute::NonLazyBind) ||
+ F->getParent()->getRtLibUseGOT()) &&
+ !(TM.shouldAssumeDSOLocal(GV) || GV->hasLocalLinkage()))
return AArch64II::MO_GOT;
if (getTargetTriple().isOSWindows()) {
diff --git a/llvm/test/CodeGen/AArch64/fno-plt.ll b/llvm/test/CodeGen/AArch64/fno-plt.ll
new file mode 100644
index 0000000000000..aa2040fc55736
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/fno-plt.ll
@@ -0,0 +1,80 @@
+; RUN: llc -mtriple=aarch64-linux-gnu -relocation-model=pic %s -o - | FileCheck %s
+; RUN: llc -mtriple=aarch64-linux-gnu -relocation-model=pic %s -o - -fast-isel | FileCheck %s
+
+; Test that -fno-plt (RtLibUseGOT module flag) routes all external function
+; calls through GOT instead of PLT. This covers regular external functions,
+; functions with NonLazyBind attribute, and C++ exception handling library
+; functions (__cxa_allocate_exception, __cxa_begin_catch, _Unwind_Resume).
+
+declare void @external_func()
+
+define void @caller_external() nounwind {
+; CHECK-LABEL: caller_external:
+; CHECK: adrp x8, :got:external_func
+; CHECK: ldr x8, [x8, :got_lo12:external_func]
+; CHECK: blr x8
+ call void @external_func()
+ ret void
+}
+
+; Function with NonLazyBind attribute — should also use GOT.
+declare void @external_func_nonlazy() #0
+
+define void @caller_nonlazy() nounwind {
+; CHECK-LABEL: caller_nonlazy:
+; CHECK: adrp x8, :got:external_func_nonlazy
+; CHECK: ldr x8, [x8, :got_lo12:external_func_nonlazy]
+; CHECK: blr x8
+ call void @external_func_nonlazy()
+ ret void
+}
+
+; __cxa_allocate_exception — registered in RuntimeLibcalls.td, should use GOT.
+declare ptr @__cxa_allocate_exception(i64)
+
+define void @caller_throw() nounwind {
+; CHECK-LABEL: caller_throw:
+; CHECK: adrp [[ADRP:x[0-9]+]], :got:__cxa_allocate_exception
+; CHECK: ldr [[LD:x[0-9]+]], [[[ADRP]], :got_lo12:__cxa_allocate_exception]
+; CHECK: blr [[LD]]
+ %p = call ptr @__cxa_allocate_exception(i64 4)
+ ret void
+}
+
+; __cxa_begin_catch — registered in RuntimeLibcalls.td, should use GOT.
+declare ptr @__cxa_begin_catch(ptr)
+
+define void @caller_catch() nounwind {
+; CHECK-LABEL: caller_catch:
+; CHECK: adrp [[ADRP:x[0-9]+]], :got:__cxa_begin_catch
+; CHECK: ldr [[LD:x[0-9]+]], [[[ADRP]], :got_lo12:__cxa_begin_catch]
+; CHECK: blr [[LD]]
+ %p = call ptr @__cxa_begin_catch(ptr null)
+ ret void
+}
+
+; _Unwind_Resume — inserted by DwarfEHPrepare from a resume instruction.
+; With -fno-plt, DwarfEHPrepare marks it NonLazyBind, and
+; classifyGlobalFunctionReference routes it through GOT.
+declare i32 @__gxx_personality_v0(...)
+declare void @cleanup_func()
+
+define void @caller_resume() personality ptr @__gxx_personality_v0 {
+; CHECK-LABEL: caller_resume:
+; CHECK: adrp [[ADRP:x[0-9]+]], :got:_Unwind_Resume
+; CHECK: ldr [[LD:x[0-9]+]], [[[ADRP]], :got_lo12:_Unwind_Resume]
+; CHECK: blr [[LD]]
+entry:
+ invoke void @external_func() to label %cont unwind label %lpad
+cont:
+ ret void
+lpad:
+ %lpi = landingpad { ptr, i32 } cleanup
+ call void @cleanup_func()
+ resume { ptr, i32 } %lpi
+}
+
+attributes #0 = { nonlazybind }
+
+!llvm.module.flags = !{!0}
+!0 = !{i32 7, !"RtLibUseGOT", i32 1}
diff --git a/llvm/test/tools/llvm-tli-checker/ps4-tli-check.yaml b/llvm/test/tools/llvm-tli-checker/ps4-tli-check.yaml
index 63520a0b98089..4bafd558e6bba 100644
--- a/llvm/test/tools/llvm-tli-checker/ps4-tli-check.yaml
+++ b/llvm/test/tools/llvm-tli-checker/ps4-tli-check.yaml
@@ -32,7 +32,7 @@
# RUN: FileCheck %s --check-prefix=AVAIL --input-file %t3.txt
# RUN: FileCheck %s --check-prefix=UNAVAIL --input-file %t3.txt
#
-# CHECK: << Total TLI yes SDK no: 18
+# CHECK: << Total TLI yes SDK no: 21
# CHECK: >> Total TLI no SDK yes: 0
# CHECK: == Total TLI yes SDK yes: 277
#
@@ -40,13 +40,16 @@
# WRONG_DETAIL: >> TLI no SDK yes: '_ZdaPvj' aka operator delete[](void*, unsigned int)
# WRONG_DETAIL-COUNT-8: << TLI yes SDK no : {{.*}}__hot_cold_t
# WRONG_DETAIL-COUNT-4: << TLI yes SDK no : '__size_returning_new{{.*}}
+# WRONG_DETAIL: << TLI yes SDK no : '_Unwind_Resume'
+# WRONG_DETAIL: << TLI yes SDK no : '__cxa_allocate_exception'
+# WRONG_DETAIL: << TLI yes SDK no : '__cxa_begin_catch'
# WRONG_DETAIL: << TLI yes SDK no : 'fmaximum_num'
# WRONG_DETAIL: << TLI yes SDK no : 'fmaximum_numf'
# WRONG_DETAIL: << TLI yes SDK no : 'fmaximum_numl'
# WRONG_DETAIL: << TLI yes SDK no : 'fminimum_num'
# WRONG_DETAIL: << TLI yes SDK no : 'fminimum_numf'
# WRONG_DETAIL: << TLI yes SDK no : 'fminimum_numl'
-# WRONG_SUMMARY: << Total TLI yes SDK no: 19{{$}}
+# WRONG_SUMMARY: << Total TLI yes SDK no: 22{{$}}
# WRONG_SUMMARY: >> Total TLI no SDK yes: 1{{$}}
# WRONG_SUMMARY: == Total TLI yes SDK yes: 276
#
@@ -54,8 +57,8 @@
## the exact count first; the two directives should add up to that.
## Yes, this means additions to TLI will fail this test, but the argument
## to -COUNT can't be an expression.
-# AVAIL: TLI knows 528 symbols, 295 available
-# AVAIL-COUNT-295: {{^}} available
+# AVAIL: TLI knows 531 symbols, 298 available
+# AVAIL-COUNT-298: {{^}} available
# AVAIL-NOT: {{^}} available
# UNAVAIL-COUNT-233: not available
# UNAVAIL-NOT: not available
diff --git a/llvm/unittests/Analysis/TargetLibraryInfoTest.cpp b/llvm/unittests/Analysis/TargetLibraryInfoTest.cpp
index afeefd36a11e7..df0a402095d23 100644
--- a/llvm/unittests/Analysis/TargetLibraryInfoTest.cpp
+++ b/llvm/unittests/Analysis/TargetLibraryInfoTest.cpp
@@ -560,7 +560,10 @@ TEST_F(TargetLibraryInfoTest, ValidProto) {
"declare void @\"??_V at YAXPAXI@Z\"(ptr, i32)\n"
// These other functions were derived from the .def C declaration.
+ "declare void @_Unwind_Resume(ptr)\n"
+ "declare ptr @__cxa_allocate_exception(i64)\n"
"declare i32 @__cxa_atexit(ptr, ptr, ptr)\n"
+ "declare ptr @__cxa_begin_catch(ptr)\n"
"declare void @__cxa_guard_abort(ptr)\n"
"declare i32 @__cxa_guard_acquire(ptr)\n"
"declare void @__cxa_guard_release(ptr)\n"
``````````
</details>
https://github.com/llvm/llvm-project/pull/206659
More information about the llvm-commits
mailing list