Hi Steve and River-
For now, I’m just sketching out what this would look like and do. I’ll submit a patch to phabricator once I’m a little further on.
Yeah, that proposal link which I had previously posted was quick ‘n’ dirty. It was more-or-less to just to ascertain if I should be building on any tablegen code which already exists. Here’s a revised proposal. I’ve expanded the comments a bit, but I’m not familiar with all the terminology.
This tablegen code:
include "mlir/IR/OpBase.td"
// *****
// This would go in OpBase.td
// 'TypeMember' represents a parameter which goes in the type storage.
// 'fieldName' is the member name in the storage struct, and various function
// parameters. The type of this member can be defined multiple ways (below)
class TypeMember<string fieldName>;
// Define a new type belonging to a dialect and called 'name'
class TypeDef<Dialect dialect, string name> {
// Name of storage class to generate or use
string storageClass = name # "Storage";
// Namespace (withing dialect c++ namespace) in which the storage class resides
string storageNamespace = "detail";
// Should we generate the storage class? (Or use an existing one?)
int genStorageClass = 1;
// What is the enum containing 'kinds' called?
string kindsEnum = "Types";
// The symbol for the 'kind' entry
string kind = name;
// Use the lowercased name as the keyword for parsing/printing
string keyword = name;
// This is the list of fields in the storage class (and list of parameters
// in the creation functions). If empty, don't use or generate a storage class
list<TypeMember> contents = [];
}
// Directly use thie c++ 'Type' class name
class CppType<string cppTypeName, string fieldName> : TypeMember<fieldName>;
// Reference to another tablegen'd typedef
class TypeDefName<TypeDef type, string fieldName> : TypeMember<fieldName>;
// Generate a new struct to use in a storage field. In this case, the fieldName
// doubles as the c++ struct name.
class NewStructType<list<TypeMember> members, string fieldName> : TypeMember<fieldName>;
// A list of other TypeMembers
class ListOf<TypeMember, string fieldName> : TypeMember<fieldName>;
// ************
// Test defs
def Test_Dialect: Dialect {
let name = "TestDialect";
}
// Base class for other typedefs. Provides dialact-specific defaults
class TestType<string name> : TypeDef<Test_Dialect, name> {
// Override the default enum name
let kindsEnum = "TestDialectTypes"
}
// SimpleTypeA is a simple type. A storage class will not be generated.
def SimpleTypeA : TestType<"SimpleA"> { }
// A more complex parameterized type
def CompoundTypeA : TestType<"CompoundA"> {
// Override the default mnemonic
let keyword = "cmpnd_a";
// What types do we contain?
let contents = [
// A standard c++ int
CppType<"int", "widthOfSomething">,
// The simple type defined above
TypeDefName<SimpleTypeA, "exampleCustomType">,
// Define a new C++ struct in which to store elements
NewStructType<[
// Only contain one member which is the 'CustomTypeDefInCPP', a type
// previously defined in C++
CppType<"CustomTypeDefInCPP", "firstMember">
], "generatedStruct">
];
}
Would produce something like this C++ code (relevant td code copied as comments). This code won’t compile and may have some issues, but I think it’s close enough to to demonstrative.
// tablegen code:
// def SimpleTypeA : TestType<"SimpleTypeA"> {
// let name = "SimpleA";
// }
// This would generate the following c++ header code:
class SimpleTypeA : public Type::TypeBase<TestType, Type> {
public:
using Base::Base
static bool kindof(unsigned kind) { return kind == TestDialectTypes::SimpleA; }
static SimpleTypeA get(MLIRContext *context) {
return Base::get(context, TestDialectTypes::Test);
}
StringRef getKeyword() { return "simplea"; }
static Type parse(mlir::MLIRContext* ctxt, mlir::DialectAsmParser& parser);
}
// and the following for the .cpp file:
Type parse(mlir::MLIRContext* ctxt, mlir::DialectAsmParser& parser) {
return get(ctxt);
}
// printer code omitted (it would be the inverse) as I assume it would be obvious from the parser
// tablegen code:
// def CompoundTypeA : TestType<"CompoundTypeA"> {
// let name = "CompoundA"
// let keyword = "cmpnd_a";
// // What types do we contain?
// let contents = [
// CppType<"mlir::IntegerType", "countOfSomething">,
// TypeDefName<SimpleTypeA, "exampleCustomType">,
// NewStructType<[
// CppType<"CustomTypeDefInCPP", "firstMember">
// ], "generatedStruct">
// ];
// This would generate the following c++ header code:
namespace detail {
struct CompoundTypeAStorage;
}
class CompoundTypeA : public Type::TypeBase<TestType, Type, details::CompoundTypeAStorge> {
public:
using Base::Base
static bool kindof(unsigned kind) { return kind == TestDialectTypes::CompoundA; }
static CompoundTypeA get(MLIRContext *context, int countOfSomething, SimpleTypeA exampleCustomType, GeneratedStruct generatedStruct) {
return Base::get(context, TestDialectTypes::CompoundA, countOfSomething, exampleCustomType, generatedStruct);
}
StringRef getKeyword() { return "cmpnd_a"; }
static Type parse(mlir::MLIRContext* ctxt, mlir::DialectAsmParser& parser);
}
//
// for the .cpp file:
//
namespace details {
struct CompoundTypeAStorage : public TypeStorage {
struct GeneratedStruct {
CustomTypeDefInCPP firstMember;
static llvm::hash_code hashKey(const GeneratedStruct& generatedStruct) {
return llvm::hash_combine(firstMember);
}
LogicResult parse(mlir::MLIRContext* ctxt, mlir::DialectAsmParser& parser);
};
CompoundTypeAStorage(int countOfSomething, SimpleTypeA exampleCustomType, GeneratedStruct generatedStruct)
: countOfSomething(countOfSomething), exampleCustomType(exampleCustomType), generatedStruct(generatedStruct) {}
using KeyTy = std::tuple<int, SimpleTypeA, GeneratedStruct>;
bool operator==(const KeyTy &key) const {
return key == KeyTy(countOfSomething, exampleCustomType, generatedStruct);
}
static llvm::hash_code hashKey(const KeyTy &key) {
return llvm::hash_combine(std::get<0>(key), std::get<1>(key), std::get<2>(key));
}
static KeyTy getKey(int countOfSomething, SimpleTypeA exampleCustomType, GeneratedStruct generatedStruct) {
return KeyTy(countOfSomething, exampleCustomType, generatedStruct);
}
static CompoundTypeAStorage *construct(TypeStorageAllocator &allocator,
const KeyTy &key) {
return new (allocator.allocate<CompoundTypeAStorage>())
CompoundTypeAStorage(key.first, key.second);
}
static Type parse(mlir::MLIRContext* ctxt, mlir::DialectAsmParser& parser);
int countOfSomething;
SimpleTypeA exampleCustomType;
GeneratedStruct generatedStruct;
};
}
Type CompoundTypeA::parse(mlir::MLIRContext* ctxt, mlir::DialectAsmParser& parser) {
return details::CompoundTypeAStorage::parse(ctxt, parser);
}
Type CompoundTypeAStorage::parse(mlir::MLIRContext* ctxt, mlir::DialectAsmParser& parser) {
int countOfSomething;
Type exampleCustomType;
GeneratedStruct generatedStruct;
if (parser.parseLess()) return Type();
if (parser.parseInt(countOfSomething)) return Type();
if (parser.parseComma()) return Type();
if (parser.parseType(exampleCustomType)) return Type();
if (!generatedStruct.isa<SimpleTypeA>()) {
parser.emitError("Expected SimpleTypeA");
return nullptr;
}
if (parser.parseComma()) return Type();
generatedStruct.parse(ctxt, parser);
if (parser.parseGreater()) return Type();
return CompoundTypeA::get(ctxt, countOfSomething, exampleCustomType.cast<SimpleTypeA>(), generatedStruct);
}
ParseResult CompoundTypeAStorage::GeneratedStruct parse(mlir::MLIRContext* ctxt, mlir::DialectAsmParser& parser) {
Type firstMember;
if (parser.parseLBrace()) return failure();
if (parser.parseType(firstMember)) return failure();
if (!firstMember.isa<CustomTypeDefInCPP>())
return emitError(parser.getCurrentLocation(), "Expected CustomTypeDefInCPP");
this->firstMember = firstMember.cast<CustomTypeDefInCPP>();
if (parser.parseRBrace()) return failure();
return success();
}
// For parsing, the following snippet would be generated:
//
if (keyword == SimpleTypeA::getKeyword())
return SimpleTypeA::parse(getContext(), parser);
if (keyword == CompoundTypeA::getKeyword())
return CompoundTypeA::parse(getContext(), parser);
//
// for use in the following Dialect type parser:
//
Type TestDialect::parseType(DialectAsmParser &parser) const {
llvm::StringRef typeKeyword;
if (parser.parseKeyword(&typeKeyword))
return Type();
#define GET_TYPE_PARSER_SELECTION
#include "Dialects/TestDialect/Types.cpp.inc"
return Type();
}
Does this help?
(These files are also on GH: https://github.com/teqdruid/llvm-project/blob/tblgen-types/mlir/test/mlir-tblgen/type.td, https://github.com/teqdruid/llvm-project/blob/tblgen-types/mlir/test/mlir-tblgen/type_output.cpp)
Thanks!