[mlir] Allows to query traits from types and attributes

Types and attributes now have a `hasTrait` function that allow users to check
if a type defines a trait.
Also, AbstractType and AbstractAttribute has now a `hasTraitFn` field to carry
the implementation of the `hasTrait` function of the concrete type or attribute.
This patch also adds the remaining functions to access type and attribute traits
in TableGen.

Reviewed By: rriddle

Differential Revision: https://reviews.llvm.org/D105202
This commit is contained in:
Mathieu Fehr 2021-09-13 06:22:19 +00:00 committed by Mehdi Amini
parent 7fb2394a4f
commit 802bf02a73
13 changed files with 237 additions and 12 deletions

View file

@ -30,14 +30,18 @@ class Type;
/// a registered Attribute.
class AbstractAttribute {
public:
using HasTraitFn = llvm::unique_function<bool(TypeID) const>;
/// Look up the specified abstract attribute in the MLIRContext and return a
/// reference to it.
static const AbstractAttribute &lookup(TypeID typeID, MLIRContext *context);
/// This method is used by Dialect objects when they register the list of
/// attributes they contain.
template <typename T> static AbstractAttribute get(Dialect &dialect) {
return AbstractAttribute(dialect, T::getInterfaceMap(), T::getTypeID());
template <typename T>
static AbstractAttribute get(Dialect &dialect) {
return AbstractAttribute(dialect, T::getInterfaceMap(), T::getHasTraitFn(),
T::getTypeID());
}
/// Return the dialect this attribute was registered to.
@ -46,7 +50,8 @@ public:
/// Returns an instance of the concept object for the given interface if it
/// was registered to this attribute, null otherwise. This should not be used
/// directly.
template <typename T> typename T::Concept *getInterface() const {
template <typename T>
typename T::Concept *getInterface() const {
return interfaceMap.lookup<T>();
}
@ -56,14 +61,23 @@ public:
return interfaceMap.contains(interfaceID);
}
/// Returns true if the attribute has a particular trait.
template <template <typename T> class Trait>
bool hasTrait() const {
return hasTraitFn(TypeID::get<Trait>());
}
/// Returns true if the attribute has a particular trait.
bool hasTrait(TypeID traitID) const { return hasTraitFn(traitID); }
/// Return the unique identifier representing the concrete attribute class.
TypeID getTypeID() const { return typeID; }
private:
AbstractAttribute(Dialect &dialect, detail::InterfaceMap &&interfaceMap,
TypeID typeID)
HasTraitFn &&hasTrait, TypeID typeID)
: dialect(dialect), interfaceMap(std::move(interfaceMap)),
typeID(typeID) {}
hasTraitFn(std::move(hasTrait)), typeID(typeID) {}
/// Give StorageUserBase access to the mutable lookup.
template <typename ConcreteT, typename BaseT, typename StorageT,
@ -81,6 +95,9 @@ private:
/// This is a collection of the interfaces registered to this attribute.
detail::InterfaceMap interfaceMap;
/// Function to check if the attribute has a particular trait.
HasTraitFn hasTraitFn;
/// The unique identifier of the derived Attribute class.
const TypeID typeID;
};

View file

