ODS: Generate named accessors for raw attributes

Currently named accessors are generated for attributes returning a consumer
friendly type. But sometimes the attributes are used while transforming an
existing op and then the returned type has to be converted back into an
attribute or the raw `getAttr` needs to be used. Generate raw named accessor
for attributes to reference the raw attributes without having to use the string
interface for better compile time verification. This allows calling
`blahAttr()` instead of `getAttr("blah")`.

Raw here refers to returning the underlying storage attribute.

PiperOrigin-RevId: 284583426
This commit is contained in:
Jacques Pienaar 2019-12-09 10:28:58 -08:00 committed by A. Unique TensorFlower
parent 4a62019eb8
commit 89cef725f4
4 changed files with 65 additions and 42 deletions

View File

@ -187,7 +187,6 @@ led by `ins`:
let arguments = (ins
<type-constraint>:$<operand-name>,
...
<attr-constraint>:$<attr-name>,
...
);
@ -199,17 +198,18 @@ hierarchy. Similarly, `<attr-constraint>` is a TableGen `def` from the
information.
There is no requirements on the relative order of operands and attributes; they
can mix freely. But it is recommended to put all operands ahead of attributes,
and use an empty line to separate them to make it more visually distinguishable
if possible. The relative order of operands themselves matters.
can mix freely. The relative order of operands themselves matters. From each
named argument a named getter will be generated that returns the argument with
the return type (in the case of attributes the return type will be
constructed from the storage type, while for operands it will be `Value`). Each
attribute's raw value (e.g., as stored) can also be accessed via generated
`<name>Attr` getters for use in transformation passes where the more user
friendly return type is less suitable.
All the arguments should be named to 1) provide documentation, 2) drive
auto-generation of getter methods, 3) provide a handle to reference for other
places like constraints.
> * Place attributes after operands if possible
> * Give operands and attribute proper names
#### Variadic operands
To declare a variadic operand, wrap the `TypeConstraint` for the operand with

View File

