[polly] r295197 - [DeLICM] Add Knowledge class. NFC.

Michael Kruse via llvm-commits llvm-commits at lists.llvm.org
Wed Feb 15 08:59:10 PST 2017


Author: meinersbur
Date: Wed Feb 15 10:59:10 2017
New Revision: 295197

URL: http://llvm.org/viewvc/llvm-project?rev=295197&view=rev
Log:
[DeLICM] Add Knowledge class. NFC.

The Knowledge class remembers the state of data at any timepoint of a SCoP's
execution. Currently, it tracks whether an array element is unused or is
occupied by some value, and the writes to it. A future addition will be to also
remember which value it contains.

Objects are used to determine whether two Knowledge contain conflicting
information, i.e. two states cannot be true a the same time.

This commit was extracted from the DeLICM algorithm at
https://reviews.llvm.org/D24716.

Modified:
    polly/trunk/include/polly/DeLICM.h
    polly/trunk/lib/Transform/DeLICM.cpp
    polly/trunk/unittests/CMakeLists.txt

Modified: polly/trunk/include/polly/DeLICM.h
URL: http://llvm.org/viewvc/llvm-project/polly/trunk/include/polly/DeLICM.h?rev=295197&r1=295196&r2=295197&view=diff
==============================================================================
--- polly/trunk/include/polly/DeLICM.h (original)
+++ polly/trunk/include/polly/DeLICM.h Wed Feb 15 10:59:10 2017
@@ -18,6 +18,8 @@
 #ifndef POLLY_DELICM_H
 #define POLLY_DELICM_H
 
