MLIR 24.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/DenseSet.h"
22#include "llvm/ADT/EquivalenceClasses.h"
23#include "llvm/ADT/Hashing.h"
24#include "llvm/ADT/STLFunctionalExtras.h"
25#include "llvm/ADT/SetVector.h"
26#include "llvm/Support/Compiler.h"
27#include "llvm/Support/TypeName.h"
28#include <queue>
29#include <tuple>
30
31namespace mlir {
32
33//===----------------------------------------------------------------------===//
34// ChangeResult
35//===----------------------------------------------------------------------===//
36
37/// A result type used to indicate if a change happened. Boolean operations on
38/// ChangeResult behave as though `Change` is truth.
39enum class [[nodiscard]] ChangeResult {
42};
44 return lhs == ChangeResult::Change ? lhs : rhs;
45}
47 lhs = lhs | rhs;
48 return lhs;
49}
51 return lhs == ChangeResult::NoChange ? lhs : rhs;
52}
53
54/// Forward declare the analysis state class.
55class AnalysisState;
56
57/// Program point represents a specific location in the execution of a program.
58/// A sequence of program points can be combined into a control flow graph.
60 /// Creates a new program point at the given location.
62 : block(parentBlock), point(pp) {}
63
64 /// Creates a new program point at the given operation.
65 ProgramPoint(Operation *op) : op(op) {}
66
67 /// The concrete key type used by the storage uniquer. This class is uniqued
68 /// by its contents.
69 using KeyTy = std::tuple<Block *, Block::iterator, Operation *>;
70
71 /// Create a empty program point.
73
74 /// Create a new program point from the given program point.
76 : block(point.getBlock()), point(point.getPoint()),
77 op(point.getOperation()) {}
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.
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.
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
141private:
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
150inline 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.
168public:
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
180protected:
181 /// Create an abstract lattice anchor with type identifier.
182 explicit GenericLatticeAnchor(TypeID typeID) : typeID(typeID) {}
183
184private:
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.
199template <typename ConcreteT, typename Value>
201public:
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>
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
241private:
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.
267class DataFlowAnalysis;
268
269} // namespace mlir
270
271template <>
272struct llvm::DenseMapInfo<mlir::LatticeAnchor>
273 : public llvm::DenseMapInfo<mlir::LatticeAnchor::ParentTy> {};
274
275namespace mlir {
276
277//===----------------------------------------------------------------------===//
278// DataFlowConfig
279//===----------------------------------------------------------------------===//
280
281/// Configuration class for data flow solver and child analyses. Follows the
282/// fluent API pattern.
284public:
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
299private:
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.
326public:
327 explicit DataFlowSolver(const DataFlowConfig &config = DataFlowConfig())
328 : config(config) {
329 uniquer.registerParametricStorageType<ProgramPoint>();
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 analyses starting from the provided top-level operation and
337 /// run the analysis until fixpoint.
338 ///
339 /// An optional \p analysisFilter predicate restricts which analyses are
340 /// initialized. When no filter is given every loaded analysis is
341 /// (re-)initialized. The fixpoint loop always processes all enqueued work
342 /// items regardless of the filter.
343 LogicalResult initializeAndRun(
344 Operation *top,
345 llvm::function_ref<bool(DataFlowAnalysis &)> analysisFilter = nullptr);
346
347 /// Lookup an analysis state for the given lattice anchor. Returns null if one
348 /// does not exist.
349 template <typename StateT, typename AnchorT>
350 const StateT *lookupState(AnchorT anchor) const {
351 LatticeAnchor latticeAnchor =
353 const auto &mapIt = analysisStates.find(latticeAnchor);
354 if (mapIt == analysisStates.end())
355 return nullptr;
356 auto it = mapIt->second.find(TypeID::get<StateT>());
357 if (it == mapIt->second.end())
358 return nullptr;
359 return static_cast<const StateT *>(it->second.get());
360 }
361
362 /// Erase any analysis state associated with the given lattice anchor.
363 template <typename AnchorT>
364 void eraseState(AnchorT anchor);
365
366 /// Erase all analysis states.
368 analysisStates.clear();
369 equivalentAnchorMap.clear();
370 }
371
372 /// Get a uniqued lattice anchor instance. If one is not present, it is
373 /// created with the provided arguments.
374 template <typename AnchorT, typename... Args>
375 AnchorT *getLatticeAnchor(Args &&...args) {
376 return AnchorT::get(uniquer, std::forward<Args>(args)...);
377 }
378
379 /// Get a uniqued program point instance.
381 if (op->getBlock())
382 return uniquer.get<ProgramPoint>(/*initFn*/ {}, op->getBlock(),
383 Block::iterator(op), nullptr);
384 else
385 return uniquer.get<ProgramPoint>(/*initFn*/ {}, nullptr,
386 Block::iterator(), op);
387 }
388
390 return uniquer.get<ProgramPoint>(/*initFn*/ {}, block, block->begin(),
391 nullptr);
392 }
393
395 if (op->getBlock())
396 return uniquer.get<ProgramPoint>(/*initFn*/ {}, op->getBlock(),
397 ++Block::iterator(op), nullptr);
398 else
399 return uniquer.get<ProgramPoint>(/*initFn*/ {}, nullptr,
400 Block::iterator(), op);
401 }
402
404 return uniquer.get<ProgramPoint>(/*initFn*/ {}, block, block->end(),
405 nullptr);
406 }
407
408 /// A work item on the solver queue is a program point, child analysis pair.
409 /// Each item is processed by invoking the child analysis at the program
410 /// point.
411 using WorkItem = std::pair<ProgramPoint *, DataFlowAnalysis *>;
412 /// Push a work item onto the worklist, if it is not already there.
413 void enqueue(WorkItem item) {
414 if (pending.insert(item).second)
415 worklist.push(item);
416 }
417
418private:
419 /// Pop a work item off the worklist, which must be nonempty.
420 WorkItem dequeue() {
421 assert(!worklist.empty());
422 auto item = worklist.front();
423 worklist.pop();
424 pending.erase(item);
425 return item;
426 }
427
428public:
429 /// Get the state associated with the given lattice anchor. If it does not
430 /// exist, create an uninitialized state.
431 template <typename StateT, typename AnchorT>
432 StateT *getOrCreateState(AnchorT anchor);
433
434 /// Get leader lattice anchor in equivalence lattice anchor group, return
435 /// input lattice anchor if input not found in equivalece lattice anchor
436 /// group.
437 template <typename StateT>
438 LatticeAnchor getLeaderAnchorOrSelf(LatticeAnchor latticeAnchor) const;
439
440 /// Union input anchors under the given state.
441 template <typename StateT, typename AnchorT>
442 void unionLatticeAnchors(AnchorT anchor, AnchorT other);
443
444 /// Return given lattice is equivalent on given state.
445 template <typename StateT>
446 bool isEquivalent(LatticeAnchor lhs, LatticeAnchor rhs) const;
447
448 /// Propagate an update to an analysis state if it changed by pushing
449 /// dependent work items to the back of the queue.
450 /// This should only be used when DataFlowSolver is running.
451 /// Otherwise, the solver won't process the work items.
452 void propagateIfChanged(AnalysisState *state, ChangeResult changed);
453
454 /// Get the configuration of the solver.
455 const DataFlowConfig &getConfig() const { return config; }
456
457private:
458 /// Configuration of the dataflow solver.
459 DataFlowConfig config;
460
461 /// The solver is working on the worklist.
462 bool isRunning = false;
463
464 /// The solver's work queue. Work items can be inserted to the front of the
465 /// queue to be processed greedily, speeding up computations that otherwise
466 /// quickly degenerate to quadratic due to propagation of state updates.
467 std::queue<WorkItem> worklist;
468 /// Keep track of pending work items, to prevent redundant processing of items
469 /// that were enqueued multiple times.
470 DenseSet<WorkItem> pending;
471
472 /// Type-erased instances of the children analyses.
474
475 /// The storage uniquer instance that owns the memory of the allocated lattice
476 /// anchors
477 StorageUniquer uniquer;
478
479 /// A type-erased map of lattice anchors to associated analysis states for
480 /// first-class lattice anchors.
482 analysisStates;
483
484 /// A map of Ananlysis state type to the equivalent lattice anchors.
485 /// Lattice anchors are considered equivalent under a certain analysis state
486 /// type if and only if, the analysis states pointed to by these lattice
487 /// anchors necessarily contain identical value.
489
490 /// Allow the base child analysis class to access the internals of the solver.
491 friend class DataFlowAnalysis;
492};
493
494//===----------------------------------------------------------------------===//
495// AnalysisState
496//===----------------------------------------------------------------------===//
497
498/// Base class for generic analysis states. Analysis states contain data-flow
499/// information that are attached to lattice anchors and which evolve as the
500/// analysis iterates.
501///
502/// This class places no restrictions on the semantics of analysis states beyond
503/// these requirements.
504///
505/// 1. Querying the state of a lattice anchor prior to visiting that anchor
506/// results in uninitialized state. Analyses must be aware of uninitialized
507/// states.
508/// 2. Analysis states can reach fixpoints, where subsequent updates will never
509/// trigger a change in the state.
510/// 3. Analysis states that are uninitialized can be forcefully initialized to a
511/// default value.
513public:
514 virtual ~AnalysisState();
515
516 /// Create the analysis state on the given lattice anchor.
518
519 /// Returns the lattice anchor this state is located at.
520 LatticeAnchor getAnchor() const { return anchor; }
521
522 /// Print the contents of the analysis state.
523 virtual void print(raw_ostream &os) const = 0;
524 LLVM_DUMP_METHOD void dump() const;
525
526 /// Add a dependency to this analysis state on a lattice anchor and an
527 /// analysis. If this state is updated, the analysis will be invoked on the
528 /// given lattice anchor again (in onUpdate()).
529 void addDependency(ProgramPoint *point, DataFlowAnalysis *analysis);
530
531protected:
532 /// This function is called by the solver when the analysis state is updated
533 /// to enqueue more work items. For example, if a state tracks dependents
534 /// through the IR (e.g. use-def chains), this function can be implemented to
535 /// push those dependents on the worklist.
536 virtual void onUpdate(DataFlowSolver *solver) const {
537 for (const DataFlowSolver::WorkItem &item : dependents)
538 solver->enqueue(item);
539 }
540
541 /// The lattice anchor to which the state belongs.
543
544#if LLVM_ENABLE_ABI_BREAKING_CHECKS
545 /// When compiling with debugging, keep a name for the analysis state.
546 StringRef debugName;
547#endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
548
549private:
550 /// The dependency relations originating from this analysis state. An entry
551 /// `state -> (analysis, anchor)` is created when `analysis` queries `state`
552 /// when updating `anchor`.
553 ///
554 /// When this state is updated, all dependent child analysis invocations are
555 /// pushed to the back of the queue. Use a `SetVector` to keep the analysis
556 /// deterministic.
557 ///
558 /// Store the dependents on the analysis state for efficiency.
560
561 /// Allow the framework to access the dependents.
562 friend class DataFlowSolver;
563};
564
565//===----------------------------------------------------------------------===//
566// DataFlowSolver definition
567//===----------------------------------------------------------------------===//
568// This method is defined outside `DataFlowSolver` and after `AnalysisState`
569// to prevent issues around `AnalysisState` being used before it is defined.
570template <typename AnchorT>
571void DataFlowSolver::eraseState(AnchorT anchor) {
572 LatticeAnchor latticeAnchor(anchor);
573
574 // Update equivalentAnchorMap.
575 for (auto &&[TypeId, eqClass] : equivalentAnchorMap) {
576 if (!eqClass.contains(latticeAnchor)) {
577 continue;
578 }
579 llvm::EquivalenceClasses<LatticeAnchor>::member_iterator leaderIt =
580 eqClass.findLeader(latticeAnchor);
581
582 // Update analysis states with new leader if needed.
583 if (*leaderIt == latticeAnchor && ++leaderIt != eqClass.member_end()) {
584 analysisStates[*leaderIt][TypeId] =
585 std::move(analysisStates[latticeAnchor][TypeId]);
586 }
587
588 eqClass.erase(latticeAnchor);
589 }
590
591 // Update analysis states.
592 analysisStates.erase(latticeAnchor);
593}
594
595//===----------------------------------------------------------------------===//
596// DataFlowAnalysis
597//===----------------------------------------------------------------------===//
598
599/// Base class for all data-flow analyses. A child analysis is expected to build
600/// an initial dependency graph (and optionally provide an initial state) when
601/// initialized and define transfer functions when visiting program points.
602///
603/// Subclasses defined in anonymous namespaces must provide an explicit TypeID
604/// via `MLIR_DEFINE_EXPLICIT_INTERNAL_INLINE_TYPE_ID` in their class body.
605/// This is required because `DataFlowSolver::load` resolves the analysis
606/// TypeID at load time, and the implicit TypeID fallback is not supported for
607/// classes in anonymous namespaces.
608///
609/// In classical data-flow analysis, the dependency graph is fixed and analyses
610/// define explicit transfer functions between input states and output states.
611/// In this framework, however, the dependency graph can change during the
612/// analysis, and transfer functions are opaque such that the solver doesn't
613/// know what states calling `visit` on an analysis will be updated. This allows
614/// multiple analyses to plug in and provide values for the same state.
615///
616/// Generally, when an analysis queries an uninitialized state, it is expected
617/// to "bail out", i.e., not provide any updates. When the value is initialized,
618/// the solver will re-invoke the analysis. If the solver exhausts its worklist,
619/// however, and there are still uninitialized states, the solver "nudges" the
620/// analyses by default-initializing those states.
622public:
624
625 /// Create an analysis with a reference to the parent solver.
626 explicit DataFlowAnalysis(DataFlowSolver &solver);
627
628 /// Initialize the analysis from the provided top-level operation by building
629 /// an initial dependency graph between all lattice anchors of interest. This
630 /// can be implemented by calling `visit` on all program points of interest
631 /// below the top-level operation.
632 ///
633 /// An analysis can optionally provide initial values to certain analysis
634 /// states to influence the evolution of the analysis.
635 virtual LogicalResult initialize(Operation *top) = 0;
636
637 /// Visit the given program point. This function is invoked by the solver on
638 /// this analysis with a given program point when a dependent analysis state
639 /// is updated. The function is similar to a transfer function; it queries
640 /// certain analysis states and sets other states.
641 ///
642 /// The function is expected to create dependencies on queried states and
643 /// propagate updates on changed states. A dependency can be created by
644 /// calling `addDependency` between the input state and a program point,
645 /// indicating that, if the state is updated, the solver should invoke `solve`
646 /// on the program point. The dependent point does not have to be the same as
647 /// the provided point. An update to a state is propagated by calling
648 /// `propagateIfChange` on the state. If the state has changed, then all its
649 /// dependents are placed on the worklist.
650 ///
651 /// The dependency graph does not need to be static. Each invocation of
652 /// `visit` can add new dependencies, but these dependencies will not be
653 /// dynamically added to the worklist because the solver doesn't know what
654 /// will provide a value for then.
655 virtual LogicalResult visit(ProgramPoint *point) = 0;
656
657 /// Initialize lattice anchor equivalence class from the provided top-level
658 /// operation.
659 ///
660 /// This function will union lattice anchor to same equivalent class if the
661 /// analysis can determine the lattice content of lattice anchor is
662 /// necessarily identical under the corrensponding lattice type.
664
665 /// Return the TypeID of the concrete analysis class. Valid only after
666 /// `DataFlowSolver::load<AnalysisT>` has returned; must not be called from
667 /// the analysis constructor body because the TypeID is set by `load` after
668 /// construction.
669 TypeID getTypeID() const { return analysisTypeID; }
670
671protected:
672 /// Create a dependency between the given analysis state and lattice anchor
673 /// on this analysis.
674 void addDependency(AnalysisState *state, ProgramPoint *point);
675
676 /// Propagate an update to a state if it changed.
677 void propagateIfChanged(AnalysisState *state, ChangeResult changed);
678
679 /// Register a custom lattice anchor class.
680 template <typename AnchorT>
682 solver.uniquer.registerParametricStorageType<AnchorT>();
683 }
684
685 /// Get or create a custom lattice anchor.
686 template <typename AnchorT, typename... Args>
687 AnchorT *getLatticeAnchor(Args &&...args) {
688 return solver.getLatticeAnchor<AnchorT>(std::forward<Args>(args)...);
689 }
690
691 /// Union input anchors under the given state.
692 template <typename StateT, typename AnchorT>
693 void unionLatticeAnchors(AnchorT anchor, AnchorT other) {
694 return solver.unionLatticeAnchors<StateT>(anchor, other);
695 }
696
697 /// Get the analysis state associated with the lattice anchor. The returned
698 /// state is expected to be "write-only", and any updates need to be
699 /// propagated by `propagateIfChanged`.
700 template <typename StateT, typename AnchorT>
701 StateT *getOrCreate(AnchorT anchor) {
702 return solver.getOrCreateState<StateT>(anchor);
703 }
704
705 /// Get a read-only analysis state for the given point and create a dependency
706 /// on `dependent`. If the return state is updated elsewhere, this analysis is
707 /// re-invoked on the dependent.
708 template <typename StateT, typename AnchorT>
709 const StateT *getOrCreateFor(ProgramPoint *dependent, AnchorT anchor) {
710 StateT *state = getOrCreate<StateT>(anchor);
711 if (!solver.isEquivalent<StateT>(LatticeAnchor(anchor),
712 LatticeAnchor(dependent)))
713 addDependency(state, dependent);
714 return state;
715 }
716
717 /// Get a uniqued program point instance.
719 return solver.getProgramPointBefore(op);
720 }
721
723 return solver.getProgramPointBefore(block);
724 }
725
727 return solver.getProgramPointAfter(op);
728 }
729
731 return solver.getProgramPointAfter(block);
732 }
733
734 /// Return the configuration of the solver used for this analysis.
735 const DataFlowConfig &getSolverConfig() const { return solver.getConfig(); }
736
737#if LLVM_ENABLE_ABI_BREAKING_CHECKS
738 /// When compiling with debugging, keep a name for the analyis.
739 StringRef debugName;
740#endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
741
742private:
743 /// The parent data-flow solver.
744 DataFlowSolver &solver;
745
746 /// The TypeID of the concrete analysis class. Set by
747 /// `DataFlowSolver::load` after construction; not available during the
748 /// analysis constructor.
749 TypeID analysisTypeID;
750
751 /// Allow the data-flow solver to access the internals of this class.
752 friend class DataFlowSolver;
753};
754
755template <typename AnalysisT, typename... Args>
756AnalysisT *DataFlowSolver::load(Args &&...args) {
757 childAnalyses.emplace_back(new AnalysisT(*this, std::forward<Args>(args)...));
758 childAnalyses.back()->analysisTypeID = TypeID::get<AnalysisT>();
759#if LLVM_ENABLE_ABI_BREAKING_CHECKS
760 childAnalyses.back()->debugName = llvm::getTypeName<AnalysisT>();
761#endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
762 return static_cast<AnalysisT *>(childAnalyses.back().get());
763}
764
765template <typename StateT>
768 if (!equivalentAnchorMap.contains(TypeID::get<StateT>())) {
769 return latticeAnchor;
770 }
771 const llvm::EquivalenceClasses<LatticeAnchor> &eqClass =
772 equivalentAnchorMap.at(TypeID::get<StateT>());
773 llvm::EquivalenceClasses<LatticeAnchor>::member_iterator leaderIt =
774 eqClass.findLeader(latticeAnchor);
775 if (leaderIt != eqClass.member_end()) {
776 return *leaderIt;
777 }
778 return latticeAnchor;
779}
780
781template <typename StateT, typename AnchorT>
782StateT *DataFlowSolver::getOrCreateState(AnchorT anchor) {
783 // Replace to leader anchor if found.
784 LatticeAnchor latticeAnchor(anchor);
785 latticeAnchor = getLeaderAnchorOrSelf<StateT>(latticeAnchor);
786 std::unique_ptr<AnalysisState> &state =
787 analysisStates[latticeAnchor][TypeID::get<StateT>()];
788 if (!state) {
789 state = std::unique_ptr<StateT>(new StateT(anchor));
790#if LLVM_ENABLE_ABI_BREAKING_CHECKS
791 state->debugName = llvm::getTypeName<StateT>();
792#endif // LLVM_ENABLE_ABI_BREAKING_CHECKS
793 }
794 return static_cast<StateT *>(state.get());
795}
796
797template <typename StateT>
799 if (!equivalentAnchorMap.contains(TypeID::get<StateT>())) {
800 return false;
801 }
802 const llvm::EquivalenceClasses<LatticeAnchor> &eqClass =
803 equivalentAnchorMap.at(TypeID::get<StateT>());
804 if (!eqClass.contains(lhs) || !eqClass.contains(rhs))
805 return false;
806 return eqClass.isEquivalent(lhs, rhs);
807}
808
809template <typename StateT, typename AnchorT>
810void DataFlowSolver::unionLatticeAnchors(AnchorT anchor, AnchorT other) {
811 llvm::EquivalenceClasses<LatticeAnchor> &eqClass =
812 equivalentAnchorMap[TypeID::get<StateT>()];
813 eqClass.unionSets(LatticeAnchor(anchor), LatticeAnchor(other));
814}
815
817 state.print(os);
818 return os;
819}
820
821inline raw_ostream &operator<<(raw_ostream &os, const LatticeAnchor &anchor) {
822 anchor.print(os);
823 return os;
824}
825
826} // end namespace mlir
827
828namespace llvm {
829/// Allow hashing of lattice anchors and program points.
830template <>
831struct DenseMapInfo<mlir::ProgramPoint> {
832 static unsigned getHashValue(mlir::ProgramPoint pp) {
833 return hash_combine(pp.getBlock(), pp.getPoint().getNodePtr());
834 }
836 return lhs == rhs;
837 }
838};
839
840// Allow llvm::cast style functions.
841template <typename To>
842struct CastInfo<To, mlir::LatticeAnchor>
843 : public CastInfo<To, mlir::LatticeAnchor::PointerUnion> {};
844
845template <typename To>
846struct CastInfo<To, const mlir::LatticeAnchor>
847 : public CastInfo<To, const mlir::LatticeAnchor::PointerUnion> {};
848
849} // end namespace llvm
850
851#endif // MLIR_ANALYSIS_DATAFLOWFRAMEWORK_H
auto load
Base class for generic analysis states.
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.
AnalysisState(LatticeAnchor anchor)
Create the analysis state on the given lattice anchor.
virtual ~AnalysisState()
LatticeAnchor anchor
The lattice anchor to which the state belongs.
friend class DataFlowSolver
Allow the framework to access the dependents.
Block represents an ordered list of Operations.
Definition Block.h:34
OpListType::iterator iterator
Definition Block.h:165
iterator end()
Definition Block.h:169
iterator begin()
Definition Block.h:168
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.
TypeID getTypeID() const
Return the TypeID of the concrete analysis class.
ProgramPoint * getProgramPointBefore(Operation *op)
Get a uniqued program point instance.
void propagateIfChanged(AnalysisState *state, ChangeResult changed)
Propagate an update to a state if it changed.
ProgramPoint * getProgramPointBefore(Block *block)
ProgramPoint * getProgramPointAfter(Block *block)
const DataFlowConfig & getSolverConfig() const
Return the configuration of the solver used for this analysis.
StateT * getOrCreate(AnchorT anchor)
Get the analysis state associated with the lattice anchor.
ProgramPoint * getProgramPointAfter(Operation *op)
virtual void initializeEquivalentLatticeAnchor(Operation *top)
Initialize lattice anchor equivalence class from the provided top-level operation.
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...
virtual LogicalResult visit(ProgramPoint *point)=0
Visit the given program point.
AnchorT * getLatticeAnchor(Args &&...args)
Get or create a custom lattice anchor.
void registerAnchorKind()
Register a custom lattice anchor class.
friend class DataFlowSolver
Allow the data-flow solver to access the internals of this class.
const StateT * getOrCreateFor(ProgramPoint *dependent, AnchorT anchor)
Get a read-only analysis state for the given point and create a dependency on dependent.
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.
LogicalResult initializeAndRun(Operation *top, llvm::function_ref< bool(DataFlowAnalysis &)> analysisFilter=nullptr)
Initialize analyses starting from the provided top-level operation and run the analysis until fixpoin...
void unionLatticeAnchors(AnchorT anchor, AnchorT other)
Union input anchors under the given state.
void enqueue(WorkItem item)
Push a work item onto the worklist, if it is not already there.
bool isEquivalent(LatticeAnchor lhs, LatticeAnchor rhs) const
Return given lattice is equivalent on given state.
friend class DataFlowAnalysis
Allow the base child analysis class to access the internals of the solver.
ProgramPoint * getProgramPointBefore(Operation *op)
Get a uniqued program point instance.
ProgramPoint * getProgramPointAfter(Block *block)
void eraseState(AnchorT anchor)
Erase any analysis state associated with the given lattice anchor.
ProgramPoint * getProgramPointAfter(Operation *op)
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...
AnchorT * getLatticeAnchor(Args &&...args)
Get a uniqued lattice anchor instance.
const StateT * lookupState(AnchorT anchor) const
Lookup an analysis state for the given lattice anchor.
ProgramPoint * getProgramPointBefore(Block *block)
void eraseAllStates()
Erase all analysis states.
StateT * getOrCreateState(AnchorT anchor)
Get the state associated with the given lattice anchor.
const DataFlowConfig & getConfig() const
Get the configuration of the solver.
LatticeAnchor getLeaderAnchorOrSelf(LatticeAnchor latticeAnchor) const
Get leader lattice anchor in equivalence lattice anchor group, return input lattice anchor if input n...
AnalysisT * load(Args &&...args)
Load an analysis into the solver. Return the analysis instance.
DataFlowSolver(const DataFlowConfig &config=DataFlowConfig())
std::pair< ProgramPoint *, DataFlowAnalysis * > WorkItem
A work item on the solver queue is a program point, child analysis pair.
bool operator==(const Value &value) const
Two lattice anchors are equal if their values are equal.
GenericLatticeAnchorBase< ConcreteT, Value > Base
Alias for the base class.
const Value & getValue() const
Get the contents of the lattice anchor.
static bool classof(const GenericLatticeAnchor *point)
Provide LLVM-style RTTI using type IDs.
static ConcreteT * construct(StorageUniquer::StorageAllocator &alloc, ValueT &&value)
Allocate space for a lattice anchor and construct it in-place.
static ConcreteT * get(StorageUniquer &uniquer, Args &&...args)
Get a uniqued instance of this lattice anchor class with the given arguments.
GenericLatticeAnchorBase(ValueT &&value)
Construct an instance of the lattice anchor using the provided value and the type ID of the concrete ...
Value KeyTy
The concrete key type used by the storage uniquer.
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:76
Operation is the basic unit of execution within MLIR.
Definition Operation.h:87
Block * getBlock()
Returns the operation block that contains this operation.
Definition Operation.h:230
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'.
This class provides an efficient unique identifier for a specific C++ type.
Definition TypeID.h:107
static TypeID get()
Construct a type info object for the given type T.
Definition TypeID.h:245
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:227
Include the generated interface declarations.
raw_ostream & operator<<(raw_ostream &os, const AliasResult &result)
ChangeResult
A result type used to indicate if a change happened.
llvm::DenseSet< ValueT, ValueInfoT > DenseSet
Definition LLVM.h:122
ChangeResult operator&(ChangeResult lhs, ChangeResult rhs)
ChangeResult operator|(ChangeResult lhs, ChangeResult rhs)
llvm::SetVector< T, Vector, Set, N > SetVector
Definition LLVM.h:125
auto get(MLIRContext *context, Ts &&...params)
Helper method that injects context only if needed, this helps unify some of the attribute constructio...
llvm::DenseMap< KeyT, ValueT, KeyInfoT, BucketT > DenseMap
Definition LLVM.h:120
ChangeResult & operator|=(ChangeResult &lhs, ChangeResult rhs)
static unsigned getHashValue(mlir::ProgramPoint pp)
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.
PointerUnion< GenericLatticeAnchor *, ProgramPoint *, Value > ParentTy
Program point represents a specific location in the execution of a program.
bool isNull() const
Returns true if this program point is set.
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.
Block * getBlock() const
Get the block contains this program point.
Operation * getNextOp() const
Get the next operation of this program point.
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.
static ProgramPoint * construct(StorageUniquer::StorageAllocator &alloc, KeyTy &&key)
Operation * getPrevOp() const
Get the previous operation of this program point.
Operation * getOperation() const
Get the the iterator this program point refers to.
ProgramPoint(Operation *op)
Creates a new program point at the given operation.