Skip to content

Commit

Permalink
[OM] Rework ClassOp to use fields terminator
Browse files Browse the repository at this point in the history
This changes the OM dialect Class/ExternClass to use a terminator
operation similar to hw.output.

The ClassFields and ClassExternFields operations use a custom
printer/parser to provide a syntax similar to hw.module where fields use
the format `@name %val : !type` for non-extern fields and `@name :
!type` for extern fields.

Internally, the fields operations use attributes to store required
information. Both fields operations store a list of fields to preserve
definition order.  Non-extern fields store a mapping from field name to
operand index.  Extern fields store a mapping from field name to type.

The fields operations provide an API for getting the type of a field
based on the name.  The extern fields looks this up in the attribute
dictionary, while the non-extern fields operation looks up the operand
index and retrieves the type from the appropriate operand.  This ensures
that non-extern fields map to the latest type in cases where the operand
type changes after construction.

The Class/ExternClass operations provide a convenience API that lifts
the fields API to avoid having to frequently request the underlying
fields operation.

The firrtl LowerClasses logic requires changes to use this API when
constructing OM classes, as well as in the dialect conversion logic to
handle the new operations.

The OM evaluator similarly is updated for the new fields API.

The OM linker is updated to skip constructing the field name to type map
and instead using the getFieldType API provided by the classOp.

The OM object field verifier also no longer needs to construct the field
name to type maps and instead can just use the new API.

Quite a few tests required updating for the new field syntax.

Two TODOs left:
* Is there a way to attach a note to a parser error? The old code used
  this when encountering duplicate fields (the note pointed to the
  previous definition).  Because this check is now done at parse time,
  we are emitting parser errors instead of an InFlightDiagnostic.
* Right now the getFieldType API uses a `std::optional` style for
  handling cases when the type of a field doesn't exist (i.e. the
  provided name is not field).  Another option would be to expose a
  `hasField` API.
  • Loading branch information
leonardt committed Aug 20, 2024
1 parent 898eb8b commit f37ea37
Show file tree
Hide file tree
Showing 24 changed files with 993 additions and 357 deletions.
1 change: 1 addition & 0 deletions include/circt/Dialect/OM/OMOpInterfaces.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#define CIRCT_DIALECT_OM_OMOPINTERFACES_H

#include "mlir/IR/OpDefinition.h"
#include "mlir/IR/OpImplementation.h"
#include "llvm/ADT/APSInt.h"

#include "circt/Dialect/OM/OMOpInterfaces.h.inc"
Expand Down
131 changes: 115 additions & 16 deletions include/circt/Dialect/OM/OMOpInterfaces.td
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,102 @@

include "mlir/IR/OpBase.td"

def ClassFieldsLike : OpInterface<"ClassFieldsLike"> {
let cppNamespace = "circt::om";

let description = [{
Marks the end of a class-like definition by providing a list of accessible
fields.
}];
let methods = [
InterfaceMethod<"Get the class-like field to type map",
"std::optional<mlir::Type>", "getFieldType", (ins "mlir::StringAttr":$name)>,
InterfaceMethod<"Print class fields like",
"void", "printClassFieldsLike", (ins "mlir::OpAsmPrinter &":$printer,
"llvm::SmallVector<mlir::Value>":$operands),
/*methodBody=*/[{}],
/*defaultImplementation=*/[{
// * assumes fieldNames dictionary attr exists
// * assumes each name in fieldNames has an entry in fieldTypes
// dictionary attr mapping the name to type
// * perFieldAttrs, perFieldLocs optional attr/locs for each field
// * each field is emitted on a separate line
mlir::Operation *op = $_op.getOperation();

printer << "(";
printer.increaseIndent();

llvm::SmallVector<mlir::StringAttr> fieldNames = $_op.getFieldNames();

mlir::ArrayRef<mlir::Attribute> perFieldAttrs;
if (auto attrs = op->getAttr("perFieldAttrs"))
perFieldAttrs = mlir::cast<mlir::ArrayAttr>(attrs).getValue();

mlir::ArrayRef<mlir::Attribute> perFieldLocs;
if (auto locs = op->getAttr("perFieldLocs"))
perFieldLocs = mlir::cast<mlir::ArrayAttr>(locs).getValue();

for (unsigned i = 0; i < fieldNames.size(); i++) {
mlir::StringAttr name = fieldNames[i];
if (i > 0) {
printer << ",";
}
printer.printNewline();

// print symbol
printer.printSymbolName(name.getValue());

// print operand if exists
if (!operands.empty()) {
printer << " ";
printer.printOperand(operands[i]);
}

// print type
std::optional<mlir::Type> type = $_op.getFieldType(name);
assert(type.has_value() && "class field has no type");
printer << " : ";
printer.printType(type.value());

// print optional attr
if (!perFieldAttrs.empty())
if (auto fieldAttr = mlir::dyn_cast<mlir::DictionaryAttr>(perFieldAttrs[i]))
printer.printOptionalAttrDict(fieldAttr.getValue());

// print optional loc
if (!perFieldLocs.empty()) {
mlir::Location loc = mlir::cast<mlir::Location>(perFieldLocs[i]);
if (!mlir::isa<mlir::UnknownLoc>(loc))
printer.printOptionalLocationSpecifier(loc);
}
}
printer.decreaseIndent();
if (!fieldNames.empty())
printer.printNewline();
printer << ")";
printer.printOptionalAttrDict(op->getAttrs(),
/*elidedAttrs=*/{"fieldTypes", "fieldNames",
"fieldIdxs", "perFieldAttrs",
"perFieldLocs"});
}]>,
InterfaceMethod<"Get the class-like field names",
"llvm::SmallVector<mlir::StringAttr>", "getFieldNames", (ins),
/*methodBody=*/[{}],
/*defaultImplementation=*/[{
// convenience method that converts generic field names attr into small
// vector of string attrs
mlir::Attribute fieldNames = this->getOperation()->getAttr("fieldNames");
assert(fieldNames && "class fields op has no field names");

llvm::SmallVector<mlir::StringAttr> names;
for (auto name : mlir::cast<mlir::ArrayAttr>(fieldNames).getValue()) {
names.push_back(mlir::cast<mlir::StringAttr>(name));
}
return names;
}]>
];
}

