LLVM 20.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/// While OpIt points to a Value that is not an Instruction keep incrementing
49/// it. \Returns the first iterator that points to an Instruction, or end.
51 User::op_iterator OpItE) {
52 while (OpIt != OpItE && !isa<Instruction>((*OpIt).get()))
53 ++OpIt;
54 return OpIt;
55}
56
57/// Iterate over both def-use and mem dependencies.
62 DGNode *N = nullptr;
63 DependencyGraph *DAG = nullptr;
64
65 PredIterator(const User::op_iterator &OpIt, const User::op_iterator &OpItE,
67 DependencyGraph &DAG)
68 : OpIt(OpIt), OpItE(OpItE), MemIt(MemIt), N(N), DAG(&DAG) {}
69 PredIterator(const User::op_iterator &OpIt, const User::op_iterator &OpItE,
71 : OpIt(OpIt), OpItE(OpItE), N(N), DAG(&DAG) {}
72 friend class DGNode; // For constructor
73 friend class MemDGNode; // For constructor
74
75public:
76 using difference_type = std::ptrdiff_t;
77 using value_type = DGNode *;
80 using iterator_category = std::input_iterator_tag;
84 auto Copy = *this;
85 ++(*this);
86 return Copy;
87 }
88 bool operator==(const PredIterator &Other) const;
89 bool operator!=(const PredIterator &Other) const { return !(*this == Other); }
90};
91
92/// A DependencyGraph Node that points to an Instruction and contains memory
93/// dependency edges.
94class DGNode {
95protected:
97 // TODO: Use a PointerIntPair for SubclassID and I.
98 /// For isa/dyn_cast etc.
100 /// The number of unscheduled successors.
101 unsigned UnscheduledSuccs = 0;
102 /// This is true if this node has been scheduled.
103 bool Scheduled = false;
104 /// The scheduler bundle that this node belongs to.
105 SchedBundle *SB = nullptr;
106
107 void setSchedBundle(SchedBundle &SB) { this->SB = &SB; }
108 void clearSchedBundle() { this->SB = nullptr; }
109 friend class SchedBundle; // For setSchedBundle(), clearSchedBundle().
110
112 friend class MemDGNode; // For constructor.
113 friend class DependencyGraph; // For UnscheduledSuccs
114
115public:
117 assert(!isMemDepNodeCandidate(I) && "Expected Non-Mem instruction, ");
118 }
119 DGNode(const DGNode &Other) = delete;
120 virtual ~DGNode();
121 /// \Returns the number of unscheduled successors.
122 unsigned getNumUnscheduledSuccs() const { return UnscheduledSuccs; }
124 assert(UnscheduledSuccs > 0 && "Counting error!");
126 }
127 /// \Returns true if all dependent successors have been scheduled.
128 bool ready() const { return UnscheduledSuccs == 0; }
129 /// \Returns true if this node has been scheduled.
130 bool scheduled() const { return Scheduled; }
131 void setScheduled(bool NewVal) { Scheduled = NewVal; }
132 /// \Returns the scheduling bundle that this node belongs to, or nullptr.
133 SchedBundle *getSchedBundle() const { return SB; }
134 /// \Returns true if this is before \p Other in program order.
135 bool comesBefore(const DGNode *Other) { return I->comesBefore(Other->I); }
138 return PredIterator(skipNonInstr(I->op_begin(), I->op_end()), I->op_end(),
139 this, DAG);
140 }
142 return PredIterator(I->op_end(), I->op_end(), this, DAG);
143 }
145 return const_cast<DGNode *>(this)->preds_begin(DAG);
146 }
148 return const_cast<DGNode *>(this)->preds_end(DAG);
149 }
150 /// \Returns a range of DAG predecessors nodes. If this is a MemDGNode then
151 /// this will also include the memory dependency predecessors.
152 /// Please note that this can include the same node more than once, if for
153 /// example it's both a use-def predecessor and a mem dep predecessor.
155 return make_range(preds_begin(DAG), preds_end(DAG));
156 }
157
159 if (auto *II = dyn_cast<IntrinsicInst>(I)) {
160 auto IID = II->getIntrinsicID();
161 return IID == Intrinsic::stackrestore || IID == Intrinsic::stacksave;
162 }
163 return false;
164 }
165
166 /// \Returns true if intrinsic \p I touches memory. This is used by the
167 /// dependency graph.
169 auto IID = I->getIntrinsicID();
170 return IID != Intrinsic::sideeffect && IID != Intrinsic::pseudoprobe;
171 }
172
173 /// We consider \p I as a Memory Dependency Candidate instruction if it
174 /// reads/write memory or if it has side-effects. This is used by the
175 /// dependency graph.
178 return I->mayReadOrWriteMemory() &&
179 (!(II = dyn_cast<IntrinsicInst>(I)) || isMemIntrinsic(II));
180 }
181
182 /// \Returns true if \p I is fence like. It excludes non-mem intrinsics.
183 static bool isFenceLike(Instruction *I) {
185 return I->isFenceLike() &&
186 (!(II = dyn_cast<IntrinsicInst>(I)) || isMemIntrinsic(II));
187 }
188
189 /// \Returns true if \p I is a memory dependency candidate instruction.
191 AllocaInst *Alloca;
192 return isMemDepCandidate(I) ||
193 ((Alloca = dyn_cast<AllocaInst>(I)) &&
194 Alloca->isUsedWithInAlloca()) ||
196 }
197
198 Instruction *getInstruction() const { return I; }
199
200#ifndef NDEBUG
201 virtual void print(raw_ostream &OS, bool PrintDeps = true) const;
203 N.print(OS);
204 return OS;
205 }
206 LLVM_DUMP_METHOD void dump() const;
207#endif // NDEBUG
208};
209
210/// A DependencyGraph Node for instructions that may read/write memory, or have
211/// some ordering constraints, like with stacksave/stackrestore and
212/// alloca/inalloca.
213class MemDGNode final : public DGNode {
214 MemDGNode *PrevMemN = nullptr;
215 MemDGNode *NextMemN = nullptr;
216 /// Memory predecessors.
217 DenseSet<MemDGNode *> MemPreds;
218 friend class PredIterator; // For MemPreds.
219
220 void setNextNode(MemDGNode *N) { NextMemN = N; }
221 void setPrevNode(MemDGNode *N) { PrevMemN = N; }
222 friend class DependencyGraph; // For setNextNode(), setPrevNode().
223 void detachFromChain() {
224 if (PrevMemN != nullptr)
225 PrevMemN->NextMemN = NextMemN;
226 if (NextMemN != nullptr)
227 NextMemN->PrevMemN = PrevMemN;
228 PrevMemN = nullptr;
229 NextMemN = nullptr;
230 }
231
232public:
234 assert(isMemDepNodeCandidate(I) && "Expected Mem instruction!");
235 }
236 static bool classof(const DGNode *Other) {
237 return Other->SubclassID == DGNodeID::MemDGNode;
238 }
240 auto OpEndIt = I->op_end();
241 return PredIterator(skipNonInstr(I->op_begin(), OpEndIt), OpEndIt,
242 MemPreds.begin(), this, DAG);
243 }
245 return PredIterator(I->op_end(), I->op_end(), MemPreds.end(), this, DAG);
246 }
247 /// \Returns the previous Mem DGNode in instruction order.
248 MemDGNode *getPrevNode() const { return PrevMemN; }
249 /// \Returns the next Mem DGNode in instruction order.
250 MemDGNode *getNextNode() const { return NextMemN; }
251 /// Adds the mem dependency edge PredN->this. This also increments the
252 /// UnscheduledSuccs counter of the predecessor if this node has not been
253 /// scheduled.
254 void addMemPred(MemDGNode *PredN) {
255 [[maybe_unused]] auto Inserted = MemPreds.insert(PredN).second;
256 assert(Inserted && "PredN already exists!");
257 if (!Scheduled) {
258 ++PredN->UnscheduledSuccs;
259 }
260 }
261 /// \Returns true if there is a memory dependency N->this.
262 bool hasMemPred(DGNode *N) const {
263 if (auto *MN = dyn_cast<MemDGNode>(N))
264 return MemPreds.count(MN);
265 return false;
266 }
267 /// \Returns all memory dependency predecessors. Used by tests.
269 return make_range(MemPreds.begin(), MemPreds.end());
270 }
271#ifndef NDEBUG
272 virtual void print(raw_ostream &OS, bool PrintDeps = true) const override;
273#endif // NDEBUG
274};
275
276/// Convenience builders for a MemDGNode interval.
278public:
279 /// Scans the instruction chain in \p Intvl top-down, returning the top-most
280 /// MemDGNode, or nullptr.
282 const DependencyGraph &DAG);
283 /// Scans the instruction chain in \p Intvl bottom-up, returning the
284 /// bottom-most MemDGNode, or nullptr.
286 const DependencyGraph &DAG);
287 /// Given \p Instrs it finds their closest mem nodes in the interval and
288 /// returns the corresponding mem range. Note: BotN (or its neighboring mem
289 /// node) is included in the range.
291 DependencyGraph &DAG);
292 static Interval<MemDGNode> makeEmpty() { return {}; }
293};
294
296private:
298 /// The DAG spans across all instructions in this interval.
299 Interval<Instruction> DAGInterval;
300
301 Context *Ctx = nullptr;
302 std::optional<Context::CallbackID> CreateInstrCB;
303 std::optional<Context::CallbackID> EraseInstrCB;
304 std::optional<Context::CallbackID> MoveInstrCB;
305
306 std::unique_ptr<BatchAAResults> BatchAA;
307
308 enum class DependencyType {
309 ReadAfterWrite, ///> Memory dependency write -> read
310 WriteAfterWrite, ///> Memory dependency write -> write
311 WriteAfterRead, ///> Memory dependency read -> write
312 Control, ///> Control-related dependency, like with PHI/Terminator
313 Other, ///> Currently used for stack related instrs
314 None, ///> No memory/other dependency
315 };
316 /// \Returns the dependency type depending on whether instructions may
317 /// read/write memory or whether they are some specific opcode-related
318 /// restrictions.
319 /// Note: It does not check whether a memory dependency is actually correct,
320 /// as it won't call AA. Therefore it returns the worst-case dep type.
321 static DependencyType getRoughDepType(Instruction *FromI, Instruction *ToI);
322
323 // TODO: Implement AABudget.
324 /// \Returns true if there is a memory/other dependency \p SrcI->DstI.
325 bool alias(Instruction *SrcI, Instruction *DstI, DependencyType DepType);
326
327 bool hasDep(sandboxir::Instruction *SrcI, sandboxir::Instruction *DstI);
328
329 /// Go through all mem nodes in \p SrcScanRange and try to add dependencies to
330 /// \p DstN.
331 void scanAndAddDeps(MemDGNode &DstN, const Interval<MemDGNode> &SrcScanRange);
332
333 /// Sets the UnscheduledSuccs of all DGNodes in \p NewInterval based on
334 /// def-use edges.
335 void setDefUseUnscheduledSuccs(const Interval<Instruction> &NewInterval);
336
337 /// Create DAG nodes for instrs in \p NewInterval and update the MemNode
338 /// chain.
339 void createNewNodes(const Interval<Instruction> &NewInterval);
340
341 /// Helper for `notify*Instr()`. \Returns the first MemDGNode that comes
342 /// before \p N, including or excluding \p N based on \p IncludingN, or
343 /// nullptr if not found.
344 MemDGNode *getMemDGNodeBefore(DGNode *N, bool IncludingN) const;
345 /// Helper for `notifyMoveInstr()`. \Returns the first MemDGNode that comes
346 /// after \p N, including or excluding \p N based on \p IncludingN, or nullptr
347 /// if not found.
348 MemDGNode *getMemDGNodeAfter(DGNode *N, bool IncludingN) const;
349
350 /// Called by the callbacks when a new instruction \p I has been created.
351 void notifyCreateInstr(Instruction *I);
352 /// Called by the callbacks when instruction \p I is about to get
353 /// deleted.
354 void notifyEraseInstr(Instruction *I);
355 /// Called by the callbacks when instruction \p I is about to be moved to
356 /// \p To.
357 void notifyMoveInstr(Instruction *I, const BBIterator &To);
358
359public:
360 /// This constructor also registers callbacks.
362 : Ctx(&Ctx), BatchAA(std::make_unique<BatchAAResults>(AA)) {
363 CreateInstrCB = Ctx.registerCreateInstrCallback(
364 [this](Instruction *I) { notifyCreateInstr(I); });
365 EraseInstrCB = Ctx.registerEraseInstrCallback(
366 [this](Instruction *I) { notifyEraseInstr(I); });
367 MoveInstrCB = Ctx.registerMoveInstrCallback(
368 [this](Instruction *I, const BBIterator &To) {
369 notifyMoveInstr(I, To);
370 });
371 }
373 if (CreateInstrCB)
374 Ctx->unregisterCreateInstrCallback(*CreateInstrCB);
375 if (EraseInstrCB)
376 Ctx->unregisterEraseInstrCallback(*EraseInstrCB);
377 if (MoveInstrCB)
378 Ctx->unregisterMoveInstrCallback(*MoveInstrCB);
379 }
380
382 auto It = InstrToNodeMap.find(I);
383 return It != InstrToNodeMap.end() ? It->second.get() : nullptr;
384 }
385 /// Like getNode() but returns nullptr if \p I is nullptr.
387 if (I == nullptr)
388 return nullptr;
389 return getNode(I);
390 }
392 auto [It, NotInMap] = InstrToNodeMap.try_emplace(I);
393 if (NotInMap) {
395 It->second = std::make_unique<MemDGNode>(I);
396 else
397 It->second = std::make_unique<DGNode>(I);
398 }
399 return It->second.get();
400 }
401 /// Build/extend the dependency graph such that it includes \p Instrs. Returns
402 /// the range of instructions added to the DAG.
404 /// \Returns the range of instructions included in the DAG.
405 Interval<Instruction> getInterval() const { return DAGInterval; }
406 void clear() {
407 InstrToNodeMap.clear();
408 DAGInterval = {};
409 }
410#ifndef NDEBUG
411 void print(raw_ostream &OS) const;
412 LLVM_DUMP_METHOD void dump() const;
413#endif // NDEBUG
414};
415
416} // namespace llvm::sandboxir
417
418#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
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:700
void unregisterCreateInstrCallback(CallbackID ID)
Definition: Context.cpp:707
void unregisterMoveInstrCallback(CallbackID ID)
Definition: Context.cpp:720
void unregisterEraseInstrCallback(CallbackID ID)
Definition: Context.cpp:693
CallbackID registerMoveInstrCallback(MoveInstrCallback CB)
Register a callback that gets called when a SandboxIR instruction is about to be moved.
Definition: Context.cpp:713
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:686
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.
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
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:65
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.
static User::op_iterator skipNonInstr(User::op_iterator OpIt, User::op_iterator OpItE)
While OpIt points to a Value that is not an Instruction keep incrementing it.
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