+#include "polly/Support/GICHelper.h"
+
 namespace llvm {
 class PassRegistry;
 class Pass;
@@ -26,6 +28,17 @@ class Pass;
 namespace polly {
 /// Create a new DeLICM pass instance.
 llvm::Pass *createDeLICMPass();
+
+/// Determine whether two lifetimes are conflicting.
+///
+/// Used by unittesting.
+bool isConflicting(IslPtr<isl_union_set> ExistingOccupied,
+                   IslPtr<isl_union_set> ExistingUnused,
+                   IslPtr<isl_union_set> ExistingWrites,
+                   IslPtr<isl_union_set> ProposedOccupied,
+                   IslPtr<isl_union_set> ProposedUnused,
+                   IslPtr<isl_union_set> ProposedWrites,
+                   llvm::raw_ostream *OS = nullptr, unsigned Indent = 0);
 } // namespace polly
 
 namespace llvm {

Modified: polly/trunk/lib/Transform/DeLICM.cpp
URL: http://llvm.org/viewvc/llvm-project/polly/trunk/lib/Transform/DeLICM.cpp?rev=295197&r1=295196&r2=295197&view=diff
==============================================================================
--- polly/trunk/lib/Transform/DeLICM.cpp (original)
+++ polly/trunk/lib/Transform/DeLICM.cpp Wed Feb 15 10:59:10 2017
@@ -13,11 +13,106 @@
 // Namely, remove register/scalar dependencies by mapping them back to array
 // elements.
 //
+// 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
+//
+// 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.
+//
 //===----------------------------------------------------------------------===//
 
 #include "polly/DeLICM.h"
 #include "polly/ScopInfo.h"
 #include "polly/ScopPass.h"
+#include "polly/Support/ISLTools.h"
 #define DEBUG_TYPE "polly-delicm"
 
 using namespace polly;
@@ -25,6 +120,259 @@ using namespace llvm;
 
 namespace {
 
+/// Represent the knowledge of the contents of any array elements in any zone or
+/// the knowledge we would add when mapping a scalar to an array element.
+///
+/// Every array element at every zone unit has one of two states:
+///
+/// - Unused: Not occupied by any value so a transformation can change it to
+///   other values.
+///
+/// - Occupied: The element contains a value that is still needed.
+///
+/// The union of Unused and Unknown zones forms the universe, the set of all
+/// elements at every timepoint. The universe can easily be derived from the
+/// array elements that are accessed someway. Arrays that are never accessed
+/// also never play a role in any computation and can hence be ignored. With a
+/// given universe, only one of the sets needs to stored implicitly. Computing
+/// the complement is also an expensive operation, hence this class has been
+/// designed that only one of sets is needed while the other is assumed to be
+/// implicit. It can still be given, but is mostly ignored.
+///
+/// There are two use cases for the Knowledge class:
+///
+/// 1) To represent the knowledge of the current state of ScopInfo. The unused
+///    state means that an element is currently unused: there is no read of it
+///    before the next overwrite. Also called 'Existing'.
+///
+/// 2) To represent the requirements for mapping a scalar to array elements. The
+///    unused state means that there is no change/requirement. Also called
+///    'Proposed'.
+///
+/// In addition to these states at unit zones, Knowledge needs to know when
+/// values are written. This is because written values may have no lifetime (one
+/// reason is that the value is never read). Such writes would therefore never
+/// conflict, but overwrite values that might still be required. Another source
+/// of problems are multiple writes to the same element at the same timepoint,
+/// because their order is undefined.
+class Knowledge {
+private:
+  /// { [Element[] -> Zone[]] }
+  /// Set of array elements and when they are alive.
+  /// Can contain a nullptr; in this case the set is implicitly defined as the
+  /// complement of #Unused.
+  ///
+  /// The set of alive array elements is represented as zone, as the set of live
+  /// values can differ depending on how the elements are interpreted.
+  /// Assuming a value X is written at timestep [0] and read at timestep [1]
+  /// without being used at any later point, then the value is alive in the
+  /// interval ]0,1[. This interval cannot be represented by an integer set, as
+  /// it does not contain any integer point. Zones allow us to represent this
+  /// interval and can be converted to sets of timepoints when needed (e.g., in
+  /// isConflicting when comparing to the write sets).
+  /// @see convertZoneToTimepoints and this file's comment for more details.
+  IslPtr<isl_union_set> Occupied;
+
+  /// { [Element[] -> Zone[]] }
+  /// Set of array elements when they are not alive, i.e. their memory can be
+  /// used for other purposed. Can contain a nullptr; in this case the set is
+  /// implicitly defined as the complement of #Occupied.
+  IslPtr<isl_union_set> Unused;
+
+  /// { [Element[] -> Scatter[]] }
+  /// The write actions currently in the scop or that would be added when
+  /// mapping a scalar.
+  IslPtr<isl_union_set> Written;
+
+  /// Check whether this Knowledge object is well-formed.
+  void checkConsistency() const {
+#ifndef NDEBUG
+    // Default-initialized object
+    if (!Occupied && !Unused && !Written)
+      return;
+
+    assert(Occupied || Unused);
+    assert(Written);
+
+    // If not all fields are defined, we cannot derived the universe.
+    if (!Occupied || !Unused)
+      return;
+
+    assert(isl_union_set_is_disjoint(Occupied.keep(), Unused.keep()) ==
+           isl_bool_true);
+    auto Universe = give(isl_union_set_union(Occupied.copy(), Unused.copy()));
+    assert(isl_union_set_is_subset(Written.keep(), Universe.keep()) ==
+           isl_bool_true);
+#endif
+  }
+
+public:
+  /// Initialize a nullptr-Knowledge. This is only provided for convenience; do
+  /// not use such an object.
+  Knowledge() {}
+
+  /// Create a new object with the given members.
+  Knowledge(IslPtr<isl_union_set> Occupied, IslPtr<isl_union_set> Unused,
+            IslPtr<isl_union_set> Written)
+      : Occupied(std::move(Occupied)), Unused(std::move(Unused)),
+        Written(std::move(Written)) {
+    checkConsistency();
+  }
+
+  /// Alternative constructor taking isl_sets instead isl_union_sets.
+  Knowledge(IslPtr<isl_set> Occupied, IslPtr<isl_set> Unused,
+            IslPtr<isl_set> Written)
+      : Knowledge(give(isl_union_set_from_set(Occupied.take())),
+                  give(isl_union_set_from_set(Unused.take())),
+                  give(isl_union_set_from_set(Written.take()))) {}
+
+  /// Return whether this object was not default-constructed.
+  bool isUsable() const { return (Occupied || Unused) && Written; }
+
+  /// Print the content of this object to @p OS.
+  void print(llvm::raw_ostream &OS, unsigned Indent = 0) const {
+    if (isUsable()) {
+      if (Occupied)
+        OS.indent(Indent) << "Occupied: " << Occupied << "\n";
+      else
+        OS.indent(Indent) << "Occupied: <Everything else not in Unused>\n";
+      if (Unused)
+        OS.indent(Indent) << "Unused:   " << Unused << "\n";
+      else
+        OS.indent(Indent) << "Unused:   <Everything else not in Occupied>\n";
+      OS.indent(Indent) << "Written : " << Written << '\n';
+    } else {
+      OS.indent(Indent) << "Invalid knowledge\n";
+    }
+  }
+
+  /// Combine two knowledges, this and @p That.
+  void learnFrom(Knowledge That) {
+    assert(!isConflicting(*this, That));
+    assert(Unused && That.Occupied);
+    assert(
+        !That.Unused &&
+        "This function is only prepared to learn occupied elements from That");
+    assert(!Occupied && "This function does not implement "
+                        "`this->Occupied = "
+                        "give(isl_union_set_union(this->Occupied.take(), "
+                        "That.Occupied.copy()));`");
+
+    Unused = give(isl_union_set_subtract(Unused.take(), That.Occupied.copy()));
+    Written = give(isl_union_set_union(Written.take(), That.Written.take()));
+
+    checkConsistency();
+  }
+
+  /// Determine whether two Knowledges conflict with each other.
+  ///
+  /// In theory @p Existing and @p Proposed are symmetric, but the
+  /// implementation is constrained by the implicit interpretation. That is, @p
+  /// Existing must have #Unused defined (use case 1) and @p Proposed must have
+  /// #Occupied defined (use case 1).
+  ///
+  /// A conflict is defined as non-preserved semantics when they are merged. For
+  /// instance, when for the same array and zone they assume different
+  /// llvm::Values.
+  ///
+  /// @param Existing One of the knowledges with #Unused defined.
+  /// @param Proposed One of the knowledges with #Occupied defined.
+  /// @param OS       Dump the conflict reason to this output stream; use
+  ///                 nullptr to not output anything.
+  /// @param Indent   Indention for the conflict reason.
+  ///
+  /// @return True, iff the two knowledges are conflicting.
+  static bool isConflicting(const Knowledge &Existing,
+                            const Knowledge &Proposed,
+                            llvm::raw_ostream *OS = nullptr,
+                            unsigned Indent = 0) {
+    assert(Existing.Unused);
+    assert(Proposed.Occupied);
+
+#ifndef NDEBUG
+    if (Existing.Occupied && Proposed.Unused) {
+      auto ExistingUniverse = give(isl_union_set_union(Existing.Occupied.copy(),
+                                                       Existing.Unused.copy()));
+      auto ProposedUniverse = give(isl_union_set_union(Proposed.Occupied.copy(),
+                                                       Proposed.Unused.copy()));
+      assert(isl_union_set_is_equal(ExistingUniverse.keep(),
+                                    ProposedUniverse.keep()) == isl_bool_true &&
+             "Both inputs' Knowledges must be over the same universe");
+    }
+#endif
+
+    // Are the new lifetimes required for Proposed unused in Existing?
+    if (isl_union_set_is_subset(Proposed.Occupied.keep(),
+                                Existing.Unused.keep()) != isl_bool_true) {
+      if (OS) {
+        auto ConflictingLifetimes = give(isl_union_set_subtract(
+            Proposed.Occupied.copy(), Existing.Unused.copy()));
+        OS->indent(Indent) << "Proposed lifetimes are not unused in existing\n";
+        OS->indent(Indent) << "Conflicting lifetimes: " << ConflictingLifetimes
+                           << "\n";
+      }
+      return true;
+    }
+
+    // Do the writes in Existing only overwrite unused values in Proposed?
+    // We convert here the set of lifetimes to actual timepoints. A lifetime is
+    // in conflict with a set of write timepoints, if either a live timepoint is
+    // clearly within the lifetime or if a write happens at the beginning of the
+    // lifetime (where it would conflict with the value that actually writes the
+    // value alive). There is no conflict at the end of a lifetime, as the alive
+    // value will always be read, before it is overwritten again. The last
+    // property holds in Polly for all scalar values and we expect all users of
+    // Knowledge to check this property also for accesses to MemoryKind::Array.
+    auto ProposedFixedDefs =
+        convertZoneToTimepoints(Proposed.Occupied, true, false);
+    if (isl_union_set_is_disjoint(Existing.Written.keep(),
+                                  ProposedFixedDefs.keep()) != isl_bool_true) {
+      if (OS) {
+        auto ConflictingWrites = give(isl_union_set_intersect(
+            Existing.Written.copy(), ProposedFixedDefs.copy()));
+        OS->indent(Indent) << "Proposed writes into range used by existing\n";
+        OS->indent(Indent) << "Conflicting writes: " << ConflictingWrites
+                           << "\n";
+      }
+      return true;
+    }
+
+    // Do the new writes in Proposed only overwrite unused values in Existing?
+    auto ExistingAvailableDefs =
+        convertZoneToTimepoints(Existing.Unused, true, false);
+    if (isl_union_set_is_subset(Proposed.Written.keep(),
+                                ExistingAvailableDefs.keep()) !=
+        isl_bool_true) {
+      if (OS) {
+        auto ConflictingWrites = give(isl_union_set_subtract(
+            Proposed.Written.copy(), ExistingAvailableDefs.copy()));
+        OS->indent(Indent)
+            << "Proposed a lifetime where there is an Existing write into it\n";
+        OS->indent(Indent) << "Conflicting writes: " << ConflictingWrites
+                           << "\n";
+      }
+      return true;
+    }
+
+    // Does Proposed write at the same time as Existing already does (order of
+    // writes is undefined)?
+    if (isl_union_set_is_disjoint(Existing.Written.keep(),
+                                  Proposed.Written.keep()) != isl_bool_true) {
+      if (OS) {
+        auto ConflictingWrites = give(isl_union_set_intersect(
+            Existing.Written.copy(), Proposed.Written.copy()));
+        OS->indent(Indent) << "Proposed writes at the same time as an already "
+                              "Existing write\n";
+        OS->indent(Indent) << "Conflicting writes: " << ConflictingWrites
+                           << "\n";
+      }
+      return true;
+    }
+
+    return false;
+  }
+};
+
 class DeLICM : public ScopPass {
 private:
   DeLICM(const DeLICM &) = delete;
@@ -68,3 +416,18 @@ INITIALIZE_PASS_BEGIN(DeLICM, "polly-del
 INITIALIZE_PASS_DEPENDENCY(ScopInfoWrapperPass)
 INITIALIZE_PASS_END(DeLICM, "polly-delicm", "Polly - DeLICM/DePRE", false,
                     false)
+
+bool polly::isConflicting(IslPtr<isl_union_set> ExistingOccupied,
+                          IslPtr<isl_union_set> ExistingUnused,
+                          IslPtr<isl_union_set> ExistingWrites,
+                          IslPtr<isl_union_set> ProposedOccupied,
+                          IslPtr<isl_union_set> ProposedUnused,
+                          IslPtr<isl_union_set> ProposedWrites,
+                          llvm::raw_ostream *OS, unsigned Indent) {
+  Knowledge Existing(std::move(ExistingOccupied), std::move(ExistingUnused),
+                     std::move(ExistingWrites));
+  Knowledge Proposed(std::move(ProposedOccupied), std::move(ProposedUnused),
+                     std::move(ProposedWrites));
+
+  return Knowledge::isConflicting(Existing, Proposed, OS, Indent);
+}

Modified: polly/trunk/unittests/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/polly/trunk/unittests/CMakeLists.txt?rev=295197&r1=295196&r2=295197&view=diff
==============================================================================
--- polly/trunk/unittests/CMakeLists.txt (original)
+++ polly/trunk/unittests/CMakeLists.txt Wed Feb 15 10:59:10 2017
@@ -16,8 +16,9 @@ function(add_polly_unittest test_name)
 
     set_property(TARGET ${test_name} PROPERTY FOLDER "Polly")
   endif()
-  target_link_libraries(${test_name} Polly LLVMCore LLVMSupport LLVMDemangle)
+  target_link_libraries(${test_name} Polly LLVMCore LLVMSupport LLVMDemangle LLVMipo)
 endfunction()
 
 add_subdirectory(Isl)
 add_subdirectory(Flatten)
+add_subdirectory(DeLICM)




More information about the llvm-commits mailing list