[llvm] [WholeProgramDevirt] Add check for AvailableExternal and give up devirt (PR #143468)
Tianle Liu via llvm-commits
llvm-commits at lists.llvm.org
Tue Jun 10 19:39:44 PDT 2025
https://github.com/tianleliu updated https://github.com/llvm/llvm-project/pull/143468
>From 49b51cf548222550f15b0e8fa63ec56fdfe95642 Mon Sep 17 00:00:00 2001
From: tianleli <tianle.l.liu at intel.com>
Date: Mon, 9 Jun 2025 15:51:23 +0800
Subject: [PATCH 1/2] [WholeProgramDevirt] Add checking for AvailableExternal
and gives up 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.
---
.../lib/Transforms/IPO/WholeProgramDevirt.cpp | 11 ++++
.../availableexternal-check.ll | 57 +++++++++++++++++++
2 files changed, 68 insertions(+)
create mode 100644 llvm/test/Transforms/WholeProgramDevirt/availableexternal-check.ll
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"}
>From 0725ff3a5086998dd6d103f928707dbf1ff21648 Mon Sep 17 00:00:00 2001
From: tianleli <tianle.l.liu at intel.com>
Date: Wed, 11 Jun 2025 10:34:26 +0800
Subject: [PATCH 2/2] Fix case and simplify test file.
---
llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp | 6 +++---
.../WholeProgramDevirt/availableexternal-check.ll | 7 +++----
2 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp b/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp
index c0ddb50ea83ed..b86d64756998e 100644
--- a/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp
+++ b/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp
@@ -1093,7 +1093,7 @@ bool DevirtModule::tryFindVirtualCallTargets(
std::vector<VirtualCallTarget> &TargetsForSlot,
const std::set<TypeMemberInfo> &TypeMemberInfos, uint64_t ByteOffset,
ModuleSummaryIndex *ExportSummary) {
- bool hasAvailableExternally = false;
+ bool HasAvailableExternally = false;
for (const TypeMemberInfo &TM : TypeMemberInfos) {
if (!TM.Bits->GV->isConstant())
return false;
@@ -1106,11 +1106,11 @@ bool DevirtModule::tryFindVirtualCallTargets(
// Record if the first GV is AvailableExternally
if (TargetsForSlot.empty())
- hasAvailableExternally = TM.Bits->GV->hasAvailableExternallyLinkage();
+ 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 &&
+ if (!TargetsForSlot.empty() && HasAvailableExternally &&
!TM.Bits->GV->hasAvailableExternallyLinkage())
return false;
diff --git a/llvm/test/Transforms/WholeProgramDevirt/availableexternal-check.ll b/llvm/test/Transforms/WholeProgramDevirt/availableexternal-check.ll
index d72075cdc1bb3..65d594529ee06 100644
--- a/llvm/test/Transforms/WholeProgramDevirt/availableexternal-check.ll
+++ b/llvm/test/Transforms/WholeProgramDevirt/availableexternal-check.ll
@@ -8,7 +8,7 @@
; };
; 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 \
@@ -21,8 +21,8 @@
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
+ 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
+ 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
declare ptr @_ZNKSt9exception4whatEv()
@@ -50,7 +50,6 @@ attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memo
!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"}
More information about the llvm-commits
mailing list