[Handshake] Allow handshake ops to be used outside of a `handshake.func` (#6132)

... by requiring that handshake ops are nested within an operation that inherits the `FineGrainedDataflowRegionOpInterface`.
This is somewhat of a half-way solution, seeing as there isn't support for `HasParent` with an interface upstream. I've raised the issue and suggested a fix here https://github.com/llvm/llvm-project/pull/66196 but we'll see how long that takes to resolve.

Until then, added a `HasParentInterface` which does the same thing, albeit with a cryptic error message about which interface the parent op lacked (note: the whole issue here is that there isn't any name literal being generated for op interfaces).

I'll be monitoring the upstream stuff, and changing this over until then. For now, the motivation for adding this into circt is to unblock me in using handshake outside of a `handshake.func` while still having a restriction on where handshake ops can be used - i.e. i don't want to completely lift the `HasParent` restriction - users should still explicitly opt-into the fact that "using handshake => handshake ops is in a fine-grained dataflow region".
This commit is contained in:
Morten Borup Petersen 2023-09-18 10:42:02 +02:00 committed by GitHub
parent 5f5ea8fc41
commit 83abbfc0f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 52 additions and 9 deletions

View File

@ -39,14 +39,6 @@ def Handshake_Dialect : Dialect {
let usePropertiesForAttributes = 0;
}
// Base class for Handshake dialect ops.
class Handshake_Op<string mnemonic, list<Trait> traits = []>
: Op<Handshake_Dialect, mnemonic,
traits #[HasParent<"handshake::FuncOp">,
DeclareOpInterfaceMethods<NamedIOInterface>,
DeclareOpInterfaceMethods<ControlInterface>]> {
}
include "circt/Dialect/Handshake/HandshakeOps.td"
#endif // HANDSHAKE_TD

View File

@ -15,6 +15,15 @@
include "mlir/IR/OpBase.td"
def FineGrainedDataflowRegionOpInterface : OpInterface<"FineGrainedDataflowRegionOpInterface"> {
let description = [{
An interface for describing operations that define fine-grained dataflow regions.
The interface doesn't provide any methods, but is instead used to ensure
that users of handshake ops explicitly acknowledge that a given region has
fine-grained dataflow semantics.
}];
}
def SOSTInterface : OpInterface<"SOSTInterface"> {
let description = [{
Sized Operation with Single Type (SOST).

View File

@ -67,6 +67,27 @@ namespace mlir {
namespace OpTrait {
template <typename ConcreteType>
class HasClock : public TraitBase<ConcreteType, HasClock> {};
template <typename InterfaceType>
class HasParentInterface {
public:
template <typename ConcreteType>
class Impl : public TraitBase<ConcreteType,
HasParentInterface<InterfaceType>::Impl> {
public:
static LogicalResult verifyTrait(Operation *op) {
if (llvm::isa_and_nonnull<InterfaceType>(op->getParentOp()))
return success();
// @mortbopet: What a horrible error message - however, there's no way to
// report the interface name without going in and adjusting the tablegen
// backend to also emit string literal names for interfaces.
return op->emitOpError() << "expects parent op to be of the interface "
"parent type required by the given op type";
}
};
};
} // namespace OpTrait
} // namespace mlir

View File

@ -16,6 +16,19 @@ include "mlir/IR/BuiltinTypes.td"
include "mlir/IR/BuiltinAttributeInterfaces.td"
include "mlir/Interfaces/InferTypeOpInterface.td"
// @mortbopet: some kind of support for interfaces as parent ops is currently
// being tracked here: https://github.com/llvm/llvm-project/pull/66196
class HasParentInterface<string interface>
: ParamNativeOpTrait<"HasParentInterface", interface>, StructuralOpTrait {}
// Base class for Handshake dialect ops.
class Handshake_Op<string mnemonic, list<Trait> traits = []>
: Op<Handshake_Dialect, mnemonic,
traits #[HasParentInterface<"circt::handshake::FineGrainedDataflowRegionOpInterface">,
DeclareOpInterfaceMethods<NamedIOInterface>,
DeclareOpInterfaceMethods<ControlInterface>]> {
}
// This is almost exactly like a standard FuncOp, except that it has some
// extra verification conditions. In particular, each Value must
// only have a single use. Also, it defines a Dominance-Free Scope
@ -25,7 +38,8 @@ def FuncOp : Op<Handshake_Dialect, "func", [
Symbol,
RegionKindInterface,
OpAsmOpInterface,
HasClock
HasClock,
FineGrainedDataflowRegionOpInterface
]> {
let summary = "Handshake dialect function.";
let description = [{

View File

@ -259,3 +259,10 @@ handshake.func @invalid_sost_op_wrong_operands(%arg0 : i64, %arg1 : i32, %ctrl :
return %0, %ctrl : i64, none
}
// -----
func.func @handshake_op_inside_non_finegrained_dataflow_region(%arg0 : none) -> (none) {
// expected-error @+1{{op expects parent op to be of the interface parent type required by the given op type}}
%0 = handshake.join %arg0 : none
return %0 : none
}