Index: llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp =================================================================== --- llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp +++ llvm/lib/Transforms/IPO/WholeProgramDevirt.cpp @@ -287,7 +287,12 @@ struct CallSiteInfo { std::vector CallSites; + bool HasTypeTestAssumeUsers; std::vector TypeCheckedLoadUsers; + + bool isExported() const { + return HasTypeTestAssumeUsers || !TypeCheckedLoadUsers.empty(); + } }; struct VTableSlotInfo { @@ -368,9 +373,11 @@ const std::set &TypeMemberInfos, uint64_t ByteOffset); - void applySingleImplDevirt(VTableSlotInfo &SlotInfo, Constant *TheFn); + void applySingleImplDevirt(VTableSlotInfo &SlotInfo, Constant *TheFn, + bool &IsExported); bool trySingleImplDevirt(MutableArrayRef TargetsForSlot, - VTableSlotInfo &SlotInfo); + VTableSlotInfo &SlotInfo, + WholeProgramDevirtResolution *Res); bool tryEvaluateFunctionsWithArgs( MutableArrayRef TargetsForSlot, @@ -587,7 +594,7 @@ } void DevirtModule::applySingleImplDevirt(VTableSlotInfo &SlotInfo, - Constant *TheFn) { + Constant *TheFn, bool &IsExported) { auto Apply = [&](CallSiteInfo &CSInfo) { for (auto &&VCallSite : CSInfo.CallSites) { if (RemarksEnabled) @@ -598,6 +605,10 @@ if (VCallSite.NumUnsafeUses) --*VCallSite.NumUnsafeUses; } + if (CSInfo.isExported()) { + IsExported = true; + CSInfo.TypeCheckedLoadUsers.clear(); + } }; Apply(SlotInfo.CSInfo); for (auto &P : SlotInfo.ConstCSInfo) @@ -606,7 +617,7 @@ bool DevirtModule::trySingleImplDevirt( MutableArrayRef TargetsForSlot, - VTableSlotInfo &SlotInfo) { + VTableSlotInfo &SlotInfo, WholeProgramDevirtResolution *Res) { // See if the program contains a single implementation of this virtual // function. Function *TheFn = TargetsForSlot[0].Fn; @@ -617,7 +628,23 @@ // If so, update each call site to call that implementation directly. if (RemarksEnabled) TargetsForSlot[0].WasDevirt = true; - applySingleImplDevirt(SlotInfo, TheFn); + + bool IsExported = false; + applySingleImplDevirt(SlotInfo, TheFn, IsExported); + if (!IsExported) + return false; + + // If the only implementation has local linkage, we must promote to external + // to make it visible to thin LTO objects. + if (TheFn->hasLocalLinkage()) { + TheFn->setLinkage(GlobalValue::ExternalLinkage); + TheFn->setVisibility(GlobalValue::HiddenVisibility); + TheFn->setName(TheFn->getName() + "$merged"); + } + + Res->TheKind = WholeProgramDevirtResolution::SingleImpl; + Res->SingleImplName = TheFn->getName(); + return true; } @@ -1056,11 +1083,19 @@ if (!FS) continue; // FIXME: Only add live functions. + for (FunctionSummary::VFuncId VF : FS->type_test_assume_vcalls()) + for (Metadata *MD : MetadataByGUID[VF.GUID]) + CallSlots[{MD, VF.Offset}].CSInfo.HasTypeTestAssumeUsers = true; for (FunctionSummary::VFuncId VF : FS->type_checked_load_vcalls()) for (Metadata *MD : MetadataByGUID[VF.GUID]) CallSlots[{MD, VF.Offset}].CSInfo.TypeCheckedLoadUsers.push_back( FS); for (const FunctionSummary::ConstVCall &VC : + FS->type_test_assume_const_vcalls()) + for (Metadata *MD : MetadataByGUID[VC.VFunc.GUID]) + CallSlots[{MD, VC.VFunc.Offset}] + .ConstCSInfo[VC.Args].HasTypeTestAssumeUsers = true; + for (const FunctionSummary::ConstVCall &VC : FS->type_checked_load_const_vcalls()) for (Metadata *MD : MetadataByGUID[VC.VFunc.GUID]) CallSlots[{MD, VC.VFunc.Offset}] @@ -1079,7 +1114,14 @@ std::vector TargetsForSlot; if (tryFindVirtualCallTargets(TargetsForSlot, TypeIdMap[S.first.TypeID], S.first.ByteOffset)) { - if (!trySingleImplDevirt(TargetsForSlot, S.second) && + WholeProgramDevirtResolution *Res = nullptr; + if (Action == PassSummaryAction::Export && isa(S.first.TypeID)) + Res = + &Summary + ->getTypeIdSummary(cast(S.first.TypeID)->getString()) + .WPDRes[S.first.ByteOffset]; + + if (!trySingleImplDevirt(TargetsForSlot, S.second, Res) && tryVirtualConstProp(TargetsForSlot, S.second)) DidVirtualConstProp = true; Index: llvm/test/Transforms/WholeProgramDevirt/export-single-impl.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/WholeProgramDevirt/export-single-impl.ll @@ -0,0 +1,72 @@ +; RUN: opt -wholeprogramdevirt -wholeprogramdevirt-summary-action=export -wholeprogramdevirt-read-summary=%S/Inputs/export.yaml -wholeprogramdevirt-write-summary=%t -S -o - %s | FileCheck %s +; RUN: FileCheck --check-prefix=SUMMARY %s < %t + +; SUMMARY: TypeIdMap: +; SUMMARY-NEXT: typeid1: +; SUMMARY-NEXT: TTRes: +; SUMMARY-NEXT: Kind: Unsat +; SUMMARY-NEXT: SizeM1BitWidth: 0 +; SUMMARY-NEXT: WPDRes: +; SUMMARY-NEXT: 0: +; SUMMARY-NEXT: Kind: SingleImpl +; SUMMARY-NEXT: SingleImplName: vf1 +; SUMMARY-NEXT: ResByArg: +; SUMMARY-NEXT: typeid2: +; SUMMARY-NEXT: TTRes: +; SUMMARY-NEXT: Kind: Unsat +; SUMMARY-NEXT: SizeM1BitWidth: 0 +; SUMMARY-NEXT: WPDRes: +; SUMMARY-NEXT: 0: +; SUMMARY-NEXT: Kind: SingleImpl +; SUMMARY-NEXT: SingleImplName: vf2 +; SUMMARY-NEXT: ResByArg: +; SUMMARY-NEXT: typeid3: +; SUMMARY-NEXT: TTRes: +; SUMMARY-NEXT: Kind: Unsat +; SUMMARY-NEXT: SizeM1BitWidth: 0 +; SUMMARY-NEXT: WPDRes: +; SUMMARY-NEXT: 0: +; SUMMARY-NEXT: Kind: SingleImpl +; SUMMARY-NEXT: SingleImplName: vf3 +; SUMMARY-NEXT: ResByArg: +; SUMMARY-NEXT: typeid4: +; SUMMARY-NEXT: TTRes: +; SUMMARY-NEXT: Kind: Unsat +; SUMMARY-NEXT: SizeM1BitWidth: 0 +; SUMMARY-NEXT: WPDRes: +; SUMMARY-NEXT: 0: +; SUMMARY-NEXT: Kind: SingleImpl +; SUMMARY-NEXT: SingleImplName: 'vf4$merged' +; SUMMARY-NEXT: ResByArg: +; SUMMARY-NEXT: ... + +; CHECK: @vt1 = constant void (i8*)* @vf1 +@vt1 = constant void (i8*)* @vf1, !type !0 + +; CHECK: @vt2 = constant void (i8*)* @vf2 +@vt2 = constant void (i8*)* @vf2, !type !1 + +@vt3 = constant void (i8*)* @vf3, !type !2 + +; CHECK: @vt4 = constant void (i8*)* @"vf4$merged" +@vt4 = constant void (i8*)* @vf4, !type !3 + +; CHECK: declare void @vf1(i8*) +declare void @vf1(i8*) + +; CHECK: define void @vf2(i8*) +define void @vf2(i8*) { + ret void +} + +declare void @vf3(i8*) + +; CHECK: define hidden void @"vf4$merged" +define internal void @vf4(i8*) { + ret void +} + +!0 = !{i32 0, !"typeid1"} +!1 = !{i32 0, !"typeid2"} +!2 = !{i32 0, !"typeid3"} +!3 = !{i32 0, !"typeid4"}