263 changes: 263 additions & 0 deletions lld/test/ELF/lto/devirt_validate_vtable_typeinfos.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
; REQUIRES: x86

;; Common artifacts
; RUN: opt --thinlto-bc -o %t1.o %s
; RUN: opt --thinlto-bc --thinlto-split-lto-unit -o %t1_hybrid.o %s
; RUN: cp %s %t1_regular.ll
; RUN: echo '!llvm.module.flags = !{!12, !13}' >> %t1_regular.ll
; RUN: echo '!12 = !{i32 1, !"ThinLTO", i32 0}' >> %t1_regular.ll
; RUN: echo '!13 = !{i32 1, !"EnableSplitLTOUnit", i32 1}' >> %t1_regular.ll
; RUN: opt -module-summary -o %t1_regular.o %t1_regular.ll

; RUN: llvm-as %S/Inputs/devirt_validate_vtable_typeinfos.ll -o %t2.bc
; RUN: llc -relocation-model=pic -filetype=obj %t2.bc -o %t2.o
; RUN: ld.lld %t2.o -o %t2.so -shared

; RUN: llvm-as %S/Inputs/devirt_validate_vtable_typeinfos_no_rtti.ll -o %t2_nortti.bc
; RUN: llc -relocation-model=pic -filetype=obj %t2_nortti.bc -o %t2_nortti.o
; RUN: ld.lld %t2_nortti.o -o %t2_nortti.so -shared

; RUN: llvm-as %S/Inputs/devirt_validate_vtable_typeinfos_undef.ll -o %t2_undef.bc
; RUN: llc -relocation-model=pic -filetype=obj %t2_undef.bc -o %t2_undef.o
; RUN: ld.lld %t2_undef.o -o %t2_undef.so -shared

;; With --lto-whole-program-visibility, we assume no native types can interfere
;; and thus proceed with devirtualization even in the presence of native types

;; Index based WPD
; RUN: ld.lld %t1.o %t2.o -o %t3_index -save-temps --lto-whole-program-visibility \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
; RUN: llvm-dis %t1.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-IR

;; Hybrid WPD
; RUN: ld.lld %t1_hybrid.o %t2.o -o %t3_hybrid -save-temps --lto-whole-program-visibility \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
; RUN: llvm-dis %t1_hybrid.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-IR

;; Regular LTO WPD
; RUN: ld.lld %t1_regular.o %t2.o -o %t3_regular -save-temps --lto-whole-program-visibility \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
; RUN: llvm-dis %t3_regular.0.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-IR

; REMARK-DAG: single-impl: devirtualized a call to _ZN1A1nEi
; REMARK-DAG: single-impl: devirtualized a call to _ZN1D1mEi

;; With --lto-validate-all-vtables-have-type-infos, the linker checks for the presence of vtables
;; and RTTI in native files and blocks devirtualization to be conservative on correctness
;; for these types.

;; Index based WPD
; RUN: ld.lld %t1.o %t2.o -o %t4_index -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=VALIDATE
; RUN: llvm-dis %t1.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-VALIDATE-IR

;; Hybrid WPD
; RUN: ld.lld %t1_hybrid.o %t2.o -o %t4_hybrid -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=VALIDATE
; RUN: llvm-dis %t1_hybrid.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-VALIDATE-IR

;; Regular LTO WPD
; RUN: ld.lld %t1_regular.o %t2.o -o %t4_regular -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=VALIDATE
; RUN: llvm-dis %t4_regular.0.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-VALIDATE-IR

;; DSOs behave similarly

;; Index based WPD
; RUN: ld.lld %t1.o %t2.so -o %t5_index -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=VALIDATE
; RUN: llvm-dis %t1.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-VALIDATE-IR

;; Hybrid WPD
; RUN: ld.lld %t1_hybrid.o %t2.so -o %t5_hybrid -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=VALIDATE
; RUN: llvm-dis %t1_hybrid.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-VALIDATE-IR

;; Regular LTO WPD
; RUN: ld.lld %t1_regular.o %t2.so -o %t5_regular -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=VALIDATE
; RUN: llvm-dis %t5_regular.0.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-VALIDATE-IR

; VALIDATE-NOT: single-impl:
; VALIDATE: single-impl: devirtualized a call to _ZN1D1mEi
; VALIDATE-NOT: single-impl:

;; When vtables without type infos are detected in native files, we have a hole in our knowledge so
;; --lto-validate-all-vtables-have-type-infos conservatively disables --lto-whole-program-visibility

;; Index based WPD
; RUN: ld.lld %t1.o %t2_nortti.o -o %t6_index -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=NO-RTTI
; RUN: llvm-dis %t1.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-NO-RTTI-IR

;; Hybrid WPD
; RUN: ld.lld %t1_hybrid.o %t2_nortti.o -o %t6_hybrid -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=NO-RTTI
; RUN: llvm-dis %t1_hybrid.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-NO-RTTI-IR

;; Regular LTO WPD
; RUN: ld.lld %t1_regular.o %t2_nortti.o -o %t6_regular -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=NO-RTTI
; RUN: llvm-dis %t6_regular.0.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-NO-RTTI-IR

;; DSOs behave similarly

;; Index based WPD
; RUN: ld.lld %t1.o %t2_nortti.so -o %t7_index -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=NO-RTTI
; RUN: llvm-dis %t1.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-NO-RTTI-IR

;; Hybrid WPD
; RUN: ld.lld %t1_hybrid.o %t2_nortti.so -o %t7_hybrid -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=NO-RTTI
; RUN: llvm-dis %t1_hybrid.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-NO-RTTI-IR

;; Regular LTO WPD
; RUN: ld.lld %t1_regular.o %t2_nortti.so -o %t7_regular -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=NO-RTTI
; RUN: llvm-dis %t7_regular.0.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-NO-RTTI-IR

; NO-RTTI-DAG: --lto-validate-all-vtables-have-type-infos: RTTI missing for vtable _ZTV6Native, --lto-whole-program-visibility disabled
; NO-RTTI-DAG: single-impl: devirtualized a call to _ZN1D1mEi

;; --lto-known-safe-vtables=* can be used to specifically allow types to participate in WPD
;; even if they don't have corresponding RTTI

