[WPD][LLD] Add option to validate RTTI is enabled on all native types and prevent devirtualization on types with native RTTI

Discussion about this approach: https://discourse.llvm.org/t/rfc-safer-whole-program-class-hierarchy-analysis/65144/18

When enabling WPD in an environment where native binaries are present, types we want to optimize can be derived from inside these native files and devirtualizing them can lead to correctness issues. RTTI can be used as a way to determine all such types in native files and exclude them from WPD providing a safe checked way to enable WPD.

The approach is:
1. In the linker, identify if RTTI is available for all native types. If not, under `--lto-validate-all-vtables-have-type-infos` `--lto-whole-program-visibility` is automatically disabled. This is done by examining all .symtab symbols in object files and .dynsym symbols in DSOs for vtable (_ZTV) and typeinfo (_ZTI) symbols and ensuring there's always a match for every vtable symbol.
2. During thinlink, if `--lto-validate-all-vtables-have-type-infos` is set and RTTI is available for all native types, identify all typename (_ZTS) symbols via their corresponding typeinfo (_ZTI) symbols that are used natively or outside of our summary and exclude them from WPD.

Testing:
ninja check-all
large Meta service that uses boost, glog and libstdc++.so runs successfully with WPD via --lto-whole-program-visibility. Previously, native types in boost caused incorrect devirtualization that led to crashes.

Reviewed By: MaskRay, tejohnson

Differential Revision: https://reviews.llvm.org/D155659

GitOrigin-RevId: 272bd6f9cc86bf6b4dd6bd51e85c46db10e8b86a
diff --git a/ELF/Config.h b/ELF/Config.h
index 5d58fc1..12f7bf1 100644
--- a/ELF/Config.h
+++ b/ELF/Config.h
@@ -247,6 +247,7 @@
   bool ltoDebugPassManager;
   bool ltoEmitAsm;
   bool ltoUniqueBasicBlockSectionNames;
+  bool ltoValidateAllVtablesHaveTypeInfos;
   bool ltoWholeProgramVisibility;
   bool mergeArmExidx;
   bool mipsN32Abi = false;
@@ -475,6 +476,9 @@
   std::atomic<bool> hasTlsIe{false};
   // True if we need to reserve two .got entries for local-dynamic TLS model.
   std::atomic<bool> needsTlsLd{false};
+  // True if all native vtable symbols have corresponding type info symbols
+  // during LTO.
+  bool ltoAllVtablesHaveTypeInfos;
 
   // Each symbol assignment and DEFINED(sym) reference is assigned an increasing
   // order. Each DEFINED(sym) evaluation checks whether the reference happens
diff --git a/ELF/Driver.cpp b/ELF/Driver.cpp
index bf35161..8d16ee3 100644
--- a/ELF/Driver.cpp
+++ b/ELF/Driver.cpp
@@ -110,6 +110,7 @@
   needsTlsLd.store(false, std::memory_order_relaxed);
   scriptSymOrderCounter = 1;
   scriptSymOrder.clear();
+  ltoAllVtablesHaveTypeInfos = false;
 }
 
 llvm::raw_fd_ostream Ctx::openAuxiliaryFile(llvm::StringRef filename,
@@ -1036,6 +1037,63 @@
   }
 }
 
