This is an archive of the discontinued LLVM Phabricator instance.

[mlir][ods] Add codegen support for AttrOrTypeParameter verifiers
AcceptedPublic

Authored by jfurtek on May 25 2022, 3:52 PM.

Details

Summary

mlir-tblgen has enough information to automatically generate the verify()
function for some attributes and types. (The canonical example might be checking
whether or not an integer value is one of the specified enum values for enum
attributes.)

Previously, the genVerifyDecl field was used to instruct mlir-tblgen to
generate a declaration for the verify() function, and users would be required
to provide the implementation. This diff adds support for a verifyInvariants()
function that will

  • verify any individual parameters with custom verification code, and
  • call the custom verify() function (if directed via genVerifyDecl=1)

This is accomplished by:

  • Adding a verifier field to the AttrOrTypeParameter base class
  • Modifying tblgen to generate a verifier function composed of parameter and custom verification
  • Modifying the get() and getChecked() implementations in StorageUserBase to call verifyInvariants()
  • Adding custom verification for integer and bit enum attributes
  • Adding tests for integer and bit enums to verify that getChecked() will return a null attribute for invalid enum values

The existing genVerifyDecl behavior is maintained, and attributes/types will
only behave differently if a non-empty parameter verifier fields are present.

This functionality is expected to be used by a subsequent diff that adds Python
bindings for enums, and will also be used for fastmath support in the
Arithmetic dialect.

Example verifyInvariants() implementation for an enum attribute in the linalg dialect:

::mlir::LogicalResult TypeFnAttr::verifyInvariants(
        ::llvm::function_ref<::mlir::InFlightDiagnostic()> emitError,
        ::mlir::linalg::TypeFn value) {
  // Verify parameter 'value':
  if (failed(
          [&emitError](::mlir::linalg::TypeFn value) -> ::mlir::LogicalResult {
            return symbolizeTypeFn(static_cast<uint32_t>(value)).hasValue()
                       ? ::mlir::success()
                       : (emitError() << "invalid enum value");
          }(value)))
    return ::mlir::failure();
  // If genVerifyDecl were equal to 1, the line below would look like this:
  // return verify(emitError, value);
  // Instead, in this case we just return success();
  return ::mlir::success();
}

Diff Detail

Event Timeline

jfurtek created this revision.May 25 2022, 3:52 PM
jfurtek requested review of this revision.May 25 2022, 3:52 PM
rriddle added inline comments.May 27 2022, 12:18 AM
mlir/include/mlir/IR/AttrTypeBase.td
213–215

We should eventually move to a model that allows both parameter verification and user verification (similar to operations), but I'm fine with punting that.

330–331

Can we have a return type of LogicalResult instead? That would align with verifiers everywhere else.

mlir/tools/mlir-tblgen/AttrOrTypeDefGen.cpp
258–259

nit: Can you add an early return in the if branch and drop this else?

272

I would have the verification code block handle error emission, otherwise you'll end up with a worse error message here and potential for double error messages.

278

nit: I'd just drop this comment.

mlir/unittests/IR/AttributeTest.cpp
257

This couldn't be triggered from a .mlir file?

jfurtek updated this revision to Diff 433159.May 31 2022, 11:54 AM
  • Modify AttrOrTypeParameter verifier codegen to return LogicalResult
jfurtek edited the summary of this revision. (Show Details)May 31 2022, 11:56 AM
jfurtek updated this revision to Diff 433163.May 31 2022, 12:09 PM
jfurtek marked an inline comment as done.
  • Use early return as per review
jfurtek marked 3 inline comments as done.May 31 2022, 12:26 PM
jfurtek added inline comments.
mlir/include/mlir/IR/AttrTypeBase.td
213–215

I agree - I think that there is more to be done here.

At the moment, the parameter verification code from the .td file is emitted as a local (anonymous) lambda. It seems like, in the general case, it might be desirable for the AttrOrType verification function to be able to use the verification functions for each parameter (or maybe some of them). So maybe the parameter verification functions become class members, with a fixed naming convention that can be used by ODS verification code?

If you would like to sketch out or describe how it should work I can take a crack at it.

mlir/unittests/IR/AttributeTest.cpp
257

