xref: /freebsd/contrib/llvm-project/llvm/include/llvm/Transforms/Vectorize/SandboxVectorizer/DependencyGraph.h (revision 700637cbb5e582861067a11aaca4d053546871d2)
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