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 20, 2024
1 parent 5c2c6d4 commit 65f4117
Show file tree
Hide file tree
Showing 10 changed files with 261 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 insid 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 */ }]>];
}
20 changes: 20 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,24 @@ 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<"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
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 (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.
//===----------------------------------------------------------------------===//
Expand Down
71 changes: 71 additions & 0 deletions lib/Dialect/RTG/IR/RTGTypes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<InFlightDiagnostic()> emitError,
ArrayRef<StringAttr> entryNames,
ArrayRef<Type> entryTypes) {
if (entryNames.size() != entryTypes.size())
return emitError() << "must have the same number of names and types";

llvm::SmallDenseSet<StringAttr> 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<Type> types;
SmallVector<StringAttr> 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
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):
}
20 changes: 20 additions & 0 deletions test/Dialect/RTG/IR/errors.mlir
Original file line number Diff line number Diff line change
Expand Up @@ -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<a: i32> {
rtg.yield
}

// -----

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

// -----

// expected-error @below {{duplicate entry name 'a}}
rtg.test @test : !rtg.dict<a: i32, a: i32> {
^bb0(%arg0: i32, %arg1: i32):
}
8 changes: 3 additions & 5 deletions test/Dialect/RTGTest/IR/basic.mlir
Original file line number Diff line number Diff line change
@@ -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<cpu: !rtgtest.cpu> {
// CHECK: %0 = rtgtest.cpu_decl 0
%0 = rtgtest.cpu_decl 0
rtg.yield %0 : !rtgtest.cpu
}

0 comments on commit 65f4117

Please sign in to comment.