diff --git a/llvm/include/llvm/IR/Constant.h b/llvm/include/llvm/IR/Constant.h --- a/llvm/include/llvm/IR/Constant.h +++ b/llvm/include/llvm/IR/Constant.h @@ -198,6 +198,24 @@ /// hanging off of the globals. void removeDeadConstantUsers() const; + /// Return true if the constant has exactly one live use. + /// + /// This returns the same result as calling Value::hasOneUse after + /// Constant::removeDeadConstantUsers, but doesn't remove dead constants. + bool hasOneLiveUse() const { return hasNLiveUses(1); } + + /// Return true if the constant has exactly N live uses. + /// + /// This returns the same result as calling Value::hasNUses after + /// Constant::removeDeadConstantUsers, but doesn't remove dead constants. + bool hasNLiveUses(unsigned N) const; + + /// Return true if the constant has N live uses or more. + /// + /// This returns the same result as calling Value::hasNUsesOrMore after + /// Constant::removeDeadConstantUsers, but doesn't remove dead constants. + bool hasNLiveUsesOrMore(unsigned N) const; + const Constant *stripPointerCasts() const { return cast(Value::stripPointerCasts()); } diff --git a/llvm/lib/Analysis/InlineCost.cpp b/llvm/lib/Analysis/InlineCost.cpp --- a/llvm/lib/Analysis/InlineCost.cpp +++ b/llvm/lib/Analysis/InlineCost.cpp @@ -1859,8 +1859,8 @@ SingleBBBonus = Threshold * SingleBBBonusPercent / 100; VectorBonus = Threshold * VectorBonusPercent / 100; - bool OnlyOneCallAndLocalLinkage = - F.hasLocalLinkage() && F.hasOneUse() && &F == Call.getCalledFunction(); + bool OnlyOneCallAndLocalLinkage = F.hasLocalLinkage() && F.hasOneLiveUse() && + &F == Call.getCalledFunction(); // If there is only one call of the function, and it has internal linkage, // the cost of inlining it drops dramatically. It may seem odd to update // Cost in updateThreshold, but the bonus depends on the logic in this method. @@ -2665,7 +2665,7 @@ onBlockAnalyzed(BB); } - bool OnlyOneCallAndLocalLinkage = F.hasLocalLinkage() && F.hasOneUse() && + bool OnlyOneCallAndLocalLinkage = F.hasLocalLinkage() && F.hasOneLiveUse() && &F == CandidateCall.getCalledFunction(); // If this is a noduplicate call, we can still inline as long as // inlining this would cause the removal of the caller (so the instruction diff --git a/llvm/lib/IR/Constants.cpp b/llvm/lib/IR/Constants.cpp --- a/llvm/lib/IR/Constants.cpp +++ b/llvm/lib/IR/Constants.cpp @@ -716,6 +716,8 @@ /// If the specified constantexpr is dead, remove it. This involves recursively /// eliminating any dead users of the constantexpr. +/// +/// This should be kept in sync with constantIsDead. static bool removeDeadUsersOfConstant(const Constant *C) { if (isa(C)) return false; // Cannot remove this @@ -736,6 +738,23 @@ return true; } +/// Return true if the constant is dead. +/// +/// This should be kept in sync with removeDeadUsersOfConstant. +static bool constantIsDead(const Constant *C) { + if (isa(C)) + return false; + + for (const User *U : C->users()) { + const Constant *User = dyn_cast(U); + if (!User) + return false; // Non-constant usage; + if (!constantIsDead(User)) + return false; // Constant wasn't dead + } + + return true; +} void Constant::removeDeadConstantUsers() const { Value::const_user_iterator I = user_begin(), E = user_end(); @@ -764,6 +783,28 @@ } } +bool constantHasLiveUses(const Constant *C, unsigned N, bool OrMore) { + unsigned NumUses = 0; + for (const Use &use : C->uses()) { + const Constant *User = dyn_cast(use.getUser()); + if (!User || !constantIsDead(User)) { + ++NumUses; + + if (NumUses > N) + return OrMore; + } + } + return NumUses == N; +} + +bool Constant::hasNLiveUses(unsigned N) const { + return constantHasLiveUses(this, N, /* OrMore= */ false); +} + +bool Constant::hasNLiveUsesOrMore(unsigned N) const { + return constantHasLiveUses(this, N, /* OrMore= */ true); +} + Constant *Constant::replaceUndefsWith(Constant *C, Constant *Replacement) { assert(C && Replacement && "Expected non-nullptr constant arguments"); Type *Ty = C->getType(); diff --git a/llvm/test/Transforms/Inline/inline-cost-dead-users.ll b/llvm/test/Transforms/Inline/inline-cost-dead-users.ll new file mode 100644 --- /dev/null +++ b/llvm/test/Transforms/Inline/inline-cost-dead-users.ll @@ -0,0 +1,32 @@ +; Test to ensure that the inlining cost model isn't tripped up by dead constant users. +; In this case, the call to g via a bitcasted function pointer is canonicalized to +; a direct call to g with bitcasted arguments, leaving the original bitcast +; as a dead use of g. + +; RUN: opt < %s -instcombine -inline -pass-remarks=inline -S 2>&1 \ +; RUN: | FileCheck %s + +; Inline costs of f and g should be the same. + +; CHECK: 'f' inlined into 'h' with (cost=[[EXPECTED_COST:.+]], threshold={{.+}}) +; CHECK: 'g' inlined into 'h' with (cost=[[EXPECTED_COST]], threshold={{.+}}) + +%0 = type { i64, i64, i64 } +%1 = type { i64, i64, i64 } + +define internal void @f(%0* align 8 %a) unnamed_addr { +start: + ret void +} + +define internal void @g(%0* align 8 %a) unnamed_addr { +start: + ret void +} + +define void @h(%0* align 8 %a, %1* align 8 %b) unnamed_addr { +start: + call void @f(%0* align 8 %a) + call void bitcast (void (%0*)* @g to void (%1*)*)(%1* align 8 %b) + ret void +} diff --git a/llvm/test/Transforms/Inline/last-callsite.ll b/llvm/test/Transforms/Inline/last-callsite.ll --- a/llvm/test/Transforms/Inline/last-callsite.ll +++ b/llvm/test/Transforms/Inline/last-callsite.ll @@ -260,10 +260,10 @@ ; constant expression cannot be inlined because the constant expression forms ; a second use. If this part starts failing we need to use more complex ; constant expressions to reference a particular function with them. - %sink = alloca i1 - store volatile i1 icmp ne (i64 ptrtoint (void (i1)* @test4_g to i64), i64 ptrtoint(void (i1)* @test4_g to i64)), i1* %sink + %sink = alloca i64 + store volatile i64 mul (i64 ptrtoint (void (i1)* @test4_g to i64), i64 ptrtoint(void (i1)* @test4_g to i64)), i64* %sink call void @test4_g(i1 true) -; CHECK: store volatile i1 false +; CHECK: store volatile i64 mul (i64 ptrtoint (void (i1)* @test4_g to i64), i64 ptrtoint (void (i1)* @test4_g to i64)), i64* %sink ; CHECK: call void @test4_g(i1 true) ret void