diff --git a/frontends/PyRTG/src/pyrtg/tests.py b/frontends/PyRTG/src/pyrtg/tests.py index 0f8dbb5126f5..3a1f64d79e93 100644 --- a/frontends/PyRTG/src/pyrtg/tests.py +++ b/frontends/PyRTG/src/pyrtg/tests.py @@ -24,7 +24,7 @@ def name(self) -> str: def _codegen(self): test = rtg.TestOp( - self.name, + self.name, self.name, ir.TypeAttr.get( rtg.DictType.get([ (ir.StringAttr.get(name), ty) diff --git a/frontends/PyRTG/test/basic.mlir b/frontends/PyRTG/test/basic.mlir index 44674f550ab4..13f00c67fd03 100644 --- a/frontends/PyRTG/test/basic.mlir +++ b/frontends/PyRTG/test/basic.mlir @@ -30,3 +30,5 @@ rtg.test @test0() { %1 = rtg.randomize_sequence %0 rtg.embed_sequence %1 } + +rtg.target @singleton : !rtg.dict<> {} diff --git a/frontends/PyRTG/test/basic.py b/frontends/PyRTG/test/basic.py index 26e37739461c..e433d1f791f0 100644 --- a/frontends/PyRTG/test/basic.py +++ b/frontends/PyRTG/test/basic.py @@ -4,6 +4,15 @@ from pyrtg import test, sequence, target, entry, rtg, Label, Set, Integer, Bag, rtgtest, Immediate, IntegerRegister, Array, Bool, MemoryBlock, Memory, Tuple +# MLIR-LABEL: rtg.target @Singleton : !rtg.dict<> +# MLIR-NEXT: } + + +@target +class Singleton: + pass + + # MLIR-LABEL: rtg.target @Tgt0 : !rtg.dict> # MLIR-NEXT: [[C0:%.+]] = index.constant 0 # MLIR-NEXT: [[C1:%.+]] = index.constant 1 diff --git a/include/circt/Dialect/RTG/IR/RTGOps.td b/include/circt/Dialect/RTG/IR/RTGOps.td index 1e4506e0bda2..5f9fc894ddd0 100644 --- a/include/circt/Dialect/RTG/IR/RTGOps.td +++ b/include/circt/Dialect/RTG/IR/RTGOps.td @@ -687,6 +687,7 @@ def TestOp : RTGOp<"test", [ SingleBlock, NoTerminator, DeclareOpInterfaceMethods, + DeclareOpInterfaceMethods, HasParent<"mlir::ModuleOp"> ]> { let summary = "the root of a test"; @@ -701,8 +702,15 @@ def TestOp : RTGOp<"test", [ with that target. By default each test can be matched with all targets that fulfill its - requirements, but the user should be able to specify more constraints on the - matching procedure. + requirements, but the user can also directly provide a target via the + 'target' attribute. In that case, the test will only be randomized against + that target. + + The 'templateName' attribute specifies the name of the original test + template (mostly for result reporting purposes). This is because a test + (template) can be matched against many targets and during this process one + test per match is created, but all of them preserve the same test template + name. The body of this operation shall be processed the same way as an `rtg.sequence`'s body with the exception of the block arguments. @@ -712,11 +720,14 @@ def TestOp : RTGOp<"test", [ }]; let arguments = (ins SymbolNameAttr:$sym_name, - TypeAttrOf:$target); + StrAttr:$templateName, + TypeAttrOf:$targetType, + OptionalAttr:$target); let regions = (region SizedRegion<1>:$bodyRegion); let hasCustomAssemblyFormat = 1; let hasRegionVerifier = 1; + let hasVerifier = 1; } def TargetOp : RTGOp<"target", [ diff --git a/include/circt/Dialect/RTG/Transforms/RTGPasses.td b/include/circt/Dialect/RTG/Transforms/RTGPasses.td index 44fc5d227c7a..4a7631a8faf4 100644 --- a/include/circt/Dialect/RTG/Transforms/RTGPasses.td +++ b/include/circt/Dialect/RTG/Transforms/RTGPasses.td @@ -27,6 +27,8 @@ def ElaborationPass : Pass<"rtg-elaborate", "mlir::ModuleOp"> { let options = [ Option<"seed", "seed", "unsigned", /*default=*/"", "The seed for any RNG constructs used in the pass.">, + Option<"deleteUnmatchedTests", "delete-unmatched-tests", "bool", /*default=*/"true", + "Delete tests that could not be matched with a target.">, ]; let dependentDialects = ["mlir::index::IndexDialect"]; diff --git a/integration_test/Bindings/Python/dialects/rtg.py b/integration_test/Bindings/Python/dialects/rtg.py index c590caf69ed7..06752ffdb265 100644 --- a/integration_test/Bindings/Python/dialects/rtg.py +++ b/integration_test/Bindings/Python/dialects/rtg.py @@ -24,7 +24,7 @@ cpu1 = rtg.ConstantOp(rtgtest.CPUAttr.get(cpuAttr.id + 1)) rtg.YieldOp([cpu0, cpu1]) - test = rtg.TestOp('test_name', TypeAttr.get(dictTy)) + test = rtg.TestOp('test_name', 'test_name', TypeAttr.get(dictTy)) Block.create_at_start(test.bodyRegion, [cpuTy, cpuTy]) # CHECK: rtg.target @target_name : !rtg.dict { @@ -58,12 +58,18 @@ seq = rtg.SequenceOp('sequence_name', TypeAttr.get(rtg.SequenceType.get())) Block.create_at_start(seq.bodyRegion, []) - test = rtg.TestOp('test_name', TypeAttr.get(rtg.DictType.get())) + test = rtg.TestOp('test_name', 'test_name', + TypeAttr.get(rtg.DictType.get())) block = Block.create_at_start(test.bodyRegion, []) with InsertionPoint(block): seq_get = rtg.GetSequenceOp(rtg.SequenceType.get(), 'sequence_name') rtg.RandomizeSequenceOp(seq_get) + target = rtg.TargetOp('target', TypeAttr.get(rtg.DictType.get())) + block = Block.create_at_start(target.bodyRegion, []) + with InsertionPoint(block): + rtg.YieldOp([]) + # CHECK: rtg.test @test_name() { # CHECK-NEXT: [[SEQ:%.+]] = rtg.get_sequence @sequence_name # CHECK-NEXT: rtg.randomize_sequence [[SEQ]] @@ -78,7 +84,7 @@ rtgtool.populate_randomizer_pipeline(pm, options) pm.run(m.operation) - # CHECK: rtg.test @test_name() { + # CHECK: rtg.test @test_name_target() template "test_name" target @target { # CHECK-NEXT: } print(m) diff --git a/integration_test/Bindings/Python/rtg_pipeline.py b/integration_test/Bindings/Python/rtg_pipeline.py index a0937846449e..f64ff843d73d 100644 --- a/integration_test/Bindings/Python/rtg_pipeline.py +++ b/integration_test/Bindings/Python/rtg_pipeline.py @@ -1,5 +1,5 @@ # REQUIRES: bindings_python -# RUN: %PYTHON% %s %T && FileCheck %s --input-file=%T/test0.s --check-prefix=TEST0 && FileCheck %s --input-file=%T/test1.s --check-prefix=TEST1 +# RUN: %PYTHON% %s %T && FileCheck %s --input-file=%T/test0_target.s --check-prefix=TEST0 && FileCheck %s --input-file=%T/test1_target.s --check-prefix=TEST1 import sys import circt @@ -15,16 +15,21 @@ circt.register_dialects(ctx) m = Module.create() with InsertionPoint(m.body): - test = rtg.TestOp('test0', TypeAttr.get(rtg.DictType.get())) + test = rtg.TestOp('test0', 'test0', TypeAttr.get(rtg.DictType.get())) block = Block.create_at_start(test.bodyRegion, []) with InsertionPoint(block): rtgtest.rv32i_ebreak() - test = rtg.TestOp('test1', TypeAttr.get(rtg.DictType.get())) + test = rtg.TestOp('test1', 'test1', TypeAttr.get(rtg.DictType.get())) block = Block.create_at_start(test.bodyRegion, []) with InsertionPoint(block): rtgtest.rv32i_ecall() + target = rtg.TargetOp('target', TypeAttr.get(rtg.DictType.get())) + block = Block.create_at_start(target.bodyRegion, []) + with InsertionPoint(block): + rtg.YieldOp([]) + pm = PassManager() options = rtgtool.Options(seed=0, output_format=rtgtool.OutputFormat.ASM, diff --git a/lib/Dialect/RTG/IR/RTGOps.cpp b/lib/Dialect/RTG/IR/RTGOps.cpp index b29fd7324355..4dcdab1f92b5 100644 --- a/lib/Dialect/RTG/IR/RTGOps.cpp +++ b/lib/Dialect/RTG/IR/RTGOps.cpp @@ -497,18 +497,61 @@ LogicalResult ContextSwitchOp::verify() { //===----------------------------------------------------------------------===// LogicalResult TestOp::verifyRegions() { - if (!getTarget().entryTypesMatch(getBody()->getArgumentTypes())) + if (!getTargetType().entryTypesMatch(getBody()->getArgumentTypes())) return emitOpError("argument types must match dict entry types"); return success(); } +LogicalResult TestOp::verify() { + if (getTemplateName().empty()) + return emitOpError("template name must not be empty"); + + return success(); +} + +LogicalResult TestOp::verifySymbolUses(SymbolTableCollection &symbolTable) { + if (!getTargetAttr()) + return success(); + + auto target = + symbolTable.lookupNearestSymbolFrom(*this, getTargetAttr()); + if (!target) + return emitOpError() + << "'" << *getTarget() + << "' does not reference a valid 'rtg.target' operation"; + + // Check if target is a subtype of test requirements + // Since entries are sorted by name, we can do this in a single pass + size_t targetIdx = 0; + auto targetEntries = target.getTarget().getEntries(); + for (auto testEntry : getTargetType().getEntries()) { + // Find the matching entry in target entries. + while (targetIdx < targetEntries.size() && + targetEntries[targetIdx].name.getValue() < testEntry.name.getValue()) + targetIdx++; + + // Check if we found a matching entry with the same name and type + if (targetIdx >= targetEntries.size() || + targetEntries[targetIdx].name != testEntry.name || + targetEntries[targetIdx].type != testEntry.type) { + return emitOpError("referenced 'rtg.target' op's type is invalid: " + "missing entry called '") + << testEntry.name.getValue() << "' of type " << testEntry.type; + } + } + + return success(); +} + ParseResult TestOp::parse(OpAsmParser &parser, OperationState &result) { // Parse the name as a symbol. - if (parser.parseSymbolName( - result.getOrAddProperties().sym_name)) + StringAttr symNameAttr; + if (parser.parseSymbolName(symNameAttr)) return failure(); + result.getOrAddProperties().sym_name = symNameAttr; + // Parse the function signature. SmallVector arguments; SmallVector names; @@ -544,7 +587,31 @@ ParseResult TestOp::parse(OpAsmParser &parser, OperationState &result) { ArrayRef(entries)); if (!type) return failure(); - result.getOrAddProperties().target = TypeAttr::get(type); + result.getOrAddProperties().targetType = + TypeAttr::get(type); + + std::string templateName; + if (!parser.parseOptionalKeyword("template")) { + auto loc = parser.getCurrentLocation(); + if (parser.parseString(&templateName)) + return failure(); + + if (templateName.empty()) + return parser.emitError(loc, "template name must not be empty"); + } + + StringAttr templateNameAttr = symNameAttr; + if (!templateName.empty()) + templateNameAttr = StringAttr::get(result.getContext(), templateName); + + StringAttr targetName; + if (!parser.parseOptionalKeyword("target")) + if (parser.parseSymbolName(targetName)) + return failure(); + + result.getOrAddProperties().templateName = + templateNameAttr; + result.getOrAddProperties().target = targetName; auto loc = parser.getCurrentLocation(); if (parser.parseOptionalAttrDictWithKeyword(result.attributes)) @@ -574,15 +641,25 @@ void TestOp::print(OpAsmPrinter &p) { p << "("; SmallString<32> resultNameStr; llvm::interleaveComma( - llvm::zip(getTarget().getEntries(), getBody()->getArguments()), p, + llvm::zip(getTargetType().getEntries(), getBody()->getArguments()), p, [&](auto entryAndArg) { auto [entry, arg] = entryAndArg; p << entry.name.getValue() << " = "; p.printRegionArgument(arg); }); p << ")"; + + if (getSymNameAttr() != getTemplateNameAttr()) + p << " template " << getTemplateNameAttr(); + + if (getTargetAttr()) { + p << " target "; + p.printSymbolName(getTargetAttr().getValue()); + } + p.printOptionalAttrDictWithKeyword( - (*this)->getAttrs(), {getSymNameAttrName(), getTargetAttrName()}); + (*this)->getAttrs(), {getSymNameAttrName(), getTargetTypeAttrName(), + getTargetAttrName(), getTemplateNameAttrName()}); p << ' '; p.printRegion(getBodyRegion(), /*printEntryBlockArgs=*/false); } @@ -590,7 +667,7 @@ void TestOp::print(OpAsmPrinter &p) { void TestOp::getAsmBlockArgumentNames(Region ®ion, OpAsmSetValueNameFn setNameFn) { for (auto [entry, arg] : - llvm::zip(getTarget().getEntries(), region.getArguments())) + llvm::zip(getTargetType().getEntries(), region.getArguments())) setNameFn(arg, entry.name.getValue()); } diff --git a/lib/Dialect/RTG/Transforms/ElaborationPass.cpp b/lib/Dialect/RTG/Transforms/ElaborationPass.cpp index fd8590c9d17d..39683b97b75d 100644 --- a/lib/Dialect/RTG/Transforms/ElaborationPass.cpp +++ b/lib/Dialect/RTG/Transforms/ElaborationPass.cpp @@ -744,10 +744,12 @@ class Materializer { return diag; }; - Value val = materialize(state.at(operand.get()), op->getLoc(), emitError); + auto elabVal = state.at(operand.get()); + Value val = materialize(elabVal, op->getLoc(), emitError); if (!val) return failure(); + state[val] = elabVal; operand.set(val); } @@ -773,6 +775,8 @@ class Materializer { ArrayRef getBlockArgTypes() const { return blockArgTypes; } + void map(ElaboratorValue eval, Value val) { materializedValues[eval] = val; } + template OpTy create(Location location, Args &&...args) { return builder.create(location, std::forward(args)...); @@ -891,8 +895,12 @@ class Materializer { Value visit(MemoryBlockStorage *val, Location loc, function_ref emitError) { - emitError() << "materializing a memory block not supported yet"; - return Value(); + Value res = builder.create( + loc, val->type, IntegerAttr::get(builder.getIndexType(), val->size), + IntegerAttr::get(builder.getIntegerType(val->baseAddress.getBitWidth()), + val->baseAddress)); + materializedValues[val] = res; + return res; } Value visit(SequenceStorage *val, Location loc, @@ -1606,6 +1614,10 @@ class Elaborator : public RTGOpVisitor> { return DeletionKind::Delete; } + FailureOr visitOp(rtg::YieldOp op) { + return DeletionKind::Keep; + } + FailureOr visitOp(scf::IfOp op) { bool cond = get(op.getCondition()); auto &toElaborate = cond ? op.getThenRegion() : op.getElseRegion(); @@ -1616,14 +1628,13 @@ class Elaborator : public RTGOpVisitor> { // to the elaborated values outside the nested region (since it is not // isolated from above) and we want to materialize the region inline, thus // don't need a new materializer instance. - if (failed(elaborate(toElaborate))) + SmallVector yieldedVals; + if (failed(elaborate(toElaborate, {}, yieldedVals))) return failure(); // Map the results of the 'scf.if' to the yielded values. - for (auto [res, out] : - llvm::zip(op.getResults(), - toElaborate.front().getTerminator()->getOperands())) - state[res] = state.at(out); + for (auto [res, out] : llvm::zip(op.getResults(), yieldedVals)) + state[res] = out; return DeletionKind::Delete; } @@ -1648,17 +1659,18 @@ class Elaborator : public RTGOpVisitor> { state[iterArg] = state.at(initArg); // This loop performs the actual 'scf.for' loop iterations. + SmallVector yieldedVals; for (size_t i = lowerBound; i < upperBound; i += step) { - if (failed(elaborate(op.getBodyRegion()))) + yieldedVals.clear(); + if (failed(elaborate(op.getBodyRegion(), {}, yieldedVals))) return failure(); // Prepare for the next iteration by updating the mapping of the nested // regions block arguments state[op.getInductionVar()] = i + step; for (auto [iterArg, prevIterArg] : - llvm::zip(op.getRegionIterArgs(), - op.getBody()->getTerminator()->getOperands())) - state[iterArg] = state.at(prevIterArg); + llvm::zip(op.getRegionIterArgs(), yieldedVals)) + state[iterArg] = prevIterArg; } // Transfer the previously yielded values to the for loop result values. @@ -1774,7 +1786,8 @@ class Elaborator : public RTGOpVisitor> { // NOLINTNEXTLINE(misc-no-recursion) LogicalResult elaborate(Region ®ion, - ArrayRef regionArguments = {}) { + ArrayRef regionArguments, + SmallVector &terminatorOperands) { if (region.getBlocks().size() > 1) return region.getParentOp()->emitOpError( "regions with more than one block are not supported"); @@ -1807,6 +1820,10 @@ class Elaborator : public RTGOpVisitor> { }); } + if (region.front().mightHaveTerminator()) + for (auto val : region.front().getTerminator()->getOperands()) + terminatorOperands.push_back(state.at(val)); + return success(); } @@ -1852,8 +1869,9 @@ Materializer::elaborateSequence(const RandomizedSequenceStorage *seq, Materializer materializer(OpBuilder::atBlockBegin(seqOp.getBody()), testState, sharedState, elabArgs); Elaborator elaborator(sharedState, testState, materializer, seq->context); - if (failed( - elaborator.elaborate(familyOp.getBodyRegion(), seq->sequence->args))) + SmallVector yieldedVals; + if (failed(elaborator.elaborate(familyOp.getBodyRegion(), seq->sequence->args, + yieldedVals))) return {}; seqOp.setSequenceType( @@ -1873,7 +1891,7 @@ struct ElaborationPass using Base::Base; void runOnOperation() override; - void cloneTargetsIntoTests(SymbolTable &table); + void matchTestsAgainstTargets(SymbolTable &table); LogicalResult elaborateModule(ModuleOp moduleOp, SymbolTable &table); }; } // namespace @@ -1882,23 +1900,49 @@ void ElaborationPass::runOnOperation() { auto moduleOp = getOperation(); SymbolTable table(moduleOp); - cloneTargetsIntoTests(table); + matchTestsAgainstTargets(table); if (failed(elaborateModule(moduleOp, table))) return signalPassFailure(); } -void ElaborationPass::cloneTargetsIntoTests(SymbolTable &table) { +void ElaborationPass::matchTestsAgainstTargets(SymbolTable &table) { auto moduleOp = getOperation(); - for (auto target : llvm::make_early_inc_range(moduleOp.getOps())) { - for (auto test : moduleOp.getOps()) { - // If the test requires nothing from a target, we can always run it. - if (test.getTarget().getEntries().empty()) - continue; - // If the target requirements do not match, skip this test - // TODO: allow target refinements, just not coarsening - if (target.getTarget() != test.getTarget()) + for (auto test : llvm::make_early_inc_range(moduleOp.getOps())) { + if (test.getTargetAttr()) + continue; + + bool matched = false; + + for (auto target : moduleOp.getOps()) { + // Check if the target type is a subtype of the test's target type + // This means that for each entry in the test's target type, there must be + // a corresponding entry with the same name and type in the target's type + bool isSubtype = true; + auto testEntries = test.getTargetType().getEntries(); + auto targetEntries = target.getTarget().getEntries(); + + // Check if target is a subtype of test requirements + // Since entries are sorted by name, we can do this in a single pass + size_t targetIdx = 0; + for (auto testEntry : testEntries) { + // Find the matching entry in target entries. + while (targetIdx < targetEntries.size() && + targetEntries[targetIdx].name.getValue() < + testEntry.name.getValue()) + targetIdx++; + + // Check if we found a matching entry with the same name and type + if (targetIdx >= targetEntries.size() || + targetEntries[targetIdx].name != testEntry.name || + targetEntries[targetIdx].type != testEntry.type) { + isSubtype = false; + break; + } + } + + if (!isSubtype) continue; IRRewriter rewriter(test); @@ -1906,31 +1950,22 @@ void ElaborationPass::cloneTargetsIntoTests(SymbolTable &table) { auto newTest = cast(test->clone()); newTest.setSymName(test.getSymName().str() + "_" + target.getSymName().str()); - table.insert(newTest, rewriter.getInsertionPoint()); - // Copy the target body into the newly created test - IRMapping mapping; - rewriter.setInsertionPointToStart(newTest.getBody()); - for (auto &op : target.getBody()->without_terminator()) - rewriter.clone(op, mapping); + // Set the target symbol specifying that this test is only suitable for + // that target. + newTest.setTargetAttr(target.getSymNameAttr()); - for (auto [returnVal, result] : - llvm::zip(target.getBody()->getTerminator()->getOperands(), - newTest.getBody()->getArguments())) - result.replaceAllUsesWith(mapping.lookup(returnVal)); - - newTest.getBody()->eraseArguments(0, - newTest.getBody()->getNumArguments()); - newTest.setTarget(DictType::get(&getContext(), {})); + table.insert(newTest, rewriter.getInsertionPoint()); + matched = true; } - target->erase(); + if (matched || deleteUnmatchedTests) + test->erase(); } +} - // Erase all remaining non-matched tests. - for (auto test : llvm::make_early_inc_range(moduleOp.getOps())) - if (!test.getTarget().getEntries().empty()) - test->erase(); +static bool onlyLegalToMaterializeInTarget(Type type) { + return isa(type); } LogicalResult ElaborationPass::elaborateModule(ModuleOp moduleOp, @@ -1940,18 +1975,78 @@ LogicalResult ElaborationPass::elaborateModule(ModuleOp moduleOp, // Update the name cache state.names.add(moduleOp); + struct TargetElabResult { + DictType targetType; + SmallVector yields; + TestState testState; + }; + + // Map to store elaborated targets + DenseMap targetMap; + for (auto targetOp : moduleOp.getOps()) { + LLVM_DEBUG(llvm::dbgs() << "=== Elaborating target @" + << targetOp.getSymName() << "\n\n"); + + auto &result = targetMap[targetOp.getSymNameAttr()]; + result.targetType = targetOp.getTarget(); + + SmallVector blockArgs; + Materializer targetMaterializer(OpBuilder::atBlockBegin(targetOp.getBody()), + result.testState, state, blockArgs); + Elaborator targetElaborator(state, result.testState, targetMaterializer); + + // Elaborate the target + if (failed(targetElaborator.elaborate(targetOp.getBodyRegion(), {}, + result.yields))) + return failure(); + } + // Initialize the worklist with the test ops since they cannot be placed by // other ops. for (auto testOp : moduleOp.getOps()) { - TestState testState; - testState.name = testOp.getSymNameAttr(); + // Skip tests without a target attribute - these couldn't be matched + // against any target but can be useful to keep around for reporting + // purposes. + if (!testOp.getTargetAttr()) + continue; + LLVM_DEBUG(llvm::dbgs() - << "\n=== Elaborating test @" << testOp.getSymName() << "\n\n"); + << "\n=== Elaborating test @" << testOp.getTemplateName() + << " for target @" << *testOp.getTarget() << "\n\n"); + + // Get the target for this test + auto targetResult = targetMap[testOp.getTargetAttr()]; + TestState testState = targetResult.testState; + testState.name = testOp.getSymNameAttr(); + + SmallVector filteredYields; + unsigned i = 0; + for (auto [entry, yield] : + llvm::zip(targetResult.targetType.getEntries(), targetResult.yields)) { + if (i >= testOp.getTargetType().getEntries().size()) + break; + + if (entry.name == testOp.getTargetType().getEntries()[i].name) { + filteredYields.push_back(yield); + ++i; + } + } + + // Now elaborate the test with the same state, passing the target yield + // values as arguments SmallVector blockArgs; Materializer materializer(OpBuilder::atBlockBegin(testOp.getBody()), testState, state, blockArgs); + + for (auto [arg, val] : + llvm::zip(testOp.getBody()->getArguments(), filteredYields)) + if (onlyLegalToMaterializeInTarget(arg.getType())) + materializer.map(val, arg); + Elaborator elaborator(state, testState, materializer); - if (failed(elaborator.elaborate(testOp.getBodyRegion()))) + SmallVector ignore; + if (failed(elaborator.elaborate(testOp.getBodyRegion(), filteredYields, + ignore))) return failure(); materializer.finalize(); diff --git a/test/CAPI/rtg-pipelines.c b/test/CAPI/rtg-pipelines.c index f12ef1a78e20..76c64f2d0c03 100644 --- a/test/CAPI/rtg-pipelines.c +++ b/test/CAPI/rtg-pipelines.c @@ -23,6 +23,9 @@ int main(int argc, char **argv) { "}\n" "rtg.test @test() {\n" " %0 = rtg.get_sequence @seq : !rtg.sequence\n" + "}\n" + "rtg.target @target : !rtg.dict<> {\n" + " rtg.yield\n" "}\n")); if (mlirModuleIsNull(moduleOp)) { printf("ERROR: Could not parse.\n"); diff --git a/test/Dialect/RTG/IR/basic.mlir b/test/Dialect/RTG/IR/basic.mlir index 30a15e1ae148..6c824dafee7c 100644 --- a/test/Dialect/RTG/IR/basic.mlir +++ b/test/Dialect/RTG/IR/basic.mlir @@ -208,3 +208,6 @@ rtg.test @tuples() { %0 = rtg.tuple_create %idx0, %true : index, i1 %1 = rtg.tuple_extract %0 at 1 : tuple } + +// CHECK-LABEL: rtg.test @template() template "temp_name" target @target { +rtg.test @template() template "temp_name" target @target { } diff --git a/test/Dialect/RTG/IR/errors.mlir b/test/Dialect/RTG/IR/errors.mlir index 1e24fe89ff03..2fac5ff05e4e 100644 --- a/test/Dialect/RTG/IR/errors.mlir +++ b/test/Dialect/RTG/IR/errors.mlir @@ -96,7 +96,42 @@ rtg.target @target : !rtg.dict { // ----- // expected-error @below {{argument types must match dict entry types}} -"rtg.test"() <{sym_name="test", target=!rtg.dict}> ({^bb0(%b: i8):}) : () -> () +"rtg.test"() <{sym_name="test", templateName="test", targetType=!rtg.dict}> ({^bb0(%b: i8):}) : () -> () + +// ----- + +// expected-error @below {{template name must not be empty}} +"rtg.test"() <{sym_name="test", templateName="", targetType=!rtg.dict<>}> ({^bb0:}) : () -> () + +// ----- + +// expected-error @below {{'target' does not reference a valid 'rtg.target' operation}} +rtg.test @test(a = %a: i32) target @target { +} + +// ----- + +rtg.target @target : !rtg.dict<> { } + +// expected-error @below {{referenced 'rtg.target' op's type is invalid: missing entry called 'a' of type 'i32'}} +rtg.test @test(a = %a: i32) target @target { +} + +// ----- + +rtg.target @target : !rtg.dict { + %0 = index.constant 0 + rtg.yield %0 : index +} + +// expected-error @below {{referenced 'rtg.target' op's type is invalid: missing entry called 'a' of type 'i32'}} +rtg.test @test(a = %a: i32) target @target { +} + +// ----- + +// expected-error @below {{template name must not be empty}} +rtg.test @test() template "" {} // ----- diff --git a/test/Dialect/RTG/Transform/elaboration.mlir b/test/Dialect/RTG/Transform/elaboration.mlir index ca9926981177..55b35053df7d 100644 --- a/test/Dialect/RTG/Transform/elaboration.mlir +++ b/test/Dialect/RTG/Transform/elaboration.mlir @@ -12,9 +12,15 @@ func.func @dummy9(%arg0: !rtg.set>) -> () {retur func.func @dummy10(%arg0: !rtg.set>) -> () {return} func.func @dummy11(%arg0: !rtg.set) -> () {return} func.func @dummy12(%arg0: !rtg.bag) -> () {return} +func.func @dummy13(%arg0: !rtg.isa.memoryblock<32>) -> () {return} + +rtg.target @singletonTarget : !rtg.dict { + %0 = index.constant 0 + rtg.yield %0 : index +} // CHECK-LABEL: @immediates -rtg.test @immediates() { +rtg.test @immediates(singleton = %none: index) { // CHECK-NEXT: [[V0:%.+]] = rtg.constant #rtg.isa.immediate<2, -1> // CHECK-NEXT: func.call @dummy6([[V0]]) : (!rtg.isa.immediate<2>) -> () %0 = rtg.constant #rtg.isa.immediate<2, -1> @@ -29,7 +35,7 @@ rtg.test @immediates() { // Test the set operations and passing a sequence to another one via argument // CHECK-LABEL: rtg.test @setOperations -rtg.test @setOperations() { +rtg.test @setOperations(singleton = %none: index) { // CHECK-NEXT: [[V0:%.+]] = index.constant 2 // CHECK-NEXT: [[V1:%.+]] = index.constant 3 // CHECK-NEXT: [[V2:%.+]] = index.constant 4 @@ -56,7 +62,7 @@ rtg.test @setOperations() { } // CHECK-LABEL: rtg.test @setCartesianProduct -rtg.test @setCartesianProduct() { +rtg.test @setCartesianProduct(singleton = %none: index) { %idx0 = index.constant 0 %idx1 = index.constant 1 %0 = rtg.set_create %idx0, %idx1 : index @@ -101,7 +107,7 @@ rtg.test @setCartesianProduct() { } // CHECK-LABEL: rtg.test @bagOperations -rtg.test @bagOperations() { +rtg.test @bagOperations(singleton = %none: index) { // CHECK-NEXT: [[V0:%.+]] = index.constant 2 // CHECK-NEXT: [[V1:%.+]] = index.constant 8 // CHECK-NEXT: [[V2:%.+]] = index.constant 3 @@ -132,7 +138,7 @@ rtg.test @bagOperations() { } // CHECK-LABEL: rtg.test @setSize -rtg.test @setSize() { +rtg.test @setSize(singleton = %none: index) { // CHECK-NEXT: [[C:%.+]] = index.constant 1 // CHECK-NEXT: func.call @dummy2([[C]]) // CHECK-NEXT: } @@ -143,7 +149,7 @@ rtg.test @setSize() { } // CHECK-LABEL: rtg.test @bagSize -rtg.test @bagSize() { +rtg.test @bagSize(singleton = %none: index) { // CHECK-NEXT: [[C:%.+]] = index.constant 1 // CHECK-NEXT: func.call @dummy2([[C]]) // CHECK-NEXT: } @@ -185,7 +191,7 @@ rtg.sequence @seq0(%arg0: index) { } // CHECK-LABEL: rtg.test @sequenceSubstitution -rtg.test @sequenceSubstitution() { +rtg.test @sequenceSubstitution(singleton = %none: index) { // CHECK-NEXT: [[V0:%.+]] = rtg.get_sequence @seq0{{.*}} : !rtg.sequence{{$}} // CHECK-NEXT: [[V1:%.+]] = rtg.randomize_sequence [[V0]] // CHECK-NEXT: rtg.embed_sequence [[V1]] @@ -197,7 +203,7 @@ rtg.test @sequenceSubstitution() { } // CHECK-LABEL: rtg.test @sameSequenceDifferentArgs -rtg.test @sameSequenceDifferentArgs() { +rtg.test @sameSequenceDifferentArgs(singleton = %none: index) { // CHECK-NEXT: [[V0:%.*]] = rtg.get_sequence @seq0_1 : !rtg.sequence // CHECK-NEXT: [[V1:%.*]] = rtg.randomize_sequence [[V0]] // CHECK-NEXT: rtg.embed_sequence [[V1]] @@ -217,7 +223,7 @@ rtg.test @sameSequenceDifferentArgs() { } // CHECK-LABEL: rtg.test @sequenceClosureFixesRandomization -rtg.test @sequenceClosureFixesRandomization() { +rtg.test @sequenceClosureFixesRandomization(singleton = %none: index) { // CHECK-NEXT: [[V0:%.+]] = rtg.get_sequence @seq3_0 : !rtg.sequence // CHECK-NEXT: [[V1:%.+]] = rtg.randomize_sequence [[V0]] // CHECK-NEXT: rtg.embed_sequence [[V1]] @@ -254,7 +260,7 @@ rtg.sequence @seq3(%arg0: !rtg.set) { } // CHECK-LABEL: @indexOps -rtg.test @indexOps() { +rtg.test @indexOps(singleton = %none: index) { // CHECK: [[C:%.+]] = index.constant 2 %0 = index.constant 1 @@ -294,7 +300,7 @@ rtg.test @indexOps() { } // CHECK-LABEL: @scfIf -rtg.test @scfIf() { +rtg.test @scfIf(singleton = %none: index) { %0 = index.bool.constant true %1 = index.bool.constant false @@ -338,7 +344,7 @@ rtg.test @scfIf() { } // CHECK-LABEL: @scfFor -rtg.test @scfFor() { +rtg.test @scfFor(singleton = %none: index) { // CHECK-NEXT: [[C0:%.+]] = index.constant 0 // CHECK-NEXT: func.call @dummy2([[C0]]) // CHECK-NEXT: [[C1:%.+]] = index.constant 1 @@ -378,7 +384,7 @@ rtg.test @scfFor() { } // CHECK-LABEL: @fixedRegisters -rtg.test @fixedRegisters() { +rtg.test @fixedRegisters(singleton = %none: index) { // CHECK-NEXT: [[RA:%.+]] = rtg.fixed_reg #rtgtest.ra // CHECK-NEXT: [[SP:%.+]] = rtg.fixed_reg #rtgtest.sp // CHECK-NEXT: [[IMM:%.+]] = rtg.constant #rtg.isa.immediate<12, 0> @@ -390,7 +396,7 @@ rtg.test @fixedRegisters() { } // CHECK-LABEL: @virtualRegisters -rtg.test @virtualRegisters() { +rtg.test @virtualRegisters(singleton = %none: index) { // CHECK-NEXT: [[R0:%.+]] = rtg.virtual_reg [#rtgtest.a0 : !rtgtest.ireg, #rtgtest.a1 : !rtgtest.ireg] // CHECK-NEXT: [[R1:%.+]] = rtg.virtual_reg [#rtgtest.s0 : !rtgtest.ireg, #rtgtest.s1 : !rtgtest.ireg] // CHECK-NEXT: [[IMM:%.+]] = rtg.constant #rtg.isa.immediate<12, 0> @@ -419,7 +425,7 @@ rtg.test @virtualRegisters() { // CHECK: rtgtest.rv32i.jalr %arg0, %arg0 // CHECK: rtgtest.rv32i.jalr %arg1, %arg2 -// CHECK-LABEL: rtg.test @valuesWithIdentity() { +// CHECK-LABEL: rtg.test @valuesWithIdentity // CHECK: [[VREG0:%.+]] = rtg.virtual_reg [#rtgtest.a0 : !rtgtest.ireg, #rtgtest.a1 : !rtgtest.ireg] // CHECK: [[VREG1:%.+]] = rtg.virtual_reg [#rtgtest.a0 : !rtgtest.ireg, #rtgtest.a1 : !rtgtest.ireg] // CHECK: rtgtest.rv32i.jalr [[VREG0]], [[VREG1]] @@ -434,7 +440,7 @@ rtg.sequence @valuesWithIdentitySeq(%imm: !rtg.isa.immediate<12>, %reg: !rtgtest rtgtest.rv32i.jalr %r0, %r1, %imm } -rtg.test @valuesWithIdentity() { +rtg.test @valuesWithIdentity(singleton = %none: index) { %r0 = rtg.virtual_reg [#rtgtest.a0, #rtgtest.a1] %r1 = rtg.virtual_reg [#rtgtest.a0, #rtgtest.a1] %r2 = rtg.virtual_reg [#rtgtest.a0, #rtgtest.a1] @@ -450,7 +456,7 @@ rtg.test @valuesWithIdentity() { } // CHECK-LABEL: @labels -rtg.test @labels() { +rtg.test @labels(singleton = %none: index) { // CHECK-NEXT: [[L0:%.+]] = rtg.label_unique_decl "label0" // CHECK-NEXT: rtg.label local [[L0]] // CHECK-NEXT: [[L1:%.+]] = rtg.label_decl "label0" @@ -468,7 +474,7 @@ rtg.test @labels() { } // CHECK-LABEL: rtg.test @randomIntegers -rtg.test @randomIntegers() { +rtg.test @randomIntegers(singleton = %none: index) { %lower = index.constant 5 %upper = index.constant 10 %0 = rtg.random_number_in_range [%lower, %upper) {rtg.elaboration_custom_seed=0} @@ -602,8 +608,8 @@ rtg.sequence @interleaveSequencesSeq1() { rtgtest.rv32i.ecall } -// CHECK-LABEL: @interleaveSequences() -rtg.test @interleaveSequences() { +// CHECK-LABEL: rtg.test @interleaveSequences +rtg.test @interleaveSequences(singleton = %none: index) { // CHECK-NEXT: [[V0:%.+]] = rtg.get_sequence @interleaveSequencesSeq0_0 : !rtg.sequence // CHECK-NEXT: [[V2:%.+]] = rtg.randomize_sequence [[V0]] // CHECK-NEXT: [[V1:%.+]] = rtg.get_sequence @interleaveSequencesSeq1_0 : !rtg.sequence @@ -629,11 +635,13 @@ rtg.target @memoryBlocks : !rtg.dict> { // CHECK-LABEL: @memoryBlockTest_memoryBlocks rtg.test @memoryBlockTest(mem_block = %arg0: !rtg.isa.memoryblock<32>) { + func.call @dummy13(%arg0) : (!rtg.isa.memoryblock<32>) -> () + // CHECK-NEXT: func.call @dummy13(%mem_block) // CHECK-NEXT: } } // CHECK-LABEL: rtg.test @arrays -rtg.test @arrays() { +rtg.test @arrays(singleton = %none: index) { // CHECK-NEXT: [[V0:%.+]] = rtg.array_create : index // CHECK-NEXT: func.call @dummy7([[V0]]) : (!rtg.array) -> () %0 = rtg.array_create : index @@ -658,7 +666,7 @@ rtg.test @arrays() { } // CHECK-LABEL: rtg.test @arithOps -rtg.test @arithOps() { +rtg.test @arithOps(singleton = %none: index) { // CHECK-NEXT: [[V0:%.+]] = index.constant 6 // CHECK-NEXT: func.call @dummy2([[V0]]) @@ -673,7 +681,7 @@ rtg.test @arithOps() { } // CHECK-LABEL: rtg.test @tuples -rtg.test @tuples() { +rtg.test @tuples(singleton = %none: index) { %idx0 = index.constant 0 %idx1 = index.constant 1 %0 = rtg.tuple_create %idx1, %idx0 : index, index @@ -697,25 +705,50 @@ rtg.test @useFolders(single_core = %single_core: !rtgtest.cpu) { func.call @dummy2(%0) : (index) -> () } +rtg.target @subtypeTarget : !rtg.dict { + %0 = index.constant 0 + rtg.yield %0, %0 : index, index +} + +// CHECK: rtg.test @subtypeMatching_subtypeTarget( +rtg.test @subtypeMatching(b = %b: index) { + func.call @dummy2(%b) : (index) -> () +} + // ----- -rtg.test @nestedRegionsNotSupported() { +rtg.target @singletonTarget : !rtg.dict { + %0 = index.constant 0 + rtg.yield %0 : index +} + +rtg.test @nestedRegionsNotSupported(singleton = %none: index) { // expected-error @below {{ops with nested regions must be elaborated away}} scf.execute_region { scf.yield } } // ----- -rtg.test @untypedAttributes() { +rtg.target @singletonTarget : !rtg.dict { + %0 = index.constant 0 + rtg.yield %0 : index +} + +rtg.test @untypedAttributes(singleton = %none: index) { // expected-error @below {{only typed attributes supported for constant-like operations}} %0 = rtgtest.constant_test index {value = [10 : index]} } // ----- +rtg.target @singletonTarget : !rtg.dict { + %0 = index.constant 0 + rtg.yield %0 : index +} + func.func @dummy(%arg0: index) {return} -rtg.test @untypedAttributes() { +rtg.test @untypedAttributes(singleton = %none: index) { %0 = rtgtest.constant_test index {value = "str"} // expected-error @below {{materializer of dialect 'builtin' unable to materialize value for attribute '"str"'}} // expected-note @below {{while materializing value for operand#0}} @@ -724,9 +757,14 @@ rtg.test @untypedAttributes() { // ----- +rtg.target @singletonTarget : !rtg.dict { + %0 = index.constant 0 + rtg.yield %0 : index +} + func.func @dummy2(%arg0: index) -> () {return} -rtg.test @randomIntegers() { +rtg.test @randomIntegers(singleton = %none: index) { %c5 = index.constant 5 // expected-error @below {{cannot select a number from an empty range}} %0 = rtg.random_number_in_range [%c5, %c5) @@ -777,7 +815,12 @@ rtg.test @contextSwitchNotAvailable(cpu = %cpu: !rtgtest.cpu) { // ----- -rtg.test @emptySetSelect() { +rtg.target @singletonTarget : !rtg.dict { + %0 = index.constant 0 + rtg.yield %0 : index +} + +rtg.test @emptySetSelect(singleton = %none: index) { %0 = rtg.set_create : !rtg.isa.label // expected-error @below {{cannot select from an empty set}} %1 = rtg.set_select_random %0 : !rtg.set @@ -786,7 +829,12 @@ rtg.test @emptySetSelect() { // ----- -rtg.test @emptyBagSelect() { +rtg.target @singletonTarget : !rtg.dict { + %0 = index.constant 0 + rtg.yield %0 : index +} + +rtg.test @emptyBagSelect(singleton = %none: index) { %0 = rtg.bag_create : !rtg.isa.label // expected-error @below {{cannot select from an empty bag}} %1 = rtg.bag_select_random %0 : !rtg.bag @@ -795,9 +843,14 @@ rtg.test @emptyBagSelect() { // ----- +rtg.target @singletonTarget : !rtg.dict { + %0 = index.constant 0 + rtg.yield %0 : index +} + func.func @dummy6(%arg0: !rtg.isa.immediate<2>) -> () {return} -rtg.test @integerTooBig() { +rtg.test @integerTooBig(singleton = %none: index) { %1 = index.constant 8 // expected-error @below {{cannot represent 8 with 2 bits}} %2 = rtg.isa.int_to_immediate %1 : !rtg.isa.immediate<2> @@ -806,9 +859,14 @@ rtg.test @integerTooBig() { // ----- +rtg.target @singletonTarget : !rtg.dict { + %0 = index.constant 0 + rtg.yield %0 : index +} + func.func @dummy6(%arg0: index) -> () {return} -rtg.test @oobArrayAccess() { +rtg.test @oobArrayAccess(singleton = %none: index) { %0 = index.constant 0 %1 = rtg.array_create : index // expected-error @below {{invalid to access index 0 of an array with 0 elements}} @@ -818,9 +876,14 @@ rtg.test @oobArrayAccess() { // ----- +rtg.target @singletonTarget : !rtg.dict { + %0 = index.constant 0 + rtg.yield %0 : index +} + func.func @dummy6(%arg0: !rtg.array) -> () {return} -rtg.test @oobArrayAccess() { +rtg.test @oobArrayAccess(singleton = %none: index) { %0 = index.constant 0 %1 = rtg.array_create : index // expected-error @below {{invalid to access index 0 of an array with 0 elements}} @@ -830,7 +893,12 @@ rtg.test @oobArrayAccess() { // ----- -rtg.test @arith_invalid_type() { +rtg.target @singletonTarget : !rtg.dict { + %0 = index.constant 0 + rtg.yield %0 : index +} + +rtg.test @arith_invalid_type(singleton = %none: index) { %0 = arith.constant 3 : i32 // expected-error @below {{only index operands supported}} %1 = arith.addi %0, %0 : i32 @@ -838,7 +906,12 @@ rtg.test @arith_invalid_type() { // ----- -rtg.test @arith_invalid_type() { +rtg.target @singletonTarget : !rtg.dict { + %0 = index.constant 0 + rtg.yield %0 : index +} + +rtg.test @arith_invalid_type(singleton = %none: index) { %0 = arith.constant 3 : i32 // expected-error @below {{only 'i1' operands supported}} %1 = arith.andi %0, %0 : i32 @@ -846,7 +919,12 @@ rtg.test @arith_invalid_type() { // ----- -rtg.test @arith_invalid_type() { +rtg.target @singletonTarget : !rtg.dict { + %0 = index.constant 0 + rtg.yield %0 : index +} + +rtg.test @arith_invalid_type(singleton = %none: index) { %0 = arith.constant 3 : i32 // expected-error @below {{only 'i1' operands supported}} %1 = arith.xori %0, %0 : i32 @@ -854,7 +932,12 @@ rtg.test @arith_invalid_type() { // ----- -rtg.test @arith_invalid_type() { +rtg.target @singletonTarget : !rtg.dict { + %0 = index.constant 0 + rtg.yield %0 : index +} + +rtg.test @arith_invalid_type(singleton = %none: index) { %0 = arith.constant 3 : i32 // expected-error @below {{only 'i1' operands supported}} %1 = arith.ori %0, %0 : i32