Skip to content

Commit

Permalink
[GVN] Restrict equality propagation for pointers (#82458)
Browse files Browse the repository at this point in the history
This patch does the following:

Adds the following functions:
- replaceDominatedUsesWithIf() that takes a callback.

- canReplacePointersIfEqual(...) returns true if the underlying object
is the same, and for null and const dereferencable pointer replacements.

- canReplacePointersIfEqualInUse(...) returns true for the above as well
as if the use is in icmp/ptrtoint or phi/selects feeding into them.

Updates GVN using the functions above so that the pointer replacements
are only made using the above API.

https://reviews.llvm.org/D143129
  • Loading branch information
UsmanNadeem committed Apr 24, 2024
1 parent d3f6a88 commit b10e4b8
Show file tree
Hide file tree
Showing 7 changed files with 368 additions and 68 deletions.
17 changes: 10 additions & 7 deletions llvm/include/llvm/Analysis/Loads.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,14 +173,17 @@ Value *findAvailablePtrLoadStore(const MemoryLocation &Loc, Type *AccessTy,
unsigned MaxInstsToScan, BatchAAResults *AA,
bool *IsLoadCSE, unsigned *NumScanedInst);

/// Returns true if a pointer value \p A can be replace with another pointer
/// value \B if they are deemed equal through some means (e.g. information from
/// Returns true if a pointer value \p From can be replaced with another pointer
/// value \To if they are deemed equal through some means (e.g. information from
/// conditions).
/// NOTE: the current implementations is incomplete and unsound. It does not
/// reject all invalid cases yet, but will be made stricter in the future. In
/// particular this means returning true means unknown if replacement is safe.
bool canReplacePointersIfEqual(Value *A, Value *B, const DataLayout &DL,
Instruction *CtxI);
/// NOTE: The current implementation allows replacement in Icmp and PtrToInt
/// instructions, as well as when we are replacing with a null pointer.
/// Additionally it also allows replacement of pointers when both pointers have
/// the same underlying object.
bool canReplacePointersIfEqual(const Value *From, const Value *To,
const DataLayout &DL);
bool canReplacePointersInUseIfEqual(const Use &U, const Value *To,
const DataLayout &DL);
}

