Skip to content

Commit cf0e8dc

Browse files
author
Matteo Franciolini
committed
Add support for versioning properties in MLIR bytecode
[mlir] Add support for custom readProperties/writeProperties methods. Currently, operations that opt-in to adopt properties will see auto-generated readProperties/writeProperties methods to emit and parse bytecode. If a dialects opts in to use `usePropertiesForAttributes`, those definitions will be generated for the current definition of the op without the possibility to handle attribute versioning. The patch adds the capability for an operation to define its own read/write methods for the encoding of properties so that versioned operations can handle upgrading properties encodings. In addition to this, the patch adds an example showing versioning on NamedProperties through the dialect version API exposed by the reader. Reviewed By: mehdi_amini Differential Revision: https://reviews.llvm.org/D155340
1 parent 9c70a3d commit cf0e8dc

15 files changed

+273
-31
lines changed

mlir/include/mlir/IR/OpBase.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2576,6 +2576,10 @@ class Op<Dialect dialect, string mnemonic, list<Trait> props = []> {
25762576
// Whether this op has a folder.
25772577
bit hasFolder = 0;
25782578

2579+
// Whether to let ops implement their custom `readProperties` and
2580+
// `writeProperties` methods to emit bytecode.
2581+
bit useCustomPropertiesEncoding = 0;
2582+
25792583
// Op traits.
25802584
// Note: The list of traits will be uniqued by ODS.
25812585
list<Trait> traits = props;

mlir/include/mlir/TableGen/Operator.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,10 @@ class Operator {
352352

353353
bool hasFolder() const;
354354

355+
/// Whether to generate the `readProperty`/`writeProperty` methods for
356+
/// bytecode emission.
357+
bool useCustomPropertiesEncoding() const;
358+
355359
private:
356360
/// Populates the vectors containing operands, attributes, results and traits.
357361
void populateOpStructure();

mlir/lib/TableGen/Operator.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -854,3 +854,7 @@ std::string Operator::getRemoverName(StringRef name) const {
854854
}
855855

856856
bool Operator::hasFolder() const { return def.getValueAsBit("hasFolder"); }
857+
858+
bool Operator::useCustomPropertiesEncoding() const {
859+
return def.getValueAsBit("useCustomPropertiesEncoding");
860+
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

mlir/test/Bytecode/versioning/versioned_bytecode.mlir

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@
44
// Test roundtrip
55
//===--------------------------------------------------------------------===//
66

7-
// RUN: mlir-opt %S/versioned-op-1.12.mlirbc -emit-bytecode \
7+
// RUN: mlir-opt %S/versioned-op-with-prop-1.12.mlirbc -emit-bytecode \
88
// RUN: -emit-bytecode-version=0 | mlir-opt -o %t.1 && \
9-
// RUN: mlir-opt %S/versioned-op-1.12.mlirbc -o %t.2 && \
9+
// RUN: mlir-opt %S/versioned-op-with-prop-1.12.mlirbc -o %t.2 && \
1010
// RUN: diff %t.1 %t.2
1111

1212
//===--------------------------------------------------------------------===//
1313
// Test invalid versions
1414
//===--------------------------------------------------------------------===//
1515

16-
// RUN: not mlir-opt %S/versioned-op-1.12.mlirbc -emit-bytecode \
16+
// RUN: not mlir-opt %S/versioned-op-with-prop-1.12.mlirbc -emit-bytecode \
1717
// RUN: -emit-bytecode-version=-1 2>&1 | FileCheck %s --check-prefix=ERR_VERSION_NEGATIVE
1818
// ERR_VERSION_NEGATIVE: unsupported version requested -1, must be in range [{{[0-9]+}}, {{[0-9]+}}]
1919

20-
// RUN: not mlir-opt %S/versioned-op-1.12.mlirbc -emit-bytecode \
20+
// RUN: not mlir-opt %S/versioned-op-with-prop-1.12.mlirbc -emit-bytecode \
2121
// RUN: -emit-bytecode-version=999 2>&1 | FileCheck %s --check-prefix=ERR_VERSION_FUTURE
2222
// ERR_VERSION_FUTURE: unsupported version requested 999, must be in range [{{[0-9]+}}, {{[0-9]+}}]

mlir/test/Bytecode/versioning/versioned_op.mlir

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
// COM: version: 2.0
1111
// COM: "test.versionedA"() <{dims = 123 : i64, modifier = false}> : () -> ()
1212
// COM: }
13-
// RUN: mlir-opt %S/versioned-op-2.0.mlirbc 2>&1 | FileCheck %s --check-prefix=CHECK1
13+
// RUN: mlir-opt %S/versioned-op-with-prop-2.0.mlirbc 2>&1 | FileCheck %s --check-prefix=CHECK1
1414
// CHECK1: "test.versionedA"() <{dims = 123 : i64, modifier = false}> : () -> ()
1515

1616
//===--------------------------------------------------------------------===//
@@ -22,8 +22,8 @@
2222
// COM: version: 1.12
2323
// COM: "test.versionedA"() <{dimensions = 123 : i64}> : () -> ()
2424
// COM: }
25-
// RUN: mlir-opt %S/versioned-op-1.12.mlirbc 2>&1 | FileCheck %s --check-prefix=CHECK2
26-
// CHECK2: "test.versionedA"() <{dims = 123 : i64, modifier = false}> : () -> ()
25+
// RUN: mlir-opt %S/versioned-op-with-prop-1.12.mlirbc 2>&1 | FileCheck %s --check-prefix=CHECK3
26+
// CHECK3: "test.versionedA"() <{dims = 123 : i64, modifier = false}> : () -> ()
2727

2828
//===--------------------------------------------------------------------===//
2929
// Test forbidden downgrade
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// This file contains test cases related to the dialect post-parsing upgrade
2+
// mechanism.
3+
// COM: those tests parse bytecode that was generated before test dialect
4+
// adopted `usePropertiesFromAttributes`.
5+
6+
//===--------------------------------------------------------------------===//
7+
// Test generic
8+
//===--------------------------------------------------------------------===//
9+
10+
// COM: bytecode contains
11+
// COM: module {
12+
// COM: version: 2.0
13+
// COM: test.with_versioned_properties 1 | 2
14+
// COM: }
15+
// RUN: mlir-opt %S/versioned-op-with-native-prop-2.0.mlirbc 2>&1 | FileCheck %s --check-prefix=CHECK1
16+
// CHECK1: test.with_versioned_properties 1 | 2
17+
18+
//===--------------------------------------------------------------------===//
19+
// Test upgrade
20+
//===--------------------------------------------------------------------===//
21+
22+
// COM: bytecode contains
23+
// COM: module {
24+
// COM: version: 1.12
25+
26+
// COM: }
27+
// RUN: mlir-opt %S/versioned-op-with-native-prop-1.12.mlirbc 2>&1 | FileCheck %s --check-prefix=CHECK3
28+
// CHECK3: test.with_versioned_properties 1 | 0

mlir/test/lib/Dialect/Test/TestDialect.cpp

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,16 @@ static void customPrintProperties(OpAsmPrinter &p,
114114
const PropertiesWithCustomPrint &prop);
115115
static ParseResult customParseProperties(OpAsmParser &parser,
116116
PropertiesWithCustomPrint &prop);
117+
static LogicalResult setPropertiesFromAttribute(VersionedProperties &prop,
118+
Attribute attr,
119+
InFlightDiagnostic *diagnostic);
120+
static DictionaryAttr getPropertiesAsAttribute(MLIRContext *ctx,
121+
const VersionedProperties &prop);
122+
static llvm::hash_code computeHash(const VersionedProperties &prop);
123+
static void customPrintProperties(OpAsmPrinter &p,
124+
const VersionedProperties &prop);
125+
static ParseResult customParseProperties(OpAsmParser &parser,
126+
VersionedProperties &prop);
117127

118128
void test::registerTestDialect(DialectRegistry &registry) {
119129
registry.insert<TestDialect>();
@@ -1148,6 +1158,54 @@ static ParseResult customParseProperties(OpAsmParser &parser,
11481158
prop.label = std::make_shared<std::string>(std::move(label));
11491159
return success();
11501160
}
1161+
static LogicalResult
1162+
setPropertiesFromAttribute(VersionedProperties &prop, Attribute attr,
1163+
InFlightDiagnostic *diagnostic) {
1164+
DictionaryAttr dict = dyn_cast<DictionaryAttr>(attr);
1165+
if (!dict) {
1166+
if (diagnostic)
1167+
*diagnostic << "expected DictionaryAttr to set VersionedProperties";
1168+
return failure();
1169+
}
1170+
auto value1Attr = dict.getAs<IntegerAttr>("value1");
1171+
if (!value1Attr) {
1172+
if (diagnostic)
1173+
*diagnostic << "expected IntegerAttr for key `value1`";
1174+
return failure();
1175+
}
1176+
auto value2Attr = dict.getAs<IntegerAttr>("value2");
1177+
if (!value2Attr) {
1178+
if (diagnostic)
1179+
*diagnostic << "expected IntegerAttr for key `value2`";
1180+
return failure();
1181+
}
1182+
1183+
prop.value1 = value1Attr.getValue().getSExtValue();
1184+
prop.value2 = value2Attr.getValue().getSExtValue();
1185+
return success();
1186+
}
1187+
static DictionaryAttr
1188+
getPropertiesAsAttribute(MLIRContext *ctx, const VersionedProperties &prop) {
1189+
SmallVector<NamedAttribute> attrs;
1190+
Builder b{ctx};
1191+
attrs.push_back(b.getNamedAttr("value1", b.getI32IntegerAttr(prop.value1)));
1192+
attrs.push_back(b.getNamedAttr("value2", b.getI32IntegerAttr(prop.value2)));
1193+
return b.getDictionaryAttr(attrs);
1194+
}
1195+
static llvm::hash_code computeHash(const VersionedProperties &prop) {
1196+
return llvm::hash_combine(prop.value1, prop.value2);
1197+
}
1198+
static void customPrintProperties(OpAsmPrinter &p,
1199+
const VersionedProperties &prop) {
1200+
p << prop.value1 << " | " << prop.value2;
1201+
}
1202+
static ParseResult customParseProperties(OpAsmParser &parser,
1203+
VersionedProperties &prop) {
1204+
if (parser.parseInteger(prop.value1) || parser.parseVerticalBar() ||
1205+
parser.parseInteger(prop.value2))
1206+
return failure();
1207+
return success();
1208+
}
11511209

11521210
static bool parseUsingPropertyInCustom(OpAsmParser &parser, int64_t value[3]) {
11531211
return parser.parseLSquare() || parser.parseInteger(value[0]) ||
@@ -1220,6 +1278,69 @@ MutableOperandRange TestStoreWithARegionTerminator::getMutableSuccessorOperands(
12201278
return MutableOperandRange(getOperation());
12211279
}
12221280

1281+
LogicalResult
1282+
TestVersionedOpA::readProperties(::mlir::DialectBytecodeReader &reader,
1283+
::mlir::OperationState &state) {
1284+
auto &prop = state.getOrAddProperties<Properties>();
1285+
if (::mlir::failed(reader.readAttribute(prop.dims)))
1286+
return ::mlir::failure();
1287+
1288+
// Check if we have a version. If not, assume we are parsing the current
1289+
// version.
1290+
auto maybeVersion = reader.getDialectVersion("test");
1291+
if (succeeded(maybeVersion)) {
1292+
// If version is less than 2.0, there is no additional attribute to parse.
1293+
// We can materialize missing properties post parsing before verification.
1294+
const auto *version =
1295+
reinterpret_cast<const TestDialectVersion *>(*maybeVersion);
1296+
if ((version->major < 2)) {
1297+
return success();
1298+
}
1299+
}
1300+
1301+
if (::mlir::failed(reader.readAttribute(prop.modifier)))
1302+
return ::mlir::failure();
1303+
return ::mlir::success();
1304+
}
1305+
1306+
void TestVersionedOpA::writeProperties(::mlir::DialectBytecodeWriter &writer) {
1307+
auto &prop = getProperties();
1308+
writer.writeAttribute(prop.dims);
1309+
writer.writeAttribute(prop.modifier);
1310+
}
1311+
1312+
::mlir::LogicalResult TestOpWithVersionedProperties::readFromMlirBytecode(
1313+
::mlir::DialectBytecodeReader &reader, test::VersionedProperties &prop) {
1314+
uint64_t value1, value2 = 0;
1315+
if (failed(reader.readVarInt(value1)))
1316+
return failure();
1317+
1318+
// Check if we have a version. If not, assume we are parsing the current
1319+
// version.
1320+
auto maybeVersion = reader.getDialectVersion("test");
1321+
bool needToParseAnotherInt = true;
1322+
if (succeeded(maybeVersion)) {
1323+
// If version is less than 2.0, there is no additional attribute to parse.
1324+
// We can materialize missing properties post parsing before verification.
1325+
const auto *version =
1326+
reinterpret_cast<const TestDialectVersion *>(*maybeVersion);
1327+
if ((version->major < 2))
1328+
needToParseAnotherInt = false;
1329+
}
1330+
if (needToParseAnotherInt && failed(reader.readVarInt(value2)))
1331+
return failure();
1332+
1333+
prop.value1 = value1;
1334+
prop.value2 = value2;
1335+
return success();
1336+
}
1337+
void TestOpWithVersionedProperties::writeToMlirBytecode(
1338+
::mlir::DialectBytecodeWriter &writer,
1339+
const test::VersionedProperties &prop) {
1340+
writer.writeVarInt(prop.value1);
1341+
writer.writeVarInt(prop.value2);
1342+
}
1343+
12231344
#include "TestOpEnums.cpp.inc"
12241345
#include "TestOpInterfaces.cpp.inc"
12251346
#include "TestTypeInterfaces.cpp.inc"

mlir/test/lib/Dialect/Test/TestDialect.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,17 @@ class MyPropStruct {
9898
return content == rhs.content;
9999
}
100100
};
101+
struct VersionedProperties {
102+
// For the sake of testing, assume that this object was associated to version
103+
// 1.2 of the test dialect when having only one int value. In the current
104+
// version 2.0, the property has two values. We also assume that the class is
105+
// upgrade-able if value2 = 0.
106+
int value1;
107+
int value2;
108+
bool operator==(const VersionedProperties &rhs) const {
109+
return value1 == rhs.value1 && value2 == rhs.value2;
110+
}
111+
};
101112
} // namespace test
102113

103114
#define GET_OP_CLASSES

mlir/test/lib/Dialect/Test/TestDialectInterfaces.cpp

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,10 @@ struct TestBytecodeDialectInterface : public BytecodeDialectInterface {
122122
// Prior version 2.0, the old op supported only a single attribute called
123123
// "dimensions". We can perform the upgrade.
124124
topLevelOp->walk([](TestVersionedOpA op) {
125-
if (auto dims = op->getAttr("dimensions")) {
126-
op->removeAttr("dimensions");
127-
op->setAttr("dims", dims);
128-
}
129-
op->setAttr("modifier", BoolAttr::get(op->getContext(), false));
125+
// Prior version 2.0, `readProperties` did not process the modifier
126+
// attribute. Handle that according to the version here.
127+
auto &prop = op.getProperties();
128+
prop.modifier = BoolAttr::get(op->getContext(), false);
130129
});
131130
return success();
132131
}

mlir/test/lib/Dialect/Test/TestOps.td

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -785,7 +785,7 @@ def OpWithShapedTypeInferTypeInterfaceOp : TEST_Op<"op_with_shaped_type_infer_ty
785785
let results = (outs AnyTensor);
786786
}
787787

788-
def OpWithShapedTypeInferTypeAdaptorInterfaceOp :
788+
def OpWithShapedTypeInferTypeAdaptorInterfaceOp :
789789
TEST_Op<"op_with_shaped_type_infer_type_adaptor_if",
790790
[InferTensorTypeAdaptorWithReify]> {
791791
let arguments = (ins AnyTensor:$operand1, AnyTensor:$operand2);
@@ -2601,6 +2601,10 @@ def TestVersionedOpA : TEST_Op<"versionedA"> {
26012601
AnyI64Attr:$dims,
26022602
BoolAttr:$modifier
26032603
);
2604+
2605+
// Since we use properties to store attributes, we need a custom encoding
2606+
// reader/writer to handle versioning.
2607+
let useCustomPropertiesEncoding = 1;
26042608
}
26052609

26062610
def TestVersionedOpB : TEST_Op<"versionedB"> {
@@ -2742,6 +2746,51 @@ def TestOpWithNiceProperties : TEST_Op<"with_nice_properties"> {
27422746
}];
27432747
}
27442748

2749+
def VersionedProperties : Property<"VersionedProperties"> {
2750+
let convertToAttribute = [{
2751+
getPropertiesAsAttribute($_ctxt, $_storage)
2752+
}];
2753+
let convertFromAttribute = [{
2754+
return setPropertiesFromAttribute($_storage, $_attr, $_diag);
2755+
}];
2756+
let hashProperty = [{
2757+
computeHash($_storage);
2758+
}];
2759+
}
2760+
2761+
def TestOpWithVersionedProperties : TEST_Op<"with_versioned_properties"> {
2762+
let assemblyFormat = "prop-dict attr-dict";
2763+
let arguments = (ins
2764+
VersionedProperties:$prop
2765+
);
2766+
let extraClassDeclaration = [{
2767+
void printProperties(::mlir::MLIRContext *ctx, ::mlir::OpAsmPrinter &p,
2768+
const Properties &prop);
2769+
static ::mlir::ParseResult parseProperties(::mlir::OpAsmParser &parser,
2770+
::mlir::OperationState &result);
2771+
static ::mlir::LogicalResult readFromMlirBytecode(
2772+
::mlir::DialectBytecodeReader &,
2773+
test::VersionedProperties &prop);
2774+
static void writeToMlirBytecode(
2775+
::mlir::DialectBytecodeWriter &,
2776+
const test::VersionedProperties &prop);
2777+
}];
2778+
let extraClassDefinition = [{
2779+
void TestOpWithVersionedProperties::printProperties(::mlir::MLIRContext *ctx,
2780+
::mlir::OpAsmPrinter &p, const Properties &prop) {
2781+
customPrintProperties(p, prop.prop);
2782+
}
2783+
::mlir::ParseResult TestOpWithVersionedProperties::parseProperties(
2784+
::mlir::OpAsmParser &parser,
2785+
::mlir::OperationState &result) {
2786+
Properties &prop = result.getOrAddProperties<Properties>();
2787+
if (customParseProperties(parser, prop.prop))
2788+
return failure();
2789+
return success();
2790+
}
2791+
}];
2792+
}
2793+
27452794
//===----------------------------------------------------------------------===//
27462795
// Test Dataflow
27472796
//===----------------------------------------------------------------------===//
@@ -2765,7 +2814,7 @@ def TestCallAndStoreOp : TEST_Op<"call_and_store",
27652814
def TestStoreWithARegion : TEST_Op<"store_with_a_region",
27662815
[DeclareOpInterfaceMethods<RegionBranchOpInterface>,
27672816
SingleBlock]> {
2768-
let arguments = (ins
2817+
let arguments = (ins
27692818
Arg<AnyMemRef, "", [MemWrite]>:$address,
27702819
BoolAttr:$store_before_region
27712820
);

0 commit comments

Comments
 (0)