summaryrefslogtreecommitdiff
path: root/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp')
-rw-r--r--llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp107
1 files changed, 85 insertions, 22 deletions
diff --git a/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp b/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp
index cf1ff405c493..7a8946110785 100644
--- a/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp
+++ b/llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp
@@ -94,6 +94,7 @@
#include "llvm/Support/MathExtras.h"
#include "llvm/Transforms/IPO.h"
#include "llvm/Transforms/IPO/FunctionAttrs.h"
+#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#include "llvm/Transforms/Utils/Evaluator.h"
#include <algorithm>
#include <cstddef>
@@ -144,24 +145,32 @@ static cl::opt<bool>
/// This is needed to support legacy tests that don't contain
/// !vcall_visibility metadata (the mere presense of type tests
/// previously implied hidden visibility).
-cl::opt<bool>
+static cl::opt<bool>
WholeProgramVisibility("whole-program-visibility", cl::init(false),
cl::Hidden, cl::ZeroOrMore,
cl::desc("Enable whole program visibility"));
/// Provide a way to force disable whole program for debugging or workarounds,
/// when enabled via the linker.
-cl::opt<bool> DisableWholeProgramVisibility(
+static cl::opt<bool> DisableWholeProgramVisibility(
"disable-whole-program-visibility", cl::init(false), cl::Hidden,
cl::ZeroOrMore,
cl::desc("Disable whole program visibility (overrides enabling options)"));
/// Provide way to prevent certain function from being devirtualized
-cl::list<std::string>
+static cl::list<std::string>
SkipFunctionNames("wholeprogramdevirt-skip",
cl::desc("Prevent function(s) from being devirtualized"),
cl::Hidden, cl::ZeroOrMore, cl::CommaSeparated);
+/// Mechanism to add runtime checking of devirtualization decisions, trapping on
+/// any that are not correct. Useful for debugging undefined behavior leading to
+/// failures with WPD.
+static cl::opt<bool>
+ CheckDevirt("wholeprogramdevirt-check", cl::init(false), cl::Hidden,
+ cl::ZeroOrMore,
+ cl::desc("Add code to trap on incorrect devirtualizations"));
+
namespace {
struct PatternList {
std::vector<GlobPattern> Patterns;
@@ -509,6 +518,11 @@ struct DevirtModule {
MapVector<VTableSlot, VTableSlotInfo> CallSlots;
+ // Calls that have already been optimized. We may add a call to multiple
+ // VTableSlotInfos if vtable loads are coalesced and need to make sure not to
+ // optimize a call more than once.
+ SmallPtrSet<CallBase *, 8> OptimizedCalls;
+
// This map keeps track of the number of "unsafe" uses of a loaded function
// pointer. The key is the associated llvm.type.test intrinsic call generated
// by this pass. An unsafe use is one that calls the loaded function pointer
@@ -777,8 +791,9 @@ namespace llvm {
/// 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 updateVCallVisibilityInModule(Module &M,
- bool WholeProgramVisibilityEnabledInLTO) {
+void updateVCallVisibilityInModule(
+ Module &M, bool WholeProgramVisibilityEnabledInLTO,
+ const DenseSet<GlobalValue::GUID> &DynamicExportSymbols) {
if (!hasWholeProgramVisibility(WholeProgramVisibilityEnabledInLTO))
return;
for (GlobalVariable &GV : M.globals())
@@ -786,22 +801,29 @@ void updateVCallVisibilityInModule(Module &M,
// the vtable definitions. We won't have an existing vcall_visibility
// metadata on vtable definitions with public visibility.
if (GV.hasMetadata(LLVMContext::MD_type) &&
- GV.getVCallVisibility() == GlobalObject::VCallVisibilityPublic)
+ 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()))
GV.setVCallVisibilityMetadata(GlobalObject::VCallVisibilityLinkageUnit);
}
/// 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 updateVCallVisibilityInIndex(ModuleSummaryIndex &Index,
- bool WholeProgramVisibilityEnabledInLTO) {
+void updateVCallVisibilityInIndex(
+ ModuleSummaryIndex &Index, bool WholeProgramVisibilityEnabledInLTO,
+ const DenseSet<GlobalValue::GUID> &DynamicExportSymbols) {
if (!hasWholeProgramVisibility(WholeProgramVisibilityEnabledInLTO))
return;
for (auto &P : Index) {
for (auto &S : P.second.SummaryList) {
auto *GVar = dyn_cast<GlobalVarSummary>(S.get());
- if (!GVar || GVar->vTableFuncs().empty() ||
- GVar->getVCallVisibility() != GlobalObject::VCallVisibilityPublic)
+ if (!GVar ||
+ GVar->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(P.first))
continue;
GVar->setVCallVisibility(GlobalObject::VCallVisibilityLinkageUnit);
}
@@ -901,7 +923,7 @@ bool DevirtModule::runForTesting(
ExitOnErr(errorCodeToError(EC));
WriteIndexToFile(*Summary, OS);
} else {
- raw_fd_ostream OS(ClWriteSummary, EC, sys::fs::OF_Text);
+ raw_fd_ostream OS(ClWriteSummary, EC, sys::fs::OF_TextWithCRLF);
ExitOnErr(errorCodeToError(EC));
yaml::Output Out(OS);
Out << *Summary;
@@ -986,12 +1008,10 @@ bool DevirtIndex::tryFindVirtualCallTargets(
std::vector<ValueInfo> &TargetsForSlot, const TypeIdCompatibleVtableInfo TIdInfo,
uint64_t ByteOffset) {
for (const TypeIdOffsetVtableInfo &P : TIdInfo) {
- // Find the first non-available_externally linkage vtable initializer.
+ // Find a representative copy of the vtable initializer.
// We can have multiple available_externally, linkonce_odr and weak_odr
- // vtable initializers, however we want to skip available_externally as they
- // do not have type metadata attached, and therefore the summary will not
- // contain any vtable functions. We can also have multiple external
- // vtable initializers in the case of comdats, which we cannot check here.
+ // vtable initializers. We can also have multiple external vtable
+ // initializers in the case of comdats, which we cannot check here.
// The linker should give an error in this case.
//
// Also, handle the case of same-named local Vtables with the same path
@@ -1006,14 +1026,27 @@ bool DevirtIndex::tryFindVirtualCallTargets(
return false;
LocalFound = true;
}
- if (!GlobalValue::isAvailableExternallyLinkage(S->linkage())) {
- VS = cast<GlobalVarSummary>(S->getBaseObject());
+ auto *CurVS = cast<GlobalVarSummary>(S->getBaseObject());
+ if (!CurVS->vTableFuncs().empty() ||
+ // Previously clang did not attach the necessary type metadata to
+ // available_externally vtables, in which case there would not
+ // be any vtable functions listed in the summary and we need
+ // to treat this case conservatively (in case the bitcode is old).
+ // However, we will also not have any vtable functions in the
+ // case of a pure virtual base class. In that case we do want
+ // to set VS to avoid treating it conservatively.
+ !GlobalValue::isAvailableExternallyLinkage(S->linkage())) {
+ VS = CurVS;
// We cannot perform whole program devirtualization analysis on a vtable
// with public LTO visibility.
if (VS->getVCallVisibility() == GlobalObject::VCallVisibilityPublic)
return false;
}
}
+ // There will be no VS if all copies are available_externally having no
+ // type metadata. In that case we can't safely perform WPD.
+ if (!VS)
+ return false;
if (!VS->isLive())
continue;
for (auto VTP : VS->vTableFuncs()) {
@@ -1036,11 +1069,34 @@ void DevirtModule::applySingleImplDevirt(VTableSlotInfo &SlotInfo,
return;
auto Apply = [&](CallSiteInfo &CSInfo) {
for (auto &&VCallSite : CSInfo.CallSites) {
+ if (!OptimizedCalls.insert(&VCallSite.CB).second)
+ continue;
+
if (RemarksEnabled)
VCallSite.emitRemark("single-impl",
TheFn->stripPointerCasts()->getName(), OREGetter);
- VCallSite.CB.setCalledOperand(ConstantExpr::getBitCast(
- TheFn, VCallSite.CB.getCalledOperand()->getType()));
+ auto &CB = VCallSite.CB;
+ assert(!CB.getCalledFunction() && "devirtualizing direct call?");
+ IRBuilder<> Builder(&CB);
+ Value *Callee =
+ Builder.CreateBitCast(TheFn, CB.getCalledOperand()->getType());
+
+ // If checking is enabled, add support to compare the virtual function
+ // pointer to the devirtualized target. In case of a mismatch, perform a
+ // debug trap.
+ if (CheckDevirt) {
+ auto *Cond = Builder.CreateICmpNE(CB.getCalledOperand(), Callee);
+ Instruction *ThenTerm =
+ SplitBlockAndInsertIfThen(Cond, &CB, /*Unreachable=*/false);
+ Builder.SetInsertPoint(ThenTerm);
+ Function *TrapFn = Intrinsic::getDeclaration(&M, Intrinsic::debugtrap);
+ auto *CallTrap = Builder.CreateCall(TrapFn);
+ CallTrap->setDebugLoc(CB.getDebugLoc());
+ }
+
+ // Devirtualize.
+ CB.setCalledOperand(Callee);
+
// This use is no longer unsafe.
if (VCallSite.NumUnsafeUses)
--*VCallSite.NumUnsafeUses;
@@ -1106,7 +1162,7 @@ bool DevirtModule::trySingleImplDevirt(
// to make it visible to thin LTO objects. We can only get here during the
// ThinLTO export phase.
if (TheFn->hasLocalLinkage()) {
- std::string NewName = (TheFn->getName() + "$merged").str();
+ std::string NewName = (TheFn->getName() + ".llvm.merged").str();
// Since we are renaming the function, any comdats with the same name must
// also be renamed. This is required when targeting COFF, as the comdat name
@@ -1359,10 +1415,13 @@ bool DevirtModule::tryEvaluateFunctionsWithArgs(
void DevirtModule::applyUniformRetValOpt(CallSiteInfo &CSInfo, StringRef FnName,
uint64_t TheRetVal) {
- for (auto Call : CSInfo.CallSites)
+ for (auto Call : CSInfo.CallSites) {
+ if (!OptimizedCalls.insert(&Call.CB).second)
+ continue;
Call.replaceAndErase(
"uniform-ret-val", FnName, RemarksEnabled, OREGetter,
ConstantInt::get(cast<IntegerType>(Call.CB.getType()), TheRetVal));
+ }
CSInfo.markDevirt();
}
@@ -1468,6 +1527,8 @@ void DevirtModule::applyUniqueRetValOpt(CallSiteInfo &CSInfo, StringRef FnName,
bool IsOne,
Constant *UniqueMemberAddr) {
for (auto &&Call : CSInfo.CallSites) {
+ if (!OptimizedCalls.insert(&Call.CB).second)
+ continue;
IRBuilder<> B(&Call.CB);
Value *Cmp =
B.CreateICmp(IsOne ? ICmpInst::ICMP_EQ : ICmpInst::ICMP_NE, Call.VTable,
@@ -1536,6 +1597,8 @@ bool DevirtModule::tryUniqueRetValOpt(
void DevirtModule::applyVirtualConstProp(CallSiteInfo &CSInfo, StringRef FnName,
Constant *Byte, Constant *Bit) {
for (auto Call : CSInfo.CallSites) {
+ if (!OptimizedCalls.insert(&Call.CB).second)
+ continue;
auto *RetType = cast<IntegerType>(Call.CB.getType());
IRBuilder<> B(&Call.CB);
Value *Addr =