Introduce the notion of dialect attributes and dependent attributes. A dialect attribute derives its context from a specific dialect, whereas a dependent attribute derives context from what it is attached to. Following this, we now enforce that functions and function arguments may only contain dialect specific attributes. These are generic entities and cannot provide any specific context for a dependent attribute.

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
This commit is contained in:
River Riddle 2019-03-02 22:34:18 -08:00 committed by jpienaar
parent 485746f524
commit a495f960e0
7 changed files with 143 additions and 67 deletions

View 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:

View 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

View 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.

View 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.

View 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"))

View 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})

View 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
}