MLIR  21.0.0git
DataFlowFramework.h
Go to the documentation of this file.
1 //===- DataFlowFramework.h - A generic framework for data-flow analysis ---===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file defines a generic framework for writing data-flow analysis in MLIR.
10 // The framework consists of a solver, which runs the fixed-point iteration and
11 // manages analysis dependencies, and a data-flow analysis class used to
12 // implement specific analyses.
13 //
14 //===----------------------------------------------------------------------===//
15 
16 #ifndef MLIR_ANALYSIS_DATAFLOWFRAMEWORK_H
17 #define MLIR_ANALYSIS_DATAFLOWFRAMEWORK_H
18 
19 #include "mlir/IR/Operation.h"
21 #include "llvm/ADT/EquivalenceClasses.h"
22 #include "llvm/ADT/Hashing.h"
23 #include "llvm/ADT/SetVector.h"
24 #include "llvm/Support/Compiler.h"
25 #include "llvm/Support/TypeName.h"
26 #include <queue>
27 #include <tuple>
28 
29 namespace mlir {
30 
31 //===----------------------------------------------------------------------===//
32 // ChangeResult
33 //===----------------------------------------------------------------------===//
34 
35 /// A result type used to indicate if a change happened. Boolean operations on
36 /// ChangeResult behave as though `Change` is truth.
37 enum class [[nodiscard]] ChangeResult {
38  NoChange,
39  Change,
40 };
42  return lhs == ChangeResult::Change ? lhs : rhs;
43 }
45  lhs = lhs | rhs;
46  return lhs;
47 }
49  return lhs == ChangeResult::NoChange ? lhs : rhs;
50 }
51 
52 /// Forward declare the analysis state class.
53 class AnalysisState;
54 
55 /// Program point represents a specific location in the execution of a program.
56 /// A sequence of program points can be combined into a control flow graph.
58  /// Creates a new program point at the given location.
60  : block(parentBlock), point(pp) {}
61 
62  /// Creates a new program point at the given operation.
63  ProgramPoint(Operation *op) : op(op) {}
64 
65  /// The concrete key type used by the storage uniquer. This class is uniqued
66  /// by its contents.
67  using KeyTy = std::tuple<Block *, Block::iterator, Operation *>;
68 
69  /// Create a empty program point.
71 
72  /// Create a new program point from the given program point.
73  ProgramPoint(const ProgramPoint &point) {
74  this->block = point.getBlock();
75  this->point = point.getPoint();
76  this->op = point.getOperation();
77  }
78 
80  KeyTy &&key) {
81  if (std::get<0>(key)) {
82  return new (alloc.allocate<ProgramPoint>())
83  ProgramPoint(std::get<0>(key), std::get<1>(key));
84  }
85  return new (alloc.allocate<ProgramPoint>()) ProgramPoint(std::get<2>(key));
86  }
87 
88  /// Returns true if this program point is set.
89  bool isNull() const { return block == nullptr && op == nullptr; }
90 
91  /// Two program points are equal if their block and iterator are equal.
92  bool operator==(const KeyTy &key) const {
93  return block == std::get<0>(key) && point == std::get<1>(key) &&
94  op == std::get<2>(key);
95  }
96 
97  bool operator==(const ProgramPoint &pp) const {
98  return block == pp.block && point == pp.point && op == pp.op;
99  }
100 
101  /// Get the block contains this program point.
102  Block *getBlock() const { return block; }
103 
104  /// Get the the iterator this program point refers to.
105  Block::iterator getPoint() const { return point; }
106 
107  /// Get the the iterator this program point refers to.
108  Operation *getOperation() const { return op; }
109 
110  /// Get the next operation of this program point.
111  Operation *getNextOp() const {
112  assert(!isBlockEnd());
113  // If the current program point has no parent block, both the next op and
114  // the previous op point to the op corresponding to the current program
115  // point.
116  if (block == nullptr) {
117  return op;
118  }
119  return &*point;
120  }
121 
122  /// Get the previous operation of this program point.
123  Operation *getPrevOp() const {
124  assert(!isBlockStart());
125  // If the current program point has no parent block, both the next op and
126  // the previous op point to the op corresponding to the current program
127  // point.
128  if (block == nullptr) {
129  return op;
130  }
131  return &*(--Block::iterator(point));
132  }
133 
134  bool isBlockStart() const { return block && block->begin() == point; }
135 
136  bool isBlockEnd() const { return block && block->end() == point; }
137 
138  /// Print the program point.
139  void print(raw_ostream &os) const;
140 
141 private:
142  Block *block = nullptr;
143  Block::iterator point;
144 
145  /// For operations without a parent block, we record the operation itself as
146  /// its program point.
147  Operation *op = nullptr;
148 };
149 
150 inline raw_ostream &operator<<(raw_ostream &os, const ProgramPoint &point) {
151  point.print(os);
152  return os;
153 }
154 
155 //===----------------------------------------------------------------------===//
156 // GenericLatticeAnchor
157 //===----------------------------------------------------------------------===//
158 
159 /// Abstract class for generic lattice anchor. In classical data-flow analysis,
160 /// lattice anchor represent positions in a program to which lattice elements
161 /// are attached. In sparse data-flow analysis, these can be SSA values, and in
162 /// dense data-flow analysis, these are the program points before and after
163 /// every operation.
164 ///
165 /// Lattice anchor are implemented using MLIR's storage uniquer framework and
166 /// type ID system to provide RTTI.
168 public:
170 
171  /// Get the abstract lattice anchor's type identifier.
172  TypeID getTypeID() const { return typeID; }
173 
174  /// Get a derived source location for the lattice anchor.
175  virtual Location getLoc() const = 0;
176 
177  /// Print the lattice anchor.
178  virtual void print(raw_ostream &os) const = 0;
179 
180 protected:
181  /// Create an abstract lattice anchor with type identifier.
182  explicit GenericLatticeAnchor(TypeID typeID) : typeID(typeID) {}
183 
184 private:
185  /// The type identifier of the lattice anchor.
186  TypeID typeID;
187 };
188 
189 //===----------------------------------------------------------------------===//
190 // GenericLatticeAnchorBase
191 //===----------------------------------------------------------------------===//
192 
193 /// Base class for generic lattice anchor based on a concrete lattice anchor
194 /// type and a content key. This class defines the common methods required for
195 /// operability with the storage uniquer framework.
196 ///
197 /// The provided key type uniquely identifies the concrete lattice anchor
198 /// instance and are the data members of the class.
199 template <typename ConcreteT, typename Value>
201 public:
202  /// The concrete key type used by the storage uniquer. This class is uniqued
203  /// by its contents.
204  using KeyTy = Value;
205  /// Alias for the base class.
207 
208  /// Construct an instance of the lattice anchor using the provided value and
209  /// the type ID of the concrete type.
210  template <typename ValueT>
211  explicit GenericLatticeAnchorBase(ValueT &&value)
212  : GenericLatticeAnchor(TypeID::get<ConcreteT>()),
213  value(std::forward<ValueT>(value)) {}
214 
215  /// Get a uniqued instance of this lattice anchor class with the given
216  /// arguments.
217  template <typename... Args>
218  static ConcreteT *get(StorageUniquer &uniquer, Args &&...args) {
219  return uniquer.get<ConcreteT>(/*initFn=*/{}, std::forward<Args>(args)...);
220  }
221 
222  /// Allocate space for a lattice anchor and construct it in-place.
223  template <typename ValueT>
224  static ConcreteT *construct(StorageUniquer::StorageAllocator &alloc,
225  ValueT &&value) {
226  return new (alloc.allocate<ConcreteT>())
227  ConcreteT(std::forward<ValueT>(value));
228  }
229 
230  /// Two lattice anchors are equal if their values are equal.
231  bool operator==(const Value &value) const { return this->value == value; }
232 
233  /// Provide LLVM-style RTTI using type IDs.
234  static bool classof(const GenericLatticeAnchor *point) {
235  return point->getTypeID() == TypeID::get<ConcreteT>();
236  }
237 
238  /// Get the contents of the lattice anchor.
239  const Value &getValue() const { return value; }
240 
241 private:
242  /// The lattice anchor value.
243  Value value;
244 };
245 
246 //===----------------------------------------------------------------------===//
247 // LatticeAnchor
248 //===----------------------------------------------------------------------===//
249 
250 /// Fundamental IR components are supported as first-class lattice anchor.
252  : public PointerUnion<GenericLatticeAnchor *, ProgramPoint *, Value> {
254  /// Inherit constructors.
255  using ParentTy::PointerUnion;
256  /// Allow implicit conversion from the parent type.
257  LatticeAnchor(ParentTy point = nullptr) : ParentTy(point) {}
258 
259  /// Print the lattice anchor.
260  void print(raw_ostream &os) const;
261 
262  /// Get the source location of the lattice anchor.
263  Location getLoc() const;
264 };
265 
266 /// Forward declaration of the data-flow analysis class.
267 class DataFlowAnalysis;
268 
269 } // namespace mlir
270 
271 template <>
272 struct llvm::DenseMapInfo<mlir::LatticeAnchor>
273  : public llvm::DenseMapInfo<mlir::LatticeAnchor::ParentTy> {};
274 
275 namespace mlir {
276 
277 //===----------------------------------------------------------------------===//
278 // DataFlowConfig
279 //===----------------------------------------------------------------------===//
280 
281 /// Configuration class for data flow solver and child analyses. Follows the
282 /// fluent API pattern.
284 public:
285  DataFlowConfig() = default;
286 
287  /// Set whether the solver should operate interpocedurally, i.e. enter the
288  /// callee body when available. Interprocedural analyses may be more precise,
289  /// but also more expensive as more states need to be computed and the
290  /// fixpoint convergence takes longer.
292  interprocedural = enable;
293  return *this;
294  }
295 
296  /// Return `true` if the solver operates interprocedurally, `false` otherwise.
297  bool isInterprocedural() const { return interprocedural; }
298 
299 private:
300  bool interprocedural = true;
301 };
302 
303 //===----------------------------------------------------------------------===//
304 // DataFlowSolver
305 //===----------------------------------------------------------------------===//
306 
307 /// The general data-flow analysis solver. This class is responsible for
308 /// orchestrating child data-flow analyses, running the fixed-point iteration
309 /// algorithm, managing analysis state and lattice anchor memory, and tracking
310 /// dependencies between analyses, lattice anchor, and analysis states.
311 ///
312 /// Steps to run a data-flow analysis:
313 ///
314 /// 1. Load and initialize children analyses. Children analyses are instantiated
315 /// in the solver and initialized, building their dependency relations.
316 /// 2. Configure and run the analysis. The solver invokes the children analyses
317 /// according to their dependency relations until a fixed point is reached.
318 /// 3. Query analysis state results from the solver.
319 ///
320 /// Steps to re-run a data-flow analysis when IR changes:
321 /// 1. Erase all analysis states as they are no longer valid.
322 /// 2. Re-run the analysis using `initializeAndRun`.
323 ///
324 /// TODO: Optimize the internal implementation of the solver.
326 public:
327  explicit DataFlowSolver(const DataFlowConfig &config = DataFlowConfig())
328  : config(config) {
330  }
331 
332  /// Load an analysis into the solver. Return the analysis instance.
333  template <typename AnalysisT, typename... Args>
334  AnalysisT *load(Args &&...args);
335 
336  /// Initialize the children analyses starting from the provided top-level
337  /// operation and run the analysis until fixpoint.
338  LogicalResult initializeAndRun(Operation *top);
339 
340  /// Lookup an analysis state for the given lattice anchor. Returns null if one
341  /// does not exist.
342  template <typename StateT, typename AnchorT>
343  const StateT *lookupState(AnchorT anchor) const {
344  LatticeAnchor latticeAnchor =
345  getLeaderAnchorOrSelf<StateT>(LatticeAnchor(anchor));
346  const auto &mapIt = analysisStates.find(latticeAnchor);
347  if (mapIt == analysisStates.end())
348  return nullptr;
349  auto it = mapIt->second.find(TypeID::get<StateT>());
350  if (it == mapIt->second.end())
351  return nullptr;
352  return static_cast<const StateT *>(it->second.get());
353  }
354 
355  /// Erase any analysis state associated with the given lattice anchor.
356  template <typename AnchorT>
357  void eraseState(AnchorT anchor) {
358  LatticeAnchor latticeAnchor(anchor);
359 
360  // Update equivalentAnchorMap.
361  for (auto &&[TypeId, eqClass] : equivalentAnchorMap) {
362  if (!eqClass.contains(latticeAnchor)) {
363  continue;
364  }
365  llvm::EquivalenceClasses<LatticeAnchor>::member_iterator leaderIt =
366  eqClass.findLeader(latticeAnchor);
367 
368  // Update analysis states with new leader if needed.
369  if (*leaderIt == latticeAnchor && ++leaderIt != eqClass.member_end()) {
370  analysisStates[*leaderIt][TypeId] =
371  std::move(analysisStates[latticeAnchor][TypeId]);
372  }
373 
374  eqClass.erase(latticeAnchor);
375  }
376 
377  // Update analysis states.
378  analysisStates.erase(latticeAnchor);
379  }
380 
381  /// Erase all analysis states.
382  void eraseAllStates() {
383  analysisStates.clear();
384  equivalentAnchorMap.clear();
385  }
386 
387  /// Get a uniqued lattice anchor instance. If one is not present, it is
388  /// created with the provided arguments.
389  template <typename AnchorT, typename... Args>
390  AnchorT *getLatticeAnchor(Args &&...args) {
391  return AnchorT::get(uniquer, std::forward<Args>(args)...);
392  }
393 
394  /// Get a uniqued program point instance.
396  if (op->getBlock())
397  return uniquer.get<ProgramPoint>(/*initFn*/ {}, op->getBlock(),
398  Block::iterator(op), nullptr);
399  else
400  return uniquer.get<ProgramPoint>(/*initFn*/ {}, nullptr,
401  Block::iterator(), op);
402  }
403 
405  return uniquer.get<ProgramPoint>(/*initFn*/ {}, block, block->begin(),
406  nullptr);
407  }
408 
410  if (op->getBlock())
411  return uniquer.get<ProgramPoint>(/*initFn*/ {}, op->getBlock(),
412  ++Block::iterator(op), nullptr);
413  else
414  return uniquer.get<ProgramPoint>(/*initFn*/ {}, nullptr,
415  Block::iterator(), op);
416  }
417 
419  return uniquer.get<ProgramPoint>(/*initFn*/ {}, block, block->end(),
420  nullptr);
421  }
422 
423  /// A work item on the solver queue is a program point, child analysis pair.
424  /// Each item is processed by invoking the child analysis at the program
425  /// point.
426  using WorkItem = std::pair<ProgramPoint *, DataFlowAnalysis *>;
427  /// Push a work item onto the worklist.
428  void enqueue(WorkItem item) { worklist.push(std::move(item)); }
429 
430  /// Get the state associated with the given lattice anchor. If it does not
431  /// exist, create an uninitialized state.
432  template <typename StateT, typename AnchorT>
433  StateT *getOrCreateState(AnchorT anchor);
434 
435  /// Get leader lattice anchor in equivalence lattice anchor group, return
436  /// input lattice anchor if input not found in equivalece lattice anchor
437  /// group.
438  template <typename StateT>
440 
441  /// Union input anchors under the given state.
442  template <typename StateT, typename AnchorT>
443  void unionLatticeAnchors(AnchorT anchor, AnchorT other);
444 
445  /// Return given lattice is equivalent on given state.
446  template <typename StateT>
447  bool isEquivalent(LatticeAnchor lhs, LatticeAnchor rhs) const;
448 
449  /// Propagate an update to an analysis state if it changed by pushing
450  /// dependent work items to the back of the queue.
451  /// This should only be used when DataFlowSolver is running.
452  /// Otherwise, the solver won't process the work items.
454 
455  /// Get the configuration of the solver.
456  const DataFlowConfig &getConfig() const { return config; }
457 
458 private:
459  /// Configuration of the dataflow solver.
460  DataFlowConfig config;
461 
462  /// The solver is working on the worklist.
463  bool isRunning = false;
464 
465  /// The solver's work queue. Work items can be inserted to the front of the
466  /// queue to be processed greedily, speeding up computations that otherwise
467  /// quickly degenerate to quadratic due to propagation of state updates.
468  std::queue<WorkItem> worklist;
469 
470  /// Type-erased instances of the children analyses.
472 
473  /// The storage uniquer instance that owns the memory of the allocated lattice
474  /// anchors
475  StorageUniquer uniquer;
476 
477  /// A type-erased map of lattice anchors to associated analysis states for
478  /// first-class lattice anchors.
480  analysisStates;
481 
482  /// A map of Ananlysis state type to the equivalent lattice anchors.
483  /// Lattice anchors are considered equivalent under a certain analysis state
484  /// type if and only if, the analysis states pointed to by these lattice
485  /// anchors necessarily contain identical value.
487 
488  /// Allow the base child analysis class to access the internals of the solver.
489  friend class DataFlowAnalysis;
490 };
491 
492 //===----------------------------------------------------------------------===//
493 // AnalysisState
494 //===----------------------------------------------------------------------===//
495 
496 /// Base class for generic analysis states. Analysis states contain data-flow
497 /// information that are attached to lattice anchors and which evolve as the
498 /// analysis iterates.
499 ///
500 /// This class places no restrictions on the semantics of analysis states beyond
501 /// these requirements.
502 ///
503 /// 1. Querying the state of a lattice anchor prior to visiting that anchor
504 /// results in uninitialized state. Analyses must be aware of unintialized
505 /// states.
506 /// 2. Analysis states can reach fixpoints, where subsequent updates will never
507 /// trigger a change in the state.
508 /// 3. Analysis states that are uninitialized can be forcefully initialized to a
509 /// default value.
511 public:
512  virtual ~AnalysisState();
513 
514  /// Create the analysis state on the given lattice anchor.
516 
517  /// Returns the lattice anchor this state is located at.
518  LatticeAnchor getAnchor() const { return anchor; }
519 
520  /// Print the contents of the analysis state.
521  virtual void print(raw_ostream &os) const = 0;
522  LLVM_DUMP_METHOD void dump() const;
523 
524  /// Add a dependency to this analysis state on a lattice anchor and an
525  /// analysis. If this state is updated, the analysis will be invoked on the
526  /// given lattice anchor again (in onUpdate()).
527  void addDependency(ProgramPoint *point, DataFlowAnalysis *analysis);
528 
529 protected:
530  /// This function is called by the solver when the analysis state is updated
531  /// to enqueue more work items. For example, if a state tracks dependents
532  /// through the IR (e.g. use-def chains), this function can be implemented to
533  /// push those dependents on the worklist.
534  virtual void onUpdate(DataFlowSolver *solver) const {
535  for (const DataFlowSolver::WorkItem &item : dependents)
536  solver->enqueue(item);
537  }
538 
539  /// The lattice anchor to which the state belongs.
541 
542 #if LLVM_ENABLE_ABI_BREAKING_CHECKS
543  /// When compiling with debugging, keep a name for the analysis state.
544  StringRef debugName;
545 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
546 
547 private:
548  /// The dependency relations originating from this analysis state. An entry
549  /// `state -> (analysis, anchor)` is created when `analysis` queries `state`
550  /// when updating `anchor`.
551  ///
552  /// When this state is updated, all dependent child analysis invocations are
553  /// pushed to the back of the queue. Use a `SetVector` to keep the analysis
554  /// deterministic.
555  ///
556  /// Store the dependents on the analysis state for efficiency.
558 
559  /// Allow the framework to access the dependents.
560  friend class DataFlowSolver;
561 };
562 
563 //===----------------------------------------------------------------------===//
564 // DataFlowAnalysis
565 //===----------------------------------------------------------------------===//
566 
567 /// Base class for all data-flow analyses. A child analysis is expected to build
568 /// an initial dependency graph (and optionally provide an initial state) when
569 /// initialized and define transfer functions when visiting program points.
570 ///
571 /// In classical data-flow analysis, the dependency graph is fixed and analyses
572 /// define explicit transfer functions between input states and output states.
573 /// In this framework, however, the dependency graph can change during the
574 /// analysis, and transfer functions are opaque such that the solver doesn't
575 /// know what states calling `visit` on an analysis will be updated. This allows
576 /// multiple analyses to plug in and provide values for the same state.
577 ///
578 /// Generally, when an analysis queries an uninitialized state, it is expected
579 /// to "bail out", i.e., not provide any updates. When the value is initialized,
580 /// the solver will re-invoke the analysis. If the solver exhausts its worklist,
581 /// however, and there are still uninitialized states, the solver "nudges" the
582 /// analyses by default-initializing those states.
584 public:
585  virtual ~DataFlowAnalysis();
586 
587  /// Create an analysis with a reference to the parent solver.
588  explicit DataFlowAnalysis(DataFlowSolver &solver);
589 
590  /// Initialize the analysis from the provided top-level operation by building
591  /// an initial dependency graph between all lattice anchors of interest. This
592  /// can be implemented by calling `visit` on all program points of interest
593  /// below the top-level operation.
594  ///
595  /// An analysis can optionally provide initial values to certain analysis
596  /// states to influence the evolution of the analysis.
597  virtual LogicalResult initialize(Operation *top) = 0;
598 
599  /// Visit the given program point. This function is invoked by the solver on
600  /// this analysis with a given program point when a dependent analysis state
601  /// is updated. The function is similar to a transfer function; it queries
602  /// certain analysis states and sets other states.
603  ///
604  /// The function is expected to create dependencies on queried states and
605  /// propagate updates on changed states. A dependency can be created by
606  /// calling `addDependency` between the input state and a program point,
607  /// indicating that, if the state is updated, the solver should invoke `solve`
608  /// on the program point. The dependent point does not have to be the same as
609  /// the provided point. An update to a state is propagated by calling
610  /// `propagateIfChange` on the state. If the state has changed, then all its
611  /// dependents are placed on the worklist.
612  ///
613  /// The dependency graph does not need to be static. Each invocation of
614  /// `visit` can add new dependencies, but these dependencies will not be
615  /// dynamically added to the worklist because the solver doesn't know what
616  /// will provide a value for then.
617  virtual LogicalResult visit(ProgramPoint *point) = 0;
618 
619  /// Initialize lattice anchor equivalence class from the provided top-level
620  /// operation.
621  ///
622  /// This function will union lattice anchor to same equivalent class if the
623  /// analysis can determine the lattice content of lattice anchor is
624  /// necessarily identical under the corrensponding lattice type.
625  virtual void initializeEquivalentLatticeAnchor(Operation *top) { return; }
626 
627 protected:
628  /// Create a dependency between the given analysis state and lattice anchor
629  /// on this analysis.
630  void addDependency(AnalysisState *state, ProgramPoint *point);
631 
632  /// Propagate an update to a state if it changed.
634 
635  /// Register a custom lattice anchor class.
636  template <typename AnchorT>
638  solver.uniquer.registerParametricStorageType<AnchorT>();
639  }
640 
641  /// Get or create a custom lattice anchor.
642  template <typename AnchorT, typename... Args>
643  AnchorT *getLatticeAnchor(Args &&...args) {
644  return solver.getLatticeAnchor<AnchorT>(std::forward<Args>(args)...);
645  }
646 
647  /// Union input anchors under the given state.
648  template <typename StateT, typename AnchorT>
649  void unionLatticeAnchors(AnchorT anchor, AnchorT other) {
650  return solver.unionLatticeAnchors<StateT>(anchor, other);
651  }
652 
653  /// Get the analysis state associated with the lattice anchor. The returned
654  /// state is expected to be "write-only", and any updates need to be
655  /// propagated by `propagateIfChanged`.
656  template <typename StateT, typename AnchorT>
657  StateT *getOrCreate(AnchorT anchor) {
658  return solver.getOrCreateState<StateT>(anchor);
659  }
660 
661  /// Get a read-only analysis state for the given point and create a dependency
662  /// on `dependent`. If the return state is updated elsewhere, this analysis is
663  /// re-invoked on the dependent.
664  template <typename StateT, typename AnchorT>
665  const StateT *getOrCreateFor(ProgramPoint *dependent, AnchorT anchor) {
666  StateT *state = getOrCreate<StateT>(anchor);
667  if (!solver.isEquivalent<StateT>(LatticeAnchor(anchor),
668  LatticeAnchor(dependent)))
669  addDependency(state, dependent);
670  return state;
671  }
672 
673  /// Get a uniqued program point instance.
675  return solver.getProgramPointBefore(op);
676  }
677 
679  return solver.getProgramPointBefore(block);
680  }
681 
683  return solver.getProgramPointAfter(op);
684  }
685 
687  return solver.getProgramPointAfter(block);
688  }
689 
690  /// Return the configuration of the solver used for this analysis.
691  const DataFlowConfig &getSolverConfig() const { return solver.getConfig(); }
692 
693 #if LLVM_ENABLE_ABI_BREAKING_CHECKS
694  /// When compiling with debugging, keep a name for the analyis.
695  StringRef debugName;
696 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
697 
698 private:
699  /// The parent data-flow solver.
700  DataFlowSolver &solver;
701 
702  /// Allow the data-flow solver to access the internals of this class.
703  friend class DataFlowSolver;
704 };
705 
706 template <typename AnalysisT, typename... Args>
707 AnalysisT *DataFlowSolver::load(Args &&...args) {
708  childAnalyses.emplace_back(new AnalysisT(*this, std::forward<Args>(args)...));
709 #if LLVM_ENABLE_ABI_BREAKING_CHECKS
710  childAnalyses.back().get()->debugName = llvm::getTypeName<AnalysisT>();
711 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
712  return static_cast<AnalysisT *>(childAnalyses.back().get());
713 }
714 
715 template <typename StateT>
718  if (!equivalentAnchorMap.contains(TypeID::get<StateT>())) {
719  return latticeAnchor;
720  }
721  const llvm::EquivalenceClasses<LatticeAnchor> &eqClass =
722  equivalentAnchorMap.at(TypeID::get<StateT>());
723  llvm::EquivalenceClasses<LatticeAnchor>::member_iterator leaderIt =
724  eqClass.findLeader(latticeAnchor);
725  if (leaderIt != eqClass.member_end()) {
726  return *leaderIt;
727  }
728  return latticeAnchor;
729 }
730 
731 template <typename StateT, typename AnchorT>
732 StateT *DataFlowSolver::getOrCreateState(AnchorT anchor) {
733  // Replace to leader anchor if found.
734  LatticeAnchor latticeAnchor(anchor);
735  latticeAnchor = getLeaderAnchorOrSelf<StateT>(latticeAnchor);
736  std::unique_ptr<AnalysisState> &state =
737  analysisStates[latticeAnchor][TypeID::get<StateT>()];
738  if (!state) {
739  state = std::unique_ptr<StateT>(new StateT(anchor));
740 #if LLVM_ENABLE_ABI_BREAKING_CHECKS
741  state->debugName = llvm::getTypeName<StateT>();
742 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
743  }
744  return static_cast<StateT *>(state.get());
745 }
746 
747 template <typename StateT>
749  if (!equivalentAnchorMap.contains(TypeID::get<StateT>())) {
750  return false;
751  }
752  const llvm::EquivalenceClasses<LatticeAnchor> &eqClass =
753  equivalentAnchorMap.at(TypeID::get<StateT>());
754  if (!eqClass.contains(lhs) || !eqClass.contains(rhs))
755  return false;
756  return eqClass.isEquivalent(lhs, rhs);
757 }
758 
759 template <typename StateT, typename AnchorT>
760 void DataFlowSolver::unionLatticeAnchors(AnchorT anchor, AnchorT other) {
761  llvm::EquivalenceClasses<LatticeAnchor> &eqClass =
762  equivalentAnchorMap[TypeID::get<StateT>()];
763  eqClass.unionSets(LatticeAnchor(anchor), LatticeAnchor(other));
764 }
765 
766 inline raw_ostream &operator<<(raw_ostream &os, const AnalysisState &state) {
767  state.print(os);
768  return os;
769 }
770 
771 inline raw_ostream &operator<<(raw_ostream &os, const LatticeAnchor &anchor) {
772  anchor.print(os);
773  return os;
774 }
775 
776 } // end namespace mlir
777 
778 namespace llvm {
779 /// Allow hashing of lattice anchors and program points.
780 template <>
781 struct DenseMapInfo<mlir::ProgramPoint> {
783  void *pointer = llvm::DenseMapInfo<void *>::getEmptyKey();
784  return mlir::ProgramPoint(
785  (mlir::Block *)pointer,
787  }
790  return mlir::ProgramPoint(
791  (mlir::Block *)pointer,
793  }
794  static unsigned getHashValue(mlir::ProgramPoint pp) {
795  return hash_combine(pp.getBlock(), pp.getPoint().getNodePtr());
796  }
798  return lhs == rhs;
799  }
800 };
801 
802 // Allow llvm::cast style functions.
803 template <typename To>
804 struct CastInfo<To, mlir::LatticeAnchor>
805  : public CastInfo<To, mlir::LatticeAnchor::PointerUnion> {};
806 
807 template <typename To>
808 struct CastInfo<To, const mlir::LatticeAnchor>
809  : public CastInfo<To, const mlir::LatticeAnchor::PointerUnion> {};
810 
811 } // end namespace llvm
812 
813 #endif // MLIR_ANALYSIS_DATAFLOWFRAMEWORK_H
Base class for generic analysis states.
AnalysisState(LatticeAnchor anchor)
Create the analysis state on the given lattice anchor.
LLVM_DUMP_METHOD void dump() const
LatticeAnchor getAnchor() const
Returns the lattice anchor this state is located at.
void addDependency(ProgramPoint *point, DataFlowAnalysis *analysis)
Add a dependency to this analysis state on a lattice anchor and an analysis.
virtual void print(raw_ostream &os) const =0
Print the contents of the analysis state.
virtual void onUpdate(DataFlowSolver *solver) const
This function is called by the solver when the analysis state is updated to enqueue more work items.
virtual ~AnalysisState()
LatticeAnchor anchor
The lattice anchor to which the state belongs.
Block represents an ordered list of Operations.
Definition: Block.h:33
OpListType::iterator iterator
Definition: Block.h:140
iterator end()
Definition: Block.h:144
iterator begin()
Definition: Block.h:143
Base class for all data-flow analyses.
void addDependency(AnalysisState *state, ProgramPoint *point)
Create a dependency between the given analysis state and lattice anchor on this analysis.
void unionLatticeAnchors(AnchorT anchor, AnchorT other)
Union input anchors under the given state.
void propagateIfChanged(AnalysisState *state, ChangeResult changed)
Propagate an update to a state if it changed.
const StateT * getOrCreateFor(ProgramPoint *dependent, AnchorT anchor)
Get a read-only analysis state for the given point and create a dependency on dependent.
ProgramPoint * getProgramPointAfter(Operation *op)
ProgramPoint * getProgramPointBefore(Operation *op)
Get a uniqued program point instance.
virtual void initializeEquivalentLatticeAnchor(Operation *top)
Initialize lattice anchor equivalence class from the provided top-level operation.
AnchorT * getLatticeAnchor(Args &&...args)
Get or create a custom lattice anchor.
StateT * getOrCreate(AnchorT anchor)
Get the analysis state associated with the lattice anchor.
const DataFlowConfig & getSolverConfig() const
Return the configuration of the solver used for this analysis.
ProgramPoint * getProgramPointAfter(Block *block)
DataFlowAnalysis(DataFlowSolver &solver)
Create an analysis with a reference to the parent solver.
virtual LogicalResult initialize(Operation *top)=0
Initialize the analysis from the provided top-level operation by building an initial dependency graph...
ProgramPoint * getProgramPointBefore(Block *block)
virtual LogicalResult visit(ProgramPoint *point)=0
Visit the given program point.
void registerAnchorKind()
Register a custom lattice anchor class.
Configuration class for data flow solver and child analyses.
DataFlowConfig()=default
DataFlowConfig & setInterprocedural(bool enable)
Set whether the solver should operate interpocedurally, i.e.
bool isInterprocedural() const
Return true if the solver operates interprocedurally, false otherwise.
The general data-flow analysis solver.
void unionLatticeAnchors(AnchorT anchor, AnchorT other)
Union input anchors under the given state.
void enqueue(WorkItem item)
Push a work item onto the worklist.
bool isEquivalent(LatticeAnchor lhs, LatticeAnchor rhs) const
Return given lattice is equivalent on given state.
void eraseState(AnchorT anchor)
Erase any analysis state associated with the given lattice anchor.
void propagateIfChanged(AnalysisState *state, ChangeResult changed)
Propagate an update to an analysis state if it changed by pushing dependent work items to the back of...
const StateT * lookupState(AnchorT anchor) const
Lookup an analysis state for the given lattice anchor.
ProgramPoint * getProgramPointAfter(Operation *op)
const DataFlowConfig & getConfig() const
Get the configuration of the solver.
ProgramPoint * getProgramPointBefore(Operation *op)
Get a uniqued program point instance.
void eraseAllStates()
Erase all analysis states.
AnchorT * getLatticeAnchor(Args &&...args)
Get a uniqued lattice anchor instance.
ProgramPoint * getProgramPointBefore(Block *block)
StateT * getOrCreateState(AnchorT anchor)
Get the state associated with the given lattice anchor.
LatticeAnchor getLeaderAnchorOrSelf(LatticeAnchor latticeAnchor) const
Get leader lattice anchor in equivalence lattice anchor group, return input lattice anchor if input n...
ProgramPoint * getProgramPointAfter(Block *block)
AnalysisT * load(Args &&...args)
Load an analysis into the solver. Return the analysis instance.
LogicalResult initializeAndRun(Operation *top)
Initialize the children analyses starting from the provided top-level operation and run the analysis ...
DataFlowSolver(const DataFlowConfig &config=DataFlowConfig())
std::pair< ProgramPoint *, DataFlowAnalysis * > WorkItem
A work item on the solver queue is a program point, child analysis pair.
Base class for generic lattice anchor based on a concrete lattice anchor type and a content key.
bool operator==(const Value &value) const
Two lattice anchors are equal if their values are equal.
static ConcreteT * construct(StorageUniquer::StorageAllocator &alloc, ValueT &&value)
Allocate space for a lattice anchor and construct it in-place.
static bool classof(const GenericLatticeAnchor *point)
Provide LLVM-style RTTI using type IDs.
GenericLatticeAnchorBase(ValueT &&value)
Construct an instance of the lattice anchor using the provided value and the type ID of the concrete ...
const Value & getValue() const
Get the contents of the lattice anchor.
static ConcreteT * get(StorageUniquer &uniquer, Args &&...args)
Get a uniqued instance of this lattice anchor class with the given arguments.
Abstract class for generic lattice anchor.
virtual void print(raw_ostream &os) const =0
Print the lattice anchor.
TypeID getTypeID() const
Get the abstract lattice anchor's type identifier.
virtual Location getLoc() const =0
Get a derived source location for the lattice anchor.
GenericLatticeAnchor(TypeID typeID)
Create an abstract lattice anchor with type identifier.
This class defines the main interface for locations in MLIR and acts as a non-nullable wrapper around...
Definition: Location.h:66
Operation is the basic unit of execution within MLIR.
Definition: Operation.h:88
Block * getBlock()
Returns the operation block that contains this operation.
Definition: Operation.h:213
This class acts as the base storage that all storage classes must derived from.
This is a utility allocator used to allocate memory for instances of derived types.
T * allocate()
Allocate an instance of the provided type.
A utility class to get or create instances of "storage classes".
Storage * get(function_ref< void(Storage *)> initFn, TypeID id, Args &&...args)
Gets a uniqued instance of 'Storage'.
void registerParametricStorageType(TypeID id)
Register a new parametric storage class, this is necessary to create instances of this class type.
This class provides an efficient unique identifier for a specific C++ type.
Definition: TypeID.h:107
This class represents an instance of an SSA value in the MLIR system, representing a computable value...
Definition: Value.h:96
The OpAsmOpInterface, see OpAsmInterface.td for more details.
Definition: CallGraph.h:229
Include the generated interface declarations.
ChangeResult & operator|=(ChangeResult &lhs, ChangeResult rhs)
const FrozenRewritePatternSet GreedyRewriteConfig bool * changed
ChangeResult
A result type used to indicate if a change happened.
ChangeResult operator&(ChangeResult lhs, ChangeResult rhs)
ChangeResult operator|(ChangeResult lhs, ChangeResult rhs)
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
raw_ostream & operator<<(raw_ostream &os, const AliasResult &result)
Definition: AliasAnalysis.h:78
static unsigned getHashValue(mlir::ProgramPoint pp)
static mlir::ProgramPoint getEmptyKey()
static mlir::ProgramPoint getTombstoneKey()
static bool isEqual(mlir::ProgramPoint lhs, mlir::ProgramPoint rhs)
Fundamental IR components are supported as first-class lattice anchor.
LatticeAnchor(ParentTy point=nullptr)
Allow implicit conversion from the parent type.
Location getLoc() const
Get the source location of the lattice anchor.
void print(raw_ostream &os) const
Print the lattice anchor.
Program point represents a specific location in the execution of a program.
bool isNull() const
Returns true if this program point is set.
bool isBlockStart() const
ProgramPoint(Block *parentBlock, Block::iterator pp)
Creates a new program point at the given location.
Block::iterator getPoint() const
Get the the iterator this program point refers to.
ProgramPoint()
Create a empty program point.
Operation * getOperation() const
Get the the iterator this program point refers to.
Operation * getPrevOp() const
Get the previous operation of this program point.
static ProgramPoint * construct(StorageUniquer::StorageAllocator &alloc, KeyTy &&key)
bool operator==(const ProgramPoint &pp) const
bool operator==(const KeyTy &key) const
Two program points are equal if their block and iterator are equal.
ProgramPoint(const ProgramPoint &point)
Create a new program point from the given program point.
std::tuple< Block *, Block::iterator, Operation * > KeyTy
The concrete key type used by the storage uniquer.
void print(raw_ostream &os) const
Print the program point.
Operation * getNextOp() const
Get the next operation of this program point.
Block * getBlock() const
Get the block contains this program point.
ProgramPoint(Operation *op)
Creates a new program point at the given operation.