LLVM 21.0.0git
DependencyGraph.h
Go to the documentation of this file.
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"
31
32namespace llvm::sandboxir {
33
34class DependencyGraph;
35class MemDGNode;
36class SchedBundle;
37
38/// SubclassIDs for isa/dyn_cast etc.
39enum class DGNodeID {
40 DGNode,
42};
43
44class DGNode;
45class MemDGNode;
46class DependencyGraph;
47
48/// Iterate over both def-use and mem dependencies.
53 DGNode *N = nullptr;
54 DependencyGraph *DAG = nullptr;
55
56 PredIterator(const User::op_iterator &OpIt, const User::op_iterator &OpItE,
58 DependencyGraph &DAG)
59 : OpIt(OpIt), OpItE(OpItE), MemIt(MemIt), N(N), DAG(&DAG) {}
60 PredIterator(const User::op_iterator &OpIt, const User::op_iterator &OpItE,
62 : OpIt(OpIt), OpItE(OpItE), N(N), DAG(&DAG) {}
63 friend class DGNode; // For constructor
64 friend class MemDGNode; // For constructor
65
66 /// Skip iterators that don't point instructions or are outside \p DAG,
67 /// starting from \p OpIt and ending before \p OpItE.n
68 static User::op_iterator skipBadIt(User::op_iterator OpIt,
70 const DependencyGraph &DAG);
71
72public:
73 using difference_type = std::ptrdiff_t;
74 using value_type = DGNode *;
77 using iterator_category = std::input_iterator_tag;
81 auto Copy = *this;
82 ++(*this);
83 return Copy;
84 }
85 bool operator==(const PredIterator &Other) const;
86 bool operator!=(const PredIterator &Other) const { return !(*this == Other); }
87};
88
89/// A DependencyGraph Node that points to an Instruction and contains memory
90/// dependency edges.
91class DGNode {
92protected:
94 // TODO: Use a PointerIntPair for SubclassID and I.
95 /// For isa/dyn_cast etc.
97 /// The number of unscheduled successors.
98 unsigned UnscheduledSuccs = 0;
99 /// This is true if this node has been scheduled.
100 bool Scheduled = false;
101 /// The scheduler bundle that this node belongs to.
102 SchedBundle *SB = nullptr;
103
104 void setSchedBundle(SchedBundle &SB) { this->SB = &SB; }
105 void clearSchedBundle() { this->SB = nullptr; }
106 friend class SchedBundle; // For setSchedBundle(), clearSchedBundle().
107
109 friend class MemDGNode; // For constructor.
110 friend class DependencyGraph; // For UnscheduledSuccs
111
112public:
114 assert(!isMemDepNodeCandidate(I) && "Expected Non-Mem instruction, ");
115 }
116 DGNode(const DGNode &Other) = delete;
117 virtual ~DGNode();
118 /// \Returns the number of unscheduled successors.
119 unsigned getNumUnscheduledSuccs() const { return UnscheduledSuccs; }
121 assert(UnscheduledSuccs > 0 && "Counting error!");
123 }
125 /// \Returns true if all dependent successors have been scheduled.
126 bool ready() const { return UnscheduledSuccs == 0; }
127 /// \Returns true if this node has been scheduled.
128 bool scheduled() const { return Scheduled; }
129 void setScheduled(bool NewVal) { Scheduled = NewVal; }
130 /// \Returns the scheduling bundle that this node belongs to, or nullptr.
131 SchedBundle *getSchedBundle() const { return SB; }
132 /// \Returns true if this is before \p Other in program order.
133 bool comesBefore(const DGNode *Other) { return I->comesBefore(Other->I); }
136 return PredIterator(
137 PredIterator::skipBadIt(I->op_begin(), I->op_end(), DAG), I->op_end(),
138 this, DAG);
139 }
141 return PredIterator(I->op_end(), I->op_end(), this, DAG);
142 }
144 return const_cast<DGNode *>(this)->preds_begin(DAG);
145 }
147 return const_cast<DGNode *>(this)->preds_end(DAG);
148 }
149 /// \Returns a range of DAG predecessors nodes. If this is a MemDGNode then
150 /// this will also include the memory dependency predecessors.
151 /// Please note that this can include the same node more than once, if for
152 /// example it's both a use-def predecessor and a mem dep predecessor.
154 return make_range(preds_begin(DAG), preds_end(DAG));
155 }
156
158 if (auto *II = dyn_cast<IntrinsicInst>(I)) {
159 auto IID = II->getIntrinsicID();
160 return IID == Intrinsic::stackrestore || IID == Intrinsic::stacksave;
161 }
162 return false;
163 }
164
165 /// \Returns true if intrinsic \p I touches memory. This is used by the
166 /// dependency graph.
168 auto IID = I->getIntrinsicID();
169 return IID != Intrinsic::sideeffect && IID != Intrinsic::pseudoprobe;
170 }
171
172 /// We consider \p I as a Memory Dependency Candidate instruction if it
173 /// reads/write memory or if it has side-effects. This is used by the
174 /// dependency graph.
177 return I->mayReadOrWriteMemory() &&
178 (!(II = dyn_cast<IntrinsicInst>(I)) || isMemIntrinsic(II));
179 }
180
181 /// \Returns true if \p I is fence like. It excludes non-mem intrinsics.
182 static bool isFenceLike(Instruction *I) {
184 return I->isFenceLike() &&
185 (!(II = dyn_cast<IntrinsicInst>(I)) || isMemIntrinsic(II));
186 }
187
188 /// \Returns true if \p I is a memory dependency candidate instruction.
190 AllocaInst *Alloca;
191 return isMemDepCandidate(I) ||
192 ((Alloca = dyn_cast<AllocaInst>(I)) &&
193 Alloca->isUsedWithInAlloca()) ||
195 }
196
197 Instruction *getInstruction() const { return I; }
198
199#ifndef NDEBUG
200 virtual void print(raw_ostream &OS, bool PrintDeps = true) const;
202 N.print(OS);
203 return OS;
204 }
205 LLVM_DUMP_METHOD void dump() const;
206#endif // NDEBUG
207};
208
209/// A DependencyGraph Node for instructions that may read/write memory, or have
210/// some ordering constraints, like with stacksave/stackrestore and
211/// alloca/inalloca.
212class MemDGNode final : public DGNode {
213 MemDGNode *PrevMemN = nullptr;
214 MemDGNode *NextMemN = nullptr;
215 /// Memory predecessors.
216 DenseSet<MemDGNode *> MemPreds;
217 friend class PredIterator; // For MemPreds.
218 /// Creates both edges: this<->N.
219 void setNextNode(MemDGNode *N) {
220 assert(N != this && "About to point to self!");
221 NextMemN = N;
222 if (NextMemN != nullptr)
223 NextMemN->PrevMemN = this;
224 }
225 /// Creates both edges: N<->this.
226 void setPrevNode(MemDGNode *N) {
227 assert(N != this && "About to point to self!");
228 PrevMemN = N;
229 if (PrevMemN != nullptr)
230 PrevMemN->NextMemN = this;
231 }
232 friend class DependencyGraph; // For setNextNode(), setPrevNode().
233 void detachFromChain() {
234 if (PrevMemN != nullptr)
235 PrevMemN->NextMemN = NextMemN;
236 if (NextMemN != nullptr)
237 NextMemN->PrevMemN = PrevMemN;
238 PrevMemN = nullptr;
239 NextMemN = nullptr;
240 }
241
242public:
244 assert(isMemDepNodeCandidate(I) && "Expected Mem instruction!");
245 }
246 static bool classof(const DGNode *Other) {
247 return Other->SubclassID == DGNodeID::MemDGNode;
248 }
250 auto OpEndIt = I->op_end();
251 return PredIterator(PredIterator::skipBadIt(I->op_begin(), OpEndIt, DAG),
252 OpEndIt, MemPreds.begin(), this, DAG);
253 }
255 return PredIterator(I->op_end(), I->op_end(), MemPreds.end(), this, DAG);
256 }
257 /// \Returns the previous Mem DGNode in instruction order.
258 MemDGNode *getPrevNode() const { return PrevMemN; }
259 /// \Returns the next Mem DGNode in instruction order.
260 MemDGNode *getNextNode() const { return NextMemN; }
261 /// Adds the mem dependency edge PredN->this. This also increments the
262 /// UnscheduledSuccs counter of the predecessor if this node has not been
263 /// scheduled.
264 void addMemPred(MemDGNode *PredN) {
265 [[maybe_unused]] auto Inserted = MemPreds.insert(PredN).second;
266 assert(Inserted && "PredN already exists!");
267 assert(PredN != this && "Trying to add a dependency to self!");
268 if (!Scheduled) {
269 ++PredN->UnscheduledSuccs;
270 }
271 }
272 /// \Returns true if there is a memory dependency N->this.
273 bool hasMemPred(DGNode *N) const {
274 if (auto *MN = dyn_cast<MemDGNode>(N))
275 return MemPreds.count(MN);
276 return false;
277 }
278 /// \Returns all memory dependency predecessors. Used by tests.
280 return make_range(MemPreds.begin(), MemPreds.end());
281 }
282#ifndef NDEBUG
283 virtual void print(raw_ostream &OS, bool PrintDeps = true) const override;
284#endif // NDEBUG
285};
286
287/// Convenience builders for a MemDGNode interval.
289public:
290 /// Scans the instruction chain in \p Intvl top-down, returning the top-most
291 /// MemDGNode, or nullptr.
293 const DependencyGraph &DAG);
294 /// Scans the instruction chain in \p Intvl bottom-up, returning the
295 /// bottom-most MemDGNode, or nullptr.
297 const DependencyGraph &DAG);
298 /// Given \p Instrs it finds their closest mem nodes in the interval and
299 /// returns the corresponding mem range. Note: BotN (or its neighboring mem
300 /// node) is included in the range.
302 DependencyGraph &DAG);
303 static Interval<MemDGNode> makeEmpty() { return {}; }
304};
305
307private:
309 /// The DAG spans across all instructions in this interval.
310 Interval<Instruction> DAGInterval;
311
312 Context *Ctx = nullptr;
313 std::optional<Context::CallbackID> CreateInstrCB;
314 std::optional<Context::CallbackID> EraseInstrCB;
315 std::optional<Context::CallbackID> MoveInstrCB;
316
317 std::unique_ptr<BatchAAResults> BatchAA;
318
319 enum class DependencyType {
320 ReadAfterWrite, ///> Memory dependency write -> read
321 WriteAfterWrite, ///> Memory dependency write -> write
322 WriteAfterRead, ///> Memory dependency read -> write
323 Control, ///> Control-related dependency, like with PHI/Terminator
324 Other, ///> Currently used for stack related instrs
325 None, ///> No memory/other dependency
326 };
327 /// \Returns the dependency type depending on whether instructions may
328 /// read/write memory or whether they are some specific opcode-related
329 /// restrictions.
330 /// Note: It does not check whether a memory dependency is actually correct,
331 /// as it won't call AA. Therefore it returns the worst-case dep type.
332 static DependencyType getRoughDepType(Instruction *FromI, Instruction *ToI);
333
334 // TODO: Implement AABudget.
335 /// \Returns true if there is a memory/other dependency \p SrcI->DstI.
336 bool alias(Instruction *SrcI, Instruction *DstI, DependencyType DepType);
337
338 bool hasDep(sandboxir::Instruction *SrcI, sandboxir::Instruction *DstI);
339
340 /// Go through all mem nodes in \p SrcScanRange and try to add dependencies to
341 /// \p DstN.
342 void scanAndAddDeps(MemDGNode &DstN, const Interval<MemDGNode> &SrcScanRange);
343
344 /// Sets the UnscheduledSuccs of all DGNodes in \p NewInterval based on
345 /// def-use edges.
346 void setDefUseUnscheduledSuccs(const Interval<Instruction> &NewInterval);
347
348 /// Create DAG nodes for instrs in \p NewInterval and update the MemNode
349 /// chain.
350 void createNewNodes(const Interval<Instruction> &NewInterval);
351
352 /// Helper for `notify*Instr()`. \Returns the first MemDGNode that comes
353 /// before \p N, skipping \p SkipN, including or excluding \p N based on
354 /// \p IncludingN, or nullptr if not found.
355 MemDGNode *getMemDGNodeBefore(DGNode *N, bool IncludingN,
356 MemDGNode *SkipN = nullptr) const;
357 /// Helper for `notifyMoveInstr()`. \Returns the first MemDGNode that comes
358 /// after \p N, skipping \p SkipN, including or excluding \p N based on \p
359 /// IncludingN, or nullptr if not found.
360 MemDGNode *getMemDGNodeAfter(DGNode *N, bool IncludingN,
361 MemDGNode *SkipN = nullptr) const;
362
363 /// Called by the callbacks when a new instruction \p I has been created.
364 void notifyCreateInstr(Instruction *I);
365 /// Called by the callbacks when instruction \p I is about to get
366 /// deleted.
367 void notifyEraseInstr(Instruction *I);
368 /// Called by the callbacks when instruction \p I is about to be moved to
369 /// \p To.
370 void notifyMoveInstr(Instruction *I, const BBIterator &To);
371
372public:
373 /// This constructor also registers callbacks.
375 : Ctx(&Ctx), BatchAA(std::make_unique<BatchAAResults>(AA)) {
376 CreateInstrCB = Ctx.registerCreateInstrCallback(
377 [this](Instruction *I) { notifyCreateInstr(I); });
378 EraseInstrCB = Ctx.registerEraseInstrCallback(
379 [this](Instruction *I) { notifyEraseInstr(I); });
380 MoveInstrCB = Ctx.registerMoveInstrCallback(
381 [this](Instruction *I, const BBIterator &To) {
382 notifyMoveInstr(I, To);
383 });
384 }
386 if (CreateInstrCB)
387 Ctx->unregisterCreateInstrCallback(*CreateInstrCB);
388 if (EraseInstrCB)
389 Ctx->unregisterEraseInstrCallback(*EraseInstrCB);
390 if (MoveInstrCB)
391 Ctx->unregisterMoveInstrCallback(*MoveInstrCB);
392 }
393
395 auto It = InstrToNodeMap.find(I);
396 return It != InstrToNodeMap.end() ? It->second.get() : nullptr;
397 }
398 /// Like getNode() but returns nullptr if \p I is nullptr.
400 if (I == nullptr)
401 return nullptr;
402 return getNode(I);
403 }
405 auto [It, NotInMap] = InstrToNodeMap.try_emplace(I);
406 if (NotInMap) {
408 It->second = std::make_unique<MemDGNode>(I);
409 else
410 It->second = std::make_unique<DGNode>(I);
411 }
412 return It->second.get();
413 }
414 /// Build/extend the dependency graph such that it includes \p Instrs. Returns
415 /// the range of instructions added to the DAG.
417 /// \Returns the range of instructions included in the DAG.
418 Interval<Instruction> getInterval() const { return DAGInterval; }
419 void clear() {
420 InstrToNodeMap.clear();
421 DAGInterval = {};
422 }
423#ifndef NDEBUG
424 /// \Returns true if the DAG's state is clear. Used in assertions.
425 bool empty() const {
426 bool IsEmpty = InstrToNodeMap.empty();
427 assert(IsEmpty == DAGInterval.empty() &&
428 "Interval and InstrToNodeMap out of sync!");
429 return IsEmpty;
430 }
431 void print(raw_ostream &OS) const;
432 LLVM_DUMP_METHOD void dump() const;
433#endif // NDEBUG
434};
435
436} // namespace llvm::sandboxir
437
438#endif // LLVM_TRANSFORMS_VECTORIZE_SANDBOXVECTORIZER_DEPENDENCYGRAPH_H
#define LLVM_DUMP_METHOD
Mark debug helper function definitions like dump() that should not be stripped from debug builds.
Definition: Compiler.h:622
This file defines the DenseMap class.
std::optional< std::vector< StOtherPiece > > Other
Definition: ELFYAML.cpp:1315
#define I(x, y, z)
Definition: MD5.cpp:58
uint64_t IntrinsicInst * II
assert(ImpDefSCC.getReg()==AMDGPU::SCC &&ImpDefSCC.isDef())
raw_pwrite_stream & OS
ArrayRef - Represent a constant reference to an array (0 or more elements consecutively in memory),...
Definition: ArrayRef.h:41
This class is a wrapper over an AAResults, and it is intended to be used only when there are no IR ch...
Represent a node in the directed graph.
Definition: DirectedGraph.h:73
iterator find(const_arg_type_t< KeyT > Val)
Definition: DenseMap.h:156
std::pair< iterator, bool > try_emplace(KeyT &&Key, Ts &&...Args)
Definition: DenseMap.h:226
bool empty() const
Definition: DenseMap.h:98
iterator end()
Definition: DenseMap.h:84
Implements a dense probed hash-table based set.
Definition: DenseSet.h:278
std::pair< iterator, bool > insert(const ValueT &V)
Definition: DenseSet.h:213
size_type count(const_arg_type_t< ValueT > V) const
Return 1 if the specified key is in the set, 0 otherwise.
Definition: DenseSet.h:95
A range adaptor for a pair of iterators.
This class implements an extremely fast bulk output stream that can only output to a stream.
Definition: raw_ostream.h:52
bool isUsedWithInAlloca() const
Return true if this alloca is used as an inalloca argument to a call.
Definition: Instruction.h:2241
Iterator for Instructions in a `BasicBlock.
Definition: BasicBlock.h:23
CallbackID registerCreateInstrCallback(CreateInstrCallback CB)
Register a callback that gets called right after a SandboxIR instruction is created.
Definition: Context.cpp:709
void unregisterCreateInstrCallback(CallbackID ID)
Definition: Context.cpp:716
void unregisterMoveInstrCallback(CallbackID ID)
Definition: Context.cpp:729
void unregisterEraseInstrCallback(CallbackID ID)
Definition: Context.cpp:702
CallbackID registerMoveInstrCallback(MoveInstrCallback CB)
Register a callback that gets called when a SandboxIR instruction is about to be moved.
Definition: Context.cpp:722
CallbackID registerEraseInstrCallback(EraseInstrCallback CB)
Register a callback that gets called when a SandboxIR instruction is about to be removed from its par...
Definition: Context.cpp:695
A DependencyGraph Node that points to an Instruction and contains memory dependency edges.
virtual void print(raw_ostream &OS, bool PrintDeps=true) const
static bool isMemDepCandidate(Instruction *I)
We consider I as a Memory Dependency Candidate instruction if it reads/write memory or if it has side...
virtual iterator preds_end(DependencyGraph &DAG)
static bool isMemIntrinsic(IntrinsicInst *I)
\Returns true if intrinsic I touches memory.
iterator preds_begin(DependencyGraph &DAG) const
DGNode(Instruction *I, DGNodeID ID)
unsigned getNumUnscheduledSuccs() const
\Returns the number of unscheduled successors.
void setSchedBundle(SchedBundle &SB)
bool scheduled() const
\Returns true if this node has been scheduled.
unsigned UnscheduledSuccs
The number of unscheduled successors.
void setScheduled(bool NewVal)
bool ready() const
\Returns true if all dependent successors have been scheduled.
iterator_range< iterator > preds(DependencyGraph &DAG) const
\Returns a range of DAG predecessors nodes.
iterator preds_end(DependencyGraph &DAG) const
SchedBundle * SB
The scheduler bundle that this node belongs to.
bool Scheduled
This is true if this node has been scheduled.
static bool isMemDepNodeCandidate(Instruction *I)
\Returns true if I is a memory dependency candidate instruction.
SchedBundle * getSchedBundle() const
\Returns the scheduling bundle that this node belongs to, or nullptr.
DGNodeID SubclassID
For isa/dyn_cast etc.
DGNode(const DGNode &Other)=delete
static bool isFenceLike(Instruction *I)
\Returns true if I is fence like. It excludes non-mem intrinsics.
LLVM_DUMP_METHOD void dump() const
Instruction * getInstruction() const
static bool isStackSaveOrRestoreIntrinsic(Instruction *I)
bool comesBefore(const DGNode *Other)
\Returns true if this is before Other in program order.
friend raw_ostream & operator<<(raw_ostream &OS, DGNode &N)
virtual iterator preds_begin(DependencyGraph &DAG)
Interval< Instruction > getInterval() const
\Returns the range of instructions included in the DAG.
bool empty() const
\Returns true if the DAG's state is clear. Used in assertions.
LLVM_DUMP_METHOD void dump() const
DGNode * getNode(Instruction *I) const
DependencyGraph(AAResults &AA, Context &Ctx)
This constructor also registers callbacks.
DGNode * getNodeOrNull(Instruction *I) const
Like getNode() but returns nullptr if I is nullptr.
void print(raw_ostream &OS) const
DGNode * getOrCreateNode(Instruction *I)
Interval< Instruction > extend(ArrayRef< Instruction * > Instrs)
Build/extend the dependency graph such that it includes Instrs.
A sandboxir::User with operands, opcode and linked with previous/next instructions in an instruction ...
Definition: Instruction.h:42
bool mayReadOrWriteMemory() const
Definition: Instruction.h:339
bool comesBefore(const Instruction *Other) const
Given an instruction Other in the same basic block as this instruction, return true if this instructi...
Definition: Instruction.h:214
bool empty() const
Definition: Interval.h:99
Convenience builders for a MemDGNode interval.
static MemDGNode * getBotMemDGNode(const Interval< Instruction > &Intvl, const DependencyGraph &DAG)
Scans the instruction chain in Intvl bottom-up, returning the bottom-most MemDGNode,...
static Interval< MemDGNode > makeEmpty()
static MemDGNode * getTopMemDGNode(const Interval< Instruction > &Intvl, const DependencyGraph &DAG)
Scans the instruction chain in Intvl top-down, returning the top-most MemDGNode, or nullptr.
static Interval< MemDGNode > make(const Interval< Instruction > &Instrs, DependencyGraph &DAG)
Given Instrs it finds their closest mem nodes in the interval and returns the corresponding mem range...
A DependencyGraph Node for instructions that may read/write memory, or have some ordering constraints...
iterator preds_end(DependencyGraph &DAG) override
iterator preds_begin(DependencyGraph &DAG) override
bool hasMemPred(DGNode *N) const
\Returns true if there is a memory dependency N->this.
static bool classof(const DGNode *Other)
iterator_range< DenseSet< MemDGNode * >::const_iterator > memPreds() const
\Returns all memory dependency predecessors. Used by tests.
MemDGNode * getNextNode() const
\Returns the next Mem DGNode in instruction order.
virtual void print(raw_ostream &OS, bool PrintDeps=true) const override
MemDGNode * getPrevNode() const
\Returns the previous Mem DGNode in instruction order.
void addMemPred(MemDGNode *PredN)
Adds the mem dependency edge PredN->this.
Iterator for the Use edges of a User's operands.
Definition: User.h:23
Iterate over both def-use and mem dependencies.
bool operator!=(const PredIterator &Other) const
std::input_iterator_tag iterator_category
The nodes that need to be scheduled back-to-back in a single scheduling cycle form a SchedBundle.
Definition: Scheduler.h:96
virtual op_iterator op_begin()
Definition: User.h:102
virtual op_iterator op_end()
Definition: User.h:106
This provides a very simple, boring adaptor for a begin and end iterator into a range type.
DGNodeID
SubclassIDs for isa/dyn_cast etc.
iterator_range< T > make_range(T x, T y)
Convenience function for iterating over sub-ranges.
@ None
Definition: CodeGenData.h:106
@ Other
Any other memory.
Implement std::hash so that hash_code can be used in STL containers.
Definition: BitVector.h:858
#define N