CIRCT 21.0.0git
Loading...
Searching...
No Matches
ElaborationPass.cpp
Go to the documentation of this file.
1//===- ElaborationPass.cpp - RTG ElaborationPass implementation -----------===//
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 pass elaborates the random parts of the RTG dialect.
10// It performs randomization top-down, i.e., random constructs in a sequence
11// that is invoked multiple times can yield different randomization results
12// for each invokation.
13//
14//===----------------------------------------------------------------------===//
15
21#include "mlir/Dialect/Arith/IR/Arith.h"
22#include "mlir/Dialect/Index/IR/IndexDialect.h"
23#include "mlir/Dialect/Index/IR/IndexOps.h"
24#include "mlir/Dialect/SCF/IR/SCF.h"
25#include "mlir/IR/IRMapping.h"
26#include "mlir/IR/PatternMatch.h"
27#include "llvm/ADT/DenseMapInfoVariant.h"
28#include "llvm/Support/Debug.h"
29#include <queue>
30#include <random>
31
32namespace circt {
33namespace rtg {
34#define GEN_PASS_DEF_ELABORATIONPASS
35#include "circt/Dialect/RTG/Transforms/RTGPasses.h.inc"
36} // namespace rtg
37} // namespace circt
38
39using namespace mlir;
40using namespace circt;
41using namespace circt::rtg;
42using llvm::MapVector;
43
44#define DEBUG_TYPE "rtg-elaboration"
45
46//===----------------------------------------------------------------------===//
47// Uniform Distribution Helper
48//
49// Simplified version of
50// https://github.com/llvm/llvm-project/blob/main/libcxx/include/__random/uniform_int_distribution.h
51// We use our custom version here to get the same results when compiled with
52// different compiler versions and standard libraries.
53//===----------------------------------------------------------------------===//
54
55static uint32_t computeMask(size_t w) {
56 size_t n = w / 32 + (w % 32 != 0);
57 size_t w0 = w / n;
58 return w0 > 0 ? uint32_t(~0) >> (32 - w0) : 0;
59}
60
61/// Get a number uniformly at random in the in specified range.
62static uint32_t getUniformlyInRange(std::mt19937 &rng, uint32_t a, uint32_t b) {
63 const uint32_t diff = b - a + 1;
64 if (diff == 1)
65 return a;
66
67 const uint32_t digits = std::numeric_limits<uint32_t>::digits;
68 if (diff == 0)
69 return rng();
70
71 uint32_t width = digits - llvm::countl_zero(diff) - 1;
72 if ((diff & (std::numeric_limits<uint32_t>::max() >> (digits - width))) != 0)
73 ++width;
74
75 uint32_t mask = computeMask(diff);
76 uint32_t u;
77 do {
78 u = rng() & mask;
79 } while (u >= diff);
80
81 return u + a;
82}
83
84//===----------------------------------------------------------------------===//
85// Elaborator Value
86//===----------------------------------------------------------------------===//
87
88namespace {
89struct ArrayStorage;
90struct BagStorage;
91struct SequenceStorage;
92struct RandomizedSequenceStorage;
93struct InterleavedSequenceStorage;
94struct SetStorage;
95struct VirtualRegisterStorage;
96struct UniqueLabelStorage;
97struct TupleStorage;
98struct MemoryBlockStorage;
99
100/// Simple wrapper around a 'StringAttr' such that we know to materialize it as
101/// a label declaration instead of calling the builtin dialect constant
102/// materializer.
103struct LabelValue {
104 LabelValue(StringAttr name) : name(name) {}
105
106 bool operator==(const LabelValue &other) const { return name == other.name; }
107
108 /// The label name.
109 StringAttr name;
110};
111
112/// The abstract base class for elaborated values.
113using ElaboratorValue =
114 std::variant<TypedAttr, BagStorage *, bool, size_t, SequenceStorage *,
115 RandomizedSequenceStorage *, InterleavedSequenceStorage *,
116 SetStorage *, VirtualRegisterStorage *, UniqueLabelStorage *,
117 LabelValue, ArrayStorage *, TupleStorage *,
118 MemoryBlockStorage *>;
119
120// NOLINTNEXTLINE(readability-identifier-naming)
121llvm::hash_code hash_value(const LabelValue &val) {
122 return llvm::hash_value(val.name);
123}
124
125// NOLINTNEXTLINE(readability-identifier-naming)
126llvm::hash_code hash_value(const ElaboratorValue &val) {
127 return std::visit(
128 [&val](const auto &alternative) {
129 // Include index in hash to make sure same value as different
130 // alternatives don't collide.
131 return llvm::hash_combine(val.index(), alternative);
132 },
133 val);
134}
135
136} // namespace
137
138namespace llvm {
139
140template <>
141struct DenseMapInfo<bool> {
142 static inline unsigned getEmptyKey() { return false; }
143 static inline unsigned getTombstoneKey() { return true; }
144 static unsigned getHashValue(const bool &val) { return val * 37U; }
145
146 static bool isEqual(const bool &lhs, const bool &rhs) { return lhs == rhs; }
147};
148template <>
149struct DenseMapInfo<LabelValue> {
150 static inline LabelValue getEmptyKey() {
152 }
153 static inline LabelValue getTombstoneKey() {
155 }
156 static unsigned getHashValue(const LabelValue &val) {
157 return hash_value(val);
158 }
159
160 static bool isEqual(const LabelValue &lhs, const LabelValue &rhs) {
161 return lhs == rhs;
162 }
163};
164
165} // namespace llvm
166
167//===----------------------------------------------------------------------===//
168// Elaborator Value Storages and Internalization
169//===----------------------------------------------------------------------===//
170
171namespace {
172
173/// Lightweight object to be used as the key for internalization sets. It caches
174/// the hashcode of the internalized object and a pointer to it. This allows a
175/// delayed allocation and construction of the actual object and thus only has
176/// to happen if the object is not already in the set.
177template <typename StorageTy>
178struct HashedStorage {
179 HashedStorage(unsigned hashcode = 0, StorageTy *storage = nullptr)
180 : hashcode(hashcode), storage(storage) {}
181
182 unsigned hashcode;
183 StorageTy *storage;
184};
185
186/// A DenseMapInfo implementation to support 'insert_as' for the internalization
187/// sets. When comparing two 'HashedStorage's we can just compare the already
188/// internalized storage pointers, otherwise we have to call the costly
189/// 'isEqual' method.
190template <typename StorageTy>
191struct StorageKeyInfo {
192 static inline HashedStorage<StorageTy> getEmptyKey() {
193 return HashedStorage<StorageTy>(0,
194 DenseMapInfo<StorageTy *>::getEmptyKey());
195 }
196 static inline HashedStorage<StorageTy> getTombstoneKey() {
197 return HashedStorage<StorageTy>(
198 0, DenseMapInfo<StorageTy *>::getTombstoneKey());
199 }
200
201 static inline unsigned getHashValue(const HashedStorage<StorageTy> &key) {
202 return key.hashcode;
203 }
204 static inline unsigned getHashValue(const StorageTy &key) {
205 return key.hashcode;
206 }
207
208 static inline bool isEqual(const HashedStorage<StorageTy> &lhs,
209 const HashedStorage<StorageTy> &rhs) {
210 return lhs.storage == rhs.storage;
211 }
212 static inline bool isEqual(const StorageTy &lhs,
213 const HashedStorage<StorageTy> &rhs) {
214 if (isEqual(rhs, getEmptyKey()) || isEqual(rhs, getTombstoneKey()))
215 return false;
216
217 return lhs.isEqual(rhs.storage);
218 }
219};
220
221/// Storage object for an '!rtg.set<T>'.
222struct SetStorage {
223 SetStorage(SetVector<ElaboratorValue> &&set, Type type)
224 : hashcode(llvm::hash_combine(
225 type, llvm::hash_combine_range(set.begin(), set.end()))),
226 set(std::move(set)), type(type) {}
227
228 bool isEqual(const SetStorage *other) const {
229 return hashcode == other->hashcode && set == other->set &&
230 type == other->type;
231 }
232
233 // The cached hashcode to avoid repeated computations.
234 const unsigned hashcode;
235
236 // Stores the elaborated values contained in the set.
237 const SetVector<ElaboratorValue> set;
238
239 // Store the set type such that we can materialize this evaluated value
240 // also in the case where the set is empty.
241 const Type type;
242};
243
244/// Storage object for an '!rtg.bag<T>'.
245struct BagStorage {
246 BagStorage(MapVector<ElaboratorValue, uint64_t> &&bag, Type type)
247 : hashcode(llvm::hash_combine(
248 type, llvm::hash_combine_range(bag.begin(), bag.end()))),
249 bag(std::move(bag)), type(type) {}
250
251 bool isEqual(const BagStorage *other) const {
252 return hashcode == other->hashcode && llvm::equal(bag, other->bag) &&
253 type == other->type;
254 }
255
256 // The cached hashcode to avoid repeated computations.
257 const unsigned hashcode;
258
259 // Stores the elaborated values contained in the bag with their number of
260 // occurences.
261 const MapVector<ElaboratorValue, uint64_t> bag;
262
263 // Store the bag type such that we can materialize this evaluated value
264 // also in the case where the bag is empty.
265 const Type type;
266};
267
268/// Storage object for an '!rtg.sequence'.
269struct SequenceStorage {
270 SequenceStorage(StringAttr familyName, SmallVector<ElaboratorValue> &&args)
271 : hashcode(llvm::hash_combine(
272 familyName, llvm::hash_combine_range(args.begin(), args.end()))),
273 familyName(familyName), args(std::move(args)) {}
274
275 bool isEqual(const SequenceStorage *other) const {
276 return hashcode == other->hashcode && familyName == other->familyName &&
277 args == other->args;
278 }
279
280 // The cached hashcode to avoid repeated computations.
281 const unsigned hashcode;
282
283 // The name of the sequence family this sequence is derived from.
284 const StringAttr familyName;
285
286 // The elaborator values used during substitution of the sequence family.
287 const SmallVector<ElaboratorValue> args;
288};
289
290/// Storage object for an '!rtg.randomized_sequence'.
291struct RandomizedSequenceStorage {
292 RandomizedSequenceStorage(StringRef name,
293 ContextResourceAttrInterface context,
294 StringAttr test, SequenceStorage *sequence)
295 : hashcode(llvm::hash_combine(name, context, test, sequence)), name(name),
296 context(context), test(test), sequence(sequence) {}
297
298 bool isEqual(const RandomizedSequenceStorage *other) const {
299 return hashcode == other->hashcode && name == other->name &&
300 context == other->context && test == other->test &&
301 sequence == other->sequence;
302 }
303
304 // The cached hashcode to avoid repeated computations.
305 const unsigned hashcode;
306
307 // The name of this fully substituted and elaborated sequence.
308 const StringRef name;
309
310 // The context under which this sequence is placed.
311 const ContextResourceAttrInterface context;
312
313 // The test in which this sequence is placed.
314 const StringAttr test;
315
316 const SequenceStorage *sequence;
317};
318
319/// Storage object for interleaved '!rtg.randomized_sequence'es.
320struct InterleavedSequenceStorage {
321 InterleavedSequenceStorage(SmallVector<ElaboratorValue> &&sequences,
322 uint32_t batchSize)
323 : sequences(std::move(sequences)), batchSize(batchSize),
324 hashcode(llvm::hash_combine(
325 llvm::hash_combine_range(sequences.begin(), sequences.end()),
326 batchSize)) {}
327
328 explicit InterleavedSequenceStorage(RandomizedSequenceStorage *sequence)
329 : sequences(SmallVector<ElaboratorValue>(1, sequence)), batchSize(1),
330 hashcode(llvm::hash_combine(
331 llvm::hash_combine_range(sequences.begin(), sequences.end()),
332 batchSize)) {}
333
334 bool isEqual(const InterleavedSequenceStorage *other) const {
335 return hashcode == other->hashcode && sequences == other->sequences &&
336 batchSize == other->batchSize;
337 }
338
339 const SmallVector<ElaboratorValue> sequences;
340
341 const uint32_t batchSize;
342
343 // The cached hashcode to avoid repeated computations.
344 const unsigned hashcode;
345};
346
347/// Represents a unique virtual register.
348struct VirtualRegisterStorage {
349 VirtualRegisterStorage(ArrayAttr allowedRegs) : allowedRegs(allowedRegs) {}
350
351 // NOTE: we don't need an 'isEqual' function and 'hashcode' here because
352 // VirtualRegisters are never internalized.
353
354 // The list of fixed registers allowed to be selected for this virtual
355 // register.
356 const ArrayAttr allowedRegs;
357};
358
359struct UniqueLabelStorage {
360 UniqueLabelStorage(StringAttr name) : name(name) {}
361
362 // NOTE: we don't need an 'isEqual' function and 'hashcode' here because
363 // VirtualRegisters are never internalized.
364
365 /// The label name. For unique labels, this is just the prefix.
366 const StringAttr name;
367};
368
369/// Storage object for '!rtg.array`-typed values.
370struct ArrayStorage {
371 ArrayStorage(Type type, SmallVector<ElaboratorValue> &&array)
372 : hashcode(llvm::hash_combine(
373 type, llvm::hash_combine_range(array.begin(), array.end()))),
374 type(type), array(array) {}
375
376 bool isEqual(const ArrayStorage *other) const {
377 return hashcode == other->hashcode && type == other->type &&
378 array == other->array;
379 }
380
381 // The cached hashcode to avoid repeated computations.
382 const unsigned hashcode;
383
384 /// The type of the array. This is necessary because an array of size 0
385 /// cannot be reconstructed without knowing the original element type.
386 const Type type;
387
388 /// The label name. For unique labels, this is just the prefix.
389 const SmallVector<ElaboratorValue> array;
390};
391
392/// Storage object for 'tuple`-typed values.
393struct TupleStorage {
394 TupleStorage(SmallVector<ElaboratorValue> &&values)
395 : hashcode(llvm::hash_combine_range(values.begin(), values.end())),
396 values(std::move(values)) {}
397
398 bool isEqual(const TupleStorage *other) const {
399 return hashcode == other->hashcode && values == other->values;
400 }
401
402 // The cached hashcode to avoid repeated computations.
403 const unsigned hashcode;
404
405 const SmallVector<ElaboratorValue> values;
406};
407
408/// Storage object for '!rtg.isa.memoryblock`-typed values.
409struct MemoryBlockStorage {
410 MemoryBlockStorage(const APInt &baseAddress, const APInt &endAddress)
411 : baseAddress(baseAddress), endAddress(endAddress) {}
412
413 // The base address of the memory. The width of the APInt also represents the
414 // address width of the memory. This is an APInt to support memories of
415 // >64-bit machines.
416 const APInt baseAddress;
417
418 // The last address of the memory.
419 const APInt endAddress;
420};
421
422/// An 'Internalizer' object internalizes storages and takes ownership of them.
423/// When the initializer object is destroyed, all owned storages are also
424/// deallocated and thus must not be accessed anymore.
425class Internalizer {
426public:
427 /// Internalize a storage of type `StorageTy` constructed with arguments
428 /// `args`. The pointers returned by this method can be used to compare
429 /// objects when, e.g., computing set differences, uniquing the elements in a
430 /// set, etc. Otherwise, we'd need to do a deep value comparison in those
431 /// situations.
432 template <typename StorageTy, typename... Args>
433 StorageTy *internalize(Args &&...args) {
434 StorageTy storage(std::forward<Args>(args)...);
435
436 auto existing = getInternSet<StorageTy>().insert_as(
437 HashedStorage<StorageTy>(storage.hashcode), storage);
438 StorageTy *&storagePtr = existing.first->storage;
439 if (existing.second)
440 storagePtr =
441 new (allocator.Allocate<StorageTy>()) StorageTy(std::move(storage));
442
443 return storagePtr;
444 }
445
446 template <typename StorageTy, typename... Args>
447 StorageTy *create(Args &&...args) {
448 return new (allocator.Allocate<StorageTy>())
449 StorageTy(std::forward<Args>(args)...);
450 }
451
452private:
453 template <typename StorageTy>
454 DenseSet<HashedStorage<StorageTy>, StorageKeyInfo<StorageTy>> &
455 getInternSet() {
456 if constexpr (std::is_same_v<StorageTy, ArrayStorage>)
457 return internedArrays;
458 else if constexpr (std::is_same_v<StorageTy, SetStorage>)
459 return internedSets;
460 else if constexpr (std::is_same_v<StorageTy, BagStorage>)
461 return internedBags;
462 else if constexpr (std::is_same_v<StorageTy, SequenceStorage>)
463 return internedSequences;
464 else if constexpr (std::is_same_v<StorageTy, RandomizedSequenceStorage>)
465 return internedRandomizedSequences;
466 else if constexpr (std::is_same_v<StorageTy, InterleavedSequenceStorage>)
467 return internedInterleavedSequences;
468 else if constexpr (std::is_same_v<StorageTy, TupleStorage>)
469 return internedTuples;
470 else
471 static_assert(!sizeof(StorageTy),
472 "no intern set available for this storage type.");
473 }
474
475 // This allocator allocates on the heap. It automatically deallocates all
476 // objects it allocated once the allocator itself is destroyed.
477 llvm::BumpPtrAllocator allocator;
478
479 // The sets holding the internalized objects. We use one set per storage type
480 // such that we can have a simpler equality checking function (no need to
481 // compare some sort of TypeIDs).
482 DenseSet<HashedStorage<ArrayStorage>, StorageKeyInfo<ArrayStorage>>
483 internedArrays;
484 DenseSet<HashedStorage<SetStorage>, StorageKeyInfo<SetStorage>> internedSets;
485 DenseSet<HashedStorage<BagStorage>, StorageKeyInfo<BagStorage>> internedBags;
486 DenseSet<HashedStorage<SequenceStorage>, StorageKeyInfo<SequenceStorage>>
487 internedSequences;
488 DenseSet<HashedStorage<RandomizedSequenceStorage>,
489 StorageKeyInfo<RandomizedSequenceStorage>>
490 internedRandomizedSequences;
491 DenseSet<HashedStorage<InterleavedSequenceStorage>,
492 StorageKeyInfo<InterleavedSequenceStorage>>
493 internedInterleavedSequences;
494 DenseSet<HashedStorage<TupleStorage>, StorageKeyInfo<TupleStorage>>
495 internedTuples;
496};
497
498} // namespace
499
500#ifndef NDEBUG
501
502static llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
503 const ElaboratorValue &value);
504
505static void print(TypedAttr val, llvm::raw_ostream &os) {
506 os << "<attr " << val << ">";
507}
508
509static void print(BagStorage *val, llvm::raw_ostream &os) {
510 os << "<bag {";
511 llvm::interleaveComma(val->bag, os,
512 [&](const std::pair<ElaboratorValue, uint64_t> &el) {
513 os << el.first << " -> " << el.second;
514 });
515 os << "} at " << val << ">";
516}
517
518static void print(bool val, llvm::raw_ostream &os) {
519 os << "<bool " << (val ? "true" : "false") << ">";
520}
521
522static void print(size_t val, llvm::raw_ostream &os) {
523 os << "<index " << val << ">";
524}
525
526static void print(SequenceStorage *val, llvm::raw_ostream &os) {
527 os << "<sequence @" << val->familyName.getValue() << "(";
528 llvm::interleaveComma(val->args, os,
529 [&](const ElaboratorValue &val) { os << val; });
530 os << ") at " << val << ">";
531}
532
533static void print(RandomizedSequenceStorage *val, llvm::raw_ostream &os) {
534 os << "<randomized-sequence @" << val->name << " derived from @"
535 << val->sequence->familyName.getValue() << " under context "
536 << val->context << " in test " << val->test << "(";
537 llvm::interleaveComma(val->sequence->args, os,
538 [&](const ElaboratorValue &val) { os << val; });
539 os << ") at " << val << ">";
540}
541
542static void print(InterleavedSequenceStorage *val, llvm::raw_ostream &os) {
543 os << "<interleaved-sequence [";
544 llvm::interleaveComma(val->sequences, os,
545 [&](const ElaboratorValue &val) { os << val; });
546 os << "] batch-size " << val->batchSize << " at " << val << ">";
547}
548
549static void print(ArrayStorage *val, llvm::raw_ostream &os) {
550 os << "<array [";
551 llvm::interleaveComma(val->array, os,
552 [&](const ElaboratorValue &val) { os << val; });
553 os << "] at " << val << ">";
554}
555
556static void print(SetStorage *val, llvm::raw_ostream &os) {
557 os << "<set {";
558 llvm::interleaveComma(val->set, os,
559 [&](const ElaboratorValue &val) { os << val; });
560 os << "} at " << val << ">";
561}
562
563static void print(const VirtualRegisterStorage *val, llvm::raw_ostream &os) {
564 os << "<virtual-register " << val << " " << val->allowedRegs << ">";
565}
566
567static void print(const UniqueLabelStorage *val, llvm::raw_ostream &os) {
568 os << "<unique-label " << val << " " << val->name << ">";
569}
570
571static void print(const LabelValue &val, llvm::raw_ostream &os) {
572 os << "<label " << val.name << ">";
573}
574
575static void print(const TupleStorage *val, llvm::raw_ostream &os) {
576 os << "<tuple (";
577 llvm::interleaveComma(val->values, os,
578 [&](const ElaboratorValue &val) { os << val; });
579 os << ")>";
580}
581
582static void print(const MemoryBlockStorage *val, llvm::raw_ostream &os) {
583 os << "<memory-block {"
584 << ", address-width=" << val->baseAddress.getBitWidth()
585 << ", base-address=" << val->baseAddress
586 << ", end-address=" << val->endAddress << "}>";
587}
588
589static llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
590 const ElaboratorValue &value) {
591 std::visit([&](auto val) { print(val, os); }, value);
592
593 return os;
594}
595
596#endif
597
598//===----------------------------------------------------------------------===//
599// Elaborator Value Materialization
600//===----------------------------------------------------------------------===//
601
602namespace {
603
604/// Construct an SSA value from a given elaborated value.
605class Materializer {
606public:
607 Materializer(OpBuilder builder) : builder(builder) {}
608
609 /// Materialize IR representing the provided `ElaboratorValue` and return the
610 /// `Value` or a null value on failure.
611 Value materialize(ElaboratorValue val, Location loc,
612 std::queue<RandomizedSequenceStorage *> &elabRequests,
613 function_ref<InFlightDiagnostic()> emitError) {
614 auto iter = materializedValues.find(val);
615 if (iter != materializedValues.end())
616 return iter->second;
617
618 LLVM_DEBUG(llvm::dbgs() << "Materializing " << val << "\n\n");
619
620 return std::visit(
621 [&](auto val) { return visit(val, loc, elabRequests, emitError); },
622 val);
623 }
624
625 /// If `op` is not in the same region as the materializer insertion point, a
626 /// clone is created at the materializer's insertion point by also
627 /// materializing the `ElaboratorValue`s for each operand just before it.
628 /// Otherwise, all operations after the materializer's insertion point are
629 /// deleted until `op` is reached. An error is returned if the operation is
630 /// before the insertion point.
631 LogicalResult
632 materialize(Operation *op, DenseMap<Value, ElaboratorValue> &state,
633 std::queue<RandomizedSequenceStorage *> &elabRequests) {
634 if (op->getNumRegions() > 0)
635 return op->emitOpError("ops with nested regions must be elaborated away");
636
637 // We don't support opaque values. If there is an SSA value that has a
638 // use-site it needs an equivalent ElaborationValue representation.
639 // NOTE: We could support cases where there is initially a use-site but that
640 // op is guaranteed to be deleted during elaboration. Or the use-sites are
641 // replaced with freshly materialized values from the ElaborationValue. But
642 // then, why can't we delete the value defining op?
643 for (auto res : op->getResults())
644 if (!res.use_empty())
645 return op->emitOpError(
646 "ops with results that have uses are not supported");
647
648 if (op->getParentRegion() == builder.getBlock()->getParent()) {
649 // We are doing in-place materialization, so mark all ops deleted until we
650 // reach the one to be materialized and modify it in-place.
651 deleteOpsUntil([&](auto iter) { return &*iter == op; });
652
653 if (builder.getInsertionPoint() == builder.getBlock()->end())
654 return op->emitError("operation did not occur after the current "
655 "materializer insertion point");
656
657 LLVM_DEBUG(llvm::dbgs() << "Modifying in-place: " << *op << "\n\n");
658 } else {
659 LLVM_DEBUG(llvm::dbgs() << "Materializing a clone of " << *op << "\n\n");
660 op = builder.clone(*op);
661 builder.setInsertionPoint(op);
662 }
663
664 for (auto &operand : op->getOpOperands()) {
665 auto emitError = [&]() {
666 auto diag = op->emitError();
667 diag.attachNote(op->getLoc())
668 << "while materializing value for operand#"
669 << operand.getOperandNumber();
670 return diag;
671 };
672
673 Value val = materialize(state.at(operand.get()), op->getLoc(),
674 elabRequests, emitError);
675 if (!val)
676 return failure();
677
678 operand.set(val);
679 }
680
681 builder.setInsertionPointAfter(op);
682 return success();
683 }
684
685 /// Should be called once the `Region` is successfully materialized. No calls
686 /// to `materialize` should happen after this anymore.
687 void finalize() {
688 deleteOpsUntil([](auto iter) { return false; });
689
690 for (auto *op : llvm::reverse(toDelete))
691 op->erase();
692 }
693
694 template <typename OpTy, typename... Args>
695 OpTy create(Location location, Args &&...args) {
696 return builder.create<OpTy>(location, std::forward<Args>(args)...);
697 }
698
699private:
700 void deleteOpsUntil(function_ref<bool(Block::iterator)> stop) {
701 auto ip = builder.getInsertionPoint();
702 while (ip != builder.getBlock()->end() && !stop(ip)) {
703 LLVM_DEBUG(llvm::dbgs() << "Marking to be deleted: " << *ip << "\n\n");
704 toDelete.push_back(&*ip);
705
706 builder.setInsertionPointAfter(&*ip);
707 ip = builder.getInsertionPoint();
708 }
709 }
710
711 Value visit(TypedAttr val, Location loc,
712 std::queue<RandomizedSequenceStorage *> &elabRequests,
713 function_ref<InFlightDiagnostic()> emitError) {
714 // For index attributes (and arithmetic operations on them) we use the
715 // index dialect.
716 if (auto intAttr = dyn_cast<IntegerAttr>(val);
717 intAttr && isa<IndexType>(val.getType())) {
718 Value res = builder.create<index::ConstantOp>(loc, intAttr);
719 materializedValues[val] = res;
720 return res;
721 }
722
723 // For any other attribute, we just call the materializer of the dialect
724 // defining that attribute.
725 auto *op =
726 val.getDialect().materializeConstant(builder, val, val.getType(), loc);
727 if (!op) {
728 emitError() << "materializer of dialect '"
729 << val.getDialect().getNamespace()
730 << "' unable to materialize value for attribute '" << val
731 << "'";
732 return Value();
733 }
734
735 Value res = op->getResult(0);
736 materializedValues[val] = res;
737 return res;
738 }
739
740 Value visit(size_t val, Location loc,
741 std::queue<RandomizedSequenceStorage *> &elabRequests,
742 function_ref<InFlightDiagnostic()> emitError) {
743 Value res = builder.create<index::ConstantOp>(loc, val);
744 materializedValues[val] = res;
745 return res;
746 }
747
748 Value visit(bool val, Location loc,
749 std::queue<RandomizedSequenceStorage *> &elabRequests,
750 function_ref<InFlightDiagnostic()> emitError) {
751 Value res = builder.create<index::BoolConstantOp>(loc, val);
752 materializedValues[val] = res;
753 return res;
754 }
755
756 Value visit(ArrayStorage *val, Location loc,
757 std::queue<RandomizedSequenceStorage *> &elabRequests,
758 function_ref<InFlightDiagnostic()> emitError) {
759 SmallVector<Value> elements;
760 elements.reserve(val->array.size());
761 for (auto el : val->array) {
762 auto materialized = materialize(el, loc, elabRequests, emitError);
763 if (!materialized)
764 return Value();
765
766 elements.push_back(materialized);
767 }
768
769 Value res = builder.create<ArrayCreateOp>(loc, val->type, elements);
770 materializedValues[val] = res;
771 return res;
772 }
773
774 Value visit(SetStorage *val, Location loc,
775 std::queue<RandomizedSequenceStorage *> &elabRequests,
776 function_ref<InFlightDiagnostic()> emitError) {
777 SmallVector<Value> elements;
778 elements.reserve(val->set.size());
779 for (auto el : val->set) {
780 auto materialized = materialize(el, loc, elabRequests, emitError);
781 if (!materialized)
782 return Value();
783
784 elements.push_back(materialized);
785 }
786
787 auto res = builder.create<SetCreateOp>(loc, val->type, elements);
788 materializedValues[val] = res;
789 return res;
790 }
791
792 Value visit(BagStorage *val, Location loc,
793 std::queue<RandomizedSequenceStorage *> &elabRequests,
794 function_ref<InFlightDiagnostic()> emitError) {
795 SmallVector<Value> values, weights;
796 values.reserve(val->bag.size());
797 weights.reserve(val->bag.size());
798 for (auto [val, weight] : val->bag) {
799 auto materializedVal = materialize(val, loc, elabRequests, emitError);
800 auto materializedWeight =
801 materialize(weight, loc, elabRequests, emitError);
802 if (!materializedVal || !materializedWeight)
803 return Value();
804
805 values.push_back(materializedVal);
806 weights.push_back(materializedWeight);
807 }
808
809 auto res = builder.create<BagCreateOp>(loc, val->type, values, weights);
810 materializedValues[val] = res;
811 return res;
812 }
813
814 Value visit(SequenceStorage *val, Location loc,
815 std::queue<RandomizedSequenceStorage *> &elabRequests,
816 function_ref<InFlightDiagnostic()> emitError) {
817 emitError() << "materializing a non-randomized sequence not supported yet";
818 return Value();
819 }
820
821 Value visit(RandomizedSequenceStorage *val, Location loc,
822 std::queue<RandomizedSequenceStorage *> &elabRequests,
823 function_ref<InFlightDiagnostic()> emitError) {
824 elabRequests.push(val);
825 Value seq = builder.create<GetSequenceOp>(
826 loc, SequenceType::get(builder.getContext(), {}), val->name);
827 Value res = builder.create<RandomizeSequenceOp>(loc, seq);
828 materializedValues[val] = res;
829 return res;
830 }
831
832 Value visit(InterleavedSequenceStorage *val, Location loc,
833 std::queue<RandomizedSequenceStorage *> &elabRequests,
834 function_ref<InFlightDiagnostic()> emitError) {
835 SmallVector<Value> sequences;
836 for (auto seqVal : val->sequences)
837 sequences.push_back(materialize(seqVal, loc, elabRequests, emitError));
838
839 if (sequences.size() == 1)
840 return sequences[0];
841
842 Value res =
843 builder.create<InterleaveSequencesOp>(loc, sequences, val->batchSize);
844 materializedValues[val] = res;
845 return res;
846 }
847
848 Value visit(VirtualRegisterStorage *val, Location loc,
849 std::queue<RandomizedSequenceStorage *> &elabRequests,
850 function_ref<InFlightDiagnostic()> emitError) {
851 Value res = builder.create<VirtualRegisterOp>(loc, val->allowedRegs);
852 materializedValues[val] = res;
853 return res;
854 }
855
856 Value visit(UniqueLabelStorage *val, Location loc,
857 std::queue<RandomizedSequenceStorage *> &elabRequests,
858 function_ref<InFlightDiagnostic()> emitError) {
859 Value res = builder.create<LabelUniqueDeclOp>(loc, val->name, ValueRange());
860 materializedValues[val] = res;
861 return res;
862 }
863
864 Value visit(const LabelValue &val, Location loc,
865 std::queue<RandomizedSequenceStorage *> &elabRequests,
866 function_ref<InFlightDiagnostic()> emitError) {
867 Value res = builder.create<LabelDeclOp>(loc, val.name, ValueRange());
868 materializedValues[val] = res;
869 return res;
870 }
871
872 Value visit(TupleStorage *val, Location loc,
873 std::queue<RandomizedSequenceStorage *> &elabRequests,
874 function_ref<InFlightDiagnostic()> emitError) {
875 SmallVector<Value> materialized;
876 materialized.reserve(val->values.size());
877 for (auto v : val->values)
878 materialized.push_back(materialize(v, loc, elabRequests, emitError));
879 Value res = builder.create<TupleCreateOp>(loc, materialized);
880 materializedValues[val] = res;
881 return res;
882 }
883
884private:
885 /// Cache values we have already materialized to reuse them later. We start
886 /// with an insertion point at the start of the block and cache the (updated)
887 /// insertion point such that future materializations can also reuse previous
888 /// materializations without running into dominance issues (or requiring
889 /// additional checks to avoid them).
890 DenseMap<ElaboratorValue, Value> materializedValues;
891
892 /// Cache the builder to continue insertions at their current insertion point
893 /// for the reason stated above.
894 OpBuilder builder;
895
896 SmallVector<Operation *> toDelete;
897};
898
899//===----------------------------------------------------------------------===//
900// Elaboration Visitor
901//===----------------------------------------------------------------------===//
902
903/// Used to signal to the elaboration driver whether the operation should be
904/// removed.
905enum class DeletionKind { Keep, Delete };
906
907/// Elaborator state that should be shared by all elaborator instances.
908struct ElaboratorSharedState {
909 ElaboratorSharedState(SymbolTable &table, unsigned seed)
910 : table(table), rng(seed) {}
911
912 SymbolTable &table;
913 std::mt19937 rng;
914 Namespace names;
915 Internalizer internalizer;
916
917 /// The worklist used to keep track of the test and sequence operations to
918 /// make sure they are processed top-down (BFS traversal).
919 std::queue<RandomizedSequenceStorage *> worklist;
920};
921
922/// A collection of state per RTG test.
923struct TestState {
924 /// The name of the test.
925 StringAttr name;
926
927 /// The context switches registered for this test.
928 MapVector<
929 std::pair<ContextResourceAttrInterface, ContextResourceAttrInterface>,
930 SequenceStorage *>
931 contextSwitches;
932};
933
934/// Interprets the IR to perform and lower the represented randomizations.
935class Elaborator : public RTGOpVisitor<Elaborator, FailureOr<DeletionKind>> {
936public:
938 using RTGBase::visitOp;
939
940 Elaborator(ElaboratorSharedState &sharedState, TestState &testState,
941 Materializer &materializer,
942 ContextResourceAttrInterface currentContext = {})
943 : sharedState(sharedState), testState(testState),
944 materializer(materializer), currentContext(currentContext) {}
945
946 template <typename ValueTy>
947 inline ValueTy get(Value val) const {
948 return std::get<ValueTy>(state.at(val));
949 }
950
951 FailureOr<DeletionKind> visitPureOp(Operation *op) {
952 SmallVector<Attribute> operands;
953 for (auto operand : op->getOperands()) {
954 auto evalValue = state[operand];
955 if (std::holds_alternative<TypedAttr>(evalValue))
956 operands.push_back(std::get<TypedAttr>(evalValue));
957 else
958 return visitUnhandledOp(op);
959 }
960
961 SmallVector<OpFoldResult> results;
962 if (failed(op->fold(operands, results)))
963 return visitUnhandledOp(op);
964
965 // We don't support in-place folders.
966 if (results.size() != op->getNumResults())
967 return visitUnhandledOp(op);
968
969 for (auto [res, val] : llvm::zip(results, op->getResults())) {
970 auto attr = llvm::dyn_cast_or_null<TypedAttr>(res.dyn_cast<Attribute>());
971 if (!attr)
972 return op->emitError(
973 "only typed attributes supported for constant-like operations");
974
975 auto intAttr = dyn_cast<IntegerAttr>(attr);
976 if (intAttr && isa<IndexType>(attr.getType()))
977 state[op->getResult(0)] = size_t(intAttr.getInt());
978 else if (intAttr && intAttr.getType().isSignlessInteger(1))
979 state[op->getResult(0)] = bool(intAttr.getInt());
980 else
981 state[op->getResult(0)] = attr;
982 }
983
984 return DeletionKind::Delete;
985 }
986
987 /// Print a nice error message for operations we don't support yet.
988 FailureOr<DeletionKind> visitUnhandledOp(Operation *op) {
989 return op->emitOpError("elaboration not supported");
990 }
991
992 FailureOr<DeletionKind> visitExternalOp(Operation *op) {
993 auto memOp = dyn_cast<MemoryEffectOpInterface>(op);
994 if (op->hasTrait<OpTrait::ConstantLike>() || (memOp && memOp.hasNoEffect()))
995 return visitPureOp(op);
996
997 // TODO: we only have this to be able to write tests for this pass without
998 // having to add support for more operations for now, so it should be
999 // removed once it is not necessary anymore for writing tests
1000 if (op->use_empty())
1001 return DeletionKind::Keep;
1002
1003 return visitUnhandledOp(op);
1004 }
1005
1006 FailureOr<DeletionKind> visitOp(ConstantOp op) { return visitPureOp(op); }
1007
1008 FailureOr<DeletionKind> visitOp(GetSequenceOp op) {
1009 SmallVector<ElaboratorValue> replacements;
1010 state[op.getResult()] =
1011 sharedState.internalizer.internalize<SequenceStorage>(
1012 op.getSequenceAttr(), std::move(replacements));
1013 return DeletionKind::Delete;
1014 }
1015
1016 FailureOr<DeletionKind> visitOp(SubstituteSequenceOp op) {
1017 auto *seq = get<SequenceStorage *>(op.getSequence());
1018
1019 SmallVector<ElaboratorValue> replacements(seq->args);
1020 for (auto replacement : op.getReplacements())
1021 replacements.push_back(state.at(replacement));
1022
1023 state[op.getResult()] =
1024 sharedState.internalizer.internalize<SequenceStorage>(
1025 seq->familyName, std::move(replacements));
1026
1027 return DeletionKind::Delete;
1028 }
1029
1030 FailureOr<DeletionKind> visitOp(RandomizeSequenceOp op) {
1031 auto *seq = get<SequenceStorage *>(op.getSequence());
1032
1033 auto name = sharedState.names.newName(seq->familyName.getValue());
1034 auto *randomizedSeq =
1035 sharedState.internalizer.internalize<RandomizedSequenceStorage>(
1036 name, currentContext, testState.name, seq);
1037 state[op.getResult()] =
1038 sharedState.internalizer.internalize<InterleavedSequenceStorage>(
1039 randomizedSeq);
1040 return DeletionKind::Delete;
1041 }
1042
1043 FailureOr<DeletionKind> visitOp(InterleaveSequencesOp op) {
1044 SmallVector<ElaboratorValue> sequences;
1045 for (auto seq : op.getSequences())
1046 sequences.push_back(get<InterleavedSequenceStorage *>(seq));
1047
1048 state[op.getResult()] =
1049 sharedState.internalizer.internalize<InterleavedSequenceStorage>(
1050 std::move(sequences), op.getBatchSize());
1051 return DeletionKind::Delete;
1052 }
1053
1054 // NOLINTNEXTLINE(misc-no-recursion)
1055 LogicalResult isValidContext(ElaboratorValue value, Operation *op) const {
1056 if (std::holds_alternative<RandomizedSequenceStorage *>(value)) {
1057 auto *seq = std::get<RandomizedSequenceStorage *>(value);
1058 if (seq->context != currentContext) {
1059 auto err = op->emitError("attempting to place sequence ")
1060 << seq->name << " derived from "
1061 << seq->sequence->familyName.getValue() << " under context "
1062 << currentContext
1063 << ", but it was previously randomized for context ";
1064 if (seq->context)
1065 err << seq->context;
1066 else
1067 err << "'default'";
1068 return err;
1069 }
1070 return success();
1071 }
1072
1073 auto *interVal = std::get<InterleavedSequenceStorage *>(value);
1074 for (auto val : interVal->sequences)
1075 if (failed(isValidContext(val, op)))
1076 return failure();
1077 return success();
1078 }
1079
1080 FailureOr<DeletionKind> visitOp(EmbedSequenceOp op) {
1081 auto *seqVal = get<InterleavedSequenceStorage *>(op.getSequence());
1082 if (failed(isValidContext(seqVal, op)))
1083 return failure();
1084
1085 return DeletionKind::Keep;
1086 }
1087
1088 FailureOr<DeletionKind> visitOp(SetCreateOp op) {
1089 SetVector<ElaboratorValue> set;
1090 for (auto val : op.getElements())
1091 set.insert(state.at(val));
1092
1093 state[op.getSet()] = sharedState.internalizer.internalize<SetStorage>(
1094 std::move(set), op.getSet().getType());
1095 return DeletionKind::Delete;
1096 }
1097
1098 FailureOr<DeletionKind> visitOp(SetSelectRandomOp op) {
1099 auto set = get<SetStorage *>(op.getSet())->set;
1100
1101 if (set.empty())
1102 return op->emitError("cannot select from an empty set");
1103
1104 size_t selected;
1105 if (auto intAttr =
1106 op->getAttrOfType<IntegerAttr>("rtg.elaboration_custom_seed")) {
1107 std::mt19937 customRng(intAttr.getInt());
1108 selected = getUniformlyInRange(customRng, 0, set.size() - 1);
1109 } else {
1110 selected = getUniformlyInRange(sharedState.rng, 0, set.size() - 1);
1111 }
1112
1113 state[op.getResult()] = set[selected];
1114 return DeletionKind::Delete;
1115 }
1116
1117 FailureOr<DeletionKind> visitOp(SetDifferenceOp op) {
1118 auto original = get<SetStorage *>(op.getOriginal())->set;
1119 auto diff = get<SetStorage *>(op.getDiff())->set;
1120
1121 SetVector<ElaboratorValue> result(original);
1122 result.set_subtract(diff);
1123
1124 state[op.getResult()] = sharedState.internalizer.internalize<SetStorage>(
1125 std::move(result), op.getResult().getType());
1126 return DeletionKind::Delete;
1127 }
1128
1129 FailureOr<DeletionKind> visitOp(SetUnionOp op) {
1130 SetVector<ElaboratorValue> result;
1131 for (auto set : op.getSets())
1132 result.set_union(get<SetStorage *>(set)->set);
1133
1134 state[op.getResult()] = sharedState.internalizer.internalize<SetStorage>(
1135 std::move(result), op.getType());
1136 return DeletionKind::Delete;
1137 }
1138
1139 FailureOr<DeletionKind> visitOp(SetSizeOp op) {
1140 auto size = get<SetStorage *>(op.getSet())->set.size();
1141 state[op.getResult()] = size;
1142 return DeletionKind::Delete;
1143 }
1144
1145 // {a0,a1} x {b0,b1} x {c0,c1} -> {(a0), (a1)} -> {(a0,b0), (a0,b1), (a1,b0),
1146 // (a1,b1)} -> {(a0,b0,c0), (a0,b0,c1), (a0,b1,c0), (a0,b1,c1), (a1,b0,c0),
1147 // (a1,b0,c1), (a1,b1,c0), (a1,b1,c1)}
1148 FailureOr<DeletionKind> visitOp(SetCartesianProductOp op) {
1149 SetVector<ElaboratorValue> result;
1150 SmallVector<SmallVector<ElaboratorValue>> tuples;
1151 tuples.push_back({});
1152
1153 for (auto input : op.getInputs()) {
1154 auto &set = get<SetStorage *>(input)->set;
1155 if (set.empty()) {
1156 SetVector<ElaboratorValue> empty;
1157 state[op.getResult()] =
1158 sharedState.internalizer.internalize<SetStorage>(std::move(empty),
1159 op.getType());
1160 return DeletionKind::Delete;
1161 }
1162
1163 for (unsigned i = 0, e = tuples.size(); i < e; ++i) {
1164 for (auto setEl : set.getArrayRef().drop_back()) {
1165 tuples.push_back(tuples[i]);
1166 tuples.back().push_back(setEl);
1167 }
1168 tuples[i].push_back(set.back());
1169 }
1170 }
1171
1172 for (auto &tup : tuples)
1173 result.insert(
1174 sharedState.internalizer.internalize<TupleStorage>(std::move(tup)));
1175
1176 state[op.getResult()] = sharedState.internalizer.internalize<SetStorage>(
1177 std::move(result), op.getType());
1178 return DeletionKind::Delete;
1179 }
1180
1181 FailureOr<DeletionKind> visitOp(SetConvertToBagOp op) {
1182 auto set = get<SetStorage *>(op.getInput())->set;
1183 MapVector<ElaboratorValue, uint64_t> bag;
1184 for (auto val : set)
1185 bag.insert({val, 1});
1186 state[op.getResult()] = sharedState.internalizer.internalize<BagStorage>(
1187 std::move(bag), op.getType());
1188 return DeletionKind::Delete;
1189 }
1190
1191 FailureOr<DeletionKind> visitOp(BagCreateOp op) {
1192 MapVector<ElaboratorValue, uint64_t> bag;
1193 for (auto [val, multiple] :
1194 llvm::zip(op.getElements(), op.getMultiples())) {
1195 // If the multiple is not stored as an AttributeValue, the elaboration
1196 // must have already failed earlier (since we don't have
1197 // unevaluated/opaque values).
1198 bag[state.at(val)] += get<size_t>(multiple);
1199 }
1200
1201 state[op.getBag()] = sharedState.internalizer.internalize<BagStorage>(
1202 std::move(bag), op.getType());
1203 return DeletionKind::Delete;
1204 }
1205
1206 FailureOr<DeletionKind> visitOp(BagSelectRandomOp op) {
1207 auto bag = get<BagStorage *>(op.getBag())->bag;
1208
1209 if (bag.empty())
1210 return op->emitError("cannot select from an empty bag");
1211
1212 SmallVector<std::pair<ElaboratorValue, uint32_t>> prefixSum;
1213 prefixSum.reserve(bag.size());
1214 uint32_t accumulator = 0;
1215 for (auto [val, weight] : bag) {
1216 accumulator += weight;
1217 prefixSum.push_back({val, accumulator});
1218 }
1219
1220 auto customRng = sharedState.rng;
1221 if (auto intAttr =
1222 op->getAttrOfType<IntegerAttr>("rtg.elaboration_custom_seed")) {
1223 customRng = std::mt19937(intAttr.getInt());
1224 }
1225
1226 auto idx = getUniformlyInRange(customRng, 0, accumulator - 1);
1227 auto *iter = llvm::upper_bound(
1228 prefixSum, idx,
1229 [](uint32_t a, const std::pair<ElaboratorValue, uint32_t> &b) {
1230 return a < b.second;
1231 });
1232
1233 state[op.getResult()] = iter->first;
1234 return DeletionKind::Delete;
1235 }
1236
1237 FailureOr<DeletionKind> visitOp(BagDifferenceOp op) {
1238 auto original = get<BagStorage *>(op.getOriginal())->bag;
1239 auto diff = get<BagStorage *>(op.getDiff())->bag;
1240
1241 MapVector<ElaboratorValue, uint64_t> result;
1242 for (const auto &el : original) {
1243 if (!diff.contains(el.first)) {
1244 result.insert(el);
1245 continue;
1246 }
1247
1248 if (op.getInf())
1249 continue;
1250
1251 auto toDiff = diff.lookup(el.first);
1252 if (el.second <= toDiff)
1253 continue;
1254
1255 result.insert({el.first, el.second - toDiff});
1256 }
1257
1258 state[op.getResult()] = sharedState.internalizer.internalize<BagStorage>(
1259 std::move(result), op.getType());
1260 return DeletionKind::Delete;
1261 }
1262
1263 FailureOr<DeletionKind> visitOp(BagUnionOp op) {
1264 MapVector<ElaboratorValue, uint64_t> result;
1265 for (auto bag : op.getBags()) {
1266 auto val = get<BagStorage *>(bag)->bag;
1267 for (auto [el, multiple] : val)
1268 result[el] += multiple;
1269 }
1270
1271 state[op.getResult()] = sharedState.internalizer.internalize<BagStorage>(
1272 std::move(result), op.getType());
1273 return DeletionKind::Delete;
1274 }
1275
1276 FailureOr<DeletionKind> visitOp(BagUniqueSizeOp op) {
1277 auto size = get<BagStorage *>(op.getBag())->bag.size();
1278 state[op.getResult()] = size;
1279 return DeletionKind::Delete;
1280 }
1281
1282 FailureOr<DeletionKind> visitOp(BagConvertToSetOp op) {
1283 auto bag = get<BagStorage *>(op.getInput())->bag;
1284 SetVector<ElaboratorValue> set;
1285 for (auto [k, v] : bag)
1286 set.insert(k);
1287 state[op.getResult()] = sharedState.internalizer.internalize<SetStorage>(
1288 std::move(set), op.getType());
1289 return DeletionKind::Delete;
1290 }
1291
1292 FailureOr<DeletionKind> visitOp(FixedRegisterOp op) {
1293 return visitPureOp(op);
1294 }
1295
1296 FailureOr<DeletionKind> visitOp(VirtualRegisterOp op) {
1297 state[op.getResult()] =
1298 sharedState.internalizer.create<VirtualRegisterStorage>(
1299 op.getAllowedRegsAttr());
1300 return DeletionKind::Delete;
1301 }
1302
1303 StringAttr substituteFormatString(StringAttr formatString,
1304 ValueRange substitutes) const {
1305 if (substitutes.empty() || formatString.empty())
1306 return formatString;
1307
1308 auto original = formatString.getValue().str();
1309 for (auto [i, subst] : llvm::enumerate(substitutes)) {
1310 size_t startPos = 0;
1311 std::string from = "{{" + std::to_string(i) + "}}";
1312 while ((startPos = original.find(from, startPos)) != std::string::npos) {
1313 auto substString = std::to_string(get<size_t>(subst));
1314 original.replace(startPos, from.length(), substString);
1315 }
1316 }
1317
1318 return StringAttr::get(formatString.getContext(), original);
1319 }
1320
1321 FailureOr<DeletionKind> visitOp(ArrayCreateOp op) {
1322 SmallVector<ElaboratorValue> array;
1323 array.reserve(op.getElements().size());
1324 for (auto val : op.getElements())
1325 array.emplace_back(state.at(val));
1326
1327 state[op.getResult()] = sharedState.internalizer.internalize<ArrayStorage>(
1328 op.getResult().getType(), std::move(array));
1329 return DeletionKind::Delete;
1330 }
1331
1332 FailureOr<DeletionKind> visitOp(ArrayExtractOp op) {
1333 auto array = get<ArrayStorage *>(op.getArray())->array;
1334 size_t idx = get<size_t>(op.getIndex());
1335
1336 if (array.size() <= idx)
1337 return op->emitError("invalid to access index ")
1338 << idx << " of an array with " << array.size() << " elements";
1339
1340 state[op.getResult()] = array[idx];
1341 return DeletionKind::Delete;
1342 }
1343
1344 FailureOr<DeletionKind> visitOp(ArrayInjectOp op) {
1345 auto array = get<ArrayStorage *>(op.getArray())->array;
1346 size_t idx = get<size_t>(op.getIndex());
1347
1348 if (array.size() <= idx)
1349 return op->emitError("invalid to access index ")
1350 << idx << " of an array with " << array.size() << " elements";
1351
1352 array[idx] = state[op.getValue()];
1353 state[op.getResult()] = sharedState.internalizer.internalize<ArrayStorage>(
1354 op.getResult().getType(), std::move(array));
1355 return DeletionKind::Delete;
1356 }
1357
1358 FailureOr<DeletionKind> visitOp(ArraySizeOp op) {
1359 auto array = get<ArrayStorage *>(op.getArray())->array;
1360 state[op.getResult()] = array.size();
1361 return DeletionKind::Delete;
1362 }
1363
1364 FailureOr<DeletionKind> visitOp(LabelDeclOp op) {
1365 auto substituted =
1366 substituteFormatString(op.getFormatStringAttr(), op.getArgs());
1367 state[op.getLabel()] = LabelValue(substituted);
1368 return DeletionKind::Delete;
1369 }
1370
1371 FailureOr<DeletionKind> visitOp(LabelUniqueDeclOp op) {
1372 state[op.getLabel()] = sharedState.internalizer.create<UniqueLabelStorage>(
1373 substituteFormatString(op.getFormatStringAttr(), op.getArgs()));
1374 return DeletionKind::Delete;
1375 }
1376
1377 FailureOr<DeletionKind> visitOp(LabelOp op) { return DeletionKind::Keep; }
1378
1379 FailureOr<DeletionKind> visitOp(RandomNumberInRangeOp op) {
1380 size_t lower = get<size_t>(op.getLowerBound());
1381 size_t upper = get<size_t>(op.getUpperBound()) - 1;
1382 if (lower > upper)
1383 return op->emitError("cannot select a number from an empty range");
1384
1385 if (auto intAttr =
1386 op->getAttrOfType<IntegerAttr>("rtg.elaboration_custom_seed")) {
1387 std::mt19937 customRng(intAttr.getInt());
1388 state[op.getResult()] =
1389 size_t(getUniformlyInRange(customRng, lower, upper));
1390 } else {
1391 state[op.getResult()] =
1392 size_t(getUniformlyInRange(sharedState.rng, lower, upper));
1393 }
1394
1395 return DeletionKind::Delete;
1396 }
1397
1398 FailureOr<DeletionKind> visitOp(IntToImmediateOp op) {
1399 size_t input = get<size_t>(op.getInput());
1400 auto width = op.getType().getWidth();
1401 auto emitError = [&]() { return op->emitError(); };
1402 if (input > APInt::getAllOnes(width).getZExtValue())
1403 return emitError() << "cannot represent " << input << " with " << width
1404 << " bits";
1405
1406 state[op.getResult()] =
1407 ImmediateAttr::get(op.getContext(), APInt(width, input));
1408 return DeletionKind::Delete;
1409 }
1410
1411 FailureOr<DeletionKind> visitOp(OnContextOp op) {
1412 ContextResourceAttrInterface from = currentContext,
1413 to = cast<ContextResourceAttrInterface>(
1414 get<TypedAttr>(op.getContext()));
1415 if (!currentContext)
1416 from = DefaultContextAttr::get(op->getContext(), to.getType());
1417
1418 auto emitError = [&]() {
1419 auto diag = op.emitError();
1420 diag.attachNote(op.getLoc())
1421 << "while materializing value for context switching for " << op;
1422 return diag;
1423 };
1424
1425 if (from == to) {
1426 Value seqVal = materializer.materialize(
1427 get<SequenceStorage *>(op.getSequence()), op.getLoc(),
1428 sharedState.worklist, emitError);
1429 Value randSeqVal =
1430 materializer.create<RandomizeSequenceOp>(op.getLoc(), seqVal);
1431 materializer.create<EmbedSequenceOp>(op.getLoc(), randSeqVal);
1432 return DeletionKind::Delete;
1433 }
1434
1435 // Switch to the desired context.
1436 // First, check if a context switch is registered that has the concrete
1437 // context as source and target.
1438 auto *iter = testState.contextSwitches.find({from, to});
1439
1440 // Try with 'any' context as target and the concrete context as source.
1441 if (iter == testState.contextSwitches.end())
1442 iter = testState.contextSwitches.find(
1443 {from, AnyContextAttr::get(op->getContext(), to.getType())});
1444
1445 // Try with 'any' context as source and the concrete context as target.
1446 if (iter == testState.contextSwitches.end())
1447 iter = testState.contextSwitches.find(
1448 {AnyContextAttr::get(op->getContext(), from.getType()), to});
1449
1450 // Try with 'any' context for both the source and the target.
1451 if (iter == testState.contextSwitches.end())
1452 iter = testState.contextSwitches.find(
1453 {AnyContextAttr::get(op->getContext(), from.getType()),
1454 AnyContextAttr::get(op->getContext(), to.getType())});
1455
1456 // Otherwise, fail with an error because we couldn't find a user
1457 // specification on how to switch between the requested contexts.
1458 // NOTE: we could think about supporting context switching via intermediate
1459 // context, i.e., treat it as a transitive relation.
1460 if (iter == testState.contextSwitches.end())
1461 return op->emitError("no context transition registered to switch from ")
1462 << from << " to " << to;
1463
1464 auto familyName = iter->second->familyName;
1465 SmallVector<ElaboratorValue> args{from, to,
1466 get<SequenceStorage *>(op.getSequence())};
1467 auto *seq = sharedState.internalizer.internalize<SequenceStorage>(
1468 familyName, std::move(args));
1469 auto *randSeq =
1470 sharedState.internalizer.internalize<RandomizedSequenceStorage>(
1471 sharedState.names.newName(familyName.getValue()), to,
1472 testState.name, seq);
1473 Value seqVal = materializer.materialize(randSeq, op.getLoc(),
1474 sharedState.worklist, emitError);
1475 materializer.create<EmbedSequenceOp>(op.getLoc(), seqVal);
1476
1477 return DeletionKind::Delete;
1478 }
1479
1480 FailureOr<DeletionKind> visitOp(ContextSwitchOp op) {
1481 testState.contextSwitches[{op.getFromAttr(), op.getToAttr()}] =
1482 get<SequenceStorage *>(op.getSequence());
1483 return DeletionKind::Delete;
1484 }
1485
1486 FailureOr<DeletionKind> visitOp(TupleCreateOp op) {
1487 SmallVector<ElaboratorValue> values;
1488 values.reserve(op.getElements().size());
1489 for (auto el : op.getElements())
1490 values.push_back(state[el]);
1491
1492 state[op.getResult()] =
1493 sharedState.internalizer.internalize<TupleStorage>(std::move(values));
1494 return DeletionKind::Delete;
1495 }
1496
1497 FailureOr<DeletionKind> visitOp(TupleExtractOp op) {
1498 auto *tuple = get<TupleStorage *>(op.getTuple());
1499 state[op.getResult()] = tuple->values[op.getIndex().getZExtValue()];
1500 return DeletionKind::Delete;
1501 }
1502
1503 FailureOr<DeletionKind> visitOp(CommentOp op) { return DeletionKind::Keep; }
1504
1505 FailureOr<DeletionKind> visitOp(MemoryBlockDeclareOp op) {
1506 state[op.getResult()] = sharedState.internalizer.create<MemoryBlockStorage>(
1507 op.getEndAddress(), op.getBaseAddress());
1508 return DeletionKind::Delete;
1509 }
1510
1511 FailureOr<DeletionKind> visitOp(scf::IfOp op) {
1512 bool cond = get<bool>(op.getCondition());
1513 auto &toElaborate = cond ? op.getThenRegion() : op.getElseRegion();
1514 if (toElaborate.empty())
1515 return DeletionKind::Delete;
1516
1517 // Just reuse this elaborator for the nested region because we need access
1518 // to the elaborated values outside the nested region (since it is not
1519 // isolated from above) and we want to materialize the region inline, thus
1520 // don't need a new materializer instance.
1521 if (failed(elaborate(toElaborate)))
1522 return failure();
1523
1524 // Map the results of the 'scf.if' to the yielded values.
1525 for (auto [res, out] :
1526 llvm::zip(op.getResults(),
1527 toElaborate.front().getTerminator()->getOperands()))
1528 state[res] = state.at(out);
1529
1530 return DeletionKind::Delete;
1531 }
1532
1533 FailureOr<DeletionKind> visitOp(scf::ForOp op) {
1534 if (!(std::holds_alternative<size_t>(state.at(op.getLowerBound())) &&
1535 std::holds_alternative<size_t>(state.at(op.getStep())) &&
1536 std::holds_alternative<size_t>(state.at(op.getUpperBound()))))
1537 return op->emitOpError("can only elaborate index type iterator");
1538
1539 auto lowerBound = get<size_t>(op.getLowerBound());
1540 auto step = get<size_t>(op.getStep());
1541 auto upperBound = get<size_t>(op.getUpperBound());
1542
1543 // Prepare for first iteration by assigning the nested regions block
1544 // arguments. We can just reuse this elaborator because we need access to
1545 // values elaborated in the parent region anyway and materialize everything
1546 // inline (i.e., don't need a new materializer).
1547 state[op.getInductionVar()] = lowerBound;
1548 for (auto [iterArg, initArg] :
1549 llvm::zip(op.getRegionIterArgs(), op.getInitArgs()))
1550 state[iterArg] = state.at(initArg);
1551
1552 // This loop performs the actual 'scf.for' loop iterations.
1553 for (size_t i = lowerBound; i < upperBound; i += step) {
1554 if (failed(elaborate(op.getBodyRegion())))
1555 return failure();
1556
1557 // Prepare for the next iteration by updating the mapping of the nested
1558 // regions block arguments
1559 state[op.getInductionVar()] = i + step;
1560 for (auto [iterArg, prevIterArg] :
1561 llvm::zip(op.getRegionIterArgs(),
1562 op.getBody()->getTerminator()->getOperands()))
1563 state[iterArg] = state.at(prevIterArg);
1564 }
1565
1566 // Transfer the previously yielded values to the for loop result values.
1567 for (auto [res, iterArg] :
1568 llvm::zip(op->getResults(), op.getRegionIterArgs()))
1569 state[res] = state.at(iterArg);
1570
1571 return DeletionKind::Delete;
1572 }
1573
1574 FailureOr<DeletionKind> visitOp(scf::YieldOp op) {
1575 return DeletionKind::Delete;
1576 }
1577
1578 FailureOr<DeletionKind> visitOp(arith::AddIOp op) {
1579 if (!isa<IndexType>(op.getType()))
1580 return op->emitError("only index operands supported");
1581
1582 size_t lhs = get<size_t>(op.getLhs());
1583 size_t rhs = get<size_t>(op.getRhs());
1584 state[op.getResult()] = lhs + rhs;
1585 return DeletionKind::Delete;
1586 }
1587
1588 FailureOr<DeletionKind> visitOp(arith::AndIOp op) {
1589 if (!op.getType().isSignlessInteger(1))
1590 return op->emitError("only 'i1' operands supported");
1591
1592 bool lhs = get<bool>(op.getLhs());
1593 bool rhs = get<bool>(op.getRhs());
1594 state[op.getResult()] = lhs && rhs;
1595 return DeletionKind::Delete;
1596 }
1597
1598 FailureOr<DeletionKind> visitOp(arith::XOrIOp op) {
1599 if (!op.getType().isSignlessInteger(1))
1600 return op->emitError("only 'i1' operands supported");
1601
1602 bool lhs = get<bool>(op.getLhs());
1603 bool rhs = get<bool>(op.getRhs());
1604 state[op.getResult()] = lhs != rhs;
1605 return DeletionKind::Delete;
1606 }
1607
1608 FailureOr<DeletionKind> visitOp(arith::OrIOp op) {
1609 if (!op.getType().isSignlessInteger(1))
1610 return op->emitError("only 'i1' operands supported");
1611
1612 bool lhs = get<bool>(op.getLhs());
1613 bool rhs = get<bool>(op.getRhs());
1614 state[op.getResult()] = lhs || rhs;
1615 return DeletionKind::Delete;
1616 }
1617
1618 FailureOr<DeletionKind> visitOp(arith::SelectOp op) {
1619 bool cond = get<bool>(op.getCondition());
1620 auto trueVal = state[op.getTrueValue()];
1621 auto falseVal = state[op.getFalseValue()];
1622 state[op.getResult()] = cond ? trueVal : falseVal;
1623 return DeletionKind::Delete;
1624 }
1625
1626 FailureOr<DeletionKind> visitOp(index::AddOp op) {
1627 size_t lhs = get<size_t>(op.getLhs());
1628 size_t rhs = get<size_t>(op.getRhs());
1629 state[op.getResult()] = lhs + rhs;
1630 return DeletionKind::Delete;
1631 }
1632
1633 FailureOr<DeletionKind> visitOp(index::CmpOp op) {
1634 size_t lhs = get<size_t>(op.getLhs());
1635 size_t rhs = get<size_t>(op.getRhs());
1636 bool result;
1637 switch (op.getPred()) {
1638 case index::IndexCmpPredicate::EQ:
1639 result = lhs == rhs;
1640 break;
1641 case index::IndexCmpPredicate::NE:
1642 result = lhs != rhs;
1643 break;
1644 case index::IndexCmpPredicate::ULT:
1645 result = lhs < rhs;
1646 break;
1647 case index::IndexCmpPredicate::ULE:
1648 result = lhs <= rhs;
1649 break;
1650 case index::IndexCmpPredicate::UGT:
1651 result = lhs > rhs;
1652 break;
1653 case index::IndexCmpPredicate::UGE:
1654 result = lhs >= rhs;
1655 break;
1656 default:
1657 return op->emitOpError("elaboration not supported");
1658 }
1659 state[op.getResult()] = result;
1660 return DeletionKind::Delete;
1661 }
1662
1663 FailureOr<DeletionKind> dispatchOpVisitor(Operation *op) {
1664 return TypeSwitch<Operation *, FailureOr<DeletionKind>>(op)
1665 .Case<
1666 // Arith ops
1667 arith::AddIOp, arith::XOrIOp, arith::AndIOp, arith::OrIOp,
1668 arith::SelectOp,
1669 // Index ops
1670 index::AddOp, index::CmpOp,
1671 // SCF ops
1672 scf::IfOp, scf::ForOp, scf::YieldOp>(
1673 [&](auto op) { return visitOp(op); })
1674 .Default([&](Operation *op) { return RTGBase::dispatchOpVisitor(op); });
1675 }
1676
1677 // NOLINTNEXTLINE(misc-no-recursion)
1678 LogicalResult elaborate(Region &region,
1679 ArrayRef<ElaboratorValue> regionArguments = {}) {
1680 if (region.getBlocks().size() > 1)
1681 return region.getParentOp()->emitOpError(
1682 "regions with more than one block are not supported");
1683
1684 for (auto [arg, elabArg] :
1685 llvm::zip(region.getArguments(), regionArguments))
1686 state[arg] = elabArg;
1687
1688 Block *block = &region.front();
1689 for (auto &op : *block) {
1690 auto result = dispatchOpVisitor(&op);
1691 if (failed(result))
1692 return failure();
1693
1694 if (*result == DeletionKind::Keep)
1695 if (failed(materializer.materialize(&op, state, sharedState.worklist)))
1696 return failure();
1697
1698 LLVM_DEBUG({
1699 llvm::dbgs() << "Elaborated " << op << " to\n[";
1700
1701 llvm::interleaveComma(op.getResults(), llvm::dbgs(), [&](auto res) {
1702 if (state.contains(res))
1703 llvm::dbgs() << state.at(res);
1704 else
1705 llvm::dbgs() << "unknown";
1706 });
1707
1708 llvm::dbgs() << "]\n\n";
1709 });
1710 }
1711
1712 return success();
1713 }
1714
1715private:
1716 // State to be shared between all elaborator instances.
1717 ElaboratorSharedState &sharedState;
1718
1719 // State to a specific RTG test and the sequences placed within it.
1720 TestState &testState;
1721
1722 // Allows us to materialize ElaboratorValues to the IR operations necessary to
1723 // obtain an SSA value representing that elaborated value.
1724 Materializer &materializer;
1725
1726 // A map from SSA values to a pointer of an interned elaborator value.
1727 DenseMap<Value, ElaboratorValue> state;
1728
1729 // The current context we are elaborating under.
1730 ContextResourceAttrInterface currentContext;
1731};
1732} // namespace
1733
1734//===----------------------------------------------------------------------===//
1735// Elaborator Pass
1736//===----------------------------------------------------------------------===//
1737
1738namespace {
1739struct ElaborationPass
1740 : public rtg::impl::ElaborationPassBase<ElaborationPass> {
1741 using Base::Base;
1742
1743 void runOnOperation() override;
1744 void cloneTargetsIntoTests(SymbolTable &table);
1745 LogicalResult elaborateModule(ModuleOp moduleOp, SymbolTable &table);
1746};
1747} // namespace
1748
1749void ElaborationPass::runOnOperation() {
1750 auto moduleOp = getOperation();
1751 SymbolTable table(moduleOp);
1752
1753 cloneTargetsIntoTests(table);
1754
1755 if (failed(elaborateModule(moduleOp, table)))
1756 return signalPassFailure();
1757}
1758
1759void ElaborationPass::cloneTargetsIntoTests(SymbolTable &table) {
1760 auto moduleOp = getOperation();
1761 for (auto target : llvm::make_early_inc_range(moduleOp.getOps<TargetOp>())) {
1762 for (auto test : moduleOp.getOps<TestOp>()) {
1763 // If the test requires nothing from a target, we can always run it.
1764 if (test.getTarget().getEntries().empty())
1765 continue;
1766
1767 // If the target requirements do not match, skip this test
1768 // TODO: allow target refinements, just not coarsening
1769 if (target.getTarget() != test.getTarget())
1770 continue;
1771
1772 IRRewriter rewriter(test);
1773 // Create a new test for the matched target
1774 auto newTest = cast<TestOp>(test->clone());
1775 newTest.setSymName(test.getSymName().str() + "_" +
1776 target.getSymName().str());
1777 table.insert(newTest, rewriter.getInsertionPoint());
1778
1779 // Copy the target body into the newly created test
1780 IRMapping mapping;
1781 rewriter.setInsertionPointToStart(newTest.getBody());
1782 for (auto &op : target.getBody()->without_terminator())
1783 rewriter.clone(op, mapping);
1784
1785 for (auto [returnVal, result] :
1786 llvm::zip(target.getBody()->getTerminator()->getOperands(),
1787 newTest.getBody()->getArguments()))
1788 result.replaceAllUsesWith(mapping.lookup(returnVal));
1789
1790 newTest.getBody()->eraseArguments(0,
1791 newTest.getBody()->getNumArguments());
1792 newTest.setTarget(DictType::get(&getContext(), {}));
1793 }
1794
1795 target->erase();
1796 }
1797
1798 // Erase all remaining non-matched tests.
1799 for (auto test : llvm::make_early_inc_range(moduleOp.getOps<TestOp>()))
1800 if (!test.getTarget().getEntries().empty())
1801 test->erase();
1802}
1803
1804LogicalResult ElaborationPass::elaborateModule(ModuleOp moduleOp,
1805 SymbolTable &table) {
1806 ElaboratorSharedState state(table, seed);
1807
1808 // Update the name cache
1809 state.names.add(moduleOp);
1810
1811 // Initialize the worklist with the test ops since they cannot be placed by
1812 // other ops.
1813 DenseMap<StringAttr, TestState> testStates;
1814 for (auto testOp : moduleOp.getOps<TestOp>()) {
1815 LLVM_DEBUG(llvm::dbgs()
1816 << "\n=== Elaborating test @" << testOp.getSymName() << "\n\n");
1817 Materializer materializer(OpBuilder::atBlockBegin(testOp.getBody()));
1818 testStates[testOp.getSymNameAttr()].name = testOp.getSymNameAttr();
1819 Elaborator elaborator(state, testStates[testOp.getSymNameAttr()],
1820 materializer);
1821 if (failed(elaborator.elaborate(testOp.getBodyRegion())))
1822 return failure();
1823
1824 materializer.finalize();
1825 }
1826
1827 // Do top-down BFS traversal such that elaborating a sequence further down
1828 // does not fix the outcome for multiple placements.
1829 while (!state.worklist.empty()) {
1830 auto *curr = state.worklist.front();
1831 state.worklist.pop();
1832
1833 if (table.lookup<SequenceOp>(curr->name))
1834 continue;
1835
1836 auto familyOp = table.lookup<SequenceOp>(curr->sequence->familyName);
1837 // TODO: don't clone if this is the only remaining reference to this
1838 // sequence
1839 OpBuilder builder(familyOp);
1840 auto seqOp = builder.cloneWithoutRegions(familyOp);
1841 seqOp.getBodyRegion().emplaceBlock();
1842 seqOp.setSymName(curr->name);
1843 seqOp.setSequenceType(
1844 SequenceType::get(builder.getContext(), ArrayRef<Type>{}));
1845 table.insert(seqOp);
1846 assert(seqOp.getSymName() == curr->name && "should not have been renamed");
1847
1848 LLVM_DEBUG(llvm::dbgs()
1849 << "\n=== Elaborating sequence family @" << familyOp.getSymName()
1850 << " into @" << seqOp.getSymName() << " under context "
1851 << curr->context << "\n\n");
1852
1853 Materializer materializer(OpBuilder::atBlockBegin(seqOp.getBody()));
1854 Elaborator elaborator(state, testStates[curr->test], materializer,
1855 curr->context);
1856 if (failed(elaborator.elaborate(familyOp.getBodyRegion(),
1857 curr->sequence->args)))
1858 return failure();
1859
1860 materializer.finalize();
1861 }
1862
1863 return success();
1864}
assert(baseType &&"element must be base type")
static uint32_t computeMask(size_t w)
static uint32_t getUniformlyInRange(std::mt19937 &rng, uint32_t a, uint32_t b)
Get a number uniformly at random in the in specified range.
static void print(TypedAttr val, llvm::raw_ostream &os)
static InstancePath empty
A namespace that is used to store existing names and generate new names in some scope within the IR.
Definition Namespace.h:30
This helps visit TypeOp nodes.
Definition RTGVisitors.h:29
ResultType visitExternalOp(Operation *op, ExtraArgs... args)
Definition RTGVisitors.h:90
ResultType visitUnhandledOp(Operation *op, ExtraArgs... args)
This callback is invoked on any operations that are not handled by the concrete visitor.
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp:55
OS & operator<<(OS &os, const InnerSymTarget &target)
Printing InnerSymTarget's.
static bool operator==(const ModulePort &a, const ModulePort &b)
Definition HWTypes.h:35
static llvm::hash_code hash_value(const ModulePort &port)
Definition HWTypes.h:38
The InstanceGraph op interface, see InstanceGraphInterface.td for more details.
size_t hash_combine(size_t h1, size_t h2)
C++'s stdlib doesn't have a hash_combine function. This is a simple one.
Definition Utils.h:32
Definition rtg.py:1
Definition seq.py:1
static bool isEqual(const LabelValue &lhs, const LabelValue &rhs)
static unsigned getHashValue(const LabelValue &val)
static bool isEqual(const bool &lhs, const bool &rhs)
static unsigned getTombstoneKey()
static unsigned getHashValue(const bool &val)