Index: include/llvm/Transforms/IPO/WholeProgramDevirt.h =================================================================== --- include/llvm/Transforms/IPO/WholeProgramDevirt.h +++ include/llvm/Transforms/IPO/WholeProgramDevirt.h @@ -134,6 +134,9 @@ // Whether the target is big endian. bool IsBigEndian; + // Whether at least one call site to the target was devirtualized. + bool WasDevirt; + // The minimum byte offset before the address point. This covers the bytes in // the vtable object before the address point (e.g. RTTI, access-to-top, // vtables for other base classes) and is equal to the offset from the start Index: lib/Transforms/IPO/WholeProgramDevirt.cpp =================================================================== --- lib/Transforms/IPO/WholeProgramDevirt.cpp +++ lib/Transforms/IPO/WholeProgramDevirt.cpp @@ -217,15 +217,16 @@ // of that field for details. unsigned *NumUnsafeUses; - void emitRemark() { + void emitRemark(const Twine &OptName, const Twine &TargetName) { Function *F = CS.getCaller(); - emitOptimizationRemark(F->getContext(), DEBUG_TYPE, *F, - CS.getInstruction()->getDebugLoc(), - "devirtualized call"); + emitOptimizationRemark( + F->getContext(), DEBUG_TYPE, *F, + CS.getInstruction()->getDebugLoc(), + OptName + Twine(": devirtualized a call to ") + TargetName); } - void replaceAndErase(Value *New) { - emitRemark(); + void replaceAndErase(const Twine &OptName, const Twine &TargetName, Value *New) { + emitRemark(OptName, TargetName); CS->replaceAllUsesWith(New); if (auto II = dyn_cast(CS.getInstruction())) { BranchInst::Create(II->getNormalDest(), CS.getInstruction()); @@ -271,16 +272,16 @@ tryFindVirtualCallTargets(std::vector &TargetsForSlot, const std::set &TypeMemberInfos, uint64_t ByteOffset); - bool trySingleImplDevirt(ArrayRef TargetsForSlot, + bool trySingleImplDevirt(MutableArrayRef TargetsForSlot, MutableArrayRef CallSites); bool tryEvaluateFunctionsWithArgs( MutableArrayRef TargetsForSlot, ArrayRef Args); bool tryUniformRetValOpt(IntegerType *RetType, - ArrayRef TargetsForSlot, + MutableArrayRef TargetsForSlot, MutableArrayRef CallSites); bool tryUniqueRetValOpt(unsigned BitWidth, - ArrayRef TargetsForSlot, + MutableArrayRef TargetsForSlot, MutableArrayRef CallSites); bool tryVirtualConstProp(MutableArrayRef TargetsForSlot, ArrayRef CallSites); @@ -393,7 +394,7 @@ } bool DevirtModule::trySingleImplDevirt( - ArrayRef TargetsForSlot, + MutableArrayRef TargetsForSlot, MutableArrayRef CallSites) { // See if the program contains a single implementation of this virtual // function. @@ -402,9 +403,10 @@ if (TheFn != Target.Fn) return false; + TargetsForSlot[0].WasDevirt = true; // If so, update each call site to call that implementation directly. for (auto &&VCallSite : CallSites) { - VCallSite.emitRemark(); + VCallSite.emitRemark("simple-impl", TheFn->getName()); VCallSite.CS.setCalledFunction(ConstantExpr::getBitCast( TheFn, VCallSite.CS.getCalledValue()->getType())); // This use is no longer unsafe. @@ -442,7 +444,7 @@ } bool DevirtModule::tryUniformRetValOpt( - IntegerType *RetType, ArrayRef TargetsForSlot, + IntegerType *RetType, MutableArrayRef TargetsForSlot, MutableArrayRef CallSites) { // Uniform return value optimization. If all functions return the same // constant, replace all calls with that constant. @@ -453,12 +455,15 @@ auto TheRetValConst = ConstantInt::get(RetType, TheRetVal); for (auto Call : CallSites) - Call.replaceAndErase(TheRetValConst); + Call.replaceAndErase("uniform-ret-val", TargetsForSlot[0].Fn->getName(), + TheRetValConst); + for (auto &&Target : TargetsForSlot) + Target.WasDevirt = true; return true; } bool DevirtModule::tryUniqueRetValOpt( - unsigned BitWidth, ArrayRef TargetsForSlot, + unsigned BitWidth, MutableArrayRef TargetsForSlot, MutableArrayRef CallSites) { // IsOne controls whether we look for a 0 or a 1. auto tryUniqueRetValOptFor = [&](bool IsOne) { @@ -482,8 +487,12 @@ OneAddr = B.CreateConstGEP1_64(OneAddr, UniqueMember->Offset); Value *Cmp = B.CreateICmp(IsOne ? ICmpInst::ICMP_EQ : ICmpInst::ICMP_NE, Call.VTable, OneAddr); - Call.replaceAndErase(Cmp); + Call.replaceAndErase("unique-ret-val", TargetsForSlot[0].Fn->getName(), Cmp); } + // Update devirtualization statistics for targets. + for (auto &&Target : TargetsForSlot) + Target.WasDevirt = true; + return true; }; @@ -591,6 +600,9 @@ setAfterReturnValues(TargetsForSlot, AllocAfter, BitWidth, OffsetByte, OffsetBit); + for (auto &&Target : TargetsForSlot) + Target.WasDevirt = true; + // Rewrite each call to a load from OffsetByte/OffsetBit. for (auto Call : CSByConstantArg.second) { IRBuilder<> B(Call.CS.getInstruction()); @@ -600,27 +612,19 @@ Value *Bit = ConstantInt::get(Int8Ty, 1ULL << OffsetBit); Value *BitsAndBit = B.CreateAnd(Bits, Bit); auto IsBitSet = B.CreateICmpNE(BitsAndBit, ConstantInt::get(Int8Ty, 0)); - Call.replaceAndErase(IsBitSet); + Call.replaceAndErase("virtual-const-prop-1-bit", + TargetsForSlot[0].Fn->getName(), IsBitSet); } else { Value *ValAddr = B.CreateBitCast(Addr, RetType->getPointerTo()); Value *Val = B.CreateLoad(RetType, ValAddr); - Call.replaceAndErase(Val); + Call.replaceAndErase("virtual-const-prop", + TargetsForSlot[0].Fn->getName(), Val); } } } return true; } -static void emitTargetsRemarks(const std::vector &TargetsForSlot) { - for (const VirtualCallTarget &Target : TargetsForSlot) { - Function *F = Target.Fn; - DISubprogram *SP = F->getSubprogram(); - DebugLoc DL = SP ? DebugLoc::get(SP->getScopeLine(), 0, SP) : DebugLoc(); - emitOptimizationRemark(F->getContext(), DEBUG_TYPE, *F, DL, - std::string("devirtualized ") + F->getName().str()); - } -} - void DevirtModule::rebuildGlobal(VTableBits &B) { if (B.Before.Bytes.empty() && B.After.Bytes.empty()) return; @@ -817,6 +821,7 @@ // For each (type, offset) pair: bool DidVirtualConstProp = false; + std::map DevirtTargets; for (auto &S : CallSlots) { // Search each of the members of the type identifier for the virtual // function implementation at offset S.first.ByteOffset, and add to @@ -826,15 +831,23 @@ S.first.ByteOffset)) continue; - if (trySingleImplDevirt(TargetsForSlot, S.second)) { - emitTargetsRemarks(TargetsForSlot); - continue; + if (!trySingleImplDevirt(TargetsForSlot, S.second)) + if (tryVirtualConstProp(TargetsForSlot, S.second)) + DidVirtualConstProp = true; + + for (auto &&T : TargetsForSlot) { + if (T.WasDevirt) { + DevirtTargets[T.Fn->getName()] = T.Fn; + } } + } - if (tryVirtualConstProp(TargetsForSlot, S.second)) { - emitTargetsRemarks(TargetsForSlot); - DidVirtualConstProp = true; - } + for (auto &DT : DevirtTargets) { + Function *F = DT.second; + DISubprogram *SP = F->getSubprogram(); + DebugLoc DL = SP ? DebugLoc::get(SP->getScopeLine(), 0, SP) : DebugLoc(); + emitOptimizationRemark(F->getContext(), DEBUG_TYPE, *F, DL, + Twine("devirtualized ") + F->getName()); } // If we were able to eliminate all unsafe uses for a type checked load, Index: test/Transforms/WholeProgramDevirt/devirt-single-impl-check.ll =================================================================== --- test/Transforms/WholeProgramDevirt/devirt-single-impl-check.ll +++ test/Transforms/WholeProgramDevirt/devirt-single-impl-check.ll @@ -3,7 +3,7 @@ target datalayout = "e-p:64:64" target triple = "x86_64-unknown-linux-gnu" -; CHECK: remark: :0:0: devirtualized call +; CHECK: remark: :0:0: simple-impl: devirtualized a call to vf ; CHECK-NOT: devirtualized call @vt1 = constant [1 x i8*] [i8* bitcast (void (i8*)* @vf to i8*)], !type !0 Index: test/Transforms/WholeProgramDevirt/devirt-single-impl.ll =================================================================== --- test/Transforms/WholeProgramDevirt/devirt-single-impl.ll +++ test/Transforms/WholeProgramDevirt/devirt-single-impl.ll @@ -3,9 +3,7 @@ target datalayout = "e-p:64:64" target triple = "x86_64-unknown-linux-gnu" -; CHECK: remark: :0:0: devirtualized call -; CHECK: remark: :0:0: devirtualized vf -; CHECK: remark: :0:0: devirtualized vf +; CHECK: remark: :0:0: simple-impl: devirtualized a call to vf @vt1 = constant [1 x i8*] [i8* bitcast (void (i8*)* @vf to i8*)], !type !0 @vt2 = constant [1 x i8*] [i8* bitcast (void (i8*)* @vf to i8*)], !type !0 Index: test/Transforms/WholeProgramDevirt/virtual-const-prop-check.ll =================================================================== --- test/Transforms/WholeProgramDevirt/virtual-const-prop-check.ll +++ test/Transforms/WholeProgramDevirt/virtual-const-prop-check.ll @@ -3,22 +3,16 @@ target datalayout = "e-p:64:64" target triple = "x86_64-unknown-linux-gnu" -; CHECK: remark: :0:0: devirtualized call +; CHECK: remark: :0:0: virtual-const-prop: devirtualized a call to vf1i32 +; CHECK: remark: :0:0: virtual-const-prop-1-bit: devirtualized a call to vf1i1 +; CHECK: remark: :0:0: virtual-const-prop-1-bit: devirtualized a call to vf0i1 +; CHECK: remark: :0:0: devirtualized vf0i1 +; CHECK: remark: :0:0: devirtualized vf1i1 ; CHECK: remark: :0:0: devirtualized vf1i32 ; CHECK: remark: :0:0: devirtualized vf2i32 ; CHECK: remark: :0:0: devirtualized vf3i32 ; CHECK: remark: :0:0: devirtualized vf4i32 -; CHECK: remark: :0:0: devirtualized call -; CHECK: remark: :0:0: devirtualized vf1i1 -; CHECK: remark: :0:0: devirtualized vf0i1 -; CHECK: remark: :0:0: devirtualized vf1i1 -; CHECK: remark: :0:0: devirtualized vf0i1 -; CHECK: remark: :0:0: devirtualized call -; CHECK: remark: :0:0: devirtualized vf0i1 -; CHECK: remark: :0:0: devirtualized vf1i1 -; CHECK: remark: :0:0: devirtualized vf0i1 -; CHECK: remark: :0:0: devirtualized vf1i1 -; CHECK-NOT: devirtualized call +; CHECK-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*] [