Skip to content

[GVN] Restrict equality propagation for pointers #82458

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -3429,15 +3429,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 @@ -3481,6 +3481,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
Loading