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 @@ -91,6 +91,7 @@ #include "llvm/Support/Errc.h" #include "llvm/Support/Error.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/GlobPattern.h" #include "llvm/Support/MathExtras.h" #include "llvm/Transforms/IPO.h" #include "llvm/Transforms/IPO/FunctionAttrs.h" @@ -156,6 +157,29 @@ cl::ZeroOrMore, cl::desc("Disable whole program visibility (overrides enabling options)")); +/// Provide way to prevent certain function from being devirtualized +cl::list + SkipFunctionNames("wholeprogramdevirt-skip", + cl::desc("Prevent function(s) from being devirtualized"), + cl::Hidden, cl::ZeroOrMore, cl::CommaSeparated); + +namespace { +struct PatternList { + std::vector Patterns; + template void init(const T &StringList) { + for (const auto &S : StringList) + if (Expected Pat = GlobPattern::create(S)) + Patterns.push_back(std::move(*Pat)); + } + bool match(StringRef S) { + for (const GlobPattern &P : Patterns) + if (P.match(S)) + return true; + return false; + } +}; +} // namespace + // Find the minimum offset that we may store a value of size Size bits at. If // IsAfter is set, look for an offset before the object, otherwise look for an // offset after the object. @@ -491,6 +515,7 @@ // eliminate the type check by RAUWing the associated llvm.type.test call with // true. std::map NumUnsafeUsesForTypeTest; + PatternList FunctionsToSkip; DevirtModule(Module &M, function_ref AARGetter, function_ref OREGetter, @@ -506,6 +531,7 @@ IntPtrTy(M.getDataLayout().getIntPtrType(M.getContext(), 0)), RemarksEnabled(areRemarksEnabled()), OREGetter(OREGetter) { assert(!(ExportSummary && ImportSummary)); + FunctionsToSkip.init(SkipFunctionNames); } bool areRemarksEnabled(); @@ -614,12 +640,16 @@ MapVector CallSlots; + PatternList FunctionsToSkip; + DevirtIndex( ModuleSummaryIndex &ExportSummary, std::set &ExportedGUIDs, std::map> &LocalWPDTargetsMap) : ExportSummary(ExportSummary), ExportedGUIDs(ExportedGUIDs), - LocalWPDTargetsMap(LocalWPDTargetsMap) {} + LocalWPDTargetsMap(LocalWPDTargetsMap) { + FunctionsToSkip.init(SkipFunctionNames); + } bool tryFindVirtualCallTargets(std::vector &TargetsForSlot, const TypeIdCompatibleVtableInfo TIdInfo, @@ -926,6 +956,9 @@ if (!Fn) return false; + if (FunctionsToSkip.match(Fn->getName())) + return false; + // We can disregard __cxa_pure_virtual as a possible call target, as // calls to pure virtuals are UB. if (Fn->getName() == "__cxa_pure_virtual") @@ -1105,6 +1138,11 @@ if (!Size) return false; + // Don't devirtualize function if we're told to skip it + // in -wholeprogramdevirt-skip. + if (FunctionsToSkip.match(TheFn.name())) + return false; + // If the summary list contains multiple summaries where at least one is // a local, give up, as we won't know which (possibly promoted) name to use. for (auto &S : TheFn.getSummaryList()) diff --git a/llvm/test/ThinLTO/X86/devirt.ll b/llvm/test/ThinLTO/X86/devirt.ll --- a/llvm/test/ThinLTO/X86/devirt.ll +++ b/llvm/test/ThinLTO/X86/devirt.ll @@ -47,6 +47,21 @@ ; RUN: -r=%t2.o,_ZTV1D,px 2>&1 | FileCheck %s --check-prefix=REMARK ; RUN: llvm-dis %t3.1.4.opt.bc -o - | FileCheck %s --check-prefix=CHECK-IR +; Check that we're able to prevent specific function from being +; devirtualized when running index based WPD. +; RUN: llvm-lto2 run %t2.o -save-temps -pass-remarks=. \ +; RUN: -whole-program-visibility \ +; RUN: -wholeprogramdevirt-skip=_ZN1A1nEi \ +; RUN: -o %t3 \ +; RUN: -r=%t2.o,test,px \ +; RUN: -r=%t2.o,_ZN1A1nEi,p \ +; RUN: -r=%t2.o,_ZN1B1fEi,p \ +; RUN: -r=%t2.o,_ZN1C1fEi,p \ +; RUN: -r=%t2.o,_ZN1D1mEi,p \ +; RUN: -r=%t2.o,_ZTV1B,px \ +; RUN: -r=%t2.o,_ZTV1C,px \ +; RUN: -r=%t2.o,_ZTV1D,px 2>&1 | FileCheck %s --check-prefix=SKIP + ; New PM, Index based WPD ; RUN: llvm-lto2 run %t2.o -save-temps -use-new-pm -pass-remarks=. \ ; RUN: -whole-program-visibility \ @@ -110,6 +125,8 @@ ; REMARK-DAG: single-impl: devirtualized a call to _ZN1A1nEi ; REMARK-DAG: single-impl: devirtualized a call to _ZN1D1mEi +; SKIP-NOT: devirtualized a call to _ZN1A1nEi + 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" diff --git a/llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-check.ll b/llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-check.ll --- a/llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-check.ll +++ b/llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-check.ll @@ -1,4 +1,12 @@ ; RUN: opt -S -wholeprogramdevirt -whole-program-visibility -pass-remarks=wholeprogramdevirt %s 2>&1 | FileCheck %s +; Skipping vf0i1 is identical to setting public LTO visibility. We don't devirtualize vf0i1 and all other +; virtual call targets. +; RUN: opt -S -wholeprogramdevirt -whole-program-visibility -pass-remarks=wholeprogramdevirt -wholeprogramdevirt-skip=vf0i1 %s 2>&1 | FileCheck %s --check-prefix=SKIP +; We have two set of call targets {vf0i1, vf1i1} and {vf1i32, vf2i32, vf3i32, vf4i32}. +; The command below prevents both of them from devirtualization. +; RUN: opt -S -wholeprogramdevirt -whole-program-visibility -pass-remarks=wholeprogramdevirt -wholeprogramdevirt-skip=vf0i1,vf1i32 %s 2>&1 | FileCheck %s --check-prefix=SKIP-ALL +; Check wildcard +; RUN: opt -S -wholeprogramdevirt -whole-program-visibility -pass-remarks=wholeprogramdevirt -wholeprogramdevirt-skip=vf?i1 %s 2>&1 | FileCheck %s --check-prefix=SKIP target datalayout = "e-p:64:64" target triple = "x86_64-unknown-linux-gnu" @@ -14,6 +22,15 @@ ; CHECK: remark: :0:0: devirtualized vf4i32 ; CHECK-NOT: devirtualized +; SKIP: remark: :0:0: virtual-const-prop: devirtualized a call to vf1i32 +; SKIP: remark: :0:0: devirtualized vf1i32 +; SKIP: remark: :0:0: devirtualized vf2i32 +; SKIP: remark: :0:0: devirtualized vf3i32 +; SKIP: remark: :0:0: devirtualized vf4i32 +; SKIP-NOT: devirtualized + +; SKIP-ALL-NOT: devirtualized + ; CHECK: [[VT1DATA:@[^ ]*]] = private constant { [8 x i8], [3 x i8*], [0 x i8] } { [8 x i8] c"\00\00\00\01\01\00\00\00", [3 x i8*] [i8* bitcast (i1 (i8*)* @vf0i1 to i8*), i8* bitcast (i1 (i8*)* @vf1i1 to i8*), i8* bitcast (i32 (i8*)* @vf1i32 to i8*)], [0 x i8] zeroinitializer }, section "vt1sec", !type [[T8:![0-9]+]] @vt1 = constant [3 x i8*] [ i8* bitcast (i1 (i8*)* @vf0i1 to i8*),