def ClassLike : OpInterface<"ClassLike"> {
let cppNamespace = "circt::om";

Expand All @@ -37,22 +133,25 @@ def ClassLike : OpInterface<"ClassLike"> {
"mlir::Region &", "getBody", (ins)>,
InterfaceMethod<"Get the class-like body block",
"mlir::Block *", "getBodyBlock", (ins),
/*methodBody=*/[{ return $_op.getBodyBlock(); }]>
];
}

def ClassFieldLike : OpInterface<"ClassFieldLike"> {
let cppNamespace = "circt::om";

let description = [{
Common functionality for class-like field operations.
}];

let methods = [
InterfaceMethod<"Get the class-like field's type",
"mlir::Type", "getType", (ins)>,
InterfaceMethod<"Get the class-like field's name attribute",
"mlir::StringAttr", "getNameAttr", (ins)>
/*methodBody=*/[{ return $_op.getBodyBlock(); }]>,
InterfaceMethod<"Get the class-like fields op",
"circt::om::ClassFieldsLike", "getFieldsOp", (ins),
/*methodBody=*/[{}],
/*defaultImplementation=*/[{
return mlir::cast<ClassFieldsLike>($_op.getBodyBlock()->getTerminator());
}]>,
InterfaceMethod<"Get the class-like field type",
"std::optional<mlir::Type>", "getFieldType", (ins "mlir::StringAttr":$field),
/*methodBody=*/[{}],
/*defaultImplementation=*/[{
return $_op.getFieldsOp().getFieldType(field);
}]>,
InterfaceMethod<"Get the class-like field names",
"llvm::SmallVector<mlir::StringAttr>", "getFieldNames", (ins),
/*methodBody=*/[{}],
/*defaultImplementation=*/[{
return $_op.getFieldsOp().getFieldNames();
}]>
];
}

Expand Down
1 change: 1 addition & 0 deletions include/circt/Dialect/OM/OMOps.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/OpDefinition.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include "mlir/Interfaces/InferTypeOpInterface.h"

#define GET_OP_CLASSES
Expand Down
61 changes: 38 additions & 23 deletions include/circt/Dialect/OM/OMOps.td
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#ifndef CIRCT_DIALECT_OM_OMOPS_TD
#define CIRCT_DIALECT_OM_OMOPS_TD

include "mlir/Interfaces/ControlFlowInterfaces.td"
include "circt/Dialect/OM/OMDialect.td"
include "circt/Dialect/OM/OMEnums.td"
include "circt/Dialect/OM/OMOpInterfaces.td"
Expand All @@ -33,7 +34,7 @@ class OMOp<string mnemonic, list<Trait> traits = []> :