;; Index based WPD
; RUN: ld.lld %t1.o %t2_nortti.o -o %t8_index -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: --lto-known-safe-vtables=_ZTV6Native -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
; RUN: llvm-dis %t1.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-IR

;; Hybrid WPD
; RUN: ld.lld %t1_hybrid.o %t2_nortti.o -o %t8_hybrid -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: --lto-known-safe-vtables=_ZTV6Native -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
; RUN: llvm-dis %t1_hybrid.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-IR

;; Regular LTO WPD
; RUN: ld.lld %t1_regular.o %t2_nortti.o -o %t8_regular -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: --lto-known-safe-vtables=_ZTV6Native -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
; RUN: llvm-dis %t8_regular.0.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-IR

;; Only check for definitions of vtables symbols, just having a reference does not allow a type to
;; be derived from

;; Index based WPD
; RUN: ld.lld %t1.o %t2_undef.o -o %t9_index -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
; RUN: llvm-dis %t1.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-IR

;; Hybrid WPD
; RUN: ld.lld %t1_hybrid.o %t2_undef.o -o %t9_hybrid -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
; RUN: llvm-dis %t1_hybrid.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-IR

;; Regular LTO WPD
; RUN: ld.lld %t1_regular.o %t2_undef.o -o %t9_regular -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
; RUN: llvm-dis %t9_regular.0.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-IR

target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

%struct.A = type { ptr }
%struct.B = type { %struct.A }
%struct.C = type { %struct.A }
%struct.D = type { ptr }

@_ZTV1B = linkonce_odr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr @_ZTI1B, ptr @_ZN1B1fEi, ptr @_ZN1A1nEi] }, !type !0, !type !1, !type !2, !type !3, !type !4, !type !5
@_ZTV1C = linkonce_odr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr @_ZTI1C, ptr @_ZN1C1fEi, ptr @_ZN1A1nEi] }, !type !0, !type !1, !type !2, !type !6, !type !7, !type !8
@_ZTV1D = internal constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1D, ptr @_ZN1D1mEi] }, !type !9, !vcall_visibility !11

@_ZTS1A = linkonce_odr constant [3 x i8] c"1A\00"
@_ZTI1A = linkonce_odr constant { ptr, ptr } { ptr null, ptr @_ZTS1A }

@_ZTS1B = linkonce_odr constant [3 x i8] c"1B\00"
@_ZTI1B = linkonce_odr constant { ptr, ptr, ptr } { ptr null, ptr @_ZTS1B, ptr @_ZTI1A }

@_ZTS1C = linkonce_odr constant [3 x i8] c"1C\00"
@_ZTI1C = linkonce_odr constant { ptr, ptr, ptr } { ptr null, ptr @_ZTS1C, ptr @_ZTI1A }

@_ZTS1D = internal constant [3 x i8] c"1D\00"
@_ZTI1D = internal constant { ptr, ptr } { ptr null, ptr @_ZTS1D }

;; Prevent the vtables from being dead code eliminated.
@llvm.used = appending global [3 x ptr] [ ptr @_ZTV1B, ptr @_ZTV1C, ptr @_ZTV1D ]

; CHECK-COMMON-IR-LABEL: define dso_local i32 @_start
define i32 @_start(ptr %obj, ptr %obj2, i32 %a) {
entry:
%vtable = load ptr, ptr %obj
%p = call i1 @llvm.type.test(ptr %vtable, metadata !"_ZTS1A")
call void @llvm.assume(i1 %p)
%fptrptr = getelementptr ptr, ptr %vtable, i32 1
%fptr1 = load ptr, ptr %fptrptr, align 8

;; Check that the call was devirtualized.
; CHECK-IR: %call = tail call i32 @_ZN1A1nEi
;; --lto-whole-program-visibility disabled so no devirtualization
; CHECK-VALIDATE-IR: %call = tail call i32 %fptr1
; CHECK-NO-RTTI-IR: %call = tail call i32 %fptr1
%call = tail call i32 %fptr1(ptr nonnull %obj, i32 %a)

%fptr22 = load ptr, ptr %vtable, align 8

;; We still have to call it as virtual.
; CHECK-IR: %call2 = tail call i32 %fptr22
; CHECK-VALIDATE-IR: %call2 = tail call i32 %fptr22
; CHECK-NO-RTTI-IR: %call2 = tail call i32 %fptr22
%call2 = tail call i32 %fptr22(ptr nonnull %obj, i32 %call)

%vtable2 = load ptr, ptr %obj2
%p2 = call i1 @llvm.type.test(ptr %vtable2, metadata !10)
call void @llvm.assume(i1 %p2)

%fptr33 = load ptr, ptr %vtable2, align 8

;; Check that the call was devirtualized.
; CHECK-IR: %call3 = tail call i32 @_ZN1D1mEi
;; Types not present in native files can still be devirtualized
; CHECK-VALIDATE-IR: %call3 = tail call i32 @_ZN1D1mEi
;; --lto-whole-program-visibility disabled but being local this
;; has VCallVisibilityTranslationUnit visibility so it's still devirtualized
; CHECK-NO-RTTI-IR: %call3 = tail call i32 @_ZN1D1mEi
%call3 = tail call i32 %fptr33(ptr nonnull %obj2, i32 %call2)

ret i32 %call3
}
; CHECK-COMMON-IR-LABEL: ret i32
; CHECK-COMMON-IR-LABEL: }

declare i1 @llvm.type.test(ptr, metadata)
declare void @llvm.assume(i1)

define linkonce_odr i32 @_ZN1B1fEi(ptr %this, i32 %a) #0 {
ret i32 0;
}

define linkonce_odr i32 @_ZN1A1nEi(ptr %this, i32 %a) #0 {
ret i32 0;
}

define linkonce_odr i32 @_ZN1C1fEi(ptr %this, i32 %a) #0 {
ret i32 0;
}

define internal i32 @_ZN1D1mEi(ptr %this, i32 %a) #0 {
ret i32 0;
}

;; Make sure we don't inline or otherwise optimize out the direct calls.
attributes #0 = { noinline optnone }

