This PR implements python enum bindings for *all* the enums - this includes `I*Attrs` (including positional/bit) and `Dialect/EnumAttr`.
There are a few parts to this:
1. CMake: a small addition to `declare_mlir_dialect_python_bindings` and `declare_mlir_dialect_extension_python_bindings` to generate the enum, a boolean arg `GEN_ENUM_BINDINGS` to make it opt-in (even though it works for basically all of the dialects), and an optional `GEN_ENUM_BINDINGS_TD_FILE` for handling corner cases.
2. EnumPythonBindingGen.cpp: there are two weedy aspects here that took investigation:
1. If an enum attribute is not a `Dialect/EnumAttr` then the `EnumAttrInfo` record is canonical, as far as both the cases of the enum **and the `AttrDefName`**. On the otherhand, if an enum is a `Dialect/EnumAttr` then the `EnumAttr` record has the correct `AttrDefName` ("load bearing", i.e., populates `ods.ir.AttributeBuilder('<NAME>')`) but its `enum` field contains the cases, which is an instance of `EnumAttrInfo`. The solution is to generate an one enum class for both `Dialect/EnumAttr` and "independent" `EnumAttrInfo` but to make that class interopable with two builder registrations that both do the right thing (see next sub-bullet).
2. Because we don't have a good connection to cpp `EnumAttr`, i.e., only the `enum class` getters are exposed (like `DimensionAttr::get(Dimension value)`), we have to resort to parsing e.g., `Attribute.parse(f'#gpu<dim {x}>')`. This means that the set of supported `assemblyFormat`s (for the enum) is fixed at compile of MLIR (currently 2, the only 2 I saw). There might be some things that could be done here but they would require quite a bit more C API work to support generically (e.g., casting ints to enum cases and binding all the getters or going generically through the `symbolize*` methods, like `symbolizeDimension(uint32_t)` or `symbolizeDimension(StringRef)`).
A few small changes:
1. In addition, since this patch registers default builders for attributes where people might've had their own builders already written, I added a `replace` param to `AttributeBuilder.insert` (`False` by default).
2. `makePythonEnumCaseName` can't handle all the different ways in which people write their enum cases, e.g., `llvm.CConv.Intel_OCL_BI`, which gets turned into `INTEL_O_C_L_B_I` (because `llvm::convertToSnakeFromCamelCase` doesn't look for runs of caps). So I dropped it. On the otherhand regularization does need to done because some enums have `None` as a case (and others might have other python keywords).
3. I turned on `llvm` dialect generation here in order to test `nvvm.WGMMAScaleIn`, which is an enum with [[ d7e26b5620/mlir/include/mlir/IR/EnumAttr.td (L22-L25) | no explicit discriminator ]] for the `neg` case.
Note, dialects that didn't get a `GEN_ENUM_BINDINGS` don't have any enums to generate.
Let me know if I should add more tests (the three trivial ones I added exercise both the supported `assemblyFormat`s and `replace=True`).
Reviewed By: stellaraccident
Differential Revision: https://reviews.llvm.org/D157934
178 lines
6.8 KiB
C++
178 lines
6.8 KiB
C++
//===- EnumPythonBindingGen.cpp - Generator of Python API for ODS enums ---===//
|
|
//
|
|
// 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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// EnumPythonBindingGen uses ODS specification of MLIR enum attributes to
|
|
// generate the corresponding Python binding classes.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
#include "OpGenHelpers.h"
|
|
|
|
#include "mlir/TableGen/AttrOrTypeDef.h"
|
|
#include "mlir/TableGen/Attribute.h"
|
|
#include "mlir/TableGen/Dialect.h"
|
|
#include "mlir/TableGen/GenInfo.h"
|
|
#include "llvm/Support/FormatVariadic.h"
|
|
#include "llvm/TableGen/Record.h"
|
|
|
|
using namespace mlir;
|
|
using namespace mlir::tblgen;
|
|
|
|
/// File header and includes.
|
|
constexpr const char *fileHeader = R"Py(
|
|
# Autogenerated by mlir-tblgen; don't manually edit.
|
|
|
|
from enum import IntEnum, auto, IntFlag
|
|
from ._ods_common import _cext as _ods_cext
|
|
from ..ir import register_attribute_builder
|
|
_ods_ir = _ods_cext.ir
|
|
|
|
)Py";
|
|
|
|
/// Makes enum case name Python-compatible, i.e. UPPER_SNAKE_CASE.
|
|
static std::string makePythonEnumCaseName(StringRef name) {
|
|
if (isPythonReserved(name.str()))
|
|
return (name + "_").str();
|
|
return name.str();
|
|
}
|
|
|
|
/// Emits the Python class for the given enum.
|
|
static void emitEnumClass(EnumAttr enumAttr, raw_ostream &os) {
|
|
os << llvm::formatv("class {0}({1}):\n", enumAttr.getEnumClassName(),
|
|
enumAttr.isBitEnum() ? "IntFlag" : "IntEnum");
|
|
if (!enumAttr.getSummary().empty())
|
|
os << llvm::formatv(" \"\"\"{0}\"\"\"\n", enumAttr.getSummary());
|
|
os << "\n";
|
|
|
|
for (const EnumAttrCase &enumCase : enumAttr.getAllCases()) {
|
|
os << llvm::formatv(
|
|
" {0} = {1}\n", makePythonEnumCaseName(enumCase.getSymbol()),
|
|
enumCase.getValue() >= 0 ? std::to_string(enumCase.getValue())
|
|
: "auto()");
|
|
}
|
|
|
|
os << "\n";
|
|
|
|
if (enumAttr.isBitEnum()) {
|
|
os << llvm::formatv(" def __iter__(self):\n"
|
|
" return iter([case for case in type(self) if "
|
|
"(self & case) is case])\n");
|
|
os << llvm::formatv(" def __len__(self):\n"
|
|
" return bin(self).count(\"1\")\n");
|
|
os << "\n";
|
|
}
|
|
|
|
os << llvm::formatv(" def __str__(self):\n");
|
|
if (enumAttr.isBitEnum())
|
|
os << llvm::formatv(" if len(self) > 1:\n"
|
|
" return \"{0}\".join(map(str, self))\n",
|
|
enumAttr.getDef().getValueAsString("separator"));
|
|
for (const EnumAttrCase &enumCase : enumAttr.getAllCases()) {
|
|
os << llvm::formatv(" if self is {0}.{1}:\n",
|
|
enumAttr.getEnumClassName(),
|
|
makePythonEnumCaseName(enumCase.getSymbol()));
|
|
os << llvm::formatv(" return \"{0}\"\n", enumCase.getStr());
|
|
}
|
|
os << llvm::formatv(
|
|
" raise ValueError(\"Unknown {0} enum entry.\")\n\n\n",
|
|
enumAttr.getEnumClassName());
|
|
os << "\n";
|
|
}
|
|
|
|
/// Attempts to extract the bitwidth B from string "uintB_t" describing the
|
|
/// type. This bitwidth information is not readily available in ODS. Returns
|
|
/// `false` on success, `true` on failure.
|
|
static bool extractUIntBitwidth(StringRef uintType, int64_t &bitwidth) {
|
|
if (!uintType.consume_front("uint"))
|
|
return true;
|
|
if (!uintType.consume_back("_t"))
|
|
return true;
|
|
return uintType.getAsInteger(/*Radix=*/10, bitwidth);
|
|
}
|
|
|
|
/// Emits an attribute builder for the given enum attribute to support automatic
|
|
/// conversion between enum values and attributes in Python. Returns
|
|
/// `false` on success, `true` on failure.
|
|
static bool emitAttributeBuilder(const EnumAttr &enumAttr, raw_ostream &os) {
|
|
int64_t bitwidth;
|
|
if (extractUIntBitwidth(enumAttr.getUnderlyingType(), bitwidth)) {
|
|
llvm::errs() << "failed to identify bitwidth of "
|
|
<< enumAttr.getUnderlyingType();
|
|
return true;
|
|
}
|
|
|
|
os << llvm::formatv("@register_attribute_builder(\"{0}\")\n",
|
|
enumAttr.getAttrDefName());
|
|
os << llvm::formatv("def _{0}(x, context):\n",
|
|
enumAttr.getAttrDefName().lower());
|
|
os << llvm::formatv(
|
|
" return "
|
|
"_ods_ir.IntegerAttr.get(_ods_ir.IntegerType.get_signless({0}, "
|
|
"context=context), int(x))\n\n",
|
|
bitwidth);
|
|
return false;
|
|
}
|
|
|
|
/// Emits an attribute builder for the given dialect enum attribute to support
|
|
/// automatic conversion between enum values and attributes in Python. Returns
|
|
/// `false` on success, `true` on failure.
|
|
static bool emitDialectEnumAttributeBuilder(StringRef attrDefName,
|
|
StringRef formatString,
|
|
raw_ostream &os) {
|
|
os << llvm::formatv("@register_attribute_builder(\"{0}\")\n", attrDefName);
|
|
os << llvm::formatv("def _{0}(x, context):\n", attrDefName.lower());
|
|
os << llvm::formatv(" return "
|
|
"_ods_ir.Attribute.parse(f'{0}', context=context)\n\n",
|
|
formatString);
|
|
return false;
|
|
}
|
|
|
|
/// Emits Python bindings for all enums in the record keeper. Returns
|
|
/// `false` on success, `true` on failure.
|
|
static bool emitPythonEnums(const llvm::RecordKeeper &recordKeeper,
|
|
raw_ostream &os) {
|
|
os << fileHeader;
|
|
for (auto &it :
|
|
recordKeeper.getAllDerivedDefinitionsIfDefined("EnumAttrInfo")) {
|
|
EnumAttr enumAttr(*it);
|
|
emitEnumClass(enumAttr, os);
|
|
emitAttributeBuilder(enumAttr, os);
|
|
}
|
|
for (auto &it : recordKeeper.getAllDerivedDefinitionsIfDefined("EnumAttr")) {
|
|
AttrOrTypeDef attr(&*it);
|
|
if (!attr.getMnemonic()) {
|
|
llvm::errs() << "enum case " << attr
|
|
<< " needs mnemonic for python enum bindings generation";
|
|
return true;
|
|
}
|
|
StringRef mnemonic = attr.getMnemonic().value();
|
|
std::optional<StringRef> assemblyFormat = attr.getAssemblyFormat();
|
|
StringRef dialect = attr.getDialect().getName();
|
|
if (assemblyFormat == "`<` $value `>`") {
|
|
emitDialectEnumAttributeBuilder(
|
|
attr.getName(),
|
|
llvm::formatv("#{0}.{1}<{{str(x)}>", dialect, mnemonic).str(), os);
|
|
} else if (assemblyFormat == "$value") {
|
|
emitDialectEnumAttributeBuilder(
|
|
attr.getName(),
|
|
llvm::formatv("#{0}<{1} {{str(x)}>", dialect, mnemonic).str(), os);
|
|
} else {
|
|
llvm::errs()
|
|
<< "unsupported assembly format for python enum bindings generation";
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Registers the enum utility generator to mlir-tblgen.
|
|
static mlir::GenRegistration
|
|
genPythonEnumBindings("gen-python-enum-bindings",
|
|
"Generate Python bindings for enum attributes",
|
|
&emitPythonEnums);
|