Files
llvm/polly/lib/Transform/Simplify.cpp
Michael Kruse 7a0f7dbf2d [Polly] Introduce PhaseManager and remove LPM support (#125442) (#167560)
Reapply of a22d1c2225. Using this PR for
pre-merge CI.

Instead of relying on any pass manager to schedule Polly's passes, add
Polly's own pipeline manager which is seen as a monolithic pass in
LLVM's pass manager. Polly's former passes are now phases of the new
PhaseManager component.

Relying on LLVM's pass manager (the legacy as well as the New Pass
Manager) to manage Polly's phases never was a good fit that the
PhaseManager resolves:

* Polly passes were modifying analysis results, in particular RegionInfo
and ScopInfo. This means that there was not just one unique and
"definite" analysis result, the actual result depended on which analyses
ran prior, and the pass manager was not allowed to throw away cached
analyses or prior SCoP optimizations would have been forgotten. The LLVM
pass manger's persistance of analysis results is not contractual but
designed for caching.

* Polly depends on a particular execution order of passes and regions
(e.g. regression tests, invalidation of consecutive SCoPs). LLVM's pass
manager does not guarantee any excecution order.

* Polly does not completely preserve DominatorTree, RegionInfo,
LoopInfo, or ScalarEvolution, but only as-needed for Polly's own uses.
Because the ScopDetection object stores references to those analyses, it
still had to lie to the pass manager that they would be preserved, or
the pass manager would have released and recomputed the invalidated
analysis objects that ScopDetection/ScopInfo was still referencing. To
ensure that no non-Polly pass would see these not-completely-preserved
analyses, all analyses still had to be thrown away after the
ScopPassManager, respectively with a BarrierNoopPass in case of the LPM.
 
* The NPM's PassInstrumentation wraps the IR unit into an `llvm::Any`
object, but implementations such as PrintIRInstrumentation call
llvm_unreachable on encountering an unknown IR unit, such as SCoPs, with
no extension points to add support. Hence LLVM crashes when dumping IR
between SCoP passes (such as `-print-before-changed` with Polly being
active).

The new PhaseManager uses some command line options that previously
belonged to Polly's legacy passes, such as `-polly-print-detect` (so the
option will continue to work). Hence the LPM support is incompatible
with the new approach and support for it is removed.
2025-11-14 00:45:54 +01:00

830 lines
30 KiB
C++

