From 2633cab23225705add00606f77213fa66a61c917 Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Fri, 22 Nov 2024 17:20:56 +0000 Subject: [PATCH] [RTG] Add ElaborationPass --- docs/Dialects/RTG.md | 4 + include/circt/Dialect/RTG/CMakeLists.txt | 1 + .../Dialect/RTG/Transforms/CMakeLists.txt | 6 + .../circt/Dialect/RTG/Transforms/RTGPasses.h | 49 ++ .../circt/Dialect/RTG/Transforms/RTGPasses.td | 32 + include/circt/InitAllPasses.h | 2 + lib/Dialect/RTG/CMakeLists.txt | 1 + lib/Dialect/RTG/Transforms/CMakeLists.txt | 15 + .../RTG/Transforms/ElaborationPass.cpp | 549 ++++++++++++++++++ test/Dialect/RTG/Transform/elaboration.mlir | 123 ++++ 10 files changed, 782 insertions(+) create mode 100644 include/circt/Dialect/RTG/Transforms/CMakeLists.txt create mode 100644 include/circt/Dialect/RTG/Transforms/RTGPasses.h create mode 100644 include/circt/Dialect/RTG/Transforms/RTGPasses.td create mode 100644 lib/Dialect/RTG/Transforms/CMakeLists.txt create mode 100644 lib/Dialect/RTG/Transforms/ElaborationPass.cpp create mode 100644 test/Dialect/RTG/Transform/elaboration.mlir diff --git a/docs/Dialects/RTG.md b/docs/Dialects/RTG.md index f9ccefb5a483..9b25b6e87d54 100644 --- a/docs/Dialects/RTG.md +++ b/docs/Dialects/RTG.md @@ -273,3 +273,7 @@ companion dialect to define any backends. ## Types [include "Dialects/RTGTypes.md] + +## Passes + +[include "Dialects/RTGPasses.md] diff --git a/include/circt/Dialect/RTG/CMakeLists.txt b/include/circt/Dialect/RTG/CMakeLists.txt index f33061b2d87c..9f57627c321f 100644 --- a/include/circt/Dialect/RTG/CMakeLists.txt +++ b/include/circt/Dialect/RTG/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(IR) +add_subdirectory(Transforms) diff --git a/include/circt/Dialect/RTG/Transforms/CMakeLists.txt b/include/circt/Dialect/RTG/Transforms/CMakeLists.txt new file mode 100644 index 000000000000..06f8b8112747 --- /dev/null +++ b/include/circt/Dialect/RTG/Transforms/CMakeLists.txt @@ -0,0 +1,6 @@ +set(LLVM_TARGET_DEFINITIONS RTGPasses.td) +mlir_tablegen(RTGPasses.h.inc -gen-pass-decls) +add_public_tablegen_target(CIRCTRTGTransformsIncGen) + +# Generate Pass documentation. +add_circt_doc(RTGPasses RTGPasses -gen-pass-doc) diff --git a/include/circt/Dialect/RTG/Transforms/RTGPasses.h b/include/circt/Dialect/RTG/Transforms/RTGPasses.h new file mode 100644 index 000000000000..2275934d9e93 --- /dev/null +++ b/include/circt/Dialect/RTG/Transforms/RTGPasses.h @@ -0,0 +1,49 @@ +//===- RTGPasses.h - RTG pass entry points ----------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This header file defines prototypes that expose pass constructors. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_RTG_RTGPASSES_H +#define CIRCT_DIALECT_RTG_RTGPASSES_H + +#include "mlir/Pass/Pass.h" +#include +#include + +namespace circt { +namespace rtg { + +/// Options for the ElaborationPass +struct ElaborationOptions { + /// The seed for any RNG constructs used in the pass. If `std::nullopt` is + /// passed, no seed is used and thus the IR after each invocation of this pass + /// will be different non-deterministically. However, if a nubmer is provided, + /// the pass is guaranteed to produce the same IR every time. + std::optional seed; + + /// When in debug mode the pass queries the values that would otherwise be + /// provided by the RNG from an attribute attached to the operation called + /// 'rtg.elaboration'. + bool debugMode = false; +}; + +std::unique_ptr createElaborationPass(); +std::unique_ptr +createElaborationPass(const ElaborationOptions &options); + +/// Generate the code for registering passes. +#define GEN_PASS_REGISTRATION +#include "circt/Dialect/RTG/Transforms/RTGPasses.h.inc" +#undef GEN_PASS_REGISTRATION + +} // namespace rtg +} // namespace circt + +#endif // CIRCT_DIALECT_RTG_RTGPASSES_H diff --git a/include/circt/Dialect/RTG/Transforms/RTGPasses.td b/include/circt/Dialect/RTG/Transforms/RTGPasses.td new file mode 100644 index 000000000000..6769924a4d81 --- /dev/null +++ b/include/circt/Dialect/RTG/Transforms/RTGPasses.td @@ -0,0 +1,32 @@ +//===-- RTGPasses.td - RTG pass definition file ------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines the passes that operate on the RTG dialect. +// +//===----------------------------------------------------------------------===// + +#ifndef CIRCT_DIALECT_RTG_RTGPASSES_TD +#define CIRCT_DIALECT_RTG_RTGPASSES_TD + +include "mlir/Pass/PassBase.td" + +def ElaborationPass : Pass<"rtg-elaborate", "mlir::ModuleOp"> { + let summary = "elaborate the randomization parts"; + let description = [{ + This pass interprets most RTG operations to perform the represented + randomization and in the process get rid of those operations. This means, + after this pass the IR does not contain any random constructs within tests + anymore. + }]; + + // Define a custom constructor to have more control over the pass options + // (e.g., std::optional options are not handled very well). + let constructor = "::circt::rtg::createElaborationPass()"; +} + +#endif // CIRCT_DIALECT_RTG_RTGPASSES_TD diff --git a/include/circt/InitAllPasses.h b/include/circt/InitAllPasses.h index 36ae439f2970..82f137d580dd 100644 --- a/include/circt/InitAllPasses.h +++ b/include/circt/InitAllPasses.h @@ -32,6 +32,7 @@ #include "circt/Dialect/Moore/MoorePasses.h" #include "circt/Dialect/OM/OMPasses.h" #include "circt/Dialect/Pipeline/PipelinePasses.h" +#include "circt/Dialect/RTG/Transforms/RTGPasses.h" #include "circt/Dialect/SSP/SSPPasses.h" #include "circt/Dialect/SV/SVPasses.h" #include "circt/Dialect/Seq/SeqPasses.h" @@ -73,6 +74,7 @@ inline void registerAllPasses() { sv::registerPasses(); handshake::registerPasses(); ibis::registerPasses(); + rtg::registerPasses(); hw::registerPasses(); pipeline::registerPasses(); sim::registerPasses(); diff --git a/lib/Dialect/RTG/CMakeLists.txt b/lib/Dialect/RTG/CMakeLists.txt index f33061b2d87c..9f57627c321f 100644 --- a/lib/Dialect/RTG/CMakeLists.txt +++ b/lib/Dialect/RTG/CMakeLists.txt @@ -1 +1,2 @@ add_subdirectory(IR) +add_subdirectory(Transforms) diff --git a/lib/Dialect/RTG/Transforms/CMakeLists.txt b/lib/Dialect/RTG/Transforms/CMakeLists.txt new file mode 100644 index 000000000000..cd363a383510 --- /dev/null +++ b/lib/Dialect/RTG/Transforms/CMakeLists.txt @@ -0,0 +1,15 @@ +add_circt_dialect_library(CIRCTRTGTransforms + ElaborationPass.cpp + + DEPENDS + CIRCTRTGTransformsIncGen + + LINK_COMPONENTS + Support + + LINK_LIBS PRIVATE + CIRCTRTGDialect + MLIRIR + MLIRPass +) + diff --git a/lib/Dialect/RTG/Transforms/ElaborationPass.cpp b/lib/Dialect/RTG/Transforms/ElaborationPass.cpp new file mode 100644 index 000000000000..e13923b665a4 --- /dev/null +++ b/lib/Dialect/RTG/Transforms/ElaborationPass.cpp @@ -0,0 +1,549 @@ +//===- ElaborationPass.cpp - RTG ElaborationPass implementation -----------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This pass elaborates the random parts of the RTG dialect. +// It performs randomization top-down, i.e., random constructs in a sequence +// that is invoked multiple times can yield different randomization results +// for each invokation. +// +//===----------------------------------------------------------------------===// + +#include "circt/Dialect/RTG/IR/RTGOps.h" +#include "circt/Dialect/RTG/IR/RTGVisitors.h" +#include "circt/Dialect/RTG/Transforms/RTGPasses.h" +#include "mlir/IR/PatternMatch.h" +#include +#include + +namespace circt { +namespace rtg { +#define GEN_PASS_DEF_ELABORATIONPASS +#include "circt/Dialect/RTG/Transforms/RTGPasses.h.inc" +} // namespace rtg +} // namespace circt + +using namespace mlir; +using namespace circt; +using namespace circt::rtg; + +#define DEBUG_TYPE "rtg-elaboration" + +namespace { + +//===----------------------------------------------------------------------===// +// Elaborator Values +//===----------------------------------------------------------------------===// + +/// The base class for elaborated values. Using the base class directly +/// represents an opaque value, i.e., an SSA value which we cannot further +/// interpret. +/// Derived classes will also hold the opaque value such that we always have an +/// SSA value at hand that we can use as a replacement for the concrete +/// interpreted value when needed (alternatively we could materialize new IR, +/// but that's more expensive). +/// The derived classes are supposed to +/// * add fields necessary to hold the concrete value +/// * override the virtual methods to compute equivalence based on the +/// interpreted values +/// * implement LLVM's RTTI mechanism +class ElaboratorValue { +public: + /// Creates an opaque value. + ElaboratorValue(Value value) : ElaboratorValue(value, true) { + assert(value && "null values not allowed"); + } + + virtual ~ElaboratorValue() {} + + Type getType() const { return value.getType(); } + Value getOpaqueValue() const { return value; } + bool isOpaqueValue() const { return isOpaque; } + virtual bool containsOpaqueValue() const { return isOpaque; } + + virtual llvm::hash_code getHashValue() const { + return llvm::hash_combine(value, isOpaque); + } + + virtual bool isEqual(const ElaboratorValue &other) const { + return isOpaque == other.isOpaque && value == other.value; + } + +protected: + ElaboratorValue(Value value, bool isOpaque) + : isOpaque(isOpaque), value(value) {} + +private: + const bool isOpaque; + const Value value; +}; + +/// Holds an evaluated value of a `SetType`'d value. +class SetValue : public ElaboratorValue { +public: + SetValue(Value value, SmallVector &&set, + bool debug = false) + : ElaboratorValue(value, false), originalSize(set.size()), debug(debug), + set(set) { + assert(isa(value.getType())); + + if (debug) + for (auto [i, val] : llvm::enumerate(set)) + debugMap[i] = val; + + // Make sure the vector is sorted and has no duplicates. + llvm::sort(this->set); + this->set.erase(std::unique(this->set.begin(), this->set.end()), + this->set.end()); + } + + // Implement LLVMs RTTI + static bool classof(const ElaboratorValue *val) { + return !val->isOpaqueValue() && SetType::classof(val->getType()); + } + + bool containsOpaqueValue() const override { + return llvm::any_of(set, [](auto el) { return el->containsOpaqueValue(); }); + } + + llvm::hash_code getHashValue() const override { + return llvm::hash_combine_range(set.begin(), set.end()); + } + + bool isEqual(const ElaboratorValue &other) const override { + auto *otherSet = dyn_cast(&other); + if (!otherSet) + return false; + return set == otherSet->set; + } + + ArrayRef getAsArrayRef() const { return set; } + + ElaboratorValue *getAtIndexForDebug(size_t idx) const { + assert(debug && "must have been constructed in debug mode"); + return debugMap.lookup(idx); + } + + const size_t originalSize; + +private: + // Whether this set was constructed in debug mode. + const bool debug; + + // We currently use a sorted vector to represent sets. Note that it is sorted + // by the pointer value and thus non-deterministic. + // We probably want to do some profiling in the future to see if a DenseSet or + // other representation is better suited. + SmallVector set; + + // A map to guarantee deterministic behavior when the 'rtg.elaboration' + // attribute is used in debug mode. + DenseMap debugMap; +}; + +/// Holds an evaluated value of a `SequenceType`'d value. +class SequenceClosureValue : public ElaboratorValue { +public: + SequenceClosureValue(Value value, StringAttr sequence, + ArrayRef args) + : ElaboratorValue(value, false), sequence(sequence), args(args) { + assert(isa(value.getType())); + } + + // Implement LLVMs RTTI + static bool classof(const ElaboratorValue *val) { + return !val->isOpaqueValue() && SequenceType::classof(val->getType()); + } + + bool containsOpaqueValue() const override { + return llvm::any_of(args, + [](auto el) { return el->containsOpaqueValue(); }); + } + + llvm::hash_code getHashValue() const override { + return llvm::hash_combine( + sequence, llvm::hash_combine_range(args.begin(), args.end())); + } + + bool isEqual(const ElaboratorValue &other) const override { + auto *seq = dyn_cast(&other); + if (!seq) + return false; + + return sequence == seq->sequence && args == seq->args; + } + + StringAttr getSequence() const { return sequence; } + ArrayRef getArgs() const { return args; } + +private: + StringAttr sequence; + SmallVector args; +}; + +//===----------------------------------------------------------------------===// +// Hash Map Helpers +//===----------------------------------------------------------------------===// + +// NOLINTNEXTLINE(readability-identifier-naming) +inline llvm::hash_code hash_value(const ElaboratorValue &val) { + return val.getHashValue(); +} + +struct InternMapInfo : public DenseMapInfo { + static unsigned getHashValue(const ElaboratorValue *value) { + auto *tk = getTombstoneKey(); + auto *ek = getEmptyKey(); + if (value == tk || value == ek) + return DenseMapInfo::getHashValue(value); + + return hash_value(*value); + } + + static bool isEqual(const ElaboratorValue *lhs, const ElaboratorValue *rhs) { + if (lhs == rhs) + return true; + + auto *tk = getTombstoneKey(); + auto *ek = getEmptyKey(); + if (lhs == tk || rhs == tk || lhs == ek || rhs == ek) + return false; + + return lhs->isEqual(*rhs); + } +}; + +//===----------------------------------------------------------------------===// +// Main Elaborator Implementation +//===----------------------------------------------------------------------===// + +/// Used to signal to the elaboration driver whether the operation should be +/// removed. +enum class DeletionKind { Keep, Delete }; + +/// Interprets the IR to perform and lower the represented randomizations. +class Elaborator : public RTGOpVisitor, + function_ref> { +public: + using RTGOpVisitor, + function_ref>::visitOp; + + Elaborator(SymbolTable &table, const ElaborationOptions &options) + : options(options), symTable(table) { + + // Initialize the RNG + std::random_device dev; + unsigned s = dev(); + if (options.seed.has_value()) + s = *options.seed; + rng = std::mt19937(s); + } + + /// Helper to perform internalization and keep track of interpreted value for + /// the given SSA value. + template + void internalizeResult(Value val, Args &&...args) { + auto ptr = std::make_unique(val, std::forward(args)...); + auto *e = ptr.get(); + auto [iter, _] = interned.insert({e, std::move(ptr)}); + state[val] = iter->second.get(); + } + + /// Print a nice error message for operations we don't support yet. + FailureOr + visitUnhandledOp(Operation *op, + function_ref addToWorklist) { + return op->emitError("elaboration not supported"); + } + + FailureOr + visitExternalOp(Operation *op, + function_ref addToWorklist) { + // Treat values defined by external ops as opaque, non-elaborated values. + for (auto res : op->getResults()) + internalizeResult(res); + + return DeletionKind::Keep; + } + + FailureOr + visitOp(SequenceClosureOp op, function_ref addToWorklist) { + SmallVector args; + args.reserve(op.getArgs().size()); + for (auto arg : op.getArgs()) + args.push_back(state.at(arg)); + + internalizeResult(op.getResult(), + op.getSequenceAttr(), args); + + return DeletionKind::Delete; + } + + FailureOr + visitOp(InvokeSequenceOp op, function_ref addToWorklist) { + auto *sequenceClosure = + cast(state.at(op.getSequence())); + + IRRewriter rewriter(op); + auto sequence = symTable.lookupNearestSymbolFrom( + op->getParentOfType(), sequenceClosure->getSequence()); + auto *clone = sequence->clone(); + SmallVector args; + args.reserve(sequenceClosure->getArgs().size()); + for (auto &arg : sequenceClosure->getArgs()) { + Value val = arg->getOpaqueValue(); + // Note: If the value is defined inside the same block it must be before + // this op as we would already have a dominance violation to start with. + if (val.getParentBlock() != op->getBlock()) + return op.emitError("closure argument defined outside this block"); + + args.push_back(val); + } + + for (auto &op : clone->getRegion(0).front()) + addToWorklist(&op); + + rewriter.inlineBlockBefore(&clone->getRegion(0).front(), op, args); + clone->erase(); + return DeletionKind::Delete; + } + + FailureOr + visitOp(SetCreateOp op, function_ref addToWorklist) { + SmallVector set; + for (auto val : op.getElements()) { + auto *interpValue = state.at(val); + if (interpValue->containsOpaqueValue()) + return op->emitError("cannot create a set of opaque values because " + "they cannot be reliably uniqued"); + set.emplace_back(interpValue); + } + + internalizeResult(op.getSet(), std::move(set), options.debugMode); + return DeletionKind::Delete; + } + + FailureOr + visitOp(SetSelectRandomOp op, function_ref addToWorklist) { + auto *set = cast(state.at(op.getSet())); + + ElaboratorValue *selected; + if (options.debugMode) { + auto intAttr = op->getAttrOfType("rtg.elaboration"); + if (set->originalSize != set->getAsArrayRef().size()) + op->emitWarning("set contained ") + << (set->originalSize - set->getAsArrayRef().size()) + << " duplicate value(s), the value at index " << intAttr.getInt() + << " might not be the intended one"; + selected = set->getAtIndexForDebug(intAttr.getInt()); + if (!selected) + return op->emitError("'rtg.elaboration' attribute value out of bounds, " + "must be between 0 (incl.) and ") + << set->originalSize << " (excl.)"; + } else { + std::uniform_int_distribution dist( + 0, set->getAsArrayRef().size() - 1); + selected = set->getAsArrayRef()[dist(rng)]; + } + + state[op.getResult()] = selected; + return DeletionKind::Delete; + } + + FailureOr + visitOp(SetDifferenceOp op, function_ref addToWorklist) { + auto original = cast(state.at(op.getOriginal()))->getAsArrayRef(); + auto diff = cast(state.at(op.getDiff()))->getAsArrayRef(); + + SmallVector result; + std::set_difference(original.begin(), original.end(), diff.begin(), + diff.end(), std::inserter(result, result.end())); + + internalizeResult(op.getResult(), std::move(result), + options.debugMode); + return DeletionKind::Delete; + } + + LogicalResult elaborate(TestOp testOp) { + DenseSet visited; + std::deque worklist; + DenseSet toDelete; + for (auto &op : *testOp.getBody()) + if (op.use_empty()) + worklist.push_back(&op); + + while (!worklist.empty()) { + auto *curr = worklist.back(); + if (visited.contains(curr)) { + worklist.pop_back(); + continue; + } + + if (curr->getNumRegions() != 0) + return curr->emitError("nested regions not supported"); + + bool addedSomething = false; + for (auto val : curr->getOperands()) { + if (state.contains(val)) + continue; + + auto *defOp = val.getDefiningOp(); + assert(defOp && "cannot be a BlockArgument here"); + if (!visited.contains(defOp)) { + worklist.push_back(defOp); + addedSomething = true; + } + } + + if (addedSomething) + continue; + + auto addToWorklist = [&](Operation *op) { + if (op->use_empty()) + worklist.push_front(op); + }; + auto result = dispatchOpVisitor(curr, addToWorklist); + if (failed(result)) + return failure(); + + if (*result == DeletionKind::Delete) + toDelete.insert(curr); + + visited.insert(curr); + worklist.pop_back(); + } + + // FIXME: this assumes that we didn't query the opaque value from an + // interpreted elaborator value in a way that it can remain used in the IR. + for (auto *op : toDelete) { + op->dropAllUses(); + op->erase(); + } + + // Reduce max memory consumption and make sure the values cannot be accessed + // anymore because we deleted the ops above. + state.clear(); + interned.clear(); + + return success(); + } + +private: + const ElaborationOptions &options; + std::mt19937 rng; + + // A map used to intern elaborator values. We do this such that we can + // compare pointers when, e.g., computing set differences, uniquing the + // elements in a set, etc. Otherwise, we'd need to do a deep value comparison + // in those situations. + // Use a pointer as the key with custom MapInfo because of object slicing when + // inserting an object of a derived class of ElaboratorValue. + // The custom MapInfo makes sure that we do a value comparison instead of + // comparing the pointers. + DenseMap, InternMapInfo> + interned; + + // A map from SSA values to a pointer of an interned elaborator value. + DenseMap state; + + SymbolTable symTable; +}; + +//===----------------------------------------------------------------------===// +// Elaborator Pass +//===----------------------------------------------------------------------===// + +class ElaborationPass : public rtg::impl::ElaborationPassBase { +public: + ElaborationPass() : ElaborationPassBase() {} + ElaborationPass(const ElaborationPass &other) : ElaborationPassBase(other) {} + ElaborationPass(const rtg::ElaborationOptions &options) + : ElaborationPassBase(), options(options) { + if (options.seed.has_value()) + seed = options.seed.value(); + debugMode = options.debugMode; + } + void runOnOperation() override; + void cloneTargetsIntoTests(); + +private: + ElaborationOptions options; + Pass::Option seed{*this, "seed", + llvm::cl::desc("Seed for the RNG.")}; + Pass::Option debugMode{*this, "debug", + llvm::cl::desc("Enable debug mode."), + llvm::cl::init(false)}; +}; +} // end namespace + +void ElaborationPass::runOnOperation() { + if (seed.hasValue()) + options.seed = seed; + options.debugMode = debugMode; + + cloneTargetsIntoTests(); + + auto moduleOp = getOperation(); + SymbolTable table(moduleOp); + Elaborator elaborator(table, options); + + for (auto testOp : moduleOp.getOps()) + if (failed(elaborator.elaborate(testOp))) + return signalPassFailure(); +} + +void ElaborationPass::cloneTargetsIntoTests() { + auto moduleOp = getOperation(); + for (auto target : llvm::make_early_inc_range(moduleOp.getOps())) { + for (auto test : moduleOp.getOps()) { + // If the test requries nothing from a target, we can always run it. + if (test.getTarget().getEntryTypes().empty()) + continue; + + // If the target requirements do not match, skip this test + // TODO: allow target refinements, just not coarsening + if (target.getTarget() != test.getTarget()) + continue; + + IRRewriter rewriter(test); + // Create a new test for the matched target + auto newTest = cast(rewriter.clone(*test)); + newTest.setSymName(test.getSymName().str() + "_" + + target.getSymName().str()); + + // Copy the target body into the newly created test + rewriter.setInsertionPoint(target); + auto newTarget = cast(rewriter.clone(*target)); + auto *yield = newTarget.getBody()->getTerminator(); + rewriter.inlineBlockBefore(newTarget.getBody(), newTest.getBody(), + newTest.getBody()->begin()); + rewriter.replaceAllUsesWith(newTest.getBody()->getArguments(), + yield->getOperands()); + newTest.getBody()->eraseArguments(0, + newTest.getBody()->getNumArguments()); + rewriter.eraseOp(yield); + rewriter.eraseOp(newTarget); + newTest.setTarget(DictType::get(&getContext(), {}, {})); + } + + target->erase(); + } + + // Erase all remaining non-matched tests. + for (auto test : llvm::make_early_inc_range(moduleOp.getOps())) + if (!test.getTarget().getEntryNames().empty()) + test->erase(); +} + +std::unique_ptr rtg::createElaborationPass() { + return std::make_unique(); +} + +std::unique_ptr +rtg::createElaborationPass(const rtg::ElaborationOptions &options) { + return std::make_unique(options); +} diff --git a/test/Dialect/RTG/Transform/elaboration.mlir b/test/Dialect/RTG/Transform/elaboration.mlir new file mode 100644 index 000000000000..362ab1ba8313 --- /dev/null +++ b/test/Dialect/RTG/Transform/elaboration.mlir @@ -0,0 +1,123 @@ +// RUN: circt-opt --rtg-elaborate=debug=true --split-input-file --verify-diagnostics %s | FileCheck %s + +// CHECK-LABEL: rtg.sequence @seq0 +rtg.sequence @seq0 { + %2 = arith.constant 2 : i32 +} + +// CHECK-LABEL: rtg.sequence @seq2 +rtg.sequence @seq2 { +^bb0(%arg0: !rtg.sequence): + %0 = rtg.sequence_closure @seq0 + %set = rtg.set_create %arg0, %0 : !rtg.sequence + // expected-warning @below {{set contained 1 duplicate value(s), the value at index 0 might not be the intended one}} + %seq = rtg.set_select_random %set : !rtg.set {rtg.elaboration = 0} + rtg.invoke_sequence %seq + rtg.invoke_sequence %seq +} + +// Test the set operations and passing a sequence ot another one via argument +// CHECK-LABEL: rtg.test @setOperations +rtg.test @setOperations : !rtg.dict<> { + // CHECK-NEXT: arith.constant 2 : i32 + // CHECK-NEXT: arith.constant 2 : i32 + // CHECK-NEXT: } + %0 = rtg.sequence_closure @seq0 + %1 = rtg.sequence_closure @seq2(%0 : !rtg.sequence) + %set = rtg.set_create %0, %1 : !rtg.sequence + %seq = rtg.set_select_random %set : !rtg.set {rtg.elaboration = 0} + %new_set = rtg.set_create %seq : !rtg.sequence + %diff = rtg.set_difference %set, %new_set : !rtg.set + %seq1 = rtg.set_select_random %diff : !rtg.set {rtg.elaboration = 0} + rtg.invoke_sequence %seq1 +} + +// CHECK-LABEL: rtg.sequence @seq3 +rtg.sequence @seq3 { +^bb0(%arg0: !rtg.set): + %seq = rtg.set_select_random %arg0 : !rtg.set {rtg.elaboration = 0} + rtg.invoke_sequence %seq +} + +// CHECK-LABEL: rtg.test @setArguments +rtg.test @setArguments : !rtg.dict<> { + // CHECK-NEXT: arith.constant 2 : i32 + // CHECK-NEXT: arith.constant 2 : i32 + // CHECK-NEXT: } + %0 = rtg.sequence_closure @seq0 + %1 = rtg.sequence_closure @seq2(%0 : !rtg.sequence) + %2 = rtg.set_create %1, %0 : !rtg.sequence + %3 = rtg.sequence_closure @seq3(%2 : !rtg.set) + rtg.invoke_sequence %3 +} + +// CHECK-LABEL: rtg.sequence @seq4 +rtg.sequence @seq4 { +^bb0(%arg0: !rtg.sequence): + %0 = rtg.sequence_closure @seq0 + %set = rtg.set_create %arg0, %0 : !rtg.sequence +} + +// Make sure we also delete ops that don't have any users and thus could be +// skipped and end up with null operands because the defining op was deleted due +// to other users. +// CHECK-LABEL: rtg.test @noNullOperands +rtg.test @noNullOperands : !rtg.dict<> { + // CHECK-NEXT: } + %1 = rtg.sequence_closure @seq0 + %2 = rtg.sequence_closure @seq4(%1 : !rtg.sequence) + rtg.invoke_sequence %2 +} + +rtg.target @target0 : !rtg.dict { + %0 = arith.constant 0 : i32 + rtg.yield %0 : i32 +} + +rtg.target @target1 : !rtg.dict { + %0 = arith.constant 1 : i32 + rtg.yield %0 : i32 +} + +// CHECK-LABEL: @targetTest_target0 +// CHECK: [[V0:%.+]] = arith.constant 0 +// CHECK: arith.addi [[V0]], [[V0]] + +// CHECK-LABEL: @targetTest_target1 +// CHECK: [[V0:%.+]] = arith.constant 1 +// CHECK: arith.addi [[V0]], [[V0]] +rtg.test @targetTest : !rtg.dict { +^bb0(%arg0: i32): + arith.addi %arg0, %arg0 : i32 +} + +// CHECK-NOT: @unmatchedTest +rtg.test @unmatchedTest : !rtg.dict { +^bb0(%arg0: i64): + arith.addi %arg0, %arg0 : i64 +} + +// ----- + +rtg.test @opaqueValuesAndSets : !rtg.dict<> { + %0 = arith.constant 2 : i32 + // expected-error @below {{cannot create a set of opaque values because they cannot be reliably uniqued}} + %1 = rtg.set_create %0 : i32 +} + +// ----- + +rtg.sequence @seq0 { + %2 = arith.constant 2 : i32 +} + +// Test that the elaborator value interning works as intended and exercise 'set_select_random' error messages. +rtg.test @setOperations : !rtg.dict<> { + %0 = rtg.sequence_closure @seq0 + %1 = rtg.sequence_closure @seq0 + %set = rtg.set_create %0, %1 : !rtg.sequence + // expected-warning @below {{set contained 1 duplicate value(s), the value at index 2 might not be the intended one}} + // expected-error @below {{'rtg.elaboration' attribute value out of bounds, must be between 0 (incl.) and 2 (excl.)}} + %seq = rtg.set_select_random %set : !rtg.set {rtg.elaboration = 2} + rtg.invoke_sequence %seq +}