Skip to content

Commit

Permalink
[RTG] Add TestOp, TargetOp, and DictType
Browse files Browse the repository at this point in the history
  • Loading branch information
maerhart committed Nov 26, 2024
1 parent 62ea468 commit 506937f
Show file tree
Hide file tree
Showing 11 changed files with 303 additions and 13 deletions.
7 changes: 7 additions & 0 deletions include/circt/Dialect/RTG/IR/RTGInterfaces.td
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [{
Expand All @@ -30,6 +31,12 @@ def ContextResourceOpInterface : OpInterface<"ContextResourceOpInterface"> {
];
}

/// Context resources can only be defined inside the `rtg.target` operation.
def ContextResourceDefining : TraitList<[
DeclareOpInterfaceMethods<ContextResourceOpInterface>,
HasParent<"::circt::rtg::TargetOp">,
]>;

def ContextResourceTypeInterface : TypeInterface<
"ContextResourceTypeInterface"> {
let description = [{
Expand Down
80 changes: 80 additions & 0 deletions include/circt/Dialect/RTG/IR/RTGOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -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<DictType>:$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<DictType>:$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<AnyType>:$operands);
let assemblyFormat = "($operands^ `:` type($operands))? attr-dict";

let builders = [OpBuilder<(ins), [{ /* nothing to do */ }]>];
}
24 changes: 24 additions & 0 deletions include/circt/Dialect/RTG/IR/RTGTypes.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,30 @@
#include "mlir/IR/BuiltinTypes.h"
#include "mlir/IR/Types.h"

namespace circt {
namespace rtg {

/// Defines an entry in an `!rtg.dict`.
struct DictEntry {
mlir::StringAttr name;
mlir::Type type;
};

inline bool operator<(const DictEntry &entry, const DictEntry &other) {
return entry.name.getValue() < other.name.getValue();
}

inline bool operator==(const DictEntry &entry, const DictEntry &other) {
return entry.name == other.name && entry.type == other.type;
}

inline llvm::hash_code hash_value(const DictEntry &entry) {
return llvm::hash_combine(entry.name, entry.type);
}

} // namespace rtg
} // namespace circt

#define GET_TYPEDEF_CLASSES
#include "circt/Dialect/RTG/IR/RTGTypes.h.inc"

Expand Down
25 changes: 25 additions & 0 deletions include/circt/Dialect/RTG/IR/RTGTypes.td
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,29 @@ class SetTypeOf<Type elementType> : ContainerType<
elementType, SetType.predicate,
"llvm::cast<rtg::SetType>($_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<"::circt::rtg::DictEntry", "dict entries">:$entries);

let extraClassDeclaration = [{
/// Checks if the types of the dictionary entries match the ones in the
/// given type range. The dictionary entries are sorted by ascending names.
bool entryTypesMatch(mlir::TypeRange types) const;
}];

let mnemonic = "dict";

let hasCustomAssemblyFormat = 1;
let genVerifyDecl = 1;
}

#endif // CIRCT_DIALECT_RTG_IR_RTGTYPES_TD
19 changes: 12 additions & 7 deletions include/circt/Dialect/RTG/IR/RTGVisitors.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ class RTGOpVisitor {
auto *thisCast = static_cast<ConcreteType *>(this);
return TypeSwitch<Operation *, ResultType>(op)
.template Case<SequenceOp, SequenceClosureOp, SetCreateOp,
SetSelectRandomOp, SetDifferenceOp, InvokeSequenceOp>(
[&](auto expr) -> ResultType {
return thisCast->visitOp(expr, args...);
})
SetSelectRandomOp, SetDifferenceOp, InvokeSequenceOp,
TestOp, TargetOp, YieldOp>([&](auto expr) -> ResultType {
return thisCast->visitOp(expr, args...);
})
.template Case<ContextResourceOpInterface>(
[&](auto expr) -> ResultType {
return thisCast->visitContextResourceOp(expr, args...);
Expand Down Expand Up @@ -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
};

Expand All @@ -90,9 +93,10 @@ class RTGTypeVisitor {
ResultType dispatchTypeVisitor(Type type, ExtraArgs... args) {
auto *thisCast = static_cast<ConcreteType *>(this);
return TypeSwitch<Type, ResultType>(type)
.template Case<SequenceType, SetType>([&](auto expr) -> ResultType {
return thisCast->visitType(expr, args...);
})
.template Case<SequenceType, SetType, DictType>(
[&](auto expr) -> ResultType {
return thisCast->visitType(expr, args...);
})
.template Case<ContextResourceTypeInterface>(
[&](auto expr) -> ResultType {
return thisCast->visitContextResourceType(expr, args...);
Expand Down Expand Up @@ -134,6 +138,7 @@ class RTGTypeVisitor {

HANDLE(SequenceType, Unhandled);
HANDLE(SetType, Unhandled);
HANDLE(DictType, Unhandled);
#undef HANDLE
};

Expand Down
2 changes: 1 addition & 1 deletion include/circt/Dialect/RTGTest/IR/RTGTestOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class RTGTestOp<string mnemonic, list<Trait> traits = []> :
def CPUDeclOp : RTGTestOp<"cpu_decl", [
Pure,
ConstantLike,
DeclareOpInterfaceMethods<ContextResourceOpInterface>,
ContextResourceDefining,
]> {
let summary = "declare a CPU";
let description = [{
Expand Down
23 changes: 23 additions & 0 deletions lib/Dialect/RTG/IR/RTGOps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,29 @@ LogicalResult SetCreateOp::verify() {
return success();
}

//===----------------------------------------------------------------------===//
// TestOp
//===----------------------------------------------------------------------===//

LogicalResult TestOp::verifyRegions() {
if (!getTarget().entryTypesMatch(getBody()->getArgumentTypes()))
return emitOpError("argument types must match dict entry types");

return success();
}

//===----------------------------------------------------------------------===//
// TargetOp
//===----------------------------------------------------------------------===//

LogicalResult TargetOp::verifyRegions() {
if (!getTarget().entryTypesMatch(
getBody()->getTerminator()->getOperandTypes()))
return emitOpError("terminator operand types must match dict entry types");

return success();
}

//===----------------------------------------------------------------------===//
// TableGen generated logic.
//===----------------------------------------------------------------------===//
Expand Down
70 changes: 70 additions & 0 deletions lib/Dialect/RTG/IR/RTGTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,76 @@ using namespace rtg;
#define GET_TYPEDEF_CLASSES
#include "circt/Dialect/RTG/IR/RTGTypes.cpp.inc"

//===----------------------------------------------------------------------===//
// DictType
//===----------------------------------------------------------------------===/

LogicalResult DictType::verify(function_ref<InFlightDiagnostic()> emitError,
ArrayRef<DictEntry> entries) {
StringAttr last;
for (auto entry : entries) {
if (entry.name.empty())
return emitError() << "empty strings not allowed as entry names";

if (last && entry.name.getValue() <= last.getValue())
return emitError() << "dictionary must be sorted by names and contain no "
"duplicates, first violation at entry '"
<< entry.name.getValue() << "'";

last = entry.name;
}

return success();
}

Type DictType::parse(AsmParser &p) {
SmallVector<DictEntry> entries;
auto loc = p.getCurrentLocation();

auto parseResult = p.parseCommaSeparatedList(
mlir::AsmParser::Delimiter::LessGreater, [&]() -> ParseResult {
std::string name;
Type type;
loc = p.getCurrentLocation();

if (p.parseKeywordOrString(&name) || p.parseColon() ||
p.parseType(type))
return failure();

DictEntry entry;
entry.name = StringAttr::get(p.getContext(), name);
entry.type = type;
entries.emplace_back(entry);
return success();
});

if (failed(parseResult))
return Type();

auto emitError = [&]() { return p.emitError(loc); };

// Call 'getChecked' here such that we do not have to repeat the verification
// checks in the parser here, but still get the errors reported at meaningful
// locations.
return getChecked(emitError, p.getContext(), entries);
}

void DictType::print(AsmPrinter &p) const {
p << '<';
llvm::interleaveComma(getEntries(), p, [&](auto entry) {
p.printKeywordOrString(entry.name.getValue());
p << ": " << entry.type;
});
p << ">";
}

bool DictType::entryTypesMatch(TypeRange types) const {
return llvm::equal(getEntries(), types,
[](const DictEntry &entry, const Type &type) {
return entry.type == type;
});
}

void circt::rtg::RTGDialect::registerTypes() {
addTypes<
#define GET_TYPEDEF_LIST
Expand Down
24 changes: 24 additions & 0 deletions test/Dialect/RTG/IR/basic.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -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<num_cpus: i32, num_modes: i32> {
// CHECK: rtg.yield %{{.*}}, %{{.*}} : i32, i32
// CHECK: }
rtg.target @target : !rtg.dict<num_cpus: i32, num_modes: i32> {
%1 = arith.constant 4 : i32
rtg.yield %1, %1 : i32, i32
}

// CHECK-LABEL: rtg.test @test : !rtg.dict<num_cpus: i32, num_modes: i32> {
// CHECK: ^bb0(%arg0: i32, %arg1: i32):
// CHECK: }
rtg.test @test : !rtg.dict<num_cpus: i32, num_modes: i32> {
^bb0(%arg0: i32, %arg1: i32):
}
34 changes: 34 additions & 0 deletions test/Dialect/RTG/IR/errors.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,37 @@ 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<a: i32> {
rtg.yield
}

// -----

// expected-error @below {{argument types must match dict entry types}}
rtg.test @test : !rtg.dict<a: i32> {
}

// -----

// expected-error @below {{dictionary must be sorted by names and contain no duplicates, first violation at entry 'a'}}
rtg.test @test : !rtg.dict<a: i32, a: i32> {
^bb0(%arg0: i32, %arg1: i32):
}

// -----

// expected-error @below {{dictionary must be sorted by names and contain no duplicates, first violation at entry 'a'}}
rtg.test @test : !rtg.dict<b: i32, a: i32> {
^bb0(%arg0: i32, %arg1: i32):
}

// -----

// expected-error @below {{empty strings not allowed as entry names}}
rtg.test @test : !rtg.dict<"": i32> {
^bb0(%arg0: i32):
}
Loading

0 comments on commit 506937f

Please sign in to comment.