Skip to content

Commit 49b51cf

Browse files
committed
[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.
1 parent 5d3899d commit 49b51cf

File tree

2 files changed

+68
-0
lines changed

2 files changed

+68
-0
lines changed

llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,6 +1093,7 @@ bool DevirtModule::tryFindVirtualCallTargets(
10931093
std::vector<VirtualCallTarget> &TargetsForSlot,
10941094
const std::set<TypeMemberInfo> &TypeMemberInfos, uint64_t ByteOffset,
10951095
ModuleSummaryIndex *ExportSummary) {
1096+
bool hasAvailableExternally = false;
10961097
for (const TypeMemberInfo &TM : TypeMemberInfos) {
10971098
if (!TM.Bits->GV->isConstant())
10981099
return false;
@@ -1103,6 +1104,16 @@ bool DevirtModule::tryFindVirtualCallTargets(
11031104
GlobalObject::VCallVisibilityPublic)
11041105
return false;
11051106

1107+
// Record if the first GV is AvailableExternally
1108+
if (TargetsForSlot.empty())
1109+
hasAvailableExternally = TM.Bits->GV->hasAvailableExternallyLinkage();
1110+
1111+
// When the first GV is AvailableExternally, check if all other GVs are
1112+
// also AvailableExternally. If they are not the same, return false.
1113+
if (!TargetsForSlot.empty() && hasAvailableExternally &&
1114+
!TM.Bits->GV->hasAvailableExternallyLinkage())
1115+
return false;
1116+
11061117
Function *Fn = nullptr;
11071118
Constant *C = nullptr;
11081119
std::tie(Fn, C) =
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
; RUN: opt -S -passes=wholeprogramdevirt -whole-program-visibility %s | FileCheck %s
2+
3+
; This test is reduced from C++ code like this:
4+
; class A :public std::exception {
5+
; public:
6+
; A() {};
7+
; const char* what () const throw () {return "A";}
8+
; };
9+
; long test(std::exception *p) {
10+
; const char* ch = p->what();
11+
; return std::strlen(ch);
12+
; }
13+
;
14+
; Build command is "clang++ -O2 -target x86_64-unknown-linux -flto=thin \
15+
; -fwhole-program-vtables -static-libstdc++ -Wl,-plugin-opt=-whole-program-visibility"
16+
;
17+
; _ZTVSt9exception's visibility is 1 (Linkage Unit), and available_externally.
18+
; But another vtable _ZTV1A.0 is not available_externally.
19+
; They should not do devirtualization because they are in different linkage type.
20+
21+
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"
22+
target triple = "x86_64-unknown-linux"
23+
24+
@_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
25+
@_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
26+
27+
declare ptr @_ZNKSt9exception4whatEv()
28+
29+
define i64 @_Z4testPSt9exception() {
30+
%1 = call i1 @llvm.type.test(ptr null, metadata !"_ZTSSt9exception")
31+
tail call void @llvm.assume(i1 %1)
32+
%2 = getelementptr i8, ptr null, i64 16
33+
%3 = load ptr, ptr %2, align 8
34+
%4 = tail call ptr %3(ptr null)
35+
ret i64 0
36+
}
37+
38+
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(inaccessiblemem: write)
39+
declare void @llvm.assume(i1 noundef) #0
40+
41+
declare ptr @_ZNK1A4whatEv()
42+
43+
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
44+
declare i1 @llvm.type.test(ptr, metadata) #1
45+
46+
; CHECK-NOT: call void (...) @llvm.icall.branch.funnel
47+
48+
attributes #0 = { nocallback nofree nosync nounwind willreturn memory(inaccessiblemem: write) }
49+
attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
50+
51+
!0 = !{i64 16, !"_ZTSSt9exception"}
52+
!1 = !{i64 32, !"_ZTSMSt9exceptionKDoFPKcvE.virtual"}
53+
!2 = !{i64 1}
54+
!3 = !{i32 16, !"_ZTS1A"}
55+
!4 = !{i32 32, !"_ZTSM1AKDoFPKcvE.virtual"}
56+
!5 = !{i32 16, !"_ZTSSt9exception"}
57+
!6 = !{i32 32, !"_ZTSMSt9exceptionKDoFPKcvE.virtual"}

0 commit comments

Comments
 (0)