//===------ ZoneAlgo.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 // //===----------------------------------------------------------------------===// // // Derive information about array elements between statements ("Zones"). // // The algorithms here work on the scatter space - the image space of the // schedule returned by Scop::getSchedule(). We call an element in that space a // "timepoint". Timepoints are lexicographically ordered such that we can // defined ranges in the scatter space. We use two flavors of such ranges: // Timepoint sets and zones. A timepoint set is simply a subset of the scatter // space and is directly stored as isl_set. // // Zones are used to describe the space between timepoints as open sets, i.e. // they do not contain the extrema. Using isl rational sets to express these // would be overkill. We also cannot store them as the integer timepoints they // contain; the (nonempty) zone between 1 and 2 would be empty and // indistinguishable from e.g. the zone between 3 and 4. Also, we cannot store // the integer set including the extrema; the set ]1,2[ + ]3,4[ could be // coalesced to ]1,3[, although we defined the range [2,3] to be not in the set. // Instead, we store the "half-open" integer extrema, including the lower bound, // but excluding the upper bound. Examples: // // * The set { [i] : 1 <= i <= 3 } represents the zone ]0,3[ (which contains the // integer points 1 and 2, but not 0 or 3) // // * { [1] } represents the zone ]0,1[ // // * { [i] : i = 1 or i = 3 } represents the zone ]0,1[ + ]2,3[ // // Therefore, an integer i in the set represents the zone ]i-1,i[, i.e. strictly // speaking the integer points never belong to the zone. However, depending an // the interpretation, one might want to include them. Part of the // interpretation may not be known when the zone is constructed. // // Reads are assumed to always take place before writes, hence we can think of // reads taking place at the beginning of a timepoint and writes at the end. // // Let's assume that the zone represents the lifetime of a variable. That is, // the zone begins with a write that defines the value during its lifetime and // ends with the last read of that value. In the following we consider whether a // read/write at the beginning/ending of the lifetime zone should be within the // zone or outside of it. // // * A read at the timepoint that starts the live-range loads the previous // value. Hence, exclude the timepoint starting the zone. // // * A write at the timepoint that starts the live-range is not defined whether // it occurs before or after the write that starts the lifetime. We do not // allow this situation to occur. Hence, we include the timepoint starting the // zone to determine whether they are conflicting. // // * A read at the timepoint that ends the live-range reads the same variable. // We include the timepoint at the end of the zone to include that read into // the live-range. Doing otherwise would mean that the two reads access // different values, which would mean that the value they read are both alive // at the same time but occupy the same variable. // // * A write at the timepoint that ends the live-range starts a new live-range. // It must not be included in the live-range of the previous definition. // // All combinations of reads and writes at the endpoints are possible, but most // of the time only the write->read (for instance, a live-range from definition // to last use) and read->write (for instance, an unused range from last use to // overwrite) and combinations are interesting (half-open ranges). write->write // zones might be useful as well in some context to represent // output-dependencies. // // @see convertZoneToTimepoints // // // The code makes use of maps and sets in many different spaces. To not loose // track in which space a set or map is expected to be in, variables holding an // isl reference are usually annotated in the comments. They roughly follow isl // syntax for spaces, but only the tuples, not the dimensions. The tuples have a // meaning as follows: // // * Space[] - An unspecified tuple. Used for function parameters such that the // function caller can use it for anything they like. // // * Domain[] - A statement instance as returned by ScopStmt::getDomain() // isl_id_get_name: Stmt_<NameOfBasicBlock> // isl_id_get_user: Pointer to ScopStmt // // * Element[] - An array element as in the range part of // MemoryAccess::getAccessRelation() // isl_id_get_name: MemRef_<NameOfArrayVariable> // isl_id_get_user: Pointer to ScopArrayInfo // // * Scatter[] - Scatter space or space of timepoints // Has no tuple id // // * Zone[] - Range between timepoints as described above // Has no tuple id // // * ValInst[] - An llvm::Value as defined at a specific timepoint. // // A ValInst[] itself can be structured as one of: // // * [] - An unknown value. // Always zero dimensions // Has no tuple id // // * Value[] - An llvm::Value that is read-only in the SCoP, i.e. its // runtime content does not depend on the timepoint. // Always zero dimensions // isl_id_get_name: Val_<NameOfValue> // isl_id_get_user: A pointer to an llvm::Value // // * SCEV[...] - A synthesizable llvm::SCEV Expression. // In contrast to a Value[] is has at least one dimension per // SCEVAddRecExpr in the SCEV. // // * [Domain[] -> Value[]] - An llvm::Value that may change during the // Scop's execution. // The tuple itself has no id, but it wraps a map space holding a // statement instance which defines the llvm::Value as the map's domain // and llvm::Value itself as range. // // @see makeValInst() // // An annotation "{ Domain[] -> Scatter[] }" therefore means: A map from a // statement instance to a timepoint, aka a schedule. There is only one scatter // space, but most of the time multiple statements are processed in one set. // This is why most of the time isl_union_map has to be used. // // The basic algorithm works as follows: // At first we verify that the SCoP is compatible with this technique. For // instance, two writes cannot write to the same location at the same statement // instance because we cannot determine within the polyhedral model which one // comes first. Once this was verified, we compute zones at which an array // element is unused. This computation can fail if it takes too long. Then the // main algorithm is executed. Because every store potentially trails an unused // zone, we start at stores. We search for a scalar (MemoryKind::Value or // MemoryKind::PHI) that we can map to the array element overwritten by the // store, preferably one that is used by the store or at least the ScopStmt. // When it does not conflict with the lifetime of the values in the array // element, the map is applied and the unused zone updated as it is now used. We // continue to try to map scalars to the array element until there are no more // candidates to map. The algorithm is greedy in the sense that the first scalar // not conflicting will be mapped. Other scalars processed later that could have // fit the same unused zone will be rejected. As such the result depends on the // processing order. // //===----------------------------------------------------------------------===// #include "polly/ZoneAlgo.h" #include "polly/ScopInfo.h" #include "polly/Support/GICHelper.h" #include "polly/Support/ISLTools.h" #include "polly/Support/VirtualInstruction.h" #include "llvm/ADT/Statistic.h" #include "llvm/Support/raw_ostream.h" #include "polly/Support/PollyDebug.h" #define DEBUG_TYPE … STATISTIC(NumIncompatibleArrays, "Number of not zone-analyzable arrays"); STATISTIC(NumCompatibleArrays, "Number of zone-analyzable arrays"); STATISTIC(NumRecursivePHIs, "Number of recursive PHIs"); STATISTIC(NumNormalizablePHIs, "Number of normalizable PHIs"); STATISTIC(NumPHINormialization, "Number of PHI executed normalizations"); usingnamespacepolly; usingnamespacellvm; static isl::union_map computeReachingDefinition(isl::union_map Schedule, isl::union_map Writes, bool InclDef, bool InclRedef) { … } /// Compute the reaching definition of a scalar. /// /// Compared to computeReachingDefinition, there is just one element which is /// accessed and therefore only a set if instances that accesses that element is /// required. /// /// @param Schedule { DomainWrite[] -> Scatter[] } /// @param Writes { DomainWrite[] } /// @param InclDef Include the timepoint of the definition to the result. /// @param InclRedef Include the timepoint of the overwrite into the result. /// /// @return { Scatter[] -> DomainWrite[] } static isl::union_map computeScalarReachingDefinition(isl::union_map Schedule, isl::union_set Writes, bool InclDef, bool InclRedef) { … } /// Compute the reaching definition of a scalar. /// /// This overload accepts only a single writing statement as an isl_map, /// consequently the result also is only a single isl_map. /// /// @param Schedule { DomainWrite[] -> Scatter[] } /// @param Writes { DomainWrite[] } /// @param InclDef Include the timepoint of the definition to the result. /// @param InclRedef Include the timepoint of the overwrite into the result. /// /// @return { Scatter[] -> DomainWrite[] } static isl::map computeScalarReachingDefinition(isl::union_map Schedule, isl::set Writes, bool InclDef, bool InclRedef) { … } isl::union_map polly::makeUnknownForDomain(isl::union_set Domain) { … } /// Create a domain-to-unknown value mapping. /// /// @see makeUnknownForDomain(isl::union_set) /// /// @param Domain { Domain[] } /// /// @return { Domain[] -> ValInst[] } static isl::map makeUnknownForDomain(isl::set Domain) { … } /// Return whether @p Map maps to an unknown value. /// /// @param { [] -> ValInst[] } static bool isMapToUnknown(const isl::map &Map) { … } isl::union_map polly::filterKnownValInst(const isl::union_map &UMap) { … } ZoneAlgorithm::ZoneAlgorithm(const char *PassName, Scop *S, LoopInfo *LI) : … { … } /// Check if all stores in @p Stmt store the very same value. /// /// This covers a special situation occurring in Polybench's /// covariance/correlation (which is typical for algorithms that cover symmetric /// matrices): /// /// for (int i = 0; i < n; i += 1) /// for (int j = 0; j <= i; j += 1) { /// double x = ...; /// C[i][j] = x; /// C[j][i] = x; /// } /// /// For i == j, the same value is written twice to the same element.Double /// writes to the same element are not allowed in DeLICM because its algorithm /// does not see which of the writes is effective.But if its the same value /// anyway, it doesn't matter. /// /// LLVM passes, however, cannot simplify this because the write is necessary /// for i != j (unless it would add a condition for one of the writes to occur /// only if i != j). /// /// TODO: In the future we may want to extent this to make the checks /// specific to different memory locations. static bool onlySameValueWrites(ScopStmt *Stmt) { … } /// Is @p InnerLoop nested inside @p OuterLoop? static bool isInsideLoop(Loop *OuterLoop, Loop *InnerLoop) { … } void ZoneAlgorithm::collectIncompatibleElts(ScopStmt *Stmt, isl::union_set &IncompatibleElts, isl::union_set &AllElts) { … } void ZoneAlgorithm::addArrayReadAccess(MemoryAccess *MA) { … } isl::union_map ZoneAlgorithm::getWrittenValue(MemoryAccess *MA, isl::map AccRel) { … } void ZoneAlgorithm::addArrayWriteAccess(MemoryAccess *MA) { … } /// For an llvm::Value defined in @p DefStmt, compute the RAW dependency for a /// use in every instance of @p UseStmt. /// /// @param UseStmt Statement a scalar is used in. /// @param DefStmt Statement a scalar is defined in. /// /// @return { DomainUse[] -> DomainDef[] } isl::map ZoneAlgorithm::computeUseToDefFlowDependency(ScopStmt *UseStmt, ScopStmt *DefStmt) { … } /// Return whether @p PHI refers (also transitively through other PHIs) to /// itself. /// /// loop: /// %phi1 = phi [0, %preheader], [%phi1, %loop] /// br i1 %c, label %loop, label %exit /// /// exit: /// %phi2 = phi [%phi1, %bb] /// /// In this example, %phi1 is recursive, but %phi2 is not. static bool isRecursivePHI(const PHINode *PHI) { … } isl::union_map ZoneAlgorithm::computePerPHI(const ScopArrayInfo *SAI) { … } isl::union_set ZoneAlgorithm::makeEmptyUnionSet() const { … } isl::union_map ZoneAlgorithm::makeEmptyUnionMap() const { … } void ZoneAlgorithm::collectCompatibleElts() { … } isl::map ZoneAlgorithm::getScatterFor(ScopStmt *Stmt) const { … } isl::map ZoneAlgorithm::getScatterFor(MemoryAccess *MA) const { … } isl::union_map ZoneAlgorithm::getScatterFor(isl::union_set Domain) const { … } isl::map ZoneAlgorithm::getScatterFor(isl::set Domain) const { … } isl::set ZoneAlgorithm::getDomainFor(ScopStmt *Stmt) const { … } isl::set ZoneAlgorithm::getDomainFor(MemoryAccess *MA) const { … } isl::map ZoneAlgorithm::getAccessRelationFor(MemoryAccess *MA) const { … } isl::map ZoneAlgorithm::getDefToTarget(ScopStmt *DefStmt, ScopStmt *TargetStmt) { … } isl::map ZoneAlgorithm::getScalarReachingDefinition(ScopStmt *Stmt) { … } isl::map ZoneAlgorithm::getScalarReachingDefinition(isl::set DomainDef) { … } isl::map ZoneAlgorithm::makeUnknownForDomain(ScopStmt *Stmt) const { … } isl::id ZoneAlgorithm::makeValueId(Value *V) { … } isl::space ZoneAlgorithm::makeValueSpace(Value *V) { … } isl::set ZoneAlgorithm::makeValueSet(Value *V) { … } isl::map ZoneAlgorithm::makeValInst(Value *Val, ScopStmt *UserStmt, Loop *Scope, bool IsCertain) { … } /// Remove all computed PHIs out of @p Input and replace by their incoming /// value. /// /// @param Input { [] -> ValInst[] } /// @param ComputedPHIs Set of PHIs that are replaced. Its ValInst must appear /// on the LHS of @p NormalizeMap. /// @param NormalizeMap { ValInst[] -> ValInst[] } static isl::union_map normalizeValInst(isl::union_map Input, const DenseSet<PHINode *> &ComputedPHIs, isl::union_map NormalizeMap) { … } isl::union_map ZoneAlgorithm::makeNormalizedValInst(llvm::Value *Val, ScopStmt *UserStmt, llvm::Loop *Scope, bool IsCertain) { … } bool ZoneAlgorithm::isCompatibleAccess(MemoryAccess *MA) { … } bool ZoneAlgorithm::isNormalizable(MemoryAccess *MA) { … } isl::boolean ZoneAlgorithm::isNormalized(isl::map Map) { … } isl::boolean ZoneAlgorithm::isNormalized(isl::union_map UMap) { … } void ZoneAlgorithm::computeCommon() { … } void ZoneAlgorithm::computeNormalizedPHIs() { … } void ZoneAlgorithm::printAccesses(llvm::raw_ostream &OS, int Indent) const { … } isl::union_map ZoneAlgorithm::computeKnownFromMustWrites() const { … } isl::union_map ZoneAlgorithm::computeKnownFromLoad() const { … } isl::union_map ZoneAlgorithm::computeKnown(bool FromWrite, bool FromRead) const { … }