It could, but that exercises a different code path: the parameter parser (i.e. creating the enum from an IR string). Enum parameter parsers don't currently accept raw integers - they require one (or more) of the string value identifiers.

The Python bindings will (in subsequent commits) pass enum values through the C interface, using the underlying integer type. (The enum generated by mlir-tblgen uses a C++ enum class, which is not compatible with a C interface.) The Python binding code would call getChecked(), much like the test code here, and raise a Python exception if the attribute can't be created.

rriddle added inline comments.Jun 2 2022, 1:31 AM
mlir/include/mlir/IR/AttrTypeBase.td
213–215

I think we should do something like what we do for operations, i.e. we change the verify method called by get/getChecked to be a method called verifyInvariants, which is the thing that we auto-generate. Users that want additional verification can define a verify method as they do today.

The places we'd need to update to call the new method should be near: https://github.com/llvm/llvm-project/blob/db15e31212436ae51c04e8b5fcc2f140db4d6626/mlir/include/mlir/IR/StorageUniquerSupport.h#L140

For the method itself, I think it would look something like:

// This method is what we auto-generate.
static LogicalResult verifyInvariants(...) {
  // Auto-generated verification for parameters goes here.
  ...

  // Invoke the user defined verifier or a empty one here:
  return verify(...);
}

The idea there is that the user never really interacts with verifyInvariants directly, they just implement the verify method.

327

Can you document this field in the AttributesAndTypes.md doc as well? Should likely be added to the parameters section IIRC.

jfurtek planned changes to this revision.Jun 2 2022, 9:18 AM
jfurtek requested review of this revision.Jun 7 2022, 12:29 PM

This still needs work - I am just changing the state to "requesting review" to continue the discussion.

mlir/include/mlir/IR/AttrTypeBase.td
213–215

I've modified the get() and getChecked() methods (locally) to call a tblgen-erated verifyInvariants() method.

/// Get or create a new ConcreteT instance within the ctx. If the arguments
/// provided are invalid, errors are emitted using the provided `emitError`
/// and a null object is returned.
template <typename... Args>
static ConcreteT getChecked(function_ref<InFlightDiagnostic()> emitErrorFn,
                            MLIRContext *ctx, Args... args) {
  // If the construction invariants fail then we return a null attribute.
  if (failed(ConcreteT::verifyInvariants(emitErrorFn, args...)))
    return ConcreteT();
  return UniquerT::template get<ConcreteT>(ctx, args...);
}

Where it gets tricky (and currently fails to compile) is for attributes/types that define custom builders.

For operations, there can be a single post-construction verify() method that checks the operation state. But it seems like for attributes/types, we would need a verifyInvariants() function for each builder (default and custom). This seems doable - I just want to see if you agree.

For the attribute/type default builder case, the getChecked() (and verifyInvariants()) signature is derived from the attribute/type parameters, and mlir-tblgen can use the verifier field of the parameter for the generated verification code. (No problems here.)

However, attribute/type custom builder arguments can be different than the attribute/type parameter types. For example, the builtin Tuple type has this:

let parameters = (ins "ArrayRef<Type>":$types);
let builders = [
  TypeBuilder<(ins "TypeRange":$elementTypes), [{
    return $_get($_ctxt, elementTypes);
  }]>,
  TypeBuilder<(ins), [{
    return $_get($_ctxt, TypeRange());
  }]>
];

In the general case, there is no way to associate custom builder arguments to a specific parameter that has verification code. (For example, consider a custom builder with arguments that are in a different order than the parameter list.) And even if a mapping were known, the parameter type may not be unambigously constructible from the custom builder argument. So it seems that the responsibility for verification needs to be entirely on user-provided code when custom builders are used.

So I would think that it would work like this:

  • mlir-tblgen will generate a verifyInvariants() implementation that matches the signature of default builder (unless skipDefaultBuilders=1). This default verifyInvariants() implementation will invoke the verifier code for attribute/type parameters that have that field. In addition, if genVerfifyDecl=1, a declaration for a custom verify() method will be provided, and the user-provided verify() implementation will be called from the default automatically generated verifyInvariants() function.
  • For attributes/types with custom builders and parameters that DO NOT contain verification code, mlir-tblgen will generate a default trivial implementation of verifyInvariants() that always returns success(). (However, if genVerifyDecl=1, then a verify() method will be declared, and the user-provided implementation will be called by the automatically generated verifyInvariants() function.)
  • For attributes/types with customer builders and parameters that DO contain verification code, the tblgen record must have genVerifyDecl=1. (mlir-tblgen will return an error if genVerifyDecl is not equal to 1.) In this case, mlir-tblgen will generate verify() method prototypes to match each custom builder. User-provided verify() implementations will be called from automatically generated verifyInvariants() functions, and the user-provided verify() shall be responsible for verification of ALL custom builder arguments.