!0 = !{i64 16, !"_ZTS1A"}
!1 = !{i64 16, !"_ZTSM1AFviE.virtual"}
!2 = !{i64 24, !"_ZTSM1AFviE.virtual"}
!3 = !{i64 16, !"_ZTS1B"}
!4 = !{i64 16, !"_ZTSM1BFviE.virtual"}
!5 = !{i64 24, !"_ZTSM1BFviE.virtual"}
!6 = !{i64 16, !"_ZTS1C"}
!7 = !{i64 16, !"_ZTSM1CFviE.virtual"}
!8 = !{i64 24, !"_ZTSM1CFviE.virtual"}
!9 = !{i64 16, !10}
!10 = distinct !{}
!11 = !{i64 2}
183 changes: 183 additions & 0 deletions lld/test/ELF/lto/devirt_validate_vtable_typeinfos_mixed_lto.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
; REQUIRES: x86

; RUN: rm -rf %t.dir
; RUN: split-file %s %t.dir
; RUN: cd %t.dir

;; Common artifacts
; RUN: opt --thinlto-bc --thinlto-split-lto-unit -o %t1.o ThinLTO.ll
; RUN: opt -module-summary -o %t2.o RegularLTO.ll

;; --lto-whole-program-visibility when there's split ThinLTO and a RegularLTO with summary optimizes
;; using the combined index.
; RUN: ld.lld %t1.o %t2.o -o %t3 -save-temps --lto-whole-program-visibility \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
; RUN: llvm-dis %t1.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-IR,CHECK-COMMON-IR
; RUN: llvm-dis %t3.0.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-REGULAR-IR,CHECK-COMMON-REGULAR-IR

;; --lto-validate-all-vtables-have-type-infos when there's split ThinLTO and a RegularLTO with summary behaves the same
;; as everything is present in the combined index.
; RUN: ld.lld %t1.o %t2.o -o %t3 -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
; RUN: llvm-dis %t1.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-IR,CHECK-COMMON-IR
; RUN: llvm-dis %t3.0.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-REGULAR-IR,CHECK-COMMON-REGULAR-IR

; REMARK-DAG: single-impl: devirtualized a call to _ZN1D1mEi
; REMARK-DAG: single-impl: devirtualized a call to _ZN1A1nEi

;--- ThinLTO.ll
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

%struct.A = type { ptr }
%struct.B = type { %struct.A }
%struct.C = type { %struct.A }
%struct.D = type { ptr }

@_ZTV1B = linkonce_odr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr @_ZTI1B, ptr @_ZN1A1fEi, ptr @_ZN1A1nEi] }, !type !0, !type !1, !type !2, !type !3, !type !4, !type !5
@_ZTV1C = linkonce_odr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr @_ZTI1C, ptr @_ZN1A1fEi, ptr @_ZN1A1nEi] }, !type !0, !type !1, !type !2, !type !6, !type !7, !type !8
@_ZTV1D = internal constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1D, ptr @_ZN1D1mEi] }, !type !9, !vcall_visibility !11

@_ZTS1A = linkonce_odr constant [3 x i8] c"1A\00"
@_ZTI1A = linkonce_odr constant { ptr, ptr } { ptr null, ptr @_ZTS1A }

@_ZTS1B = linkonce_odr constant [3 x i8] c"1B\00"
@_ZTI1B = linkonce_odr constant { ptr, ptr, ptr } { ptr null, ptr @_ZTS1B, ptr @_ZTI1A }

@_ZTS1C = linkonce_odr constant [3 x i8] c"1C\00"
@_ZTI1C = linkonce_odr constant { ptr, ptr, ptr } { ptr null, ptr @_ZTS1C, ptr @_ZTI1A }

@_ZTS1D = internal constant [3 x i8] c"1D\00"
@_ZTI1D = internal constant { ptr, ptr } { ptr null, ptr @_ZTS1D }

;; Prevent the vtables from being dead code eliminated.
@llvm.used = appending global [3 x ptr] [ ptr @_ZTV1B, ptr @_ZTV1C, ptr @_ZTV1D ], section "llvm.metadata"

; CHECK-COMMON-IR-LABEL: define dso_local i32 @_start
define i32 @_start(ptr %obj, ptr %obj2, i32 %a) {
;; Call function built with RegularLTO
%RegularLTOResult = call i32 @RegularLTO(ptr %obj, i32 %a)

;; ThinLTO code starts here
%vtable = load ptr, ptr %obj
%p = call i1 @llvm.type.test(ptr %vtable, metadata !"_ZTS1A")
call void @llvm.assume(i1 %p)
%fptrptr = getelementptr ptr, ptr %vtable, i32 1
%fptr1 = load ptr, ptr %fptrptr, align 8

;; Check that the call was devirtualized.
; CHECK-IR: %call = tail call i32 @_ZN1A1nEi
%call = tail call i32 %fptr1(ptr nonnull %obj, i32 %a)

%fptr22 = load ptr, ptr %vtable, align 8

;; Check that the call was not devirtualized.
; CHECK-IR: %call2 = tail call i32 %fptr22
%call2 = tail call i32 %fptr22(ptr nonnull %obj, i32 %call)

%vtable2 = load ptr, ptr %obj2
%p2 = call i1 @llvm.type.test(ptr %vtable2, metadata !10)
call void @llvm.assume(i1 %p2)

%fptr33 = load ptr, ptr %vtable2, align 8

;; Check that the call was devirtualized.
; CHECK-IR: %call3 = tail call i32 @_ZN1D1mEi
%call3 = tail call i32 %fptr33(ptr nonnull %obj2, i32 %call2)

ret i32 %call3
}
; CHECK-COMMON-IR-LABEL: ret i32
; CHECK-COMMON-IR-LABEL: }

declare i32 @RegularLTO(ptr)
declare i1 @llvm.type.test(ptr, metadata)
declare void @llvm.assume(i1)

define linkonce_odr i32 @_ZN1A1fEi(ptr %this, i32 %a) #0 {
ret i32 0;
}

define linkonce_odr i32 @_ZN1A1nEi(ptr %this, i32 %a) #0 {
ret i32 0;
}

define internal i32 @_ZN1D1mEi(ptr %this, i32 %a) #0 {
ret i32 0;
}