@ -84,6 +84,12 @@ public:
friend ::llvm::hash_code hash_value(Attribute arg);
/// Returns true if the type was registered with a particular trait.
template <template <typename T> class Trait>
bool hasTrait() {
return getAbstractAttribute().hasTrait<Trait>();
}
/// Return the abstract descriptor for this attribute.
const AbstractTy &getAbstractAttribute() const {
return impl->getAbstractAttribute();

View file

@ -1809,6 +1809,40 @@ class PredTrait<string descr, Pred pred> : Trait {
Pred predicate = pred;
}
//===----------------------------------------------------------------------===//
// TypeTrait definitions
//===----------------------------------------------------------------------===//
// TypeTrait represents a trait regarding a type.
// TODO: Remove this class in favor of using Trait.
class TypeTrait;
// These classes are used to define type specific traits.
class NativeTypeTrait<string name> : NativeTrait<name, "Type">, TypeTrait;
class ParamNativeTypeTrait<string prop, string params>
: ParamNativeTrait<prop, params, "Type">, TypeTrait;
class GenInternalTypeTrait<string prop>
: GenInternalTrait<prop, "Type">, TypeTrait;
class PredTypeTrait<string descr, Pred pred>
: PredTrait<descr, pred>, TypeTrait;
//===----------------------------------------------------------------------===//
// AttrTrait definitions
//===----------------------------------------------------------------------===//
// AttrTrait represents a trait regarding an attribute.
// TODO: Remove this class in favor of using Trait.
class AttrTrait;
// These classes are used to define attribute specific traits.
class NativeAttrTrait<string name> : NativeTrait<name, "Attribute">, AttrTrait;
class ParamNativeAttrTrait<string prop, string params>
: ParamNativeTrait<prop, params, "Attribute">, AttrTrait;
class GenInternalAttrTrait<string prop>
: GenInternalTrait<prop, "Attribute">, AttrTrait;
class PredAttrTrait<string descr, Pred pred>
: PredTrait<descr, pred>, AttrTrait;
//===----------------------------------------------------------------------===//
// OpTrait definitions
//===----------------------------------------------------------------------===//

View file

@ -57,6 +57,25 @@ protected:
// StorageUserBase
//===----------------------------------------------------------------------===//
namespace storage_user_base_impl {
/// Returns true if this given Trait ID matches the IDs of any of the provided
/// trait types `Traits`.
template <template <typename T> class... Traits>
bool hasTrait(TypeID traitID) {
TypeID traitIDs[] = {TypeID::get<Traits>()...};
for (unsigned i = 0, e = sizeof...(Traits); i != e; ++i)
if (traitIDs[i] == traitID)
return true;
return false;
}
// We specialize for the empty case to not define an empty array.
template <>
inline bool hasTrait(TypeID traitID) {
return false;
}
} // namespace storage_user_base_impl
/// Utility class for implementing users of storage classes uniqued by a
/// StorageUniquer. Clients are not expected to interact with this class
/// directly.
@ -69,6 +88,7 @@ public:
/// Utility declarations for the concrete attribute class.
using Base = StorageUserBase<ConcreteT, BaseT, StorageT, UniquerT, Traits...>;
using ImplType = StorageT;
using HasTraitFn = bool (*)(TypeID);
/// Return a unique identifier for the concrete type.
static TypeID getTypeID() { return TypeID::get<ConcreteT>(); }
@ -87,6 +107,14 @@ public:
return detail::InterfaceMap::template get<Traits<ConcreteT>...>();
}
/// Returns the function that returns true if the given Trait ID matches the
/// IDs of any of the traits defined by the storage user.
static HasTraitFn getHasTraitFn() {
return [](TypeID id) {
return storage_user_base_impl::hasTrait<Traits...>(id);
};
}
/// Attach the given models as implementations of the corresponding interfaces
/// for the concrete storage user class. The type must be registered with the
/// context, i.e. the dialect to which the type belongs must be loaded. The

View file

@ -29,14 +29,18 @@ class MLIRContext;
/// a registered Type.
class AbstractType {
public:
using HasTraitFn = llvm::unique_function<bool(TypeID) const>;
/// Look up the specified abstract type in the MLIRContext and return a
/// reference to it.
static const AbstractType &lookup(TypeID typeID, MLIRContext *context);
/// This method is used by Dialect objects when they register the list of
/// types they contain.
template <typename T> static AbstractType get(Dialect &dialect) {
return AbstractType(dialect, T::getInterfaceMap(), T::getTypeID());
template <typename T>
static AbstractType get(Dialect &dialect) {
return AbstractType(dialect, T::getInterfaceMap(), T::getHasTraitFn(),
T::getTypeID());
}
/// This method is used by Dialect objects to register types with
@ -44,8 +48,9 @@ public:
/// The use of this method is in general discouraged in favor of
/// 'get<CustomType>(dialect)';
static AbstractType get(Dialect &dialect, detail::InterfaceMap &&interfaceMap,
TypeID typeID) {
return AbstractType(dialect, std::move(interfaceMap), typeID);
HasTraitFn &&hasTrait, TypeID typeID) {
return AbstractType(dialect, std::move(interfaceMap), std::move(hasTrait),
typeID);
}
/// Return the dialect this type was registered to.
@ -63,14 +68,23 @@ public:
return interfaceMap.contains(interfaceID);
}
/// Returns true if the type has a particular trait.
template <template <typename T> class Trait>
bool hasTrait() const {
return hasTraitFn(TypeID::get<Trait>());
}
/// Returns true if the type has a particular trait.
bool hasTrait(TypeID traitID) const { return hasTraitFn(traitID); }
/// Return the unique identifier representing the concrete type class.
TypeID getTypeID() const { return typeID; }
private:
AbstractType(Dialect &dialect, detail::InterfaceMap &&interfaceMap,
TypeID typeID)
HasTraitFn &&hasTrait, TypeID typeID)
: dialect(dialect), interfaceMap(std::move(interfaceMap)),
typeID(typeID) {}
hasTraitFn(std::move(hasTrait)), typeID(typeID) {}
/// Give StorageUserBase access to the mutable lookup.
template <typename ConcreteT, typename BaseT, typename StorageT,
@ -88,6 +102,9 @@ private:
/// This is a collection of the interfaces registered to this type.
detail::InterfaceMap interfaceMap;
/// Function to check if the type has a particular trait.
HasTraitFn hasTraitFn;
/// The unique identifier of the derived Type class.
const TypeID typeID;
};

View file

@ -169,6 +169,12 @@ public:
return Type(reinterpret_cast<ImplType *>(const_cast<void *>(pointer)));
}
/// Returns true if the type was registered with a particular trait.
template <template <typename T> class Trait>
bool hasTrait() {
return getAbstractType().hasTrait<Trait>();
}
/// Return the abstract type descriptor for this type.
const AbstractTy &getAbstractType() { return impl->getAbstractType(); }

View file

