MLIR  20.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/Hashing.h"
22 #include "llvm/ADT/SetVector.h"
23 #include "llvm/Support/Compiler.h"
24 #include "llvm/Support/TypeName.h"
25 #include <queue>
26 #include <tuple>
27 
28 namespace mlir {
29 
30 //===----------------------------------------------------------------------===//
31 // ChangeResult
32 //===----------------------------------------------------------------------===//
33 
34 /// A result type used to indicate if a change happened. Boolean operations on
35 /// ChangeResult behave as though `Change` is truth.
36 enum class [[nodiscard]] ChangeResult {
37  NoChange,
38  Change,
39 };
41  return lhs == ChangeResult::Change ? lhs : rhs;
42 }
44  lhs = lhs | rhs;
45  return lhs;
46 }
48  return lhs == ChangeResult::NoChange ? lhs : rhs;
49 }
50 
51 /// Forward declare the analysis state class.
52 class AnalysisState;
53 
54 /// Program point represents a specific location in the execution of a program.
55 /// A sequence of program points can be combined into a control flow graph.
57  /// Creates a new program point at the given location.
59  : block(parentBlock), point(pp) {}
60 
61  /// Creates a new program point at the given operation.
62  ProgramPoint(Operation *op) : op(op) {}
63 
64  /// The concrete key type used by the storage uniquer. This class is uniqued
65  /// by its contents.
66  using KeyTy = std::tuple<Block *, Block::iterator, Operation *>;
67 
68  /// Create a empty program point.
70 
71  /// Create a new program point from the given program point.
72  ProgramPoint(const ProgramPoint &point) {
73  this->block = point.getBlock();
74  this->point = point.getPoint();
75  this->op = point.getOperation();
76  }
77 
79  KeyTy &&key) {
80  if (std::get<0>(key)) {
81  return new (alloc.allocate<ProgramPoint>())
82  ProgramPoint(std::get<0>(key), std::get<1>(key));
83  }
84  return new (alloc.allocate<ProgramPoint>()) ProgramPoint(std::get<2>(key));
85  }
86 
87  /// Returns true if this program point is set.
88  bool isNull() const { return block == nullptr && op == nullptr; }
89 
90  /// Two program points are equal if their block and iterator are equal.
91  bool operator==(const KeyTy &key) const {
92  return block == std::get<0>(key) && point == std::get<1>(key) &&
93  op == std::get<2>(key);
94  }
95 
96  bool operator==(const ProgramPoint &pp) const {
97  return block == pp.block && point == pp.point && op == pp.op;
98  }
99 
100  /// Get the block contains this program point.
101  Block *getBlock() const { return block; }
102 
103  /// Get the the iterator this program point refers to.
104  Block::iterator getPoint() const { return point; }
105 
106  /// Get the the iterator this program point refers to.
107  Operation *getOperation() const { return op; }
108 
109  /// Get the next operation of this program point.
110  Operation *getNextOp() const {
111  assert(!isBlockEnd());
112  // If the current program point has no parent block, both the next op and
113  // the previous op point to the op corresponding to the current program
114  // point.
115  if (block == nullptr) {
116  return op;
117  }
118  return &*point;
119  }
120 
121  /// Get the previous operation of this program point.
122  Operation *getPrevOp() const {
123  assert(!isBlockStart());
124  // If the current program point has no parent block, both the next op and
125  // the previous op point to the op corresponding to the current program
126  // point.
127  if (block == nullptr) {
128  return op;
129  }
130  return &*(--Block::iterator(point));
131  }
132 
133  bool isBlockStart() const { return block && block->begin() == point; }
134 
135  bool isBlockEnd() const { return block && block->end() == point; }
136 
137  /// Print the program point.
138  void print(raw_ostream &os) const;
139 
140 private:
141  Block *block = nullptr;
142  Block::iterator point;
143 
144  /// For operations without a parent block, we record the operation itself as
145  /// its program point.
146  Operation *op = nullptr;
147 };
148 
149 inline raw_ostream &operator<<(raw_ostream &os, ProgramPoint point) {
150  point.print(os);
151  return os;
152 }
153 
154 //===----------------------------------------------------------------------===//
155 // GenericLatticeAnchor
156 //===----------------------------------------------------------------------===//
157 
158 /// Abstract class for generic lattice anchor. In classical data-flow analysis,
159 /// lattice anchor represent positions in a program to which lattice elements
160 /// are attached. In sparse data-flow analysis, these can be SSA values, and in
161 /// dense data-flow analysis, these are the program points before and after
162 /// every operation.
163 ///
164 /// Lattice anchor are implemented using MLIR's storage uniquer framework and
165 /// type ID system to provide RTTI.
167 public:
169 
170  /// Get the abstract lattice anchor's type identifier.
171  TypeID getTypeID() const { return typeID; }
172 
173  /// Get a derived source location for the lattice anchor.
174  virtual Location getLoc() const = 0;
175 
176  /// Print the lattice anchor.
177  virtual void print(raw_ostream &os) const = 0;
178 
179 protected:
180  /// Create an abstract lattice anchor with type identifier.
181  explicit GenericLatticeAnchor(TypeID typeID) : typeID(typeID) {}
182 
183 private:
184  /// The type identifier of the lattice anchor.
185  TypeID typeID;
186 };
187 
188 //===----------------------------------------------------------------------===//
189 // GenericLatticeAnchorBase
190 //===----------------------------------------------------------------------===//
191 
192 /// Base class for generic lattice anchor based on a concrete lattice anchor
193 /// type and a content key. This class defines the common methods required for
194 /// operability with the storage uniquer framework.
195 ///
196 /// The provided key type uniquely identifies the concrete lattice anchor
197 /// instance and are the data members of the class.
198 template <typename ConcreteT, typename Value>
200 public:
201  /// The concrete key type used by the storage uniquer. This class is uniqued
202  /// by its contents.
203  using KeyTy = Value;
204  /// Alias for the base class.
206 
207  /// Construct an instance of the lattice anchor using the provided value and
208  /// the type ID of the concrete type.
209  template <typename ValueT>
210  explicit GenericLatticeAnchorBase(ValueT &&value)
211  : GenericLatticeAnchor(TypeID::get<ConcreteT>()),
212  value(std::forward<ValueT>(value)) {}
213 
214  /// Get a uniqued instance of this lattice anchor class with the given
215  /// arguments.
216  template <typename... Args>
217  static ConcreteT *get(StorageUniquer &uniquer, Args &&...args) {
218  return uniquer.get<ConcreteT>(/*initFn=*/{}, std::forward<Args>(args)...);
219  }
220 
221  /// Allocate space for a lattice anchor and construct it in-place.
222  template <typename ValueT>
223  static ConcreteT *construct(StorageUniquer::StorageAllocator &alloc,
224  ValueT &&value) {
225  return new (alloc.allocate<ConcreteT>())
226  ConcreteT(std::forward<ValueT>(value));
227  }
228 
229  /// Two lattice anchors are equal if their values are equal.
230  bool operator==(const Value &value) const { return this->value == value; }
231 
232  /// Provide LLVM-style RTTI using type IDs.
233  static bool classof(const GenericLatticeAnchor *point) {
234  return point->getTypeID() == TypeID::get<ConcreteT>();
235  }
236 
237  /// Get the contents of the lattice anchor.
238  const Value &getValue() const { return value; }
239 
240 private:
241  /// The lattice anchor value.
242  Value value;
243 };
244 
245 //===----------------------------------------------------------------------===//
246 // LatticeAnchor
247 //===----------------------------------------------------------------------===//
248 
249 /// Fundamental IR components are supported as first-class lattice anchor.
251  : public PointerUnion<GenericLatticeAnchor *, ProgramPoint *, Value> {
253  /// Inherit constructors.
254  using ParentTy::PointerUnion;
255  /// Allow implicit conversion from the parent type.
256  LatticeAnchor(ParentTy point = nullptr) : ParentTy(point) {}
257 
258  /// Print the lattice anchor.
259  void print(raw_ostream &os) const;
260 
261  /// Get the source location of the lattice anchor.
262  Location getLoc() const;
263 };
264 
265 /// Forward declaration of the data-flow analysis class.
266 class DataFlowAnalysis;
267 
268 //===----------------------------------------------------------------------===//
269 // DataFlowConfig
270 //===----------------------------------------------------------------------===//
271 
272 /// Configuration class for data flow solver and child analyses. Follows the
273 /// fluent API pattern.
275 public:
276  DataFlowConfig() = default;
277 
278  /// Set whether the solver should operate interpocedurally, i.e. enter the
279  /// callee body when available. Interprocedural analyses may be more precise,
280  /// but also more expensive as more states need to be computed and the
281  /// fixpoint convergence takes longer.
283  interprocedural = enable;
284  return *this;
285  }
286 
287  /// Return `true` if the solver operates interprocedurally, `false` otherwise.
288  bool isInterprocedural() const { return interprocedural; }
289 
290 private:
291  bool interprocedural = true;
292 };
293 
294 //===----------------------------------------------------------------------===//
295 // DataFlowSolver
296 //===----------------------------------------------------------------------===//
297 
298 /// The general data-flow analysis solver. This class is responsible for
299 /// orchestrating child data-flow analyses, running the fixed-point iteration
300 /// algorithm, managing analysis state and lattice anchor memory, and tracking
301 /// dependencies between analyses, lattice anchor, and analysis states.
302 ///
303 /// Steps to run a data-flow analysis:
304 ///
305 /// 1. Load and initialize children analyses. Children analyses are instantiated
306 /// in the solver and initialized, building their dependency relations.
307 /// 2. Configure and run the analysis. The solver invokes the children analyses
308 /// according to their dependency relations until a fixed point is reached.
309 /// 3. Query analysis state results from the solver.
310 ///
311 /// TODO: Optimize the internal implementation of the solver.
313 public:
314  explicit DataFlowSolver(const DataFlowConfig &config = DataFlowConfig())
315  : config(config) {
317  }
318 
319  /// Load an analysis into the solver. Return the analysis instance.
320  template <typename AnalysisT, typename... Args>
321  AnalysisT *load(Args &&...args);
322 
323  /// Initialize the children analyses starting from the provided top-level
324  /// operation and run the analysis until fixpoint.
325  LogicalResult initializeAndRun(Operation *top);
326 
327  /// Lookup an analysis state for the given lattice anchor. Returns null if one
328  /// does not exist.
329  template <typename StateT, typename AnchorT>
330  const StateT *lookupState(AnchorT anchor) const {
331  auto it =
332  analysisStates.find({LatticeAnchor(anchor), TypeID::get<StateT>()});
333  if (it == analysisStates.end())
334  return nullptr;
335  return static_cast<const StateT *>(it->second.get());
336  }
337 
338  /// Erase any analysis state associated with the given lattice anchor.
339  template <typename AnchorT>
340  void eraseState(AnchorT anchor) {
341  LatticeAnchor la(anchor);
342 
343  for (auto it = analysisStates.begin(); it != analysisStates.end(); ++it) {
344  if (it->first.first == la)
345  analysisStates.erase(it);
346  }
347  }
348 
349  /// Get a uniqued lattice anchor instance. If one is not present, it is
350  /// created with the provided arguments.
351  template <typename AnchorT, typename... Args>
352  AnchorT *getLatticeAnchor(Args &&...args) {
353  return AnchorT::get(uniquer, std::forward<Args>(args)...);
354  }
355 
356  /// Get a uniqued program point instance.
358  if (op->getBlock())
359  return uniquer.get<ProgramPoint>(/*initFn*/ {}, op->getBlock(),
360  Block::iterator(op), nullptr);
361  else
362  return uniquer.get<ProgramPoint>(/*initFn*/ {}, nullptr,
363  Block::iterator(), op);
364  }
365 
367  return uniquer.get<ProgramPoint>(/*initFn*/ {}, block, block->begin(),
368  nullptr);
369  }
370 
372  if (op->getBlock())
373  return uniquer.get<ProgramPoint>(/*initFn*/ {}, op->getBlock(),
374  ++Block::iterator(op), nullptr);
375  else
376  return uniquer.get<ProgramPoint>(/*initFn*/ {}, nullptr,
377  Block::iterator(), op);
378  }
379 
381  return uniquer.get<ProgramPoint>(/*initFn*/ {}, block, block->end(),
382  nullptr);
383  }
384 
385  /// A work item on the solver queue is a program point, child analysis pair.
386  /// Each item is processed by invoking the child analysis at the program
387  /// point.
388  using WorkItem = std::pair<ProgramPoint *, DataFlowAnalysis *>;
389  /// Push a work item onto the worklist.
390  void enqueue(WorkItem item) { worklist.push(std::move(item)); }
391 
392  /// Get the state associated with the given lattice anchor. If it does not
393  /// exist, create an uninitialized state.
394  template <typename StateT, typename AnchorT>
395  StateT *getOrCreateState(AnchorT anchor);
396 
397  /// Propagate an update to an analysis state if it changed by pushing
398  /// dependent work items to the back of the queue.
399  void propagateIfChanged(AnalysisState *state, ChangeResult changed);
400 
401  /// Get the configuration of the solver.
402  const DataFlowConfig &getConfig() const { return config; }
403 
404 private:
405  /// Configuration of the dataflow solver.
406  DataFlowConfig config;
407 
408  /// The solver's work queue. Work items can be inserted to the front of the
409  /// queue to be processed greedily, speeding up computations that otherwise
410  /// quickly degenerate to quadratic due to propagation of state updates.
411  std::queue<WorkItem> worklist;
412 
413  /// Type-erased instances of the children analyses.
415 
416  /// The storage uniquer instance that owns the memory of the allocated lattice
417  /// anchors
418  StorageUniquer uniquer;
419 
420  /// A type-erased map of lattice anchors to associated analysis states for
421  /// first-class lattice anchors.
422  DenseMap<std::pair<LatticeAnchor, TypeID>, std::unique_ptr<AnalysisState>>
423  analysisStates;
424 
425  /// Allow the base child analysis class to access the internals of the solver.
426  friend class DataFlowAnalysis;
427 };
428 
429 //===----------------------------------------------------------------------===//
430 // AnalysisState
431 //===----------------------------------------------------------------------===//
432 
433 /// Base class for generic analysis states. Analysis states contain data-flow
434 /// information that are attached to lattice anchors and which evolve as the
435 /// analysis iterates.
436 ///
437 /// This class places no restrictions on the semantics of analysis states beyond
438 /// these requirements.
439 ///
440 /// 1. Querying the state of a lattice anchor prior to visiting that anchor
441 /// results in uninitialized state. Analyses must be aware of unintialized
442 /// states.
443 /// 2. Analysis states can reach fixpoints, where subsequent updates will never
444 /// trigger a change in the state.
445 /// 3. Analysis states that are uninitialized can be forcefully initialized to a
446 /// default value.
448 public:
449  virtual ~AnalysisState();
450 
451  /// Create the analysis state on the given lattice anchor.
453 
454  /// Returns the lattice anchor this state is located at.
455  LatticeAnchor getAnchor() const { return anchor; }
456 
457  /// Print the contents of the analysis state.
458  virtual void print(raw_ostream &os) const = 0;
459  LLVM_DUMP_METHOD void dump() const;
460 
461  /// Add a dependency to this analysis state on a lattice anchor and an
462  /// analysis. If this state is updated, the analysis will be invoked on the
463  /// given lattice anchor again (in onUpdate()).
464  void addDependency(ProgramPoint *point, DataFlowAnalysis *analysis);
465 
466 protected:
467  /// This function is called by the solver when the analysis state is updated
468  /// to enqueue more work items. For example, if a state tracks dependents
469  /// through the IR (e.g. use-def chains), this function can be implemented to
470  /// push those dependents on the worklist.
471  virtual void onUpdate(DataFlowSolver *solver) const {
472  for (const DataFlowSolver::WorkItem &item : dependents)
473  solver->enqueue(item);
474  }
475 
476  /// The lattice anchor to which the state belongs.
478 
479 #if LLVM_ENABLE_ABI_BREAKING_CHECKS
480  /// When compiling with debugging, keep a name for the analysis state.
481  StringRef debugName;
482 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
483 
484 private:
485  /// The dependency relations originating from this analysis state. An entry
486  /// `state -> (analysis, anchor)` is created when `analysis` queries `state`
487  /// when updating `anchor`.
488  ///
489  /// When this state is updated, all dependent child analysis invocations are
490  /// pushed to the back of the queue. Use a `SetVector` to keep the analysis
491  /// deterministic.
492  ///
493  /// Store the dependents on the analysis state for efficiency.
495 
496  /// Allow the framework to access the dependents.
497  friend class DataFlowSolver;
498 };
499 
500 //===----------------------------------------------------------------------===//
501 // DataFlowAnalysis
502 //===----------------------------------------------------------------------===//
503 
504 /// Base class for all data-flow analyses. A child analysis is expected to build
505 /// an initial dependency graph (and optionally provide an initial state) when
506 /// initialized and define transfer functions when visiting program points.
507 ///
508 /// In classical data-flow analysis, the dependency graph is fixed and analyses
509 /// define explicit transfer functions between input states and output states.
510 /// In this framework, however, the dependency graph can change during the
511 /// analysis, and transfer functions are opaque such that the solver doesn't
512 /// know what states calling `visit` on an analysis will be updated. This allows
513 /// multiple analyses to plug in and provide values for the same state.
514 ///
515 /// Generally, when an analysis queries an uninitialized state, it is expected
516 /// to "bail out", i.e., not provide any updates. When the value is initialized,
517 /// the solver will re-invoke the analysis. If the solver exhausts its worklist,
518 /// however, and there are still uninitialized states, the solver "nudges" the
519 /// analyses by default-initializing those states.
521 public:
522  virtual ~DataFlowAnalysis();
523 
524  /// Create an analysis with a reference to the parent solver.
525  explicit DataFlowAnalysis(DataFlowSolver &solver);
526 
527  /// Initialize the analysis from the provided top-level operation by building
528  /// an initial dependency graph between all lattice anchors of interest. This
529  /// can be implemented by calling `visit` on all program points of interest
530  /// below the top-level operation.
531  ///
532  /// An analysis can optionally provide initial values to certain analysis
533  /// states to influence the evolution of the analysis.
534  virtual LogicalResult initialize(Operation *top) = 0;
535 
536  /// Visit the given program point. This function is invoked by the solver on
537  /// this analysis with a given program point when a dependent analysis state
538  /// is updated. The function is similar to a transfer function; it queries
539  /// certain analysis states and sets other states.
540  ///
541  /// The function is expected to create dependencies on queried states and
542  /// propagate updates on changed states. A dependency can be created by
543  /// calling `addDependency` between the input state and a program point,
544  /// indicating that, if the state is updated, the solver should invoke `solve`
545  /// on the program point. The dependent point does not have to be the same as
546  /// the provided point. An update to a state is propagated by calling
547  /// `propagateIfChange` on the state. If the state has changed, then all its
548  /// dependents are placed on the worklist.
549  ///
550  /// The dependency graph does not need to be static. Each invocation of
551  /// `visit` can add new dependencies, but these dependencies will not be
552  /// dynamically added to the worklist because the solver doesn't know what
553  /// will provide a value for then.
554  virtual LogicalResult visit(ProgramPoint *point) = 0;
555 
556 protected:
557  /// Create a dependency between the given analysis state and lattice anchor
558  /// on this analysis.
559  void addDependency(AnalysisState *state, ProgramPoint *point);
560 
561  /// Propagate an update to a state if it changed.
562  void propagateIfChanged(AnalysisState *state, ChangeResult changed);
563 
564  /// Register a custom lattice anchor class.
565  template <typename AnchorT>
567  solver.uniquer.registerParametricStorageType<AnchorT>();
568  }
569 
570  /// Get or create a custom lattice anchor.
571  template <typename AnchorT, typename... Args>
572  AnchorT *getLatticeAnchor(Args &&...args) {
573  return solver.getLatticeAnchor<AnchorT>(std::forward<Args>(args)...);
574  }
575 
576  /// Get the analysis state associated with the lattice anchor. The returned
577  /// state is expected to be "write-only", and any updates need to be
578  /// propagated by `propagateIfChanged`.
579  template <typename StateT, typename AnchorT>
580  StateT *getOrCreate(AnchorT anchor) {
581  return solver.getOrCreateState<StateT>(anchor);
582  }
583 
584  /// Get a read-only analysis state for the given point and create a dependency
585  /// on `dependent`. If the return state is updated elsewhere, this analysis is
586  /// re-invoked on the dependent.
587  template <typename StateT, typename AnchorT>
588  const StateT *getOrCreateFor(ProgramPoint *dependent, AnchorT anchor) {
589  StateT *state = getOrCreate<StateT>(anchor);
590  addDependency(state, dependent);
591  return state;
592  }
593 
594  /// Get a uniqued program point instance.
596  return solver.getProgramPointBefore(op);
597  }
598 
600  return solver.getProgramPointBefore(block);
601  }
602 
604  return solver.getProgramPointAfter(op);
605  }
606 
608  return solver.getProgramPointAfter(block);
609  }
610 
611  /// Return the configuration of the solver used for this analysis.
612  const DataFlowConfig &getSolverConfig() const { return solver.getConfig(); }
613 
614 #if LLVM_ENABLE_ABI_BREAKING_CHECKS
615  /// When compiling with debugging, keep a name for the analyis.
616  StringRef debugName;
617 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
618 
619 private:
620  /// The parent data-flow solver.
621  DataFlowSolver &solver;
622 
623  /// Allow the data-flow solver to access the internals of this class.
624  friend class DataFlowSolver;
625 };
626 
627 template <typename AnalysisT, typename... Args>
628 AnalysisT *DataFlowSolver::load(Args &&...args) {
629  childAnalyses.emplace_back(new AnalysisT(*this, std::forward<Args>(args)...));
630 #if LLVM_ENABLE_ABI_BREAKING_CHECKS
631  childAnalyses.back().get()->debugName = llvm::getTypeName<AnalysisT>();
632 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
633  return static_cast<AnalysisT *>(childAnalyses.back().get());
634 }
635 
636 template <typename StateT, typename AnchorT>
637 StateT *DataFlowSolver::getOrCreateState(AnchorT anchor) {
638  std::unique_ptr<AnalysisState> &state =
639  analysisStates[{LatticeAnchor(anchor), TypeID::get<StateT>()}];
640  if (!state) {
641  state = std::unique_ptr<StateT>(new StateT(anchor));
642 #if LLVM_ENABLE_ABI_BREAKING_CHECKS
643  state->debugName = llvm::getTypeName<StateT>();
644 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
645  }
646  return static_cast<StateT *>(state.get());
647 }
648 
649 inline raw_ostream &operator<<(raw_ostream &os, const AnalysisState &state) {
650  state.print(os);
651  return os;
652 }
653 
654 inline raw_ostream &operator<<(raw_ostream &os, LatticeAnchor anchor) {
655  anchor.print(os);
656  return os;
657 }
658 
659 } // end namespace mlir
660 
661 namespace llvm {
662 /// Allow hashing of lattice anchors and program points.
663 template <>
664 struct DenseMapInfo<mlir::ProgramPoint> {
666  void *pointer = llvm::DenseMapInfo<void *>::getEmptyKey();
667  return mlir::ProgramPoint(
668  (mlir::Block *)pointer,
670  }
673  return mlir::ProgramPoint(
674  (mlir::Block *)pointer,
676  }
677  static unsigned getHashValue(mlir::ProgramPoint pp) {
678  return hash_combine(pp.getBlock(), pp.getPoint().getNodePtr());
679  }
681  return lhs == rhs;
682  }
683 };
684 
685 template <>
686 struct DenseMapInfo<mlir::LatticeAnchor>
687  : public DenseMapInfo<mlir::LatticeAnchor::ParentTy> {};
688 
689 // Allow llvm::cast style functions.
690 template <typename To>
691 struct CastInfo<To, mlir::LatticeAnchor>
692  : public CastInfo<To, mlir::LatticeAnchor::PointerUnion> {};
693 
694 template <typename To>
695 struct CastInfo<To, const mlir::LatticeAnchor>
696  : public CastInfo<To, const mlir::LatticeAnchor::PointerUnion> {};
697 
698 } // end namespace llvm
699 
700 #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 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.
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 enqueue(WorkItem item)
Push a work item onto the worklist.
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.
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.
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:104
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)
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.