Skip to content

Commit 93d0536

Browse files
authored
[RTG] Add TestOp, TargetOp, and DictType (#7856)
1 parent 7b70ec4 commit 93d0536

File tree

11 files changed

+303
-13
lines changed

11 files changed

+303
-13
lines changed

include/circt/Dialect/RTG/IR/RTGInterfaces.td

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#define CIRCT_DIALECT_RTG_IR_RTGINTERFACES_TD
1111

1212
include "mlir/IR/Interfaces.td"
13+
include "mlir/IR/OpBase.td"
1314

1415
def ContextResourceOpInterface : OpInterface<"ContextResourceOpInterface"> {
1516
let description = [{
@@ -30,6 +31,12 @@ def ContextResourceOpInterface : OpInterface<"ContextResourceOpInterface"> {
3031
];
3132
}
3233

34+
/// Context resources can only be defined inside the `rtg.target` operation.
35+
def ContextResourceDefining : TraitList<[
36+
DeclareOpInterfaceMethods<ContextResourceOpInterface>,
37+
HasParent<"::circt::rtg::TargetOp">,
38+
]>;
39+
3340
def ContextResourceTypeInterface : TypeInterface<
3441
"ContextResourceTypeInterface"> {
3542
let description = [{

include/circt/Dialect/RTG/IR/RTGOps.td

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,3 +140,83 @@ def SetDifferenceOp : RTGOp<"set_difference", [
140140
$original `,` $diff `:` qualified(type($output)) attr-dict
141141
}];
142142
}
143+
144+
//===- Test Specification Operations --------------------------------------===//
145+
146+
def TestOp : RTGOp<"test", [
147+
IsolatedFromAbove,
148+
Symbol,
149+
SingleBlock,
150+
NoTerminator,
151+
HasParent<"mlir::ModuleOp">
152+
]> {
153+
let summary = "the root of a test";
154+
let description = [{
155+
This operation declares the root of a randomized or directed test.
156+
The target attribute specifies requirements of this test. These can be
157+
refined by `rtg.require` operations inside this operation's body. A test
158+
can only be matched with a target if the target fulfills all the test's
159+
requirements. However, the target may provide more than the test requires.
160+
For example, if the target allows execution in a user and privileged mode,
161+
but the test only requires and runs in user mode, it can still be matched
162+
with that target.
163+
164+
By default each test can be matched with all targets that fulfill its
165+
requirements, but the user should be able to specify more constraints on the
166+
matching procedure.
167+
168+
The body of this operation shall be processed the same way as an
169+
`rtg.sequence`'s body with the exception of the block arguments.
170+
The arguments must match the fields of the dict type in the target attribute
171+
exactly. The test must not have any additional arguments and cannot be
172+
referenced by an `rtg.sequence_closure` operation.
173+
}];
174+
175+
let arguments = (ins SymbolNameAttr:$sym_name,
176+
TypeAttrOf<DictType>:$target);
177+
let regions = (region SizedRegion<1>:$bodyRegion);
178+
179+
let assemblyFormat = [{
180+
$sym_name `:` $target attr-dict-with-keyword $bodyRegion
181+
}];
182+
183+
let hasRegionVerifier = 1;
184+
}
185+
186+
def TargetOp : RTGOp<"target", [
187+
IsolatedFromAbove,
188+
Symbol,
189+
NoRegionArguments,
190+
SingleBlockImplicitTerminator<"rtg::YieldOp">,
191+
HasParent<"mlir::ModuleOp">
192+
]> {
193+
let summary = "defines a test target";
194+
let description = [{
195+
This operation specifies capabilities of a specific test target and can
196+
provide additional information about it. These are added as operands to the
197+
`yield` terminator and implicitly packed up into an `!rtg.dict` type which
198+
is passed to tests that are matched with this target.
199+
200+
These capabilities can, for example, consist of the number of CPUs, supported
201+
priviledge modes, available memories, etc.
202+
}];
203+
204+
let arguments = (ins SymbolNameAttr:$sym_name,
205+
TypeAttrOf<DictType>:$target);
206+
let regions = (region SizedRegion<1>:$bodyRegion);
207+
208+
let assemblyFormat = [{
209+
$sym_name `:` $target attr-dict-with-keyword $bodyRegion
210+
}];
211+
212+
let hasRegionVerifier = 1;
213+
}
214+
215+
def YieldOp : RTGOp<"yield", [Pure, Terminator]> {
216+
let summary = "terminates RTG operation regions";
217+
218+
let arguments = (ins Variadic<AnyType>:$operands);
219+
let assemblyFormat = "($operands^ `:` type($operands))? attr-dict";
220+
221+
let builders = [OpBuilder<(ins), [{ /* nothing to do */ }]>];
222+
}

include/circt/Dialect/RTG/IR/RTGTypes.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,30 @@
1313
#include "mlir/IR/BuiltinTypes.h"
1414
#include "mlir/IR/Types.h"
1515

16+
namespace circt {
17+
namespace rtg {
18+
19+
/// Defines an entry in an `!rtg.dict`.
20+
struct DictEntry {
21+
mlir::StringAttr name;
22+
mlir::Type type;
23+
};
24+
25+
inline bool operator<(const DictEntry &entry, const DictEntry &other) {
26+
return entry.name.getValue() < other.name.getValue();
27+
}
28+
29+
inline bool operator==(const DictEntry &entry, const DictEntry &other) {
30+
return entry.name == other.name && entry.type == other.type;
31+
}
32+
33+
inline llvm::hash_code hash_value(const DictEntry &entry) {
34+
return llvm::hash_combine(entry.name, entry.type);
35+
}
36+
37+
} // namespace rtg
38+
} // namespace circt
39+
1640
#define GET_TYPEDEF_CLASSES
1741
#include "circt/Dialect/RTG/IR/RTGTypes.h.inc"
1842

include/circt/Dialect/RTG/IR/RTGTypes.td

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,29 @@ class SetTypeOf<Type elementType> : ContainerType<
4747
elementType, SetType.predicate,
4848
"llvm::cast<rtg::SetType>($_self).getElementType()", "set">;
4949

50+
def DictType : RTGTypeDef<"Dict"> {
51+
let summary = "a dictionary";
52+
let description = [{
53+
This type is a dictionary with a static set of entries. This datatype does
54+
not make any assumptions about how the values are stored (could be a struct,
55+
a map, etc.). Furthermore, two values of this type should be considered
56+
equivalent if they have the same set of entry names and types and the values
57+
match for each entry, independent of the order.
58+
}];
59+
60+
let parameters = (ins
61+
ArrayRefParameter<"::circt::rtg::DictEntry", "dict entries">:$entries);
62+
63+
let extraClassDeclaration = [{
64+
/// Checks if the types of the dictionary entries match the ones in the
65+
/// given type range. The dictionary entries are sorted by ascending names.
66+
bool entryTypesMatch(mlir::TypeRange types) const;
67+
}];
68+
69+
let mnemonic = "dict";
70+
71+
let hasCustomAssemblyFormat = 1;
72+
let genVerifyDecl = 1;
73+
}
74+
5075
#endif // CIRCT_DIALECT_RTG_IR_RTGTYPES_TD

include/circt/Dialect/RTG/IR/RTGVisitors.h

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,10 @@ class RTGOpVisitor {
3131
auto *thisCast = static_cast<ConcreteType *>(this);
3232
return TypeSwitch<Operation *, ResultType>(op)
3333
.template Case<SequenceOp, SequenceClosureOp, SetCreateOp,
34-
SetSelectRandomOp, SetDifferenceOp, InvokeSequenceOp>(
35-
[&](auto expr) -> ResultType {
36-
return thisCast->visitOp(expr, args...);
37-
})
34+
SetSelectRandomOp, SetDifferenceOp, InvokeSequenceOp,
35+
TestOp, TargetOp, YieldOp>([&](auto expr) -> ResultType {
36+
return thisCast->visitOp(expr, args...);
37+
})
3838
.template Case<ContextResourceOpInterface>(
3939
[&](auto expr) -> ResultType {
4040
return thisCast->visitContextResourceOp(expr, args...);
@@ -79,6 +79,9 @@ class RTGOpVisitor {
7979
HANDLE(SetCreateOp, Unhandled);
8080
HANDLE(SetSelectRandomOp, Unhandled);
8181
HANDLE(SetDifferenceOp, Unhandled);
82+
HANDLE(TestOp, Unhandled);
83+
HANDLE(TargetOp, Unhandled);
84+
HANDLE(YieldOp, Unhandled);
8285
#undef HANDLE
8386
};
8487

@@ -90,9 +93,10 @@ class RTGTypeVisitor {
9093
ResultType dispatchTypeVisitor(Type type, ExtraArgs... args) {
9194
auto *thisCast = static_cast<ConcreteType *>(this);
9295
return TypeSwitch<Type, ResultType>(type)
93-
.template Case<SequenceType, SetType>([&](auto expr) -> ResultType {
94-
return thisCast->visitType(expr, args...);
95-
})
96+
.template Case<SequenceType, SetType, DictType>(
97+
[&](auto expr) -> ResultType {
98+
return thisCast->visitType(expr, args...);
99+
})
96100
.template Case<ContextResourceTypeInterface>(
97101
[&](auto expr) -> ResultType {
98102
return thisCast->visitContextResourceType(expr, args...);
@@ -134,6 +138,7 @@ class RTGTypeVisitor {
134138

135139
HANDLE(SequenceType, Unhandled);
136140
HANDLE(SetType, Unhandled);
141+
HANDLE(DictType, Unhandled);
137142
#undef HANDLE
138143
};
139144

include/circt/Dialect/RTGTest/IR/RTGTestOps.td

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class RTGTestOp<string mnemonic, list<Trait> traits = []> :
2121
def CPUDeclOp : RTGTestOp<"cpu_decl", [
2222
Pure,
2323
ConstantLike,
24-
DeclareOpInterfaceMethods<ContextResourceOpInterface>,
24+
ContextResourceDefining,
2525
]> {
2626
let summary = "declare a CPU";
2727
let description = [{

lib/Dialect/RTG/IR/RTGOps.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,29 @@ LogicalResult SetCreateOp::verify() {
7878
return success();
7979
}
8080

81+
//===----------------------------------------------------------------------===//
82+
// TestOp
83+
//===----------------------------------------------------------------------===//
84+
85+
LogicalResult TestOp::verifyRegions() {
86+
if (!getTarget().entryTypesMatch(getBody()->getArgumentTypes()))
87+
return emitOpError("argument types must match dict entry types");
88+
89+
return success();
90+
}
91+
92+
//===----------------------------------------------------------------------===//
93+
// TargetOp
94+
//===----------------------------------------------------------------------===//
95+
96+
LogicalResult TargetOp::verifyRegions() {
97+
if (!getTarget().entryTypesMatch(
98+
getBody()->getTerminator()->getOperandTypes()))
99+
return emitOpError("terminator operand types must match dict entry types");
100+
101+
return success();
102+
}
103+
81104
//===----------------------------------------------------------------------===//
82105
// TableGen generated logic.
83106
//===----------------------------------------------------------------------===//

lib/Dialect/RTG/IR/RTGTypes.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,76 @@ using namespace rtg;
1919
#define GET_TYPEDEF_CLASSES
2020
#include "circt/Dialect/RTG/IR/RTGTypes.cpp.inc"
2121

22+
//===----------------------------------------------------------------------===//
23+
// DictType
24+
//===----------------------------------------------------------------------===/
25+
26+
LogicalResult DictType::verify(function_ref<InFlightDiagnostic()> emitError,
27+
ArrayRef<DictEntry> entries) {
28+
StringAttr last;
29+
for (auto entry : entries) {
30+
if (entry.name.empty())
31+
return emitError() << "empty strings not allowed as entry names";
32+
33+
if (last && entry.name.getValue() <= last.getValue())
34+
return emitError() << "dictionary must be sorted by names and contain no "
35+
"duplicates, first violation at entry '"
36+
<< entry.name.getValue() << "'";
37+
38+
last = entry.name;
39+
}
40+
41+
return success();
42+
}
43+
44+
Type DictType::parse(AsmParser &p) {
45+
SmallVector<DictEntry> entries;
46+
auto loc = p.getCurrentLocation();
47+
48+
auto parseResult = p.parseCommaSeparatedList(
49+
mlir::AsmParser::Delimiter::LessGreater, [&]() -> ParseResult {
50+
std::string name;
51+
Type type;
52+
loc = p.getCurrentLocation();
53+
54+
if (p.parseKeywordOrString(&name) || p.parseColon() ||
55+
p.parseType(type))
56+
return failure();
57+
58+
DictEntry entry;
59+
entry.name = StringAttr::get(p.getContext(), name);
60+
entry.type = type;
61+
entries.emplace_back(entry);
62+
return success();
63+
});
64+
65+
if (failed(parseResult))
66+
return Type();
67+
68+
auto emitError = [&]() { return p.emitError(loc); };
69+
70+
// Call 'getChecked' here such that we do not have to repeat the verification
71+
// checks in the parser here, but still get the errors reported at meaningful
72+
// locations.
73+
return getChecked(emitError, p.getContext(), entries);
74+
}
75+
76+
void DictType::print(AsmPrinter &p) const {
77+
p << '<';
78+
llvm::interleaveComma(getEntries(), p, [&](auto entry) {
79+
p.printKeywordOrString(entry.name.getValue());
80+
p << ": " << entry.type;
81+
});
82+
p << ">";
83+
}
84+
85+
bool DictType::entryTypesMatch(TypeRange types) const {
86+
return llvm::equal(getEntries(), types,
87+
[](const DictEntry &entry, const Type &type) {
88+
return entry.type == type;
89+
});
90+
}
91+
2292
void circt::rtg::RTGDialect::registerTypes() {
2393
addTypes<
2494
#define GET_TYPEDEF_LIST

test/Dialect/RTG/IR/basic.mlir

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,27 @@ func.func @sets(%arg0: i32, %arg1: i32) {
3838

3939
return
4040
}
41+
42+
// CHECK-LABEL: rtg.target @empty_target : !rtg.dict<> {
43+
// CHECK-NOT: rtg.yield
44+
rtg.target @empty_target : !rtg.dict<> {
45+
rtg.yield
46+
}
47+
48+
// CHECK-LABEL: rtg.test @empty_test : !rtg.dict<> {
49+
rtg.test @empty_test : !rtg.dict<> { }
50+
51+
// CHECK-LABEL: rtg.target @target : !rtg.dict<num_cpus: i32, num_modes: i32> {
52+
// CHECK: rtg.yield %{{.*}}, %{{.*}} : i32, i32
53+
// CHECK: }
54+
rtg.target @target : !rtg.dict<num_cpus: i32, num_modes: i32> {
55+
%1 = arith.constant 4 : i32
56+
rtg.yield %1, %1 : i32, i32
57+
}
58+
59+
// CHECK-LABEL: rtg.test @test : !rtg.dict<num_cpus: i32, num_modes: i32> {
60+
// CHECK: ^bb0(%arg0: i32, %arg1: i32):
61+
// CHECK: }
62+
rtg.test @test : !rtg.dict<num_cpus: i32, num_modes: i32> {
63+
^bb0(%arg0: i32, %arg1: i32):
64+
}

test/Dialect/RTG/IR/errors.mlir

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,37 @@ rtg.sequence @seq0 {
1616

1717
// expected-error @below {{referenced 'rtg.sequence' op's argument types must match 'args' types}}
1818
rtg.sequence_closure @seq0
19+
20+
// -----
21+
22+
// expected-error @below {{terminator operand types must match dict entry types}}
23+
rtg.target @target : !rtg.dict<a: i32> {
24+
rtg.yield
25+
}
26+
27+
// -----
28+
29+
// expected-error @below {{argument types must match dict entry types}}
30+
rtg.test @test : !rtg.dict<a: i32> {
31+
}
32+
33+
// -----
34+
35+
// expected-error @below {{dictionary must be sorted by names and contain no duplicates, first violation at entry 'a'}}
36+
rtg.test @test : !rtg.dict<a: i32, a: i32> {
37+
^bb0(%arg0: i32, %arg1: i32):
38+
}
39+
40+
// -----
41+
42+
// expected-error @below {{dictionary must be sorted by names and contain no duplicates, first violation at entry 'a'}}
43+
rtg.test @test : !rtg.dict<b: i32, a: i32> {
44+
^bb0(%arg0: i32, %arg1: i32):
45+
}
46+
47+
// -----
48+
49+
// expected-error @below {{empty strings not allowed as entry names}}
50+
rtg.test @test : !rtg.dict<"": i32> {
51+
^bb0(%arg0: i32):
52+
}

0 commit comments

Comments
 (0)