@ -1,5 +1,5 @@
// RUN: mlir-tblgen -gen-op-decls -I %S/../../include %s | FileCheck %s --check-prefix=DECL
// RUN: mlir-tblgen -gen-op-defs -I %S/../../include %s | FileCheck %s --check-prefix=DEF
// RUN: mlir-tblgen -gen-op-decls -I %S/../../include %s | FileCheck %s --check-prefix=DECL --dump-input-on-failure
// RUN: mlir-tblgen -gen-op-defs -I %S/../../include %s | FileCheck %s --check-prefix=DEF --dump-input-on-failure
include "mlir/IR/OpBase.td"
@ -32,18 +32,24 @@ def AOp : NS_Op<"a_op", []> {
// Test getter methods
// ---
// DEF: some-attr-kind AOp::aAttrAttr()
// DEF-NEXT: this->getAttr("aAttr").cast<some-attr-kind>()
// DEF: some-return-type AOp::aAttr() {
// DEF-NEXT: auto attr = this->getAttr("aAttr").cast<some-attr-kind>();
// DEF-NEXT: auto attr = aAttrAttr()
// DEF-NEXT: return attr.some-convert-from-storage();
// DEF: some-attr-kind AOp::bAttrAttr()
// DEF-NEXT: return this->getAttr("bAttr").dyn_cast_or_null<some-attr-kind>()
// DEF: some-return-type AOp::bAttr() {
// DEF-NEXT: auto attr = this->getAttr("bAttr").dyn_cast_or_null<some-attr-kind>();
// DEF-NEXT: auto attr = bAttrAttr();
// DEF-NEXT: if (!attr)
// DEF-NEXT: return some-const-builder-call(mlir::Builder(this->getContext()), 4.2).some-convert-from-storage();
// DEF-NEXT: return attr.some-convert-from-storage();
// DEF: some-attr-kind AOp::cAttrAttr()
// DEF-NEXT: return this->getAttr("cAttr").dyn_cast_or_null<some-attr-kind>()
// DEF: Optional<some-return-type> AOp::cAttr() {
// DEF-NEXT: auto attr = this->getAttr("cAttr").dyn_cast_or_null<some-attr-kind>();
// DEF-NEXT: auto attr = cAttrAttr()
// DEF-NEXT: return attr ? Optional<some-return-type>(attr.some-convert-from-storage()) : (llvm::None);
// Test build methods

View File

@ -42,15 +42,15 @@ def NS_AOp : NS_Op<"a_op", [NoSideEffect]> {
// CHECK-LABEL: NS::AOp declarations
// CHECK: class AOpOperandAdaptor {
// CHECK: public:
// CHECK: AOpOperandAdaptor(ArrayRef<Value *> values);
// CHECK: ArrayRef<Value *> getODSOperands(unsigned index);
// CHECK: Value *a();
// CHECK: ArrayRef<Value *> b();
// CHECK: private:
// CHECK: ArrayRef<Value *> tblgen_operands;
// CHECK: };
// CHECK: class AOpOperandAdaptor {
// CHECK: public:
// CHECK: AOpOperandAdaptor(ArrayRef<Value *> values);
// CHECK: ArrayRef<Value *> getODSOperands(unsigned index);
// CHECK: Value *a();
// CHECK: ArrayRef<Value *> b();
// CHECK: private:
// CHECK: ArrayRef<Value *> tblgen_operands;
// CHECK: };
// CHECK: class AOp : public Op<AOp, OpTrait::AtLeastNResults<1>::Impl, OpTrait::HasNoSideEffect, OpTrait::AtLeastNOperands<1>::Impl
// CHECK: public:
@ -63,7 +63,9 @@ def NS_AOp : NS_Op<"a_op", [NoSideEffect]> {
// CHECK: Operation::result_range getODSResults(unsigned index);
// CHECK: Value *r();
// CHECK: Region &someRegion();
// CHECK: IntegerAttr attr1Attr()
// CHECK: APInt attr1();
// CHECK: FloatAttr attr2Attr()
// CHECK: Optional< APFloat > attr2();
// CHECK: static void build(Value *val);
// CHECK: static void build(Builder *tblgen_builder, OperationState &tblgen_state, Type r, ArrayRef<Type> s, Value *a, ValueRange b, IntegerAttr attr1, /*optional*/FloatAttr attr2);

View File

@ -464,8 +464,7 @@ void OpClass::writeDeclTo(raw_ostream &os) const {
os << extraClassDeclaration << "\n";
if (hasPrivateMethod) {
os << '\n';
os << "private:\n";
os << "\nprivate:\n";
for (const auto &method : methods) {
if (method.isPrivate()) {
method.writeDeclTo(os);
@ -668,29 +667,19 @@ void OpEmitter::emitDef(raw_ostream &os) { opClass.writeDefTo(os); }
void OpEmitter::genAttrGetters() {
FmtContext fctx;
fctx.withBuilder("mlir::Builder(this->getContext())");
for (auto &namedAttr : op.getAttributes()) {
const auto &name = namedAttr.name;
const auto &attr = namedAttr.attr;
// Emit the derived attribute body.
auto emitDerivedAttr = [&](StringRef name, Attribute attr) {
auto &method = opClass.newMethod(attr.getReturnType(), name);
auto &body = method.body();
body << " " << attr.getDerivedCodeBody() << "\n";
};
// Emit the derived attribute body.
if (attr.isDerivedAttr()) {
body << " " << attr.getDerivedCodeBody() << "\n";
continue;
}
// Emit normal emitter.
// Return the queried attribute with the correct return type.
auto attrVal =
(attr.hasDefaultValue() || attr.isOptional())
? formatv("this->getAttr(\"{0}\").dyn_cast_or_null<{1}>()", name,
attr.getStorageType())
: formatv("this->getAttr(\"{0}\").cast<{1}>()", name,
attr.getStorageType());
body << " auto attr = " << attrVal << ";\n";
// Emit with return type specified.
auto emitAttrWithReturnType = [&](StringRef name, Attribute attr) {
auto &method = opClass.newMethod(attr.getReturnType(), name);
auto &body = method.body();
body << " auto attr = " << name << "Attr();\n";
if (attr.hasDefaultValue()) {
// Returns the default value if not set.
// TODO: this is inefficient, we are recreating the attribute for every
@ -705,6 +694,32 @@ void OpEmitter::genAttrGetters() {
body << " return "
<< tgfmt(attr.getConvertFromStorageCall(), &fctx.withSelf("attr"))
<< ";\n";
};
// Generate raw named accessor type. This is a wrapper class that allows
// referring to the attributes via accessors instead of having to use
// the string interface for better compile time verification.
auto emitAttrWithStorageType = [&](StringRef name, Attribute attr) {
auto &method =
opClass.newMethod(attr.getStorageType(), (name + "Attr").str());
auto &body = method.body();
body << " return this->getAttr(\"" << name << "\").";
if (attr.isOptional() || attr.hasDefaultValue())
body << "dyn_cast_or_null<";
else
body << "cast<";
body << attr.getStorageType() << ">();";
};
for (auto &namedAttr : op.getAttributes()) {
const auto &name = namedAttr.name;
const auto &attr = namedAttr.attr;
if (attr.isDerivedAttr()) {
emitDerivedAttr(name, attr);
} else {
emitAttrWithStorageType(name, attr);
emitAttrWithReturnType(name, attr);
}
}
}