class OMClassLike<string mnemonic, list<Trait> traits = []> :
OMOp<mnemonic, traits # [
SingleBlock, NoTerminator, Symbol, RegionKindInterface, IsolatedFromAbove,
Symbol, RegionKindInterface, IsolatedFromAbove,
DeclareOpInterfaceMethods<OpAsmOpInterface, ["getAsmBlockArgumentNames"]>,
DeclareOpInterfaceMethods<ClassLike>]> {

Expand All @@ -57,16 +58,17 @@ class OMClassLike<string mnemonic, list<Trait> traits = []> :
let hasVerifier = 1;
}

class OMClassFieldLike<string mnemonic, list<Trait> traits = []> :
OMOp<mnemonic, traits # [
DeclareOpInterfaceMethods<ClassFieldLike>]> {
class OMClassFieldsLike<string mnemonic, list<Trait> traits = []> :
OMOp<mnemonic, traits # [Terminator, ReturnLike, Pure,
DeclareOpInterfaceMethods<ClassFieldsLike>]> {
}

//===----------------------------------------------------------------------===//
// Class definitions
//===----------------------------------------------------------------------===//

def ClassOp : OMClassLike<"class"> {
def ClassOp : OMClassLike<"class", [
SingleBlockImplicitTerminator<"ClassFieldsOp">]> {
let extraClassDeclaration = [{
mlir::Block *getBodyBlock() { return &getBody().front(); }
// This builds a ClassOp, and populates it with the CLassFieldOps.
Expand All @@ -82,46 +84,59 @@ def ClassOp : OMClassLike<"class"> {
static mlir::RegionKind getRegionKind(unsigned index) {
return mlir::RegionKind::Graph;
}

// Convenience methods for creating ClassFieldsOp
void addFields(mlir::OpBuilder &builder, mlir::Location loc,
llvm::ArrayRef<mlir::Attribute> fieldNames,
llvm::ArrayRef<mlir::Value> fieldValues);

void addFields(mlir::OpBuilder &builder,
llvm::ArrayRef<mlir::Location> locs,
llvm::ArrayRef<mlir::Attribute> fieldNames,
llvm::ArrayRef<mlir::Value> fieldValues);

void addFields(mlir::OpBuilder &builder,
mlir::Location loc,
llvm::ArrayRef<llvm::StringRef> fieldNames,
llvm::ArrayRef<mlir::Value> fieldValues);
}];
}

def ClassFieldOp : OMClassFieldLike<"class.field",
[HasParent<"ClassOp">]> {
let arguments = (ins
SymbolNameAttr:$name,
AnyType:$value
);
def ClassFieldsOp : OMClassFieldsLike<"class.fields", [HasParent<"ClassOp">]> {
let arguments = (ins Variadic<AnyType>:$fieldArgs);

let assemblyFormat = [{
$name `,` $value attr-dict `:` type($value)
}];
let hasCustomAssemblyFormat = 1;
}

//===----------------------------------------------------------------------===//
// External class definitions
//===----------------------------------------------------------------------===//

def ClassExternOp : OMClassLike<"class.extern"> {
def ClassExternOp : OMClassLike<"class.extern", [
SingleBlockImplicitTerminator<"ClassExternFieldsOp">]> {
let extraClassDeclaration = [{
mlir::Block *getBodyBlock() { return &getBody().front(); }

// Implement RegionKindInterface.
static mlir::RegionKind getRegionKind(unsigned index) {
return mlir::RegionKind::Graph;
}

// Convenience methods for creating ClassExternFieldsOp
void addFields(mlir::OpBuilder &builder, mlir::Location loc,
llvm::ArrayRef<mlir::StringAttr> fieldNames,
llvm::ArrayRef<mlir::Type> fieldTypes);
void addFields(mlir::OpBuilder &builder,
llvm::ArrayRef<mlir::Location> locs,
llvm::ArrayRef<mlir::StringAttr> fieldNames,
llvm::ArrayRef<mlir::Type> fieldTypes);
}];
}

def ClassExternFieldOp : OMClassFieldLike<"class.extern.field",
def ClassExternFieldsOp : OMClassFieldsLike<"class.extern.fields",
[HasParent<"ClassExternOp">]> {
let arguments = (ins
SymbolNameAttr:$name,
TypeAttr:$type
);

let assemblyFormat = [{
$name attr-dict `:` $type
}];
let hasCustomAssemblyFormat = 1;
}

//===----------------------------------------------------------------------===//
Expand Down
Loading

0 comments on commit f37ea37

Please sign in to comment.