@ -564,3 +564,39 @@ func @graph_region_cant_have_blocks() {
"terminator"() : () -> ()
}
}
// -----
// Check that we can query traits in types
func @succeeded_type_traits() {
// CHECK: "test.result_type_with_trait"() : () -> !test.test_type_with_trait
"test.result_type_with_trait"() : () -> !test.test_type_with_trait
return
}
// -----
// Check that we can query traits in types
func @failed_type_traits() {
// expected-error@+1 {{result type should have trait 'TestTypeTrait'}}
"test.result_type_with_trait"() : () -> i32
return
}
// -----
// Check that we can query traits in attributes
func @succeeded_attr_traits() {
// CHECK: "test.attr_with_trait"() {attr = #test.attr_with_trait} : () -> ()
"test.attr_with_trait"() {attr = #test.attr_with_trait} : () -> ()
return
}
// -----
// Check that we can query traits in attributes
func @failed_attr_traits() {
// expected-error@+1 {{'attr' attribute should have trait 'TestAttrTrait'}}
"test.attr_with_trait"() {attr = 42 : i32} : () -> ()
return
}

View file

@ -17,7 +17,8 @@
include "TestOps.td"
// All of the attributes will extend this class.
class Test_Attr<string name> : AttrDef<Test_Dialect, name>;
class Test_Attr<string name, list<Trait> traits = []>
: AttrDef<Test_Dialect, name, traits>;
def SimpleAttrA : Test_Attr<"SimpleA"> {
let mnemonic = "smpla";
@ -54,4 +55,12 @@ def AttrWithTypeBuilder : Test_Attr<"AttrWithTypeBuilder"> {
let typeBuilder = "$_attr.getType()";
}
def TestAttrTrait : NativeAttrTrait<"TestAttrTrait">;
// The definition of a singleton attribute that has a trait.
def AttrWithTrait : Test_Attr<"AttrWithTrait", [TestAttrTrait]> {
let mnemonic = "attr_with_trait";
let parameters = (ins );
}
#endif // TEST_ATTRDEFS

View file

@ -16,6 +16,7 @@
#include <tuple>
#include "TestTraits.h"
#include "mlir/IR/Attributes.h"
#include "mlir/IR/Diagnostics.h"
#include "mlir/IR/Dialect.h"

View file

@ -648,6 +648,30 @@ def DefaultDialectOp : TEST_Op<"default_dialect", [OpAsmOpInterface]> {
let assemblyFormat = "regions attr-dict-with-keyword";
}
// This operation requires its return type to have the trait 'TestTypeTrait'.
def ResultTypeWithTraitOp : TEST_Op<"result_type_with_trait", []> {
let results = (outs AnyType);
let verifier = [{
if((*this)->getResultTypes()[0].hasTrait<TypeTrait::TestTypeTrait>())
return success();
return this->emitError("result type should have trait 'TestTypeTrait'");
}];
}
// This operation requires its "attr" attribute to have the
// trait 'TestAttrTrait'.
def AttrWithTraitOp : TEST_Op<"attr_with_trait", []> {
let arguments = (ins AnyAttr:$attr);
let verifier = [{
if (this->attr().hasTrait<AttributeTrait::TestAttrTrait>())
return success();
return this->emitError("'attr' attribute should have trait 'TestAttrTrait'");
}];
}
//===----------------------------------------------------------------------===//
// Test Locations
//===----------------------------------------------------------------------===//

View file

@ -0,0 +1,39 @@
//===- TestTraits.h - MLIR Test Traits --------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file contains traits defined by the TestDialect.
//
//===----------------------------------------------------------------------===//
#ifndef MLIR_TESTTRAITS_H
#define MLIR_TESTTRAITS_H
#include "mlir/IR/Attributes.h"
#include "mlir/IR/Types.h"
namespace mlir {
namespace TypeTrait {
/// A trait defined on types for testing purposes.
template <typename ConcreteType>
class TestTypeTrait : public TypeTrait::TraitBase<ConcreteType, TestTypeTrait> {
};
} // namespace TypeTrait
namespace AttributeTrait {
/// A trait defined on attributes for testing purposes.
template <typename ConcreteType>
class TestAttrTrait
: public AttributeTrait::TraitBase<ConcreteType, TestAttrTrait> {};
} // namespace AttributeTrait
} // namespace mlir
#endif // MLIR_TESTTRAITS_H

View file

@ -182,4 +182,11 @@ def TestMemRefElementType : Test_Type<"TestMemRefElementType",
let mnemonic = "memref_element";
}
def TestTypeTrait : NativeTypeTrait<"TestTypeTrait">;
// The definition of a singleton type that has a trait.
def TestTypeWithTrait : Test_Type<"TestTypeWithTrait", [TestTypeTrait]> {
let mnemonic = "test_type_with_trait";
}
#endif // TEST_TYPEDEFS

View file

@ -16,6 +16,7 @@
#include <tuple>
#include "TestTraits.h"
#include "mlir/IR/Diagnostics.h"
#include "mlir/IR/Dialect.h"
#include "mlir/IR/DialectImplementation.h"