Is this how you envisioned the feature working?

jfurtek planned changes to this revision.Jun 9 2022, 2:16 PM
jfurtek updated this revision to Diff 438069.Jun 17 2022, 5:00 PM
  • Modify AttrOrTypeParameter verifier codegen to return LogicalResult
  • Use early return as per review
  • Add mlir-tblgen-eration of verifyInvariants
  • Generate multiple verifyInvariants() functions - one per builder
  • Fix some build errors
  • Add using declaration for verifyInvariants() and a default variadic template implementation
  • Add generation of multiple verify() functions
  • Update AttrOrTypeDef to use optional verifyParameters field. Add documentation and an additional unit test.
  • Revert FloatAttr::verify() type change. Fix formatting.
  • Fix formatting
jfurtek edited the summary of this revision. (Show Details)Jun 17 2022, 5:10 PM
jfurtek updated this revision to Diff 438843.Jun 21 2022, 2:52 PM
jfurtek edited the summary of this revision. (Show Details)
  • Modify AttrOrTypeParameter verifier codegen to return LogicalResult
  • Use early return as per review
  • Add mlir-tblgen-eration of verifyInvariants
  • Generate multiple verifyInvariants() functions - one per builder
  • Fix some build errors
  • Add using declaration for verifyInvariants() and a default variadic template implementation
  • Add generation of multiple verify() functions
  • Update AttrOrTypeDef to use optional verifyParameters field. Add documentation and an additional unit test.
  • Revert FloatAttr::verify() type change. Fix formatting.
  • Fix formatting
  • Remove unnecessary use of verifyParameters tblgen field
jfurtek marked 3 inline comments as done.Jun 22 2022, 9:32 AM
jfurtek updated this revision to Diff 439062.Jun 22 2022, 9:37 AM
  • Fix clang formatting
jfurtek edited the summary of this revision. (Show Details)Jun 22 2022, 9:37 AM
rriddle accepted this revision.Jun 27 2022, 12:17 PM

Looks really great! Thanks for tackling this. (Sorry for the delay, coming back from two week OOO)

mlir/docs/AttributesAndTypes.md
871–874

This could just link to the AttrParameter, TypeParameter, and AttrOrTypeParameter section above, which should likely also get a little blurb about the verifier field (which could just be the next paragraph + example you have here..

mlir/tools/mlir-tblgen/AttrOrTypeDefGen.cpp
245

You could cache the result of def.genVerifyDecl() given that you invoke it again immediately after, otherwise you could shift the control flow to something like:

if (def.genVerifyDecl()) {
    // Declare the verify() method, to be implemented by the user.
    ...

} else if (!def.anyParamHasVerifier()) {
  return;
}
284

This doesn't work?

This revision is now accepted and ready to land.Jun 27 2022, 12:17 PM
jfurtek updated this revision to Diff 441457.Jun 30 2022, 10:57 AM
  • Modify AttrOrTypeParameter verifier codegen to return LogicalResult
  • Use early return as per review
  • Add mlir-tblgen-eration of verifyInvariants
  • Generate multiple verifyInvariants() functions - one per builder
  • Fix some build errors
  • Add using declaration for verifyInvariants() and a default variadic template implementation
  • Add generation of multiple verify() functions
  • Update AttrOrTypeDef to use optional verifyParameters field. Add documentation and an additional unit test.
  • Revert FloatAttr::verify() type change. Fix formatting.
  • Fix formatting
  • Remove unnecessary use of verifyParameters tblgen field
  • Fix clang formatting
  • Revert unintended function changes
  • Update documentation and clean up control flow based on review feedback
jfurtek marked 4 inline comments as done.Jun 30 2022, 10:59 AM

I don't have commit privileges - can someone land this when convenient? Thanks!