+template <class ELFT>
+static void ltoValidateAllVtablesHaveTypeInfos(opt::InputArgList &args) {
+  DenseSet<StringRef> typeInfoSymbols;
+  SmallSetVector<StringRef, 0> vtableSymbols;
+  auto processVtableAndTypeInfoSymbols = [&](StringRef name) {
+    if (name.consume_front("_ZTI"))
+      typeInfoSymbols.insert(name);
+    else if (name.consume_front("_ZTV"))
+      vtableSymbols.insert(name);
+  };
+
+  // Examine all native symbol tables.
+  for (ELFFileBase *f : ctx.objectFiles) {
+    using Elf_Sym = typename ELFT::Sym;
+    for (const Elf_Sym &s : f->template getGlobalELFSyms<ELFT>()) {
+      if (s.st_shndx != SHN_UNDEF) {
+        StringRef name = check(s.getName(f->getStringTable()));
+        processVtableAndTypeInfoSymbols(name);
+      }
+    }
+  }
+
+  for (SharedFile *f : ctx.sharedFiles) {
+    using Elf_Sym = typename ELFT::Sym;
+    for (const Elf_Sym &s : f->template getELFSyms<ELFT>()) {
+      if (s.st_shndx != SHN_UNDEF) {
+        StringRef name = check(s.getName(f->getStringTable()));
+        processVtableAndTypeInfoSymbols(name);
+      }
+    }
+  }
+
+  SmallSetVector<StringRef, 0> vtableSymbolsWithNoRTTI;
+  for (StringRef s : vtableSymbols)
+    if (!typeInfoSymbols.count(s))
+      vtableSymbolsWithNoRTTI.insert(s);
+
+  // Remove known safe symbols.
+  for (auto *arg : args.filtered(OPT_lto_known_safe_vtables)) {
+    StringRef knownSafeName = arg->getValue();
+    if (!knownSafeName.consume_front("_ZTV"))
+      error("--lto-known-safe-vtables=: expected symbol to start with _ZTV, "
+            "but got " +
+            knownSafeName);
+    vtableSymbolsWithNoRTTI.remove(knownSafeName);
+  }
+
+  ctx.ltoAllVtablesHaveTypeInfos = vtableSymbolsWithNoRTTI.empty();
+  // Check for unmatched RTTI symbols
+  for (StringRef s : vtableSymbolsWithNoRTTI) {
+    message(
+        "--lto-validate-all-vtables-have-type-infos: RTTI missing for vtable "
+        "_ZTV" +
+        s + ", --lto-whole-program-visibility disabled");
+  }
+}
+
 static DebugCompressionType getCompressionType(StringRef s, StringRef option) {
   DebugCompressionType type = StringSwitch<DebugCompressionType>(s)
                                   .Case("zlib", DebugCompressionType::Zlib)
@@ -1236,6 +1294,9 @@
   config->ltoWholeProgramVisibility =
       args.hasFlag(OPT_lto_whole_program_visibility,
                    OPT_no_lto_whole_program_visibility, false);
+  config->ltoValidateAllVtablesHaveTypeInfos =
+      args.hasFlag(OPT_lto_validate_all_vtables_have_type_infos,
+                   OPT_no_lto_validate_all_vtables_have_type_infos, false);
   config->ltoo = args::getInteger(args, OPT_lto_O, 2);
   if (config->ltoo > 3)
     error("invalid optimization level for LTO: " + Twine(config->ltoo));
@@ -2815,6 +2876,10 @@
                                 config->ltoEmitAsm ||
                                 !config->thinLTOModulesToCompile.empty();
 
+  // Handle --lto-validate-all-vtables-have-type-infos.
+  if (config->ltoValidateAllVtablesHaveTypeInfos)
+    invokeELFT(ltoValidateAllVtablesHaveTypeInfos, args);
+
   // Do link-time optimization if given files are LLVM bitcode files.
   // This compiles bitcode files into real object files.
   //
diff --git a/ELF/LTO.cpp b/ELF/LTO.cpp
index 1e68d20..eb8f8d7 100644
--- a/ELF/LTO.cpp
+++ b/ELF/LTO.cpp
@@ -152,6 +152,9 @@
   c.DwoDir = std::string(config->dwoDir);
 
   c.HasWholeProgramVisibility = config->ltoWholeProgramVisibility;
+  c.ValidateAllVtablesHaveTypeInfos =
+      config->ltoValidateAllVtablesHaveTypeInfos;
+  c.AllVtablesHaveTypeInfos = ctx.ltoAllVtablesHaveTypeInfos;
   c.AlwaysEmitRegularLTOObj = !config->ltoObjPath.empty();
 
   for (const llvm::StringRef &name : config->thinLTOModulesToCompile)
diff --git a/ELF/Options.td b/ELF/Options.td
index a3adf49..ab61b16 100644
--- a/ELF/Options.td
+++ b/ELF/Options.td
@@ -604,9 +604,14 @@
 defm lto_pgo_warn_mismatch: BB<"lto-pgo-warn-mismatch",
   "turn on warnings about profile cfg mismatch (default)",
   "turn off warnings about profile cfg mismatch">;
+defm lto_known_safe_vtables : EEq<"lto-known-safe-vtables", 
+  "When --lto-validate-all-vtables-have-type-infos is enabled, skip validation on these vtables (_ZTV symbols)">;
 def lto_obj_path_eq: JJ<"lto-obj-path=">;
 def lto_sample_profile: JJ<"lto-sample-profile=">,
   HelpText<"Sample profile file path">;
+defm lto_validate_all_vtables_have_type_infos: BB<"lto-validate-all-vtables-have-type-infos",
+  "Validate that all vtables have type infos for LTO link",
+  "Do not validate that all vtables have type infos for LTO link">;
 defm lto_whole_program_visibility: BB<"lto-whole-program-visibility",
   "Asserts that the LTO link has whole program visibility",
   "Asserts that the LTO link does not have whole program visibility">;
diff --git a/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos.ll b/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos.ll
new file mode 100644
index 0000000..fb35783
--- /dev/null
+++ b/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos.ll
@@ -0,0 +1,26 @@
+; 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 }
+
+@_ZTV6Native = linkonce_odr unnamed_addr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr @_ZTI6Native, ptr @_ZN1A1nEi, ptr @_ZN6Native1fEi] }
+@_ZTS6Native = linkonce_odr constant [8 x i8] c"6Native\00"
+@_ZTI6Native = linkonce_odr constant { ptr, ptr, ptr } { ptr null, ptr @_ZTS6Native, 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 }
+
+
+define linkonce_odr i32 @_ZN6Native1fEi(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 }
diff --git a/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos_no_rtti.ll b/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos_no_rtti.ll
new file mode 100644
index 0000000..4533504
--- /dev/null
+++ b/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos_no_rtti.ll
@@ -0,0 +1,19 @@
+; 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 }
+
+@_ZTV6Native = linkonce_odr unnamed_addr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr null, ptr @_ZN1A1nEi, ptr @_ZN6Native1fEi] }
+
+define linkonce_odr i32 @_ZN6Native1fEi(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 }
diff --git a/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos_ref.ll b/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos_ref.ll
new file mode 100644
index 0000000..43df836
--- /dev/null
+++ b/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos_ref.ll
@@ -0,0 +1,68 @@
+;; Source code:
+;; cat > a.h <<'eof'
+;; struct A { virtual int foo(); };
+;; int bar(A *a);
+;; eof
+;; cat > b.cc <<'eof'
+;; #include "a.h"
+;; struct B : A { int foo() { return 2; } };
+;; int baz() { B b; return bar(&b); }
+;; eof
+;; clang++ -flto=thin b.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.B = type { %struct.A }
+%struct.A = type { ptr }
+
+@_ZTV1B = linkonce_odr dso_local unnamed_addr constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1B, ptr @_ZN1B3fooEv] }, !type !0, !type !1, !type !2, !type !3
+@_ZTS1B = linkonce_odr dso_local constant [3 x i8] c"1B\00"
+@_ZTI1A = external constant ptr
+@_ZTI1B = linkonce_odr dso_local constant { ptr, ptr, ptr } { ptr null, ptr @_ZTS1B, ptr @_ZTI1A }
+@_ZTV1A = external unnamed_addr constant { [3 x ptr] }
+
+define dso_local noundef i32 @_Z3bazv() #0 {
+entry:
+  %b = alloca %struct.B
+  call void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(8) %b)
+  %call = call noundef i32 @_Z3barP1A(ptr noundef %b)
+  ret i32 %call
+}
+
+define linkonce_odr dso_local void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(8) %this) #0 {
+entry:
+  %this.addr = alloca ptr
+  store ptr %this, ptr %this.addr
+  %this1 = load ptr, ptr %this.addr
+  call void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(8) %this1)
+  store ptr getelementptr inbounds ({ [3 x ptr] }, ptr @_ZTV1B, i32 0, inrange i32 0, i32 2), ptr %this1
+  ret void
+}
+
+declare i32 @_Z3barP1A(ptr noundef)
+
+define linkonce_odr dso_local void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(8) %this) #0 {
+entry:
+  %this.addr = alloca ptr
+  store ptr %this, ptr %this.addr
+  %this1 = load ptr, ptr %this.addr
+  store ptr getelementptr inbounds ({ [3 x ptr] }, ptr @_ZTV1A, i32 0, inrange i32 0, i32 2), ptr %this1
+  ret void
+}
+
+define linkonce_odr i32 @_ZN1B3fooEv(ptr noundef nonnull align 8 dereferenceable(8) %this) #0 {
+entry:
+  %this.addr = alloca ptr
+  store ptr %this, ptr %this.addr
+  %this1 = load ptr, ptr %this.addr
+  ret i32 2
+}
+
+;; 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"}
+!2 = !{i64 16, !"_ZTS1B"}
+!3 = !{i64 16, !"_ZTSM1BFivE.virtual"}
diff --git a/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos_undef.ll b/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos_undef.ll
new file mode 100644
index 0000000..6cc55df
--- /dev/null
+++ b/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos_undef.ll
@@ -0,0 +1,16 @@
+; 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"
+
+@_ZTV1B = external unnamed_addr constant { [4 x ptr] }
+
+define linkonce_odr void @_ZN1BC2Ev(ptr %this) #0 {
+  %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 ({ [4 x ptr] }, ptr @_ZTV1B, i32 0, inrange i32 0, i32 2), ptr %this1, align 8
+  ret void
+}
+
+attributes #0 = { noinline optnone }
diff --git a/test/ELF/lto/devirt_validate_vtable_typeinfos.ll b/test/ELF/lto/devirt_validate_vtable_typeinfos.ll
new file mode 100644
index 0000000..d6ac53f
--- /dev/null
+++ b/test/ELF/lto/devirt_validate_vtable_typeinfos.ll
@@ -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}
diff --git a/test/ELF/lto/devirt_validate_vtable_typeinfos_mixed_lto.ll b/test/ELF/lto/devirt_validate_vtable_typeinfos_mixed_lto.ll
new file mode 100644
index 0000000..15040b8
--- /dev/null
+++ b/test/ELF/lto/devirt_validate_vtable_typeinfos_mixed_lto.ll
@@ -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}
diff --git a/test/ELF/lto/devirt_validate_vtable_typeinfos_no_rtti.ll b/test/ELF/lto/devirt_validate_vtable_typeinfos_no_rtti.ll
new file mode 100644
index 0000000..30bd756
--- /dev/null
+++ b/test/ELF/lto/devirt_validate_vtable_typeinfos_no_rtti.ll
@@ -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}
diff --git a/test/ELF/lto/devirt_validate_vtable_typeinfos_ref.ll b/test/ELF/lto/devirt_validate_vtable_typeinfos_ref.ll
new file mode 100644
index 0000000..4ef048d
--- /dev/null
+++ b/test/ELF/lto/devirt_validate_vtable_typeinfos_ref.ll
@@ -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"}