Introduce the notion of dialect attributes and dependent attributes. A dialect attrib...
authorRiver Riddle <riverriddle@google.com>
Sun, 3 Mar 2019 06:34:18 +0000 (22:34 -0800)
committerjpienaar <jpienaar@google.com>
Fri, 29 Mar 2019 23:55:05 +0000 (16:55 -0700)
Dialect attributes are defined as:

        dialect-namespace `.` attr-name `:` attribute-value

Dialects can override any of the following hooks to verify the validity of a given attribute:
  * verifyFunctionAttribute
  * verifyFunctionArgAttribute
  * verifyInstructionAttribute

PiperOrigin-RevId: 236507970

mlir/g3doc/LangRef.md
mlir/include/mlir/IR/Dialect.h
mlir/lib/Analysis/Verifier.cpp
mlir/lib/IR/AsmPrinter.cpp
mlir/lib/Parser/Parser.cpp
mlir/test/IR/invalid.mlir
mlir/test/IR/parser.mlir

index 7a7b077..4d975af 100644 (file)
@@ -867,14 +867,48 @@ Syntax:
 ``` {.ebnf}
 attribute-dict ::= `{` `}`
                  | `{` attribute-entry (`,` attribute-entry)* `}`
-attribute-entry ::= `:`? bare-id `:` attribute-value
+attribute-entry ::= dialect-attribute-entry | dependent-attribute-entry
+dialect-attribute-entry ::= dialect-namespace `.` bare-id `:` attribute-value
+dependent-attribute-entry ::= dependent-attribute-name `:` attribute-value
+dependent-attribute-name ::= (letter|[_]) (letter|digit|[_$])*
 ```
 
 Attributes are the mechanism for specifying constant data in MLIR in places
 where a variable is never allowed - e.g. the index of a