//===------ Simplify.cpp ----------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Simplify a SCoP by removing unnecessary statements and accesses.
//
//===----------------------------------------------------------------------===//
#include "polly/Simplify.h"
#include "polly/Options.h"
#include "polly/ScopInfo.h"
#include "polly/ScopPass.h"
#include "polly/Support/GICHelper.h"
#include "polly/Support/ISLOStream.h"
#include "polly/Support/ISLTools.h"
#include "polly/Support/VirtualInstruction.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/Support/Debug.h"
#include <optional>
#include "polly/Support/PollyDebug.h"
#define DEBUG_TYPE "polly-simplify"
using namespace llvm;
using namespace polly;
namespace {
static cl::opt<bool>
PollyPrintSimplify("polly-print-simplify",
cl::desc("Polly - Print Simplify actions"),
cl::cat(PollyCategory));
#define TWO_STATISTICS(VARNAME, DESC) \
static llvm::Statistic VARNAME[2] = { \
{DEBUG_TYPE, #VARNAME "0", DESC " (first)"}, \
{DEBUG_TYPE, #VARNAME "1", DESC " (second)"}}
/// Number of max disjuncts we allow in removeOverwrites(). This is to avoid
/// that the analysis of accesses in a statement is becoming too complex. Chosen
/// to be relatively small because all the common cases should access only few
/// array elements per statement.
static unsigned const SimplifyMaxDisjuncts = 4;
TWO_STATISTICS(ScopsProcessed, "Number of SCoPs processed");
TWO_STATISTICS(ScopsModified, "Number of SCoPs simplified");
TWO_STATISTICS(TotalEmptyDomainsRemoved,
"Number of statement with empty domains removed in any SCoP");
TWO_STATISTICS(TotalOverwritesRemoved, "Number of removed overwritten writes");
TWO_STATISTICS(TotalWritesCoalesced, "Number of writes coalesced with another");
TWO_STATISTICS(TotalRedundantWritesRemoved,
"Number of writes of same value removed in any SCoP");
TWO_STATISTICS(TotalEmptyPartialAccessesRemoved,
"Number of empty partial accesses removed");
TWO_STATISTICS(TotalDeadAccessesRemoved, "Number of dead accesses removed");
TWO_STATISTICS(TotalDeadInstructionsRemoved,
"Number of unused instructions removed");
TWO_STATISTICS(TotalStmtsRemoved, "Number of statements removed in any SCoP");
TWO_STATISTICS(NumValueWrites, "Number of scalar value writes after Simplify");
TWO_STATISTICS(
NumValueWritesInLoops,
"Number of scalar value writes nested in affine loops after Simplify");
TWO_STATISTICS(NumPHIWrites,
"Number of scalar phi writes after the first simplification");
TWO_STATISTICS(
NumPHIWritesInLoops,
"Number of scalar phi writes nested in affine loops after Simplify");
TWO_STATISTICS(NumSingletonWrites, "Number of singleton writes after Simplify");
TWO_STATISTICS(
NumSingletonWritesInLoops,
"Number of singleton writes nested in affine loops after Simplify");
static bool isImplicitRead(MemoryAccess *MA) {
return MA->isRead() && MA->isOriginalScalarKind();
}
static bool isExplicitAccess(MemoryAccess *MA) {
return MA->isOriginalArrayKind();
}
static bool isImplicitWrite(MemoryAccess *MA) {
return MA->isWrite() && MA->isOriginalScalarKind();
}
/// Like isl::union_map::unite, but may also return an underapproximated
/// result if getting too complex.
///
/// This is implemented by adding disjuncts to the results until the limit is
/// reached.
static isl::union_map underapproximatedAddMap(isl::union_map UMap,
isl::map Map) {
if (UMap.is_null() || Map.is_null())
return {};
isl::map PrevMap = UMap.extract_map(Map.get_space());
// Fast path: If known that we cannot exceed the disjunct limit, just add
// them.
if (unsignedFromIslSize(PrevMap.n_basic_map()) +
unsignedFromIslSize(Map.n_basic_map()) <=
SimplifyMaxDisjuncts)
return UMap.unite(Map);
isl::map Result = isl::map::empty(PrevMap.get_space());
for (isl::basic_map BMap : PrevMap.get_basic_map_list()) {
if (unsignedFromIslSize(Result.n_basic_map()) > SimplifyMaxDisjuncts)
break;
Result = Result.unite(BMap);
}
for (isl::basic_map BMap : Map.get_basic_map_list()) {
if (unsignedFromIslSize(Result.n_basic_map()) > SimplifyMaxDisjuncts)
break;
Result = Result.unite(BMap);
}
isl::union_map UResult =
UMap.subtract(isl::map::universe(PrevMap.get_space()));
UResult.unite(Result);
return UResult;
}
class SimplifyImpl final {
private:
/// The invocation id (if there are multiple instances in the pass manager's
/// pipeline) to determine which statistics to update.
int CallNo;
/// The last/current SCoP that is/has been processed.
Scop *S = nullptr;
/// Number of statements with empty domains removed from the SCoP.
int EmptyDomainsRemoved = 0;
/// Number of writes that are overwritten anyway.
int OverwritesRemoved = 0;
/// Number of combined writes.
int WritesCoalesced = 0;
/// Number of redundant writes removed from this SCoP.
int RedundantWritesRemoved = 0;
/// Number of writes with empty access domain removed.
int EmptyPartialAccessesRemoved = 0;
/// Number of unused accesses removed from this SCoP.
int DeadAccessesRemoved = 0;
/// Number of unused instructions removed from this SCoP.
int DeadInstructionsRemoved = 0;
/// Number of unnecessary statements removed from the SCoP.
int StmtsRemoved = 0;
/// Remove statements that are never executed due to their domains being
/// empty.
///
/// In contrast to Scop::simplifySCoP, this removes based on the SCoP's
/// effective domain, i.e. including the SCoP's context as used by some other
/// simplification methods in this pass. This is necessary because the
/// analysis on empty domains is unreliable, e.g. remove a scalar value
/// definition MemoryAccesses, but not its use.
void removeEmptyDomainStmts();
/// Remove writes that are overwritten unconditionally later in the same
/// statement.
///
/// There must be no read of the same value between the write (that is to be
/// removed) and the overwrite.
void removeOverwrites();
/// Combine writes that write the same value if possible.
///
/// This function is able to combine:
/// - Partial writes with disjoint domain.
/// - Writes that write to the same array element.
///
/// In all cases, both writes must write the same values.
void coalesceWrites();
/// Remove writes that just write the same value already stored in the
/// element.
void removeRedundantWrites();
/// Remove statements without side effects.
void removeUnnecessaryStmts();
/// Remove accesses that have an empty domain.
void removeEmptyPartialAccesses();
/// Mark all reachable instructions and access, and sweep those that are not
/// reachable.
void markAndSweep(LoopInfo *LI);
/// Print simplification statistics to @p OS.
void printStatistics(llvm::raw_ostream &OS, int Indent = 0) const;
/// Print the current state of all MemoryAccesses to @p OS.
void printAccesses(llvm::raw_ostream &OS, int Indent = 0) const;
public:
explicit SimplifyImpl(int CallNo = 0) : CallNo(CallNo) {}
void run(Scop &S, LoopInfo *LI);
void printScop(raw_ostream &OS, Scop &S) const;
/// Return whether at least one simplification has been applied.
bool isModified() const;
};
/// Return whether at least one simplification has been applied.
bool SimplifyImpl::isModified() const {
return EmptyDomainsRemoved > 0 || OverwritesRemoved > 0 ||
WritesCoalesced > 0 || RedundantWritesRemoved > 0 ||
EmptyPartialAccessesRemoved > 0 || DeadAccessesRemoved > 0 ||
DeadInstructionsRemoved > 0 || StmtsRemoved > 0;
}
/// Remove statements that are never executed due to their domains being
/// empty.
///
/// In contrast to Scop::simplifySCoP, this removes based on the SCoP's
/// effective domain, i.e. including the SCoP's context as used by some other
/// simplification methods in this pass. This is necessary because the
/// analysis on empty domains is unreliable, e.g. remove a scalar value
/// definition MemoryAccesses, but not its use.
void SimplifyImpl::removeEmptyDomainStmts() {
size_t NumStmtsBefore = S->getSize();
S->removeStmts([](ScopStmt &Stmt) -> bool {
auto EffectiveDomain =
Stmt.getDomain().intersect_params(Stmt.getParent()->getContext());
return EffectiveDomain.is_empty();
});
assert(NumStmtsBefore >= S->getSize());
EmptyDomainsRemoved = NumStmtsBefore - S->getSize();
POLLY_DEBUG(dbgs() << "Removed " << EmptyDomainsRemoved << " (of "
<< NumStmtsBefore << ") statements with empty domains \n");
TotalEmptyDomainsRemoved[CallNo] += EmptyDomainsRemoved;
}
/// Remove writes that are overwritten unconditionally later in the same
/// statement.
///
/// There must be no read of the same value between the write (that is to be
/// removed) and the overwrite.
void SimplifyImpl::removeOverwrites() {
for (auto &Stmt : *S) {
isl::set Domain = Stmt.getDomain();
isl::union_map WillBeOverwritten = isl::union_map::empty(S->getIslCtx());
SmallVector<MemoryAccess *, 32> Accesses(getAccessesInOrder(Stmt));
// Iterate in reverse order, so the overwrite comes before the write that
// is to be removed.
for (auto *MA : reverse(Accesses)) {
// In region statements, the explicit accesses can be in blocks that are
// can be executed in any order. We therefore process only the implicit
// writes and stop after that.
if (Stmt.isRegionStmt() && isExplicitAccess(MA))
break;
auto AccRel = MA->getAccessRelation();
AccRel = AccRel.intersect_domain(Domain);
AccRel = AccRel.intersect_params(S->getContext());
// If a value is read in-between, do not consider it as overwritten.
if (MA->isRead()) {
// Invalidate all overwrites for the array it accesses to avoid too
// complex isl sets.
isl::map AccRelUniv = isl::map::universe(AccRel.get_space());
WillBeOverwritten = WillBeOverwritten.subtract(AccRelUniv);
continue;
}
// If all of a write's elements are overwritten, remove it.
isl::union_map AccRelUnion = AccRel;
if (AccRelUnion.is_subset(WillBeOverwritten)) {
POLLY_DEBUG(dbgs() << "Removing " << MA
<< " which will be overwritten anyway\n");
Stmt.removeSingleMemoryAccess(MA);
OverwritesRemoved++;
TotalOverwritesRemoved[CallNo]++;
}
// Unconditional writes overwrite other values.
if (MA->isMustWrite()) {
// Avoid too complex isl sets. If necessary, throw away some of the
// knowledge.
WillBeOverwritten = underapproximatedAddMap(WillBeOverwritten, AccRel);
}
}
}
}
/// Combine writes that write the same value if possible.
///
/// This function is able to combine:
/// - Partial writes with disjoint domain.
/// - Writes that write to the same array element.
///
/// In all cases, both writes must write the same values.
void SimplifyImpl::coalesceWrites() {
for (auto &Stmt : *S) {
isl::set Domain = Stmt.getDomain().intersect_params(S->getContext());
// We let isl do the lookup for the same-value condition. For this, we
// wrap llvm::Value into an isl::set such that isl can do the lookup in
// its hashtable implementation. llvm::Values are only compared within a
// ScopStmt, so the map can be local to this scope. TODO: Refactor with
// ZoneAlgorithm::makeValueSet()
SmallDenseMap<Value *, isl::set> ValueSets;
auto makeValueSet = [&ValueSets, this](Value *V) -> isl::set {
assert(V);
isl::set &Result = ValueSets[V];
if (Result.is_null()) {
isl::ctx Ctx = S->getIslCtx();
std::string Name = getIslCompatibleName(
"Val", V, ValueSets.size() - 1, std::string(), UseInstructionNames);
isl::id Id = isl::id::alloc(Ctx, Name, V);
Result = isl::set::universe(
isl::space(Ctx, 0, 0).set_tuple_id(isl::dim::set, Id));
}
return Result;
};
// List of all eligible (for coalescing) writes of the future.
// { [Domain[] -> Element[]] -> [Value[] -> MemoryAccess[]] }
isl::union_map FutureWrites = isl::union_map::empty(S->getIslCtx());
// Iterate over accesses from the last to the first.
SmallVector<MemoryAccess *, 32> Accesses(getAccessesInOrder(Stmt));
for (MemoryAccess *MA : reverse(Accesses)) {
// In region statements, the explicit accesses can be in blocks that can
// be executed in any order. We therefore process only the implicit
// writes and stop after that.
if (Stmt.isRegionStmt() && isExplicitAccess(MA))
break;
// { Domain[] -> Element[] }
isl::map AccRel = MA->getLatestAccessRelation().intersect_domain(Domain);
// { [Domain[] -> Element[]] }
isl::set AccRelWrapped = AccRel.wrap();
// { Value[] }
isl::set ValSet;
if (MA->isMustWrite() && (MA->isOriginalScalarKind() ||
isa<StoreInst>(MA->getAccessInstruction()))) {
// Normally, tryGetValueStored() should be used to determine which
// element is written, but it can return nullptr; For PHI accesses,
// getAccessValue() returns the PHI instead of the PHI's incoming
// value. In this case, where we only compare values of a single
// statement, this is fine, because within a statement, a PHI in a
// successor block has always the same value as the incoming write. We
// still preferably use the incoming value directly so we also catch
// direct uses of that.
Value *StoredVal = MA->tryGetValueStored();
if (!StoredVal)
StoredVal = MA->getAccessValue();
ValSet = makeValueSet(StoredVal);
// { Domain[] }
isl::set AccDomain = AccRel.domain();
// Parts of the statement's domain that is not written by this access.
isl::set UndefDomain = Domain.subtract(AccDomain);
// { Element[] }
isl::set ElementUniverse =
isl::set::universe(AccRel.get_space().range());
// { Domain[] -> Element[] }
isl::map UndefAnything =
isl::map::from_domain_and_range(UndefDomain, ElementUniverse);
// We are looking a compatible write access. The other write can
// access these elements...
isl::map AllowedAccesses = AccRel.unite(UndefAnything);
// ... and must write the same value.
// { [Domain[] -> Element[]] -> Value[] }
isl::map Filter =
isl::map::from_domain_and_range(AllowedAccesses.wrap(), ValSet);
// Lookup future write that fulfills these conditions.
// { [[Domain[] -> Element[]] -> Value[]] -> MemoryAccess[] }
isl::union_map Filtered =
FutureWrites.uncurry().intersect_domain(Filter.wrap());
// Iterate through the candidates.
for (isl::map Map : Filtered.get_map_list()) {
MemoryAccess *OtherMA = (MemoryAccess *)Map.get_space()
.get_tuple_id(isl::dim::out)
.get_user();
isl::map OtherAccRel =
OtherMA->getLatestAccessRelation().intersect_domain(Domain);
// The filter only guaranteed that some of OtherMA's accessed
// elements are allowed. Verify that it only accesses allowed
// elements. Otherwise, continue with the next candidate.
if (!OtherAccRel.is_subset(AllowedAccesses).is_true())
continue;
// The combined access relation.
// { Domain[] -> Element[] }
isl::map NewAccRel = AccRel.unite(OtherAccRel);
simplify(NewAccRel);
// Carry out the coalescing.
Stmt.removeSingleMemoryAccess(MA);
OtherMA->setNewAccessRelation(NewAccRel);
// We removed MA, OtherMA takes its role.
MA = OtherMA;
TotalWritesCoalesced[CallNo]++;
WritesCoalesced++;
// Don't look for more candidates.
break;
}
}
// Two writes cannot be coalesced if there is another access (to some of
// the written elements) between them. Remove all visited write accesses
// from the list of eligible writes. Don't just remove the accessed
// elements, but any MemoryAccess that touches any of the invalidated
// elements.
SmallPtrSet<MemoryAccess *, 2> TouchedAccesses;
for (isl::map Map :
FutureWrites.intersect_domain(AccRelWrapped).get_map_list()) {
MemoryAccess *MA = (MemoryAccess *)Map.get_space()
.range()
.unwrap()
.get_tuple_id(isl::dim::out)
.get_user();
TouchedAccesses.insert(MA);
}
isl::union_map NewFutureWrites =
isl::union_map::empty(FutureWrites.ctx());
for (isl::map FutureWrite : FutureWrites.get_map_list()) {
MemoryAccess *MA = (MemoryAccess *)FutureWrite.get_space()
.range()
.unwrap()
.get_tuple_id(isl::dim::out)
.get_user();
if (!TouchedAccesses.count(MA))
NewFutureWrites = NewFutureWrites.unite(FutureWrite);
}
FutureWrites = NewFutureWrites;
if (MA->isMustWrite() && !ValSet.is_null()) {
// { MemoryAccess[] }
auto AccSet =
isl::set::universe(isl::space(S->getIslCtx(), 0, 0)
.set_tuple_id(isl::dim::set, MA->getId()));
// { Val[] -> MemoryAccess[] }
isl::map ValAccSet = isl::map::from_domain_and_range(ValSet, AccSet);
// { [Domain[] -> Element[]] -> [Value[] -> MemoryAccess[]] }
isl::map AccRelValAcc =
isl::map::from_domain_and_range(AccRelWrapped, ValAccSet.wrap());
FutureWrites = FutureWrites.unite(AccRelValAcc);
}
}
}
}
/// Remove writes that just write the same value already stored in the
/// element.
void SimplifyImpl::removeRedundantWrites() {
for (auto &Stmt : *S) {
SmallDenseMap<Value *, isl::set> ValueSets;
auto makeValueSet = [&ValueSets, this](Value *V) -> isl::set {
assert(V);
isl::set &Result = ValueSets[V];
if (Result.is_null()) {
isl_ctx *Ctx = S->getIslCtx().get();
std::string Name = getIslCompatibleName(
"Val", V, ValueSets.size() - 1, std::string(), UseInstructionNames);
isl::id Id = isl::manage(isl_id_alloc(Ctx, Name.c_str(), V));
Result = isl::set::universe(
isl::space(Ctx, 0, 0).set_tuple_id(isl::dim::set, Id));
}
return Result;
};
isl::set Domain = Stmt.getDomain();
Domain = Domain.intersect_params(S->getContext());
// List of element reads that still have the same value while iterating
// through the MemoryAccesses.
// { [Domain[] -> Element[]] -> Val[] }
isl::union_map Known = isl::union_map::empty(S->getIslCtx());
SmallVector<MemoryAccess *, 32> Accesses(getAccessesInOrder(Stmt));
for (MemoryAccess *MA : Accesses) {
// Is the memory access in a defined order relative to the other
// accesses? In region statements, only the first and the last accesses
// have defined order. Execution of those in the middle may depend on
// runtime conditions an therefore cannot be modified.
bool IsOrdered =
Stmt.isBlockStmt() || MA->isOriginalScalarKind() ||
(!S->getBoxedLoops().size() && MA->getAccessInstruction() &&
Stmt.getEntryBlock() == MA->getAccessInstruction()->getParent());
isl::map AccRel = MA->getAccessRelation();
AccRel = AccRel.intersect_domain(Domain);
isl::set AccRelWrapped = AccRel.wrap();
// Determine whether a write is redundant (stores only values that are
// already present in the written array elements) and remove it if this
// is the case.
if (IsOrdered && MA->isMustWrite() &&
(isa<StoreInst>(MA->getAccessInstruction()) ||
MA->isOriginalScalarKind())) {
Value *StoredVal = MA->tryGetValueStored();
if (!StoredVal)
StoredVal = MA->getAccessValue();
if (StoredVal) {
// Lookup in the set of known values.
isl::map AccRelStoredVal = isl::map::from_domain_and_range(
AccRelWrapped, makeValueSet(StoredVal));
if (isl::union_map(AccRelStoredVal).is_subset(Known)) {
POLLY_DEBUG(dbgs() << "Cleanup of " << MA << ":\n");
POLLY_DEBUG(dbgs() << " Scalar: " << *StoredVal << "\n");
POLLY_DEBUG(dbgs() << " AccRel: " << AccRel << "\n");
Stmt.removeSingleMemoryAccess(MA);
RedundantWritesRemoved++;
TotalRedundantWritesRemoved[CallNo]++;
}
}
}
// Update the know values set.
if (MA->isRead()) {
// Loaded values are the currently known values of the array element
// it was loaded from.
Value *LoadedVal = MA->getAccessValue();
if (LoadedVal && IsOrdered) {
isl::map AccRelVal = isl::map::from_domain_and_range(
AccRelWrapped, makeValueSet(LoadedVal));
Known = Known.unite(AccRelVal);
}
} else if (MA->isWrite()) {
// Remove (possibly) overwritten values from the known elements set.
// We remove all elements of the accessed array to avoid too complex
// isl sets.
isl::set AccRelUniv = isl::set::universe(AccRelWrapped.get_space());
Known = Known.subtract_domain(AccRelUniv);
// At this point, we could add the written value of must-writes.
// However, writing same values is already handled by
// coalesceWrites().
}
}
}
}
/// Remove statements without side effects.
void SimplifyImpl::removeUnnecessaryStmts() {
auto NumStmtsBefore = S->getSize();
S->simplifySCoP(true);
assert(NumStmtsBefore >= S->getSize());
StmtsRemoved = NumStmtsBefore - S->getSize();
POLLY_DEBUG(dbgs() << "Removed " << StmtsRemoved << " (of " << NumStmtsBefore
<< ") statements\n");
TotalStmtsRemoved[CallNo] += StmtsRemoved;
}
/// Remove accesses that have an empty domain.
void SimplifyImpl::removeEmptyPartialAccesses() {
for (ScopStmt &Stmt : *S) {
// Defer the actual removal to not invalidate iterators.
SmallVector<MemoryAccess *, 8> DeferredRemove;
for (MemoryAccess *MA : Stmt) {
if (!MA->isWrite())
continue;
isl::map AccRel = MA->getAccessRelation();
if (!AccRel.is_empty().is_true())
continue;
POLLY_DEBUG(
dbgs() << "Removing " << MA
<< " because it's a partial access that never occurs\n");
DeferredRemove.push_back(MA);
}
for (MemoryAccess *MA : DeferredRemove) {
Stmt.removeSingleMemoryAccess(MA);
EmptyPartialAccessesRemoved++;
TotalEmptyPartialAccessesRemoved[CallNo]++;
}
}
}
/// Mark all reachable instructions and access, and sweep those that are not
/// reachable.
void SimplifyImpl::markAndSweep(LoopInfo *LI) {
DenseSet<MemoryAccess *> UsedMA;
DenseSet<VirtualInstruction> UsedInsts;
// Get all reachable instructions and accesses.
markReachable(S, LI, UsedInsts, UsedMA);
// Remove all non-reachable accesses.
// We need get all MemoryAccesses first, in order to not invalidate the
// iterators when removing them.
SmallVector<MemoryAccess *, 64> AllMAs;
for (ScopStmt &Stmt : *S)
AllMAs.append(Stmt.begin(), Stmt.end());
for (MemoryAccess *MA : AllMAs) {
if (UsedMA.count(MA))
continue;
POLLY_DEBUG(dbgs() << "Removing " << MA
<< " because its value is not used\n");
ScopStmt *Stmt = MA->getStatement();
Stmt->removeSingleMemoryAccess(MA);
DeadAccessesRemoved++;
TotalDeadAccessesRemoved[CallNo]++;
}
// Remove all non-reachable instructions.
for (ScopStmt &Stmt : *S) {
// Note that for region statements, we can only remove the non-terminator
// instructions of the entry block. All other instructions are not in the
// instructions list, but implicitly always part of the statement.
SmallVector<Instruction *, 32> AllInsts(Stmt.insts_begin(),
Stmt.insts_end());
SmallVector<Instruction *, 32> RemainInsts;
for (Instruction *Inst : AllInsts) {
auto It = UsedInsts.find({&Stmt, Inst});
if (It == UsedInsts.end()) {
POLLY_DEBUG(dbgs() << "Removing "; Inst->print(dbgs());
dbgs() << " because it is not used\n");
DeadInstructionsRemoved++;
TotalDeadInstructionsRemoved[CallNo]++;
continue;
}
RemainInsts.push_back(Inst);
// If instructions appear multiple times, keep only the first.
UsedInsts.erase(It);
}
// Set the new instruction list to be only those we did not remove.
Stmt.setInstructions(RemainInsts);
}
}
/// Print simplification statistics to @p OS.
void SimplifyImpl::printStatistics(llvm::raw_ostream &OS, int Indent) const {
OS.indent(Indent) << "Statistics {\n";
OS.indent(Indent + 4) << "Empty domains removed: " << EmptyDomainsRemoved
<< '\n';
OS.indent(Indent + 4) << "Overwrites removed: " << OverwritesRemoved << '\n';
OS.indent(Indent + 4) << "Partial writes coalesced: " << WritesCoalesced
<< "\n";
OS.indent(Indent + 4) << "Redundant writes removed: "
<< RedundantWritesRemoved << "\n";
OS.indent(Indent + 4) << "Accesses with empty domains removed: "
<< EmptyPartialAccessesRemoved << "\n";
OS.indent(Indent + 4) << "Dead accesses removed: " << DeadAccessesRemoved
<< '\n';
OS.indent(Indent + 4) << "Dead instructions removed: "
<< DeadInstructionsRemoved << '\n';
OS.indent(Indent + 4) << "Stmts removed: " << StmtsRemoved << "\n";
OS.indent(Indent) << "}\n";
}
/// Print the current state of all MemoryAccesses to @p OS.
void SimplifyImpl::printAccesses(llvm::raw_ostream &OS, int Indent) const {
OS.indent(Indent) << "After accesses {\n";
for (auto &Stmt : *S) {
OS.indent(Indent + 4) << Stmt.getBaseName() << "\n";
for (auto *MA : Stmt)
MA->print(OS);
}
OS.indent(Indent) << "}\n";
}
void SimplifyImpl::run(Scop &S, LoopInfo *LI) {
// Must not have run before.
assert(!this->S);
assert(!isModified());
// Prepare processing of this SCoP.
this->S = &S;
ScopsProcessed[CallNo]++;
POLLY_DEBUG(dbgs() << "Removing statements that are never executed...\n");
removeEmptyDomainStmts();
POLLY_DEBUG(dbgs() << "Removing partial writes that never happen...\n");
removeEmptyPartialAccesses();
POLLY_DEBUG(dbgs() << "Removing overwrites...\n");
removeOverwrites();
POLLY_DEBUG(dbgs() << "Coalesce partial writes...\n");
coalesceWrites();
POLLY_DEBUG(dbgs() << "Removing redundant writes...\n");
removeRedundantWrites();
POLLY_DEBUG(dbgs() << "Cleanup unused accesses...\n");
markAndSweep(LI);
POLLY_DEBUG(dbgs() << "Removing statements without side effects...\n");
removeUnnecessaryStmts();
if (isModified())
ScopsModified[CallNo]++;
POLLY_DEBUG(dbgs() << "\nFinal Scop:\n");
POLLY_DEBUG(dbgs() << S);
auto ScopStats = S.getStatistics();
NumValueWrites[CallNo] += ScopStats.NumValueWrites;
NumValueWritesInLoops[CallNo] += ScopStats.NumValueWritesInLoops;
NumPHIWrites[CallNo] += ScopStats.NumPHIWrites;
NumPHIWritesInLoops[CallNo] += ScopStats.NumPHIWritesInLoops;
NumSingletonWrites[CallNo] += ScopStats.NumSingletonWrites;
NumSingletonWritesInLoops[CallNo] += ScopStats.NumSingletonWritesInLoops;
}
void SimplifyImpl::printScop(raw_ostream &OS, Scop &S) const {
assert(&S == this->S &&
"Can only print analysis for the last processed SCoP");
printStatistics(OS);
if (!isModified()) {
OS << "SCoP could not be simplified\n";
return;
}
printAccesses(OS);
}
static llvm::PreservedAnalyses
runSimplifyUsingNPM(Scop &S, ScopAnalysisManager &SAM,
ScopStandardAnalysisResults &SAR, SPMUpdater &U, int CallNo,
raw_ostream *OS) {
SimplifyImpl Impl(CallNo);
Impl.run(S, &SAR.LI);
if (OS) {
*OS << "Printing analysis 'Polly - Simplify' for region: '" << S.getName()
<< "' in function '" << S.getFunction().getName() << "':\n";
Impl.printScop(*OS, S);
}
if (!Impl.isModified())
return llvm::PreservedAnalyses::all();
PreservedAnalyses PA;
PA.preserveSet<AllAnalysesOn<Module>>();
PA.preserveSet<AllAnalysesOn<Function>>();
PA.preserveSet<AllAnalysesOn<Loop>>();
return PA;
}
} // anonymous namespace
llvm::PreservedAnalyses SimplifyPass::run(Scop &S, ScopAnalysisManager &SAM,
ScopStandardAnalysisResults &SAR,
SPMUpdater &U) {
return runSimplifyUsingNPM(S, SAM, SAR, U, CallNo, nullptr);
}
llvm::PreservedAnalyses
SimplifyPrinterPass::run(Scop &S, ScopAnalysisManager &SAM,
ScopStandardAnalysisResults &SAR, SPMUpdater &U) {
return runSimplifyUsingNPM(S, SAM, SAR, U, CallNo, &OS);
}
SmallVector<MemoryAccess *, 32> polly::getAccessesInOrder(ScopStmt &Stmt) {
SmallVector<MemoryAccess *, 32> Accesses;
for (MemoryAccess *MemAcc : Stmt)
if (isImplicitRead(MemAcc))
Accesses.push_back(MemAcc);
for (MemoryAccess *MemAcc : Stmt)
if (isExplicitAccess(MemAcc))
Accesses.push_back(MemAcc);
for (MemoryAccess *MemAcc : Stmt)
if (isImplicitWrite(MemAcc))
Accesses.push_back(MemAcc);
return Accesses;
}
bool polly::runSimplify(Scop &S, int CallNo) {
SimplifyImpl Impl(CallNo);
Impl.run(S, S.getLI());
if (PollyPrintSimplify) {
outs() << "Printing analysis 'Polly - Simplify' for region: '"
<< S.getName() << "' in function '" << S.getFunction().getName()
<< "':\n";
Impl.printScop(outs(), S);
}
return Impl.isModified();
}