diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h --- a/lld/ELF/Config.h +++ b/lld/ELF/Config.h @@ -242,6 +242,8 @@ bool ltoDebugPassManager; bool ltoEmitAsm; bool ltoUniqueBasicBlockSectionNames; + bool ltoValidateAllVtablesHaveTypeInfos; + bool ltoAllVtablesHaveTypeInfos; bool ltoWholeProgramVisibility; bool mergeArmExidx; bool mipsN32Abi = false; diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp --- a/lld/ELF/Driver.cpp +++ b/lld/ELF/Driver.cpp @@ -1021,6 +1021,87 @@ } } +template +static void ltoValidateAllVtablesHaveTypeInfos(opt::InputArgList &args) { + llvm::DenseSet typeInfoSymbols; + llvm::DenseSet 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 (InputFile *f : ctx.objectFiles) { + for (Symbol *sym : f->getSymbols()) { + processVtableAndTypeInfoSymbols(sym->getName()); + } + } + + for (SharedFile *f : ctx.sharedFiles) { + using Elf_Sym = typename ELFT::Sym; + for (const Elf_Sym &s : f->template getELFSyms()) { + StringRef name = check(s.getName(f->getStringTable())); + processVtableAndTypeInfoSymbols(name); + } + } + + // Skip known safe names + std::vector knownSafeVtableNames = { + // These types are used to implement RTTI itself (Itanium C++ ABI 2.9.5) + // and provided by the runtime meaning they exist as vtables without + // RTTI themselves + // abi::__pointer_to_member_type_info + "_ZTVN10__cxxabiv129__pointer_to_member_type_infoE", + // abi::__enum_type_info + "_ZTVN10__cxxabiv116__enum_type_infoE", + // abi::__array_type_info + "_ZTVN10__cxxabiv117__array_type_infoE", + // abi::__class_type_info + "_ZTVN10__cxxabiv117__class_type_infoE", + // abi::__pbase_type_info + "_ZTVN10__cxxabiv117__pbase_type_infoE", + // abi::__pointer_type_info + "_ZTVN10__cxxabiv119__pointer_type_infoE", + // abi::__function_type_info + "_ZTVN10__cxxabiv120__function_type_infoE", + // abi::__si_class_type_info + "_ZTVN10__cxxabiv120__si_class_type_infoE", + // abi::__vmi_class_type_info + "_ZTVN10__cxxabiv121__vmi_class_type_infoE", + // abi::__fundamental_type_info + "_ZTVN10__cxxabiv123__fundamental_type_infoE"}; + + for (auto *arg : args.filtered(OPT_lto_known_safe_vtables)) { + StringRef name = arg->getValue(); + knownSafeVtableNames.push_back(name); + } + + DenseSet vtableSymbolsWithNoRTTI; + for (auto &s : vtableSymbols) { + if (!typeInfoSymbols.count(s)) { + vtableSymbolsWithNoRTTI.insert(s); + } + } + + // Remove known safe symbols + for (auto &knownSafeName : knownSafeVtableNames) { + knownSafeName.consume_front("_ZTV"); + vtableSymbolsWithNoRTTI.erase(knownSafeName); + } + + config->ltoAllVtablesHaveTypeInfos = true; + // Check for unmatched RTTI symbols + for (auto &s : vtableSymbolsWithNoRTTI) { + warn("--lto-validate-all-vtables-have-type-infos: RTTI missing for vtable " + "_ZTV" + + s + ", --lto-whole-program-visibility disabled"); + config->ltoAllVtablesHaveTypeInfos = false; + } +} + static DebugCompressionType getCompressionType(StringRef s, StringRef option) { DebugCompressionType type = StringSwitch(s) .Case("zlib", DebugCompressionType::Zlib) @@ -1216,6 +1297,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)); @@ -2784,6 +2868,10 @@ config->ltoEmitAsm || !config->thinLTOModulesToCompile.empty(); + // Handle -lto-validate-all-vtables-had-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/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp --- a/lld/ELF/LTO.cpp +++ b/lld/ELF/LTO.cpp @@ -152,6 +152,9 @@ c.DwoDir = std::string(config->dwoDir); c.HasWholeProgramVisibility = config->ltoWholeProgramVisibility; + c.ValidateAllVtablesHaveTypeInfos = + config->ltoValidateAllVtablesHaveTypeInfos; + c.AllVtablesHaveTypeInfos = config->ltoAllVtablesHaveTypeInfos; c.AlwaysEmitRegularLTOObj = !config->ltoObjPath.empty(); for (const llvm::StringRef &name : config->thinLTOModulesToCompile) diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td --- a/lld/ELF/Options.td +++ b/lld/ELF/Options.td @@ -598,9 +598,13 @@ 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 : Eq<"lto-known-safe-vtables", "When --lto-validate-all-vtables-have-type-infos is enabled, skip validation on these vtables">; 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/lld/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos.ll b/lld/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos.ll new file mode 100644 --- /dev/null +++ b/lld/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos.ll @@ -0,0 +1,30 @@ +; 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-grtev4-linux-gnu" + +%struct.A = type { ptr } +%struct.Native = type { %struct.A } + +; Placeholders for testing +@_ZTVN10__cxxabiv117__class_type_infoE = linkonce_odr constant { [2 x ptr] } { [2 x ptr] [ptr null, ptr null] } +@_ZTVN10__cxxabiv120__si_class_type_infoE = linkonce_odr constant { [2 x ptr] } { [2 x ptr] [ptr null, ptr null] } + +@_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 getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv120__si_class_type_infoE, i64 2), 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 getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 2), 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/lld/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos_no_rtti.ll b/lld/test/ELF/lto/Inputs/devirt_validate_vtable_typeinfos_no_rtti.ll new file mode 100644 --- /dev/null +++ b/lld/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-grtev4-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/lld/test/ELF/lto/devirt_validate_vtable_typeinfos.ll b/lld/test/ELF/lto/devirt_validate_vtable_typeinfos.ll new file mode 100644 --- /dev/null +++ b/lld/test/ELF/lto/devirt_validate_vtable_typeinfos.ll @@ -0,0 +1,185 @@ +; REQUIRES: x86 + +;; With --lto-whole-program-visibility, we assume no native types can interfere +;; and thus proceed with devirtualization even in the presence of native types +; RUN: opt --thinlto-bc -o %t1.o %s +; RUN: llvm-as %S/Inputs/devirt_validate_vtable_typeinfos.ll -o %t2.bc +; RUN: llc -filetype=obj %t2.bc -o %t2.o +; 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-prefix=CHECK-IR + +; REMARK-DAG: single-impl: devirtualized a call to _ZN1A1nEi +; REMARK-DAG: single-impl: devirtualized a call to _ZN1D1mEi +; REMARK-DAG: single-impl: devirtualized a call to _ZN1E1mEi + +;; 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. +; RUN: ld.lld %t1.o %t2.o -o %t4 -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \ +; RUN: -mllvm -wholeprogramdevirt-print-index-based 2>&1 | FileCheck %s --check-prefix=VALIDATE +; RUN: llvm-dis %t1.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-VALIDATE-IR + +;; DSOs behave similarly +; RUN: llc -relocation-model=pic -filetype=obj %t2.bc -o %t2_pic.o +; RUN: ld.lld %t2_pic.o -o %t2.so -shared +; RUN: ld.lld %t1.o %t2.so -o %t5 -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \ +; RUN: -mllvm -wholeprogramdevirt-print-index-based 2>&1 | FileCheck %s --check-prefix=VALIDATE +; RUN: llvm-dis %t1.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-VALIDATE-IR + +; VALIDATE-DAG: Skipping devirt on type outside of summary: _ZTS1A +; VALIDATE-DAG: Devirtualized call to {{[0-9]+}} (_ZN1D1mEi) +; VALIDATE-DAG: Devirtualized call to {{[0-9]+}} (_ZN1E1mEi) + +;; 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 +; RUN: llvm-as %S/Inputs/devirt_validate_vtable_typeinfos_no_rtti.ll -o %t2_nortti.bc +; RUN: llc -filetype=obj %t2_nortti.bc -o %t2_nortti.o +; RUN: ld.lld %t1.o %t2_nortti.o -o %t6 -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-prefix=CHECK-NO-RTTI-IR + +;; DSOs behave similarly +; RUN: llc -relocation-model=pic -filetype=obj %t2_nortti.bc -o %t2_nortti_pic.o +; RUN: ld.lld %t2_nortti_pic.o -o %t2_nortti.so -shared +; RUN: ld.lld %t1.o %t2_nortti.so -o %t7 -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-prefix=CHECK-NO-RTTI-IR + +; NO-RTTI-DAG: warning: --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 _ZN1E1mEi + +;; --lto-known-safe-vtables=* can be used to specifically allow types to participate in WPD +;; even if they don't have corresponding RTTI +; RUN: llvm-as %S/Inputs/devirt_validate_vtable_typeinfos_no_rtti.ll -o %t2_nortti.bc +; RUN: llc -filetype=obj %t2_nortti.bc -o %t2_nortti.o +; RUN: ld.lld %t1.o %t2_nortti.o -o %t8 -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-prefix=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-grtev4-linux-gnu" + +%struct.A = type { ptr } +%struct.B = type { %struct.A } +%struct.C = type { %struct.A } +%struct.D = type { ptr } + +;; Placeholders for testing and also check that these don't trigger --lto-validate-all-vtables-have-type-infos +@_ZTVN10__cxxabiv117__class_type_infoE = linkonce_odr constant { [2 x ptr] } { [2 x ptr] [ptr null, ptr null] } +@_ZTVN10__cxxabiv120__si_class_type_infoE = linkonce_odr constant { [2 x ptr] } { [2 x ptr] [ptr null, ptr null] } + +@_ZTV1B = linkonce_odr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr @_ZTI1B, ptr @_ZN1B1fEi, ptr @_ZN1A1nEi] }, !type !0, !type !1 +@_ZTV1C = linkonce_odr constant { [4 x ptr] } { [4 x ptr] [ptr null, ptr @_ZTI1C, ptr @_ZN1C1fEi, ptr @_ZN1A1nEi] }, !type !0, !type !2 +@_ZTV1D = internal constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1D, ptr @_ZN1D1mEi] }, !type !3 +@_ZTV1E = internal constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1E, ptr @_ZN1E1mEi] }, !type !5, !vcall_visibility !7 + +@_ZTS1A = linkonce_odr constant [3 x i8] c"1A\00" +@_ZTI1A = linkonce_odr constant { ptr, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 2), ptr @_ZTS1A } + +@_ZTS1B = linkonce_odr constant [3 x i8] c"1B\00" +@_ZTI1B = linkonce_odr constant { ptr, ptr, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv120__si_class_type_infoE, i64 2), ptr @_ZTS1B, ptr @_ZTI1A } + +@_ZTS1C = linkonce_odr constant [3 x i8] c"1C\00" +@_ZTI1C = linkonce_odr constant { ptr, ptr, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv120__si_class_type_infoE, i64 2), ptr @_ZTS1C, ptr @_ZTI1A } + +@_ZTS1D = internal constant [3 x i8] c"1D\00" +@_ZTI1D = internal constant { ptr, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 2), ptr @_ZTS1D } + +@_ZTS1E = internal constant [3 x i8] c"1E\00" +@_ZTI1E = internal constant { ptr, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 2), ptr @_ZTS1E } + +;; Prevent the vtables from being dead code eliminated. +@llvm.used = appending global [4 x ptr] [ ptr @_ZTV1B, ptr @_ZTV1C, ptr @_ZTV1D, ptr @_ZTV1E ] + +; CHECK-IR-LABEL: define dso_local i32 @_start +define i32 @_start(ptr %obj, ptr %obj2, ptr %obj3, 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: %call3 = tail call i32 %fptr22 + ; CHECK-VALIDATE-IR: %call3 = tail call i32 %fptr22 + ; CHECK-NO-RTTI-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 + ;; Types not present in native files can still be devirtualized + ; CHECK-VALIDATE-IR: %call4 = tail call i32 @_ZN1D1mEi + ;; --lto-whole-program-visibility disabled so no devirtualization + ; CHECK-NO-RTTI-IR: %call4 = tail call i32 %fptr33 + %call4 = tail call i32 %fptr33(ptr nonnull %obj2, i32 %call3) + + %vtable3 = load ptr, ptr %obj3 + %p3 = call i1 @llvm.type.test(ptr %vtable3, metadata !6) + call void @llvm.assume(i1 %p3) + + %fptr44 = load ptr, ptr %vtable3, align 8 + + ;; Check that the call was devirtualized. + ; CHECK-IR: %call5 = tail call i32 @_ZN1E1mEi + ;; Types not present in native files can still be devirtualized + ; CHECK-VALIDATE-IR: %call5 = tail call i32 @_ZN1E1mEi + ;; --lto-whole-program-visibility disabled but this has hidden + ;; visibility so it's still devirtualized + ; CHECK-NO-RTTI-IR: %call5 = tail call i32 @_ZN1E1mEi + %call5 = tail call i32 %fptr44(ptr nonnull %obj2, i32 %call4) + ret i32 %call5 +} +; CHECK-IR-LABEL: ret i32 +; CHECK-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; +} + +define internal i32 @_ZN1E1mEi(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 16, !6} +!6 = distinct !{} +!7 = !{i64 2} diff --git a/lld/test/ELF/lto/devirt_validate_vtable_typeinfos_no_rtti.ll b/lld/test/ELF/lto/devirt_validate_vtable_typeinfos_no_rtti.ll new file mode 100644 --- /dev/null +++ b/lld/test/ELF/lto/devirt_validate_vtable_typeinfos_no_rtti.ll @@ -0,0 +1,103 @@ +; REQUIRES: x86 + +;; With just --lto-whole-program-visibility we successfully devirtualize +; RUN: opt --thinlto-bc -o %t1.o %s +; RUN: ld.lld %t1.o -o %t4 -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-prefix=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 +; RUN: ld.lld %t1.o -o %t2 -save-temps --lto-whole-program-visibility --lto-validate-all-vtables-have-type-infos \ +; RUN: -mllvm -wholeprogramdevirt-print-index-based 2>&1 | FileCheck %s --check-prefix=VALIDATE +; RUN: llvm-dis %t1.o.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-VALIDATE-IR + +; VALIDATE-DAG: Skipping devirt on type outside of summary: _ZTS1A +; VALIDATE-DAG: Devirtualized call to {{[0-9]+}} (_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-grtev4-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 + +;; Prevent the vtables from being dead code eliminated. +@llvm.used = appending global [3 x ptr] [ ptr @_ZTV1B, ptr @_ZTV1C, ptr @_ZTV1D ] + +; CHECK-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 + ;; No resolution for this but because it's local we can + ;; still devirt + ; CHECK-VALIDATE-IR: %call4 = tail call i32 @_ZN1D1mEi + %call4 = tail call i32 %fptr33(ptr nonnull %obj2, i32 %call3) + ret i32 %call4 +} +; CHECK-IR-LABEL: ret i32 +; CHECK-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 !{} diff --git a/llvm/include/llvm/LTO/Config.h b/llvm/include/llvm/LTO/Config.h --- a/llvm/include/llvm/LTO/Config.h +++ b/llvm/include/llvm/LTO/Config.h @@ -80,6 +80,11 @@ /// link. bool HasWholeProgramVisibility = false; + /// If all native vtables have corresponding type information allowing + /// usage of RTTI to block devirtualization on types used in native files. + bool ValidateAllVtablesHaveTypeInfos = false; + 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. diff --git a/llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h b/llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h --- a/llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h +++ b/llvm/include/llvm/Transforms/IPO/WholeProgramDevirt.h @@ -257,7 +257,9 @@ /// devirtualized target name will need adjustment). void runWholeProgramDevirtOnIndex( ModuleSummaryIndex &Summary, std::set &ExportedGUIDs, - std::map> &LocalWPDTargetsMap); + std::map> &LocalWPDTargetsMap, + function_ref typeInfoVisibleOutsideSummary, + bool ValidateAllVtablesHaveTypeInfos); /// Call after cross-module importing to update the recorded single impl /// devirt target names for any locals that were exported. diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp --- a/llvm/lib/LTO/LTO.cpp +++ b/llvm/lib/LTO/LTO.cpp @@ -1694,20 +1694,32 @@ std::set 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 allowed, upgrade public vcall visibility to linkage unit visibility in // the summaries before whole program devirtualization below. updateVCallVisibilityInIndex(ThinLTO.CombinedIndex, - Conf.HasWholeProgramVisibility, + WholeProgramVisibilityEnabledInLTO, DynamicExportSymbols); + auto typeInfoVisibleOutsideSummary = [&](StringRef name) { + return GlobalResolutions.count(name) + ? GlobalResolutions[name].VisibleOutsideSummary + : true; + }; + // Perform index-based WPD. This will return immediately if there are // no index entries in the typeIdMetadata map (e.g. if we are instead // performing IR-based WPD in hybrid regular/thin LTO mode). std::map> LocalWPDTargetsMap; - runWholeProgramDevirtOnIndex(ThinLTO.CombinedIndex, ExportedGUIDs, - LocalWPDTargetsMap); + runWholeProgramDevirtOnIndex( + ThinLTO.CombinedIndex, ExportedGUIDs, LocalWPDTargetsMap, + typeInfoVisibleOutsideSummary, Conf.ValidateAllVtablesHaveTypeInfos); auto isPrevailing = [&](GlobalValue::GUID GUID, const GlobalValueSummary *S) { return ThinLTO.PrevailingModuleForGUID[GUID] == S->modulePath(); diff --git a/llvm/lib/LTO/ThinLTOCodeGenerator.cpp b/llvm/lib/LTO/ThinLTOCodeGenerator.cpp --- a/llvm/lib/LTO/ThinLTOCodeGenerator.cpp +++ b/llvm/lib/LTO/ThinLTOCodeGenerator.cpp @@ -1064,12 +1064,18 @@ // TBD new interface. /* DynamicExportSymbols */ {}); + // FIXME: This needs linker information via a TBD new interface + auto typeInfoVisibleOutsideSummary = [&](StringRef name) { return true; }; + bool ValidateAllVtablesHaveTypeInfos = false; + // Perform index-based WPD. This will return immediately if there are // no index entries in the typeIdMetadata map (e.g. if we are instead // performing IR-based WPD in hybrid regular/thin LTO mode). std::map> LocalWPDTargetsMap; std::set ExportedGUIDs; - runWholeProgramDevirtOnIndex(*Index, ExportedGUIDs, LocalWPDTargetsMap); + runWholeProgramDevirtOnIndex(*Index, ExportedGUIDs, LocalWPDTargetsMap, + typeInfoVisibleOutsideSummary, + ValidateAllVtablesHaveTypeInfos); for (auto GUID : ExportedGUIDs) GUIDPreservedSymbols.insert(GUID); diff --git a/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp b/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp --- a/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp +++ b/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp @@ -725,6 +725,10 @@ // importing. std::map> &LocalWPDTargetsMap; + function_ref typeInfoVisibleOutsideSummary; + + bool ValidateAllVtablesHaveTypeInfos; + MapVector CallSlots; PatternList FunctionsToSkip; @@ -732,13 +736,18 @@ DevirtIndex( ModuleSummaryIndex &ExportSummary, std::set &ExportedGUIDs, - std::map> &LocalWPDTargetsMap) + std::map> &LocalWPDTargetsMap, + function_ref typeInfoVisibleOutsideSummary, + bool ValidateAllVtablesHaveTypeInfos) : ExportSummary(ExportSummary), ExportedGUIDs(ExportedGUIDs), - LocalWPDTargetsMap(LocalWPDTargetsMap) { + LocalWPDTargetsMap(LocalWPDTargetsMap), + typeInfoVisibleOutsideSummary(typeInfoVisibleOutsideSummary), + ValidateAllVtablesHaveTypeInfos(ValidateAllVtablesHaveTypeInfos) { FunctionsToSkip.init(SkipFunctionNames); } bool tryFindVirtualCallTargets(std::vector &TargetsForSlot, + bool &isLocal, const TypeIdCompatibleVtableInfo TIdInfo, uint64_t ByteOffset); @@ -857,8 +866,12 @@ void runWholeProgramDevirtOnIndex( ModuleSummaryIndex &Summary, std::set &ExportedGUIDs, - std::map> &LocalWPDTargetsMap) { - DevirtIndex(Summary, ExportedGUIDs, LocalWPDTargetsMap).run(); + std::map> &LocalWPDTargetsMap, + function_ref typeInfoVisibleOutsideSummary, + bool ValidateAllVtablesHaveTypeInfos) { + DevirtIndex(Summary, ExportedGUIDs, LocalWPDTargetsMap, + typeInfoVisibleOutsideSummary, ValidateAllVtablesHaveTypeInfos) + .run(); } void updateIndexWPDForExports( @@ -1045,8 +1058,8 @@ } bool DevirtIndex::tryFindVirtualCallTargets( - std::vector &TargetsForSlot, const TypeIdCompatibleVtableInfo TIdInfo, - uint64_t ByteOffset) { + std::vector &TargetsForSlot, bool &isLocal, + 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 @@ -1059,12 +1072,12 @@ // distinguishing path when compiling the source file. In that case we // conservatively return false early. const GlobalVarSummary *VS = nullptr; - bool LocalFound = false; + isLocal = false; for (const auto &S : P.VTableVI.getSummaryList()) { if (GlobalValue::isLocalLinkage(S->linkage())) { - if (LocalFound) + if (isLocal) return false; - LocalFound = true; + isLocal = true; } auto *CurVS = cast(S->getBaseObject()); if (!CurVS->vTableFuncs().empty() || @@ -2447,6 +2460,7 @@ // function implementation at offset S.first.ByteOffset, and add to // TargetsForSlot. std::vector TargetsForSlot; + bool isLocal; auto TidSummary = ExportSummary.getTypeIdCompatibleVtableSummary(S.first.TypeID); assert(TidSummary); // The type id summary would have been created while building the NameByGUID @@ -2454,8 +2468,22 @@ WholeProgramDevirtResolution *Res = &ExportSummary.getTypeIdSummary(S.first.TypeID) ->WPDRes[S.first.ByteOffset]; - if (tryFindVirtualCallTargets(TargetsForSlot, *TidSummary, + if (tryFindVirtualCallTargets(TargetsForSlot, isLocal, *TidSummary, S.first.ByteOffset)) { + // If the type name is referenced outside of what our summary covers + // it can be potentially derived from and be unsafe to devirtualize + if (ValidateAllVtablesHaveTypeInfos) { + // Local linkage is always safe + // Note that if RTTI is not enabled in LTO unit, the typename + // symbol doesn't exist and we don't have a resolution meaning + // typeInfoVisibleOutsideSummary will return true. + if (!isLocal && typeInfoVisibleOutsideSummary(S.first.TypeID)) { + if (PrintSummaryDevirt) + errs() << "Skipping devirt on type outside of summary: " + << S.first.TypeID << "\n"; + continue; + } + } if (!trySingleImplDevirt(TargetsForSlot, S.first, S.second, Res, DevirtTargets))