;; Make sure we don't inline or otherwise optimize out the direct calls.
attributes #0 = { noinline optnone }

!0 = !{i64 16, !"_ZTS1A"}
!1 = !{i64 16, !"_ZTSM1AFviE.virtual"}
!2 = !{i64 24, !"_ZTSM1AFviE.virtual"}
!3 = !{i64 16, !"_ZTS1B"}
!4 = !{i64 16, !"_ZTSM1BFviE.virtual"}
!5 = !{i64 24, !"_ZTSM1BFviE.virtual"}
!6 = !{i64 16, !"_ZTS1C"}
!7 = !{i64 16, !"_ZTSM1CFviE.virtual"}
!8 = !{i64 24, !"_ZTSM1CFviE.virtual"}
!9 = !{i64 16, !10}
!10 = distinct !{}
!11 = !{i64 2}

;--- RegularLTO.ll
; REQUIRES: x86

target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

%struct.A = type { ptr }
%struct.Native = type { %struct.A }

@_ZTV7Regular = linkonce_odr unnamed_addr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr @_ZTI7Regular, ptr @_ZN7Regular1fEi, ptr @_ZN1A1nEi] } , !type !0, !type !1, !type !2, !type !3, !type !4, !type !5
@_ZTS7Regular = linkonce_odr constant [9 x i8] c"7Regular\00"
@_ZTI7Regular = linkonce_odr constant { ptr, ptr, ptr } { ptr null, ptr @_ZTS7Regular, ptr @_ZTI1A }

; Base type A does not need to emit a vtable if it's never instantiated. However, RTTI still gets generated
@_ZTS1A = linkonce_odr constant [3 x i8] c"1A\00"
@_ZTI1A = linkonce_odr constant { ptr, ptr } { ptr null, ptr @_ZTS1A }

;; Prevent the vtables from being dead code eliminated.
@llvm.used = appending global [1 x ptr] [ ptr @_ZTV7Regular ], section "llvm.metadata"

; CHECK-COMMON-REGULAR-IR-LABEL: define dso_local i32 @RegularLTO
define i32 @RegularLTO(ptr %obj, i32 %a) #0 {
entry:
%vtable = load ptr, ptr %obj
%p = call i1 @llvm.type.test(ptr %vtable, metadata !"_ZTS1A")
call void @llvm.assume(i1 %p)
%fptr1 = load ptr, ptr %vtable, align 8

;; Check that the call was not devirtualized.
; CHECK-REGULAR-IR: %call = tail call i32 %fptr1
%call = tail call i32 %fptr1(ptr nonnull %obj, i32 %a)

ret i32 %call
}
; CHECK-COMMON-REGULAR-IR-LABEL: ret i32
; CHECK-COMMON-REGULAR-IR-LABEL: }

declare i1 @llvm.type.test(ptr, metadata)
declare void @llvm.assume(i1)

define linkonce_odr i32 @_ZN7Regular1fEi(ptr %this, i32 %a) #0 {
ret i32 1;
}

define linkonce_odr i32 @_ZN1A1nEi(ptr %this, i32 %a) #0 {
ret i32 0;
}

attributes #0 = { noinline optnone }
!llvm.module.flags = !{!6, !7}

!0 = !{i64 16, !"_ZTS1A"}
!1 = !{i64 16, !"_ZTSM1AFviE.virtual"}
!2 = !{i64 24, !"_ZTSM1AFviE.virtual"}
!3 = !{i64 16, !"_ZTS7Regular"}
!4 = !{i64 16, !"_ZTSM7RegularFviE.virtual"}
!5 = !{i64 24, !"_ZTSM7RegularFviE.virtual"}
!6 = !{i32 1, !"ThinLTO", i32 0}
!7 = !{i32 1, !"EnableSplitLTOUnit", i32 1}
136 changes: 136 additions & 0 deletions lld/test/ELF/lto/devirt_validate_vtable_typeinfos_no_rtti.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
; REQUIRES: x86

;; Common artifacts
; RUN: opt --thinlto-bc -o %t1.o %s
; RUN: opt --thinlto-bc --thinlto-split-lto-unit -o %t1_hybrid.o %s
; RUN: cp %s %t1_regular.ll
; RUN: echo '!llvm.module.flags = !{!6, !7}' >> %t1_regular.ll
; RUN: echo '!6 = !{i32 1, !"ThinLTO", i32 0}' >> %t1_regular.ll
; RUN: echo '!7 = !{i32 1, !"EnableSplitLTOUnit", i32 1}' >> %t1_regular.ll
; RUN: opt -module-summary -o %t1_regular.o %t1_regular.ll

;; With --lto-whole-program-visibility, we assume no native types can interfere
;; and thus proceed with devirtualization even in the presence of native types

;; Index based WPD
; RUN: ld.lld %t1.o -o %t3_index -save-temps --lto-whole-program-visibility \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
; RUN: llvm-dis %t1.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-IR

;; Hybrid WPD
; RUN: ld.lld %t1_hybrid.o -o %t3_hybrid -save-temps --lto-whole-program-visibility \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
; RUN: llvm-dis %t1_hybrid.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-IR

;; Regular LTO WPD
; RUN: ld.lld %t1_regular.o -o %t3_regular -save-temps --lto-whole-program-visibility \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=REMARK
; RUN: llvm-dis %t3_regular.0.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-IR

; REMARK-DAG: single-impl: devirtualized a call to _ZN1A1nEi
; REMARK-DAG: single-impl: devirtualized a call to _ZN1D1mEi

;; With --lto-whole-program-visibility and --lto-validate-all-vtables-have-type-infos
;; we rely on resolutions on the typename symbol to inform us of what's outside the summary.
;; Without the typename symbol in the LTO unit (e.g. RTTI disabled) this causes
;; conservative disablement of WPD on these types unless it's local

;; Index based WPD
; RUN: ld.lld %t1.o -o %t3_index -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=VALIDATE
; RUN: llvm-dis %t1.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-VALIDATE-IR

;; Hybrid WPD
; RUN: ld.lld %t1_hybrid.o -o %t3_hybrid -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=VALIDATE
; RUN: llvm-dis %t1_hybrid.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-VALIDATE-IR

