[llvm] [WholeProgramDevirt] Add check for AvailableExternal and give up devirt (PR #143468)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Jun 9 19:28:32 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-llvm-transforms
Author: Tianle Liu (tianleliu)
<details>
<summary>Changes</summary>
…devirtualization.
When a customer class inherits from a libc++ class, and is built with
"-flto -fwhole-program-vtables -static-libstdc++ \
-Wl,-plugin-opt=-whole-program-visibility", the libc++ class's vtable
is available_externally, meanwhile the customer class vtable is private.
And both of them are !vcall_visibility == Linkage Unit. In this case, icall.branch.funnel might be generated.
But the icall.branch.funnel would cause crash in LowerTypeTests
because available_externally Global_Object is skipped to save and leads to a NULL GlobalTypeMember.
Even walking around the crash in LowerTypeTests, it still crashes in SelectionDAGBuilder or VerifierPass,
because they ask operands of icall.branch.funnel must be the same GlobalValue.
This patch only fix fullLTO mode.
---
Full diff: https://github.com/llvm/llvm-project/pull/143468.diff
2 Files Affected:
- (modified) llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp (+11)
- (added) llvm/test/Transforms/WholeProgramDevirt/availableexternal-check.ll (+57)
``````````diff
diff --git a/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp b/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp
index a7d9f3ba24b24..c0ddb50ea83ed 100644
--- a/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp
+++ b/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp
@@ -1093,6 +1093,7 @@ bool DevirtModule::tryFindVirtualCallTargets(
std::vector<VirtualCallTarget> &TargetsForSlot,
const std::set<TypeMemberInfo> &TypeMemberInfos, uint64_t ByteOffset,
ModuleSummaryIndex *ExportSummary) {
+ bool hasAvailableExternally = false;
for (const TypeMemberInfo &TM : TypeMemberInfos) {
if (!TM.Bits->GV->isConstant())
return false;
@@ -1103,6 +1104,16 @@ bool DevirtModule::tryFindVirtualCallTargets(
GlobalObject::VCallVisibilityPublic)
return false;
+ // Record if the first GV is AvailableExternally
+ if (TargetsForSlot.empty())
+ hasAvailableExternally = TM.Bits->GV->hasAvailableExternallyLinkage();
+
+ // When the first GV is AvailableExternally, check if all other GVs are
+ // also AvailableExternally. If they are not the same, return false.
+ if (!TargetsForSlot.empty() && hasAvailableExternally &&
+ !TM.Bits->GV->hasAvailableExternallyLinkage())
+ return false;
+
Function *Fn = nullptr;
Constant *C = nullptr;
std::tie(Fn, C) =
diff --git a/llvm/test/Transforms/WholeProgramDevirt/availableexternal-check.ll b/llvm/test/Transforms/WholeProgramDevirt/availableexternal-check.ll
new file mode 100644
index 0000000000000..d72075cdc1bb3
--- /dev/null
+++ b/llvm/test/Transforms/WholeProgramDevirt/availableexternal-check.ll
@@ -0,0 +1,57 @@
+; RUN: opt -S -passes=wholeprogramdevirt -whole-program-visibility %s | FileCheck %s
+
+; This test is reduced from C++ code like this:
+; class A :public std::exception {
+; public:
+; A() {};
+; const char* what () const throw () {return "A";}
+; };
+; long test(std::exception *p) {
+; const char* ch = p->what();
+; return std::strlen(ch);
+; }
+;
+; Build command is "clang++ -O2 -target x86_64-unknown-linux -flto=thin \
+; -fwhole-program-vtables -static-libstdc++ -Wl,-plugin-opt=-whole-program-visibility"
+;
+; _ZTVSt9exception's visibility is 1 (Linkage Unit), and available_externally.
+; But another vtable _ZTV1A.0 is not available_externally.
+; They should not do devirtualization because they are in different linkage type.
+
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux"
+
+ at _ZTVSt9exception = available_externally constant { [5 x ptr] } { [5 x ptr] [ptr null, ptr null, ptr null, ptr null, ptr @_ZNKSt9exception4whatEv] }, !type !0, !type !1, !vcall_visibility !2
+ at _ZTV1A.0 = constant [5 x ptr] [ptr null, ptr null, ptr null, ptr null, ptr @_ZNK1A4whatEv], !type !3, !type !4, !type !5, !type !6, !vcall_visibility !2
+
+declare ptr @_ZNKSt9exception4whatEv()
+
+define i64 @_Z4testPSt9exception() {
+ %1 = call i1 @llvm.type.test(ptr null, metadata !"_ZTSSt9exception")
+ tail call void @llvm.assume(i1 %1)
+ %2 = getelementptr i8, ptr null, i64 16
+ %3 = load ptr, ptr %2, align 8
+ %4 = tail call ptr %3(ptr null)
+ ret i64 0
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn memory(inaccessiblemem: write)
+declare void @llvm.assume(i1 noundef) #0
+
+declare ptr @_ZNK1A4whatEv()
+
+; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
+declare i1 @llvm.type.test(ptr, metadata) #1
+
+; CHECK-NOT: call void (...) @llvm.icall.branch.funnel
+
+attributes #0 = { nocallback nofree nosync nounwind willreturn memory(inaccessiblemem: write) }
+attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
+
+!0 = !{i64 16, !"_ZTSSt9exception"}
+!1 = !{i64 32, !"_ZTSMSt9exceptionKDoFPKcvE.virtual"}
+!2 = !{i64 1}
+!3 = !{i32 16, !"_ZTS1A"}
+!4 = !{i32 32, !"_ZTSM1AKDoFPKcvE.virtual"}
+!5 = !{i32 16, !"_ZTSSt9exception"}
+!6 = !{i32 32, !"_ZTSMSt9exceptionKDoFPKcvE.virtual"}
``````````
</details>
https://github.com/llvm/llvm-project/pull/143468
More information about the llvm-commits
mailing list