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" 20 #include "mlir/Support/StorageUniquer.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 }; 40 inline ChangeResult operator|(ChangeResult lhs, ChangeResult rhs) { 41 return lhs == ChangeResult::Change ? lhs : rhs; 42 } 43 inline ChangeResult &operator|=(ChangeResult &lhs, ChangeResult rhs) { 44 lhs = lhs | rhs; 45 return lhs; 46 } 47 inline ChangeResult operator&(ChangeResult lhs, ChangeResult rhs) { 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. 56 struct ProgramPoint : public StorageUniquer::BaseStorage { 57 /// Creates a new program point at the given location. 58 ProgramPoint(Block *parentBlock, Block::iterator pp) 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. 69 ProgramPoint() {} 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 78 static ProgramPoint *construct(StorageUniquer::StorageAllocator &alloc, 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. 166 class GenericLatticeAnchor : public StorageUniquer::BaseStorage { 167 public: 168 virtual ~GenericLatticeAnchor(); 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> 199 class GenericLatticeAnchorBase : public GenericLatticeAnchor { 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. 205 using Base = GenericLatticeAnchorBase<ConcreteT, Value>; 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. 250 struct LatticeAnchor 251 : public PointerUnion<GenericLatticeAnchor *, ProgramPoint *, Value> { 252 using ParentTy = 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. 274 class DataFlowConfig { 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. 282 DataFlowConfig &setInterprocedural(bool enable) { 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 /// Steps to re-run a data-flow analysis when IR changes: 312 /// 1. Erase all analysis states as they are no longer valid. 313 /// 2. Re-run the analysis using `initializeAndRun`. 314 /// 315 /// TODO: Optimize the internal implementation of the solver. 316 class DataFlowSolver { 317 public: 318 explicit DataFlowSolver(const DataFlowConfig &config = DataFlowConfig()) 319 : config(config) { 320 uniquer.registerParametricStorageType<ProgramPoint>(); 321 } 322 323 /// Load an analysis into the solver. Return the analysis instance. 324 template <typename AnalysisT, typename... Args> 325 AnalysisT *load(Args &&...args); 326 327 /// Initialize the children analyses starting from the provided top-level 328 /// operation and run the analysis until fixpoint. 329 LogicalResult initializeAndRun(Operation *top); 330 331 /// Lookup an analysis state for the given lattice anchor. Returns null if one 332 /// does not exist. 333 template <typename StateT, typename AnchorT> 334 const StateT *lookupState(AnchorT anchor) const { 335 const auto &mapIt = analysisStates.find(LatticeAnchor(anchor)); 336 if (mapIt == analysisStates.end()) 337 return nullptr; 338 auto it = mapIt->second.find(TypeID::get<StateT>()); 339 if (it == mapIt->second.end()) 340 return nullptr; 341 return static_cast<const StateT *>(it->second.get()); 342 } 343 344 /// Erase any analysis state associated with the given lattice anchor. 345 template <typename AnchorT> 346 void eraseState(AnchorT anchor) { 347 LatticeAnchor la(anchor); 348 analysisStates.erase(LatticeAnchor(anchor)); 349 } 350 351 // Erase all analysis states 352 void eraseAllStates() { analysisStates.clear(); } 353 354 /// Get a uniqued lattice anchor instance. If one is not present, it is 355 /// created with the provided arguments. 356 template <typename AnchorT, typename... Args> 357 AnchorT *getLatticeAnchor(Args &&...args) { 358 return AnchorT::get(uniquer, std::forward<Args>(args)...); 359 } 360 361 /// Get a uniqued program point instance. 362 ProgramPoint *getProgramPointBefore(Operation *op) { 363 if (op->getBlock()) 364 return uniquer.get<ProgramPoint>(/*initFn*/ {}, op->getBlock(), 365 Block::iterator(op), nullptr); 366 else 367 return uniquer.get<ProgramPoint>(/*initFn*/ {}, nullptr, 368 Block::iterator(), op); 369 } 370 371 ProgramPoint *getProgramPointBefore(Block *block) { 372 return uniquer.get<ProgramPoint>(/*initFn*/ {}, block, block->begin(), 373 nullptr); 374 } 375 376 ProgramPoint *getProgramPointAfter(Operation *op) { 377 if (op->getBlock()) 378 return uniquer.get<ProgramPoint>(/*initFn*/ {}, op->getBlock(), 379 ++Block::iterator(op), nullptr); 380 else 381 return uniquer.get<ProgramPoint>(/*initFn*/ {}, nullptr, 382 Block::iterator(), op); 383 } 384 385 ProgramPoint *getProgramPointAfter(Block *block) { 386 return uniquer.get<ProgramPoint>(/*initFn*/ {}, block, block->end(), 387 nullptr); 388 } 389 390 /// A work item on the solver queue is a program point, child analysis pair. 391 /// Each item is processed by invoking the child analysis at the program 392 /// point. 393 using WorkItem = std::pair<ProgramPoint *, DataFlowAnalysis *>; 394 /// Push a work item onto the worklist. 395 void enqueue(WorkItem item) { worklist.push(std::move(item)); } 396 397 /// Get the state associated with the given lattice anchor. If it does not 398 /// exist, create an uninitialized state. 399 template <typename StateT, typename AnchorT> 400 StateT *getOrCreateState(AnchorT anchor); 401 402 /// Propagate an update to an analysis state if it changed by pushing 403 /// dependent work items to the back of the queue. 404 /// This should only be used when DataFlowSolver is running. 405 /// Otherwise, the solver won't process the work items. 406 void propagateIfChanged(AnalysisState *state, ChangeResult changed); 407 408 /// Get the configuration of the solver. 409 const DataFlowConfig &getConfig() const { return config; } 410 411 private: 412 /// Configuration of the dataflow solver. 413 DataFlowConfig config; 414 415 /// The solver is working on the worklist. 416 bool isRunning = false; 417 418 /// The solver's work queue. Work items can be inserted to the front of the 419 /// queue to be processed greedily, speeding up computations that otherwise 420 /// quickly degenerate to quadratic due to propagation of state updates. 421 std::queue<WorkItem> worklist; 422 423 /// Type-erased instances of the children analyses. 424 SmallVector<std::unique_ptr<DataFlowAnalysis>> childAnalyses; 425 426 /// The storage uniquer instance that owns the memory of the allocated lattice 427 /// anchors 428 StorageUniquer uniquer; 429 430 /// A type-erased map of lattice anchors to associated analysis states for 431 /// first-class lattice anchors. 432 DenseMap<LatticeAnchor, DenseMap<TypeID, std::unique_ptr<AnalysisState>>, 433 DenseMapInfo<LatticeAnchor::ParentTy>> 434 analysisStates; 435 436 /// Allow the base child analysis class to access the internals of the solver. 437 friend class DataFlowAnalysis; 438 }; 439 440 //===----------------------------------------------------------------------===// 441 // AnalysisState 442 //===----------------------------------------------------------------------===// 443 444 /// Base class for generic analysis states. Analysis states contain data-flow 445 /// information that are attached to lattice anchors and which evolve as the 446 /// analysis iterates. 447 /// 448 /// This class places no restrictions on the semantics of analysis states beyond 449 /// these requirements. 450 /// 451 /// 1. Querying the state of a lattice anchor prior to visiting that anchor 452 /// results in uninitialized state. Analyses must be aware of unintialized 453 /// states. 454 /// 2. Analysis states can reach fixpoints, where subsequent updates will never 455 /// trigger a change in the state. 456 /// 3. Analysis states that are uninitialized can be forcefully initialized to a 457 /// default value. 458 class AnalysisState { 459 public: 460 virtual ~AnalysisState(); 461 462 /// Create the analysis state on the given lattice anchor. 463 AnalysisState(LatticeAnchor anchor) : anchor(anchor) {} 464 465 /// Returns the lattice anchor this state is located at. 466 LatticeAnchor getAnchor() const { return anchor; } 467 468 /// Print the contents of the analysis state. 469 virtual void print(raw_ostream &os) const = 0; 470 LLVM_DUMP_METHOD void dump() const; 471 472 /// Add a dependency to this analysis state on a lattice anchor and an 473 /// analysis. If this state is updated, the analysis will be invoked on the 474 /// given lattice anchor again (in onUpdate()). 475 void addDependency(ProgramPoint *point, DataFlowAnalysis *analysis); 476 477 protected: 478 /// This function is called by the solver when the analysis state is updated 479 /// to enqueue more work items. For example, if a state tracks dependents 480 /// through the IR (e.g. use-def chains), this function can be implemented to 481 /// push those dependents on the worklist. 482 virtual void onUpdate(DataFlowSolver *solver) const { 483 for (const DataFlowSolver::WorkItem &item : dependents) 484 solver->enqueue(item); 485 } 486 487 /// The lattice anchor to which the state belongs. 488 LatticeAnchor anchor; 489 490 #if LLVM_ENABLE_ABI_BREAKING_CHECKS 491 /// When compiling with debugging, keep a name for the analysis state. 492 StringRef debugName; 493 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS 494 495 private: 496 /// The dependency relations originating from this analysis state. An entry 497 /// `state -> (analysis, anchor)` is created when `analysis` queries `state` 498 /// when updating `anchor`. 499 /// 500 /// When this state is updated, all dependent child analysis invocations are 501 /// pushed to the back of the queue. Use a `SetVector` to keep the analysis 502 /// deterministic. 503 /// 504 /// Store the dependents on the analysis state for efficiency. 505 SetVector<DataFlowSolver::WorkItem> dependents; 506 507 /// Allow the framework to access the dependents. 508 friend class DataFlowSolver; 509 }; 510 511 //===----------------------------------------------------------------------===// 512 // DataFlowAnalysis 513 //===----------------------------------------------------------------------===// 514 515 /// Base class for all data-flow analyses. A child analysis is expected to build 516 /// an initial dependency graph (and optionally provide an initial state) when 517 /// initialized and define transfer functions when visiting program points. 518 /// 519 /// In classical data-flow analysis, the dependency graph is fixed and analyses 520 /// define explicit transfer functions between input states and output states. 521 /// In this framework, however, the dependency graph can change during the 522 /// analysis, and transfer functions are opaque such that the solver doesn't 523 /// know what states calling `visit` on an analysis will be updated. This allows 524 /// multiple analyses to plug in and provide values for the same state. 525 /// 526 /// Generally, when an analysis queries an uninitialized state, it is expected 527 /// to "bail out", i.e., not provide any updates. When the value is initialized, 528 /// the solver will re-invoke the analysis. If the solver exhausts its worklist, 529 /// however, and there are still uninitialized states, the solver "nudges" the 530 /// analyses by default-initializing those states. 531 class DataFlowAnalysis { 532 public: 533 virtual ~DataFlowAnalysis(); 534 535 /// Create an analysis with a reference to the parent solver. 536 explicit DataFlowAnalysis(DataFlowSolver &solver); 537 538 /// Initialize the analysis from the provided top-level operation by building 539 /// an initial dependency graph between all lattice anchors of interest. This 540 /// can be implemented by calling `visit` on all program points of interest 541 /// below the top-level operation. 542 /// 543 /// An analysis can optionally provide initial values to certain analysis 544 /// states to influence the evolution of the analysis. 545 virtual LogicalResult initialize(Operation *top) = 0; 546 547 /// Visit the given program point. This function is invoked by the solver on 548 /// this analysis with a given program point when a dependent analysis state 549 /// is updated. The function is similar to a transfer function; it queries 550 /// certain analysis states and sets other states. 551 /// 552 /// The function is expected to create dependencies on queried states and 553 /// propagate updates on changed states. A dependency can be created by 554 /// calling `addDependency` between the input state and a program point, 555 /// indicating that, if the state is updated, the solver should invoke `solve` 556 /// on the program point. The dependent point does not have to be the same as 557 /// the provided point. An update to a state is propagated by calling 558 /// `propagateIfChange` on the state. If the state has changed, then all its 559 /// dependents are placed on the worklist. 560 /// 561 /// The dependency graph does not need to be static. Each invocation of 562 /// `visit` can add new dependencies, but these dependencies will not be 563 /// dynamically added to the worklist because the solver doesn't know what 564 /// will provide a value for then. 565 virtual LogicalResult visit(ProgramPoint *point) = 0; 566 567 protected: 568 /// Create a dependency between the given analysis state and lattice anchor 569 /// on this analysis. 570 void addDependency(AnalysisState *state, ProgramPoint *point); 571 572 /// Propagate an update to a state if it changed. 573 void propagateIfChanged(AnalysisState *state, ChangeResult changed); 574 575 /// Register a custom lattice anchor class. 576 template <typename AnchorT> 577 void registerAnchorKind() { 578 solver.uniquer.registerParametricStorageType<AnchorT>(); 579 } 580 581 /// Get or create a custom lattice anchor. 582 template <typename AnchorT, typename... Args> 583 AnchorT *getLatticeAnchor(Args &&...args) { 584 return solver.getLatticeAnchor<AnchorT>(std::forward<Args>(args)...); 585 } 586 587 /// Get the analysis state associated with the lattice anchor. The returned 588 /// state is expected to be "write-only", and any updates need to be 589 /// propagated by `propagateIfChanged`. 590 template <typename StateT, typename AnchorT> 591 StateT *getOrCreate(AnchorT anchor) { 592 return solver.getOrCreateState<StateT>(anchor); 593 } 594 595 /// Get a read-only analysis state for the given point and create a dependency 596 /// on `dependent`. If the return state is updated elsewhere, this analysis is 597 /// re-invoked on the dependent. 598 template <typename StateT, typename AnchorT> 599 const StateT *getOrCreateFor(ProgramPoint *dependent, AnchorT anchor) { 600 StateT *state = getOrCreate<StateT>(anchor); 601 addDependency(state, dependent); 602 return state; 603 } 604 605 /// Get a uniqued program point instance. 606 ProgramPoint *getProgramPointBefore(Operation *op) { 607 return solver.getProgramPointBefore(op); 608 } 609 610 ProgramPoint *getProgramPointBefore(Block *block) { 611 return solver.getProgramPointBefore(block); 612 } 613 614 ProgramPoint *getProgramPointAfter(Operation *op) { 615 return solver.getProgramPointAfter(op); 616 } 617 618 ProgramPoint *getProgramPointAfter(Block *block) { 619 return solver.getProgramPointAfter(block); 620 } 621 622 /// Return the configuration of the solver used for this analysis. 623 const DataFlowConfig &getSolverConfig() const { return solver.getConfig(); } 624 625 #if LLVM_ENABLE_ABI_BREAKING_CHECKS 626 /// When compiling with debugging, keep a name for the analyis. 627 StringRef debugName; 628 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS 629 630 private: 631 /// The parent data-flow solver. 632 DataFlowSolver &solver; 633 634 /// Allow the data-flow solver to access the internals of this class. 635 friend class DataFlowSolver; 636 }; 637 638 template <typename AnalysisT, typename... Args> 639 AnalysisT *DataFlowSolver::load(Args &&...args) { 640 childAnalyses.emplace_back(new AnalysisT(*this, std::forward<Args>(args)...)); 641 #if LLVM_ENABLE_ABI_BREAKING_CHECKS 642 childAnalyses.back().get()->debugName = llvm::getTypeName<AnalysisT>(); 643 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS 644 return static_cast<AnalysisT *>(childAnalyses.back().get()); 645 } 646 647 template <typename StateT, typename AnchorT> 648 StateT *DataFlowSolver::getOrCreateState(AnchorT anchor) { 649 std::unique_ptr<AnalysisState> &state = 650 analysisStates[LatticeAnchor(anchor)][TypeID::get<StateT>()]; 651 if (!state) { 652 state = std::unique_ptr<StateT>(new StateT(anchor)); 653 #if LLVM_ENABLE_ABI_BREAKING_CHECKS 654 state->debugName = llvm::getTypeName<StateT>(); 655 #endif // LLVM_ENABLE_ABI_BREAKING_CHECKS 656 } 657 return static_cast<StateT *>(state.get()); 658 } 659 660 inline raw_ostream &operator<<(raw_ostream &os, const AnalysisState &state) { 661 state.print(os); 662 return os; 663 } 664 665 inline raw_ostream &operator<<(raw_ostream &os, LatticeAnchor anchor) { 666 anchor.print(os); 667 return os; 668 } 669 670 } // end namespace mlir 671 672 namespace llvm { 673 /// Allow hashing of lattice anchors and program points. 674 template <> 675 struct DenseMapInfo<mlir::ProgramPoint> { 676 static mlir::ProgramPoint getEmptyKey() { 677 void *pointer = llvm::DenseMapInfo<void *>::getEmptyKey(); 678 return mlir::ProgramPoint( 679 (mlir::Block *)pointer, 680 mlir::Block::iterator((mlir::Operation *)pointer)); 681 } 682 static mlir::ProgramPoint getTombstoneKey() { 683 void *pointer = llvm::DenseMapInfo<void *>::getTombstoneKey(); 684 return mlir::ProgramPoint( 685 (mlir::Block *)pointer, 686 mlir::Block::iterator((mlir::Operation *)pointer)); 687 } 688 static unsigned getHashValue(mlir::ProgramPoint pp) { 689 return hash_combine(pp.getBlock(), pp.getPoint().getNodePtr()); 690 } 691 static bool isEqual(mlir::ProgramPoint lhs, mlir::ProgramPoint rhs) { 692 return lhs == rhs; 693 } 694 }; 695 696 // Allow llvm::cast style functions. 697 template <typename To> 698 struct CastInfo<To, mlir::LatticeAnchor> 699 : public CastInfo<To, mlir::LatticeAnchor::PointerUnion> {}; 700 701 template <typename To> 702 struct CastInfo<To, const mlir::LatticeAnchor> 703 : public CastInfo<To, const mlir::LatticeAnchor::PointerUnion> {}; 704 705 } // end namespace llvm 706 707 #endif // MLIR_ANALYSIS_DATAFLOWFRAMEWORK_H 708