Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,7 @@ bool DevirtModule::tryFindVirtualCallTargets(
std::vector<VirtualCallTarget> &TargetsForSlot,
const std::set<TypeMemberInfo> &TypeMemberInfos, uint64_t ByteOffset,
ModuleSummaryIndex *ExportSummary) {
bool hasAvailableExternally = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
bool hasAvailableExternally = false;
bool HasAvailableExternally = false;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, @shiltian. Done.

for (const TypeMemberInfo &TM : TypeMemberInfos) {
if (!TM.Bits->GV->isConstant())
return false;
Expand All @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it ok if they are all available externally?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a conservative checking that does not stop devirtualization for all available externally.
Because when doing the check of available externally, checking of VCallVisibility != VCallVisibilityPublic is pass, which means all virtual function calls from this available externally vtable are in the current LTO unit. So I guess a icall.branch.funnel whose parameters all relate to available externally vtable is allowed to generated?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I guess a icall.branch.funnel whose parameters all relate to available externally vtable is allowed to generated?

If this is a question for me, I don't know the answer. Unfortunately this intrinsic isn't documented so I don't know the semantics. Do you have a test case where all vtables are available externally, and how does that work?

// 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) =
Expand Down
57 changes: 57 additions & 0 deletions llvm/test/Transforms/WholeProgramDevirt/availableexternal-check.ll
Original file line number Diff line number Diff line change
@@ -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"

@_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
@_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"}
Loading