;; Regular LTO WPD
; RUN: ld.lld %t1_regular.o -o %t3_regular -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s --check-prefix=VALIDATE
; RUN: llvm-dis %t3_regular.0.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-COMMON-IR-LABEL,CHECK-VALIDATE-IR

; VALIDATE-DAG: single-impl: devirtualized a call to _ZN1D1mEi

target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

%struct.A = type { ptr }
%struct.B = type { %struct.A }
%struct.C = type { %struct.A }
%struct.D = type { ptr }

@_ZTV1B = linkonce_odr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr null, ptr @_ZN1B1fEi, ptr @_ZN1A1nEi] }, !type !0, !type !1
@_ZTV1C = linkonce_odr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr null, ptr @_ZN1C1fEi, ptr @_ZN1A1nEi] }, !type !0, !type !2
@_ZTV1D = internal constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr null, ptr @_ZN1D1mEi] }, !type !3, !vcall_visibility !5

;; Prevent the vtables from being dead code eliminated.
@llvm.used = appending global [3 x ptr] [ ptr @_ZTV1B, ptr @_ZTV1C, ptr @_ZTV1D ]

; CHECK-COMMON-IR-LABEL: define dso_local i32 @_start
define i32 @_start(ptr %obj, ptr %obj2, i32 %a) {
entry:
%vtable = load ptr, ptr %obj
%p = call i1 @llvm.type.test(ptr %vtable, metadata !"_ZTS1A")
call void @llvm.assume(i1 %p)
%fptrptr = getelementptr ptr, ptr %vtable, i32 1
%fptr1 = load ptr, ptr %fptrptr, align 8

;; Check that the call was devirtualized.
; CHECK-IR: %call = tail call i32 @_ZN1A1nEi
;; No resolution for _ZTS1A means we don't devirtualize
; CHECK-VALIDATE-IR: %call = tail call i32 %fptr1
%call = tail call i32 %fptr1(ptr nonnull %obj, i32 %a)

%fptr22 = load ptr, ptr %vtable, align 8

;; We still have to call it as virtual.
; CHECK-IR: %call3 = tail call i32 %fptr22
; CHECK-VALIDATE-IR: %call3 = tail call i32 %fptr22
%call3 = tail call i32 %fptr22(ptr nonnull %obj, i32 %call)

%vtable2 = load ptr, ptr %obj2
%p2 = call i1 @llvm.type.test(ptr %vtable2, metadata !4)
call void @llvm.assume(i1 %p2)

%fptr33 = load ptr, ptr %vtable2, align 8

;; Check that the call was devirtualized.
; CHECK-IR: %call4 = tail call i32 @_ZN1D1mEi
;; Being local this has VCallVisibilityTranslationUnit
;; visibility so it's still devirtualized
; CHECK-VALIDATE-IR: %call4 = tail call i32 @_ZN1D1mEi
%call4 = tail call i32 %fptr33(ptr nonnull %obj2, i32 %call3)
ret i32 %call4
}
; CHECK-COMMON-IR-LABEL: ret i32
; CHECK-COMMON-IR-LABEL: }

declare i1 @llvm.type.test(ptr, metadata)
declare void @llvm.assume(i1)

define linkonce_odr i32 @_ZN1B1fEi(ptr %this, i32 %a) #0 {
ret i32 0;
}

define linkonce_odr i32 @_ZN1A1nEi(ptr %this, i32 %a) #0 {
ret i32 0;
}

define linkonce_odr i32 @_ZN1C1fEi(ptr %this, i32 %a) #0 {
ret i32 0;
}

define internal i32 @_ZN1D1mEi(ptr %this, i32 %a) #0 {
ret i32 0;
}

;; Make sure we don't inline or otherwise optimize out the direct calls.
attributes #0 = { noinline optnone }

!0 = !{i64 16, !"_ZTS1A"}
!1 = !{i64 16, !"_ZTS1B"}
!2 = !{i64 16, !"_ZTS1C"}
!3 = !{i64 16, !4}
!4 = distinct !{}
!5 = !{i64 2}
130 changes: 130 additions & 0 deletions lld/test/ELF/lto/devirt_validate_vtable_typeinfos_ref.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
; REQUIRES: x86

;; Common artifacts
; RUN: opt --thinlto-bc -o %t1.o %s
; RUN: opt --thinlto-bc --thinlto-split-lto-unit -o %t1_hybrid.o %s
; RUN: cp %s %t1_regular.ll
; RUN: echo '!llvm.module.flags = !{!2, !3}' >> %t1_regular.ll
; RUN: echo '!2 = !{i32 1, !"ThinLTO", i32 0}' >> %t1_regular.ll
; RUN: echo '!3 = !{i32 1, !"EnableSplitLTOUnit", i32 1}' >> %t1_regular.ll
; RUN: opt -module-summary -o %t1_regular.o %t1_regular.ll

; RUN: llvm-as %S/Inputs/devirt_validate_vtable_typeinfos_ref.ll -o %t2.bc
; RUN: llc -relocation-model=pic -filetype=obj %t2.bc -o %t2.o

;; Native objects can contain only a reference to the base type infos if the base declaration has no key functions.
;; Because of that, --lto-validate-all-vtables-have-type-infos needs to query for the type info symbol inside native files rather than the
;; type name symbol that's used as the key in !type metadata to correctly stop devirtualization on the native type.

;; Index based WPD
; RUN: ld.lld %t1.o %t2.o -o %t3_index -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s
; RUN: llvm-dis %t1.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-IR

;; Hybrid WPD
; RUN: ld.lld %t1_hybrid.o %t2.o -o %t3_hybrid -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s
; RUN: llvm-dis %t1_hybrid.o.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-IR

;; Regular LTO WPD
; RUN: ld.lld %t1_regular.o %t2.o -o %t3_regular -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \
; RUN: -mllvm -pass-remarks=. 2>&1 | FileCheck %s
; RUN: llvm-dis %t3_regular.0.4.opt.bc -o - | FileCheck %s --check-prefixes=CHECK-IR

; CHECK-NOT: single-impl: devirtualized a call to _ZN1A3fooEv

