LLVM 22.0.0git
VPlanVerifier.cpp
Go to the documentation of this file.
1//===-- VPlanVerifier.cpp -------------------------------------------------===//
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/// \file
10/// This file defines the class VPlanVerifier, which contains utility functions
11/// to check the consistency and invariants of a VPlan.
12///
13//===----------------------------------------------------------------------===//
14
15#include "VPlanVerifier.h"
16#include "VPlan.h"
17#include "VPlanCFG.h"
18#include "VPlanDominatorTree.h"
19#include "VPlanHelpers.h"
20#include "VPlanPatternMatch.h"
22#include "llvm/ADT/TypeSwitch.h"
23
24#define DEBUG_TYPE "loop-vectorize"
25
26using namespace llvm;
27using namespace VPlanPatternMatch;
28
29namespace {
30class VPlanVerifier {
31 const VPDominatorTree &VPDT;
32 VPTypeAnalysis &TypeInfo;
33 bool VerifyLate;
34
36
37 // Verify that phi-like recipes are at the beginning of \p VPBB, with no
38 // other recipes in between. Also check that only header blocks contain
39 // VPHeaderPHIRecipes.
40 bool verifyPhiRecipes(const VPBasicBlock *VPBB);
41
42 /// Verify that \p EVL is used correctly. The user must be either in
43 /// EVL-based recipes as a last operand or VPInstruction::Add which is
44 /// incoming value into EVL's recipe.
45 bool verifyEVLRecipe(const VPInstruction &EVL) const;
46
47 bool verifyVPBasicBlock(const VPBasicBlock *VPBB);
48
49 bool verifyBlock(const VPBlockBase *VPB);
50
51 /// Helper function that verifies the CFG invariants of the VPBlockBases
52 /// within
53 /// \p Region. Checks in this function are generic for VPBlockBases. They are
54 /// not specific for VPBasicBlocks or VPRegionBlocks.
55 bool verifyBlocksInRegion(const VPRegionBlock *Region);
56
57 /// Verify the CFG invariants of VPRegionBlock \p Region and its nested
58 /// VPBlockBases. Do not recurse inside nested VPRegionBlocks.
59 bool verifyRegion(const VPRegionBlock *Region);
60
61 /// Verify the CFG invariants of VPRegionBlock \p Region and its nested
62 /// VPBlockBases. Recurse inside nested VPRegionBlocks.
63 bool verifyRegionRec(const VPRegionBlock *Region);
64
65public:
66 VPlanVerifier(VPDominatorTree &VPDT, VPTypeAnalysis &TypeInfo,
67 bool VerifyLate)
68 : VPDT(VPDT), TypeInfo(TypeInfo), VerifyLate(VerifyLate) {}
69
70 bool verify(const VPlan &Plan);
71};
72} // namespace
73
74bool VPlanVerifier::verifyPhiRecipes(const VPBasicBlock *VPBB) {
75 auto RecipeI = VPBB->begin();
76 auto End = VPBB->end();
77 unsigned NumActiveLaneMaskPhiRecipes = 0;
78 bool IsHeaderVPBB = VPBlockUtils::isHeader(VPBB, VPDT);
79 while (RecipeI != End && RecipeI->isPhi()) {
81 NumActiveLaneMaskPhiRecipes++;
82
83 if (IsHeaderVPBB &&
85 errs() << "Found non-header PHI recipe in header VPBB";
86#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
87 errs() << ": ";
88 RecipeI->dump();
89#endif
90 return false;
91 }
92
93 if (!IsHeaderVPBB && isa<VPHeaderPHIRecipe>(*RecipeI)) {
94 errs() << "Found header PHI recipe in non-header VPBB";
95#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
96 errs() << ": ";
97 RecipeI->dump();
98#endif
99 return false;
100 }
101
102 // Check if the recipe operands match the number of predecessors.
103 // TODO Extend to other phi-like recipes.
104 if (auto *PhiIRI = dyn_cast<VPIRPhi>(&*RecipeI)) {
105 if (PhiIRI->getNumOperands() != VPBB->getNumPredecessors()) {
106 errs() << "Phi-like recipe with different number of operands and "
107 "predecessors.\n";
108 // TODO: Print broken recipe. At the moment printing an ill-formed
109 // phi-like recipe may crash.
110 return false;
111 }
112 }
113
114 RecipeI++;
115 }
116
117 if (!VerifyLate && NumActiveLaneMaskPhiRecipes > 1) {
118 errs() << "There should be no more than one VPActiveLaneMaskPHIRecipe";
119 return false;
120 }
121
122 while (RecipeI != End) {
123 if (RecipeI->isPhi() && !isa<VPBlendRecipe>(&*RecipeI)) {
124 errs() << "Found phi-like recipe after non-phi recipe";
125
126#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
127 errs() << ": ";
128 RecipeI->dump();
129 errs() << "after\n";
130 std::prev(RecipeI)->dump();
131#endif
132 return false;
133 }
134 RecipeI++;
135 }
136 return true;
137}
138
139bool VPlanVerifier::verifyEVLRecipe(const VPInstruction &EVL) const {
141 errs() << "verifyEVLRecipe should only be called on "
142 "VPInstruction::ExplicitVectorLength\n";
143 return false;
144 }
145 auto VerifyEVLUse = [&](const VPRecipeBase &R,
146 const unsigned ExpectedIdx) -> bool {
148 unsigned UseCount = count(Ops, &EVL);
149 if (UseCount != 1 || Ops[ExpectedIdx] != &EVL) {
150 errs() << "EVL is used as non-last operand in EVL-based recipe\n";
151 return false;
152 }
153 return true;
154 };
155 return all_of(EVL.users(), [this, &VerifyEVLUse](VPUser *U) {
156 return TypeSwitch<const VPUser *, bool>(U)
157 .Case<VPWidenIntrinsicRecipe>([&](const VPWidenIntrinsicRecipe *S) {
158 return VerifyEVLUse(*S, S->getNumOperands() - 1);
159 })
160 .Case<VPWidenStoreEVLRecipe, VPReductionEVLRecipe,
161 VPWidenIntOrFpInductionRecipe, VPWidenPointerInductionRecipe>(
162 [&](const VPRecipeBase *S) { return VerifyEVLUse(*S, 2); })
163 .Case<VPScalarIVStepsRecipe>([&](auto *R) {
164 if (R->getNumOperands() != 3) {
165 errs() << "Unrolling with EVL tail folding not yet supported\n";
166 return false;
167 }
168 return VerifyEVLUse(*R, 2);
169 })
170 .Case<VPWidenLoadEVLRecipe, VPVectorEndPointerRecipe,
171 VPInterleaveEVLRecipe>(
172 [&](const VPRecipeBase *R) { return VerifyEVLUse(*R, 1); })
173 .Case<VPInstructionWithType>(
174 [&](const VPInstructionWithType *S) { return VerifyEVLUse(*S, 0); })
175 .Case<VPInstruction>([&](const VPInstruction *I) {
176 if (I->getOpcode() == Instruction::PHI ||
177 I->getOpcode() == Instruction::ICmp ||
178 I->getOpcode() == Instruction::Sub)
179 return VerifyEVLUse(*I, 1);
180 switch (I->getOpcode()) {
181 case Instruction::Add:
182 break;
183 case Instruction::UIToFP:
184 case Instruction::Trunc:
185 case Instruction::ZExt:
186 case Instruction::Mul:
187 case Instruction::FMul:
189 // Opcodes above can only use EVL after wide inductions have been
190 // expanded.
191 if (!VerifyLate) {
192 errs() << "EVL used by unexpected VPInstruction\n";
193 return false;
194 }
195 break;
196 default:
197 errs() << "EVL used by unexpected VPInstruction\n";
198 return false;
199 }
200 // EVLIVIncrement is only used by EVLIV & BranchOnCount.
201 // Having more than two users is unexpected.
202 if (I->getOpcode() != VPInstruction::Broadcast &&
203 I->getNumUsers() != 1 &&
204 (I->getNumUsers() != 2 ||
206 m_VPValue()))))) {
207 errs() << "EVL is used in VPInstruction with multiple users\n";
208 return false;
209 }
210 if (!VerifyLate && !isa<VPEVLBasedIVPHIRecipe>(*I->users().begin())) {
211 errs() << "Result of VPInstruction::Add with EVL operand is "
212 "not used by VPEVLBasedIVPHIRecipe\n";
213 return false;
214 }
215 return true;
216 })
217 .Default([&](const VPUser *U) {
218 errs() << "EVL has unexpected user\n";
219 return false;
220 });
221 });
222}
223
224bool VPlanVerifier::verifyVPBasicBlock(const VPBasicBlock *VPBB) {
225 if (!verifyPhiRecipes(VPBB))
226 return false;
227
228 // Verify that defs in VPBB dominate all their uses.
229 DenseMap<const VPRecipeBase *, unsigned> RecipeNumbering;
230 unsigned Cnt = 0;
231 for (const VPRecipeBase &R : *VPBB)
232 RecipeNumbering[&R] = Cnt++;
233
234 for (const VPRecipeBase &R : *VPBB) {
235 if (isa<VPIRInstruction>(&R) && !isa<VPIRBasicBlock>(VPBB)) {
236 errs() << "VPIRInstructions ";
237#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
238 R.dump();
239 errs() << " ";
240#endif
241 errs() << "not in a VPIRBasicBlock!\n";
242 return false;
243 }
244 for (const VPValue *V : R.definedValues()) {
245 // Verify that we can infer a scalar type for each defined value. With
246 // assertions enabled, inferScalarType will perform some consistency
247 // checks during type inference.
248 if (!TypeInfo.inferScalarType(V)) {
249 errs() << "Failed to infer scalar type!\n";
250 return false;
251 }
252
253 for (const VPUser *U : V->users()) {
254 auto *UI = cast<VPRecipeBase>(U);
255 if (isa<VPIRPhi>(UI) &&
256 UI->getNumOperands() != UI->getParent()->getNumPredecessors()) {
257 errs() << "Phi-like recipe with different number of operands and "
258 "predecessors.\n";
259 return false;
260 }
261
262 if (auto *Phi = dyn_cast<VPPhiAccessors>(UI)) {
263 for (const auto &[IncomingVPV, IncomingVPBB] :
264 Phi->incoming_values_and_blocks()) {
265 if (IncomingVPV != V)
266 continue;
267
268 if (VPDT.dominates(VPBB, IncomingVPBB))
269 continue;
270
271 errs() << "Incoming def does not dominate incoming block!\n";
272#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
273 VPSlotTracker Tracker(VPBB->getPlan());
274 IncomingVPV->getDefiningRecipe()->print(errs(), " ", Tracker);
275 errs() << "\n does not dominate " << IncomingVPBB->getName()
276 << " for\n";
277 UI->print(errs(), " ", Tracker);
278#endif
279 return false;
280 }
281 continue;
282 }
283 // TODO: Also verify VPPredInstPHIRecipe.
285 continue;
286
287 // If the user is in the same block, check it comes after R in the
288 // block.
289 if (UI->getParent() == VPBB) {
290 if (RecipeNumbering[UI] >= RecipeNumbering[&R])
291 continue;
292 } else {
293 if (VPDT.dominates(VPBB, UI->getParent()))
294 continue;
295 }
296
297 errs() << "Use before def!\n";
298#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP)
299 VPSlotTracker Tracker(VPBB->getPlan());
300 UI->print(errs(), " ", Tracker);
301 errs() << "\n before\n";
302 R.print(errs(), " ", Tracker);
303 errs() << "\n";
304#endif
305 return false;
306 }
307 }
308 if (const auto *VPI = dyn_cast<VPInstruction>(&R)) {
309 switch (VPI->getOpcode()) {
311 if (!verifyEVLRecipe(*VPI)) {
312 errs() << "EVL VPValue is not used correctly\n";
313 return false;
314 }
315 break;
316 default:
317 break;
318 }
319 }
320 }
321
322 auto *IRBB = dyn_cast<VPIRBasicBlock>(VPBB);
323 if (!IRBB)
324 return true;
325
326 if (!WrappedIRBBs.insert(IRBB->getIRBasicBlock()).second) {
327 errs() << "Same IR basic block used by multiple wrapper blocks!\n";
328 return false;
329 }
330
331 return true;
332}
333
334/// Utility function that checks whether \p VPBlockVec has duplicate
335/// VPBlockBases.
336static bool hasDuplicates(const SmallVectorImpl<VPBlockBase *> &VPBlockVec) {
338 for (const auto *Block : VPBlockVec) {
339 if (!VPBlockSet.insert(Block).second)
340 return true;
341 }
342 return false;
343}
344
345bool VPlanVerifier::verifyBlock(const VPBlockBase *VPB) {
346 auto *VPBB = dyn_cast<VPBasicBlock>(VPB);
347 // Check block's condition bit.
348 if (!isa<VPIRBasicBlock>(VPB)) {
349 if (VPB->getNumSuccessors() > 1 ||
350 (VPBB && VPBB->getParent() && VPBB->isExiting() &&
351 !VPBB->getParent()->isReplicator())) {
352 if (!VPBB || !VPBB->getTerminator()) {
353 errs() << "Block has multiple successors but doesn't "
354 "have a proper branch recipe!\n";
355 return false;
356 }
357 } else {
358 if (VPBB && VPBB->getTerminator()) {
359 errs() << "Unexpected branch recipe!\n";
360 return false;
361 }
362 }
363 }
364
365 // Check block's successors.
366 const auto &Successors = VPB->getSuccessors();
367 // There must be only one instance of a successor in block's successor list.
368 // TODO: This won't work for switch statements.
369 if (hasDuplicates(Successors)) {
370 errs() << "Multiple instances of the same successor.\n";
371 return false;
372 }
373
374 for (const VPBlockBase *Succ : Successors) {
375 // There must be a bi-directional link between block and successor.
376 const auto &SuccPreds = Succ->getPredecessors();
377 if (!is_contained(SuccPreds, VPB)) {
378 errs() << "Missing predecessor link.\n";
379 return false;
380 }
381 }
382
383 // Check block's predecessors.
384 const auto &Predecessors = VPB->getPredecessors();
385 // There must be only one instance of a predecessor in block's predecessor
386 // list.
387 // TODO: This won't work for switch statements.
388 if (hasDuplicates(Predecessors)) {
389 errs() << "Multiple instances of the same predecessor.\n";
390 return false;
391 }
392
393 for (const VPBlockBase *Pred : Predecessors) {
394 // Block and predecessor must be inside the same region.
395 if (Pred->getParent() != VPB->getParent()) {
396 errs() << "Predecessor is not in the same region.\n";
397 return false;
398 }
399
400 // There must be a bi-directional link between block and predecessor.
401 const auto &PredSuccs = Pred->getSuccessors();
402 if (!is_contained(PredSuccs, VPB)) {
403 errs() << "Missing successor link.\n";
404 return false;
405 }
406 }
407 return !VPBB || verifyVPBasicBlock(VPBB);
408}
409
410bool VPlanVerifier::verifyBlocksInRegion(const VPRegionBlock *Region) {
411 for (const VPBlockBase *VPB : vp_depth_first_shallow(Region->getEntry())) {
412 // Check block's parent.
413 if (VPB->getParent() != Region) {
414 errs() << "VPBlockBase has wrong parent\n";
415 return false;
416 }
417
418 if (!verifyBlock(VPB))
419 return false;
420 }
421 return true;
422}
423
424bool VPlanVerifier::verifyRegion(const VPRegionBlock *Region) {
425 const VPBlockBase *Entry = Region->getEntry();
426 const VPBlockBase *Exiting = Region->getExiting();
427
428 // Entry and Exiting shouldn't have any predecessor/successor, respectively.
429 if (Entry->hasPredecessors()) {
430 errs() << "region entry block has predecessors\n";
431 return false;
432 }
433 if (Exiting->getNumSuccessors() != 0) {
434 errs() << "region exiting block has successors\n";
435 return false;
436 }
437
438 return verifyBlocksInRegion(Region);
439}
440
441bool VPlanVerifier::verifyRegionRec(const VPRegionBlock *Region) {
442 // Recurse inside nested regions and check all blocks inside the region.
443 return verifyRegion(Region) &&
445 [this](const VPBlockBase *VPB) {
446 const auto *SubRegion = dyn_cast<VPRegionBlock>(VPB);
447 return !SubRegion || verifyRegionRec(SubRegion);
448 });
449}
450
451bool VPlanVerifier::verify(const VPlan &Plan) {
453 [this](const VPBlockBase *VPB) { return !verifyBlock(VPB); }))
454 return false;
455
456 const VPRegionBlock *TopRegion = Plan.getVectorLoopRegion();
457 // TODO: Verify all blocks using vp_depth_first_deep iterators.
458 if (!TopRegion)
459 return true;
460
461 if (!verifyRegionRec(TopRegion))
462 return false;
463
464 if (TopRegion->getParent()) {
465 errs() << "VPlan Top Region should have no parent.\n";
466 return false;
467 }
468
469 const VPBasicBlock *Entry = dyn_cast<VPBasicBlock>(TopRegion->getEntry());
470 if (!Entry) {
471 errs() << "VPlan entry block is not a VPBasicBlock\n";
472 return false;
473 }
474
475 if (!isa<VPCanonicalIVPHIRecipe>(&*Entry->begin())) {
476 errs() << "VPlan vector loop header does not start with a "
477 "VPCanonicalIVPHIRecipe\n";
478 return false;
479 }
480
481 const VPBasicBlock *Exiting = dyn_cast<VPBasicBlock>(TopRegion->getExiting());
482 if (!Exiting) {
483 errs() << "VPlan exiting block is not a VPBasicBlock\n";
484 return false;
485 }
486
487 if (Exiting->empty()) {
488 errs() << "VPlan vector loop exiting block must end with BranchOnCount or "
489 "BranchOnCond VPInstruction but is empty\n";
490 return false;
491 }
492
493 auto *LastInst = dyn_cast<VPInstruction>(std::prev(Exiting->end()));
494 if (!match(LastInst, m_CombineOr(m_BranchOnCond(), m_BranchOnCount()))) {
495 errs() << "VPlan vector loop exit must end with BranchOnCount or "
496 "BranchOnCond VPInstruction\n";
497 return false;
498 }
499
500 return true;
501}
502
503bool llvm::verifyVPlanIsValid(const VPlan &Plan, bool VerifyLate) {
504 VPDominatorTree VPDT(const_cast<VPlan &>(Plan));
505 VPTypeAnalysis TypeInfo(Plan);
506 VPlanVerifier Verifier(VPDT, TypeInfo, VerifyLate);
507 return Verifier.verify(Plan);
508}
@ Default
const AbstractManglingParser< Derived, Alloc >::OperatorInfo AbstractManglingParser< Derived, Alloc >::Ops[]
#define I(x, y, z)
Definition MD5.cpp:58
ppc ctr loops verify
verify safepoint Safepoint IR Verifier
This file defines the SmallPtrSet class.
This file implements the TypeSwitch template, which mimics a switch() statement whose cases are type ...
This file implements dominator tree analysis for a single level of a VPlan's H-CFG.
This file contains the declarations of different VPlan-related auxiliary helpers.
static bool hasDuplicates(const SmallVectorImpl< VPBlockBase * > &VPBlockVec)
Utility function that checks whether VPBlockVec has duplicate VPBlockBases.
This file declares the class VPlanVerifier, which contains utility functions to check the consistency...
This file contains the declarations of the Vectorization Plan base classes:
bool dominates(const DomTreeNodeBase< NodeT > *A, const DomTreeNodeBase< NodeT > *B) const
dominates - Returns true iff A dominates B.
Implements a dense probed hash-table based set with some number of buckets stored inline.
Definition DenseSet.h:291
std::pair< iterator, bool > insert(PtrType Ptr)
Inserts Ptr if and only if there is no element in the container equal to Ptr.
SmallPtrSet - This class implements a set which is optimized for holding SmallSize or less elements.
This class consists of common code factored out of the SmallVector class to reduce code duplication b...
VPBasicBlock serves as the leaf of the Hierarchical Control-Flow Graph.
Definition VPlan.h:3821
iterator end()
Definition VPlan.h:3858
iterator begin()
Recipe iterator methods.
Definition VPlan.h:3856
bool empty() const
Definition VPlan.h:3867
VPBlockBase is the building block of the Hierarchical Control-Flow Graph.
Definition VPlan.h:80
VPRegionBlock * getParent()
Definition VPlan.h:172
size_t getNumSuccessors() const
Definition VPlan.h:218
size_t getNumPredecessors() const
Definition VPlan.h:219
const VPBlocksTy & getPredecessors() const
Definition VPlan.h:203
const VPBlocksTy & getSuccessors() const
Definition VPlan.h:197
static bool isHeader(const VPBlockBase *VPB, const VPDominatorTree &VPDT)
Returns true if VPB is a loop header, based on regions or VPDT in their absence.
Template specialization of the standard LLVM dominator tree utility for VPBlockBases.
This is a concrete Recipe that models a single VPlan-level instruction.
Definition VPlan.h:976
unsigned getOpcode() const
Definition VPlan.h:1120
VPRegionBlock represents a collection of VPBasicBlocks and VPRegionBlocks which form a Single-Entry-S...
Definition VPlan.h:4009
const VPBlockBase * getEntry() const
Definition VPlan.h:4045
const VPBlockBase * getExiting() const
Definition VPlan.h:4057
An analysis for type-inference for VPValues.
Type * inferScalarType(const VPValue *V)
Infer the type of V. Returns the scalar type of V.
user_range users()
Definition VPlanValue.h:134
VPlan models a candidate for vectorization, encoding various decisions take to produce efficient outp...
Definition VPlan.h:4139
VPBasicBlock * getEntry()
Definition VPlan.h:4233
LLVM_ABI_FOR_TEST VPRegionBlock * getVectorLoopRegion()
Returns the VPRegionBlock of the vector loop.
Definition VPlan.cpp:1027
std::pair< iterator, bool > insert(const ValueT &V)
Definition DenseSet.h:202
@ Entry
Definition COFF.h:862
bool match(Val *V, const Pattern &P)
specificval_ty m_Specific(const Value *V)
Match if we have a specific specified value.
MatchFunctor< Val, Pattern > match_fn(const Pattern &P)
A match functor that can be used as a UnaryPredicate in functional algorithms like all_of.
match_combine_or< LTy, RTy > m_CombineOr(const LTy &L, const RTy &R)
Combine two pattern matchers matching L || R.
VPInstruction_match< VPInstruction::BranchOnCount > m_BranchOnCount()
class_match< VPValue > m_VPValue()
Match an arbitrary VPValue and ignore it.
VPInstruction_match< VPInstruction::BranchOnCond > m_BranchOnCond()
NodeAddr< PhiNode * > Phi
Definition RDFGraph.h:390
This is an optimization pass for GlobalISel generic memory operations.
bool all_of(R &&range, UnaryPredicate P)
Provide wrappers to std::all_of which take ranges instead of having to pass begin/end explicitly.
Definition STLExtras.h:1725
LLVM_ABI_FOR_TEST bool verifyVPlanIsValid(const VPlan &Plan, bool VerifyLate=false)
Verify invariants for general VPlans.
decltype(auto) dyn_cast(const From &Val)
dyn_cast<X> - Return the argument parameter cast to the specified type.
Definition Casting.h:643
iterator_range< df_iterator< VPBlockShallowTraversalWrapper< VPBlockBase * > > > vp_depth_first_shallow(VPBlockBase *G)
Returns an iterator range to traverse the graph starting at G in depth-first order.
Definition VPlanCFG.h:216
bool any_of(R &&range, UnaryPredicate P)
Provide wrappers to std::any_of which take ranges instead of having to pass begin/end explicitly.
Definition STLExtras.h:1732
bool none_of(R &&Range, UnaryPredicate P)
Provide wrappers to std::none_of which take ranges instead of having to pass begin/end explicitly.
Definition STLExtras.h:1739
class LLVM_GSL_OWNER SmallVector
Forward declaration of SmallVector so that calculateSmallVectorDefaultInlinedElements can reference s...
bool isa(const From &Val)
isa<X> - Return true if the parameter to the template is an instance of one of the template type argu...
Definition Casting.h:547
LLVM_ABI raw_fd_ostream & errs()
This returns a reference to a raw_ostream for standard error.
auto count(R &&Range, const E &Element)
Wrapper function around std::count to count the number of times an element Element occurs in the give...
Definition STLExtras.h:1954
decltype(auto) cast(const From &Val)
cast<X> - Return the argument parameter cast to the specified type.
Definition Casting.h:559
bool is_contained(R &&Range, const E &Element)
Returns true if Element is found in Range.
Definition STLExtras.h:1897