Skip to content

[mlir][affine] Use alias analysis to redetermine intervening memory effects in affine-scalrep #90859

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 1 commit into from
May 9, 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
7 changes: 5 additions & 2 deletions mlir/include/mlir/Dialect/Affine/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#ifndef MLIR_DIALECT_AFFINE_UTILS_H
#define MLIR_DIALECT_AFFINE_UTILS_H

#include "mlir/Analysis/AliasAnalysis.h"
#include "mlir/Dialect/Affine/Analysis/AffineAnalysis.h"
#include "mlir/Dialect/Affine/IR/AffineOps.h"
#include "mlir/IR/OpDefinition.h"
Expand Down Expand Up @@ -106,7 +107,8 @@ struct VectorizationStrategy {
/// loads and eliminate invariant affine loads; consequently, eliminate dead
/// allocs.
void affineScalarReplace(func::FuncOp f, DominanceInfo &domInfo,
PostDominanceInfo &postDomInfo);
PostDominanceInfo &postDomInfo,
AliasAnalysis &analysis);

/// Vectorizes affine loops in 'loops' using the n-D vectorization factors in
/// 'vectorSizes'. By default, each vectorization factor is applied
Expand Down Expand Up @@ -325,7 +327,8 @@ OpFoldResult linearizeIndex(ArrayRef<OpFoldResult> multiIndex,
/// will check if there is no write to the memory between `start` and `memOp`
/// that would change the read within `memOp`.
template <typename EffectType, typename T>
bool hasNoInterveningEffect(Operation *start, T memOp);
bool hasNoInterveningEffect(Operation *start, T memOp,
llvm::function_ref<bool(Value, Value)> mayAlias);

struct AffineValueExpr {
explicit AffineValueExpr(AffineExpr e) : e(e) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include "mlir/Dialect/Affine/Passes.h"

#include "mlir/Analysis/AliasAnalysis.h"
#include "mlir/Dialect/Affine/Utils.h"
#include "mlir/Dialect/Func/IR/FuncOps.h"
#include "mlir/IR/Dominance.h"
Expand Down Expand Up @@ -47,5 +48,6 @@ mlir::affine::createAffineScalarReplacementPass() {

void AffineScalarReplacement::runOnOperation() {
affineScalarReplace(getOperation(), getAnalysis<DominanceInfo>(),
getAnalysis<PostDominanceInfo>());
getAnalysis<PostDominanceInfo>(),
getAnalysis<AliasAnalysis>());
}
52 changes: 28 additions & 24 deletions mlir/lib/Dialect/Affine/Utils/Utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -678,12 +678,9 @@ static bool mayHaveEffect(Operation *srcMemOp, Operation *destMemOp,
}

template <typename EffectType, typename T>
bool mlir::affine::hasNoInterveningEffect(Operation *start, T memOp) {
auto isLocallyAllocated = [](Value memref) {
auto *defOp = memref.getDefiningOp();
return defOp && hasSingleEffect<MemoryEffects::Allocate>(defOp, memref);
};

bool mlir::affine::hasNoInterveningEffect(
Operation *start, T memOp,
llvm::function_ref<bool(Value, Value)> mayAlias) {
// A boolean representing whether an intervening operation could have impacted
// memOp.
bool hasSideEffect = false;
Expand All @@ -704,11 +701,8 @@ bool mlir::affine::hasNoInterveningEffect(Operation *start, T memOp) {
// If op causes EffectType on a potentially aliasing location for
// memOp, mark as having the effect.
if (isa<EffectType>(effect.getEffect())) {
// TODO: This should be replaced with a check for no aliasing.
// Aliasing information should be passed to this method.
if (effect.getValue() && effect.getValue() != memref &&
isLocallyAllocated(memref) &&
isLocallyAllocated(effect.getValue()))
!mayAlias(effect.getValue(), memref))
continue;
opMayHaveEffect = true;
break;
Expand Down Expand Up @@ -832,10 +826,10 @@ bool mlir::affine::hasNoInterveningEffect(Operation *start, T memOp) {
/// other operations will overwrite the memory loaded between the given load
/// and store. If such a value exists, the replaced `loadOp` will be added to
/// `loadOpsToErase` and its memref will be added to `memrefsToErase`.
static void forwardStoreToLoad(AffineReadOpInterface loadOp,
SmallVectorImpl<Operation *> &loadOpsToErase,
SmallPtrSetImpl<Value> &memrefsToErase,
DominanceInfo &domInfo) {
static void forwardStoreToLoad(
AffineReadOpInterface loadOp, SmallVectorImpl<Operation *> &loadOpsToErase,
SmallPtrSetImpl<Value> &memrefsToErase, DominanceInfo &domInfo,
llvm::function_ref<bool(Value, Value)> mayAlias) {

// The store op candidate for forwarding that satisfies all conditions
// to replace the load, if any.
Expand Down Expand Up @@ -872,7 +866,8 @@ static void forwardStoreToLoad(AffineReadOpInterface loadOp,

// 4. Ensure there is no intermediate operation which could replace the
// value in memory.
if (!affine::hasNoInterveningEffect<MemoryEffects::Write>(storeOp, loadOp))
if (!affine::hasNoInterveningEffect<MemoryEffects::Write>(storeOp, loadOp,
mayAlias))
continue;

// We now have a candidate for forwarding.
Expand Down Expand Up @@ -901,7 +896,8 @@ static void forwardStoreToLoad(AffineReadOpInterface loadOp,
template bool
mlir::affine::hasNoInterveningEffect<mlir::MemoryEffects::Read,
affine::AffineReadOpInterface>(
mlir::Operation *, affine::AffineReadOpInterface);
mlir::Operation *, affine::AffineReadOpInterface,
llvm::function_ref<bool(Value, Value)>);

// This attempts to find stores which have no impact on the final result.
// A writing op writeA will be eliminated if there exists an op writeB if
Expand All @@ -910,7 +906,8 @@ mlir::affine::hasNoInterveningEffect<mlir::MemoryEffects::Read,
// 3) There is no potential read between writeA and writeB.
static void findUnusedStore(AffineWriteOpInterface writeA,
SmallVectorImpl<Operation *> &opsToErase,
PostDominanceInfo &postDominanceInfo) {
PostDominanceInfo &postDominanceInfo,
llvm::function_ref<bool(Value, Value)> mayAlias) {

for (Operation *user : writeA.getMemRef().getUsers()) {
// Only consider writing operations.
Expand Down Expand Up @@ -939,7 +936,8 @@ static void findUnusedStore(AffineWriteOpInterface writeA,

// There cannot be an operation which reads from memory between
// the two writes.
if (!affine::hasNoInterveningEffect<MemoryEffects::Read>(writeA, writeB))
if (!affine::hasNoInterveningEffect<MemoryEffects::Read>(writeA, writeB,
mayAlias))
continue;

opsToErase.push_back(writeA);
Expand All @@ -955,7 +953,8 @@ static void findUnusedStore(AffineWriteOpInterface writeA,
// 3) There is no write between loadA and loadB.
static void loadCSE(AffineReadOpInterface loadA,
SmallVectorImpl<Operation *> &loadOpsToErase,
DominanceInfo &domInfo) {
DominanceInfo &domInfo,
llvm::function_ref<bool(Value, Value)> mayAlias) {
SmallVector<AffineReadOpInterface, 4> loadCandidates;
for (auto *user : loadA.getMemRef().getUsers()) {
auto loadB = dyn_cast<AffineReadOpInterface>(user);
Expand All @@ -976,7 +975,7 @@ static void loadCSE(AffineReadOpInterface loadA,

// 3. There should not be a write between loadA and loadB.
if (!affine::hasNoInterveningEffect<MemoryEffects::Write>(
loadB.getOperation(), loadA))
loadB.getOperation(), loadA, mayAlias))
continue;

// Check if two values have the same shape. This is needed for affine vector
Expand Down Expand Up @@ -1034,24 +1033,29 @@ static void loadCSE(AffineReadOpInterface loadA,
// than dealloc) remain.
//
void mlir::affine::affineScalarReplace(func::FuncOp f, DominanceInfo &domInfo,
PostDominanceInfo &postDomInfo) {
PostDominanceInfo &postDomInfo,
AliasAnalysis &aliasAnalysis) {
// Load op's whose results were replaced by those forwarded from stores.
SmallVector<Operation *, 8> opsToErase;

// A list of memref's that are potentially dead / could be eliminated.
SmallPtrSet<Value, 4> memrefsToErase;

auto mayAlias = [&](Value val1, Value val2) -> bool {
return !aliasAnalysis.alias(val1, val2).isNo();
};

// Walk all load's and perform store to load forwarding.
f.walk([&](AffineReadOpInterface loadOp) {
forwardStoreToLoad(loadOp, opsToErase, memrefsToErase, domInfo);
forwardStoreToLoad(loadOp, opsToErase, memrefsToErase, domInfo, mayAlias);
});
for (auto *op : opsToErase)
op->erase();
opsToErase.clear();

// Walk all store's and perform unused store elimination
f.walk([&](AffineWriteOpInterface storeOp) {
findUnusedStore(storeOp, opsToErase, postDomInfo);
findUnusedStore(storeOp, opsToErase, postDomInfo, mayAlias);
});
for (auto *op : opsToErase)
op->erase();
Expand Down Expand Up @@ -1084,7 +1088,7 @@ void mlir::affine::affineScalarReplace(func::FuncOp f, DominanceInfo &domInfo,
// stores. Otherwise, some stores are wrongly seen as having an intervening
// effect.
f.walk([&](AffineReadOpInterface loadOp) {
loadCSE(loadOp, opsToErase, domInfo);
loadCSE(loadOp, opsToErase, domInfo, mayAlias);
});
for (auto *op : opsToErase)
op->erase();
Expand Down
18 changes: 18 additions & 0 deletions mlir/test/Dialect/Affine/scalrep.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,24 @@ func.func @redundant_store_elim(%out : memref<512xf32>) {
// CHECK-NEXT: affine.store
// CHECK-NEXT: }

// CHECK-LABEL: func @redundant_store_elim_nonintervening

func.func @redundant_store_elim_nonintervening(%in : memref<512xf32>) {
%cf1 = arith.constant 1.0 : f32
%out = memref.alloc() : memref<512xf32>
affine.for %i = 0 to 16 {
affine.store %cf1, %out[32*%i] : memref<512xf32>
%0 = affine.load %in[32*%i] : memref<512xf32>
affine.store %0, %out[32*%i] : memref<512xf32>
}
return
}

// CHECK: affine.for
// CHECK-NEXT: affine.load
// CHECK-NEXT: affine.store
// CHECK-NEXT: }

// CHECK-LABEL: func @redundant_store_elim_fail

func.func @redundant_store_elim_fail(%out : memref<512xf32>) {
Expand Down