;; Source code:
;; cat > a.h <<'eof'
;; struct A { virtual int foo(); };
;; int bar(A *a);
;; eof
;; cat > main.cc <<'eof'
;; #include "a.h"
;;
;; int A::foo() { return 1; }
;; int bar(A *a) { return a->foo(); }
;;
;; extern int baz();
;; int main() {
;; A a;
;; int i = bar(&a);
;; int j = baz();
;; return i + j;
;; }
;; eof
;; clang++ -fwhole-program-vtables -fno-split-lto-unit -flto=thin main.cc -c

target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

%struct.A = type { %struct.Abase }
%struct.Abase = type { ptr }

@_ZTV1A = dso_local unnamed_addr constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1A, ptr @_ZN1A3fooEv] }, align 8, !type !0, !type !1
@_ZTS1A = dso_local constant [3 x i8] c"1A\00", align 1
@_ZTI1A = dso_local constant { ptr, ptr } { ptr null, ptr @_ZTS1A }, align 8

define dso_local noundef i32 @_ZN1A3fooEv(ptr noundef nonnull align 8 dereferenceable(8) %this) #0 align 2 {
entry:
%this.addr = alloca ptr
store ptr %this, ptr %this.addr
%this1 = load ptr, ptr %this.addr
ret i32 1
}

; CHECK-IR: define dso_local noundef i32 @_Z3barP1A
define dso_local noundef i32 @_Z3barP1A(ptr noundef %a) #0 {
entry:
%a.addr = alloca ptr
store ptr %a, ptr %a.addr
%0 = load ptr, ptr %a.addr
%vtable = load ptr, ptr %0
%1 = call i1 @llvm.public.type.test(ptr %vtable, metadata !"_ZTS1A")
call void @llvm.assume(i1 %1)
%vfn = getelementptr inbounds ptr, ptr %vtable, i64 0
%fptr = load ptr, ptr %vfn
;; Check that the call was not devirtualized.
; CHECK-IR: %call = call noundef i32 %fptr
%call = call noundef i32 %fptr(ptr noundef nonnull align 8 dereferenceable(8) %0)
ret i32 %call
}
; CHECK-IR: ret i32
; CHECK-IR: }

declare i1 @llvm.public.type.test(ptr, metadata)
declare void @llvm.assume(i1 noundef)

define dso_local noundef i32 @main() #0 {
entry:
%retval = alloca i32, align 4
%a = alloca %struct.A, align 8
%i = alloca i32, align 4
%j = alloca i32, align 4
store i32 0, ptr %retval, align 4
call void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(8) %a)
%call = call noundef i32 @_Z3barP1A(ptr noundef %a)
store i32 %call, ptr %i, align 4
%call1 = call noundef i32 @_Z3bazv()
store i32 %call1, ptr %j, align 4
%0 = load i32, ptr %i, align 4
%1 = load i32, ptr %j, align 4
%add = add nsw i32 %0, %1
ret i32 %add
}

define linkonce_odr dso_local void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(8) %this) #0 align 2 {
entry:
%this.addr = alloca ptr, align 8
store ptr %this, ptr %this.addr, align 8
%this1 = load ptr, ptr %this.addr, align 8
store ptr getelementptr inbounds ({ [3 x ptr] }, ptr @_ZTV1A, i32 0, inrange i32 0, i32 2), ptr %this1, align 8
ret void
}

declare noundef i32 @_Z3bazv()

;; Make sure we don't inline or otherwise optimize out the direct calls.
attributes #0 = { noinline optnone }

!0 = !{i64 16, !"_ZTS1A"}
!1 = !{i64 16, !"_ZTSM1AFivE.virtual"}
6 changes: 6 additions & 0 deletions llvm/include/llvm/LTO/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ struct Config {
/// link.
bool HasWholeProgramVisibility = false;

/// We're validating that all native vtables have corresponding type infos.
bool ValidateAllVtablesHaveTypeInfos = false;
/// If all native vtables have corresponding type infos, allow
/// usage of RTTI to block devirtualization on types used in native files.
bool AllVtablesHaveTypeInfos = false;

/// Always emit a Regular LTO object even when it is empty because no Regular
/// LTO modules were linked. This option is useful for some build system which
/// want to know a priori all possible output files.
Expand Down
12 changes: 10 additions & 2 deletions llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h
Original file line number Diff line number Diff line change
Expand Up @@ -243,10 +243,18 @@ void updatePublicTypeTestCalls(Module &M,
bool WholeProgramVisibilityEnabledInLTO);
void updateVCallVisibilityInModule(
Module &M, bool WholeProgramVisibilityEnabledInLTO,
const DenseSet<GlobalValue::GUID> &DynamicExportSymbols);
const DenseSet<GlobalValue::GUID> &DynamicExportSymbols,
bool ValidateAllVtablesHaveTypeInfos,
function_ref<bool(StringRef)> IsVisibleToRegularObj);
void updateVCallVisibilityInIndex(
ModuleSummaryIndex &Index, bool WholeProgramVisibilityEnabledInLTO,
const DenseSet<GlobalValue::GUID> &DynamicExportSymbols);
const DenseSet<GlobalValue::GUID> &DynamicExportSymbols,
const DenseSet<GlobalValue::GUID> &VisibleToRegularObjSymbols);

void getVisibleToRegularObjVtableGUIDs(
ModuleSummaryIndex &Index,
DenseSet<GlobalValue::GUID> &VisibleToRegularObjSymbols,
function_ref<bool(StringRef)> IsVisibleToRegularObj);

