diff --git a/llvm/include/llvm/Transforms/IPO/Attributor.h b/llvm/include/llvm/Transforms/IPO/Attributor.h index abcdc9318a7a2..05dcd83d90c6a 100644 --- a/llvm/include/llvm/Transforms/IPO/Attributor.h +++ b/llvm/include/llvm/Transforms/IPO/Attributor.h @@ -118,6 +118,7 @@ #include "llvm/IR/Attributes.h" #include "llvm/IR/ConstantRange.h" #include "llvm/IR/Constants.h" +#include "llvm/IR/GlobalValue.h" #include "llvm/IR/InstIterator.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" @@ -3948,6 +3949,14 @@ struct AAIsDead using Base = StateWrapper, AbstractAttribute>; AAIsDead(const IRPosition &IRP, Attributor &A) : Base(IRP) {} + /// See AbstractAttribute::isValidIRPositionForInit + static bool isValidIRPositionForInit(Attributor &A, const IRPosition &IRP) { + if (IRP.getPositionKind() == IRPosition::IRP_FUNCTION) + return isa(IRP.getAnchorValue()) && + !cast(IRP.getAnchorValue()).isDeclaration(); + return true; + } + /// State encoding bits. A set bit in the state means the property holds. enum { HAS_NO_EFFECT = 1 << 0, @@ -6139,6 +6148,45 @@ struct AAAddressSpace : public StateWrapper { static const char ID; }; +/// An abstract interface for llvm::GlobalValue information interference. +struct AAGlobalValueInfo + : public StateWrapper { + AAGlobalValueInfo(const IRPosition &IRP, Attributor &A) + : StateWrapper(IRP) {} + + /// See AbstractAttribute::isValidIRPositionForInit + static bool isValidIRPositionForInit(Attributor &A, const IRPosition &IRP) { + if (IRP.getPositionKind() != IRPosition::IRP_FLOAT) + return false; + auto *GV = dyn_cast(&IRP.getAnchorValue()); + if (!GV) + return false; + return GV->hasLocalLinkage(); + } + + /// Create an abstract attribute view for the position \p IRP. + static AAGlobalValueInfo &createForPosition(const IRPosition &IRP, + Attributor &A); + + /// Return true iff \p U is a potential use of the associated global value. + virtual bool isPotentialUse(const Use &U) const = 0; + + /// See AbstractAttribute::getName() + const std::string getName() const override { return "AAGlobalValueInfo"; } + + /// See AbstractAttribute::getIdAddr() + const char *getIdAddr() const override { return &ID; } + + /// This function should return true if the type of the \p AA is + /// AAGlobalValueInfo + static bool classof(const AbstractAttribute *AA) { + return (AA->getIdAddr() == &ID); + } + + /// Unique ID (due to the unique address) + static const char ID; +}; + /// An abstract interface for indirect call information interference. struct AAIndirectCallInfo : public StateWrapper { diff --git a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp index ada4def7cfdb1..d91b4f158c829 100644 --- a/llvm/lib/Transforms/IPO/AttributorAttributes.cpp +++ b/llvm/lib/Transforms/IPO/AttributorAttributes.cpp @@ -193,6 +193,7 @@ PIPE_OPERATOR(AAAssumptionInfo) PIPE_OPERATOR(AAUnderlyingObjects) PIPE_OPERATOR(AAAddressSpace) PIPE_OPERATOR(AAIndirectCallInfo) +PIPE_OPERATOR(AAGlobalValueInfo) #undef PIPE_OPERATOR @@ -12056,6 +12057,134 @@ struct AAUnderlyingObjectsFunction final : AAUnderlyingObjectsImpl { }; } // namespace +/// ------------------------ Global Value Info ------------------------------- +namespace { +struct AAGlobalValueInfoFloating : public AAGlobalValueInfo { + AAGlobalValueInfoFloating(const IRPosition &IRP, Attributor &A) + : AAGlobalValueInfo(IRP, A) {} + + /// See AbstractAttribute::initialize(...). + void initialize(Attributor &A) override {} + + bool checkUse(Attributor &A, const Use &U, bool &Follow, + SmallVectorImpl &Worklist) { + Instruction *UInst = dyn_cast(U.getUser()); + if (!UInst) { + Follow = true; + return true; + } + + LLVM_DEBUG(dbgs() << "[AAGlobalValueInfo] Check use: " << *U.get() << " in " + << *UInst << "\n"); + + if (auto *Cmp = dyn_cast(U.getUser())) { + int Idx = &Cmp->getOperandUse(0) == &U; + if (isa(Cmp->getOperand(Idx))) + return true; + return U == &getAnchorValue(); + } + + // Explicitly catch return instructions. + if (isa(UInst)) { + auto CallSitePred = [&](AbstractCallSite ACS) { + Worklist.push_back(ACS.getInstruction()); + return true; + }; + bool UsedAssumedInformation = false; + // TODO: We should traverse the uses or add a "non-call-site" CB. + if (!A.checkForAllCallSites(CallSitePred, *UInst->getFunction(), + /*RequireAllCallSites=*/true, this, + UsedAssumedInformation)) + return false; + return true; + } + + // For now we only use special logic for call sites. However, the tracker + // itself knows about a lot of other non-capturing cases already. + auto *CB = dyn_cast(UInst); + if (!CB) + return false; + // Direct calls are OK uses. + if (CB->isCallee(&U)) + return true; + // Non-argument uses are scary. + if (!CB->isArgOperand(&U)) + return false; + // TODO: Iterate callees. + auto *Fn = dyn_cast(CB->getCalledOperand()); + if (!Fn || !A.isFunctionIPOAmendable(*Fn)) + return false; + + unsigned ArgNo = CB->getArgOperandNo(&U); + Worklist.push_back(Fn->getArg(ArgNo)); + return true; + } + + ChangeStatus updateImpl(Attributor &A) override { + unsigned NumUsesBefore = Uses.size(); + + SmallPtrSet Visited; + SmallVector Worklist; + Worklist.push_back(&getAnchorValue()); + + auto UsePred = [&](const Use &U, bool &Follow) -> bool { + Uses.insert(&U); + switch (DetermineUseCaptureKind(U, nullptr)) { + case UseCaptureKind::NO_CAPTURE: + return checkUse(A, U, Follow, Worklist); + case UseCaptureKind::MAY_CAPTURE: + return checkUse(A, U, Follow, Worklist); + case UseCaptureKind::PASSTHROUGH: + Follow = true; + return true; + } + return true; + }; + auto EquivalentUseCB = [&](const Use &OldU, const Use &NewU) { + Uses.insert(&OldU); + return true; + }; + + while (!Worklist.empty()) { + const Value *V = Worklist.pop_back_val(); + if (!Visited.insert(V).second) + continue; + if (!A.checkForAllUses(UsePred, *this, *V, + /* CheckBBLivenessOnly */ true, + DepClassTy::OPTIONAL, + /* IgnoreDroppableUses */ true, EquivalentUseCB)) { + return indicatePessimisticFixpoint(); + } + } + + return Uses.size() == NumUsesBefore ? ChangeStatus::UNCHANGED + : ChangeStatus::CHANGED; + } + + bool isPotentialUse(const Use &U) const override { + return !isValidState() || Uses.contains(&U); + } + + /// See AbstractAttribute::manifest(...). + ChangeStatus manifest(Attributor &A) override { + return ChangeStatus::UNCHANGED; + } + + /// See AbstractAttribute::getAsStr(). + const std::string getAsStr(Attributor *A) const override { + return "[" + std::to_string(Uses.size()) + " uses]"; + } + + void trackStatistics() const override { + STATS_DECLTRACK_FLOATING_ATTR(GlobalValuesTracked); + } + +private: + /// Set of (transitive) uses of this GlobalValue. + SmallPtrSet Uses; +}; +} // namespace + /// ------------------------ Indirect Call Info ------------------------------- namespace { struct AAIndirectCallInfoCallSite : public AAIndirectCallInfo { @@ -12085,11 +12214,30 @@ struct AAIndirectCallInfoCallSite : public AAIndirectCallInfo { ChangeStatus updateImpl(Attributor &A) override { CallBase *CB = cast(getCtxI()); + const Use &CalleeUse = CB->getCalledOperandUse(); Value *FP = CB->getCalledOperand(); SmallSetVector AssumedCalleesNow; bool AllCalleesKnownNow = AllCalleesKnown; + auto CheckPotentialCalleeUse = [&](Function &PotentialCallee, + bool &UsedAssumedInformation) { + const auto *GIAA = A.getAAFor( + *this, IRPosition::value(PotentialCallee), DepClassTy::OPTIONAL); + if (!GIAA || GIAA->isPotentialUse(CalleeUse)) + return true; + UsedAssumedInformation = !GIAA->isAtFixpoint(); + return false; + }; + + auto AddPotentialCallees = [&]() { + for (auto *PotentialCallee : PotentialCallees) { + bool UsedAssumedInformation = false; + if (CheckPotentialCalleeUse(*PotentialCallee, UsedAssumedInformation)) + AssumedCalleesNow.insert(PotentialCallee); + } + }; + // Use simplification to find potential callees, if !callees was present, // fallback to that set if necessary. bool UsedAssumedInformation = false; @@ -12099,7 +12247,7 @@ struct AAIndirectCallInfoCallSite : public AAIndirectCallInfo { UsedAssumedInformation)) { if (PotentialCallees.empty()) return indicatePessimisticFixpoint(); - AssumedCalleesNow.set_union(PotentialCallees); + AddPotentialCallees(); } // Try to find a reason for \p Fn not to be a potential callee. If none was @@ -12112,6 +12260,13 @@ struct AAIndirectCallInfoCallSite : public AAIndirectCallInfo { if (CachedResult.has_value()) return CachedResult.value(); + bool UsedAssumedInformation = false; + if (!CheckPotentialCalleeUse(Fn, UsedAssumedInformation)) { + if (!UsedAssumedInformation) + CachedResult = false; + return false; + } + int NumFnArgs = Fn.arg_size(); int NumCBArgs = CB->arg_size(); @@ -12147,16 +12302,12 @@ struct AAIndirectCallInfoCallSite : public AAIndirectCallInfo { continue; } if (!PotentialCallees.empty()) { - AssumedCalleesNow.set_union(PotentialCallees); + AddPotentialCallees(); break; } AllCalleesKnownNow = false; } - // If we can't specialize at all, give up now. - if (!AllCalleesKnownNow && AssumedCalleesNow.empty()) - return indicatePessimisticFixpoint(); - if (AssumedCalleesNow == AssumedCallees && AllCalleesKnown == AllCalleesKnownNow) return ChangeStatus::UNCHANGED; @@ -12168,6 +12319,9 @@ struct AAIndirectCallInfoCallSite : public AAIndirectCallInfo { /// See AbstractAttribute::manifest(...). ChangeStatus manifest(Attributor &A) override { + // If we can't specialize at all, give up now. + if (!AllCalleesKnown && AssumedCallees.empty()) + return ChangeStatus::UNCHANGED; ChangeStatus Changed = ChangeStatus::UNCHANGED; CallBase *CB = cast(getCtxI()); @@ -12550,6 +12704,7 @@ const char AAAssumptionInfo::ID = 0; const char AAUnderlyingObjects::ID = 0; const char AAAddressSpace::ID = 0; const char AAIndirectCallInfo::ID = 0; +const char AAGlobalValueInfo::ID = 0; // Macro magic to create the static generator function for attributes that // follow the naming scheme. @@ -12688,6 +12843,8 @@ CREATE_ALL_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAUnderlyingObjects) CREATE_ABSTRACT_ATTRIBUTE_FOR_ONE_POSITION(IRP_CALL_SITE, CallSite, AAIndirectCallInfo) +CREATE_ABSTRACT_ATTRIBUTE_FOR_ONE_POSITION(IRP_FLOAT, Floating, + AAGlobalValueInfo) CREATE_FUNCTION_ONLY_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAHeapToStack) CREATE_FUNCTION_ONLY_ABSTRACT_ATTRIBUTE_FOR_POSITION(AAUndefinedBehavior) diff --git a/llvm/test/Transforms/Attributor/callgraph.ll b/llvm/test/Transforms/Attributor/callgraph.ll index 272cf53ae9d8e..547137046ca93 100644 --- a/llvm/test/Transforms/Attributor/callgraph.ll +++ b/llvm/test/Transforms/Attributor/callgraph.ll @@ -18,7 +18,7 @@ define dso_local void @func1() { br i1 %1, label %2, label %3 2: ; preds = %0 - call void @func2() + call void @func2(i1 false) br label %3 3: ; preds = %2, %0 @@ -27,14 +27,48 @@ define dso_local void @func1() { } declare void @func3() -declare void @func4() - -define dso_local void @func2() { -; CHECK-LABEL: @func2( -; CHECK-NEXT: call void @func4() +define internal void @func4() { +; CHECK-LABEL: @func4( +; CHECK-NEXT: call void @func3() ; CHECK-NEXT: ret void ; - call void @func4() + call void @func3() + ret void +} +define internal void @internal_good() { +; CHECK-LABEL: @internal_good( +; CHECK-NEXT: call void @void(ptr @func4) +; CHECK-NEXT: ret void +; + call void @void(ptr @func4) + ret void +} + +define dso_local void @func2(i1 %c) { +; UPTO2-LABEL: @func2( +; UPTO2-NEXT: [[F:%.*]] = select i1 [[C:%.*]], ptr @internal_good, ptr @func4 +; UPTO2-NEXT: [[TMP1:%.*]] = icmp eq ptr [[F]], @func4 +; UPTO2-NEXT: br i1 [[TMP1]], label [[TMP2:%.*]], label [[TMP3:%.*]] +; UPTO2: 2: +; UPTO2-NEXT: call void @func4() +; UPTO2-NEXT: br label [[TMP6:%.*]] +; UPTO2: 3: +; UPTO2-NEXT: br i1 true, label [[TMP4:%.*]], label [[TMP5:%.*]] +; UPTO2: 4: +; UPTO2-NEXT: call void @internal_good() +; UPTO2-NEXT: br label [[TMP6]] +; UPTO2: 5: +; UPTO2-NEXT: unreachable +; UPTO2: 6: +; UPTO2-NEXT: ret void +; +; LIMI0-LABEL: @func2( +; LIMI0-NEXT: [[F:%.*]] = select i1 [[C:%.*]], ptr @internal_good, ptr @func4 +; LIMI0-NEXT: call void [[F]](), !callees !0 +; LIMI0-NEXT: ret void +; + %f = select i1 %c, ptr @internal_good, ptr @func4 + call void %f() ret void } @@ -61,7 +95,7 @@ define void @func5(i32 %0) { ; LIMI0-LABEL: @func5( ; LIMI0-NEXT: [[TMP2:%.*]] = icmp ne i32 [[TMP0:%.*]], 0 ; LIMI0-NEXT: [[TMP3:%.*]] = select i1 [[TMP2]], ptr @func4, ptr @func3 -; LIMI0-NEXT: call void [[TMP3]](), !callees !0 +; LIMI0-NEXT: call void [[TMP3]](), !callees !1 ; LIMI0-NEXT: ret void ; %2 = icmp ne i32 %0, 0 @@ -151,7 +185,7 @@ define i32 @non_matching_fp1(i1 %c1, i1 %c2, i1 %c) { ; LIMI0-NEXT: [[FP1:%.*]] = select i1 [[C1:%.*]], ptr @retI32, ptr @takeI32 ; LIMI0-NEXT: [[FP2:%.*]] = select i1 [[C2:%.*]], ptr @retFloatTakeFloat, ptr @void ; LIMI0-NEXT: [[FP:%.*]] = select i1 [[C:%.*]], ptr [[FP1]], ptr [[FP2]] -; LIMI0-NEXT: [[CALL:%.*]] = call i32 [[FP]](i32 42), !callees !1 +; LIMI0-NEXT: [[CALL:%.*]] = call i32 [[FP]](i32 42), !callees !2 ; LIMI0-NEXT: ret i32 [[CALL]] ; %fp1 = select i1 %c1, ptr @retI32, ptr @takeI32 @@ -214,7 +248,7 @@ define i32 @non_matching_fp1_noundef(i1 %c1, i1 %c2, i1 %c) { ; LIMI0-NEXT: [[FP1:%.*]] = select i1 [[C1:%.*]], ptr @retI32, ptr @takeI32 ; LIMI0-NEXT: [[FP2:%.*]] = select i1 [[C2:%.*]], ptr @retFloatTakeFloatFloatNoundef, ptr @void ; LIMI0-NEXT: [[FP:%.*]] = select i1 [[C:%.*]], ptr [[FP1]], ptr [[FP2]] -; LIMI0-NEXT: [[CALL:%.*]] = call i32 [[FP]](i32 42), !callees !2 +; LIMI0-NEXT: [[CALL:%.*]] = call i32 [[FP]](i32 42), !callees !3 ; LIMI0-NEXT: ret i32 [[CALL]] ; %fp1 = select i1 %c1, ptr @retI32, ptr @takeI32 @@ -489,6 +523,8 @@ define void @func6() { ret void } +; Cannot be internal_good as it is internal and we see all uses. +; Can be func4 since it escapes. define void @func7(ptr %unknown) { ; UPTO2-LABEL: @func7( ; UPTO2-NEXT: [[TMP1:%.*]] = icmp eq ptr [[UNKNOWN:%.*]], @func3 @@ -507,7 +543,7 @@ define void @func7(ptr %unknown) { ; UPTO2-NEXT: ret void ; ; LIMI0-LABEL: @func7( -; LIMI0-NEXT: call void [[UNKNOWN:%.*]](), !callees !0 +; LIMI0-NEXT: call void [[UNKNOWN:%.*]](), !callees !1 ; LIMI0-NEXT: ret void ; call void %unknown(), !callees !2 @@ -528,7 +564,7 @@ define void @undef_in_callees() { ; ; LIMI0-LABEL: @undef_in_callees( ; LIMI0-NEXT: cond.end.i: -; LIMI0-NEXT: call void undef(ptr undef, i32 undef, ptr undef), !callees !5 +; LIMI0-NEXT: call void undef(ptr undef, i32 undef, ptr undef), !callees !6 ; LIMI0-NEXT: ret void ; cond.end.i: @@ -566,6 +602,9 @@ cond.end.i: ; These ones are added because of the callees metadata. ; DOT-DAG: Node[[FUNC7]] -> Node[[FUNC3]]; ; DOT-DAG: Node[[FUNC7]] -> Node[[FUNC4]]; + +; UTC_ARGS: --enable + ;. ; UNLIM: [[META0:![0-9]+]] = !{!1} ; UNLIM: [[META1:![0-9]+]] = !{i64 0, i1 false} @@ -577,12 +616,13 @@ cond.end.i: ; LIMI2: [[META3:![0-9]+]] = !{i64 0, i1 false} ; LIMI2: [[META4:![0-9]+]] = distinct !{ptr undef, ptr null} ;. -; LIMI0: [[META0:![0-9]+]] = !{ptr @func3, ptr @func4} -; LIMI0: [[META1:![0-9]+]] = !{ptr @takeI32, ptr @retI32, ptr @void, ptr @retFloatTakeFloat} -; LIMI0: [[META2:![0-9]+]] = !{ptr @takeI32, ptr @retI32, ptr @void} -; LIMI0: [[META3:![0-9]+]] = !{!4} -; LIMI0: [[META4:![0-9]+]] = !{i64 0, i1 false} -; LIMI0: [[META5:![0-9]+]] = distinct !{ptr undef, ptr null} +; LIMI0: [[META0:![0-9]+]] = !{ptr @func4, ptr @internal_good} +; LIMI0: [[META1:![0-9]+]] = !{ptr @func3, ptr @func4} +; LIMI0: [[META2:![0-9]+]] = !{ptr @takeI32, ptr @retI32, ptr @void, ptr @retFloatTakeFloat} +; LIMI0: [[META3:![0-9]+]] = !{ptr @takeI32, ptr @retI32, ptr @void} +; LIMI0: [[META4:![0-9]+]] = !{!5} +; LIMI0: [[META5:![0-9]+]] = !{i64 0, i1 false} +; LIMI0: [[META6:![0-9]+]] = distinct !{ptr undef, ptr null} ;. ;; NOTE: These prefixes are unused and the list is autogenerated. Do not add tests below this line: ; DOT: {{.*}}