1 //===- DependencyGraph.h ----------------------------------------*- C++ -*-===// 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 declares the dependency graph used by the vectorizer's instruction 10 // scheduler. 11 // 12 // The nodes of the graph are objects of the `DGNode` class. Each `DGNode` 13 // object points to an instruction. 14 // The edges between `DGNode`s are implicitly defined by an ordered set of 15 // predecessor nodes, to save memory. 16 // Finally the whole dependency graph is an object of the `DependencyGraph` 17 // class, which also provides the API for creating/extending the graph from 18 // input Sandbox IR. 19 // 20 //===----------------------------------------------------------------------===// 21 22 #ifndef LLVM_TRANSFORMS_VECTORIZE_SANDBOXVECTORIZER_DEPENDENCYGRAPH_H 23 #define LLVM_TRANSFORMS_VECTORIZE_SANDBOXVECTORIZER_DEPENDENCYGRAPH_H 24 25 #include "llvm/ADT/DenseMap.h" 26 #include "llvm/ADT/iterator_range.h" 27 #include "llvm/Analysis/AliasAnalysis.h" 28 #include "llvm/SandboxIR/Instruction.h" 29 #include "llvm/SandboxIR/IntrinsicInst.h" 30 #include "llvm/Support/Compiler.h" 31 #include "llvm/Transforms/Vectorize/SandboxVectorizer/Interval.h" 32 33 namespace llvm::sandboxir { 34 35 class DependencyGraph; 36 class MemDGNode; 37 class SchedBundle; 38 39 /// SubclassIDs for isa/dyn_cast etc. 40 enum class DGNodeID { 41 DGNode, 42 MemDGNode, 43 }; 44 45 class DGNode; 46 class MemDGNode; 47 class DependencyGraph; 48 49 // Defined in Transforms/Vectorize/SandboxVectorizer/Interval.cpp 50 extern template class LLVM_TEMPLATE_ABI Interval<MemDGNode>; 51 52 /// Iterate over both def-use and mem dependencies. 53 class PredIterator { 54 User::op_iterator OpIt; 55 User::op_iterator OpItE; 56 DenseSet<MemDGNode *>::iterator MemIt; 57 DGNode *N = nullptr; 58 DependencyGraph *DAG = nullptr; 59 PredIterator(const User::op_iterator & OpIt,const User::op_iterator & OpItE,const DenseSet<MemDGNode * >::iterator & MemIt,DGNode * N,DependencyGraph & DAG)60 PredIterator(const User::op_iterator &OpIt, const User::op_iterator &OpItE, 61 const DenseSet<MemDGNode *>::iterator &MemIt, DGNode *N, 62 DependencyGraph &DAG) 63 : OpIt(OpIt), OpItE(OpItE), MemIt(MemIt), N(N), DAG(&DAG) {} PredIterator(const User::op_iterator & OpIt,const User::op_iterator & OpItE,DGNode * N,DependencyGraph & DAG)64 PredIterator(const User::op_iterator &OpIt, const User::op_iterator &OpItE, 65 DGNode *N, DependencyGraph &DAG) 66 : OpIt(OpIt), OpItE(OpItE), N(N), DAG(&DAG) {} 67 friend class DGNode; // For constructor 68 friend class MemDGNode; // For constructor 69 70 /// Skip iterators that don't point instructions or are outside \p DAG, 71 /// starting from \p OpIt and ending before \p OpItE.n 72 LLVM_ABI static User::op_iterator skipBadIt(User::op_iterator OpIt, 73 User::op_iterator OpItE, 74 const DependencyGraph &DAG); 75 76 public: 77 using difference_type = std::ptrdiff_t; 78 using value_type = DGNode *; 79 using pointer = value_type *; 80 using reference = value_type &; 81 using iterator_category = std::input_iterator_tag; 82 LLVM_ABI value_type operator*(); 83 LLVM_ABI PredIterator &operator++(); 84 PredIterator operator++(int) { 85 auto Copy = *this; 86 ++(*this); 87 return Copy; 88 } 89 LLVM_ABI bool operator==(const PredIterator &Other) const; 90 bool operator!=(const PredIterator &Other) const { return !(*this == Other); } 91 }; 92 93 /// A DependencyGraph Node that points to an Instruction and contains memory 94 /// dependency edges. 95 class LLVM_ABI DGNode { 96 protected: 97 Instruction *I; 98 // TODO: Use a PointerIntPair for SubclassID and I. 99 /// For isa/dyn_cast etc. 100 DGNodeID SubclassID; 101 /// The number of unscheduled successors. 102 unsigned UnscheduledSuccs = 0; 103 /// This is true if this node has been scheduled. 104 bool Scheduled = false; 105 /// The scheduler bundle that this node belongs to. 106 SchedBundle *SB = nullptr; 107 108 void setSchedBundle(SchedBundle &SB); clearSchedBundle()109 void clearSchedBundle() { this->SB = nullptr; } 110 friend class SchedBundle; // For setSchedBundle(), clearSchedBundle(). 111 DGNode(Instruction * I,DGNodeID ID)112 DGNode(Instruction *I, DGNodeID ID) : I(I), SubclassID(ID) {} 113 friend class MemDGNode; // For constructor. 114 friend class DependencyGraph; // For UnscheduledSuccs 115 116 public: DGNode(Instruction * I)117 DGNode(Instruction *I) : I(I), SubclassID(DGNodeID::DGNode) { 118 assert(!isMemDepNodeCandidate(I) && "Expected Non-Mem instruction, "); 119 } 120 DGNode(const DGNode &Other) = delete; 121 virtual ~DGNode(); 122 /// \Returns the number of unscheduled successors. getNumUnscheduledSuccs()123 unsigned getNumUnscheduledSuccs() const { return UnscheduledSuccs; } 124 // TODO: Make this private? decrUnscheduledSuccs()125 void decrUnscheduledSuccs() { 126 assert(UnscheduledSuccs > 0 && "Counting error!"); 127 --UnscheduledSuccs; 128 } incrUnscheduledSuccs()129 void incrUnscheduledSuccs() { ++UnscheduledSuccs; } resetScheduleState()130 void resetScheduleState() { 131 UnscheduledSuccs = 0; 132 Scheduled = false; 133 } 134 /// \Returns true if all dependent successors have been scheduled. ready()135 bool ready() const { return UnscheduledSuccs == 0; } 136 /// \Returns true if this node has been scheduled. scheduled()137 bool scheduled() const { return Scheduled; } setScheduled(bool NewVal)138 void setScheduled(bool NewVal) { Scheduled = NewVal; } 139 /// \Returns the scheduling bundle that this node belongs to, or nullptr. getSchedBundle()140 SchedBundle *getSchedBundle() const { return SB; } 141 /// \Returns true if this is before \p Other in program order. comesBefore(const DGNode * Other)142 bool comesBefore(const DGNode *Other) { return I->comesBefore(Other->I); } 143 using iterator = PredIterator; preds_begin(DependencyGraph & DAG)144 virtual iterator preds_begin(DependencyGraph &DAG) { 145 return PredIterator( 146 PredIterator::skipBadIt(I->op_begin(), I->op_end(), DAG), I->op_end(), 147 this, DAG); 148 } preds_end(DependencyGraph & DAG)149 virtual iterator preds_end(DependencyGraph &DAG) { 150 return PredIterator(I->op_end(), I->op_end(), this, DAG); 151 } preds_begin(DependencyGraph & DAG)152 iterator preds_begin(DependencyGraph &DAG) const { 153 return const_cast<DGNode *>(this)->preds_begin(DAG); 154 } preds_end(DependencyGraph & DAG)155 iterator preds_end(DependencyGraph &DAG) const { 156 return const_cast<DGNode *>(this)->preds_end(DAG); 157 } 158 /// \Returns a range of DAG predecessors nodes. If this is a MemDGNode then 159 /// this will also include the memory dependency predecessors. 160 /// Please note that this can include the same node more than once, if for 161 /// example it's both a use-def predecessor and a mem dep predecessor. preds(DependencyGraph & DAG)162 iterator_range<iterator> preds(DependencyGraph &DAG) const { 163 return make_range(preds_begin(DAG), preds_end(DAG)); 164 } 165 isStackSaveOrRestoreIntrinsic(Instruction * I)166 static bool isStackSaveOrRestoreIntrinsic(Instruction *I) { 167 if (auto *II = dyn_cast<IntrinsicInst>(I)) { 168 auto IID = II->getIntrinsicID(); 169 return IID == Intrinsic::stackrestore || IID == Intrinsic::stacksave; 170 } 171 return false; 172 } 173 174 /// \Returns true if intrinsic \p I touches memory. This is used by the 175 /// dependency graph. isMemIntrinsic(IntrinsicInst * I)176 static bool isMemIntrinsic(IntrinsicInst *I) { 177 auto IID = I->getIntrinsicID(); 178 return IID != Intrinsic::sideeffect && IID != Intrinsic::pseudoprobe; 179 } 180 181 /// We consider \p I as a Memory Dependency Candidate instruction if it 182 /// reads/write memory or if it has side-effects. This is used by the 183 /// dependency graph. isMemDepCandidate(Instruction * I)184 static bool isMemDepCandidate(Instruction *I) { 185 IntrinsicInst *II; 186 return I->mayReadOrWriteMemory() && 187 (!(II = dyn_cast<IntrinsicInst>(I)) || isMemIntrinsic(II)); 188 } 189 190 /// \Returns true if \p I is fence like. It excludes non-mem intrinsics. isFenceLike(Instruction * I)191 static bool isFenceLike(Instruction *I) { 192 IntrinsicInst *II; 193 return I->isFenceLike() && 194 (!(II = dyn_cast<IntrinsicInst>(I)) || isMemIntrinsic(II)); 195 } 196 197 /// \Returns true if \p I is a memory dependency candidate instruction. isMemDepNodeCandidate(Instruction * I)198 static bool isMemDepNodeCandidate(Instruction *I) { 199 AllocaInst *Alloca; 200 return isMemDepCandidate(I) || 201 ((Alloca = dyn_cast<AllocaInst>(I)) && 202 Alloca->isUsedWithInAlloca()) || 203 isStackSaveOrRestoreIntrinsic(I) || isFenceLike(I); 204 } 205 getInstruction()206 Instruction *getInstruction() const { return I; } 207 208 #ifndef NDEBUG 209 virtual void print(raw_ostream &OS, bool PrintDeps = true) const; 210 friend raw_ostream &operator<<(raw_ostream &OS, DGNode &N) { 211 N.print(OS); 212 return OS; 213 } 214 LLVM_DUMP_METHOD void dump() const; 215 #endif // NDEBUG 216 }; 217 218 /// A DependencyGraph Node for instructions that may read/write memory, or have 219 /// some ordering constraints, like with stacksave/stackrestore and 220 /// alloca/inalloca. 221 class MemDGNode final : public DGNode { 222 MemDGNode *PrevMemN = nullptr; 223 MemDGNode *NextMemN = nullptr; 224 /// Memory predecessors. 225 DenseSet<MemDGNode *> MemPreds; 226 /// Memory successors. 227 DenseSet<MemDGNode *> MemSuccs; 228 friend class PredIterator; // For MemPreds. 229 /// Creates both edges: this<->N. setNextNode(MemDGNode * N)230 void setNextNode(MemDGNode *N) { 231 assert(N != this && "About to point to self!"); 232 NextMemN = N; 233 if (NextMemN != nullptr) 234 NextMemN->PrevMemN = this; 235 } 236 /// Creates both edges: N<->this. setPrevNode(MemDGNode * N)237 void setPrevNode(MemDGNode *N) { 238 assert(N != this && "About to point to self!"); 239 PrevMemN = N; 240 if (PrevMemN != nullptr) 241 PrevMemN->NextMemN = this; 242 } 243 friend class DependencyGraph; // For setNextNode(), setPrevNode(). detachFromChain()244 void detachFromChain() { 245 if (PrevMemN != nullptr) 246 PrevMemN->NextMemN = NextMemN; 247 if (NextMemN != nullptr) 248 NextMemN->PrevMemN = PrevMemN; 249 PrevMemN = nullptr; 250 NextMemN = nullptr; 251 } 252 253 public: MemDGNode(Instruction * I)254 MemDGNode(Instruction *I) : DGNode(I, DGNodeID::MemDGNode) { 255 assert(isMemDepNodeCandidate(I) && "Expected Mem instruction!"); 256 } classof(const DGNode * Other)257 static bool classof(const DGNode *Other) { 258 return Other->SubclassID == DGNodeID::MemDGNode; 259 } preds_begin(DependencyGraph & DAG)260 iterator preds_begin(DependencyGraph &DAG) override { 261 auto OpEndIt = I->op_end(); 262 return PredIterator(PredIterator::skipBadIt(I->op_begin(), OpEndIt, DAG), 263 OpEndIt, MemPreds.begin(), this, DAG); 264 } preds_end(DependencyGraph & DAG)265 iterator preds_end(DependencyGraph &DAG) override { 266 return PredIterator(I->op_end(), I->op_end(), MemPreds.end(), this, DAG); 267 } 268 /// \Returns the previous Mem DGNode in instruction order. getPrevNode()269 MemDGNode *getPrevNode() const { return PrevMemN; } 270 /// \Returns the next Mem DGNode in instruction order. getNextNode()271 MemDGNode *getNextNode() const { return NextMemN; } 272 /// Adds the mem dependency edge PredN->this. This also increments the 273 /// UnscheduledSuccs counter of the predecessor if this node has not been 274 /// scheduled. addMemPred(MemDGNode * PredN)275 void addMemPred(MemDGNode *PredN) { 276 [[maybe_unused]] auto Inserted = MemPreds.insert(PredN).second; 277 assert(Inserted && "PredN already exists!"); 278 assert(PredN != this && "Trying to add a dependency to self!"); 279 PredN->MemSuccs.insert(this); 280 if (!Scheduled) { 281 ++PredN->UnscheduledSuccs; 282 } 283 } 284 /// Removes the memory dependency PredN->this. This also updates the 285 /// UnscheduledSuccs counter of PredN if this node has not been scheduled. removeMemPred(MemDGNode * PredN)286 void removeMemPred(MemDGNode *PredN) { 287 MemPreds.erase(PredN); 288 PredN->MemSuccs.erase(this); 289 if (!Scheduled) { 290 PredN->decrUnscheduledSuccs(); 291 } 292 } 293 /// \Returns true if there is a memory dependency N->this. hasMemPred(DGNode * N)294 bool hasMemPred(DGNode *N) const { 295 if (auto *MN = dyn_cast<MemDGNode>(N)) 296 return MemPreds.count(MN); 297 return false; 298 } 299 /// \Returns all memory dependency predecessors. Used by tests. memPreds()300 iterator_range<DenseSet<MemDGNode *>::const_iterator> memPreds() const { 301 return make_range(MemPreds.begin(), MemPreds.end()); 302 } 303 /// \Returns all memory dependency successors. memSuccs()304 iterator_range<DenseSet<MemDGNode *>::const_iterator> memSuccs() const { 305 return make_range(MemSuccs.begin(), MemSuccs.end()); 306 } 307 #ifndef NDEBUG 308 virtual void print(raw_ostream &OS, bool PrintDeps = true) const override; 309 #endif // NDEBUG 310 }; 311 312 /// Convenience builders for a MemDGNode interval. 313 class MemDGNodeIntervalBuilder { 314 public: 315 /// Scans the instruction chain in \p Intvl top-down, returning the top-most 316 /// MemDGNode, or nullptr. 317 LLVM_ABI static MemDGNode *getTopMemDGNode(const Interval<Instruction> &Intvl, 318 const DependencyGraph &DAG); 319 /// Scans the instruction chain in \p Intvl bottom-up, returning the 320 /// bottom-most MemDGNode, or nullptr. 321 LLVM_ABI static MemDGNode *getBotMemDGNode(const Interval<Instruction> &Intvl, 322 const DependencyGraph &DAG); 323 /// Given \p Instrs it finds their closest mem nodes in the interval and 324 /// returns the corresponding mem range. Note: BotN (or its neighboring mem 325 /// node) is included in the range. 326 LLVM_ABI static Interval<MemDGNode> make(const Interval<Instruction> &Instrs, 327 DependencyGraph &DAG); makeEmpty()328 static Interval<MemDGNode> makeEmpty() { return {}; } 329 }; 330 331 class DependencyGraph { 332 private: 333 DenseMap<Instruction *, std::unique_ptr<DGNode>> InstrToNodeMap; 334 /// The DAG spans across all instructions in this interval. 335 Interval<Instruction> DAGInterval; 336 337 Context *Ctx = nullptr; 338 std::optional<Context::CallbackID> CreateInstrCB; 339 std::optional<Context::CallbackID> EraseInstrCB; 340 std::optional<Context::CallbackID> MoveInstrCB; 341 std::optional<Context::CallbackID> SetUseCB; 342 343 std::unique_ptr<BatchAAResults> BatchAA; 344 345 enum class DependencyType { 346 ReadAfterWrite, ///> Memory dependency write -> read 347 WriteAfterWrite, ///> Memory dependency write -> write 348 WriteAfterRead, ///> Memory dependency read -> write 349 Control, ///> Control-related dependency, like with PHI/Terminator 350 Other, ///> Currently used for stack related instrs 351 None, ///> No memory/other dependency 352 }; 353 /// \Returns the dependency type depending on whether instructions may 354 /// read/write memory or whether they are some specific opcode-related 355 /// restrictions. 356 /// Note: It does not check whether a memory dependency is actually correct, 357 /// as it won't call AA. Therefore it returns the worst-case dep type. 358 static DependencyType getRoughDepType(Instruction *FromI, Instruction *ToI); 359 360 // TODO: Implement AABudget. 361 /// \Returns true if there is a memory/other dependency \p SrcI->DstI. 362 bool alias(Instruction *SrcI, Instruction *DstI, DependencyType DepType); 363 364 bool hasDep(sandboxir::Instruction *SrcI, sandboxir::Instruction *DstI); 365 366 /// Go through all mem nodes in \p SrcScanRange and try to add dependencies to 367 /// \p DstN. 368 void scanAndAddDeps(MemDGNode &DstN, const Interval<MemDGNode> &SrcScanRange); 369 370 /// Sets the UnscheduledSuccs of all DGNodes in \p NewInterval based on 371 /// def-use edges. 372 void setDefUseUnscheduledSuccs(const Interval<Instruction> &NewInterval); 373 374 /// Create DAG nodes for instrs in \p NewInterval and update the MemNode 375 /// chain. 376 void createNewNodes(const Interval<Instruction> &NewInterval); 377 378 /// Helper for `notify*Instr()`. \Returns the first MemDGNode that comes 379 /// before \p N, skipping \p SkipN, including or excluding \p N based on 380 /// \p IncludingN, or nullptr if not found. 381 MemDGNode *getMemDGNodeBefore(DGNode *N, bool IncludingN, 382 MemDGNode *SkipN = nullptr) const; 383 /// Helper for `notifyMoveInstr()`. \Returns the first MemDGNode that comes 384 /// after \p N, skipping \p SkipN, including or excluding \p N based on \p 385 /// IncludingN, or nullptr if not found. 386 MemDGNode *getMemDGNodeAfter(DGNode *N, bool IncludingN, 387 MemDGNode *SkipN = nullptr) const; 388 389 /// Called by the callbacks when a new instruction \p I has been created. 390 LLVM_ABI void notifyCreateInstr(Instruction *I); 391 /// Called by the callbacks when instruction \p I is about to get 392 /// deleted. 393 LLVM_ABI void notifyEraseInstr(Instruction *I); 394 /// Called by the callbacks when instruction \p I is about to be moved to 395 /// \p To. 396 LLVM_ABI void notifyMoveInstr(Instruction *I, const BBIterator &To); 397 /// Called by the callbacks when \p U's source is about to be set to \p NewSrc 398 LLVM_ABI void notifySetUse(const Use &U, Value *NewSrc); 399 400 public: 401 /// This constructor also registers callbacks. DependencyGraph(AAResults & AA,Context & Ctx)402 DependencyGraph(AAResults &AA, Context &Ctx) 403 : Ctx(&Ctx), BatchAA(std::make_unique<BatchAAResults>(AA)) { 404 CreateInstrCB = Ctx.registerCreateInstrCallback( 405 [this](Instruction *I) { notifyCreateInstr(I); }); 406 EraseInstrCB = Ctx.registerEraseInstrCallback( 407 [this](Instruction *I) { notifyEraseInstr(I); }); 408 MoveInstrCB = Ctx.registerMoveInstrCallback( 409 [this](Instruction *I, const BBIterator &To) { 410 notifyMoveInstr(I, To); 411 }); 412 SetUseCB = Ctx.registerSetUseCallback( 413 [this](const Use &U, Value *NewSrc) { notifySetUse(U, NewSrc); }); 414 } ~DependencyGraph()415 ~DependencyGraph() { 416 if (CreateInstrCB) 417 Ctx->unregisterCreateInstrCallback(*CreateInstrCB); 418 if (EraseInstrCB) 419 Ctx->unregisterEraseInstrCallback(*EraseInstrCB); 420 if (MoveInstrCB) 421 Ctx->unregisterMoveInstrCallback(*MoveInstrCB); 422 if (SetUseCB) 423 Ctx->unregisterSetUseCallback(*SetUseCB); 424 } 425 getNode(Instruction * I)426 DGNode *getNode(Instruction *I) const { 427 auto It = InstrToNodeMap.find(I); 428 return It != InstrToNodeMap.end() ? It->second.get() : nullptr; 429 } 430 /// Like getNode() but returns nullptr if \p I is nullptr. getNodeOrNull(Instruction * I)431 DGNode *getNodeOrNull(Instruction *I) const { 432 if (I == nullptr) 433 return nullptr; 434 return getNode(I); 435 } getOrCreateNode(Instruction * I)436 DGNode *getOrCreateNode(Instruction *I) { 437 auto [It, NotInMap] = InstrToNodeMap.try_emplace(I); 438 if (NotInMap) { 439 if (DGNode::isMemDepNodeCandidate(I)) 440 It->second = std::make_unique<MemDGNode>(I); 441 else 442 It->second = std::make_unique<DGNode>(I); 443 } 444 return It->second.get(); 445 } 446 /// Build/extend the dependency graph such that it includes \p Instrs. Returns 447 /// the range of instructions added to the DAG. 448 LLVM_ABI Interval<Instruction> extend(ArrayRef<Instruction *> Instrs); 449 /// \Returns the range of instructions included in the DAG. getInterval()450 Interval<Instruction> getInterval() const { return DAGInterval; } clear()451 void clear() { 452 InstrToNodeMap.clear(); 453 DAGInterval = {}; 454 } 455 #ifndef NDEBUG 456 /// \Returns true if the DAG's state is clear. Used in assertions. empty()457 bool empty() const { 458 bool IsEmpty = InstrToNodeMap.empty(); 459 assert(IsEmpty == DAGInterval.empty() && 460 "Interval and InstrToNodeMap out of sync!"); 461 return IsEmpty; 462 } 463 void print(raw_ostream &OS) const; 464 LLVM_DUMP_METHOD void dump() const; 465 #endif // NDEBUG 466 }; 467 } // namespace llvm::sandboxir 468 469 #endif // LLVM_TRANSFORMS_VECTORIZE_SANDBOXVECTORIZER_DEPENDENCYGRAPH_H 470