/// Perform index-based whole program devirtualization on the \p Summary
/// index. Any devirtualized targets used by a type test in another module
Expand Down
55 changes: 47 additions & 8 deletions llvm/lib/LTO/LTO.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1270,13 +1270,27 @@ Error LTO::runRegularLTO(AddStreamFn AddStream) {

updateMemProfAttributes(*RegularLTO.CombinedModule, ThinLTO.CombinedIndex);

bool WholeProgramVisibilityEnabledInLTO =
Conf.HasWholeProgramVisibility &&
// If validation is enabled, upgrade visibility only when all vtables
// have typeinfos.
(!Conf.ValidateAllVtablesHaveTypeInfos || Conf.AllVtablesHaveTypeInfos);

// This returns true when the name is local or not defined. Locals are
// expected to be handled separately.
auto IsVisibleToRegularObj = [&](StringRef name) {
auto It = GlobalResolutions.find(name);
return (It == GlobalResolutions.end() || It->second.VisibleOutsideSummary);
};

// If allowed, upgrade public vcall visibility metadata to linkage unit
// visibility before whole program devirtualization in the optimizer.
updateVCallVisibilityInModule(*RegularLTO.CombinedModule,
Conf.HasWholeProgramVisibility,
DynamicExportSymbols);
updateVCallVisibilityInModule(
*RegularLTO.CombinedModule, WholeProgramVisibilityEnabledInLTO,
DynamicExportSymbols, Conf.ValidateAllVtablesHaveTypeInfos,
IsVisibleToRegularObj);
updatePublicTypeTestCalls(*RegularLTO.CombinedModule,
Conf.HasWholeProgramVisibility);
WholeProgramVisibilityEnabledInLTO);

if (Conf.PreOptModuleHook &&
!Conf.PreOptModuleHook(0, *RegularLTO.CombinedModule))
Expand Down Expand Up @@ -1683,13 +1697,38 @@ Error LTO::runThinLTO(AddStreamFn AddStream, FileCache Cache,

std::set<GlobalValue::GUID> ExportedGUIDs;

if (hasWholeProgramVisibility(Conf.HasWholeProgramVisibility))
bool WholeProgramVisibilityEnabledInLTO =
Conf.HasWholeProgramVisibility &&
// If validation is enabled, upgrade visibility only when all vtables
// have typeinfos.
(!Conf.ValidateAllVtablesHaveTypeInfos || Conf.AllVtablesHaveTypeInfos);
if (hasWholeProgramVisibility(WholeProgramVisibilityEnabledInLTO))
ThinLTO.CombinedIndex.setWithWholeProgramVisibility();

// If we're validating, get the vtable symbols that should not be
// upgraded because they correspond to typeIDs outside of index-based
// WPD info.
DenseSet<GlobalValue::GUID> VisibleToRegularObjSymbols;
if (WholeProgramVisibilityEnabledInLTO &&
Conf.ValidateAllVtablesHaveTypeInfos) {
// This returns true when the name is local or not defined. Locals are
// expected to be handled separately.
auto IsVisibleToRegularObj = [&](StringRef name) {
auto It = GlobalResolutions.find(name);
return (It == GlobalResolutions.end() ||
It->second.VisibleOutsideSummary);
};

getVisibleToRegularObjVtableGUIDs(ThinLTO.CombinedIndex,
VisibleToRegularObjSymbols,
IsVisibleToRegularObj);
}

// If allowed, upgrade public vcall visibility to linkage unit visibility in
// the summaries before whole program devirtualization below.
updateVCallVisibilityInIndex(ThinLTO.CombinedIndex,
Conf.HasWholeProgramVisibility,
DynamicExportSymbols);
updateVCallVisibilityInIndex(
ThinLTO.CombinedIndex, WholeProgramVisibilityEnabledInLTO,
DynamicExportSymbols, VisibleToRegularObjSymbols);

// Perform index-based WPD. This will return immediately if there are
// no index entries in the typeIdMetadata map (e.g. if we are instead
Expand Down
13 changes: 8 additions & 5 deletions llvm/lib/LTO/LTOCodeGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -605,11 +605,14 @@ bool LTOCodeGenerator::optimize() {
// pipeline run below.
updatePublicTypeTestCalls(*MergedModule,
/* WholeProgramVisibilityEnabledInLTO */ false);
updateVCallVisibilityInModule(*MergedModule,
/* WholeProgramVisibilityEnabledInLTO */ false,
// FIXME: This needs linker information via a
// TBD new interface.
/* DynamicExportSymbols */ {});
updateVCallVisibilityInModule(
*MergedModule,
/* WholeProgramVisibilityEnabledInLTO */ false,
// FIXME: These need linker information via a
// TBD new interface.
/*DynamicExportSymbols=*/{},
/*ValidateAllVtablesHaveTypeInfos=*/false,
/*IsVisibleToRegularObj=*/[](StringRef) { return true; });

// We always run the verifier once on the merged module, the `DisableVerify`
// parameter only applies to subsequent verify.
Expand Down
9 changes: 6 additions & 3 deletions llvm/lib/LTO/ThinLTOCodeGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1057,11 +1057,14 @@ void ThinLTOCodeGenerator::run() {
// via the internal option. Must be done before WPD below.
if (hasWholeProgramVisibility(/* WholeProgramVisibilityEnabledInLTO */ false))
Index->setWithWholeProgramVisibility();

// FIXME: This needs linker information via a TBD new interface
updateVCallVisibilityInIndex(*Index,
/* WholeProgramVisibilityEnabledInLTO */ false,
// FIXME: This needs linker information via a
/*WholeProgramVisibilityEnabledInLTO=*/false,
// FIXME: These need linker information via a
// TBD new interface.
/* DynamicExportSymbols */ {});
/*DynamicExportSymbols=*/{},
/*VisibleToRegularObjSymbols=*/{});

// Perform index-based WPD. This will return immediately if there are
// no index entries in the typeIdMetadata map (e.g. if we are instead
Expand Down
76 changes: 71 additions & 5 deletions llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -782,12 +782,52 @@ bool llvm::hasWholeProgramVisibility(bool WholeProgramVisibilityEnabledInLTO) {
!DisableWholeProgramVisibility;
}

static bool
typeIDVisibleToRegularObj(StringRef TypeID,
function_ref<bool(StringRef)> IsVisibleToRegularObj) {
// TypeID for member function pointer type is an internal construct
// and won't exist in IsVisibleToRegularObj. The full TypeID
// will be present and participate in invalidation.
if (TypeID.ends_with(".virtual"))
return false;

// TypeID that doesn't start with Itanium mangling (_ZTS) will be
// non-externally visible types which cannot interact with
// external native files. See CodeGenModule::CreateMetadataIdentifierImpl.
if (!TypeID.consume_front("_ZTS"))
return false;

// TypeID is keyed off the type name symbol (_ZTS). However, the native
// object may not contain this symbol if it does not contain a key
// function for the base type and thus only contains a reference to the
// type info (_ZTI). To catch this case we query using the type info
// symbol corresponding to the TypeID.
std::string typeInfo = ("_ZTI" + TypeID).str();
return IsVisibleToRegularObj(typeInfo);
}

static bool
skipUpdateDueToValidation(GlobalVariable &GV,
function_ref<bool(StringRef)> IsVisibleToRegularObj) {
SmallVector<MDNode *, 2> Types;
GV.getMetadata(LLVMContext::MD_type, Types);

for (auto Type : Types)
if (auto *TypeID = dyn_cast<MDString>(Type->getOperand(1).get()))
return typeIDVisibleToRegularObj(TypeID->getString(),
IsVisibleToRegularObj);

return false;
}

/// If whole program visibility asserted, then upgrade all public vcall
/// visibility metadata on vtable definitions to linkage unit visibility in
/// Module IR (for regular or hybrid LTO).
void llvm::updateVCallVisibilityInModule(
Module &M, bool WholeProgramVisibilityEnabledInLTO,
const DenseSet<GlobalValue::GUID> &DynamicExportSymbols) {
const DenseSet<GlobalValue::GUID> &DynamicExportSymbols,
bool ValidateAllVtablesHaveTypeInfos,
function_ref<bool(StringRef)> IsVisibleToRegularObj) {
if (!hasWholeProgramVisibility(WholeProgramVisibilityEnabledInLTO))
return;
for (GlobalVariable &GV : M.globals()) {
Expand All @@ -798,7 +838,13 @@ void llvm::updateVCallVisibilityInModule(
GV.getVCallVisibility() == GlobalObject::VCallVisibilityPublic &&
// Don't upgrade the visibility for symbols exported to the dynamic
// linker, as we have no information on their eventual use.
!DynamicExportSymbols.count(GV.getGUID()))
!DynamicExportSymbols.count(GV.getGUID()) &&
// With validation enabled, we want to exclude symbols visible to
// regular objects. Local symbols will be in this group due to the
// current implementation but those with VCallVisibilityTranslationUnit
// will have already been marked in clang so are unaffected.
!(ValidateAllVtablesHaveTypeInfos &&
skipUpdateDueToValidation(GV, IsVisibleToRegularObj)))
GV.setVCallVisibilityMetadata(GlobalObject::VCallVisibilityLinkageUnit);
}
}
Expand Down Expand Up @@ -830,12 +876,26 @@ void llvm::updatePublicTypeTestCalls(Module &M,
}
}

