[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