CIRCT 24.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/Func/IR/FuncOps.h"
23#include "mlir/Dialect/Index/IR/IndexDialect.h"
24#include "mlir/Dialect/Index/IR/IndexOps.h"
25#include "mlir/Dialect/SCF/IR/SCF.h"
26#include "mlir/IR/IRMapping.h"
27#include "mlir/IR/PatternMatch.h"
28#include "llvm/ADT/DenseMapInfoVariant.h"
29#include "llvm/ADT/ScopeExit.h"
30#include "llvm/ADT/SmallPtrSet.h"
31#include "llvm/Support/Debug.h"
32#include <memory>
33#include <random>
34#include <type_traits>
35
36namespace circt {
37namespace rtg {
38#define GEN_PASS_DEF_ELABORATIONPASS
39#include "circt/Dialect/RTG/Transforms/RTGPasses.h.inc"
40} // namespace rtg
41} // namespace circt
42
43using namespace mlir;
44using namespace circt;
45using namespace circt::rtg;
46
47#define DEBUG_TYPE "rtg-elaboration"
48
49namespace {
50/// Helper class to generate random numbers supporting scopes of randomization.
51struct RngScope {
52 RngScope() = delete;
53 RngScope(const RngScope &) = default;
54 RngScope &operator=(const RngScope &) = default;
55
56 RngScope(RngScope &&) = default;
57 RngScope &operator=(RngScope &&) = default;
58
59 explicit RngScope(uint32_t seed) : rng(seed) {}
60
61 //===--------------------------------------------------------------------===//
62 // Uniform Distribution Helper
63 //
64 // Simplified version of
65 // https://github.com/llvm/llvm-project/blob/main/libcxx/include/__random/uniform_int_distribution.h
66 // We use our custom version here to get the same results when compiled with
67 // different compiler versions and standard libraries.
68 //===--------------------------------------------------------------------===//
69
70 /// Get a number uniformly at random in the in specified range.
71 uint32_t getUniformlyInRange(uint32_t a, uint32_t b) {
72 const uint32_t diff = b - a + 1;
73 if (diff == 1)
74 return a;
75
76 if (diff == 0)
77 return rng();
78
79 uint32_t mask =
80 std::numeric_limits<uint32_t>::max() >> (32 - llvm::Log2_32_Ceil(diff));
81 uint32_t u;
82 do {
83 u = rng() & mask;
84 } while (u >= diff);
85
86 return u + a;
87 }
88
89 RngScope getNested() { return RngScope(rng()); }
90
91private:
92 std::mt19937 rng;
93};
94} // namespace
95
96//===----------------------------------------------------------------------===//
97// Elaborator Value
98//===----------------------------------------------------------------------===//
99
100namespace {
101struct ArrayStorage;
102struct BagStorage;
103struct SequenceStorage;
104struct RandomizedSequenceStorage;
105struct InterleavedSequenceStorage;
106struct SetStorage;
107struct VirtualRegisterStorage;
108struct UniqueLabelStorage;
109struct TupleStorage;
110struct MemoryStorage;
111struct MemoryBlockStorage;
112struct SymbolicComputationWithIdentityStorage;
113struct SymbolicComputationWithIdentityValue;
114struct SymbolicComputationStorage;
115struct OpaqueExternalStorage;
116struct MutStorage;
117struct ContinuationStorage;
118
119/// The abstract base class for elaborated values.
120using ElaboratorValue =
121 std::variant<TypedAttr, BagStorage *, bool, size_t, SequenceStorage *,
122 RandomizedSequenceStorage *, InterleavedSequenceStorage *,
123 SetStorage *, VirtualRegisterStorage *, UniqueLabelStorage *,
124 ArrayStorage *, TupleStorage *, MemoryStorage *,
125 MemoryBlockStorage *, SymbolicComputationWithIdentityStorage *,
126 SymbolicComputationWithIdentityValue *,
127 SymbolicComputationStorage *, OpaqueExternalStorage *,
128 MutStorage *, ContinuationStorage *>;
129using LexicalEnvironment = DenseMap<Value, ElaboratorValue>;
130
131// NOLINTNEXTLINE(readability-identifier-naming)
132llvm::hash_code hash_value(const ElaboratorValue &val) {
133 return std::visit(
134 [&val](const auto &alternative) {
135 // Include index in hash to make sure same value as different
136 // alternatives don't collide.
137 return llvm::hash_combine(val.index(), alternative);
138 },
139 val);
140}
141
142} // namespace
143
144namespace llvm {
145
146template <>
147struct DenseMapInfo<bool> {
148 static unsigned getHashValue(const bool &val) { return val * 37U; }
149
150 static bool isEqual(const bool &lhs, const bool &rhs) { return lhs == rhs; }
151};
152
153} // namespace llvm
154
155//===----------------------------------------------------------------------===//
156// Elaborator Value Storages and Internalization
157//===----------------------------------------------------------------------===//
158
159namespace {
160
161/// Lightweight object to be used as the key for internalization sets. It caches
162/// the hashcode of the internalized object and a pointer to it. This allows a
163/// delayed allocation and construction of the actual object and thus only has
164/// to happen if the object is not already in the set.
165template <typename StorageTy>
166struct HashedStorage {
167 HashedStorage(unsigned hashcode = 0, StorageTy *storage = nullptr)
168 : hashcode(hashcode), storage(storage) {}
169
170 unsigned hashcode;
171 StorageTy *storage;
172};
173
174/// A DenseMapInfo implementation to support 'insert_as' for the internalization
175/// sets. When comparing two 'HashedStorage's we can just compare the already
176/// internalized storage pointers, otherwise we have to call the costly
177/// 'isEqual' method.
178template <typename StorageTy>
179struct StorageKeyInfo {
180 static inline unsigned getHashValue(const HashedStorage<StorageTy> &key) {
181 return key.hashcode;
182 }
183 static inline unsigned getHashValue(const StorageTy &key) {
184 return key.hashcode;
185 }
186
187 static inline bool isEqual(const HashedStorage<StorageTy> &lhs,
188 const HashedStorage<StorageTy> &rhs) {
189 return lhs.storage == rhs.storage;
190 }
191 static inline bool isEqual(const StorageTy &lhs,
192 const HashedStorage<StorageTy> &rhs) {
193 if (!rhs.storage)
194 return false;
195
196 return lhs.isEqual(rhs.storage);
197 }
198};
199
200// Values with structural equivalence intended to be internalized.
201//===----------------------------------------------------------------------===//
202
203/// Base class for all storage objects that can be materialized as attributes.
204/// This provides a cache for the attribute that was materialized from this
205/// storage.
206struct CachableStorage {
207 TypedAttr attrCache;
208};
209
210/// Storage object for an '!rtg.set<T>'.
211struct SetStorage : CachableStorage {
212 static unsigned computeHash(const SetVector<ElaboratorValue> &set,
213 Type type) {
214 llvm::hash_code setHash = 0;
215 for (auto el : set) {
216 // Just XOR all hashes because it's a commutative operation and
217 // `llvm::hash_combine_range` is not commutative.
218 // We don't want the order in which elements were added to influence the
219 // hash and thus the equivalence of sets.
220 setHash = setHash ^ llvm::hash_combine(el);
221 }
222 return llvm::hash_combine(type, setHash);
223 }
224
225 SetStorage(SetVector<ElaboratorValue> &&set, Type type)
226 : hashcode(computeHash(set, type)), set(std::move(set)), type(type) {}
227
228 bool isEqual(const SetStorage *other) const {
229 // Note: we are not using the `==` operator of `SetVector` because it
230 // takes the order in which elements were added into account (since it's a
231 // vector after all). We just use it as a convenient way to keep track of a
232 // deterministic order for re-materialization.
233 bool allContained = true;
234 for (auto el : set)
235 allContained &= other->set.contains(el);
236
237 return hashcode == other->hashcode && set.size() == other->set.size() &&
238 allContained && type == other->type;
239 }
240
241 // The cached hashcode to avoid repeated computations.
242 const unsigned hashcode;
243
244 // Stores the elaborated values contained in the set.
245 const SetVector<ElaboratorValue> set;
246
247 // Store the set type such that we can materialize this evaluated value
248 // also in the case where the set is empty.
249 const Type type;
250};
251
252/// Storage object for an '!rtg.bag<T>'.
253struct BagStorage {
254 BagStorage(MapVector<ElaboratorValue, uint64_t> &&bag, Type type)
255 : hashcode(llvm::hash_combine(
256 type, llvm::hash_combine_range(bag.begin(), bag.end()))),
257 bag(std::move(bag)), type(type) {}
258
259 bool isEqual(const BagStorage *other) const {
260 return hashcode == other->hashcode && llvm::equal(bag, other->bag) &&
261 type == other->type;
262 }
263
264 // The cached hashcode to avoid repeated computations.
265 const unsigned hashcode;
266
267 // Stores the elaborated values contained in the bag with their number of
268 // occurences.
269 const MapVector<ElaboratorValue, uint64_t> bag;
270
271 // Store the bag type such that we can materialize this evaluated value
272 // also in the case where the bag is empty.
273 const Type type;
274};
275
276/// Storage object for an '!rtg.sequence'.
277struct SequenceStorage {
278 SequenceStorage(StringAttr familyName, SmallVector<ElaboratorValue> &&args)
279 : hashcode(llvm::hash_combine(
280 familyName, llvm::hash_combine_range(args.begin(), args.end()))),
281 familyName(familyName), args(std::move(args)) {}
282
283 bool isEqual(const SequenceStorage *other) const {
284 return hashcode == other->hashcode && familyName == other->familyName &&
285 args == other->args;
286 }
287
288 // The cached hashcode to avoid repeated computations.
289 const unsigned hashcode;
290
291 // The name of the sequence family this sequence is derived from.
292 const StringAttr familyName;
293
294 // The elaborator values used during substitution of the sequence family.
295 const SmallVector<ElaboratorValue> args;
296};
297
298/// Storage object for interleaved '!rtg.randomized_sequence'es.
299struct InterleavedSequenceStorage {
300 InterleavedSequenceStorage(SmallVector<ElaboratorValue> &&sequences,
301 uint32_t batchSize)
302 : sequences(std::move(sequences)), batchSize(batchSize),
303 hashcode(llvm::hash_combine(
304 llvm::hash_combine_range(sequences.begin(), sequences.end()),
305 batchSize)) {}
306
307 explicit InterleavedSequenceStorage(RandomizedSequenceStorage *sequence)
308 : sequences(SmallVector<ElaboratorValue>(1, sequence)), batchSize(1),
309 hashcode(llvm::hash_combine(
310 llvm::hash_combine_range(sequences.begin(), sequences.end()),
311 batchSize)) {}
312
313 bool isEqual(const InterleavedSequenceStorage *other) const {
314 return hashcode == other->hashcode && sequences == other->sequences &&
315 batchSize == other->batchSize;
316 }
317
318 const SmallVector<ElaboratorValue> sequences;
319
320 const uint32_t batchSize;
321
322 // The cached hashcode to avoid repeated computations.
323 const unsigned hashcode;
324};
325
326/// Storage object for '!rtg.array`-typed values.
327struct ArrayStorage {
328 ArrayStorage(Type type, SmallVector<ElaboratorValue> &&array)
329 : hashcode(llvm::hash_combine(
330 type, llvm::hash_combine_range(array.begin(), array.end()))),
331 type(type), array(array) {}
332
333 bool isEqual(const ArrayStorage *other) const {
334 return hashcode == other->hashcode && type == other->type &&
335 array == other->array;
336 }
337
338 // The cached hashcode to avoid repeated computations.
339 const unsigned hashcode;
340
341 /// The type of the array. This is necessary because an array of size 0
342 /// cannot be reconstructed without knowing the original element type.
343 const Type type;
344
345 /// The label name. For unique labels, this is just the prefix.
346 const SmallVector<ElaboratorValue> array;
347};
348
349/// Storage object for 'tuple`-typed values.
350struct TupleStorage : CachableStorage {
351 TupleStorage(SmallVector<ElaboratorValue> &&values)
352 : hashcode(llvm::hash_combine_range(values.begin(), values.end())),
353 values(std::move(values)) {}
354
355 bool isEqual(const TupleStorage *other) const {
356 return hashcode == other->hashcode && values == other->values;
357 }
358
359 // The cached hashcode to avoid repeated computations.
360 const unsigned hashcode;
361
362 const SmallVector<ElaboratorValue> values;
363};
364
365struct SymbolicComputationStorage {
366 SymbolicComputationStorage(const DenseMap<Value, ElaboratorValue> &state,
367 Operation *op)
368 : name(op->getName()), resultTypes(op->getResultTypes()),
369 operands(llvm::map_range(op->getOperands(),
370 [&](Value v) { return state.lookup(v); })),
371 attributes(op->getAttrDictionary()),
372 properties(op->getPropertiesAsAttribute()),
373 hashcode(llvm::hash_combine(name, llvm::hash_combine_range(resultTypes),
374 llvm::hash_combine_range(operands),
375 attributes, op->hashProperties())) {}
376
377 bool isEqual(const SymbolicComputationStorage *other) const {
378 return hashcode == other->hashcode && name == other->name &&
379 resultTypes == other->resultTypes && operands == other->operands &&
380 attributes == other->attributes && properties == other->properties;
381 }
382
383 const OperationName name;
384 const SmallVector<Type> resultTypes;
385 const SmallVector<ElaboratorValue> operands;
386 const DictionaryAttr attributes;
387 const Attribute properties;
388 const unsigned hashcode;
389};
390
391// Values with identity not intended to be internalized.
392//===----------------------------------------------------------------------===//
393
394/// Base class for storages that represent values with identity, i.e., two
395/// values are not considered equivalent if they are structurally the same, but
396/// each definition of such a value is unique. E.g., unique labels or virtual
397/// registers. These cannot be materialized anew in each nested sequence, but
398/// must be passed as arguments.
399struct IdentityValue {
400
401 IdentityValue(Type type, Location loc) : type(type), loc(loc) {}
402
403#ifndef NDEBUG
404
405 /// In debug mode, track whether this value was already materialized to
406 /// assert if it's illegally materialized multiple times.
407 ///
408 /// Instead of deleting operations defining these values and materializing
409 /// them again, we could retain the operations. However, we still need
410 /// specific storages to represent these values in some cases, e.g., to get
411 /// the size of a memory allocation. Also, elaboration of nested control-flow
412 /// regions (e.g. `scf.for`) relies on materialization of such values lazily
413 /// instead of cloning the operations eagerly.
414 bool alreadyMaterialized = false;
415
416#endif
417
418 const Type type;
419 const Location loc;
420};
421
422/// Represents a unique virtual register.
423struct VirtualRegisterStorage : IdentityValue {
424 VirtualRegisterStorage(VirtualRegisterConfigAttr allowedRegs, Type type,
425 Location loc)
426 : IdentityValue(type, loc), allowedRegs(allowedRegs) {}
427
428 // NOTE: we don't need an 'isEqual' function and 'hashcode' here because
429 // VirtualRegisters are never internalized.
430
431 // The list of fixed registers allowed to be selected for this virtual
432 // register.
433 const VirtualRegisterConfigAttr allowedRegs;
434};
435
436struct UniqueLabelStorage : IdentityValue {
437 UniqueLabelStorage(const ElaboratorValue &name, Location loc)
438 : IdentityValue(LabelType::get(loc->getContext()), loc), name(name) {}
439
440 // NOTE: we don't need an 'isEqual' function and 'hashcode' here because
441 // VirtualRegisters are never internalized.
442
443 /// The label name. For unique labels, this is just the prefix.
444 const ElaboratorValue name;
445};
446
447/// Storage object for '!rtg.isa.memoryblock`-typed values.
448struct MemoryBlockStorage : IdentityValue {
449 MemoryBlockStorage(const APInt &baseAddress, const APInt &endAddress,
450 Type type, Location loc)
451 : IdentityValue(type, loc), baseAddress(baseAddress),
452 endAddress(endAddress) {}
453
454 // The base address of the memory. The width of the APInt also represents the
455 // address width of the memory. This is an APInt to support memories of
456 // >64-bit machines.
457 const APInt baseAddress;
458
459 // The last address of the memory.
460 const APInt endAddress;
461};
462
463/// Storage object for '!rtg.isa.memory`-typed values.
464struct MemoryStorage : IdentityValue {
465 MemoryStorage(MemoryBlockStorage *memoryBlock, size_t size, size_t alignment,
466 Location loc)
467 : IdentityValue(MemoryType::get(memoryBlock->type.getContext(),
468 memoryBlock->baseAddress.getBitWidth()),
469 loc),
470 memoryBlock(memoryBlock), size(size), alignment(alignment) {}
471
472 MemoryBlockStorage *memoryBlock;
473 const size_t size;
474 const size_t alignment;
475};
476
477/// Storage object for an '!rtg.randomized_sequence'.
478struct RandomizedSequenceStorage : IdentityValue {
479 RandomizedSequenceStorage(ContextResourceAttrInterface context,
480 SequenceStorage *sequence, Location loc)
481 : IdentityValue(
482 RandomizedSequenceType::get(sequence->familyName.getContext()),
483 loc),
484 context(context), sequence(sequence) {}
485
486 // The context under which this sequence is placed.
487 const ContextResourceAttrInterface context;
488
489 const SequenceStorage *sequence;
490};
491
492/// Operation must have at least 1 result.
493struct SymbolicComputationWithIdentityStorage : IdentityValue {
494 SymbolicComputationWithIdentityStorage(
495 const DenseMap<Value, ElaboratorValue> &state, Operation *op)
496 : IdentityValue(op->getResult(0).getType(), op->getLoc()),
497 name(op->getName()), resultTypes(op->getResultTypes()),
498 operands(llvm::map_range(op->getOperands(),
499 [&](Value v) { return state.lookup(v); })),
500 attributes(op->getAttrDictionary()),
501 properties(op->getPropertiesAsAttribute()) {}
502
503 const OperationName name;
504 const SmallVector<Type> resultTypes;
505 const SmallVector<ElaboratorValue> operands;
506 const DictionaryAttr attributes;
507 const Attribute properties;
508};
509
510struct SymbolicComputationWithIdentityValue : IdentityValue {
511 SymbolicComputationWithIdentityValue(
512 Type type, const SymbolicComputationWithIdentityStorage *storage,
513 unsigned idx)
514 : IdentityValue(type, storage->loc), storage(storage), idx(idx) {
515 assert(
516 idx != 0 &&
517 "Use SymbolicComputationWithIdentityStorage for result with index 0.");
518 }
519
520 const SymbolicComputationWithIdentityStorage *storage;
521 const unsigned idx;
522};
523
524/// Storage for SSA values produced by external ops with regions — block
525/// arguments and results. Carries identity only; the corresponding new-IR
526/// `Value` is registered via `Materializer::map()` immediately upon creation so
527/// `Materializer::materialize()` always hits its cache and never dispatches
528// through `visit()`.
529struct OpaqueExternalStorage : IdentityValue {
530 OpaqueExternalStorage(Type type, Location loc) : IdentityValue(type, loc) {}
531};
532
533struct ContinuationBoundary {
534 uint64_t sequence = 0;
535 SmallVector<uint64_t> externalRegions;
536 uint64_t elaborator = 0;
537};
538
539/// Storage object for '!rtg.mut<T>' — a mutable elaboration-time cell.
540/// Each rtg.mut_create produces a unique cell (identity value).
541struct MutStorage : IdentityValue {
542 MutStorage(Type type, Location loc, ContinuationBoundary boundary)
543 : IdentityValue(type, loc), boundary(std::move(boundary)) {}
544
545 /// Boundaries whose lifetime contains this cell.
546 ContinuationBoundary boundary;
547};
548
549/// Maps effect names to their handler regions within a single rtg.handle op.
550struct HandlerFrame {
551 uint64_t promptToken = 0;
552 DenseMap<StringAttr, Region *> handlers;
553};
554
555/// A block invocation owns its lexical bindings and instruction cursor.
556struct BlockFrame {
557 Block *block = nullptr;
558 Operation *nextOp = nullptr;
559 bool keepTerminator = false;
560 bool eraseTerminator = true;
561 bool replay = false;
562 LexicalEnvironment environment;
563};
564
565/// Completion frames make nested-region control flow explicit and replayable.
566class Elaborator;
567
568struct ScfIfFrame {
569 scf::IfOp op;
570 bool replay = false;
571
572 LogicalResult step(Elaborator &elaborator,
573 ArrayRef<ElaboratorValue> yieldedValues);
574};
575
576struct ScfForFrame {
577 scf::ForOp op;
578 size_t nextIteration = 0;
579 size_t upperBound = 0;
580 size_t loopStep = 1;
581 bool replay = false;
582 bool completed = false;
583 LexicalEnvironment loopEnvironment;
584
585 LogicalResult step(Elaborator &elaborator,
586 ArrayRef<ElaboratorValue> yieldedValues);
587};
588
589struct RandomScopeFrame {
590 RandomScopeOp op;
591 RngScope outerRng;
592 bool replay = false;
593
594 RandomScopeFrame(RandomScopeOp op, RngScope outerRng, bool replay)
595 : op(op), outerRng(std::move(outerRng)), replay(replay) {}
596};
597
598struct WithHandlersFrame {
599 WithHandlersOp op;
600 uint64_t promptId = 0;
601 bool replay = false;
602};
603
604struct HandlerClauseFrame {};
605
606struct FunctionCallFrame {
607 func::CallOp call;
608};
609
610/// The materialized shell owns an external region while its block is visited.
611struct ExternalOpFrame {
612 Operation *originalOp = nullptr;
613 Operation *materializedOp = nullptr;
614 size_t nextRegion = 0;
615 uint64_t activeBoundary = 0;
616 bool replay = false;
617 LexicalEnvironment parentEnvironment;
618 OpBuilder::InsertPoint parentInsertionPoint;
619};
620
621/// Saves the handler and RNG context around one replay shot.
622struct ResumeFrame {
623 SmallVector<HandlerFrame> handlerStack;
624 RngScope rng;
625
626 ResumeFrame(SmallVector<HandlerFrame> handlerStack, RngScope rng)
627 : handlerStack(std::move(handlerStack)), rng(std::move(rng)) {}
628};
629
630using EvalFramePayload =
631 std::variant<BlockFrame, ScfIfFrame, ScfForFrame, RandomScopeFrame,
632 WithHandlersFrame, HandlerClauseFrame, FunctionCallFrame,
633 ExternalOpFrame, ResumeFrame>;
634
635struct EvalFrame {
636 EvalFramePayload payload;
637
638 template <typename FrameTy>
639 explicit EvalFrame(FrameTy &&frame) : payload(std::forward<FrameTy>(frame)) {}
640
641 EvalFrame(const EvalFrame &) = default;
642
643 std::unique_ptr<EvalFrame> clone() const {
644 return std::make_unique<EvalFrame>(*this);
645 }
646
647 template <typename FrameTy>
648 FrameTy *getIf() {
649 return std::get_if<FrameTy>(&payload);
650 }
651
652 template <typename FrameTy>
653 const FrameTy *getIf() const {
654 return std::get_if<FrameTy>(&payload);
655 }
656
657 template <typename FrameTy>
658 FrameTy &get() {
659 return std::get<FrameTy>(payload);
660 }
661
662 template <typename FrameTy>
663 bool holds() const {
664 return std::holds_alternative<FrameTy>(payload);
665 }
666};
667
668using EvalFrameStack = SmallVector<std::unique_ptr<EvalFrame>, 0>;
669
670/// Storage object for '!rtg.continuation<T>' — a captured elaboration frame.
671/// Holds a replayable lexical environment and evaluator frames to resume.
672struct ContinuationStorage : IdentityValue {
673 ContinuationStorage(EvalFrameStack capturedFrames, Value performResult,
674 SmallVector<HandlerFrame> capturedHandlerStack,
675 const LexicalEnvironment &capturedEnvironment,
676 RngScope capturedRng, uint64_t promptToken,
677 ContinuationBoundary boundary, Type resumeType,
678 Location loc)
679 : IdentityValue(ContinuationType::get(loc.getContext(), resumeType), loc),
680 capturedFrames(std::move(capturedFrames)), performResult(performResult),
681 capturedHandlerStack(std::move(capturedHandlerStack)),
682 capturedEnvironment(capturedEnvironment),
683 capturedRng(std::move(capturedRng)), promptToken(promptToken),
684 boundary(std::move(boundary)) {}
685
686 /// Suspended evaluator frames from the perform site up to the handler prompt.
687 EvalFrameStack capturedFrames;
688 /// The SSA result of the rtg.perform op (null for unit-result effects).
689 Value performResult;
690 /// Handler-stack snapshot at the point the perform was triggered.
691 SmallVector<HandlerFrame> capturedHandlerStack;
692 /// SSA bindings at the perform site. Identity-bearing cells remain shared.
693 LexicalEnvironment capturedEnvironment;
694 /// RNG state at the perform site, restored independently for each shot.
695 RngScope capturedRng;
696 /// Dynamic handler installation that delimits this continuation.
697 uint64_t promptToken;
698 /// Dynamic boundaries that the captured ops must not outlive.
699 ContinuationBoundary boundary;
700};
701
702/// An 'Internalizer' object internalizes storages and takes ownership of them.
703/// When the initializer object is destroyed, all owned storages are also
704/// deallocated and thus must not be accessed anymore.
705class Internalizer {
706public:
707 ~Internalizer() {
708 for (auto it = destructors.rbegin(); it != destructors.rend(); ++it)
709 it->second(it->first);
710 }
711
712 /// Internalize a storage of type `StorageTy` constructed with arguments
713 /// `args`. The pointers returned by this method can be used to compare
714 /// objects when, e.g., computing set differences, uniquing the elements in a
715 /// set, etc. Otherwise, we'd need to do a deep value comparison in those
716 /// situations.
717 template <typename StorageTy, typename... Args>
718 StorageTy *internalize(Args &&...args) {
719 static_assert(!std::is_base_of_v<IdentityValue, StorageTy> &&
720 "values with identity must not be internalized");
721
722 StorageTy storage(std::forward<Args>(args)...);
723
724 auto existing = getInternSet<StorageTy>().insert_as(
725 HashedStorage<StorageTy>(storage.hashcode), storage);
726 StorageTy *&storagePtr = existing.first->storage;
727 if (existing.second) {
728 storagePtr =
729 new (allocator.Allocate<StorageTy>()) StorageTy(std::move(storage));
730 registerDestructor(storagePtr);
731 }
732
733 return storagePtr;
734 }
735
736 template <typename StorageTy, typename... Args>
737 StorageTy *create(Args &&...args) {
738 static_assert(std::is_base_of_v<IdentityValue, StorageTy> &&
739 "values with structural equivalence must be internalized");
740
741 StorageTy *storage = new (allocator.Allocate<StorageTy>())
742 StorageTy(std::forward<Args>(args)...);
743 registerDestructor(storage);
744 return storage;
745 }
746
747private:
748 template <typename StorageTy>
749 void registerDestructor(StorageTy *storage) {
750 if constexpr (!std::is_trivially_destructible_v<StorageTy>)
751 destructors.emplace_back(storage, [](void *ptr) {
752 static_cast<StorageTy *>(ptr)->~StorageTy();
753 });
754 }
755
756 template <typename StorageTy>
757 DenseSet<HashedStorage<StorageTy>, StorageKeyInfo<StorageTy>> &
758 getInternSet() {
759 if constexpr (std::is_same_v<StorageTy, ArrayStorage>)
760 return internedArrays;
761 else if constexpr (std::is_same_v<StorageTy, SetStorage>)
762 return internedSets;
763 else if constexpr (std::is_same_v<StorageTy, BagStorage>)
764 return internedBags;
765 else if constexpr (std::is_same_v<StorageTy, SequenceStorage>)
766 return internedSequences;
767 else if constexpr (std::is_same_v<StorageTy, RandomizedSequenceStorage>)
768 return internedRandomizedSequences;
769 else if constexpr (std::is_same_v<StorageTy, InterleavedSequenceStorage>)
770 return internedInterleavedSequences;
771 else if constexpr (std::is_same_v<StorageTy, TupleStorage>)
772 return internedTuples;
773 else if constexpr (std::is_same_v<StorageTy, SymbolicComputationStorage>)
774 return internedSymbolicComputationWithIdentityValues;
775 else
776 static_assert(!sizeof(StorageTy),
777 "no intern set available for this storage type.");
778 }
779
780 // This allocator allocates raw storage on the heap and releases it when
781 // destroyed. The registered destructors above handle object lifetimes.
782 llvm::BumpPtrAllocator allocator;
783
784 /// BumpPtrAllocator releases raw storage but does not destroy objects placed
785 /// in it. Track non-trivial objects so their owned heap allocations are
786 /// freed.
787 SmallVector<std::pair<void *, void (*)(void *)>> destructors;
788
789 // The sets holding the internalized objects. We use one set per storage type
790 // such that we can have a simpler equality checking function (no need to
791 // compare some sort of TypeIDs).
792 DenseSet<HashedStorage<ArrayStorage>, StorageKeyInfo<ArrayStorage>>
793 internedArrays;
794 DenseSet<HashedStorage<SetStorage>, StorageKeyInfo<SetStorage>> internedSets;
795 DenseSet<HashedStorage<BagStorage>, StorageKeyInfo<BagStorage>> internedBags;
796 DenseSet<HashedStorage<SequenceStorage>, StorageKeyInfo<SequenceStorage>>
797 internedSequences;
798 DenseSet<HashedStorage<RandomizedSequenceStorage>,
799 StorageKeyInfo<RandomizedSequenceStorage>>
800 internedRandomizedSequences;
801 DenseSet<HashedStorage<InterleavedSequenceStorage>,
802 StorageKeyInfo<InterleavedSequenceStorage>>
803 internedInterleavedSequences;
804 DenseSet<HashedStorage<TupleStorage>, StorageKeyInfo<TupleStorage>>
805 internedTuples;
806 DenseSet<HashedStorage<SymbolicComputationStorage>,
807 StorageKeyInfo<SymbolicComputationStorage>>
808 internedSymbolicComputationWithIdentityValues;
809};
810
811} // namespace
812
813#ifndef NDEBUG
814
815static llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
816 const ElaboratorValue &value);
817
818static void print(TypedAttr val, llvm::raw_ostream &os) {
819 os << "<attr " << val << ">";
820}
821
822static void print(BagStorage *val, llvm::raw_ostream &os) {
823 os << "<bag {";
824 llvm::interleaveComma(val->bag, os,
825 [&](const std::pair<ElaboratorValue, uint64_t> &el) {
826 os << el.first << " -> " << el.second;
827 });
828 os << "} at " << val << ">";
829}
830
831static void print(bool val, llvm::raw_ostream &os) {
832 os << "<bool " << (val ? "true" : "false") << ">";
833}
834
835static void print(size_t val, llvm::raw_ostream &os) {
836 os << "<index " << val << ">";
837}
838
839static void print(SequenceStorage *val, llvm::raw_ostream &os) {
840 os << "<sequence @" << val->familyName.getValue() << "(";
841 llvm::interleaveComma(val->args, os,
842 [&](const ElaboratorValue &val) { os << val; });
843 os << ") at " << val << ">";
844}
845
846static void print(RandomizedSequenceStorage *val, llvm::raw_ostream &os) {
847 os << "<randomized-sequence derived from @"
848 << val->sequence->familyName.getValue() << " under context "
849 << val->context << "(";
850 llvm::interleaveComma(val->sequence->args, os,
851 [&](const ElaboratorValue &val) { os << val; });
852 os << ") at " << val << ">";
853}
854
855static void print(InterleavedSequenceStorage *val, llvm::raw_ostream &os) {
856 os << "<interleaved-sequence [";
857 llvm::interleaveComma(val->sequences, os,
858 [&](const ElaboratorValue &val) { os << val; });
859 os << "] batch-size " << val->batchSize << " at " << val << ">";
860}
861
862static void print(ArrayStorage *val, llvm::raw_ostream &os) {
863 os << "<array [";
864 llvm::interleaveComma(val->array, os,
865 [&](const ElaboratorValue &val) { os << val; });
866 os << "] at " << val << ">";
867}
868
869static void print(SetStorage *val, llvm::raw_ostream &os) {
870 os << "<set {";
871 llvm::interleaveComma(val->set, os,
872 [&](const ElaboratorValue &val) { os << val; });
873 os << "} at " << val << ">";
874}
875
876static void print(const VirtualRegisterStorage *val, llvm::raw_ostream &os) {
877 os << "<virtual-register " << val << " " << val->allowedRegs << ">";
878}
879
880static void print(const UniqueLabelStorage *val, llvm::raw_ostream &os) {
881 os << "<unique-label " << val << " " << val->name << ">";
882}
883
884static void print(const TupleStorage *val, llvm::raw_ostream &os) {
885 os << "<tuple (";
886 llvm::interleaveComma(val->values, os,
887 [&](const ElaboratorValue &val) { os << val; });
888 os << ")>";
889}
890
891static void print(const MemoryStorage *val, llvm::raw_ostream &os) {
892 os << "<memory {" << ElaboratorValue(val->memoryBlock)
893 << ", size=" << val->size << ", alignment=" << val->alignment << "}>";
894}
895
896static void print(const MemoryBlockStorage *val, llvm::raw_ostream &os) {
897 os << "<memory-block {"
898 << ", address-width=" << val->baseAddress.getBitWidth()
899 << ", base-address=" << val->baseAddress
900 << ", end-address=" << val->endAddress << "}>";
901}
902
903static void print(const SymbolicComputationWithIdentityValue *val,
904 llvm::raw_ostream &os) {
905 os << "<symbolic-computation-with-identity-value (" << val->storage << ") at "
906 << val->idx << ">";
907}
908
909static void print(const SymbolicComputationWithIdentityStorage *val,
910 llvm::raw_ostream &os) {
911 os << "<symbolic-computation-with-identity " << val->name << "(";
912 llvm::interleaveComma(val->operands, os,
913 [&](const ElaboratorValue &val) { os << val; });
914 os << ") -> " << val->resultTypes << " with attributes " << val->attributes
915 << " and properties " << val->properties;
916 os << ">";
917}
918
919static void print(const SymbolicComputationStorage *val,
920 llvm::raw_ostream &os) {
921 os << "<symbolic-computation " << val->name << "(";
922 llvm::interleaveComma(val->operands, os,
923 [&](const ElaboratorValue &val) { os << val; });
924 os << ") -> " << val->resultTypes << " with attributes " << val->attributes
925 << " and properties " << val->properties;
926 os << ">";
927}
928
929static void print(const OpaqueExternalStorage *val, llvm::raw_ostream &os) {
930 os << "<opaque-external " << val->type << ">";
931}
932
933static void print(const MutStorage *val, llvm::raw_ostream &os) {
934 os << "<mut at " << val << ">";
935}
936
937static void print(const ContinuationStorage *val, llvm::raw_ostream &os) {
938 os << "<continuation with " << val->capturedFrames.size() << " frames at "
939 << val << ">";
940}
941
942static llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
943 const ElaboratorValue &value) {
944 std::visit([&](auto val) { print(val, os); }, value);
945
946 return os;
947}
948
949#endif
950
951//===----------------------------------------------------------------------===//
952// Attribute <-> ElaboratorValue Converters
953//===----------------------------------------------------------------------===//
954
955namespace {
956
957/// Convert an attribute to an ElaboratorValue. This handles nested attributes
958/// like SetAttr and TupleAttr recursively.
959class AttributeToElaboratorValueConverter {
960public:
961 AttributeToElaboratorValueConverter(Internalizer &internalizer)
962 : internalizer(internalizer) {}
963
964 /// Convert an attribute to an ElaboratorValue.
965 FailureOr<ElaboratorValue> convert(Attribute attr) {
966 return llvm::TypeSwitch<Attribute, FailureOr<ElaboratorValue>>(attr)
967 .Case<IntegerAttr, SetAttr, TupleAttr>(
968 [&](auto attr) { return convert(attr); })
969 .Case<TypedAttr>([&](auto typedAttr) -> FailureOr<ElaboratorValue> {
970 return ElaboratorValue(typedAttr);
971 })
972 .Default(
973 [&](Attribute) -> FailureOr<ElaboratorValue> { return failure(); });
974 }
975
976private:
977 FailureOr<ElaboratorValue> convert(IntegerAttr attr) {
978 if (attr.getType().isSignlessInteger(1))
979 return ElaboratorValue(bool(attr.getInt()));
980 if (isa<IndexType>(attr.getType()))
981 return ElaboratorValue(size_t(attr.getInt()));
982 return ElaboratorValue(attr);
983 }
984
985 FailureOr<ElaboratorValue> convert(SetAttr setAttr) {
986 SetVector<ElaboratorValue> set;
987 for (auto element : *setAttr.getElements()) {
988 auto converted = convert(element);
989 if (failed(converted))
990 return failure();
991 set.insert(*converted);
992 }
993 auto *storage =
994 internalizer.internalize<SetStorage>(std::move(set), setAttr.getType());
995 // Cache the original attribute for efficient materialization
996 storage->attrCache = setAttr;
997 return ElaboratorValue(storage);
998 }
999
1000 FailureOr<ElaboratorValue> convert(TupleAttr tupleAttr) {
1001 SmallVector<ElaboratorValue> values;
1002 for (auto element : tupleAttr.getElements()) {
1003 auto converted = convert(element);
1004 if (failed(converted))
1005 return failure();
1006 values.push_back(*converted);
1007 }
1008 auto *storage = internalizer.internalize<TupleStorage>(std::move(values));
1009 // Cache the original attribute for efficient materialization
1010 storage->attrCache = tupleAttr;
1011 return ElaboratorValue(storage);
1012 }
1013
1014 Internalizer &internalizer;
1015};
1016
1017/// Convert an ElaboratorValue to an attribute when possible. Not all
1018/// ElaboratorValues can be converted to attributes (e.g., values with
1019/// identity).
1020class ElaboratorValueToAttributeConverter {
1021public:
1022 ElaboratorValueToAttributeConverter(MLIRContext *context)
1023 : context(context) {}
1024
1025 /// Convert an ElaboratorValue to an attribute. Returns a null attribute if
1026 /// the conversion is not possible.
1027 TypedAttr convert(const ElaboratorValue &value) {
1028 return std::visit(
1029 [&](auto val) -> TypedAttr {
1030 if constexpr (std::is_base_of_v<CachableStorage,
1031 std::remove_pointer_t<
1032 std::decay_t<decltype(value)>>>) {
1033 if (val->attrCache)
1034 return val->attrCache;
1035 }
1036 return visit(val);
1037 },
1038 value);
1039 }
1040
1041private:
1042 TypedAttr visit(TypedAttr val) { return val; }
1043
1044 TypedAttr visit(bool val) {
1045 return IntegerAttr::get(IntegerType::get(context, 1), val);
1046 }
1047
1048 TypedAttr visit(size_t val) {
1049 return IntegerAttr::get(IndexType::get(context), val);
1050 }
1051
1052 TypedAttr visit(SetStorage *val) {
1053 DenseSet<TypedAttr> elements;
1054 for (auto element : val->set) {
1055 auto converted = convert(element);
1056 if (!converted)
1057 return {};
1058 auto typedAttr = dyn_cast<TypedAttr>(converted);
1059 if (!typedAttr)
1060 return {};
1061 elements.insert(typedAttr);
1062 }
1063 return SetAttr::get(cast<SetType>(val->type), &elements);
1064 }
1065
1066 TypedAttr visit(TupleStorage *val) {
1067 SmallVector<TypedAttr> elements;
1068 for (auto element : val->values) {
1069 auto converted = convert(element);
1070 if (!converted)
1071 return {};
1072 auto typedAttr = dyn_cast<TypedAttr>(converted);
1073 if (!typedAttr)
1074 return {};
1075 elements.push_back(typedAttr);
1076 }
1077 return TupleAttr::get(context, elements);
1078 }
1079
1080 // Default implementation for storage types that cannot be converted to
1081 // attributes. Using a macro to avoid repetition. std::visit does not support
1082 // any kind of "default" clauses, unfortunately.
1083#define VISIT_UNSUPPORTED(STORAGETYPE) \
1084 /* NOLINTNEXTLINE(bugprone-macro-parentheses)*/ \
1085 TypedAttr visit(STORAGETYPE *val) { return {}; }
1086
1087 VISIT_UNSUPPORTED(ArrayStorage)
1088 VISIT_UNSUPPORTED(BagStorage)
1089 VISIT_UNSUPPORTED(SequenceStorage)
1090 VISIT_UNSUPPORTED(RandomizedSequenceStorage)
1091 VISIT_UNSUPPORTED(InterleavedSequenceStorage)
1092 VISIT_UNSUPPORTED(VirtualRegisterStorage)
1093 VISIT_UNSUPPORTED(UniqueLabelStorage)
1094 VISIT_UNSUPPORTED(MemoryStorage)
1095 VISIT_UNSUPPORTED(MemoryBlockStorage)
1096 VISIT_UNSUPPORTED(SymbolicComputationWithIdentityStorage)
1097 VISIT_UNSUPPORTED(SymbolicComputationWithIdentityValue)
1098 VISIT_UNSUPPORTED(SymbolicComputationStorage)
1099 VISIT_UNSUPPORTED(OpaqueExternalStorage)
1100 VISIT_UNSUPPORTED(MutStorage)
1101 VISIT_UNSUPPORTED(ContinuationStorage)
1102
1103#undef VISIT_UNSUPPORTED
1104
1105 MLIRContext *context;
1106};
1107
1108} // namespace
1109
1110//===----------------------------------------------------------------------===//
1111// Elaborator Value Materialization
1112//===----------------------------------------------------------------------===//
1113
1114namespace {
1115
1116/// State that should be shared by all elaborator and materializer instances.
1117struct SharedState {
1118 SharedState(MLIRContext *ctxt, SymbolTable &table)
1119 : ctxt(ctxt), table(table) {}
1120
1121 uint64_t createBoundaryToken() { return nextBoundaryToken++; }
1122
1123 MLIRContext *ctxt;
1124 SymbolTable &table;
1125 Namespace names;
1126 Internalizer internalizer;
1127 DenseMap<MutStorage *, ElaboratorValue> mutableStore;
1128
1129private:
1130 uint64_t nextBoundaryToken = 1;
1131};
1132
1133/// A collection of state per RTG test.
1134struct TestState {
1135 explicit TestState(unsigned seed) : rng(RngScope(seed)) {}
1136
1137 /// The name of the test.
1138 StringAttr name;
1139
1140 /// The context switches registered for this test.
1141 MapVector<
1142 std::pair<ContextResourceAttrInterface, ContextResourceAttrInterface>,
1143 SequenceStorage *>
1144 contextSwitches;
1145
1146 /// The root RNG scope for this test.
1147 RngScope rng;
1148};
1149
1150/// Construct an SSA value from a given elaborated value.
1151class Materializer {
1152public:
1153 Materializer(OpBuilder builder, TestState &testState,
1154 SharedState &sharedState,
1155 SmallVector<ElaboratorValue> &blockArgs)
1156 : builder(builder), rootBlock(builder.getBlock()), testState(testState),
1157 sharedState(sharedState), blockArgs(blockArgs),
1158 attrConverter(builder.getContext()) {}
1159
1160 /// Materialize IR representing the provided `ElaboratorValue` and return the
1161 /// `Value` or a null value on failure.
1162 Value materialize(ElaboratorValue val, Location loc,
1163 function_ref<InFlightDiagnostic()> emitError) {
1164 auto iter = materializedValues.find(val);
1165 if (iter != materializedValues.end())
1166 return iter->second;
1167
1168 LLVM_DEBUG(llvm::dbgs() << "Materializing " << val);
1169
1170 if (auto res = tryMaterializeAsConstant(val, loc))
1171 return res;
1172
1173 // In debug mode, track whether values with identity were already
1174 // materialized before and assert in such a situation.
1175 Value res = std::visit(
1176 [&](auto value) {
1177 if constexpr (std::is_base_of_v<IdentityValue,
1178 std::remove_pointer_t<
1179 std::decay_t<decltype(value)>>>) {
1180 if (identityValueRoot.contains(value)) {
1181#ifndef NDEBUG
1182 bool &materialized =
1183 static_cast<IdentityValue *>(value)->alreadyMaterialized;
1184 assert(!materialized && "must not already be materialized");
1185 materialized = true;
1186#endif
1187
1188 return visit(value, loc, emitError);
1189 }
1190
1191 Value arg = builder.getBlock()->addArgument(value->type, loc);
1192 blockArgs.push_back(val);
1193 blockArgTypes.push_back(arg.getType());
1194 materializedValues[val] = arg;
1195 return arg;
1196 }
1197
1198 return visit(value, loc, emitError);
1199 },
1200 val);
1201
1202 LLVM_DEBUG(llvm::dbgs() << " to\n" << res << "\n\n");
1203
1204 return res;
1205 }
1206
1207 bool isInPlace(Operation *op) const {
1208 return builder.getBlock()->getParent() == op->getParentRegion();
1209 }
1210
1211 /// If `op` is not in the same region as the materializer insertion point, a
1212 /// clone is created at the materializer's insertion point by also
1213 /// materializing the `ElaboratorValue`s for each operand just before it.
1214 /// Otherwise, all operations after the materializer's insertion point are
1215 /// deleted until `op` is reached. An error is returned if the operation is
1216 /// before the insertion point.
1217 LogicalResult materialize(Operation *op,
1218 DenseMap<Value, ElaboratorValue> &state,
1219 bool forceClone = false) {
1220 // Region-bearing ops must be elaborated away before reaching here (either
1221 // by a dedicated visitor, or by visitExternalOp which performs its own
1222 // structural rewrite). Routing them through this generic operand-only
1223 // materialize path would silently keep un-elaborated regions in the
1224 // output.
1225 if (op->getNumRegions() > 0)
1226 return op->emitOpError("ops with nested regions must be elaborated away");
1227
1228 // A retained operation's used results need an elaborated representation so
1229 // later operations can rematerialize them at the rewritten call site.
1230 for (auto res : op->getResults())
1231 if (!res.use_empty() && !state.contains(res))
1232 return op->emitOpError(
1233 "ops with results that have uses are not supported");
1234
1235 Operation *sourceOp = op;
1236 if (isInPlace(op) && !forceClone) {
1237 // We are doing in-place materialization, so mark all ops deleted until we
1238 // reach the one to be materialized and modify it in-place.
1239 deleteOpsUntil([&](auto iter) { return &*iter == op; });
1240
1241 if (builder.getInsertionPoint() == builder.getBlock()->end())
1242 return op->emitError("operation did not occur after the current "
1243 "materializer insertion point");
1244
1245 LLVM_DEBUG(llvm::dbgs() << "Modifying in-place: " << *op << "\n\n");
1246 } else {
1247 LLVM_DEBUG(llvm::dbgs() << "Materializing a clone of " << *op << "\n\n");
1248 op = builder.clone(*op);
1249 builder.setInsertionPoint(op);
1250 }
1251
1252 for (auto &operand : op->getOpOperands()) {
1253 auto emitError = [&]() {
1254 auto diag = op->emitError();
1255 diag.attachNote(op->getLoc())
1256 << "while materializing value for operand#"
1257 << operand.getOperandNumber();
1258 return diag;
1259 };
1260
1261 auto elabVal = state.at(operand.get());
1262 Value val = materialize(elabVal, op->getLoc(), emitError);
1263 if (!val)
1264 return failure();
1265
1266 state[val] = elabVal;
1267 operand.set(val);
1268 }
1269
1270 for (auto [sourceResult, result] :
1271 llvm::zip_equal(sourceOp->getResults(), op->getResults()))
1272 if (auto it = state.find(sourceResult); it != state.end())
1273 materializedValues[it->second] = result;
1274
1275 builder.setInsertionPointAfter(op);
1276 return success();
1277 }
1278
1279 /// Should be called once the `Region` is successfully materialized. No calls
1280 /// to `materialize` should happen after this anymore.
1281 void finalize() {
1282 deleteOpsUntil([](auto iter) { return false; });
1283
1284 for (auto *op : llvm::reverse(toDelete))
1285 op->erase();
1286 }
1287
1288 /// Tell this materializer that it is responsible for materializing the given
1289 /// identity value at the earliest position it is needed, and should't
1290 /// request the value via block argument.
1291 void registerIdentityValue(IdentityValue *val) {
1292 identityValueRoot.insert(val);
1293 }
1294
1295 ArrayRef<Type> getBlockArgTypes() const { return blockArgTypes; }
1296
1297 void map(ElaboratorValue eval, Value val) { materializedValues[eval] = val; }
1298
1299 OpBuilder &getBuilder() { return builder; }
1300
1301 template <typename OpTy, typename... Args>
1302 OpTy create(Location location, Args &&...args) {
1303 return OpTy::create(builder, location, std::forward<Args>(args)...);
1304 }
1305
1306private:
1307 Value tryMaterializeAsConstant(ElaboratorValue val, Location loc) {
1308 if (auto attr = attrConverter.convert(val)) {
1309 // Hoist constants to the materializer's root block when the builder's
1310 // current insertion point is in a nested region. This ensures the
1311 // constant's defining region dominates every potential use, including
1312 // sibling regions that share this materializer's value cache.
1313 OpBuilder::InsertionGuard guard(builder);
1314 if (builder.getBlock() != rootBlock)
1315 builder.setInsertionPointToStart(rootBlock);
1316 Value res = ConstantOp::create(builder, loc, attr);
1317 materializedValues[val] = res;
1318 return res;
1319 }
1320
1321 return Value();
1322 }
1323
1324 SequenceOp elaborateSequence(const RandomizedSequenceStorage *seq,
1325 SmallVector<ElaboratorValue> &elabArgs);
1326
1327 void deleteOpsUntil(function_ref<bool(Block::iterator)> stop) {
1328 auto ip = builder.getInsertionPoint();
1329 while (ip != builder.getBlock()->end() && !stop(ip)) {
1330 LLVM_DEBUG(llvm::dbgs() << "Marking to be deleted: " << *ip << "\n\n");
1331 toDelete.push_back(&*ip);
1332
1333 builder.setInsertionPointAfter(&*ip);
1334 ip = builder.getInsertionPoint();
1335 }
1336 }
1337
1338 Value visit(TypedAttr val, Location loc,
1339 function_ref<InFlightDiagnostic()> emitError) {
1340 return {};
1341 }
1342
1343 Value visit(size_t val, Location loc,
1344 function_ref<InFlightDiagnostic()> emitError) {
1345 return {};
1346 }
1347
1348 Value visit(bool val, Location loc,
1349 function_ref<InFlightDiagnostic()> emitError) {
1350 return {};
1351 }
1352
1353 Value visit(ArrayStorage *val, Location loc,
1354 function_ref<InFlightDiagnostic()> emitError) {
1355 SmallVector<Value> elements;
1356 elements.reserve(val->array.size());
1357 for (auto el : val->array) {
1358 auto materialized = materialize(el, loc, emitError);
1359 if (!materialized)
1360 return Value();
1361
1362 elements.push_back(materialized);
1363 }
1364
1365 Value res = ArrayCreateOp::create(builder, loc, val->type, elements);
1366 materializedValues[val] = res;
1367 return res;
1368 }
1369
1370 Value visit(SetStorage *val, Location loc,
1371 function_ref<InFlightDiagnostic()> emitError) {
1372 SmallVector<Value> elements;
1373 elements.reserve(val->set.size());
1374 for (auto el : val->set) {
1375 auto materialized = materialize(el, loc, emitError);
1376 if (!materialized)
1377 return Value();
1378
1379 elements.push_back(materialized);
1380 }
1381
1382 auto res = SetCreateOp::create(builder, loc, val->type, elements);
1383 materializedValues[val] = res;
1384 return res;
1385 }
1386
1387 Value visit(BagStorage *val, Location loc,
1388 function_ref<InFlightDiagnostic()> emitError) {
1389 SmallVector<Value> values, weights;
1390 values.reserve(val->bag.size());
1391 weights.reserve(val->bag.size());
1392 for (auto [val, weight] : val->bag) {
1393 auto materializedVal = materialize(val, loc, emitError);
1394 auto materializedWeight = materialize(weight, loc, emitError);
1395 if (!materializedVal || !materializedWeight)
1396 return Value();
1397
1398 values.push_back(materializedVal);
1399 weights.push_back(materializedWeight);
1400 }
1401
1402 auto res = BagCreateOp::create(builder, loc, val->type, values, weights);
1403 materializedValues[val] = res;
1404 return res;
1405 }
1406
1407 Value visit(MemoryBlockStorage *val, Location loc,
1408 function_ref<InFlightDiagnostic()> emitError) {
1409 auto intType = builder.getIntegerType(val->baseAddress.getBitWidth());
1410 Value res = MemoryBlockDeclareOp::create(
1411 builder, val->loc, val->type,
1412 IntegerAttr::get(intType, val->baseAddress),
1413 IntegerAttr::get(intType, val->endAddress));
1414 materializedValues[val] = res;
1415 return res;
1416 }
1417
1418 Value visit(MemoryStorage *val, Location loc,
1419 function_ref<InFlightDiagnostic()> emitError) {
1420 auto memBlock = materialize(val->memoryBlock, val->loc, emitError);
1421 auto memSize = materialize(val->size, val->loc, emitError);
1422 auto memAlign = materialize(val->alignment, val->loc, emitError);
1423 if (!(memBlock && memSize && memAlign))
1424 return {};
1425
1426 Value res =
1427 MemoryAllocOp::create(builder, val->loc, memBlock, memSize, memAlign);
1428 materializedValues[val] = res;
1429 return res;
1430 }
1431
1432 Value visit(SequenceStorage *val, Location loc,
1433 function_ref<InFlightDiagnostic()> emitError) {
1434 emitError() << "materializing a non-randomized sequence not supported yet";
1435 return Value();
1436 }
1437
1438 Value visit(RandomizedSequenceStorage *val, Location loc,
1439 function_ref<InFlightDiagnostic()> emitError) {
1440 // To know which values we have to pass by argument (and not just pass all
1441 // that migth be used eagerly), we have to elaborate the sequence family if
1442 // not already done so.
1443 // We need to get back the sequence to reference, and the list of elaborated
1444 // values to pass as arguments.
1445 SmallVector<ElaboratorValue> elabArgs;
1446 // NOTE: we wouldn't need to elaborate the sequence if it doesn't contain
1447 // randomness to be elaborated.
1448 SequenceOp seqOp = elaborateSequence(val, elabArgs);
1449 if (!seqOp)
1450 return {};
1451
1452 // Materialize all the values we need to pass as arguments and collect their
1453 // types.
1454 SmallVector<Value> args;
1455 SmallVector<Type> argTypes;
1456 for (auto arg : elabArgs) {
1457 Value materialized = materialize(arg, val->loc, emitError);
1458 if (!materialized)
1459 return {};
1460
1461 args.push_back(materialized);
1462 argTypes.push_back(materialized.getType());
1463 }
1464
1465 Value res = GetSequenceOp::create(
1466 builder, val->loc, SequenceType::get(builder.getContext(), argTypes),
1467 seqOp.getSymName());
1468
1469 // Only materialize a substitute_sequence op when we have arguments to
1470 // substitute since this op does not support 0 arguments.
1471 if (!args.empty())
1472 res = SubstituteSequenceOp::create(builder, val->loc, res, args);
1473
1474 res = RandomizeSequenceOp::create(builder, val->loc, res);
1475
1476 materializedValues[val] = res;
1477 return res;
1478 }
1479
1480 Value visit(InterleavedSequenceStorage *val, Location loc,
1481 function_ref<InFlightDiagnostic()> emitError) {
1482 SmallVector<Value> sequences;
1483 for (auto seqVal : val->sequences) {
1484 Value materialized = materialize(seqVal, loc, emitError);
1485 if (!materialized)
1486 return {};
1487
1488 sequences.push_back(materialized);
1489 }
1490
1491 if (sequences.size() == 1)
1492 return sequences[0];
1493
1494 Value res =
1495 InterleaveSequencesOp::create(builder, loc, sequences, val->batchSize);
1496 materializedValues[val] = res;
1497 return res;
1498 }
1499
1500 Value visit(VirtualRegisterStorage *val, Location loc,
1501 function_ref<InFlightDiagnostic()> emitError) {
1502 Value res = VirtualRegisterOp::create(builder, val->loc, val->allowedRegs);
1503 materializedValues[val] = res;
1504 return res;
1505 }
1506
1507 Value visit(UniqueLabelStorage *val, Location loc,
1508 function_ref<InFlightDiagnostic()> emitError) {
1509 auto materialized = materialize(val->name, val->loc, emitError);
1510 if (!materialized)
1511 return {};
1512 Value res = LabelUniqueDeclOp::create(builder, val->loc, materialized);
1513 materializedValues[val] = res;
1514 return res;
1515 }
1516
1517 Value visit(TupleStorage *val, Location loc,
1518 function_ref<InFlightDiagnostic()> emitError) {
1519 SmallVector<Value> materialized;
1520 materialized.reserve(val->values.size());
1521 for (auto v : val->values) {
1522 Value value = materialize(v, loc, emitError);
1523 if (!value)
1524 return {};
1525 materialized.push_back(value);
1526 }
1527 Value res = TupleCreateOp::create(builder, loc, materialized);
1528 materializedValues[val] = res;
1529 return res;
1530 }
1531
1532 Value visit(MutStorage *val, Location loc,
1533 function_ref<InFlightDiagnostic()> emitError) {
1534 emitError() << "mutable cell cannot escape into materialized IR";
1535 return {};
1536 }
1537
1538 Value visit(ContinuationStorage *val, Location loc,
1539 function_ref<InFlightDiagnostic()> emitError) {
1540 emitError() << "continuation cannot escape through materialized IR, "
1541 "a sequence, or an opaque external-region shell";
1542 return {};
1543 }
1544
1545 Value visit(SymbolicComputationWithIdentityValue *val, Location loc,
1546 function_ref<InFlightDiagnostic()> emitError) {
1547 auto *noConstStorage =
1548 const_cast<SymbolicComputationWithIdentityStorage *>(val->storage);
1549 auto res0 = materialize(noConstStorage, loc, emitError);
1550 if (!res0)
1551 return {};
1552
1553 auto *op = res0.getDefiningOp();
1554 auto res = op->getResults()[val->idx];
1555 materializedValues[val] = res;
1556 return res;
1557 }
1558
1559 Value visit(SymbolicComputationWithIdentityStorage *val, Location loc,
1560 function_ref<InFlightDiagnostic()> emitError) {
1561 SmallVector<Value> operands;
1562 for (auto operand : val->operands) {
1563 auto materialized = materialize(operand, val->loc, emitError);
1564 if (!materialized)
1565 return {};
1566
1567 operands.push_back(materialized);
1568 }
1569
1570 OperationState state(val->loc, val->name);
1571 state.addTypes(val->resultTypes);
1572 state.attributes = val->attributes;
1573 state.propertiesAttr = val->properties;
1574 state.addOperands(operands);
1575 auto *op = builder.create(state);
1576
1577 materializedValues[val] = op->getResult(0);
1578 return op->getResult(0);
1579 }
1580
1581 Value visit(SymbolicComputationStorage *val, Location loc,
1582 function_ref<InFlightDiagnostic()> emitError) {
1583 SmallVector<Value> operands;
1584 for (auto operand : val->operands) {
1585 auto materialized = materialize(operand, loc, emitError);
1586 if (!materialized)
1587 return {};
1588
1589 operands.push_back(materialized);
1590 }
1591
1592 OperationState state(loc, val->name);
1593 state.addTypes(val->resultTypes);
1594 state.attributes = val->attributes;
1595 state.propertiesAttr = val->properties;
1596 state.addOperands(operands);
1597 auto *op = builder.create(state);
1598
1599 for (auto res : op->getResults())
1600 materializedValues[val] = res;
1601
1602 return op->getResult(0);
1603 }
1604
1605 Value visit(OpaqueExternalStorage *val, Location loc,
1606 function_ref<InFlightDiagnostic()> emitError) {
1607 // Opaque external values (block arguments and results of external
1608 // region-bearing ops) are always pre-mapped to a concrete SSA value by
1609 // visitExternalOp, so materialize() short-circuits via the cache before
1610 // reaching here.
1611 emitError() << "cannot materialize opaque external value";
1612 return {};
1613 }
1614
1615private:
1616 /// Cache values we have already materialized to reuse them later. We start
1617 /// with an insertion point at the start of the block and cache the (updated)
1618 /// insertion point such that future materializations can also reuse previous
1619 /// materializations without running into dominance issues (or requiring
1620 /// additional checks to avoid them).
1621 DenseMap<ElaboratorValue, Value> materializedValues;
1622
1623 /// Cache the builder to continue insertions at their current insertion point
1624 /// for the reason stated above.
1625 OpBuilder builder;
1626
1627 /// The block this materializer was constructed at. Used as the hoist target
1628 /// for constants that would otherwise be sunk into nested sibling regions.
1629 Block *rootBlock;
1630
1631 SmallVector<Operation *> toDelete;
1632
1633 TestState &testState;
1634 SharedState &sharedState;
1635
1636 /// Keep track of the block arguments we had to add to this materializer's
1637 /// block for identity values and also remember which elaborator values are
1638 /// expected to be passed as arguments from outside.
1639 SmallVector<ElaboratorValue> &blockArgs;
1640 SmallVector<Type> blockArgTypes;
1641
1642 /// Identity values in this set are materialized by this materializer,
1643 /// otherwise they are added as block arguments and the block that wants to
1644 /// embed this sequence is expected to provide a value for it.
1645 DenseSet<IdentityValue *> identityValueRoot;
1646
1647 /// Helper to convert ElaboratorValues to attributes.
1648 ElaboratorValueToAttributeConverter attrConverter;
1649};
1650
1651//===----------------------------------------------------------------------===//
1652// Elaboration Visitor
1653//===----------------------------------------------------------------------===//
1654
1655/// Used to signal to the elaboration driver whether the operation should be
1656/// removed.
1657enum class DeletionKind { Keep, Delete };
1658
1659/// A perform yields control to the trampoline instead of recursively invoking
1660/// its handler. The prompt identifies the matching dynamic handler frame.
1661struct ControlTransfer {
1662 uint64_t promptId;
1663 PerformOp perform;
1664};
1665
1666/// Interprets the IR to perform and lower the represented randomizations.
1667class Elaborator : public RTGOpVisitor<Elaborator, FailureOr<DeletionKind>> {
1668public:
1669 using RTGBase = RTGOpVisitor<Elaborator, FailureOr<DeletionKind>>;
1670 using RTGBase::visitOp;
1671
1672 Elaborator(SharedState &sharedState, TestState &testState,
1673 Materializer &materializer,
1674 ContextResourceAttrInterface currentContext = {},
1675 uint64_t sequenceBoundaryToken = 0)
1676 : sharedState(sharedState), testState(testState),
1677 materializer(materializer), currentContext(currentContext),
1678 sequenceBoundaryToken(sequenceBoundaryToken),
1679 elaboratorBoundaryToken(sharedState.createBoundaryToken()),
1680 attrConverter(sharedState.internalizer),
1681 elabValConverter(sharedState.ctxt) {}
1682
1683 template <typename ValueTy>
1684 inline ValueTy get(Value val) const {
1685 return std::get<ValueTy>(state.at(val));
1686 }
1687
1688 /// Print a nice error message for operations we don't support yet.
1689 FailureOr<DeletionKind> visitUnhandledOp(Operation *op) {
1690 return visitOpGeneric(op);
1691 }
1692
1693 FailureOr<DeletionKind> visitExternalOp(Operation *op) {
1694 if (op->getNumRegions() == 0)
1695 return visitOpGeneric(op);
1696 return op->emitOpError(
1697 "region-bearing operations must be scheduled by the evaluator");
1698 }
1699
1700 FailureOr<DeletionKind> visitOp(GetSequenceOp op) {
1701 SmallVector<ElaboratorValue> replacements;
1702 state[op.getResult()] =
1703 sharedState.internalizer.internalize<SequenceStorage>(
1704 op.getSequenceAttr().getAttr(), std::move(replacements));
1705 return DeletionKind::Delete;
1706 }
1707
1708 FailureOr<DeletionKind> visitOp(SubstituteSequenceOp op) {
1709 if (isSymbolic(state.at(op.getSequence())))
1710 return visitOpGeneric(op);
1711
1712 auto *seq = get<SequenceStorage *>(op.getSequence());
1713
1714 SmallVector<ElaboratorValue> replacements(seq->args);
1715 for (auto replacement : op.getReplacements())
1716 replacements.push_back(state.at(replacement));
1717
1718 state[op.getResult()] =
1719 sharedState.internalizer.internalize<SequenceStorage>(
1720 seq->familyName, std::move(replacements));
1721
1722 return DeletionKind::Delete;
1723 }
1724
1725 FailureOr<DeletionKind> visitOp(RandomizeSequenceOp op) {
1726 auto *seq = get<SequenceStorage *>(op.getSequence());
1727 auto *randomizedSeq =
1728 sharedState.internalizer.create<RandomizedSequenceStorage>(
1729 currentContext, seq, op.getLoc());
1730 materializer.registerIdentityValue(randomizedSeq);
1731 state[op.getResult()] =
1732 sharedState.internalizer.internalize<InterleavedSequenceStorage>(
1733 randomizedSeq);
1734 return DeletionKind::Delete;
1735 }
1736
1737 FailureOr<DeletionKind> visitOp(InterleaveSequencesOp op) {
1738 SmallVector<ElaboratorValue> sequences;
1739 for (auto seq : op.getSequences())
1740 sequences.push_back(state.at(seq));
1741
1742 state[op.getResult()] =
1743 sharedState.internalizer.internalize<InterleavedSequenceStorage>(
1744 std::move(sequences), op.getBatchSize());
1745 return DeletionKind::Delete;
1746 }
1747
1748 // NOLINTNEXTLINE(misc-no-recursion)
1749 LogicalResult isValidContext(ElaboratorValue value, Operation *op) const {
1750 if (std::holds_alternative<RandomizedSequenceStorage *>(value)) {
1751 auto *seq = std::get<RandomizedSequenceStorage *>(value);
1752 if (seq->context != currentContext) {
1753 auto err = op->emitError("attempting to place sequence derived from ")
1754 << seq->sequence->familyName.getValue() << " under context "
1755 << currentContext
1756 << ", but it was previously randomized for context ";
1757 if (seq->context)
1758 err << seq->context;
1759 else
1760 err << "'default'";
1761 return err;
1762 }
1763 return success();
1764 }
1765
1766 auto *interVal = std::get<InterleavedSequenceStorage *>(value);
1767 for (auto val : interVal->sequences)
1768 if (failed(isValidContext(val, op)))
1769 return failure();
1770 return success();
1771 }
1772
1773 FailureOr<DeletionKind> visitOp(EmbedSequenceOp op) {
1774 auto *seqVal = get<InterleavedSequenceStorage *>(op.getSequence());
1775 if (failed(isValidContext(seqVal, op)))
1776 return failure();
1777
1778 return DeletionKind::Keep;
1779 }
1780
1781 FailureOr<DeletionKind> visitOp(SetCreateOp op) {
1782 SetVector<ElaboratorValue> set;
1783 for (auto val : op.getElements())
1784 set.insert(state.at(val));
1785
1786 state[op.getSet()] = sharedState.internalizer.internalize<SetStorage>(
1787 std::move(set), op.getSet().getType());
1788 return DeletionKind::Delete;
1789 }
1790
1791 FailureOr<DeletionKind> visitOp(SetSelectRandomOp op) {
1792 auto set = get<SetStorage *>(op.getSet())->set;
1793
1794 if (set.empty())
1795 return op->emitError("cannot select from an empty set");
1796
1797 size_t selected = testState.rng.getUniformlyInRange(0, set.size() - 1);
1798 state[op.getResult()] = set[selected];
1799 return DeletionKind::Delete;
1800 }
1801
1802 FailureOr<DeletionKind> visitOp(SetDifferenceOp op) {
1803 auto original = get<SetStorage *>(op.getOriginal())->set;
1804 auto diff = get<SetStorage *>(op.getDiff())->set;
1805
1806 SetVector<ElaboratorValue> result(original);
1807 result.set_subtract(diff);
1808
1809 state[op.getResult()] = sharedState.internalizer.internalize<SetStorage>(
1810 std::move(result), op.getResult().getType());
1811 return DeletionKind::Delete;
1812 }
1813
1814 FailureOr<DeletionKind> visitOp(SetUnionOp op) {
1815 SetVector<ElaboratorValue> result;
1816 for (auto set : op.getSets())
1817 result.set_union(get<SetStorage *>(set)->set);
1818
1819 state[op.getResult()] = sharedState.internalizer.internalize<SetStorage>(
1820 std::move(result), op.getType());
1821 return DeletionKind::Delete;
1822 }
1823
1824 FailureOr<DeletionKind> visitOp(SetSizeOp op) {
1825 auto size = get<SetStorage *>(op.getSet())->set.size();
1826 state[op.getResult()] = size;
1827 return DeletionKind::Delete;
1828 }
1829
1830 // {a0,a1} x {b0,b1} x {c0,c1} -> {(a0), (a1)} -> {(a0,b0), (a0,b1), (a1,b0),
1831 // (a1,b1)} -> {(a0,b0,c0), (a0,b0,c1), (a0,b1,c0), (a0,b1,c1), (a1,b0,c0),
1832 // (a1,b0,c1), (a1,b1,c0), (a1,b1,c1)}
1833 FailureOr<DeletionKind> visitOp(SetCartesianProductOp op) {
1834 SetVector<ElaboratorValue> result;
1835 SmallVector<SmallVector<ElaboratorValue>> tuples;
1836 tuples.push_back({});
1837
1838 for (auto input : op.getInputs()) {
1839 auto &set = get<SetStorage *>(input)->set;
1840 if (set.empty()) {
1841 SetVector<ElaboratorValue> empty;
1842 state[op.getResult()] =
1843 sharedState.internalizer.internalize<SetStorage>(std::move(empty),
1844 op.getType());
1845 return DeletionKind::Delete;
1846 }
1847
1848 for (unsigned i = 0, e = tuples.size(); i < e; ++i) {
1849 for (auto setEl : set.getArrayRef().drop_back()) {
1850 tuples.push_back(tuples[i]);
1851 tuples.back().push_back(setEl);
1852 }
1853 tuples[i].push_back(set.back());
1854 }
1855 }
1856
1857 for (auto &tup : tuples)
1858 result.insert(
1859 sharedState.internalizer.internalize<TupleStorage>(std::move(tup)));
1860
1861 state[op.getResult()] = sharedState.internalizer.internalize<SetStorage>(
1862 std::move(result), op.getType());
1863 return DeletionKind::Delete;
1864 }
1865
1866 FailureOr<DeletionKind> visitOp(SetConvertToBagOp op) {
1867 auto set = get<SetStorage *>(op.getInput())->set;
1868 MapVector<ElaboratorValue, uint64_t> bag;
1869 for (auto val : set)
1870 bag.insert({val, 1});
1871 state[op.getResult()] = sharedState.internalizer.internalize<BagStorage>(
1872 std::move(bag), op.getType());
1873 return DeletionKind::Delete;
1874 }
1875
1876 FailureOr<DeletionKind> visitOp(BagCreateOp op) {
1877 MapVector<ElaboratorValue, uint64_t> bag;
1878 for (auto [val, multiple] :
1879 llvm::zip(op.getElements(), op.getMultiples())) {
1880 // If the multiple is not stored as an AttributeValue, the elaboration
1881 // must have already failed earlier (since we don't have
1882 // unevaluated/opaque values).
1883 bag[state.at(val)] += get<size_t>(multiple);
1884 }
1885
1886 state[op.getBag()] = sharedState.internalizer.internalize<BagStorage>(
1887 std::move(bag), op.getType());
1888 return DeletionKind::Delete;
1889 }
1890
1891 FailureOr<DeletionKind> visitOp(BagSelectRandomOp op) {
1892 auto bag = get<BagStorage *>(op.getBag())->bag;
1893
1894 if (bag.empty())
1895 return op->emitError("cannot select from an empty bag");
1896
1897 SmallVector<std::pair<ElaboratorValue, uint32_t>> prefixSum;
1898 prefixSum.reserve(bag.size());
1899 uint32_t accumulator = 0;
1900 for (auto [val, weight] : bag) {
1901 accumulator += weight;
1902 prefixSum.push_back({val, accumulator});
1903 }
1904
1905 auto idx = testState.rng.getUniformlyInRange(0, accumulator - 1);
1906 auto *iter = llvm::upper_bound(
1907 prefixSum, idx,
1908 [](uint32_t a, const std::pair<ElaboratorValue, uint32_t> &b) {
1909 return a < b.second;
1910 });
1911
1912 state[op.getResult()] = iter->first;
1913 return DeletionKind::Delete;
1914 }
1915
1916 FailureOr<DeletionKind> visitOp(BagDifferenceOp op) {
1917 auto original = get<BagStorage *>(op.getOriginal())->bag;
1918 auto diff = get<BagStorage *>(op.getDiff())->bag;
1919
1920 MapVector<ElaboratorValue, uint64_t> result;
1921 for (const auto &el : original) {
1922 if (!diff.contains(el.first)) {
1923 result.insert(el);
1924 continue;
1925 }
1926
1927 if (op.getInf())
1928 continue;
1929
1930 auto toDiff = diff.lookup(el.first);
1931 if (el.second <= toDiff)
1932 continue;
1933
1934 result.insert({el.first, el.second - toDiff});
1935 }
1936
1937 state[op.getResult()] = sharedState.internalizer.internalize<BagStorage>(
1938 std::move(result), op.getType());
1939 return DeletionKind::Delete;
1940 }
1941
1942 FailureOr<DeletionKind> visitOp(BagUnionOp op) {
1943 MapVector<ElaboratorValue, uint64_t> result;
1944 for (auto bag : op.getBags()) {
1945 auto val = get<BagStorage *>(bag)->bag;
1946 for (auto [el, multiple] : val)
1947 result[el] += multiple;
1948 }
1949
1950 state[op.getResult()] = sharedState.internalizer.internalize<BagStorage>(
1951 std::move(result), op.getType());
1952 return DeletionKind::Delete;
1953 }
1954
1955 FailureOr<DeletionKind> visitOp(BagUniqueSizeOp op) {
1956 auto size = get<BagStorage *>(op.getBag())->bag.size();
1957 state[op.getResult()] = size;
1958 return DeletionKind::Delete;
1959 }
1960
1961 FailureOr<DeletionKind> visitOp(BagConvertToSetOp op) {
1962 auto bag = get<BagStorage *>(op.getInput())->bag;
1963 SetVector<ElaboratorValue> set;
1964 for (auto [k, v] : bag)
1965 set.insert(k);
1966 state[op.getResult()] = sharedState.internalizer.internalize<SetStorage>(
1967 std::move(set), op.getType());
1968 return DeletionKind::Delete;
1969 }
1970
1971 FailureOr<DeletionKind> visitOp(VirtualRegisterOp op) {
1972 auto *val = sharedState.internalizer.create<VirtualRegisterStorage>(
1973 op.getAllowedRegsAttr(), op.getType(), op.getLoc());
1974 state[op.getResult()] = val;
1975 materializer.registerIdentityValue(val);
1976 return DeletionKind::Delete;
1977 }
1978
1979 FailureOr<DeletionKind> visitOp(ArrayCreateOp op) {
1980 SmallVector<ElaboratorValue> array;
1981 array.reserve(op.getElements().size());
1982 for (auto val : op.getElements())
1983 array.emplace_back(state.at(val));
1984
1985 state[op.getResult()] = sharedState.internalizer.internalize<ArrayStorage>(
1986 op.getResult().getType(), std::move(array));
1987 return DeletionKind::Delete;
1988 }
1989
1990 FailureOr<DeletionKind> visitOp(StringToASCIIArrayOp op) {
1991 auto opaque = state.at(op.getString());
1992 if (isSymbolic(opaque))
1993 return visitOpGeneric(op);
1994
1995 auto strAttr = dyn_cast<StringAttr>(std::get<TypedAttr>(opaque));
1996 if (!strAttr)
1997 return op->emitError("expected a string attribute");
1998
1999 auto i8Ty = IntegerType::get(op.getContext(), 8);
2000 SmallVector<ElaboratorValue> array;
2001 array.reserve(strAttr.getValue().size());
2002 for (unsigned char c : strAttr.getValue())
2003 array.push_back(ElaboratorValue(IntegerAttr::get(i8Ty, c)));
2004
2005 state[op.getResult()] = sharedState.internalizer.internalize<ArrayStorage>(
2006 op.getResult().getType(), std::move(array));
2007 return DeletionKind::Delete;
2008 }
2009
2010 FailureOr<DeletionKind> visitOp(ArrayExtractOp op) {
2011 auto array = get<ArrayStorage *>(op.getArray())->array;
2012 size_t idx = get<size_t>(op.getIndex());
2013
2014 if (array.size() <= idx)
2015 return op->emitError("invalid to access index ")
2016 << idx << " of an array with " << array.size() << " elements";
2017
2018 state[op.getResult()] = array[idx];
2019 return DeletionKind::Delete;
2020 }
2021
2022 FailureOr<DeletionKind> visitOp(ArrayInjectOp op) {
2023 auto arrayOpaque = state.at(op.getArray());
2024 auto idxOpaque = state.at(op.getIndex());
2025 if (isSymbolic(arrayOpaque) || isSymbolic(idxOpaque))
2026 return visitOpGeneric(op);
2027
2028 auto array = std::get<ArrayStorage *>(arrayOpaque)->array;
2029 size_t idx = std::get<size_t>(idxOpaque);
2030
2031 if (array.size() <= idx)
2032 return op->emitError("invalid to access index ")
2033 << idx << " of an array with " << array.size() << " elements";
2034
2035 array[idx] = state.at(op.getValue());
2036 state[op.getResult()] = sharedState.internalizer.internalize<ArrayStorage>(
2037 op.getResult().getType(), std::move(array));
2038 return DeletionKind::Delete;
2039 }
2040
2041 FailureOr<DeletionKind> visitOp(ArrayAppendOp op) {
2042 auto array = std::get<ArrayStorage *>(state.at(op.getArray()))->array;
2043 array.push_back(state.at(op.getElement()));
2044 state[op.getResult()] = sharedState.internalizer.internalize<ArrayStorage>(
2045 op.getResult().getType(), std::move(array));
2046 return DeletionKind::Delete;
2047 }
2048
2049 FailureOr<DeletionKind> visitOp(ArraySizeOp op) {
2050 auto array = get<ArrayStorage *>(op.getArray())->array;
2051 state[op.getResult()] = array.size();
2052 return DeletionKind::Delete;
2053 }
2054
2055 FailureOr<DeletionKind> visitOp(LabelUniqueDeclOp op) {
2056 auto *val = sharedState.internalizer.create<UniqueLabelStorage>(
2057 state.at(op.getNamePrefix()), op.getLoc());
2058 state[op.getLabel()] = val;
2059 materializer.registerIdentityValue(val);
2060 return DeletionKind::Delete;
2061 }
2062
2063 FailureOr<DeletionKind> visitOp(RandomScopeOp op) {
2064 return op->emitOpError("random_scope must be scheduled by the evaluator");
2065 }
2066
2067 FailureOr<DeletionKind> visitOp(RandomNumberInRangeOp op) {
2068 size_t lower = get<size_t>(op.getLowerBound());
2069 size_t upper = get<size_t>(op.getUpperBound());
2070 if (lower > upper)
2071 return op->emitError("cannot select a number from an empty range");
2072
2073 state[op.getResult()] =
2074 size_t(testState.rng.getUniformlyInRange(lower, upper));
2075 return DeletionKind::Delete;
2076 }
2077
2078 FailureOr<DeletionKind> visitOp(IntToImmediateOp op) {
2079 size_t input = get<size_t>(op.getInput());
2080 auto width = op.getType().getWidth();
2081 auto emitError = [&]() { return op->emitError(); };
2082 if (input > APInt::getAllOnes(width).getZExtValue())
2083 return emitError() << "cannot represent " << input << " with " << width
2084 << " bits";
2085
2086 state[op.getResult()] =
2087 IntegerAttr::get(IntegerType::get(op.getContext(), width), input);
2088 return DeletionKind::Delete;
2089 }
2090
2091 FailureOr<DeletionKind> visitOp(OnContextOp op) {
2092 ContextResourceAttrInterface from = currentContext,
2093 to = cast<ContextResourceAttrInterface>(
2094 get<TypedAttr>(op.getContext()));
2095 if (!currentContext)
2096 from = DefaultContextAttr::get(op->getContext(), to.getType());
2097
2098 auto emitError = [&]() {
2099 auto diag = op.emitError();
2100 diag.attachNote(op.getLoc())
2101 << "while materializing value for context switching for " << op;
2102 return diag;
2103 };
2104
2105 if (from == to) {
2106 Value seqVal = materializer.materialize(
2107 get<SequenceStorage *>(op.getSequence()), op.getLoc(), emitError);
2108 if (!seqVal)
2109 return failure();
2110
2111 Value randSeqVal =
2112 materializer.create<RandomizeSequenceOp>(op.getLoc(), seqVal);
2113 materializer.create<EmbedSequenceOp>(op.getLoc(), randSeqVal);
2114 return DeletionKind::Delete;
2115 }
2116
2117 // Switch to the desired context.
2118 // First, check if a context switch is registered that has the concrete
2119 // context as source and target.
2120 auto *iter = testState.contextSwitches.find({from, to});
2121
2122 // Try with 'any' context as target and the concrete context as source.
2123 if (iter == testState.contextSwitches.end())
2124 iter = testState.contextSwitches.find(
2125 {from, AnyContextAttr::get(op->getContext(), to.getType())});
2126
2127 // Try with 'any' context as source and the concrete context as target.
2128 if (iter == testState.contextSwitches.end())
2129 iter = testState.contextSwitches.find(
2130 {AnyContextAttr::get(op->getContext(), from.getType()), to});
2131
2132 // Try with 'any' context for both the source and the target.
2133 if (iter == testState.contextSwitches.end())
2134 iter = testState.contextSwitches.find(
2135 {AnyContextAttr::get(op->getContext(), from.getType()),
2136 AnyContextAttr::get(op->getContext(), to.getType())});
2137
2138 // Otherwise, fail with an error because we couldn't find a user
2139 // specification on how to switch between the requested contexts.
2140 // NOTE: we could think about supporting context switching via intermediate
2141 // context, i.e., treat it as a transitive relation.
2142 if (iter == testState.contextSwitches.end())
2143 return op->emitError("no context transition registered to switch from ")
2144 << from << " to " << to;
2145
2146 auto familyName = iter->second->familyName;
2147 SmallVector<ElaboratorValue> args{from, to,
2148 get<SequenceStorage *>(op.getSequence())};
2149 auto *seq = sharedState.internalizer.internalize<SequenceStorage>(
2150 familyName, std::move(args));
2151 auto *randSeq = sharedState.internalizer.create<RandomizedSequenceStorage>(
2152 to, seq, op.getLoc());
2153 materializer.registerIdentityValue(randSeq);
2154 Value seqVal = materializer.materialize(randSeq, op.getLoc(), emitError);
2155 if (!seqVal)
2156 return failure();
2157
2158 materializer.create<EmbedSequenceOp>(op.getLoc(), seqVal);
2159 return DeletionKind::Delete;
2160 }
2161
2162 FailureOr<DeletionKind> visitOp(ContextSwitchOp op) {
2163 testState.contextSwitches[{op.getFromAttr(), op.getToAttr()}] =
2164 get<SequenceStorage *>(op.getSequence());
2165 return DeletionKind::Delete;
2166 }
2167
2168 FailureOr<DeletionKind> visitOp(MemoryBlockDeclareOp op) {
2169 auto *val = sharedState.internalizer.create<MemoryBlockStorage>(
2170 op.getBaseAddress(), op.getEndAddress(), op.getType(), op.getLoc());
2171 state[op.getResult()] = val;
2172 materializer.registerIdentityValue(val);
2173 return DeletionKind::Delete;
2174 }
2175
2176 FailureOr<DeletionKind> visitOp(MemoryAllocOp op) {
2177 size_t size = get<size_t>(op.getSize());
2178 size_t alignment = get<size_t>(op.getAlignment());
2179 auto *memBlock = get<MemoryBlockStorage *>(op.getMemoryBlock());
2180 auto *val = sharedState.internalizer.create<MemoryStorage>(
2181 memBlock, size, alignment, op.getLoc());
2182 state[op.getResult()] = val;
2183 materializer.registerIdentityValue(val);
2184 return DeletionKind::Delete;
2185 }
2186
2187 FailureOr<DeletionKind> visitOp(MemorySizeOp op) {
2188 auto *memory = get<MemoryStorage *>(op.getMemory());
2189 state[op.getResult()] = memory->size;
2190 return DeletionKind::Delete;
2191 }
2192
2193 // Mutable cell ops
2194 //===--------------------------------------------------------------------===//
2195
2196 FailureOr<DeletionKind> visitOp(MutCreateOp op) {
2197 auto *mut = sharedState.internalizer.create<MutStorage>(
2198 op.getRef().getType(), op.getLoc(),
2199 ContinuationBoundary{sequenceBoundaryToken, externalRegionStack,
2200 elaboratorBoundaryToken});
2201 sharedState.mutableStore[mut] = state.at(op.getInitialValue());
2202 state[op.getRef()] = mut;
2203 materializer.registerIdentityValue(mut);
2204 return DeletionKind::Delete;
2205 }
2206
2207 FailureOr<MutStorage *> getMutableCell(Value ref, Operation *op) {
2208 auto it = state.find(ref);
2209 if (it == state.end())
2210 return op->emitOpError("mutable reference has no elaborated value");
2211
2212 auto *mut = std::get_if<MutStorage *>(&it->second);
2213 if (!mut || !*mut || !sharedState.mutableStore.contains(*mut))
2214 return op->emitOpError("invalid mutable cell reference");
2215
2216 return *mut;
2217 }
2218
2219 void collectContinuations(ElaboratorValue value,
2220 SmallVectorImpl<ContinuationStorage *> &result,
2221 SmallPtrSetImpl<const void *> &visited) const {
2222 if (auto *continuation = std::get_if<ContinuationStorage *>(&value)) {
2223 result.push_back(*continuation);
2224 return;
2225 }
2226
2227 if (auto *mut = std::get_if<MutStorage *>(&value)) {
2228 if (!visited.insert(*mut).second)
2229 return;
2230 auto it = sharedState.mutableStore.find(*mut);
2231 if (it != sharedState.mutableStore.end())
2232 collectContinuations(it->second, result, visited);
2233 return;
2234 }
2235
2236 auto collectRange = [&](auto &&range) {
2237 for (auto nested : range)
2238 collectContinuations(nested, result, visited);
2239 };
2240 if (auto *set = std::get_if<SetStorage *>(&value)) {
2241 if (visited.insert(*set).second)
2242 collectRange((*set)->set);
2243 } else if (auto *bag = std::get_if<BagStorage *>(&value)) {
2244 if (visited.insert(*bag).second)
2245 for (const auto &entry : (*bag)->bag)
2246 collectContinuations(entry.first, result, visited);
2247 } else if (auto *array = std::get_if<ArrayStorage *>(&value)) {
2248 if (visited.insert(*array).second)
2249 collectRange((*array)->array);
2250 } else if (auto *tuple = std::get_if<TupleStorage *>(&value)) {
2251 if (visited.insert(*tuple).second)
2252 collectRange((*tuple)->values);
2253 } else if (auto *symbolic =
2254 std::get_if<SymbolicComputationStorage *>(&value)) {
2255 if (visited.insert(*symbolic).second)
2256 collectRange((*symbolic)->operands);
2257 } else if (auto *symbolic =
2258 std::get_if<SymbolicComputationWithIdentityStorage *>(
2259 &value)) {
2260 if (visited.insert(*symbolic).second)
2261 collectRange((*symbolic)->operands);
2262 } else if (auto *symbolic =
2263 std::get_if<SymbolicComputationWithIdentityValue *>(
2264 &value)) {
2265 if (visited.insert(*symbolic).second)
2266 collectRange((*symbolic)->storage->operands);
2267 }
2268 }
2269
2270 FailureOr<DeletionKind> visitOp(MutReadOp op) {
2271 auto mut = getMutableCell(op.getRef(), op);
2272 if (failed(mut))
2273 return failure();
2274 state[op.getValue()] = sharedState.mutableStore.at(*mut);
2275 return DeletionKind::Delete;
2276 }
2277
2278 FailureOr<DeletionKind> visitOp(MutWriteOp op) {
2279 auto mut = getMutableCell(op.getRef(), op);
2280 if (failed(mut))
2281 return failure();
2282
2283 SmallVector<ContinuationStorage *> continuations;
2284 SmallPtrSet<const void *, 16> visited;
2285 collectContinuations(state.at(op.getNewValue()), continuations, visited);
2286 for (auto *continuation : continuations) {
2287 if (continuation->boundary.sequence != (*mut)->boundary.sequence)
2288 return op->emitOpError("continuation cannot escape a sequence boundary "
2289 "through mutable storage");
2290 if (continuation->boundary.elaborator != (*mut)->boundary.elaborator)
2291 return op->emitOpError(
2292 "continuation cannot escape its elaborator invocation through "
2293 "mutable storage");
2294 if (!llvm::equal(continuation->boundary.externalRegions,
2295 (*mut)->boundary.externalRegions))
2296 return op->emitOpError("continuation cannot escape an opaque "
2297 "external-region shell through mutable storage");
2298 }
2299
2300 sharedState.mutableStore[*mut] = state.at(op.getNewValue());
2301 return DeletionKind::Delete;
2302 }
2303
2304 // Effect handler ops
2305 //===--------------------------------------------------------------------===//
2306
2307 FailureOr<DeletionKind> visitOp(EffectOp op) { return DeletionKind::Keep; }
2308
2309 FailureOr<DeletionKind> visitOp(WithHandlersOp op) {
2310 return op->emitOpError("with_handlers must be scheduled by the evaluator");
2311 }
2312
2313 FailureOr<DeletionKind> visitOp(PerformOp op) {
2314 return op->emitOpError("perform must return a control transfer");
2315 }
2316
2317 FailureOr<DeletionKind> visitOp(ResumeOp op) {
2318 return op->emitOpError("resume must be scheduled by the evaluator");
2319 }
2320
2321 //===--------------------------------------------------------------------===//
2322
2323 FailureOr<DeletionKind> visitOp(TupleCreateOp op) {
2324 SmallVector<ElaboratorValue> values;
2325 values.reserve(op.getElements().size());
2326 for (auto el : op.getElements())
2327 values.push_back(state.at(el));
2328
2329 state[op.getResult()] =
2330 sharedState.internalizer.internalize<TupleStorage>(std::move(values));
2331 return DeletionKind::Delete;
2332 }
2333
2334 FailureOr<DeletionKind> visitOp(TupleExtractOp op) {
2335 auto *tuple = get<TupleStorage *>(op.getTuple());
2336 state[op.getResult()] = tuple->values[op.getIndex().getZExtValue()];
2337 return DeletionKind::Delete;
2338 }
2339
2340 FailureOr<DeletionKind> visitOp(scf::IfOp op) {
2341 return op->emitOpError("scf.if must be scheduled by the evaluator");
2342 }
2343
2344 FailureOr<DeletionKind> visitOp(scf::ForOp op) {
2345 return op->emitOpError("scf.for must be scheduled by the evaluator");
2346 }
2347
2348 FailureOr<DeletionKind> visitOp(arith::AddIOp op) {
2349 if (!isa<IndexType>(op.getType()))
2350 return visitOpGeneric(op);
2351
2352 size_t lhs = get<size_t>(op.getLhs());
2353 size_t rhs = get<size_t>(op.getRhs());
2354 state[op.getResult()] = lhs + rhs;
2355 return DeletionKind::Delete;
2356 }
2357
2358 FailureOr<DeletionKind> visitOp(arith::AndIOp op) {
2359 if (!op.getType().isSignlessInteger(1))
2360 return visitOpGeneric(op);
2361
2362 bool lhs = get<bool>(op.getLhs());
2363 bool rhs = get<bool>(op.getRhs());
2364 state[op.getResult()] = lhs && rhs;
2365 return DeletionKind::Delete;
2366 }
2367
2368 FailureOr<DeletionKind> visitOp(arith::XOrIOp op) {
2369 if (!op.getType().isSignlessInteger(1))
2370 return visitOpGeneric(op);
2371
2372 bool lhs = get<bool>(op.getLhs());
2373 bool rhs = get<bool>(op.getRhs());
2374 state[op.getResult()] = lhs != rhs;
2375 return DeletionKind::Delete;
2376 }
2377
2378 FailureOr<DeletionKind> visitOp(arith::OrIOp op) {
2379 if (!op.getType().isSignlessInteger(1))
2380 return visitOpGeneric(op);
2381
2382 bool lhs = get<bool>(op.getLhs());
2383 bool rhs = get<bool>(op.getRhs());
2384 state[op.getResult()] = lhs || rhs;
2385 return DeletionKind::Delete;
2386 }
2387
2388 FailureOr<DeletionKind> visitOp(arith::SelectOp op) {
2389 auto condOpaque = state.at(op.getCondition());
2390 if (isSymbolic(condOpaque))
2391 return visitOpGeneric(op);
2392
2393 bool cond = std::get<bool>(condOpaque);
2394 auto trueVal = state.at(op.getTrueValue());
2395 auto falseVal = state.at(op.getFalseValue());
2396 state[op.getResult()] = cond ? trueVal : falseVal;
2397 return DeletionKind::Delete;
2398 }
2399
2400 FailureOr<DeletionKind> visitOp(index::AddOp op) {
2401 size_t lhs = get<size_t>(op.getLhs());
2402 size_t rhs = get<size_t>(op.getRhs());
2403 state[op.getResult()] = lhs + rhs;
2404 return DeletionKind::Delete;
2405 }
2406
2407 FailureOr<DeletionKind> visitOp(index::SubOp op) {
2408 size_t lhs = get<size_t>(op.getLhs());
2409 size_t rhs = get<size_t>(op.getRhs());
2410 state[op.getResult()] = lhs - rhs;
2411 return DeletionKind::Delete;
2412 }
2413
2414 FailureOr<DeletionKind> visitOp(index::MulOp op) {
2415 size_t lhs = get<size_t>(op.getLhs());
2416 size_t rhs = get<size_t>(op.getRhs());
2417 state[op.getResult()] = lhs * rhs;
2418 return DeletionKind::Delete;
2419 }
2420
2421 FailureOr<DeletionKind> visitOp(index::DivUOp op) {
2422 size_t lhs = get<size_t>(op.getLhs());
2423 size_t rhs = get<size_t>(op.getRhs());
2424
2425 if (rhs == 0)
2426 return op->emitOpError("attempted division by zero");
2427
2428 state[op.getResult()] = lhs / rhs;
2429 return DeletionKind::Delete;
2430 }
2431
2432 FailureOr<DeletionKind> visitOp(index::CeilDivUOp op) {
2433 size_t lhs = get<size_t>(op.getLhs());
2434 size_t rhs = get<size_t>(op.getRhs());
2435
2436 if (rhs == 0)
2437 return op->emitOpError("attempted division by zero");
2438
2439 if (lhs == 0)
2440 state[op.getResult()] = (lhs + rhs - 1) / rhs;
2441 else
2442 state[op.getResult()] = 1 + ((lhs - 1) / rhs);
2443
2444 return DeletionKind::Delete;
2445 }
2446
2447 FailureOr<DeletionKind> visitOp(index::RemUOp op) {
2448 size_t lhs = get<size_t>(op.getLhs());
2449 size_t rhs = get<size_t>(op.getRhs());
2450
2451 if (rhs == 0)
2452 return op->emitOpError("attempted division by zero");
2453
2454 state[op.getResult()] = lhs % rhs;
2455 return DeletionKind::Delete;
2456 }
2457
2458 FailureOr<DeletionKind> visitOp(index::AndOp op) {
2459 size_t lhs = get<size_t>(op.getLhs());
2460 size_t rhs = get<size_t>(op.getRhs());
2461 state[op.getResult()] = lhs & rhs;
2462 return DeletionKind::Delete;
2463 }
2464
2465 FailureOr<DeletionKind> visitOp(index::OrOp op) {
2466 size_t lhs = get<size_t>(op.getLhs());
2467 size_t rhs = get<size_t>(op.getRhs());
2468 state[op.getResult()] = lhs | rhs;
2469 return DeletionKind::Delete;
2470 }
2471
2472 FailureOr<DeletionKind> visitOp(index::XOrOp op) {
2473 size_t lhs = get<size_t>(op.getLhs());
2474 size_t rhs = get<size_t>(op.getRhs());
2475 state[op.getResult()] = lhs ^ rhs;
2476 return DeletionKind::Delete;
2477 }
2478
2479 FailureOr<DeletionKind> visitOp(index::ShlOp op) {
2480 size_t lhs = get<size_t>(op.getLhs());
2481 size_t rhs = get<size_t>(op.getRhs());
2482 state[op.getResult()] = lhs << rhs;
2483 return DeletionKind::Delete;
2484 }
2485
2486 FailureOr<DeletionKind> visitOp(index::ShrUOp op) {
2487 size_t lhs = get<size_t>(op.getLhs());
2488 size_t rhs = get<size_t>(op.getRhs());
2489 state[op.getResult()] = lhs >> rhs;
2490 return DeletionKind::Delete;
2491 }
2492
2493 FailureOr<DeletionKind> visitOp(index::MaxUOp op) {
2494 size_t lhs = get<size_t>(op.getLhs());
2495 size_t rhs = get<size_t>(op.getRhs());
2496 state[op.getResult()] = std::max(lhs, rhs);
2497 return DeletionKind::Delete;
2498 }
2499
2500 FailureOr<DeletionKind> visitOp(index::MinUOp op) {
2501 size_t lhs = get<size_t>(op.getLhs());
2502 size_t rhs = get<size_t>(op.getRhs());
2503 state[op.getResult()] = std::min(lhs, rhs);
2504 return DeletionKind::Delete;
2505 }
2506
2507 FailureOr<DeletionKind> visitOp(index::CmpOp op) {
2508 size_t lhs = get<size_t>(op.getLhs());
2509 size_t rhs = get<size_t>(op.getRhs());
2510 bool result;
2511 switch (op.getPred()) {
2512 case index::IndexCmpPredicate::EQ:
2513 result = lhs == rhs;
2514 break;
2515 case index::IndexCmpPredicate::NE:
2516 result = lhs != rhs;
2517 break;
2518 case index::IndexCmpPredicate::ULT:
2519 result = lhs < rhs;
2520 break;
2521 case index::IndexCmpPredicate::ULE:
2522 result = lhs <= rhs;
2523 break;
2524 case index::IndexCmpPredicate::UGT:
2525 result = lhs > rhs;
2526 break;
2527 case index::IndexCmpPredicate::UGE:
2528 result = lhs >= rhs;
2529 break;
2530 default:
2531 return op->emitOpError("elaboration not supported");
2532 }
2533 state[op.getResult()] = result;
2534 return DeletionKind::Delete;
2535 }
2536
2537 bool isSymbolic(ElaboratorValue val) {
2538 return std::holds_alternative<SymbolicComputationWithIdentityValue *>(
2539 val) ||
2540 std::holds_alternative<SymbolicComputationWithIdentityStorage *>(
2541 val) ||
2542 std::holds_alternative<SymbolicComputationStorage *>(val) ||
2543 std::holds_alternative<OpaqueExternalStorage *>(val);
2544 }
2545
2546 bool isSymbolic(Operation *op) {
2547 return llvm::any_of(op->getOperands(), [&](auto operand) {
2548 auto val = state.at(operand);
2549 return isSymbolic(val);
2550 });
2551 }
2552
2553 /// If all operands are constants, try to fold the operation and register its
2554 /// result values with the folded results in the elaborator state.
2555 /// Returns 'false' if operation has to be handled symbolically.
2556 bool attemptConcreteCase(Operation *op) {
2557 if (op->getNumResults() == 0)
2558 return false;
2559
2560 SmallVector<Attribute> operands;
2561 for (auto operand : op->getOperands()) {
2562 auto evalValue = state[operand];
2563 auto attr = elabValConverter.convert(evalValue);
2564 operands.push_back(attr);
2565 }
2566
2567 SmallVector<OpFoldResult> results;
2568 if (failed(op->fold(operands, results)))
2569 return false;
2570
2571 if (results.size() != op->getNumResults())
2572 return false;
2573
2574 for (auto [res, val] : llvm::zip(results, op->getResults())) {
2575 auto attr = llvm::dyn_cast_or_null<TypedAttr>(res.dyn_cast<Attribute>());
2576 if (!attr)
2577 return false;
2578
2579 if (attr.getType() != val.getType())
2580 return false;
2581
2582 // Try to convert the attribute to an ElaboratorValue
2583 auto converted = attrConverter.convert(attr);
2584 if (succeeded(converted)) {
2585 state[val] = *converted;
2586 continue;
2587 }
2588 return false;
2589 }
2590
2591 return true;
2592 }
2593
2594 FailureOr<DeletionKind> visitOpGeneric(Operation *op) {
2595 if (op->getNumResults() == 0)
2596 return DeletionKind::Keep;
2597
2598 if (attemptConcreteCase(op))
2599 return DeletionKind::Delete;
2600
2601 if (mlir::isMemoryEffectFree(op)) {
2602 if (op->getNumResults() != 1)
2603 return op->emitOpError(
2604 "symbolic elaboration of memory-effect-free operations with "
2605 "multiple results not supported");
2606
2607 state[op->getResult(0)] =
2608 sharedState.internalizer.internalize<SymbolicComputationStorage>(
2609 state, op);
2610 return DeletionKind::Delete;
2611 }
2612
2613 // We assume that reordering operations with only allocate effects is
2614 // allowed.
2615 // FIXME: this is not how the MLIR MemoryEffects interface intends it.
2616 // We should create our own interface/trait for that use-case.
2617 // Or modify the elaboration pass to keep track of the ordering of such
2618 // instructions and materialize all operations that are not already
2619 // materialized but have to happen before the current alloc operation to be
2620 // materialized.
2621 bool onlyAlloc = mlir::hasSingleEffect<mlir::MemoryEffects::Allocate>(op);
2622 onlyAlloc |= isa<ValidateOp>(op);
2623
2624 auto *validationVal =
2625 sharedState.internalizer.create<SymbolicComputationWithIdentityStorage>(
2626 state, op);
2627 materializer.registerIdentityValue(validationVal);
2628 state[op->getResult(0)] = validationVal;
2629
2630 for (auto [i, res] : llvm::enumerate(op->getResults())) {
2631 if (i == 0)
2632 continue;
2633 auto *val =
2634 sharedState.internalizer.create<SymbolicComputationWithIdentityValue>(
2635 res.getType(), validationVal, i);
2636 state[res] = val;
2637 materializer.registerIdentityValue(val);
2638 }
2639 return onlyAlloc ? DeletionKind::Delete : DeletionKind::Keep;
2640 }
2641
2642 bool supportsSymbolicValuesNonGenerically(Operation *op) {
2643 return isa<SubstituteSequenceOp, ArrayCreateOp, ArrayInjectOp,
2644 TupleCreateOp, MutCreateOp, MutReadOp, MutWriteOp,
2645 arith::SelectOp, func::CallOp, func::CallIndirectOp>(op);
2646 }
2647
2648 FailureOr<DeletionKind> dispatchOpVisitor(Operation *op) {
2649 if (isSymbolic(op) && !supportsSymbolicValuesNonGenerically(op))
2650 return visitOpGeneric(op);
2651
2652 return TypeSwitch<Operation *, FailureOr<DeletionKind>>(op)
2653 .Case<
2654 // Arith ops
2655 arith::AddIOp, arith::XOrIOp, arith::AndIOp, arith::OrIOp,
2656 arith::SelectOp,
2657 // Index ops
2658 index::AddOp, index::SubOp, index::MulOp, index::DivUOp,
2659 index::CeilDivUOp, index::RemUOp, index::AndOp, index::OrOp,
2660 index::XOrOp, index::ShlOp, index::ShrUOp, index::MaxUOp,
2661 index::MinUOp, index::CmpOp>([&](auto op) { return visitOp(op); })
2662 .Default([&](Operation *op) { return RTGBase::dispatchOpVisitor(op); });
2663 }
2664
2665 LogicalResult pushRegionFrame(Region &region,
2666 ArrayRef<ElaboratorValue> regionArguments,
2667 bool keepTerminator, bool replay,
2668 LexicalEnvironment environment,
2669 bool eraseTerminator = true) {
2670 if (region.getBlocks().size() != 1)
2671 return region.getParentOp()->emitOpError(
2672 "regions with anything other than one block are not supported");
2673
2674 Block &block = region.front();
2675 for (auto [arg, value] : llvm::zip(block.getArguments(), regionArguments))
2676 environment[arg] = value;
2677
2678 BlockFrame frame;
2679 frame.block = &block;
2680 frame.nextOp = block.empty() ? nullptr : &block.front();
2681 frame.keepTerminator = keepTerminator;
2682 frame.eraseTerminator = eraseTerminator;
2683 frame.replay = replay;
2684 frame.environment = std::move(environment);
2685 workStack.emplace_back(std::make_unique<EvalFrame>(std::move(frame)));
2686 return success();
2687 }
2688
2689 FailureOr<ControlTransfer> visitPerformTransfer(PerformOp op) {
2690 StringAttr effectName = op.getEffectAttr().getAttr();
2691 for (size_t i = handlerStack.size(); i > 0; --i) {
2692 const HandlerFrame &frame = handlerStack[i - 1];
2693 if (frame.handlers.contains(effectName))
2694 return ControlTransfer{frame.promptToken, op};
2695 }
2696
2697 auto diag = op->emitError("no handler for effect @")
2698 << effectName.getValue();
2699 if (sequenceBoundaryToken)
2700 diag << "; effects cannot be handled across a sequence boundary";
2701 return diag;
2702 }
2703
2704 LogicalResult handleControlTransfer(ControlTransfer transfer) {
2705 size_t handlerIndex = handlerStack.size();
2706 Region *handlerRegion = nullptr;
2707 StringAttr effectName = transfer.perform.getEffectAttr().getAttr();
2708 for (size_t i = handlerStack.size(); i > 0; --i) {
2709 auto it = handlerStack[i - 1].handlers.find(effectName);
2710 if (it != handlerStack[i - 1].handlers.end()) {
2711 handlerIndex = i - 1;
2712 handlerRegion = it->second;
2713 break;
2714 }
2715 }
2716 if (!handlerRegion || handlerIndex == handlerStack.size())
2717 return transfer.perform->emitOpError(
2718 "control transfer no longer has a matching handler");
2719
2720 size_t promptIndex = workStack.size();
2721 for (size_t i = 0; i < workStack.size(); ++i)
2722 if (auto *frame = workStack[i]->getIf<WithHandlersFrame>();
2723 frame && frame->promptId == transfer.promptId) {
2724 promptIndex = i;
2725 break;
2726 }
2727 if (promptIndex == workStack.size())
2728 return transfer.perform->emitOpError(
2729 "active handler prompt has no evaluator frame");
2730
2731 for (size_t i = promptIndex + 1; i < workStack.size(); ++i)
2732 if (workStack[i]->holds<ExternalOpFrame>())
2733 return transfer.perform->emitError(
2734 "effect handler cannot capture control across an opaque "
2735 "external-region shell");
2736
2737 auto *activeBlock = workStack.back()->getIf<BlockFrame>();
2738 if (!activeBlock)
2739 return transfer.perform->emitOpError(
2740 "perform is not executing in a block frame");
2741 LexicalEnvironment performEnvironment = activeBlock->environment;
2742 SmallVector<HandlerFrame> capturedHandlerStack(handlerStack);
2743 RngScope capturedRng = testState.rng;
2744 ContinuationBoundary capturedBoundary{
2745 sequenceBoundaryToken, externalRegionStack, elaboratorBoundaryToken};
2746 EvalFrameStack capturedFrames;
2747
2748 Value performResult = transfer.perform.getNumResults()
2749 ? Value(transfer.perform.getResult())
2750 : Value();
2751 Type resumeType = performResult
2752 ? performResult.getType()
2753 : NoneType::get(transfer.perform.getContext());
2754
2755 while (workStack.size() > promptIndex) {
2756 EvalFrame &frame = *workStack.back();
2757 capturedFrames.push_back(frame.clone());
2758 if (auto *randomScope = frame.getIf<RandomScopeFrame>()) {
2759 testState.rng = randomScope->outerRng;
2760 } else if (auto *external = frame.getIf<ExternalOpFrame>()) {
2761 if (external->activeBoundary && !externalRegionStack.empty())
2762 externalRegionStack.pop_back();
2763 materializer.getBuilder().restoreInsertionPoint(
2764 external->parentInsertionPoint);
2765 } else if (auto *resume = frame.getIf<ResumeFrame>()) {
2766 handlerStack = resume->handlerStack;
2767 testState.rng = resume->rng;
2768 }
2769 workStack.pop_back();
2770 }
2771 std::reverse(capturedFrames.begin(), capturedFrames.end());
2772 auto *continuation = sharedState.internalizer.create<ContinuationStorage>(
2773 std::move(capturedFrames), performResult,
2774 std::move(capturedHandlerStack), performEnvironment,
2775 std::move(capturedRng), transfer.promptId, std::move(capturedBoundary),
2776 resumeType, transfer.perform.getLoc());
2777 materializer.registerIdentityValue(continuation);
2778
2779 SmallVector<ElaboratorValue> handlerArguments;
2780 for (Value operand : transfer.perform.getOperands())
2781 handlerArguments.push_back(performEnvironment.at(operand));
2782 handlerArguments.push_back(continuation);
2783
2784 handlerStack.resize(handlerIndex);
2785 state = performEnvironment;
2786 workStack.emplace_back(std::make_unique<EvalFrame>(HandlerClauseFrame{}));
2787 return pushRegionFrame(*handlerRegion, handlerArguments,
2788 /*keepTerminator=*/false, /*replay=*/false,
2789 std::move(performEnvironment));
2790 }
2791
2792 LogicalResult scheduleResume(ResumeOp op) {
2793 auto stateIt = state.find(op.getContinuation());
2794 if (stateIt == state.end())
2795 return op->emitOpError("continuation has no elaborated value");
2796 auto *continuation = std::get_if<ContinuationStorage *>(&stateIt->second);
2797 if (!continuation || !*continuation)
2798 return op->emitOpError(
2799 "cannot resume a continuation that is not owned by the elaborator");
2800
2801 ContinuationStorage *cont = *continuation;
2802 if (cont->boundary.sequence != sequenceBoundaryToken)
2803 return op->emitError("continuation cannot escape a sequence boundary");
2804 if (cont->boundary.elaborator != elaboratorBoundaryToken)
2805 return op->emitError(
2806 "continuation cannot escape its elaborator invocation");
2807 if (!llvm::equal(cont->boundary.externalRegions, externalRegionStack))
2808 return op->emitError(
2809 "continuation cannot escape an opaque external-region shell");
2810
2811 std::optional<ElaboratorValue> resumeValue;
2812 if (cont->performResult && op.getValue())
2813 resumeValue = state.at(op.getValue());
2814
2815 auto *block = workStack.back()->getIf<BlockFrame>();
2816 if (!block)
2817 return op->emitOpError("resume is not executing in a block frame");
2818 block->environment = state;
2819
2820 workStack.emplace_back(
2821 std::make_unique<EvalFrame>(ResumeFrame(handlerStack, testState.rng)));
2822 handlerStack = cont->capturedHandlerStack;
2823 testState.rng = cont->capturedRng;
2824
2825 EvalFrameStack replayFrames;
2826 for (const auto &prototype : cont->capturedFrames)
2827 replayFrames.push_back(prototype->clone());
2828 for (auto &framePtr : replayFrames) {
2829 EvalFrame &frame = *framePtr;
2830 if (auto *blockFrame = frame.getIf<BlockFrame>())
2831 blockFrame->replay = true;
2832 else if (auto *ifFrame = frame.getIf<ScfIfFrame>())
2833 ifFrame->replay = true;
2834 else if (auto *forFrame = frame.getIf<ScfForFrame>())
2835 forFrame->replay = true;
2836 else if (auto *scopeFrame = frame.getIf<RandomScopeFrame>())
2837 scopeFrame->replay = true;
2838 else if (auto *handlerFrame = frame.getIf<WithHandlersFrame>())
2839 handlerFrame->replay = true;
2840 else if (auto *externalFrame = frame.getIf<ExternalOpFrame>())
2841 externalFrame->replay = true;
2842 }
2843 if (resumeValue) {
2844 auto *performBlock = replayFrames.back()->getIf<BlockFrame>();
2845 if (!performBlock)
2846 return op->emitOpError(
2847 "captured continuation does not end in a block frame");
2848 performBlock->environment[cont->performResult] = *resumeValue;
2849 }
2850 for (auto &frame : replayFrames)
2851 workStack.push_back(std::move(frame));
2852 return success();
2853 }
2854
2855 LogicalResult startExternalRegion(size_t frameIndex, bool &started) {
2856 auto &frame = workStack[frameIndex]->get<ExternalOpFrame>();
2857 RegionRange oldRegions = frame.originalOp->getRegions();
2858 while (frame.nextRegion < oldRegions.size()) {
2859 size_t regionIndex = frame.nextRegion++;
2860 Region &oldRegion = *oldRegions[regionIndex];
2861 if (oldRegion.empty())
2862 continue;
2863 if (oldRegion.getBlocks().size() != 1)
2864 return frame.originalOp->emitOpError(
2865 "regions with anything other than one block are not supported");
2866
2867 Region &newRegion = frame.materializedOp->getRegion(regionIndex);
2868 Block &oldBlock = oldRegion.front();
2869 Block &newBlock = newRegion.emplaceBlock();
2870 LexicalEnvironment environment = frame.parentEnvironment;
2871 for (auto oldArg : oldBlock.getArguments()) {
2872 Value newArg = newBlock.addArgument(oldArg.getType(), oldArg.getLoc());
2873 auto *storage = sharedState.internalizer.create<OpaqueExternalStorage>(
2874 oldArg.getType(), oldArg.getLoc());
2875 environment[oldArg] = storage;
2876 materializer.map(storage, newArg);
2877 }
2878
2879 uint64_t boundary = sharedState.createBoundaryToken();
2880 frame.activeBoundary = boundary;
2881 externalRegionStack.push_back(boundary);
2882 materializer.getBuilder().setInsertionPoint(&newBlock, newBlock.begin());
2883 started = true;
2884 return pushRegionFrame(oldRegion, {}, /*keepTerminator=*/true,
2885 frame.replay, std::move(environment));
2886 }
2887 started = false;
2888 return success();
2889 }
2890
2891 LogicalResult startExternalOp(Operation *op, bool replay) {
2892 auto &builder = materializer.getBuilder();
2893 Operation *newOp = op->cloneWithoutRegions();
2894 builder.insert(newOp);
2895 builder.setInsertionPoint(newOp);
2896 for (auto &operand : newOp->getOpOperands()) {
2897 auto emitError = [&]() {
2898 auto diag = newOp->emitError();
2899 diag.attachNote(newOp->getLoc())
2900 << "while materializing operand#" << operand.getOperandNumber()
2901 << " of external region op";
2902 return diag;
2903 };
2904 Value value = materializer.materialize(state.at(operand.get()),
2905 newOp->getLoc(), emitError);
2906 if (!value)
2907 return failure();
2908 operand.set(value);
2909 }
2910 builder.setInsertionPointAfter(newOp);
2911
2912 ExternalOpFrame frame;
2913 frame.originalOp = op;
2914 frame.materializedOp = newOp;
2915 frame.replay = replay;
2916 frame.parentEnvironment = state;
2917 frame.parentInsertionPoint = builder.saveInsertionPoint();
2918 workStack.emplace_back(std::make_unique<EvalFrame>(std::move(frame)));
2919 bool started = false;
2920 if (failed(startExternalRegion(workStack.size() - 1, started)))
2921 return failure();
2922 if (!started)
2923 return finishExternalOp(workStack.size() - 1);
2924 return success();
2925 }
2926
2927 LogicalResult finishExternalOp(size_t frameIndex) {
2928 auto &frame = workStack[frameIndex]->get<ExternalOpFrame>();
2929 auto *parent = workStack[frameIndex - 1]->getIf<BlockFrame>();
2930 if (!parent)
2931 return frame.originalOp->emitOpError(
2932 "external region op lost its parent block frame");
2933 for (auto [oldResult, newResult] :
2934 llvm::zip(frame.originalOp->getResults(),
2935 frame.materializedOp->getResults())) {
2936 auto *storage = sharedState.internalizer.create<OpaqueExternalStorage>(
2937 oldResult.getType(), oldResult.getLoc());
2938 parent->environment[oldResult] = storage;
2939 materializer.map(storage, newResult);
2940 }
2941 workStack.pop_back();
2942 return success();
2943 }
2944
2945 LogicalResult startForIteration(size_t frameIndex) {
2946 auto &frame = workStack[frameIndex]->get<ScfForFrame>();
2947 if (frame.nextIteration >= frame.upperBound) {
2948 auto *parent = workStack[frameIndex - 1]->getIf<BlockFrame>();
2949 if (!parent)
2950 return frame.op->emitOpError("scf.for lost its parent block frame");
2951 for (auto [result, iterArg] :
2952 llvm::zip(frame.op->getResults(), frame.op.getRegionIterArgs()))
2953 parent->environment[result] = frame.loopEnvironment.at(iterArg);
2954 frame.completed = true;
2955 return success();
2956 }
2957
2958 size_t iteration = frame.nextIteration;
2959 frame.nextIteration += frame.loopStep;
2960 LexicalEnvironment environment = frame.loopEnvironment;
2961 environment[frame.op.getInductionVar()] = iteration;
2962 return pushRegionFrame(frame.op.getBodyRegion(), {},
2963 /*keepTerminator=*/false, frame.replay,
2964 std::move(environment));
2965 }
2966
2967 LogicalResult completeFrame(SmallVector<ElaboratorValue> yieldedValues) {
2968 if (workStack.empty() || workStack.back()->holds<BlockFrame>())
2969 return success();
2970
2971 if (auto *frame = workStack.back()->getIf<ScfIfFrame>()) {
2972 if (failed(frame->step(*this, yieldedValues)))
2973 return failure();
2974 workStack.pop_back();
2975 return success();
2976 }
2977
2978 if (auto *frame = workStack.back()->getIf<ScfForFrame>()) {
2979 if (failed(frame->step(*this, yieldedValues)))
2980 return failure();
2981 if (frame->completed)
2982 workStack.pop_back();
2983 return success();
2984 }
2985
2986 if (auto *frame = workStack.back()->getIf<RandomScopeFrame>()) {
2987 auto *parent = workStack[workStack.size() - 2]->getIf<BlockFrame>();
2988 if (!parent)
2989 return frame->op->emitOpError(
2990 "random_scope lost its parent block frame");
2991 for (auto [result, value] :
2992 llvm::zip(frame->op.getResults(), yieldedValues))
2993 parent->environment[result] = value;
2994 testState.rng = frame->outerRng;
2995 workStack.pop_back();
2996 return success();
2997 }
2998
2999 if (auto *frame = workStack.back()->getIf<WithHandlersFrame>()) {
3000 if (handlerStack.empty() ||
3001 handlerStack.back().promptToken != frame->promptId)
3002 return frame->op->emitOpError(
3003 "handler stack does not match completed scope");
3004 handlerStack.pop_back();
3005 workStack.pop_back();
3006 return success();
3007 }
3008
3009 if (workStack.back()->holds<HandlerClauseFrame>()) {
3010 workStack.pop_back();
3011 return success();
3012 }
3013
3014 if (auto *frame = workStack.back()->getIf<FunctionCallFrame>()) {
3015 if (yieldedValues.size() != frame->call.getNumResults())
3016 return frame->call.emitOpError(
3017 "callee return values do not match call results");
3018 assert(workStack.size() >= 2 &&
3019 "function call frame must have a parent block frame");
3020 auto *parent = workStack[workStack.size() - 2]->getIf<BlockFrame>();
3021 if (!parent)
3022 return frame->call.emitOpError(
3023 "function call lost its parent block frame");
3024 for (auto [result, value] :
3025 llvm::zip_equal(frame->call.getResults(), yieldedValues))
3026 parent->environment[result] = value;
3027 workStack.pop_back();
3028 return success();
3029 }
3030
3031 if (auto *frame = workStack.back()->getIf<ExternalOpFrame>()) {
3032 if (frame->activeBoundary) {
3033 assert(!externalRegionStack.empty() &&
3034 externalRegionStack.back() == frame->activeBoundary);
3035 externalRegionStack.pop_back();
3036 frame->activeBoundary = 0;
3037 }
3038 materializer.getBuilder().restoreInsertionPoint(
3039 frame->parentInsertionPoint);
3040 bool started = false;
3041 if (failed(startExternalRegion(workStack.size() - 1, started)))
3042 return failure();
3043 if (started)
3044 return success();
3045
3046 return finishExternalOp(workStack.size() - 1);
3047 }
3048
3049 if (auto *frame = workStack.back()->getIf<ResumeFrame>()) {
3050 handlerStack = std::move(frame->handlerStack);
3051 testState.rng = frame->rng;
3052 workStack.pop_back();
3053 return success();
3054 }
3055 llvm_unreachable("unknown evaluator frame");
3056 }
3057
3058 LogicalResult runTrampoline(SmallVector<ElaboratorValue> &rootValues) {
3059 while (!workStack.empty()) {
3060 auto *frame = workStack.back()->getIf<BlockFrame>();
3061 if (!frame) {
3062 if (failed(completeFrame({})))
3063 return failure();
3064 continue;
3065 }
3066
3067 Operation *op = frame->nextOp;
3068 if (!op ||
3069 (!frame->keepTerminator && op->hasTrait<OpTrait::IsTerminator>())) {
3070 SmallVector<ElaboratorValue> yieldedValues;
3071 if (!frame->block->empty() &&
3072 frame->block->back().hasTrait<OpTrait::IsTerminator>()) {
3073 Operation *terminator = frame->block->getTerminator();
3074 for (Value operand : terminator->getOperands())
3075 yieldedValues.push_back(frame->environment.at(operand));
3076 if (!frame->keepTerminator && frame->eraseTerminator &&
3077 materializer.isInPlace(terminator)) {
3078 auto &builder = materializer.getBuilder();
3079 if (builder.getBlock() == terminator->getBlock() &&
3080 builder.getInsertionPoint() == terminator->getIterator()) {
3081 // The builder stores its insertion point as a block iterator.
3082 // Move it to the stable end iterator before erasing that node.
3083 builder.setInsertionPointToEnd(terminator->getBlock());
3084 }
3085 terminator->erase();
3086 }
3087 }
3088 workStack.pop_back();
3089 if (workStack.empty()) {
3090 rootValues = std::move(yieldedValues);
3091 break;
3092 }
3093 if (failed(completeFrame(std::move(yieldedValues))))
3094 return failure();
3095 continue;
3096 }
3097
3098 Operation *nextOp = op->getNextNode();
3099 bool replay = frame->replay;
3100 frame->nextOp = nextOp;
3101 state = frame->environment;
3102
3103 if (isSymbolic(op) && !supportsSymbolicValuesNonGenerically(op)) {
3104 auto result = visitOpGeneric(op);
3105 if (failed(result))
3106 return failure();
3107 if (*result == DeletionKind::Keep &&
3108 failed(materializer.materialize(op, state, replay)))
3109 return failure();
3110 workStack.back()->get<BlockFrame>().environment = state;
3111 continue;
3112 }
3113
3114 if (auto perform = dyn_cast<PerformOp>(op)) {
3115 auto transfer = visitPerformTransfer(perform);
3116 if (failed(transfer))
3117 return failure();
3118 workStack.back()->get<BlockFrame>().environment = state;
3119 if (failed(handleControlTransfer(*transfer)))
3120 return failure();
3121 continue;
3122 }
3123
3124 if (auto resume = dyn_cast<ResumeOp>(op)) {
3125 if (failed(scheduleResume(resume)))
3126 return failure();
3127 continue;
3128 }
3129
3130 if (isa<func::CallIndirectOp>(op))
3131 return op->emitOpError("cannot elaborate indirect function call");
3132
3133 if (auto call = dyn_cast<func::CallOp>(op)) {
3134 auto callee = sharedState.table.lookup<func::FuncOp>(call.getCallee());
3135 if (!callee)
3136 return call.emitOpError("cannot elaborate unresolved function call");
3137 if (callee.isExternal())
3138 return call.emitOpError("cannot elaborate external function call");
3139 if (callee.getBody().getBlocks().size() != 1)
3140 return call.emitOpError(
3141 "RTG elaboration only supports single-block callees");
3142
3143 SmallVector<ElaboratorValue> arguments;
3144 arguments.reserve(call.getNumOperands());
3145 for (Value operand : call.getOperands())
3146 arguments.push_back(state.at(operand));
3147
3148 workStack.back()->get<BlockFrame>().environment = state;
3149 workStack.emplace_back(
3150 std::make_unique<EvalFrame>(FunctionCallFrame{call}));
3151 if (failed(pushRegionFrame(callee.getBody(), arguments,
3152 /*keepTerminator=*/false, replay,
3153 /*environment=*/{},
3154 /*eraseTerminator=*/false)))
3155 return failure();
3156 continue;
3157 }
3158
3159 if (auto ifOp = dyn_cast<scf::IfOp>(op)) {
3160 bool condition = get<bool>(ifOp.getCondition());
3161 Region &region =
3162 condition ? ifOp.getThenRegion() : ifOp.getElseRegion();
3163 workStack.back()->get<BlockFrame>().environment = state;
3164 if (region.empty())
3165 continue;
3166 workStack.emplace_back(
3167 std::make_unique<EvalFrame>(ScfIfFrame{ifOp, replay}));
3168 if (failed(pushRegionFrame(region, {}, /*keepTerminator=*/false, replay,
3169 state)))
3170 return failure();
3171 continue;
3172 }
3173
3174 if (auto forOp = dyn_cast<scf::ForOp>(op)) {
3175 if (!(std::holds_alternative<size_t>(state.at(forOp.getLowerBound())) &&
3176 std::holds_alternative<size_t>(state.at(forOp.getStep())) &&
3177 std::holds_alternative<size_t>(state.at(forOp.getUpperBound()))))
3178 return forOp->emitOpError("can only elaborate index type iterator");
3179 size_t lower = get<size_t>(forOp.getLowerBound());
3180 size_t step = get<size_t>(forOp.getStep());
3181 size_t upper = get<size_t>(forOp.getUpperBound());
3182 if (!step)
3183 return forOp->emitOpError("loop step must be greater than zero");
3184 LexicalEnvironment loopEnvironment = state;
3185 for (auto [iterArg, initArg] :
3186 llvm::zip(forOp.getRegionIterArgs(), forOp.getInitArgs()))
3187 loopEnvironment[iterArg] = state.at(initArg);
3188 workStack.back()->get<BlockFrame>().environment = state;
3189 if (lower >= upper) {
3190 for (auto [result, iterArg] :
3191 llvm::zip(forOp->getResults(), forOp.getRegionIterArgs()))
3192 workStack.back()->get<BlockFrame>().environment[result] =
3193 loopEnvironment.at(iterArg);
3194 continue;
3195 }
3196 workStack.emplace_back(std::make_unique<EvalFrame>(
3197 ScfForFrame{forOp, lower, upper, step, replay, /*completed=*/false,
3198 std::move(loopEnvironment)}));
3199 if (failed(startForIteration(workStack.size() - 1)))
3200 return failure();
3201 continue;
3202 }
3203
3204 if (auto randomScope = dyn_cast<RandomScopeOp>(op)) {
3205 RngScope outerRng = testState.rng;
3206 testState.rng = randomScope.getSeed()
3207 ? RngScope(randomScope.getSeed()->getZExtValue())
3208 : testState.rng.getNested();
3209 workStack.back()->get<BlockFrame>().environment = state;
3210 workStack.emplace_back(std::make_unique<EvalFrame>(
3211 RandomScopeFrame(randomScope, std::move(outerRng), replay)));
3212 if (failed(pushRegionFrame(randomScope.getBodyRegion(), {},
3213 /*keepTerminator=*/false, replay, state)))
3214 return failure();
3215 continue;
3216 }
3217
3218 if (auto withHandlers = dyn_cast<WithHandlersOp>(op)) {
3219 HandlerFrame handlerFrame;
3220 handlerFrame.promptToken = sharedState.createBoundaryToken();
3221 for (auto [effect, region] : llvm::zip(
3222 withHandlers.getEffects(), withHandlers.getHandlerRegions()))
3223 handlerFrame.handlers[cast<FlatSymbolRefAttr>(effect).getAttr()] =
3224 &region;
3225 uint64_t promptId = handlerFrame.promptToken;
3226 handlerStack.push_back(std::move(handlerFrame));
3227 workStack.back()->get<BlockFrame>().environment = state;
3228 workStack.emplace_back(std::make_unique<EvalFrame>(
3229 WithHandlersFrame{withHandlers, promptId, replay}));
3230 if (failed(pushRegionFrame(withHandlers.getBody(), {},
3231 /*keepTerminator=*/false, replay, state)))
3232 return failure();
3233 continue;
3234 }
3235
3236 if (op->getNumRegions() != 0) {
3237 workStack.back()->get<BlockFrame>().environment = state;
3238 if (failed(startExternalOp(op, replay)))
3239 return failure();
3240 continue;
3241 }
3242
3243 auto result = dispatchOpVisitor(op);
3244 if (failed(result))
3245 return failure();
3246 if (*result == DeletionKind::Keep &&
3247 failed(materializer.materialize(op, state, replay)))
3248 return failure();
3249 workStack.back()->get<BlockFrame>().environment = state;
3250
3251 LLVM_DEBUG({
3252 llvm::dbgs() << "Elaborated " << *op << " to\n[";
3253 llvm::interleaveComma(op->getResults(), llvm::dbgs(), [&](auto res) {
3254 if (state.contains(res))
3255 llvm::dbgs() << state.at(res);
3256 else
3257 llvm::dbgs() << "unknown";
3258 });
3259 llvm::dbgs() << "]\n\n";
3260 });
3261 }
3262 return success();
3263 }
3264
3265 LogicalResult elaborate(Region &region,
3266 ArrayRef<ElaboratorValue> regionArguments,
3267 bool keepTerminator,
3268 SmallVector<ElaboratorValue> &terminatorOperands) {
3269 if (!workStack.empty())
3270 return region.getParentOp()->emitOpError(
3271 "top-level elaboration cannot start with active evaluator frames");
3272 LexicalEnvironment savedState = std::move(state);
3273 LexicalEnvironment environment = savedState;
3274 if (failed(pushRegionFrame(region, regionArguments, keepTerminator,
3275 /*replay=*/false, std::move(environment)))) {
3276 state = std::move(savedState);
3277 return failure();
3278 }
3279 bool failedEvaluation = failed(runTrampoline(terminatorOperands));
3280 state = std::move(savedState);
3281 return failedEvaluation ? failure() : success();
3282 }
3283
3284private:
3285 // State to be shared between all elaborator instances.
3286 SharedState &sharedState;
3287
3288 // State to a specific RTG test and the sequences placed within it.
3289 TestState &testState;
3290
3291 // Allows us to materialize ElaboratorValues to the IR operations necessary to
3292 // obtain an SSA value representing that elaborated value.
3293 Materializer &materializer;
3294
3295 // A map from SSA values to a pointer of an interned elaborator value.
3296 LexicalEnvironment state;
3297
3298 // The current context we are elaborating under.
3299 ContextResourceAttrInterface currentContext;
3300
3301 /// Dynamic sequence invocation enclosing this elaborator, or zero at module
3302 /// scope.
3303 uint64_t sequenceBoundaryToken;
3304
3305 /// Lifetime of this evaluator and the copyable frame executors it owns.
3306 uint64_t elaboratorBoundaryToken;
3307
3308 /// Opaque external regions currently being interpreted.
3309 SmallVector<uint64_t> externalRegionStack;
3310
3311 // Allows us to convert attributes to ElaboratorValues.
3312 AttributeToElaboratorValueConverter attrConverter;
3313
3314 // Allows us to convert ElaboratorValues to attributes.
3315 ElaboratorValueToAttributeConverter elabValConverter;
3316
3317 // Stack of handler frames for rtg.handle scopes, innermost last.
3318 SmallVector<HandlerFrame> handlerStack;
3319
3320 /// Cloneable evaluator state from the outermost block to the current step.
3321 EvalFrameStack workStack;
3322
3323 friend struct ScfIfFrame;
3324 friend struct ScfForFrame;
3325};
3326
3327LogicalResult ScfIfFrame::step(Elaborator &elaborator,
3328 ArrayRef<ElaboratorValue> yieldedValues) {
3329 auto *parent = elaborator.workStack[elaborator.workStack.size() - 2]
3330 ->getIf<BlockFrame>();
3331 if (!parent)
3332 return op->emitOpError("scf.if lost its parent block frame");
3333 for (auto [result, value] : llvm::zip(op.getResults(), yieldedValues))
3334 parent->environment[result] = value;
3335 return success();
3336}
3337
3338LogicalResult ScfForFrame::step(Elaborator &elaborator,
3339 ArrayRef<ElaboratorValue> yieldedValues) {
3340 for (auto [iterArg, value] : llvm::zip(op.getRegionIterArgs(), yieldedValues))
3341 loopEnvironment[iterArg] = value;
3342 return elaborator.startForIteration(elaborator.workStack.size() - 1);
3343}
3344} // namespace
3345
3346SequenceOp
3347Materializer::elaborateSequence(const RandomizedSequenceStorage *seq,
3348 SmallVector<ElaboratorValue> &elabArgs) {
3349 auto familyOp =
3350 sharedState.table.lookup<SequenceOp>(seq->sequence->familyName);
3351 // TODO: don't clone if this is the only remaining reference to this
3352 // sequence
3353 OpBuilder builder(familyOp);
3354 auto seqOp = builder.cloneWithoutRegions(familyOp);
3355 auto name = sharedState.names.newName(seq->sequence->familyName.getValue());
3356 seqOp.setSymName(name);
3357 seqOp.getBodyRegion().emplaceBlock();
3358 sharedState.table.insert(seqOp);
3359 assert(seqOp.getSymName() == name && "should not have been renamed");
3360
3361 LLVM_DEBUG(llvm::dbgs() << "\n=== Elaborating sequence family @"
3362 << familyOp.getSymName() << " into @"
3363 << seqOp.getSymName() << " under context "
3364 << seq->context << "\n\n");
3365
3366 Materializer materializer(OpBuilder::atBlockBegin(seqOp.getBody()), testState,
3367 sharedState, elabArgs);
3368 Elaborator elaborator(sharedState, testState, materializer, seq->context,
3369 sharedState.createBoundaryToken());
3370 SmallVector<ElaboratorValue> yieldedVals;
3371 if (failed(elaborator.elaborate(familyOp.getBodyRegion(), seq->sequence->args,
3372 /*keepTerminator=*/false, yieldedVals)))
3373 return {};
3374
3375 seqOp.setSequenceType(
3376 SequenceType::get(builder.getContext(), materializer.getBlockArgTypes()));
3377 materializer.finalize();
3378
3379 return seqOp;
3380}
3381
3382//===----------------------------------------------------------------------===//
3383// Elaborator Pass
3384//===----------------------------------------------------------------------===//
3385
3386namespace {
3387struct ElaborationPass
3388 : public rtg::impl::ElaborationPassBase<ElaborationPass> {
3389 using Base::Base;
3390
3391 void runOnOperation() override;
3392 void matchTestsAgainstTargets(SymbolTable &table);
3393 LogicalResult elaborateModule(ModuleOp moduleOp, SymbolTable &table);
3394};
3395} // namespace
3396
3397void ElaborationPass::runOnOperation() {
3398 auto moduleOp = getOperation();
3399 SymbolTable table(moduleOp);
3400
3401 matchTestsAgainstTargets(table);
3402
3403 if (failed(elaborateModule(moduleOp, table)))
3404 return signalPassFailure();
3405}
3406
3407void ElaborationPass::matchTestsAgainstTargets(SymbolTable &table) {
3408 auto moduleOp = getOperation();
3409
3410 for (auto test : llvm::make_early_inc_range(moduleOp.getOps<TestOp>())) {
3411 if (test.getTargetAttr())
3412 continue;
3413
3414 bool matched = false;
3415
3416 for (auto target : moduleOp.getOps<TargetOp>()) {
3417 // Check if the target type is a subtype of the test's target type
3418 // This means that for each entry in the test's target type, there must be
3419 // a corresponding entry with the same name and type in the target's type
3420 bool isSubtype = true;
3421 auto testEntries = test.getTargetType().getEntries();
3422 auto targetEntries = target.getTarget().getEntries();
3423
3424 // Check if target is a subtype of test requirements
3425 // Since entries are sorted by name, we can do this in a single pass
3426 size_t targetIdx = 0;
3427 for (auto testEntry : testEntries) {
3428 // Find the matching entry in target entries.
3429 while (targetIdx < targetEntries.size() &&
3430 targetEntries[targetIdx].name.getValue() <
3431 testEntry.name.getValue())
3432 targetIdx++;
3433
3434 // Check if we found a matching entry with the same name and type
3435 if (targetIdx >= targetEntries.size() ||
3436 targetEntries[targetIdx].name != testEntry.name ||
3437 targetEntries[targetIdx].type != testEntry.type) {
3438 isSubtype = false;
3439 break;
3440 }
3441 }
3442
3443 if (!isSubtype)
3444 continue;
3445
3446 IRRewriter rewriter(test);
3447 // Create a new test for the matched target
3448 auto newTest = cast<TestOp>(test->clone());
3449 newTest.setSymName(test.getSymName().str() + "_" +
3450 target.getSymName().str());
3451
3452 // Set the target symbol specifying that this test is only suitable for
3453 // that target.
3454 newTest.setTargetAttr(target.getSymNameAttr());
3455
3456 table.insert(newTest, rewriter.getInsertionPoint());
3457 matched = true;
3458 }
3459
3460 if (matched || deleteUnmatchedTests)
3461 test->erase();
3462 }
3463}
3464
3465static bool onlyLegalToMaterializeInTarget(Type type) {
3466 return isa<MemoryBlockType, ContextResourceTypeInterface>(type);
3467}
3468
3469LogicalResult ElaborationPass::elaborateModule(ModuleOp moduleOp,
3470 SymbolTable &table) {
3471 SharedState state(moduleOp.getContext(), table);
3472
3473 // Update the name cache
3474 state.names.add(moduleOp);
3475
3476 struct TargetElabResult {
3477 TargetElabResult(DictType targetType, uint32_t seed)
3478 : targetType(targetType), testState(seed) {}
3479
3480 DictType targetType;
3481 SmallVector<ElaboratorValue> yields;
3482 TestState testState;
3483 };
3484
3485 // Map to store elaborated targets
3486 DenseMap<StringAttr, TargetElabResult> targetMap;
3487 for (auto targetOp : moduleOp.getOps<TargetOp>()) {
3488 LLVM_DEBUG(llvm::dbgs() << "=== Elaborating target @"
3489 << targetOp.getSymName() << "\n\n");
3490
3491 auto [it, inserted] = targetMap.try_emplace(targetOp.getSymNameAttr(),
3492 targetOp.getTarget(), seed);
3493 auto &result = it->second;
3494
3495 SmallVector<ElaboratorValue> blockArgs;
3496 Materializer targetMaterializer(OpBuilder::atBlockBegin(targetOp.getBody()),
3497 result.testState, state, blockArgs);
3498 Elaborator targetElaborator(state, result.testState, targetMaterializer);
3499
3500 // Elaborate the target
3501 if (failed(targetElaborator.elaborate(targetOp.getBodyRegion(), {},
3502 /*keepTerminator=*/true,
3503 result.yields)))
3504 return failure();
3505
3506 targetMaterializer.finalize();
3507 }
3508
3509 // Initialize the worklist with the test ops since they cannot be placed by
3510 // other ops.
3511 for (auto testOp : moduleOp.getOps<TestOp>()) {
3512 // Skip tests without a target attribute - these couldn't be matched
3513 // against any target but can be useful to keep around for reporting
3514 // purposes.
3515 if (!testOp.getTargetAttr())
3516 continue;
3517
3518 LLVM_DEBUG(llvm::dbgs()
3519 << "\n=== Elaborating test @" << testOp.getTemplateName()
3520 << " for target @" << *testOp.getTarget() << "\n\n");
3521
3522 // Get the target for this test
3523 auto &targetResult = targetMap.at(testOp.getTargetAttr());
3524 TestState testState(seed);
3525 testState.contextSwitches = targetResult.testState.contextSwitches;
3526 testState.name = testOp.getSymNameAttr();
3527
3528 SmallVector<ElaboratorValue> filteredYields;
3529 unsigned i = 0;
3530 for (auto [entry, yield] :
3531 llvm::zip(targetResult.targetType.getEntries(), targetResult.yields)) {
3532 if (i >= testOp.getTargetType().getEntries().size())
3533 break;
3534
3535 if (entry.name == testOp.getTargetType().getEntries()[i].name) {
3536 filteredYields.push_back(yield);
3537 ++i;
3538 }
3539 }
3540
3541 // Now elaborate the test with the same state, passing the target yield
3542 // values as arguments
3543 SmallVector<ElaboratorValue> blockArgs;
3544 Materializer materializer(OpBuilder::atBlockBegin(testOp.getBody()),
3545 testState, state, blockArgs);
3546
3547 for (auto [arg, val] :
3548 llvm::zip(testOp.getBody()->getArguments(), filteredYields))
3549 if (onlyLegalToMaterializeInTarget(arg.getType()))
3550 materializer.map(val, arg);
3551
3552 Elaborator elaborator(state, testState, materializer);
3553 SmallVector<ElaboratorValue> ignore;
3554 if (failed(elaborator.elaborate(testOp.getBodyRegion(), filteredYields,
3555 /*keepTerminator=*/false, ignore)))
3556 return failure();
3557
3558 materializer.finalize();
3559 }
3560
3561 return success();
3562}
assert(baseType &&"element must be base type")
static std::unique_ptr< Context > context
static bool onlyLegalToMaterializeInTarget(Type type)
#define VISIT_UNSUPPORTED(STORAGETYPE)
static void print(TypedAttr val, llvm::raw_ostream &os)
static LogicalResult convert(arc::ExecuteOp op, arc::ExecuteOp::Adaptor adaptor, ConversionPatternRewriter &rewriter, const TypeConverter &converter)
static Location getLoc(DefSlot slot)
Definition Mem2Reg.cpp:193
false return failure()
Parse an optional keyword or string and set instance into 'result'.
if(parseOptionalKeywordOrOptionalString(parser, result.rawName, found)) return failure()
return success()
static InstancePath empty
StringRef newName(const Twine &name)
Return a unique name, derived from the input name, and add the new name to the internal namespace.
Definition Namespace.h:86
This helps visit TypeOp nodes.
Definition RTGVisitors.h:25
FailureOr< DeletionKind > dispatchOpVisitor(Operation *op, ExtraArgs... args)
Definition RTGVisitors.h:27
Direction get(bool isOutput)
Returns an output direction if isOutput is true, otherwise returns an input direction.
Definition CalyxOps.cpp:56
StringAttr getName(ArrayAttr names, size_t idx)
Return the name at the specified index of the ArrayAttr or null if it cannot be determined.
OS & operator<<(OS &os, const InnerSymTarget &target)
Printing InnerSymTarget's.
static llvm::hash_code hash_value(const ModulePort &port)
Definition HWTypes.h:67
int64_t getBitWidth(mlir::Type type)
Return the hardware bit width of a type.
Definition HWTypes.cpp:122
@ Delete
Erase the matched ops.
Definition SVPasses.h:25
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:36
static bool isEqual(const bool &lhs, const bool &rhs)
static unsigned getHashValue(const bool &val)