LLVM 22.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"
32
33namespace llvm::sandboxir {
34
35class DependencyGraph;
36class MemDGNode;
37class SchedBundle;
38
39/// SubclassIDs for isa/dyn_cast etc.
40enum class DGNodeID {
43};
44
45class DGNode;
46class MemDGNode;
47class DependencyGraph;
48
49// Defined in Transforms/Vectorize/SandboxVectorizer/Interval.cpp
50extern template class LLVM_TEMPLATE_ABI Interval<MemDGNode>;
51
52/// Iterate over both def-use and mem dependencies.
53class PredIterator {
57 DGNode *N = nullptr;
58 DependencyGraph *DAG = nullptr;
59
60 PredIterator(const User::op_iterator &OpIt, const User::op_iterator &OpItE,
62 DependencyGraph &DAG)
63 : OpIt(OpIt), OpItE(OpItE), MemIt(MemIt), N(N), DAG(&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,
74 const DependencyGraph &DAG);
75
76public:
77 using difference_type = std::ptrdiff_t;
78 using value_type = DGNode *;
81 using iterator_category = std::input_iterator_tag;
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.
96protected:
98 // TODO: Use a PointerIntPair for SubclassID and I.
99 /// For isa/dyn_cast etc.
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
109 void clearSchedBundle() { this->SB = nullptr; }
110 friend class SchedBundle; // For setSchedBundle(), clearSchedBundle().
111
113 friend class MemDGNode; // For constructor.
114 friend class DependencyGraph; // For UnscheduledSuccs
115
116public:
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.
123 unsigned getNumUnscheduledSuccs() const { return UnscheduledSuccs; }
124 // TODO: Make this private?
126 assert(UnscheduledSuccs > 0 && "Counting error!");
128 }
132 Scheduled = false;
133 }
134 /// \Returns true if all dependent successors have been scheduled.
135 bool ready() const { return UnscheduledSuccs == 0; }
136 /// \Returns true if this node has been scheduled.
137 bool scheduled() const { return Scheduled; }
138 void setScheduled(bool NewVal) { Scheduled = NewVal; }
139 /// \Returns the scheduling bundle that this node belongs to, or nullptr.
140 SchedBundle *getSchedBundle() const { return SB; }
141 /// \Returns true if this is before \p Other in program order.
142 bool comesBefore(const DGNode *Other) { return I->comesBefore(Other->I); }
145 return PredIterator(
146 PredIterator::skipBadIt(I->op_begin(), I->op_end(), DAG), I->op_end(),
147 this, DAG);
148 }
150 return PredIterator(I->op_end(), I->op_end(), this, DAG);
151 }
153 return const_cast<DGNode *>(this)->preds_begin(DAG);
154 }
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.
165
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.
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.
186 return I->mayReadOrWriteMemory() &&
188 }
189
190 /// \Returns true if \p I is fence like. It excludes non-mem intrinsics.
191 static bool isFenceLike(Instruction *I) {
193 return I->isFenceLike() &&
195 }
196
197 /// \Returns true if \p I is a memory dependency candidate instruction.
199 AllocaInst *Alloca;
200 return isMemDepCandidate(I) ||
201 ((Alloca = dyn_cast<AllocaInst>(I)) &&
202 Alloca->isUsedWithInAlloca()) ||
204 }
205
206 Instruction *getInstruction() const { return I; }
207
208#ifndef NDEBUG
209 virtual void print(raw_ostream &OS, bool PrintDeps = true) const;
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.
221class 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.
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.
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().
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
253public:
255 assert(isMemDepNodeCandidate(I) && "Expected Mem instruction!");
256 }
257 static bool classof(const DGNode *Other) {
258 return Other->SubclassID == DGNodeID::MemDGNode;
259 }
261 auto OpEndIt = I->op_end();
262 return PredIterator(PredIterator::skipBadIt(I->op_begin(), OpEndIt, DAG),
263 OpEndIt, MemPreds.begin(), this, DAG);
264 }
266 return PredIterator(I->op_end(), I->op_end(), MemPreds.end(), this, DAG);
267 }
268 /// \Returns the previous Mem DGNode in instruction order.
269 MemDGNode *getPrevNode() const { return PrevMemN; }
270 /// \Returns the next Mem DGNode in instruction order.
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.
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.
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.
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.
301 return make_range(MemPreds.begin(), MemPreds.end());
302 }
303 /// \Returns all memory dependency successors.
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.
314public:
315 /// Scans the instruction chain in \p Intvl top-down, returning the top-most
316 /// MemDGNode, or nullptr.
318 const DependencyGraph &DAG);
319 /// Scans the instruction chain in \p Intvl bottom-up, returning the
320 /// bottom-most MemDGNode, or nullptr.
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.
327 DependencyGraph &DAG);
328 static Interval<MemDGNode> makeEmpty() { return {}; }
329};
330
332private:
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
400public:
401 /// This constructor also registers callbacks.
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 }
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
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.
432 if (I == nullptr)
433 return nullptr;
434 return getNode(I);
435 }
437 auto [It, NotInMap] = InstrToNodeMap.try_emplace(I);
438 if (NotInMap) {
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.
449 /// \Returns the range of instructions included in the DAG.
450 Interval<Instruction> getInterval() const { return DAGInterval; }
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.
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
assert(UImm &&(UImm !=~static_cast< T >(0)) &&"Invalid immediate!")
#define LLVM_ABI
Definition Compiler.h:213
#define LLVM_TEMPLATE_ABI
Definition Compiler.h:214
#define LLVM_DUMP_METHOD
Mark debug helper function definitions like dump() that should not be stripped from debug builds.
Definition Compiler.h:638
This file defines the DenseMap class.
#define I(x, y, z)
Definition MD5.cpp:58
std::pair< uint64_t, uint64_t > Interval
uint64_t IntrinsicInst * II
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.
Implements a dense probed hash-table based set.
Definition DenseSet.h:261
DenseSetIterator< false > iterator
Definition DenseSet.h:154
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:53
bool isUsedWithInAlloca() const
Return true if this alloca is used as an inalloca argument to a call.
A DependencyGraph Node that points to an Instruction and contains memory dependency edges.
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.
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)
LLVM_ABI 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:43
Convenience builders for a MemDGNode interval.
static LLVM_ABI 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 LLVM_ABI 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 LLVM_ABI 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.
iterator_range< DenseSet< MemDGNode * >::const_iterator > memSuccs() const
\Returns all memory dependency successors.
void removeMemPred(MemDGNode *PredN)
Removes the memory dependency PredN->this.
MemDGNode * getPrevNode() const
\Returns the previous Mem DGNode in instruction order.
void addMemPred(MemDGNode *PredN)
Adds the mem dependency edge PredN->this.
Iterate over both def-use and mem dependencies.
bool operator!=(const PredIterator &Other) const
LLVM_ABI PredIterator & operator++()
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:108
Represents a Def-use/Use-def edge in SandboxIR.
Definition Use.h:33
OperandUseIterator op_iterator
Definition User.h:98
A SandboxIR Value has users. This is the base class.
Definition Value.h:66
This provides a very simple, boring adaptor for a begin and end iterator into a range type.
Abstract Attribute helper functions.
Definition Attributor.h:165
unsigned ID
LLVM IR allows to use arbitrary numbers as calling convention identifiers.
Definition CallingConv.h:24
DGNodeID
SubclassIDs for isa/dyn_cast etc.
void dump(const SparseBitVector< ElementSize > &LHS, raw_ostream &out)
Printable print(const GCNRegPressure &RP, const GCNSubtarget *ST=nullptr, unsigned DynamicVGPRBlockSize=0)
decltype(auto) dyn_cast(const From &Val)
dyn_cast<X> - Return the argument parameter cast to the specified type.
Definition Casting.h:649
APInt operator*(APInt a, uint64_t RHS)
Definition APInt.h:2235
iterator_range< T > make_range(T x, T y)
Convenience function for iterating over sub-ranges.
bool operator==(const AddressRangeValuePair &LHS, const AddressRangeValuePair &RHS)
@ Other
Any other memory.
Definition ModRef.h:68
Implement std::hash so that hash_code can be used in STL containers.
Definition BitVector.h:851
#define N