From 65f4117b310210c88b86137397c36b4ce5ebcb0d Mon Sep 17 00:00:00 2001 From: Martin Erhart Date: Wed, 20 Nov 2024 11:08:50 +0000 Subject: [PATCH] [RTG] Add TestOp, TargetOp, and DictType --- include/circt/Dialect/RTG/IR/RTGInterfaces.td | 7 ++ include/circt/Dialect/RTG/IR/RTGOps.td | 80 +++++++++++++++++++ include/circt/Dialect/RTG/IR/RTGTypes.td | 20 +++++ include/circt/Dialect/RTG/IR/RTGVisitors.h | 19 +++-- .../circt/Dialect/RTGTest/IR/RTGTestOps.td | 2 +- lib/Dialect/RTG/IR/RTGOps.cpp | 23 ++++++ lib/Dialect/RTG/IR/RTGTypes.cpp | 71 ++++++++++++++++ test/Dialect/RTG/IR/basic.mlir | 24 ++++++ test/Dialect/RTG/IR/errors.mlir | 20 +++++ test/Dialect/RTGTest/IR/basic.mlir | 8 +- 10 files changed, 261 insertions(+), 13 deletions(-) diff --git a/include/circt/Dialect/RTG/IR/RTGInterfaces.td b/include/circt/Dialect/RTG/IR/RTGInterfaces.td index 5379ce9f702f..32ed7739c2a2 100644 --- a/include/circt/Dialect/RTG/IR/RTGInterfaces.td +++ b/include/circt/Dialect/RTG/IR/RTGInterfaces.td @@ -10,6 +10,7 @@ #define CIRCT_DIALECT_RTG_RTGINTERFACES_TD include "mlir/IR/Interfaces.td" +include "mlir/IR/OpBase.td" def ContextResourceOpInterface : OpInterface<"ContextResourceOpInterface"> { let description = [{ @@ -30,6 +31,12 @@ def ContextResourceOpInterface : OpInterface<"ContextResourceOpInterface"> { ]; } +/// Context resources can only be defined insid the `rtg.target` operation. +def ContextResourceDefining : TraitList<[ + DeclareOpInterfaceMethods, + HasParent<"::circt::rtg::TargetOp">, +]>; + def ContextResourceTypeInterface : TypeInterface< "ContextResourceTypeInterface"> { let description = [{ diff --git a/include/circt/Dialect/RTG/IR/RTGOps.td b/include/circt/Dialect/RTG/IR/RTGOps.td index 4c79e50786e3..95985be015cb 100644 --- a/include/circt/Dialect/RTG/IR/RTGOps.td +++ b/include/circt/Dialect/RTG/IR/RTGOps.td @@ -140,3 +140,83 @@ def SetDifferenceOp : RTGOp<"set_difference", [ $original `,` $diff `:` qualified(type($output)) attr-dict }]; } + +//===- Test Specification Operations --------------------------------------===// + +def TestOp : RTGOp<"test", [ + IsolatedFromAbove, + Symbol, + SingleBlock, + NoTerminator, + HasParent<"mlir::ModuleOp"> +]> { + let summary = "the root of a test"; + let description = [{ + This operation declares the root of a randomized or directed test. + The target attribute specifies requirements of this test. These can be + refined by `rtg.require` operations inside this operation's body. A test + can only be matched with a target if the target fulfills all the test's + requirements. However, the target may provide more than the test requires. + For example, if the target allows execution in a user and privileged mode, + but the test only requires and runs in user mode, it can still be matched + 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. + + The body of this operation shall be processed the same way as an + `rtg.sequence`'s body with the exception of the block arguments. + The arguments must match the fields of the dict type in the target attribute + exactly. The test must not have any additional arguments and cannot be + referenced by an `rtg.sequence_closure` operation. + }]; + + let arguments = (ins SymbolNameAttr:$sym_name, + TypeAttrOf:$target); + let regions = (region SizedRegion<1>:$bodyRegion); + + let assemblyFormat = [{ + $sym_name `:` $target attr-dict-with-keyword $bodyRegion + }]; + + let hasRegionVerifier = 1; +} + +def TargetOp : RTGOp<"target", [ + IsolatedFromAbove, + Symbol, + NoRegionArguments, + SingleBlockImplicitTerminator<"rtg::YieldOp">, + HasParent<"mlir::ModuleOp"> +]> { + let summary = "defines a test target"; + let description = [{ + This operation specifies capabilities of a specific test target and can + provide additional information about it. These are added as operands to the + `yield` terminator and implicitly packed up into an `!rtg.dict` type which + is passed to tests that are matched with this target. + + These capabilities can, for example, consist of the number of CPUs, supported + priviledge modes, available memories, etc. + }]; + + let arguments = (ins SymbolNameAttr:$sym_name, + TypeAttrOf:$target); + let regions = (region SizedRegion<1>:$bodyRegion); + + let assemblyFormat = [{ + $sym_name `:` $target attr-dict-with-keyword $bodyRegion + }]; + + let hasRegionVerifier = 1; +} + +def YieldOp : RTGOp<"yield", [Pure, Terminator]> { + let summary = "terminates RTG operation regions"; + + let arguments = (ins Variadic:$operands); + let assemblyFormat = "($operands^ `:` type($operands))? attr-dict"; + + let builders = [OpBuilder<(ins), [{ /* nothing to do */ }]>]; +} diff --git a/include/circt/Dialect/RTG/IR/RTGTypes.td b/include/circt/Dialect/RTG/IR/RTGTypes.td index a76c1dd1fcf8..b5afba75318b 100644 --- a/include/circt/Dialect/RTG/IR/RTGTypes.td +++ b/include/circt/Dialect/RTG/IR/RTGTypes.td @@ -47,4 +47,24 @@ class SetTypeOf : ContainerType< elementType, SetType.predicate, "llvm::cast($_self).getElementType()", "set">; +def DictType : RTGTypeDef<"Dict"> { + let summary = "a dictionary"; + let description = [{ + This type is a dictionary with a static set of entries. This datatype does + not make any assumptions about how the values are stored (could be a struct, + a map, etc.). Furthermore, two values of this type should be considered + equivalent if they have the same set of entry names and types and the values + match for each entry, independent of the order. + }]; + + let parameters = (ins + ArrayRefParameter<"mlir::StringAttr", "dict entry names">:$entryNames, + ArrayRefParameter<"mlir::Type", "dict entry types">:$entryTypes); + + let mnemonic = "dict"; + + let hasCustomAssemblyFormat = 1; + let genVerifyDecl = 1; +} + #endif // CIRCT_DIALECT_RTG_IR_RTGTYPES_TD diff --git a/include/circt/Dialect/RTG/IR/RTGVisitors.h b/include/circt/Dialect/RTG/IR/RTGVisitors.h index 580f3ab55c00..de11a8ee65e3 100644 --- a/include/circt/Dialect/RTG/IR/RTGVisitors.h +++ b/include/circt/Dialect/RTG/IR/RTGVisitors.h @@ -31,10 +31,10 @@ class RTGOpVisitor { auto *thisCast = static_cast(this); return TypeSwitch(op) .template Case( - [&](auto expr) -> ResultType { - return thisCast->visitOp(expr, args...); - }) + SetSelectRandomOp, SetDifferenceOp, InvokeSequenceOp, + TestOp, TargetOp, YieldOp>([&](auto expr) -> ResultType { + return thisCast->visitOp(expr, args...); + }) .template Case( [&](auto expr) -> ResultType { return thisCast->visitContextResourceOp(expr, args...); @@ -79,6 +79,9 @@ class RTGOpVisitor { HANDLE(SetCreateOp, Unhandled); HANDLE(SetSelectRandomOp, Unhandled); HANDLE(SetDifferenceOp, Unhandled); + HANDLE(TestOp, Unhandled); + HANDLE(TargetOp, Unhandled); + HANDLE(YieldOp, Unhandled); #undef HANDLE }; @@ -90,9 +93,10 @@ class RTGTypeVisitor { ResultType dispatchTypeVisitor(Type type, ExtraArgs... args) { auto *thisCast = static_cast(this); return TypeSwitch(type) - .template Case([&](auto expr) -> ResultType { - return thisCast->visitType(expr, args...); - }) + .template Case( + [&](auto expr) -> ResultType { + return thisCast->visitType(expr, args...); + }) .template Case( [&](auto expr) -> ResultType { return thisCast->visitContextResourceType(expr, args...); @@ -134,6 +138,7 @@ class RTGTypeVisitor { HANDLE(SequenceType, Unhandled); HANDLE(SetType, Unhandled); + HANDLE(DictType, Unhandled); #undef HANDLE }; diff --git a/include/circt/Dialect/RTGTest/IR/RTGTestOps.td b/include/circt/Dialect/RTGTest/IR/RTGTestOps.td index 401f3d79beae..e64f6c8ac21e 100644 --- a/include/circt/Dialect/RTGTest/IR/RTGTestOps.td +++ b/include/circt/Dialect/RTGTest/IR/RTGTestOps.td @@ -21,7 +21,7 @@ class RTGTestOp traits = []> : def CPUDeclOp : RTGTestOp<"cpu_decl", [ Pure, ConstantLike, - DeclareOpInterfaceMethods, + ContextResourceDefining, ]> { let summary = "declare a CPU"; let description = [{ diff --git a/lib/Dialect/RTG/IR/RTGOps.cpp b/lib/Dialect/RTG/IR/RTGOps.cpp index 6aebf52844f9..3df1bed07e7f 100644 --- a/lib/Dialect/RTG/IR/RTGOps.cpp +++ b/lib/Dialect/RTG/IR/RTGOps.cpp @@ -78,6 +78,29 @@ LogicalResult SetCreateOp::verify() { return success(); } +//===----------------------------------------------------------------------===// +// TestOp +//===----------------------------------------------------------------------===// + +LogicalResult TestOp::verifyRegions() { + if (getBody()->getArgumentTypes() != getTarget().getEntryTypes()) + return emitOpError("argument types must match dict entry types"); + + return success(); +} + +//===----------------------------------------------------------------------===// +// TargetOp +//===----------------------------------------------------------------------===// + +LogicalResult TargetOp::verifyRegions() { + if (getBody()->getTerminator()->getOperandTypes() != + getTarget().getEntryTypes()) + return emitOpError("terminator operand types must match dict entry types"); + + return success(); +} + //===----------------------------------------------------------------------===// // TableGen generated logic. //===----------------------------------------------------------------------===// diff --git a/lib/Dialect/RTG/IR/RTGTypes.cpp b/lib/Dialect/RTG/IR/RTGTypes.cpp index 676f188a49fd..cf847db40b98 100644 --- a/lib/Dialect/RTG/IR/RTGTypes.cpp +++ b/lib/Dialect/RTG/IR/RTGTypes.cpp @@ -19,6 +19,77 @@ using namespace rtg; #define GET_TYPEDEF_CLASSES #include "circt/Dialect/RTG/IR/RTGTypes.cpp.inc" +//===----------------------------------------------------------------------===// +// DictType +//===----------------------------------------------------------------------===/ + +LogicalResult DictType::verify(function_ref emitError, + ArrayRef entryNames, + ArrayRef entryTypes) { + if (entryNames.size() != entryTypes.size()) + return emitError() << "must have the same number of names and types"; + + llvm::SmallDenseSet entryNameSet; + LogicalResult result = success(); + entryNameSet.reserve(entryNames.size()); + for (auto name : entryNames) { + if (!entryNameSet.insert(name).second) { + result = failure(); + emitError() << "duplicate entry name '" << name.getValue() + << "' in rtg.dict type"; + } + } + return result; +} + +Type DictType::parse(AsmParser &p) { + llvm::StringSet<> nameSet; + SmallVector types; + SmallVector names; + bool hasDuplicateName = false; + + auto parseResult = p.parseCommaSeparatedList( + mlir::AsmParser::Delimiter::LessGreater, [&]() -> ParseResult { + std::string name; + Type type; + + auto fieldLoc = p.getCurrentLocation(); + if (p.parseKeywordOrString(&name) || p.parseColon() || + p.parseType(type)) + return failure(); + + if (!nameSet.insert(name).second) { + p.emitError(fieldLoc, "duplicate entry name \'" + name + "\'"); + // Continue parsing to print all duplicates, but make sure to error + // eventually + hasDuplicateName = true; + } + + types.push_back(type); + names.push_back(StringAttr::get(p.getContext(), name)); + return success(); + }); + + if (hasDuplicateName) + return Type(); + + if (failed(parseResult)) + return Type(); + + return get(p.getContext(), names, types); +} + +void DictType::print(AsmPrinter &p) const { + p << '<'; + llvm::interleaveComma(llvm::zip(getEntryNames(), getEntryTypes()), p, + [&](auto entry) { + auto [name, type] = entry; + p.printKeywordOrString(name.getValue()); + p << ": " << type; + }); + p << ">"; +} + void circt::rtg::RTGDialect::registerTypes() { addTypes< #define GET_TYPEDEF_LIST diff --git a/test/Dialect/RTG/IR/basic.mlir b/test/Dialect/RTG/IR/basic.mlir index eae6e394a66f..f0070e6726d7 100644 --- a/test/Dialect/RTG/IR/basic.mlir +++ b/test/Dialect/RTG/IR/basic.mlir @@ -38,3 +38,27 @@ func.func @sets(%arg0: i32, %arg1: i32) { return } + +// CHECK-LABEL: rtg.target @empty_target : !rtg.dict<> { +// CHECK-NOT: rtg.yield +rtg.target @empty_target : !rtg.dict<> { + rtg.yield +} + +// CHECK-LABEL: rtg.test @empty_test : !rtg.dict<> { +rtg.test @empty_test : !rtg.dict<> { } + +// CHECK-LABEL: rtg.target @target : !rtg.dict { +// CHECK: rtg.yield %{{.*}}, %{{.*}} : i32, i32 +// CHECK: } +rtg.target @target : !rtg.dict { + %1 = arith.constant 4 : i32 + rtg.yield %1, %1 : i32, i32 +} + +// CHECK-LABEL: rtg.test @test : !rtg.dict { +// CHECK: ^bb0(%arg0: i32, %arg1: i32): +// CHECK: } +rtg.test @test : !rtg.dict { +^bb0(%arg0: i32, %arg1: i32): +} diff --git a/test/Dialect/RTG/IR/errors.mlir b/test/Dialect/RTG/IR/errors.mlir index 66a4c818cff9..f4475e7820ea 100644 --- a/test/Dialect/RTG/IR/errors.mlir +++ b/test/Dialect/RTG/IR/errors.mlir @@ -16,3 +16,23 @@ rtg.sequence @seq0 { // expected-error @below {{referenced 'rtg.sequence' op's argument types must match 'args' types}} rtg.sequence_closure @seq0 + +// ----- + +// expected-error @below {{terminator operand types must match dict entry types}} +rtg.target @target : !rtg.dict { + rtg.yield +} + +// ----- + +// expected-error @below {{argument types must match dict entry types}} +rtg.test @test : !rtg.dict { +} + +// ----- + +// expected-error @below {{duplicate entry name 'a}} +rtg.test @test : !rtg.dict { +^bb0(%arg0: i32, %arg1: i32): +} diff --git a/test/Dialect/RTGTest/IR/basic.mlir b/test/Dialect/RTGTest/IR/basic.mlir index 897bd52dc669..d46aeac3e08c 100644 --- a/test/Dialect/RTGTest/IR/basic.mlir +++ b/test/Dialect/RTGTest/IR/basic.mlir @@ -1,11 +1,9 @@ // RUN: circt-opt %s | FileCheck %s -// TODO: replace this with `rtg.target` because ops implementing -// ContextResourceOpInterface are only allowed in such target operations. // CHECK-LABEL: @cpus -rtg.sequence @cpus { -// CHECK: !rtgtest.cpu -^bb0(%arg0: !rtgtest.cpu): +// CHECK-SAME: !rtgtest.cpu +rtg.target @cpus : !rtg.dict { // CHECK: %0 = rtgtest.cpu_decl 0 %0 = rtgtest.cpu_decl 0 + rtg.yield %0 : !rtgtest.cpu }