[llvm-branch-commits] [mlir] [MLIR][ODS] Print prop-dict fields with custom printers (PR #217589)
Mehdi Amini via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Thu Aug 20 03:51:55 PDT 2026
https://github.com/joker-eph created https://github.com/llvm/llvm-project/pull/217589
Generate a key-value prop-dict printer that dispatches each field to its ODS printer while retaining attribute conversion for non-compositional default parsers.
Keep operation-specific property printer hooks ahead of the generated implementation and preserve legacy input compatibility.
See #155475
Assisted-by: Codex
>From 7988edac7e207afc08aa3fa120b07dc40e1c10ec Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Wed, 19 Aug 2026 09:04:44 -0700
Subject: [PATCH] [MLIR][ODS] Print prop-dict fields with custom printers
Generate a key-value prop-dict printer that dispatches each field to its
ODS printer while retaining attribute conversion for non-compositional default
parsers.
Keep operation-specific property printer hooks ahead of the generated
implementation and preserve legacy input compatibility.
Assisted-by: Codex
---
mlir/docs/DefiningDialects/Operations.md | 5 +-
mlir/include/mlir/Dialect/LLVMIR/XeVMOps.td | 2 +
.../include/mlir/Dialect/XeGPU/IR/XeGPUOps.td | 2 +
mlir/include/mlir/IR/OpBase.td | 5 +
mlir/include/mlir/IR/OpDefinition.h | 22 ++-
mlir/include/mlir/TableGen/Operator.h | 3 +
mlir/lib/TableGen/Operator.cpp | 4 +
.../memref-to-emitc-alloc-copy.mlir | 8 +-
.../memref-to-emitc-alloc-dealloc.mlir | 16 +-
.../memref-to-emitc-alloc-load-store.mlir | 8 +-
.../MemRefToEmitC/memref-to-emitc-copy.mlir | 2 +-
.../memref-with-custom-types.mlir | 3 +-
.../Dialect/EmitC/member_call_opaque.mlir | 4 +-
mlir/test/IR/enum-attr-roundtrip.mlir | 8 +-
mlir/test/IR/properties.mlir | 38 +++--
mlir/test/Target/LLVMIR/Import/intrinsic.ll | 2 +-
mlir/test/lib/Dialect/Test/TestOps.td | 28 ++++
.../op-format-custom-properties-printer.td | 23 +++
mlir/test/mlir-tblgen/op-format.mlir | 12 +-
mlir/tools/mlir-tblgen/OpFormatGen.cpp | 138 +++++++++++++++++-
20 files changed, 278 insertions(+), 55 deletions(-)
create mode 100644 mlir/test/mlir-tblgen/op-format-custom-properties-printer.td
diff --git a/mlir/docs/DefiningDialects/Operations.md b/mlir/docs/DefiningDialects/Operations.md
index 419041a52b0da..a6e119727c906 100644
--- a/mlir/docs/DefiningDialects/Operations.md
+++ b/mlir/docs/DefiningDialects/Operations.md
@@ -768,7 +768,10 @@ The available directives are as follows:
`FieldParser` specialization is available or when the selected
specialization declares `isKeyValueCompositional = false`.
- The legacy `<{key = attribute, ...}>` dictionary spelling is also
- accepted when parsing and is used by the generated printer.
+ accepted when parsing. The generated printer uses the key-value
+ spelling and the same custom-printer or attribute-conversion choice.
+ Operations that provide a custom `printProperties` hook should set
+ `hasCustomPropertiesPrinter` to suppress the shadowed generated helper.
- Any property or inherent attribute that is not used elsewhere in the
format is parsed and printed as part of this list.
- If present, the `attr-dict` will not contain any inherent attributes.
diff --git a/mlir/include/mlir/Dialect/LLVMIR/XeVMOps.td b/mlir/include/mlir/Dialect/LLVMIR/XeVMOps.td
index c52a1c0c94402..f9c80235aa1ca 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/XeVMOps.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/XeVMOps.td
@@ -48,6 +48,8 @@ class XeVM_Attr<string attrName, string attrMnemonic, list<Trait> traits = []>
class XeVM_Op<string mnemonic, list<Trait> traits = []>
: LLVM_OpBase<XeVM_Dialect, mnemonic, traits> {
+ let hasCustomPropertiesPrinter = 1;
+
code extraBaseClassDeclaration = [{
void printProperties(::mlir::MLIRContext *ctx,
::mlir::OpAsmPrinter &p, const Properties &prop,
diff --git a/mlir/include/mlir/Dialect/XeGPU/IR/XeGPUOps.td b/mlir/include/mlir/Dialect/XeGPU/IR/XeGPUOps.td
index 49b98922cee4c..b6b60b252eec8 100644
--- a/mlir/include/mlir/Dialect/XeGPU/IR/XeGPUOps.td
+++ b/mlir/include/mlir/Dialect/XeGPU/IR/XeGPUOps.td
@@ -26,6 +26,8 @@ include "mlir/Interfaces/ViewLikeInterface.td"
class XeGPU_Op<string mnemonic, list<Trait> traits = []>:
Op<XeGPU_Dialect, mnemonic, traits> {
+ let hasCustomPropertiesPrinter = 1;
+
code extraBaseClassDeclaration = [{
void printProperties(::mlir::MLIRContext *ctx,
::mlir::OpAsmPrinter &p, const Properties &prop,
diff --git a/mlir/include/mlir/IR/OpBase.td b/mlir/include/mlir/IR/OpBase.td
index 0d0669e90c3f7..a130d5847a1c7 100644
--- a/mlir/include/mlir/IR/OpBase.td
+++ b/mlir/include/mlir/IR/OpBase.td
@@ -408,6 +408,11 @@ class Op<Dialect dialect, string mnemonic, list<Trait> props = []> {
/// * void print(OpAsmPrinter &p)
bit hasCustomAssemblyFormat = 0;
+ /// This field indicates that the operation provides a custom
+ /// `printProperties` hook. Setting it avoids generating the default
+ /// per-field `prop-dict` printer that the hook would shadow.
+ bit hasCustomPropertiesPrinter = 0;
+
// A bit indicating if the operation has additional invariants that need to
// verified (aside from those verified by other ODS constructs). If set to `1`,
// an additional `LogicalResult verify()` declaration will be generated on the
diff --git a/mlir/include/mlir/IR/OpDefinition.h b/mlir/include/mlir/IR/OpDefinition.h
index 075d39194ed97..d92a93746bf54 100644
--- a/mlir/include/mlir/IR/OpDefinition.h
+++ b/mlir/include/mlir/IR/OpDefinition.h
@@ -1864,6 +1864,18 @@ class Op : public OpState, public Traits<ConcreteType>... {
using detect_has_print_properties =
llvm::is_detected<has_print_properties, T>;
+ /// Trait to check if T provides a generated printer for the key-value
+ /// spelling of `prop-dict`.
+ template <typename T, typename... Args>
+ using has_print_properties_as_key_value_list =
+ decltype(T::_odsPrintPropertiesAsKeyValueList(
+ std::declval<MLIRContext *>(), std::declval<OpAsmPrinter &>(),
+ std::declval<const typename PropertiesSelector<T>::type &>(),
+ std::declval<ArrayRef<StringRef>>()));
+ template <typename T>
+ using detect_has_print_properties_as_key_value_list =
+ llvm::is_detected<has_print_properties_as_key_value_list, T>;
+
/// Trait to check if parseProperties(OpAsmParser, T) exist
template <typename T, typename... Args>
using has_parse_properties = decltype(parseProperties(
@@ -2036,15 +2048,19 @@ class Op : public OpState, public Traits<ConcreteType>... {
InferredProperties<T> &properties) {}
/// Print the operation properties with names not included within
- /// 'elidedProps'. Unless overridden, this method will try to dispatch to a
- /// `printProperties` free-function if it exists, and otherwise by converting
- /// the properties to an Attribute.
+ /// 'elidedProps'. Unless overridden, this method first tries to dispatch to a
+ /// `printProperties` free-function, then to the generated per-field printer,
+ /// and finally converts the properties to an Attribute.
template <typename T>
static void printProperties(MLIRContext *ctx, OpAsmPrinter &p,
const T &properties,
ArrayRef<StringRef> elidedProps = {}) {
if constexpr (detect_has_print_properties<T>::value)
return printProperties(p, properties, elidedProps);
+ if constexpr (detect_has_print_properties_as_key_value_list<
+ ConcreteType>::value)
+ return ConcreteType::_odsPrintPropertiesAsKeyValueList(ctx, p, properties,
+ elidedProps);
genericPrintProperties(
p, ConcreteType::getPropertiesAsAttr(ctx, properties), elidedProps);
}
diff --git a/mlir/include/mlir/TableGen/Operator.h b/mlir/include/mlir/TableGen/Operator.h
index f0514d8e61748..4c0ba2a1db9ec 100644
--- a/mlir/include/mlir/TableGen/Operator.h
+++ b/mlir/include/mlir/TableGen/Operator.h
@@ -143,6 +143,9 @@ class Operator {
/// Returns true if default builders should not be generated.
bool skipDefaultBuilders() const;
+ /// Returns true if the operation provides a custom properties printer.
+ bool hasCustomPropertiesPrinter() const;
+
/// Op result iterators.
const_value_iterator result_begin() const;
const_value_iterator result_end() const;
diff --git a/mlir/lib/TableGen/Operator.cpp b/mlir/lib/TableGen/Operator.cpp
index 82dfbcbfa4d4f..148c3408b707d 100644
--- a/mlir/lib/TableGen/Operator.cpp
+++ b/mlir/lib/TableGen/Operator.cpp
@@ -186,6 +186,10 @@ bool Operator::skipDefaultBuilders() const {
return def.getValueAsBit("skipDefaultBuilders");
}
+bool Operator::hasCustomPropertiesPrinter() const {
+ return def.getValueAsBit("hasCustomPropertiesPrinter");
+}
+
auto Operator::result_begin() const -> const_value_iterator {
return results.begin();
}
diff --git a/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-copy.mlir b/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-copy.mlir
index be0b9baf502bc..8f5b93bffe27e 100644
--- a/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-copy.mlir
+++ b/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-copy.mlir
@@ -18,7 +18,7 @@ func.func @alloc_copy(%arg0: memref<999xi32>) {
// CHECK-LABEL: func.func @alloc_copy(
// CHECK-SAME: %[[ARG0:.*]]: memref<999xi32>) {
// CHECK: %[[UNREALIZED_CONVERSION_CAST_0:.*]] = builtin.unrealized_conversion_cast %[[ARG0]] : memref<999xi32> to !emitc.array<999xi32>
-// CHECK: %[[CALL_OPAQUE_0:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
+// CHECK: %[[CALL_OPAQUE_0:.*]] = emitc.call_opaque "sizeof"() <args = [i32]> : () -> !emitc.size_t
// CHECK: %[[VAL_0:.*]] = "emitc.constant"() <{value = 999 : index}> : () -> index
// CHECK: %[[MUL_0:.*]] = emitc.mul %[[CALL_OPAQUE_0]], %[[VAL_0]] : (!emitc.size_t, index) -> !emitc.size_t
// CHECK: %[[CALL_OPAQUE_1:.*]] = emitc.call_opaque "malloc"(%[[MUL_0]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
@@ -30,11 +30,11 @@ func.func @alloc_copy(%arg0: memref<999xi32>) {
// CHECK: %[[VAL_2:.*]] = "emitc.constant"() <{value = 0 : index}> : () -> index
// CHECK: %[[SUBSCRIPT_1:.*]] = emitc.subscript %[[UNREALIZED_CONVERSION_CAST_1]]{{\[}}%[[VAL_2]]] : (!emitc.array<999xi32>, index) -> !emitc.lvalue<i32>
// CHECK: %[[ADDRESS_OF_1:.*]] = emitc.address_of %[[SUBSCRIPT_1]] : !emitc.lvalue<i32>
-// CHECK: %[[CALL_OPAQUE_2:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
+// CHECK: %[[CALL_OPAQUE_2:.*]] = emitc.call_opaque "sizeof"() <args = [i32]> : () -> !emitc.size_t
// CHECK: %[[VAL_3:.*]] = "emitc.constant"() <{value = 999 : index}> : () -> index
// CHECK: %[[MUL_1:.*]] = emitc.mul %[[CALL_OPAQUE_2]], %[[VAL_3]] : (!emitc.size_t, index) -> !emitc.size_t
// CHECK: emitc.call_opaque "memcpy"(%[[ADDRESS_OF_1]], %[[ADDRESS_OF_0]], %[[MUL_1]]) : (!emitc.ptr<i32>, !emitc.ptr<i32>, !emitc.size_t) -> ()
-// CHECK: %[[CALL_OPAQUE_3:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
+// CHECK: %[[CALL_OPAQUE_3:.*]] = emitc.call_opaque "sizeof"() <args = [i32]> : () -> !emitc.size_t
// CHECK: %[[VAL_4:.*]] = "emitc.constant"() <{value = 999 : index}> : () -> index
// CHECK: %[[MUL_2:.*]] = emitc.mul %[[CALL_OPAQUE_3]], %[[VAL_4]] : (!emitc.size_t, index) -> !emitc.size_t
// CHECK: %[[CALL_OPAQUE_4:.*]] = emitc.call_opaque "malloc"(%[[MUL_2]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
@@ -46,7 +46,7 @@ func.func @alloc_copy(%arg0: memref<999xi32>) {
// CHECK: %[[VAL_6:.*]] = "emitc.constant"() <{value = 0 : index}> : () -> index
// CHECK: %[[SUBSCRIPT_3:.*]] = emitc.subscript %[[UNREALIZED_CONVERSION_CAST_2]]{{\[}}%[[VAL_6]]] : (!emitc.array<999xi32>, index) -> !emitc.lvalue<i32>
// CHECK: %[[ADDRESS_OF_3:.*]] = emitc.address_of %[[SUBSCRIPT_3]] : !emitc.lvalue<i32>
-// CHECK: %[[CALL_OPAQUE_5:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
+// CHECK: %[[CALL_OPAQUE_5:.*]] = emitc.call_opaque "sizeof"() <args = [i32]> : () -> !emitc.size_t
// CHECK: %[[VAL_7:.*]] = "emitc.constant"() <{value = 999 : index}> : () -> index
// CHECK: %[[MUL_3:.*]] = emitc.mul %[[CALL_OPAQUE_5]], %[[VAL_7]] : (!emitc.size_t, index) -> !emitc.size_t
// CHECK: emitc.call_opaque "memcpy"(%[[ADDRESS_OF_3]], %[[ADDRESS_OF_2]], %[[MUL_3]]) : (!emitc.ptr<i32>, !emitc.ptr<i32>, !emitc.size_t) -> ()
diff --git a/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-dealloc.mlir b/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-dealloc.mlir
index 3194a40c16eeb..ad2329b922c81 100644
--- a/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-dealloc.mlir
+++ b/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-dealloc.mlir
@@ -15,7 +15,7 @@ func.func @alloc_and_dealloc() {
// CPP: module {
// CPP-NEXT: emitc.include <"cstdlib">
// CPP-LABEL: alloc_and_dealloc()
-// CPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
+// CPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <args = [i32]> : () -> !emitc.size_t
// CPP-NEXT: %[[ALLOC_SIZE:.*]] = "emitc.constant"() <{value = 999 : index}> : () -> index
// CPP-NEXT: %[[ALLOC_TOTAL_SIZE:.*]] = emitc.mul %[[ALLOC]], %[[ALLOC_SIZE]] : (!emitc.size_t, index) -> !emitc.size_t
// CPP-NEXT: %[[ALLOC_PTR:.*]] = emitc.call_opaque "malloc"(%[[ALLOC_TOTAL_SIZE]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
@@ -27,7 +27,7 @@ func.func @alloc_and_dealloc() {
// NOCPP: module {
// NOCPP-NEXT: emitc.include <"stdlib.h">
// NOCPP-LABEL: alloc_and_dealloc()
-// NOCPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
+// NOCPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <args = [i32]> : () -> !emitc.size_t
// NOCPP-NEXT: %[[ALLOC_SIZE:.*]] = "emitc.constant"() <{value = 999 : index}> : () -> index
// NOCPP-NEXT: %[[ALLOC_TOTAL_SIZE:.*]] = emitc.mul %[[ALLOC]], %[[ALLOC_SIZE]] : (!emitc.size_t, index) -> !emitc.size_t
// NOCPP-NEXT: %[[ALLOC_PTR:.*]] = emitc.call_opaque "malloc"(%[[ALLOC_TOTAL_SIZE]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
@@ -43,7 +43,7 @@ func.func @alloc_and_dealloc_aligned() {
}
// CPP-LABEL: alloc_and_dealloc_aligned
-// CPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [f32]}> : () -> !emitc.size_t
+// CPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <args = [f32]> : () -> !emitc.size_t
// CPP-NEXT: %[[ALLOC_SIZE:.*]] = "emitc.constant"() <{value = 999 : index}> : () -> index
// CPP-NEXT: %[[ALLOC_TOTAL_SIZE:.*]] = emitc.mul %[[ALLOC]], %[[ALLOC_SIZE]] : (!emitc.size_t, index) -> !emitc.size_t
// CPP-NEXT: %[[ALIGNMENT:.*]] = "emitc.constant"() <{value = 64 : index}> : () -> !emitc.size_t
@@ -54,7 +54,7 @@ func.func @alloc_and_dealloc_aligned() {
// CPP-NEXT: return
// NOCPP-LABEL: alloc_and_dealloc_aligned
-// NOCPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [f32]}> : () -> !emitc.size_t
+// NOCPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <args = [f32]> : () -> !emitc.size_t
// NOCPP-NEXT: %[[ALLOC_SIZE:.*]] = "emitc.constant"() <{value = 999 : index}> : () -> index
// NOCPP-NEXT: %[[ALLOC_TOTAL_SIZE:.*]] = emitc.mul %[[ALLOC]], %[[ALLOC_SIZE]] : (!emitc.size_t, index) -> !emitc.size_t
// NOCPP-NEXT: %[[ALIGNMENT:.*]] = "emitc.constant"() <{value = 64 : index}> : () -> !emitc.size_t
@@ -71,7 +71,7 @@ func.func @allocating_and_deallocating_multi() {
}
// CPP-LABEL: allocating_and_deallocating_multi
-// CPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
+// CPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <args = [i32]> : () -> !emitc.size_t
// CPP-NEXT: %[[ALLOC_SIZE:.*]] = "emitc.constant"() <{value = 6993 : index}> : () -> index
// CPP-NEXT: %[[ALLOC_TOTAL_SIZE:.*]] = emitc.mul %[[ALLOC]], %[[ALLOC_SIZE]] : (!emitc.size_t, index) -> !emitc.size_t
// CPP-NEXT: %[[ALLOC_PTR:.*]] = emitc.call_opaque "malloc"(%[[ALLOC_TOTAL_SIZE]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">
@@ -81,7 +81,7 @@ func.func @allocating_and_deallocating_multi() {
// CPP-NEXT: return
// NOCPP-LABEL: allocating_and_deallocating_multi
-// NOCPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
+// NOCPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <args = [i32]> : () -> !emitc.size_t
// NOCPP-NEXT: %[[ALLOC_SIZE:.*]] = "emitc.constant"() <{value = 6993 : index}> : () -> index
// NOCPP-NEXT: %[[ALLOC_TOTAL_SIZE:.*]] = emitc.mul %[[ALLOC]], %[[ALLOC_SIZE]] : (!emitc.size_t, index) -> !emitc.size_t
// NOCPP-NEXT: %[[ALLOC_PTR:.*]] = emitc.call_opaque "malloc"(%[[ALLOC_TOTAL_SIZE]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
@@ -97,7 +97,7 @@ func.func @alloc_and_dealloc_rank0() {
}
// CPP-LABEL: alloc_and_dealloc_rank0
-// CPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
+// CPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <args = [i32]> : () -> !emitc.size_t
// CPP-NEXT: %[[ALLOC_SIZE:.*]] = "emitc.constant"() <{value = 1 : index}> : () -> index
// CPP-NEXT: %[[ALLOC_TOTAL_SIZE:.*]] = emitc.mul %[[ALLOC]], %[[ALLOC_SIZE]] : (!emitc.size_t, index) -> !emitc.size_t
// CPP-NEXT: %[[ALLOC_PTR:.*]] = emitc.call_opaque "malloc"(%[[ALLOC_TOTAL_SIZE]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
@@ -107,7 +107,7 @@ func.func @alloc_and_dealloc_rank0() {
// CPP-NEXT: return
// NOCPP-LABEL: alloc_and_dealloc_rank0
-// NOCPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
+// NOCPP-NEXT: %[[ALLOC:.*]] = emitc.call_opaque "sizeof"() <args = [i32]> : () -> !emitc.size_t
// NOCPP-NEXT: %[[ALLOC_SIZE:.*]] = "emitc.constant"() <{value = 1 : index}> : () -> index
// NOCPP-NEXT: %[[ALLOC_TOTAL_SIZE:.*]] = emitc.mul %[[ALLOC]], %[[ALLOC_SIZE]] : (!emitc.size_t, index) -> !emitc.size_t
// NOCPP-NEXT: %[[ALLOC_PTR:.*]] = emitc.call_opaque "malloc"(%[[ALLOC_TOTAL_SIZE]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
diff --git a/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-load-store.mlir b/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-load-store.mlir
index 653220470bb5a..43c5733baccb3 100644
--- a/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-load-store.mlir
+++ b/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-alloc-load-store.mlir
@@ -21,7 +21,7 @@
// CHECK-SAME: %[[ARG_J:.*]]: !emitc.size_t)
func.func private @memref_alloc_store(%v : f32, %i: index, %j: index) {
/// Allocation size computation
- // CHECK: %[[SIZEOF_F32:.*]] = call_opaque "sizeof"() <{args = [f32]}> : () -> !emitc.size_t
+ // CHECK: %[[SIZEOF_F32:.*]] = call_opaque "sizeof"() <args = [f32]> : () -> !emitc.size_t
// CHECK: %[[NUM_ELEMS:.*]] = "emitc.constant"() <{value = 32 : index}> : () -> index
// CHECK: %[[TOTAL_BYTES:.*]] = mul %[[SIZEOF_F32]], %[[NUM_ELEMS]] : (!emitc.size_t, index) -> !emitc.size_t
/// Alloc
@@ -42,7 +42,7 @@ func.func private @memref_alloc_store(%v : f32, %i: index, %j: index) {
// CHECK-SAME: %[[ARG_I:.*]]: !emitc.size_t,
// CHECK-SAME: %[[ARG_J:.*]]: !emitc.size_t) -> f32
func.func private @memref_alloc_load(%i: index, %j: index) -> f32 {
- // CHECK: %[[SIZEOF_F32:.*]] = call_opaque "sizeof"() <{args = [f32]}> : () -> !emitc.size_t
+ // CHECK: %[[SIZEOF_F32:.*]] = call_opaque "sizeof"() <args = [f32]> : () -> !emitc.size_t
// CHECK: %[[NUM_ELEMS:.*]] = "emitc.constant"() <{value = 32 : index}> : () -> index
// CHECK: %[[TOTAL_BYTES:.*]] = mul %[[SIZEOF_F32]], %[[NUM_ELEMS]] : (!emitc.size_t, index) -> !emitc.size_t
// CHECK: %[[MALLOC_PTR:.*]] = call_opaque "malloc"(%[[TOTAL_BYTES]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
@@ -77,7 +77,7 @@ func.func @memref_load_store(%buff0: memref<2xf32>,
// CHECK-LABEL: emitc.func private @memref_alloc_store_rank0(
// CHECK-SAME: %[[VAL:.*]]: i32)
func.func private @memref_alloc_store_rank0(%v : i32) {
- // CHECK: %[[SIZEOF_I32:.*]] = call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
+ // CHECK: %[[SIZEOF_I32:.*]] = call_opaque "sizeof"() <args = [i32]> : () -> !emitc.size_t
// CHECK: %[[NUM_ELEMS:.*]] = "emitc.constant"() <{value = 1 : index}> : () -> index
// CHECK: %[[TOTAL_BYTES:.*]] = mul %[[SIZEOF_I32]], %[[NUM_ELEMS]] : (!emitc.size_t, index) -> !emitc.size_t
// CHECK: %[[MALLOC_PTR:.*]] = call_opaque "malloc"(%[[TOTAL_BYTES]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
@@ -92,7 +92,7 @@ func.func private @memref_alloc_store_rank0(%v : i32) {
// CHECK-LABEL: emitc.func private @memref_alloc_load_rank0() -> i32
func.func private @memref_alloc_load_rank0() -> i32 {
- // CHECK: %[[SIZEOF_I32:.*]] = call_opaque "sizeof"() <{args = [i32]}> : () -> !emitc.size_t
+ // CHECK: %[[SIZEOF_I32:.*]] = call_opaque "sizeof"() <args = [i32]> : () -> !emitc.size_t
// CHECK: %[[NUM_ELEMS:.*]] = "emitc.constant"() <{value = 1 : index}> : () -> index
// CHECK: %[[TOTAL_BYTES:.*]] = mul %[[SIZEOF_I32]], %[[NUM_ELEMS]] : (!emitc.size_t, index) -> !emitc.size_t
// CHECK: %[[MALLOC_PTR:.*]] = call_opaque "malloc"(%[[TOTAL_BYTES]]) : (!emitc.size_t) -> !emitc.ptr<!emitc.opaque<"void">>
diff --git a/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-copy.mlir b/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-copy.mlir
index 6105521d9326d..828aa68ea9f1c 100644
--- a/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-copy.mlir
+++ b/mlir/test/Conversion/MemRefToEmitC/memref-to-emitc-copy.mlir
@@ -21,7 +21,7 @@ func.func @copying(%arg0 : memref<9x4x5x7xf32>, %arg1 : memref<9x4x5x7xf32>) {
// CHECK: %[[VAL_1:.*]] = "emitc.constant"() <{value = 0 : index}> : () -> index
// CHECK: %[[SUBSCRIPT_1:.*]] = emitc.subscript %[[UNREALIZED_CONVERSION_CAST_0]]{{\[}}%[[VAL_1]], %[[VAL_1]], %[[VAL_1]], %[[VAL_1]]] : (!emitc.array<9x4x5x7xf32>, index, index, index, index) -> !emitc.lvalue<f32>
// CHECK: %[[ADDRESS_OF_1:.*]] = emitc.address_of %[[SUBSCRIPT_1]] : !emitc.lvalue<f32>
-// CHECK: %[[CALL_OPAQUE_0:.*]] = emitc.call_opaque "sizeof"() <{args = [f32]}> : () -> !emitc.size_t
+// CHECK: %[[CALL_OPAQUE_0:.*]] = emitc.call_opaque "sizeof"() <args = [f32]> : () -> !emitc.size_t
// CHECK: %[[VAL_2:.*]] = "emitc.constant"() <{value = 1260 : index}> : () -> index
// CHECK: %[[MUL_0:.*]] = emitc.mul %[[CALL_OPAQUE_0]], %[[VAL_2]] : (!emitc.size_t, index) -> !emitc.size_t
// CHECK: emitc.call_opaque "memcpy"(%[[ADDRESS_OF_1]], %[[ADDRESS_OF_0]], %[[MUL_0]]) : (!emitc.ptr<f32>, !emitc.ptr<f32>, !emitc.size_t) -> ()
diff --git a/mlir/test/Conversion/MemRefToEmitC/memref-with-custom-types.mlir b/mlir/test/Conversion/MemRefToEmitC/memref-with-custom-types.mlir
index 4cc874633d456..bdb41baa4f743 100644
--- a/mlir/test/Conversion/MemRefToEmitC/memref-with-custom-types.mlir
+++ b/mlir/test/Conversion/MemRefToEmitC/memref-with-custom-types.mlir
@@ -5,7 +5,7 @@
// CHECK-LABEL: emitc.func @alloc_with_custom_element_type()
func.func @alloc_with_custom_element_type() {
- // CHECK: call_opaque "sizeof"() <{args = [!emitc.opaque<"TestElementT">]}> : () -> !emitc.size_t
+ // CHECK: call_opaque "sizeof"() <args = [!emitc.opaque<"TestElementT">]> : () -> !emitc.size_t
// CHECK: cast
// CHECK-SAME: !emitc.ptr<!emitc.opaque<"void">> to !emitc.ptr<!emitc.opaque<"TestElementT">>
%0 = memref.alloc() : memref<10x!test.memref_element>
@@ -43,4 +43,3 @@ func.func @load_with_custom_element_type(%i: index) -> !test.memref_element {
%v = memref.load %alloc[%i] : memref<4x!test.memref_element>
return %v : !test.memref_element
}
-
diff --git a/mlir/test/Dialect/EmitC/member_call_opaque.mlir b/mlir/test/Dialect/EmitC/member_call_opaque.mlir
index 5971d7826b270..bc4e0d55ddb48 100644
--- a/mlir/test/Dialect/EmitC/member_call_opaque.mlir
+++ b/mlir/test/Dialect/EmitC/member_call_opaque.mlir
@@ -19,14 +19,14 @@ func.func @member_call_template_args(%arg0 : !emitc.opaque<"MyClass">) {
return
}
// CHECK-LABEL: func @member_call_template_args
-// CHECK: emitc.member_call_opaque %arg0 "method"() <{template_args = [i32]}> : !emitc.opaque<"MyClass">, () -> i32
+// CHECK: emitc.member_call_opaque %arg0 "method"() <template_args = [i32]> : !emitc.opaque<"MyClass">, () -> i32
func.func @member_call_reorder(%arg0 : !emitc.opaque<"MyClass">, %arg1 : i32, %arg2 : i32) {
%0 = emitc.member_call_opaque %arg0 "method" (%arg1, %arg2) <{args = [1 : index, 0 : index]}> : !emitc.opaque<"MyClass">, (i32, i32) -> i32
return
}
// CHECK-LABEL: func @member_call_reorder
-// CHECK: emitc.member_call_opaque %arg0 "method"(%arg1, %arg2) <{args = [1 : index, 0 : index]}> : !emitc.opaque<"MyClass">, (i32, i32) -> i32
+// CHECK: emitc.member_call_opaque %arg0 "method"(%arg1, %arg2) <args = [1 : index, 0 : index]> : !emitc.opaque<"MyClass">, (i32, i32) -> i32
func.func @member_call_lvalue_arg(%arg0 : !emitc.opaque<"MyClass">, %arg1 : !emitc.lvalue<i32>) {
%0 = emitc.member_call_opaque %arg0 "method" (%arg1) : !emitc.opaque<"MyClass">, (!emitc.lvalue<i32>) -> i32
diff --git a/mlir/test/IR/enum-attr-roundtrip.mlir b/mlir/test/IR/enum-attr-roundtrip.mlir
index d02776e226c80..113b5f07ee6f3 100644
--- a/mlir/test/IR/enum-attr-roundtrip.mlir
+++ b/mlir/test/IR/enum-attr-roundtrip.mlir
@@ -44,14 +44,14 @@ func.func @test_enum_prop() -> () {
// CHECK: test.op_with_enum_prop first
"test.op_with_enum_prop"() <{value = 0 : i32}> {} : () -> ()
- // CHECK: test.op_with_enum_prop_attr_form <{value = 0 : i32}>
+ // CHECK: test.op_with_enum_prop_attr_form <value = first>
test.op_with_enum_prop_attr_form <{value = 0 : i32}>
- // CHECK: test.op_with_enum_prop_attr_form <{value = 1 : i32}>
+ // CHECK: test.op_with_enum_prop_attr_form <value = second>
test.op_with_enum_prop_attr_form <{value = #test<enum second>}>
- // CHECK: test.op_with_enum_prop_attr_form_always <{value = #test<enum first>}>
+ // CHECK: test.op_with_enum_prop_attr_form_always <value = first>
test.op_with_enum_prop_attr_form_always <{value = #test<enum first>}>
- // CHECK: test.op_with_enum_prop_attr_form_always <{value = #test<enum second>}
+ // CHECK: test.op_with_enum_prop_attr_form_always <value = second>
test.op_with_enum_prop_attr_form_always <{value = #test<enum second>}>
return
diff --git a/mlir/test/IR/properties.mlir b/mlir/test/IR/properties.mlir
index 64548e41dc111..de37707a79d4d 100644
--- a/mlir/test/IR/properties.mlir
+++ b/mlir/test/IR/properties.mlir
@@ -14,7 +14,7 @@ test.with_properties a = 32, b = "foo", c = "bar", flag = true, array = [1, 2, 3
test.with_nice_properties "foo bar" is -3
// CHECK: test.with_wrapped_properties
-// CHECK-SAME: <{prop = "content for properties"}>{{$}}
+// CHECK-SAME: <prop = "content for properties">{{$}}
// GENERIC: "test.with_wrapped_properties"()
// GENERIC-SAME: <{prop = "content for properties"}> : () -> ()
test.with_wrapped_properties <{prop = "content for properties"}>
@@ -28,44 +28,50 @@ test.empty_properties
// GENERIC: "test.empty_properties"()
test.empty_properties <>
-// The key-value spelling uses the custom parsers for both attributes and
-// properties. Until the custom printer is enabled, it round-trips to the
-// generic DictionaryAttr spelling.
-// CHECK: test.with_custom_prop_dict <{attr = 1 : i32, prop = 2 : i64}>
+// The key-value spelling uses the custom parsers and printers for both
+// attributes and properties.
+// CHECK: test.with_custom_prop_dict <prop = 2, attr = 1>
// GENERIC: "test.with_custom_prop_dict"()
// GENERIC-SAME: <{attr = 1 : i32, defaulted = 42 : i64, prop = 2 : i64, unit = false}>
test.with_custom_prop_dict <attr = 1, prop = 2>
// The generic DictionaryAttr spelling remains accepted for compatibility.
-// CHECK: test.with_custom_prop_dict <{attr = 3 : i32, prop = 4 : i64}>
+// CHECK: test.with_custom_prop_dict <prop = 4, attr = 3>
// GENERIC: "test.with_custom_prop_dict"()
// GENERIC-SAME: <{attr = 3 : i32, defaulted = 42 : i64, prop = 4 : i64, unit = false}>
test.with_custom_prop_dict <{attr = 3 : i32, prop = 4 : i64}>
// Entries are order-independent, and optional/default-valued entries use
// their custom parsers when present.
-// CHECK: test.with_custom_prop_dict <{attr = 5 : i32, defaulted = 43 : i64, optional = "set", prop = 6 : i64}>
+// CHECK: test.with_custom_prop_dict <prop = 6, defaulted = 43, attr = 5, optional = "set">
// GENERIC: "test.with_custom_prop_dict"()
// GENERIC-SAME: <{attr = 5 : i32, defaulted = 43 : i64, optional = "set", prop = 6 : i64, unit = false}>
test.with_custom_prop_dict <optional = "set", defaulted = 43, prop = 6, attr = 5>
// A field name that is also the start of an attribute must not be consumed by
// the legacy DictionaryAttr compatibility probe.
-// CHECK: test.with_custom_prop_dict <{attr = 7 : i32, prop = 8 : i64, unit}>
+// CHECK: test.with_custom_prop_dict <prop = 8, unit = unit, attr = 7>
// GENERIC: "test.with_custom_prop_dict"()
// GENERIC-SAME: <{attr = 7 : i32, defaulted = 42 : i64, prop = 8 : i64, unit}>
test.with_custom_prop_dict <unit = unit, attr = 7, prop = 8>
+// Inherent attributes use their custom assembly printer in the key-value
+// spelling. Optional enum attributes compile and are omitted when absent.
+// CHECK: test.with_custom_attr_prop_dict <prop = 9, attr = first>
+test.with_custom_attr_prop_dict <attr = first, prop = 9>
+// CHECK: test.with_custom_attr_prop_dict <prop = 10, attr = first, optionalAttr = second>
+test.with_custom_attr_prop_dict <optionalAttr = second, prop = 10, attr = first>
+
// Properties bound elsewhere in the assembly format are excluded from the
// key-value list.
-// CHECK: test.with_properties_and_attr 7 <{rhs = 8 : i64}>
+// CHECK: test.with_properties_and_attr 7 <rhs = 8>
// GENERIC: "test.with_properties_and_attr"()
// GENERIC-SAME: <{lhs = 7 : i32, rhs = 8 : i64}>
test.with_properties_and_attr 7 <rhs = 8>
// A property without a usable custom parser falls back to its attribute
// conversion for this compatibility spelling.
-// CHECK: test.with_wrapped_properties <{prop = "custom spelling"}>
+// CHECK: test.with_wrapped_properties <prop = "custom spelling">
// GENERIC: "test.with_wrapped_properties"()
// GENERIC-SAME: <{prop = "custom spelling"}>
test.with_wrapped_properties <prop = "custom spelling">
@@ -89,7 +95,8 @@ test.with_wrapped_array_properties <prop = ["first", "second"]>
// following scalar key also checks that the container does not consume the
// outer comma.
// CHECK: test.with_key_value_parser_boundaries
-// CHECK-SAME: <{maybe = [], maybeEnum = [], next = 9 : i64, specializedMaybe = [7 : i16], specializedValues = array<i32: 3, 4>, values = array<i64: 1, 2>}>
+// CHECK-SAME: <values = array<i64: 1, 2>, maybe = [], maybeEnum = [],
+// CHECK-SAME: specializedValues = [3, 4], specializedMaybe = some<7>, next = 9>
// GENERIC: "test.with_key_value_parser_boundaries"()
// GENERIC-SAME: <{maybe = [], maybeEnum = [], next = 9 : i64, specializedMaybe = [7 : i16], specializedValues = array<i32: 3, 4>, values = array<i64: 1, 2>}>
test.with_key_value_parser_boundaries <specializedValues = [3, 4], specializedMaybe = some<7>, values = array<i64: 1, 2>, maybe = [], maybeEnum = [], next = 9>
@@ -97,7 +104,7 @@ test.with_key_value_parser_boundaries <specializedValues = [3, 4], specializedMa
// A comma-separated bit-enum FieldParser is not compositional with the outer
// list, so prop-dict uses its attribute conversion before parsing another key.
// CHECK: test.op_with_bit_enum_prop_dict
-// CHECK-SAME: <{flags = 3 : i32, next = 9 : i64}>
+// CHECK-SAME: <flags = 3 : i32, next = 9>
// GENERIC: "test.op_with_bit_enum_prop_dict"()
// GENERIC-SAME: <{flags = 3 : i32, next = 9 : i64}>
test.op_with_bit_enum_prop_dict <flags = 3 : i32, next = 9>
@@ -132,7 +139,7 @@ test.variadic_segment_prop %ci64, %ci64 : %ci64 : i64, i64 : i64 end
// `<{...}>`. Without the parser-side fix, re-parsing the CHECK line below
// (which is exactly what the printer emits) fails with "duplicate or unknown
// key 'operandSegmentSizes' in dictionary attribute".
-// CHECK: test.variadic_segment_prop_bulk_type(%[[CI64]], %[[CI64]], %[[CI64]]) : (i64, i64, i64) -> (i64, i64, i64) <{operandSegmentSizes = array<i32: 2, 1>, resultSegmentSizes = array<i32: 2, 1>}>
+// CHECK: test.variadic_segment_prop_bulk_type(%[[CI64]], %[[CI64]], %[[CI64]]) : (i64, i64, i64) -> (i64, i64, i64) <operandSegmentSizes = [2, 1], resultSegmentSizes = [2, 1]>
// GENERIC: "test.variadic_segment_prop_bulk_type"(%[[CI64]], %[[CI64]], %[[CI64]]) <{operandSegmentSizes = array<i32: 2, 1>, resultSegmentSizes = array<i32: 2, 1>}> : (i64, i64, i64) -> (i64, i64, i64)
test.variadic_segment_prop_bulk_type(%ci64, %ci64, %ci64) : (i64, i64, i64) -> (i64, i64, i64) <operandSegmentSizes = [2, 1], resultSegmentSizes = [2, 1]>
@@ -190,7 +197,8 @@ test.with_array_properties ints = [1, 2] strings = ["a", "b"] nested = [[1, 2],
// Tests that DefaultValuedProp is elided from prop-dict when value equals default.
// CHECK: test.op_with_property_predicates
-// CHECK-SAME: <{array = [], more_constrained = 1 : i64, non_empty_constrained = [1], non_empty_unconstrained = [1], scalar = 1 : i64, unconstrained = 0 : i64}>
+// CHECK-SAME: <scalar = 1, more_constrained = 1, array = [],
+// CHECK-SAME: non_empty_unconstrained = [1], non_empty_constrained = [1], unconstrained = 0>
// CHECK-NOT: defaulted
test.op_with_property_predicates <{
scalar = 1 : i64,
@@ -203,8 +211,8 @@ test.op_with_property_predicates <{
// Keyed parsing composes optional and aggregate property parsers with a
// following outer dictionary entry.
// CHECK: test.op_with_property_predicates
+// CHECK-SAME: optional = 2
// CHECK-SAME: array = [3, 4]
-// CHECK-SAME: optional = [2]
test.op_with_property_predicates <
scalar = 1,
optional = 2,
diff --git a/mlir/test/Target/LLVMIR/Import/intrinsic.ll b/mlir/test/Target/LLVMIR/Import/intrinsic.ll
index 5bbb920e423c8..070ffa654129b 100644
--- a/mlir/test/Target/LLVMIR/Import/intrinsic.ll
+++ b/mlir/test/Target/LLVMIR/Import/intrinsic.ll
@@ -582,7 +582,7 @@ define void @trap_intrinsics() {
call void @llvm.trap()
; CHECK: llvm.intr.debugtrap
call void @llvm.debugtrap()
- ; CHECK: llvm.intr.ubsantrap <{failureKind = 1 : i8}>
+ ; CHECK: llvm.intr.ubsantrap <failureKind = 1>
call void @llvm.ubsantrap(i8 1)
ret void
}
diff --git a/mlir/test/lib/Dialect/Test/TestOps.td b/mlir/test/lib/Dialect/Test/TestOps.td
index 0bd7fe401c3c9..2908d743f0ece 100644
--- a/mlir/test/lib/Dialect/Test/TestOps.td
+++ b/mlir/test/lib/Dialect/Test/TestOps.td
@@ -3645,6 +3645,16 @@ def TestOpWithCustomPropDict : TEST_Op<"with_custom_prop_dict"> {
);
}
+def TestOpWithCustomAttrPropDict
+ : TEST_Op<"with_custom_attr_prop_dict"> {
+ let assemblyFormat = "prop-dict attr-dict";
+ let arguments = (ins
+ TestEnumAttr:$attr,
+ OptionalAttr<TestEnumAttr>:$optionalAttr,
+ I64Prop:$prop
+ );
+}
+
def TestOpWithPropertiesAndInferredType
: TEST_Op<"with_properties_and_inferred_type", [
DeclareOpInterfaceMethods<InferTypeOpInterface>
@@ -3842,6 +3852,13 @@ def KeyValueOptionalEnumProperty
def KeyValueSpecializedListProperty
: Property<"::llvm::SmallVector<int32_t>"> {
+ let printer = [{
+ [&]() {
+ $_printer << "[";
+ ::llvm::interleaveComma($_storage, $_printer);
+ $_printer << "]";
+ }()
+ }];
let convertToAttribute =
"return ::mlir::DenseI32ArrayAttr::get($_ctxt, $_storage);";
let convertFromAttribute = [{
@@ -3860,6 +3877,15 @@ def KeyValueSpecializedListProperty
def KeyValueSpecializedOptionalProperty
: Property<"std::optional<int16_t>"> {
+ let printer = [{
+ [&]() {
+ if (!$_storage) {
+ $_printer << "none";
+ return;
+ }
+ $_printer << "some<" << *$_storage << ">";
+ }()
+ }];
let convertToAttribute = [{
if (!$_storage)
return ::mlir::ArrayAttr::get($_ctxt, {});
@@ -4005,6 +4031,7 @@ def PropertiesWithCustomPrint : Property<"PropertiesWithCustomPrint"> {
}
def TestOpWithNiceProperties : TEST_Op<"with_nice_properties"> {
+ let hasCustomPropertiesPrinter = 1;
let assemblyFormat = "prop-dict attr-dict";
let arguments = (ins
PropertiesWithCustomPrint:$prop
@@ -4069,6 +4096,7 @@ def VersionedProperties : Property<"VersionedProperties"> {
}
def TestOpWithVersionedProperties : TEST_Op<"with_versioned_properties"> {
+ let hasCustomPropertiesPrinter = 1;
let assemblyFormat = "prop-dict attr-dict";
let arguments = (ins
VersionedProperties:$prop
diff --git a/mlir/test/mlir-tblgen/op-format-custom-properties-printer.td b/mlir/test/mlir-tblgen/op-format-custom-properties-printer.td
new file mode 100644
index 0000000000000..18743e41feb64
--- /dev/null
+++ b/mlir/test/mlir-tblgen/op-format-custom-properties-printer.td
@@ -0,0 +1,23 @@
+// RUN: mlir-tblgen -gen-op-defs -I %S/../../include %s | FileCheck %s
+
+include "mlir/IR/OpBase.td"
+
+def TestDialect : Dialect {
+ let name = "test";
+ let cppNamespace = "::test";
+}
+
+// A custom properties printer shadows the generated per-field helper.
+// CHECK-NOT: CustomPropertiesPrinterOp::_odsPrintPropertiesAsKeyValueList
+// CHECK: void CustomPropertiesPrinterOp::print
+// CHECK-NOT: CustomPropertiesPrinterOp::_odsPrintPropertiesAsKeyValueList
+def CustomPropertiesPrinterOp : Op<TestDialect, "custom_properties_printer"> {
+ let arguments = (ins I64Attr:$attr);
+ let assemblyFormat = "prop-dict attr-dict";
+ let hasCustomPropertiesPrinter = 1;
+ let extraClassDeclaration = [{
+ void printProperties(::mlir::MLIRContext *, ::mlir::OpAsmPrinter &,
+ const Properties &,
+ ::mlir::ArrayRef<::llvm::StringRef>);
+ }];
+}
diff --git a/mlir/test/mlir-tblgen/op-format.mlir b/mlir/test/mlir-tblgen/op-format.mlir
index 5f44e5cffe675..315b4e57beea0 100644
--- a/mlir/test/mlir-tblgen/op-format.mlir
+++ b/mlir/test/mlir-tblgen/op-format.mlir
@@ -297,13 +297,13 @@ test.format_optional_prop_dict <{a = [], b = 1 : i32}>
// CHECK: test.format_optional_prop_dict {{$}}
test.format_optional_prop_dict <{}>
-// CHECK: test.format_optional_prop_dict <{a = ["foo"]}>
+// CHECK: test.format_optional_prop_dict <a = "foo">
test.format_optional_prop_dict <{a = ["foo"]}>
-// CHECK: test.format_optional_prop_dict <{b = 2 : i32}>
+// CHECK: test.format_optional_prop_dict <b = 2>
test.format_optional_prop_dict <{b = 2 : i32}>
-// CHECK: test.format_optional_prop_dict <{a = ["foo"], b = 2 : i32}>
+// CHECK: test.format_optional_prop_dict <a = "foo", b = 2>
test.format_optional_prop_dict <{a = ["foo"], b = 2 : i32}>
//===----------------------------------------------------------------------===//
@@ -532,15 +532,15 @@ test.format_optional_operand_type(%i64) : i64
// CHECK: test.format_infer_type_variadic_operands(%[[I32]], %[[I32]] : i32, i32) (%[[I64]], %[[I64]] : i64, i64)
%ignored_res13:4 = test.format_infer_type_variadic_operands(%i32, %i32 : i32, i32) (%i64, %i64 : i64, i64)
-// CHECK: test.with_properties_and_attr 16 <{rhs = 16 : i64}>
+// CHECK: test.with_properties_and_attr 16 <rhs = 16>
test.with_properties_and_attr 16 <{rhs = 16 : i64}>
-// CHECK: test.with_properties_and_inferred_type 16 <{packed, rhs = 16 : i64}>
+// CHECK: test.with_properties_and_inferred_type 16 <rhs = 16, packed = unit>
%should_be_i32 = test.with_properties_and_inferred_type 16 <{packed, rhs = 16 : i64}>
// Assert through the verifier that its inferred as i32.
test.format_all_types_match_var %should_be_i32, %i32 : i32
-// CHECK: test.using_property_in_custom_and_other [1, 4, 20] <{other = 16 : i64}>
+// CHECK: test.using_property_in_custom_and_other [1, 4, 20] <other = 16>
test.using_property_in_custom_and_other [1, 4, 20] <{other = 16 : i64}>
//===----------------------------------------------------------------------===//
diff --git a/mlir/tools/mlir-tblgen/OpFormatGen.cpp b/mlir/tools/mlir-tblgen/OpFormatGen.cpp
index 815c51c91dddd..e503482ba5b71 100644
--- a/mlir/tools/mlir-tblgen/OpFormatGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpFormatGen.cpp
@@ -2297,7 +2297,7 @@ static const char *regionSingleBlockImplicitTerminatorPrinterCode = R"(
/// {1}: The name of the enum attributes symbolToString function.
static const char *enumAttrBeginPrinterCode = R"(
{
- auto caseValue = {0}();
+ auto caseValue = {0};
auto caseValueStr = {1}(caseValue);
)";
@@ -2348,6 +2348,129 @@ static void genVariadicSegmentElision(OperationFormat &fmt, Operator &op,
body << " " << elidedStorage << ".push_back(\"resultSegmentSizes\");\n";
}
+static void genEnumAttrPrinter(const NamedAttribute *var, const Operator &op,
+ MethodBody &body, StringRef valueExpression);
+
+/// Generate the key-value printer used by the default `prop-dict` printer.
+static void genKeyValuePropDictPrinter(OperationFormat &fmt, Operator &op,
+ OpClass &opClass) {
+ if (!fmt.hasPropDict || !fmt.useProperties || op.hasCustomPropertiesPrinter())
+ return;
+
+ bool hasPrintableField =
+ !op.getProperties().empty() ||
+ llvm::any_of(
+ op.getAttributes(),
+ [](const auto &attr) { return !attr.attr.isDerivedAttr(); }) ||
+ (op.getTrait("::mlir::OpTrait::AttrSizedOperandSegments") &&
+ fmt.allOperands) ||
+ (op.getTrait("::mlir::OpTrait::AttrSizedResultSegments") &&
+ fmt.allResultTypes);
+ if (!hasPrintableField)
+ return;
+
+ SmallVector<MethodParameter> paramList;
+ paramList.emplace_back("::mlir::MLIRContext *", "_odsContext");
+ paramList.emplace_back("::mlir::OpAsmPrinter &", "_odsPrinter");
+ paramList.emplace_back("const Properties &", "prop");
+ paramList.emplace_back("::mlir::ArrayRef<::llvm::StringRef>", "elidedProps");
+ Method *method = opClass.addStaticMethod(
+ "void", "_odsPrintPropertiesAsKeyValueList", std::move(paramList));
+ MethodBody &body = method->body().indent();
+
+ body << R"decl(
+bool first = true;
+auto printKey = [&](::llvm::StringRef name) {
+ _odsPrinter << (first ? "<" : ", ") << name << " = ";
+ first = false;
+};
+auto shouldPrint = [&](::llvm::StringRef name) {
+ return !::llvm::is_contained(elidedProps, name);
+};
+)decl";
+
+ auto genSegmentSizesPrinter = [&](StringRef name) {
+ body << "if (shouldPrint(\"" << name << "\")) {\n"
+ << " printKey(\"" << name << "\");\n"
+ << " _odsPrinter << \"[\";\n"
+ << " ::llvm::interleaveComma(prop." << name << ", _odsPrinter);\n"
+ << " _odsPrinter << \"]\";\n"
+ << "}\n";
+ };
+ if (op.getTrait("::mlir::OpTrait::AttrSizedOperandSegments") &&
+ fmt.allOperands)
+ genSegmentSizesPrinter("operandSegmentSizes");
+ if (op.getTrait("::mlir::OpTrait::AttrSizedResultSegments") &&
+ fmt.allResultTypes)
+ genSegmentSizesPrinter("resultSegmentSizes");
+
+ for (const NamedProperty &namedProperty : op.getProperties()) {
+ const Property &property = namedProperty.prop;
+ body << "if (shouldPrint(\"" << namedProperty.name << "\")) {\n"
+ << " printKey(\"" << namedProperty.name << "\");\n";
+ FmtContext printerContext;
+ printerContext.addSubst("_printer", "_odsPrinter");
+ printerContext.addSubst("_ctxt", "_odsContext");
+ printerContext.addSubst("_storage", "prop." + namedProperty.name);
+ if (property.usesDefaultParser()) {
+ body << " if constexpr (::mlir::detail::HasKeyValueFieldParser<"
+ "std::remove_cv_t<std::remove_reference_t<decltype(prop."
+ << namedProperty.name << ")>>>::value) {\n"
+ << " " << tgfmt(property.getPrinterCall(), &printerContext)
+ << ";\n"
+ << " } else {\n"
+ << " auto propertyAttr = [&]() -> ::mlir::Attribute {\n";
+ FmtContext conversionContext;
+ conversionContext.addSubst("_ctxt", "_odsContext");
+ conversionContext.addSubst("_storage", "prop." + namedProperty.name);
+ body << tgfmt(property.getConvertToAttributeCall(), &conversionContext)
+ << "\n"
+ << " }();\n"
+ << " _odsPrinter.printAttribute(propertyAttr);\n"
+ << " }\n";
+ } else {
+ body << " " << tgfmt(property.getPrinterCall(), &printerContext)
+ << ";\n";
+ }
+ body << "}\n";
+ }
+
+ for (const NamedAttribute &namedAttr : op.getAttributes()) {
+ if (namedAttr.attr.isDerivedAttr())
+ continue;
+ StringRef name = namedAttr.name;
+ body << "if (shouldPrint(\"" << name << "\")";
+ if (namedAttr.attr.isOptional())
+ body << " && prop." << name;
+ body << ") {\n"
+ << " printKey(\"" << name << "\");\n";
+
+ if (canFormatEnumAttr(&namedAttr)) {
+ FmtContext conversionContext;
+ conversionContext.withSelf("prop." + name);
+ std::string valueExpression = std::string(tgfmt(
+ namedAttr.attr.getConvertFromStorageCall(), &conversionContext));
+ genEnumAttrPrinter(&namedAttr, op, body, valueExpression);
+ } else if (shouldFormatSymbolNameAttr(&namedAttr)) {
+ body << " _odsPrinter.printSymbolName(prop." << name
+ << ".getValue());\n";
+ } else {
+ AttributeVariable attrVariable(&namedAttr);
+ if (attrVariable.getTypeBuilder())
+ body << " _odsPrinter.printAttributeWithoutType(prop." << name
+ << ");\n";
+ else if (attrVariable.shouldBeQualified() ||
+ namedAttr.attr.getStorageType() == "::mlir::Attribute")
+ body << " _odsPrinter.printAttribute(prop." << name << ");\n";
+ else
+ body << " _odsPrinter.printStrippedAttrOrType(prop." << name << ");\n";
+ }
+ body << "}\n";
+ }
+ body << "if (!first)\n"
+ " _odsPrinter << \">\";\n";
+}
+
/// Generate the printer for the 'prop-dict' directive.
static void genPropDictPrinter(OperationFormat &fmt, Operator &op,
MethodBody &body) {
@@ -2586,15 +2709,20 @@ static MethodBody &genTypeOperandPrinter(FormatElement *arg, const Operator &op,
/// Generate the printer for an enum attribute.
static void genEnumAttrPrinter(const NamedAttribute *var, const Operator &op,
- MethodBody &body) {
+ MethodBody &body,
+ StringRef valueExpression = {}) {
Attribute baseAttr = var->attr.getBaseAttr();
const EnumInfo enumInfo(getEnumInfoRecord(baseAttr));
std::vector<EnumCase> cases = enumInfo.getAllCases();
bool dereferenceGetter =
var->attr.isOptional() && !var->attr.hasDefaultValue();
- body << formatv(enumAttrBeginPrinterCode,
- (dereferenceGetter ? "*" : "") + op.getGetterName(var->name),
+ std::string caseValue = valueExpression.empty()
+ ? op.getGetterName(var->name) + "()"
+ : valueExpression.str();
+ if (dereferenceGetter)
+ caseValue = "*(" + caseValue + ")";
+ body << formatv(enumAttrBeginPrinterCode, caseValue,
enumInfo.getSymbolToStringFnName());
// Get a string containing all of the cases that can't be represented with a
@@ -2990,6 +3118,8 @@ void OperationFormat::genPrinter(Operator &op, OpClass &opClass) {
bool shouldEmitSpace = true, lastWasPunctuation = false;
for (FormatElement *element : elements)
genElementPrinter(element, body, op, shouldEmitSpace, lastWasPunctuation);
+
+ genKeyValuePropDictPrinter(*this, op, opClass);
}
//===----------------------------------------------------------------------===//
More information about the llvm-branch-commits
mailing list