/// Based on typeID string, get all associated vtable GUIDS that are
/// visible to regular objects.
void llvm::getVisibleToRegularObjVtableGUIDs(
ModuleSummaryIndex &Index,
DenseSet<GlobalValue::GUID> &VisibleToRegularObjSymbols,
function_ref<bool(StringRef)> IsVisibleToRegularObj) {
for (const auto &typeID : Index.typeIdCompatibleVtableMap()) {
if (typeIDVisibleToRegularObj(typeID.first, IsVisibleToRegularObj))
for (const TypeIdOffsetVtableInfo &P : typeID.second)
VisibleToRegularObjSymbols.insert(P.VTableVI.getGUID());
}
}

/// If whole program visibility asserted, then upgrade all public vcall
/// visibility metadata on vtable definition summaries to linkage unit
/// visibility in Module summary index (for ThinLTO).
void llvm::updateVCallVisibilityInIndex(
ModuleSummaryIndex &Index, bool WholeProgramVisibilityEnabledInLTO,
const DenseSet<GlobalValue::GUID> &DynamicExportSymbols) {
const DenseSet<GlobalValue::GUID> &DynamicExportSymbols,
const DenseSet<GlobalValue::GUID> &VisibleToRegularObjSymbols) {
if (!hasWholeProgramVisibility(WholeProgramVisibilityEnabledInLTO))
return;
for (auto &P : Index) {
Expand All @@ -848,6 +908,12 @@ void llvm::updateVCallVisibilityInIndex(
if (!GVar ||
GVar->getVCallVisibility() != GlobalObject::VCallVisibilityPublic)
continue;
// With validation enabled, we want to exclude symbols visible to regular
// objects. Local symbols will be in this group due to the current
// implementation but those with VCallVisibilityTranslationUnit will have
// already been marked in clang so are unaffected.
if (VisibleToRegularObjSymbols.count(P.first))
continue;
GVar->setVCallVisibility(GlobalObject::VCallVisibilityLinkageUnit);
}
}
Expand Down Expand Up @@ -1041,8 +1107,8 @@ bool DevirtModule::tryFindVirtualCallTargets(
}

bool DevirtIndex::tryFindVirtualCallTargets(
std::vector<ValueInfo> &TargetsForSlot, const TypeIdCompatibleVtableInfo TIdInfo,
uint64_t ByteOffset) {
std::vector<ValueInfo> &TargetsForSlot,
const TypeIdCompatibleVtableInfo TIdInfo, uint64_t ByteOffset) {
for (const TypeIdOffsetVtableInfo &P : TIdInfo) {
// Find a representative copy of the vtable initializer.
// We can have multiple available_externally, linkonce_odr and weak_odr
Expand Down
11 changes: 8 additions & 3 deletions llvm/tools/opt/opt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -568,9 +568,14 @@ int main(int argc, char **argv) {
// the facility for updating public visibility to linkage unit visibility when
// specified by an internal option. This is normally done during LTO which is
// not performed via opt.
updateVCallVisibilityInModule(*M,
/* WholeProgramVisibilityEnabledInLTO */ false,
/* DynamicExportSymbols */ {});
updateVCallVisibilityInModule(
*M,
/*WholeProgramVisibilityEnabledInLTO=*/false,
// FIXME: These need linker information via a
// TBD new interface.
/*DynamicExportSymbols=*/{},
/*ValidateAllVtablesHaveTypeInfos=*/false,
/*IsVisibleToRegularObj=*/[](StringRef) { return true; });

// Figure out what stream we are supposed to write to...
std::unique_ptr<ToolOutputFile> Out;
Expand Down