#endif
12 changes: 12 additions & 0 deletions llvm/include/llvm/Transforms/Utils/Local.h
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,18 @@ unsigned replaceDominatedUsesWith(Value *From, Value *To, DominatorTree &DT,
/// the end of the given BasicBlock. Returns the number of replacements made.
unsigned replaceDominatedUsesWith(Value *From, Value *To, DominatorTree &DT,
const BasicBlock *BB);
/// Replace each use of 'From' with 'To' if that use is dominated by
/// the given edge and the callback ShouldReplace returns true. Returns the
/// number of replacements made.
unsigned replaceDominatedUsesWithIf(
Value *From, Value *To, DominatorTree &DT, const BasicBlockEdge &Edge,
function_ref<bool(const Use &U, const Value *To)> ShouldReplace);
/// Replace each use of 'From' with 'To' if that use is dominated by
/// the end of the given BasicBlock and the callback ShouldReplace returns true.
/// Returns the number of replacements made.
unsigned replaceDominatedUsesWithIf(
Value *From, Value *To, DominatorTree &DT, const BasicBlock *BB,
function_ref<bool(const Use &U, const Value *To)> ShouldReplace);

/// Return true if this call calls a gc leaf function.
///
Expand Down
72 changes: 56 additions & 16 deletions llvm/lib/Analysis/Loads.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -710,22 +710,62 @@ Value *llvm::FindAvailableLoadedValue(LoadInst *Load, BatchAAResults &AA,
return Available;
}

bool llvm::canReplacePointersIfEqual(Value *A, Value *B, const DataLayout &DL,
Instruction *CtxI) {
Type *Ty = A->getType();
assert(Ty == B->getType() && Ty->isPointerTy() &&
"values must have matching pointer types");

// NOTE: The checks in the function are incomplete and currently miss illegal
// cases! The current implementation is a starting point and the
// implementation should be made stricter over time.
if (auto *C = dyn_cast<Constant>(B)) {
// Do not allow replacing a pointer with a constant pointer, unless it is
// either null or at least one byte is dereferenceable.
APInt OneByte(DL.getPointerTypeSizeInBits(Ty), 1);
return C->isNullValue() ||
isDereferenceableAndAlignedPointer(B, Align(1), OneByte, DL, CtxI);
// Returns true if a use is either in an ICmp/PtrToInt or a Phi/Select that only
// feeds into them.
static bool isPointerUseReplacable(const Use &U) {
unsigned Limit = 40;
SmallVector<const User *> Worklist({U.getUser()});
SmallPtrSet<const User *, 8> Visited;

while (!Worklist.empty() && --Limit) {
auto *User = Worklist.pop_back_val();
if (!Visited.insert(User).second)
continue;
if (isa<ICmpInst, PtrToIntInst>(User))
continue;
if (isa<PHINode, SelectInst>(User))
Worklist.append(User->user_begin(), User->user_end());
else
return false;
}

return true;
return Limit != 0;
}

// Returns true if `To` is a null pointer, constant dereferenceable pointer or
// both pointers have the same underlying objects.
static bool isPointerAlwaysReplaceable(const Value *From, const Value *To,
const DataLayout &DL) {
// This is not strictly correct, but we do it for now to retain important
// optimizations.
if (isa<ConstantPointerNull>(To))
return true;
if (isa<Constant>(To) &&
isDereferenceablePointer(To, Type::getInt8Ty(To->getContext()), DL))
return true;
if (getUnderlyingObject(From) == getUnderlyingObject(To))
return true;
return false;
}

bool llvm::canReplacePointersInUseIfEqual(const Use &U, const Value *To,
const DataLayout &DL) {
assert(U->getType() == To->getType() && "values must have matching types");
// Not a pointer, just return true.
if (!To->getType()->isPointerTy())
return true;

if (isPointerAlwaysReplaceable(&*U, To, DL))
return true;
return isPointerUseReplacable(U);
}

bool llvm::canReplacePointersIfEqual(const Value *From, const Value *To,
const DataLayout &DL) {
assert(From->getType() == To->getType() && "values must have matching types");
// Not a pointer, just return true.
if (!From->getType()->isPointerTy())
return true;

return isPointerAlwaysReplaceable(From, To, DL);
}
32 changes: 23 additions & 9 deletions llvm/lib/Transforms/Scalar/GVN.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "llvm/Analysis/GlobalsModRef.h"
#include "llvm/Analysis/InstructionPrecedenceTracking.h"
#include "llvm/Analysis/InstructionSimplify.h"
#include "llvm/Analysis/Loads.h"
#include "llvm/Analysis/LoopInfo.h"
#include "llvm/Analysis/MemoryBuiltins.h"
#include "llvm/Analysis/MemoryDependenceAnalysis.h"
Expand Down Expand Up @@ -2419,6 +2420,10 @@ bool GVNPass::propagateEquality(Value *LHS, Value *RHS,
if (isa<Constant>(LHS) || (isa<Argument>(LHS) && !isa<Constant>(RHS)))
std::swap(LHS, RHS);
assert((isa<Argument>(LHS) || isa<Instruction>(LHS)) && "Unexpected value!");
const DataLayout &DL =
isa<Argument>(LHS)
? cast<Argument>(LHS)->getParent()->getParent()->getDataLayout()
: cast<Instruction>(LHS)->getModule()->getDataLayout();

// If there is no obvious reason to prefer the left-hand side over the
// right-hand side, ensure the longest lived term is on the right-hand side,
Expand All @@ -2445,23 +2450,32 @@ bool GVNPass::propagateEquality(Value *LHS, Value *RHS,
// using the leader table is about compiling faster, not optimizing better).
// The leader table only tracks basic blocks, not edges. Only add to if we
// have the simple case where the edge dominates the end.
if (RootDominatesEnd && !isa<Instruction>(RHS))
if (RootDominatesEnd && !isa<Instruction>(RHS) &&
canReplacePointersIfEqual(LHS, RHS, DL))
addToLeaderTable(LVN, RHS, Root.getEnd());

// Replace all occurrences of 'LHS' with 'RHS' everywhere in the scope. As
// LHS always has at least one use that is not dominated by Root, this will
// never do anything if LHS has only one use.
if (!LHS->hasOneUse()) {
// Create a callback that captures the DL.
auto canReplacePointersCallBack = [&DL](const Use &U, const Value *To) {
return canReplacePointersInUseIfEqual(U, To, DL);
};
unsigned NumReplacements =
DominatesByEdge
? replaceDominatedUsesWith(LHS, RHS, *DT, Root)
: replaceDominatedUsesWith(LHS, RHS, *DT, Root.getStart());

Changed |= NumReplacements > 0;
NumGVNEqProp += NumReplacements;
// Cached information for anything that uses LHS will be invalid.
if (MD)
MD->invalidateCachedPointerInfo(LHS);
? replaceDominatedUsesWithIf(LHS, RHS, *DT, Root,
canReplacePointersCallBack)
: replaceDominatedUsesWithIf(LHS, RHS, *DT, Root.getStart(),
canReplacePointersCallBack);

if (NumReplacements > 0) {
Changed = true;
NumGVNEqProp += NumReplacements;
// Cached information for anything that uses LHS will be invalid.
if (MD)
MD->invalidateCachedPointerInfo(LHS);
}
}

// Now try to deduce additional equalities from this one. For example, if
Expand Down
26 changes: 23 additions & 3 deletions llvm/lib/Transforms/Utils/Local.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3444,15 +3444,15 @@ void llvm::patchReplacementInstruction(Instruction *I, Value *Repl) {
combineMetadataForCSE(ReplInst, I, false);
}

template <typename RootType, typename DominatesFn>
template <typename RootType, typename ShouldReplaceFn>
static unsigned replaceDominatedUsesWith(Value *From, Value *To,
const RootType &Root,
const DominatesFn &Dominates) {
const ShouldReplaceFn &ShouldReplace) {
assert(From->getType() == To->getType());

unsigned Count = 0;
for (Use &U : llvm::make_early_inc_range(From->uses())) {
if (!Dominates(Root, U))
if (!ShouldReplace(Root, U))
continue;
LLVM_DEBUG(dbgs() << "Replace dominated use of '";
From->printAsOperand(dbgs());
Expand Down Expand Up @@ -3496,6 +3496,26 @@ unsigned llvm::replaceDominatedUsesWith(Value *From, Value *To,
return ::replaceDominatedUsesWith(From, To, BB, Dominates);
}

unsigned llvm::replaceDominatedUsesWithIf(
Value *From, Value *To, DominatorTree &DT, const BasicBlockEdge &Root,
function_ref<bool(const Use &U, const Value *To)> ShouldReplace) {
auto DominatesAndShouldReplace =
[&DT, &ShouldReplace, To](const BasicBlockEdge &Root, const Use &U) {
return DT.dominates(Root, U) && ShouldReplace(U, To);
};
return ::replaceDominatedUsesWith(From, To, Root, DominatesAndShouldReplace);
}

unsigned llvm::replaceDominatedUsesWithIf(
Value *From, Value *To, DominatorTree &DT, const BasicBlock *BB,
function_ref<bool(const Use &U, const Value *To)> ShouldReplace) {
auto DominatesAndShouldReplace = [&DT, &ShouldReplace,
To](const BasicBlock *BB, const Use &U) {
return DT.dominates(BB, U) && ShouldReplace(U, To);
};
return ::replaceDominatedUsesWith(From, To, BB, DominatesAndShouldReplace);
}

bool llvm::callsGCLeafFunction(const CallBase *Call,
const TargetLibraryInfo &TLI) {
// Check if the function is specifically marked as a gc leaf function.
Expand Down
Loading

0 comments on commit b10e4b8

Please sign in to comment.