-[`dim` operation](#'dim'-operation), or the stride of a convolution.
+[`dim` operation](#'dim'-operation), or the stride of a convolution. They
+consist of a name and a [concrete attribute value](#attribute-values). It is
+possible to attach attributes to operations, functions, and function arguments.
+The set of expected attributes, their structure, and their interpretation are
+all contextually dependent on what they are attached to.
 
-Attributes have a name, and their values are represented by the following forms:
+There are two main classes of attributes; dependent and dialect. Dependent
+attributes derive their structure and meaning from what they are attached to,
+e.g the meaning of the `index` attribute on a `dim` operation is defined by the
+`dim` operation. Dialect attributes, on the other hand, derive their context and
+meaning from a specific dialect. An example of a dialect attribute may be a
+`swift.self` function argument attribute that indicates an argument is the
+self/context parameter. The context of this attribute is defined by the `swift`
+dialect and not the function argument.
+
+### Function and Argument Attributes
+
+Functions and function arguments in MLIR may have optional attributes attached
+to them. The sole constraint for these attributes is that they must be dialect
+specific attributes. This is because functions, and function arguments, are a
+generic entities and thus cannot apply any meaningful context necessary for
+dependent attributes. This has the added benefit of avoiding collisions between
+common attribute names, such as `noalias`.
+
+### Operation Attributes
+
+Operations, unlike functions and function arguments, may include both dialect
+specific and dependent attributes. This is because an operation represents a
+distinct semantic context, and can thus provide a single source of meaning to
+dependent attributes.
+
+### Attribute Values {#attribute-values}
+
+Attributes values are represented by the following forms:
 
 ``` {.ebnf}
 attribute-value ::= affine-map-attribute
@@ -889,11 +923,7 @@ attribute-value ::= affine-map-attribute
                   | type-attribute
 ```
 
-It is possible to attach attributes to instructions and functions, and the set
-of expected attributes, their structure, and the definition of that meaning is
-contextually dependent on the operation they are attached to.
-
-### AffineMap Attribute {#affine-map-attribute}
+#### AffineMap Attribute {#affine-map-attribute}
 
 Syntax:
 
@@ -903,7 +933,7 @@ affine-map-attribute ::= affine-map
 
 An affine-map attribute is an attribute that represents a affine-map object.
 
-### Array Attribute {#array-attribute}
+#### Array Attribute {#array-attribute}
 
 Syntax:
 
@@ -914,7 +944,7 @@ array-attribute ::= `[` (attribute-value (`,` attribute-value)*)? `]`
 An array attribute is an attribute that represents a collection of attribute
 values.
 
-### Boolean Attribute {#bool-attribute}
+#### Boolean Attribute {#bool-attribute}
 
 Syntax:
 
@@ -925,7 +955,7 @@ bool-attribute ::= bool-literal
 A boolean attribute is a literal attribute that represents a one-bit boolean
 value, true or false.
 
-### Elements Attributes {#elements-attributes}
+#### Elements Attributes {#elements-attributes}
 
 Syntax:
 
@@ -939,7 +969,7 @@ elements-attribute ::= dense-elements-attribute
 An elements attribute is a literal attribute that represents a constant
 [vector](#vector-type) or [tensor](#tensor-type) value.
 
-#### Dense Elements Attribute {#dense-elements-attribute}
+##### Dense Elements Attribute {#dense-elements-attribute}
 
 Syntax:
 
@@ -953,7 +983,7 @@ constant vector or tensor value has been packed to the element bitwidth. The
 element type of the vector or tensor constant must be of integer, index, or
 floating point type.
 
-#### Opaque Elements Attribute {#opaque-elements-attribute}
+##### Opaque Elements Attribute {#opaque-elements-attribute}
 
 Syntax:
 
@@ -970,7 +1000,7 @@ it.
 
 Note: The parsed string literal must be in hexadecimal form.
 
-#### Sparse Elements Attribute {#sparse-elements-attribute}
+##### Sparse Elements Attribute {#sparse-elements-attribute}
 
 Syntax:
 
@@ -1000,7 +1030,7 @@ Example:
 ///   [0, 0, 0, 0]]
 ```
 
-#### Splat Elements Attribute {#splat-elements-attribute}
+##### Splat Elements Attribute {#splat-elements-attribute}
 
 Syntax:
 
@@ -1012,7 +1042,7 @@ splat-elements-attribute ::= `splat` `<` ( tensor-type | vector-type ) `,`
 A splat elements attribute is an elements attribute that represents a tensor or
 vector constant where all elements have the same value.
 
-### Integer Attribute {#integer-attribute}
+#### Integer Attribute {#integer-attribute}
 
 Syntax:
 
@@ -1024,7 +1054,7 @@ An integer attribute is a literal attribute that represents an integral value of
 the specified integer or index type. The default type for this attribute, if one
 is not specified, is a 64-bit integer.
 
-### Integer Set Attribute {#integer-set-attribute}
+#### Integer Set Attribute {#integer-set-attribute}
 
 Syntax:
 
@@ -1034,7 +1064,7 @@ integer-set-attribute ::= affine-map
 
 An integer-set attribute is an attribute that represents a integer-set object.
 
-### Float Attribute {#float-attribute}
+#### Float Attribute {#float-attribute}
 
 Syntax:
 
@@ -1045,7 +1075,7 @@ float-attribute ::= float-literal (`:` float-type)?
 A float attribute is a literal attribute that represents a floating point value
 of the specified [float type](#floating-point-types).
 
-### Function Attribute {#function-attribute}
+#### Function Attribute {#function-attribute}
 
 Syntax:
 
@@ -1056,7 +1086,7 @@ function-attribute ::= function-id `:` function-type
 A function attribute is a literal attribute that represents a reference to the
 given function object.
 
-### String Attribute {#string-attribute}
+#### String Attribute {#string-attribute}
 
 Syntax:
 
@@ -1066,7 +1096,7 @@ string-attribute ::= string-literal
 
 A string attribute is an attribute that represents a string literal value.
 
-### Type Attribute {#type-attribute}
+#### Type Attribute {#type-attribute}
 
 Syntax:
 
index 1024c13..d677f97 100644 (file)
@@ -101,6 +101,26 @@ public:
   virtual void
   getTypeAliases(SmallVectorImpl<std::pair<StringRef, Type>> &aliases) {}
 
+  /// Verify an attribute from this dialect on the given function. Returns true
+  /// if the verification failed, false otherwise.
+  virtual bool verifyFunctionAttribute(const Function *, Attribute) {
+    return false;
+  }
+
+  /// Verify an attribute from this dialect on the argument at 'argIndex' for
+  /// the given function. Returns true if the verification failed, false
+  /// otherwise.
+  virtual bool verifyFunctionArgAttribute(const Function *, unsigned argIndex,
+                                          Attribute) {
+    return false;
+  }
+
+  /// Verify an attribute from this dialect on the given instruction. Returns
+  /// true if the verification failed, false otherwise.
+  virtual bool verifyInstructionAttribute(const Instruction *, Attribute) {
+    return false;
+  }
+
   virtual ~Dialect();
 
   /// Utility function that returns if the given string is a valid dialect
index 1be4a86..9f617e6 100644 (file)
@@ -35,6 +35,7 @@
 
 #include "mlir/Analysis/Dominance.h"
 #include "mlir/IR/Attributes.h"
+#include "mlir/IR/Dialect.h"
 #include "mlir/IR/Function.h"
 #include "mlir/IR/Instruction.h"
 #include "mlir/IR/Module.h"
@@ -68,6 +69,15 @@ public:
     return failure(message, fn);
   }
 
+  /// Returns the registered dialect for a dialect-specific attribute.
+  template <typename ErrorContext>
+  Dialect *getDialectForAttribute(const NamedAttribute &attr,
+                                  const ErrorContext &ctx) {
+    assert(attr.first.strref().contains('.') && "expected dialect attribute");
+    auto dialectNamePair = attr.first.strref().split('.');
+    return fn.getContext()->getRegisteredDialect(dialectNamePair.first);
+  }
+
   template <typename ErrorContext>
   bool verifyAttribute(Attribute attr, const ErrorContext &ctx) {
     if (!attr.isOrContainsFunction())
@@ -103,7 +113,7 @@ public:
   bool verifyInstDominance(const Instruction &inst);
 
   explicit FuncVerifier(const Function &fn)
-      : fn(fn), attrNameRegex("^:?[a-zA-Z_][a-zA-Z_0-9\\.\\$]*$") {}
+      : fn(fn), identifierRegex("^[a-zA-Z_][a-zA-Z_0-9\\.\\$]*$") {}
 
 private:
   /// The function being checked.
@@ -113,7 +123,7 @@ private:
   DominanceInfo *domInfo = nullptr;
 
   /// Regex checker for attribute names.
-  llvm::Regex attrNameRegex;
+  llvm::Regex identifierRegex;
 };
 } // end anonymous namespace
 
@@ -122,29 +132,53 @@ bool FuncVerifier::verify() {
                                    fn.getName().c_str());
 
   // Check that the function name is valid.
-  llvm::Regex funcNameRegex("^[a-zA-Z_][a-zA-Z_0-9\\.\\$]*$");
-  if (!funcNameRegex.match(fn.getName().strref()))
+  if (!identifierRegex.match(fn.getName().strref()))
     return failure("invalid function name '" + fn.getName().strref() + "'", fn);
 
   /// Verify that all of the attributes are okay.
   for (auto attr : fn.getAttrs()) {
-    if (!attrNameRegex.match(attr.first))
+    if (!identifierRegex.match(attr.first))
       return failure("invalid attribute name '" + attr.first.strref() + "'",
                      fn);
     if (verifyAttribute(attr.second, fn))
       return true;
+
+    /// Check that the attribute is a dialect attribute, i.e. contains a '.' for
+    /// the namespace.
+    if (!attr.first.strref().contains('.')) {
+      // TODO: Remove the remaining usages of non dialect attributes on
+      // functions and then enable this check.
+      // return failure("functions may only have dialect attributes", fn);
+      continue;
+    }
+
+    // Verify this attribute with the defining dialect.
+    if (auto *dialect = getDialectForAttribute(attr, fn))
+      if (dialect->verifyFunctionAttribute(&fn, attr.second))
+        return true;
   }
 
   /// Verify that all of the argument attributes are okay.
   for (unsigned i = 0, e = fn.getNumArguments(); i != e; ++i) {
     for (auto attr : fn.getArgAttrs(i)) {
-      if (!attrNameRegex.match(attr.first))
+      if (!identifierRegex.match(attr.first))
         return failure(
             llvm::formatv("invalid attribute name '{0}' on argument {1}",
                           attr.first.strref(), i),
             fn);
       if (verifyAttribute(attr.second, fn))
         return true;
+
+      /// Check that the attribute is a dialect attribute, i.e. contains a '.'
+      /// for the namespace.
+      if (!attr.first.strref().contains('.'))
+        return failure("function arguments may only have dialect attributes",
+                       fn);
+
+      // Verify this attribute with the defining dialect.
+      if (auto *dialect = getDialectForAttribute(attr, fn))
+        if (dialect->verifyFunctionArgAttribute(&fn, i, attr.second))
+          return true;
     }
   }
 
@@ -257,11 +291,18 @@ bool FuncVerifier::verifyOperation(const Instruction &op) {
 
   /// Verify that all of the attributes are okay.
   for (auto attr : op.getAttrs()) {
-    if (!attrNameRegex.match(attr.first))
+    if (!identifierRegex.match(attr.first))
       return failure("invalid attribute name '" + attr.first.strref() + "'",
                      op);
     if (verifyAttribute(attr.second, op))
       return true;
+
+    // Check for any optional dialect specific attributes.
+    if (!attr.first.strref().contains('.'))
+      continue;
+    if (auto *dialect = getDialectForAttribute(attr, op))
+      if (dialect->verifyInstructionAttribute(&op, attr.second))
+        return true;
   }
 
   // If we can get operation info for this, check the custom hook.
index b965019..b88de85 100644 (file)
@@ -77,12 +77,6 @@ static llvm::cl::opt<bool>
                        llvm::cl::desc("Print the generic op form"),
                        llvm::cl::init(false), llvm::cl::Hidden);
 
-static llvm::cl::opt<bool> printInternalAttributes(
-    "mlir-print-internal-attributes",
-    llvm::cl::desc(
-        "Print internal function and instruction attributes (':' prefix)."),
-    llvm::cl::init(false));
-
 namespace {
 class ModuleState {
 public:
@@ -1022,21 +1016,13 @@ void ModulePrinter::printOptionalAttrDict(ArrayRef<NamedAttribute> attrs,
   // Filter out any attributes that shouldn't be included.
   SmallVector<NamedAttribute, 8> filteredAttrs;
   for (auto attr : attrs) {
-    auto attrName = attr.first.strref();
-    // By default, never print attributes that start with a colon.  These are
-    // attributes represent location or other internal metadata.
-    if (!printInternalAttributes && attrName.startswith(":"))
-      return;
-
     // If the caller has requested that this attribute be ignored, then drop it.
-    bool ignore = false;
-    for (auto elide : elidedAttrs)
-      ignore |= attrName == elide;
+    if (llvm::any_of(elidedAttrs,
+                     [&](StringRef elided) { return attr.first.is(elided); }))
+      continue;
 
     // Otherwise add it to our filteredAttrs list.
-    if (!ignore) {
-      filteredAttrs.push_back(attr);
-    }
+    filteredAttrs.push_back(attr);
   }
 
   // If there are no attributes left to print after filtering, then we're done.
index d680556..7d977ae 100644 (file)
@@ -1432,7 +1432,7 @@ ParseResult Parser::parseLocationInstance(llvm::Optional<Location> *loc) {
 ///
 ///   attribute-dict ::= `{` `}`
 ///                    | `{` attribute-entry (`,` attribute-entry)* `}`
-///   attribute-entry ::= `:`? bare-id `:` attribute-value
+///   attribute-entry ::= bare-id `:` attribute-value
 ///
 ParseResult
 Parser::parseAttributeDict(SmallVectorImpl<NamedAttribute> &attributes) {
@@ -1440,17 +1440,11 @@ Parser::parseAttributeDict(SmallVectorImpl<NamedAttribute> &attributes) {
     return ParseFailure;
 
   auto parseElt = [&]() -> ParseResult {
-    // Check for an internal attribute.
-    bool isInternalAttr = consumeIf(Token::colon);
-
     // We allow keywords as attribute names.
     if (getToken().isNot(Token::bare_identifier, Token::inttype) &&
         !getToken().isKeyword())
       return emitError("expected attribute name");
-    Identifier nameId =
-        isInternalAttr
-            ? builder.getIdentifier(Twine(":" + getTokenSpelling()).str())
-            : builder.getIdentifier(getTokenSpelling());
+    Identifier nameId = builder.getIdentifier(getTokenSpelling());
     consumeToken();
 
     if (parseToken(Token::colon, "expected ':' in attribute list"))
index e6f8552..b96eaaa 100644 (file)
@@ -928,3 +928,8 @@ func @invalid_unknown_type_dialect_name() -> !invalid.dialect<"">
 
 // expected-error @+1 {{@ identifier expected to start with letter or '_'}}
 func @$invalid_function_name()
+
+// -----
+
+// expected-error @+1 {{function arguments may only have dialect attributes}}
+func @invalid_func_arg_attr(i1 {non_dialect_attr: 10})
index 04068ed..34b7462 100644 (file)
@@ -1,4 +1,4 @@
-// RUN: mlir-opt -mlir-print-internal-attributes %s | FileCheck %s
+// RUN: mlir-opt %s | FileCheck %s
 
 // CHECK-DAG: #map{{[0-9]+}} = (d0, d1, d2, d3, d4)[s0] -> (d0, d1, d2, d4, d3)
 #map0 = (d0, d1, d2, d3, d4)[s0] -> (d0, d1, d2, d4, d3)
@@ -517,8 +517,8 @@ func @floatAttrs() -> () {
 
 // CHECK-LABEL: func @externalfuncattr
 func @externalfuncattr() -> ()
-  // CHECK: attributes {a: "a\22quoted\22string", b: 4.000000e+00, c: tensor<*xf32>}
-  attributes {a: "a\"quoted\"string", b: 4.0, c: tensor<*xf32>}
+  // CHECK: attributes {dialect.a: "a\22quoted\22string", dialect.b: 4.000000e+00, dialect.c: tensor<*xf32>}
+  attributes {dialect.a: "a\"quoted\"string", dialect.b: 4.0, dialect.c: tensor<*xf32>}
 
 // CHECK-LABEL: func @funcattrempty
 func @funcattrempty() -> ()
@@ -527,8 +527,8 @@ func @funcattrempty() -> ()
 
 // CHECK-LABEL: func @funcattr
 func @funcattr() -> ()
-  // CHECK: attributes {a: "a\22quoted\22string", b: 4.000000e+00, c: tensor<*xf32>}
-  attributes {a: "a\"quoted\"string", b: 4.0, c: tensor<*xf32>} {
+  // CHECK: attributes {dialect.a: "a\22quoted\22string", dialect.b: 4.000000e+00, dialect.c: tensor<*xf32>}
+  attributes {dialect.a: "a\"quoted\"string", dialect.b: 4.0, dialect.c: tensor<*xf32>} {
 ^bb0:
   return
 }
@@ -803,20 +803,20 @@ func @unregistered_term(%arg0 : i1) -> i1 {
   return %arg1 : i1
 }
 
-// CHECK-LABEL: func @internal_attrs
-func @internal_attrs()
-    // CHECK-NEXT: attributes  {:internal.attr: 10
-    attributes {:internal.attr: 10} {
+// CHECK-LABEL: func @dialect_attrs
+func @dialect_attrs()
+    // CHECK-NEXT: attributes  {dialect.attr: 10
+    attributes {dialect.attr: 10} {
   return
 }
 
 // CHECK-LABEL: func @_valid.function$name
 func @_valid.function$name()
 
-// CHECK-LABEL: func @external_func_arg_attrs(i32, i1 {arg.attr: 10}, i32)
-func @external_func_arg_attrs(i32, i1 {arg.attr: 10}, i32)
+// CHECK-LABEL: func @external_func_arg_attrs(i32, i1 {dialect.attr: 10}, i32)
+func @external_func_arg_attrs(i32, i1 {dialect.attr: 10}, i32)
 
-// CHECK-LABEL: func @func_arg_attrs(%arg0: i1 {arg.attr: 10})
-func @func_arg_attrs(%arg0: i1 {arg.attr: 10}) {
+// CHECK-LABEL: func @func_arg_attrs(%arg0: i1 {dialect.attr: 10})
+func @func_arg_attrs(%arg0: i1 {dialect.attr: 10}) {
   return
 }