[Mlir-commits] [mlir] [MLIR] Reduce generated assembly printer compile time (NFC) (PR #225740)
Mehdi Amini
llvmlistbot at llvm.org
Wed Sep 23 06:02:12 PDT 2026
https://github.com/joker-eph updated https://github.com/llvm/llvm-project/pull/225740
>From edf24c1440134c729c4358031f97703579848451 Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Wed, 23 Sep 2026 04:53:47 -0700
Subject: [PATCH] [mlir] Reduce generated assembly printer compile time
Use fixed arrays for statically elided attributes and combine leading spaces
with literals. Initialize fixed elisions together when runtime elision is
needed; in strict properties mode, print discardable attributes directly.
Move common operand printing out of generated call sites and emit sliced
operand getters to reduce optimizer work in large printers.
For omp.target, the original patch reduced optimized print IR from 3,893 to
2,667 instructions. Its printing benchmark was 2.1% slower. check-mlir
passed before this follow-up.
Assisted-by: Codex
---
mlir/include/mlir/IR/OpImplementation.h | 18 ++++++++
mlir/lib/IR/AsmPrinter.cpp | 9 ++++
mlir/test/mlir-tblgen/op-decl-and-defs.td | 2 +
mlir/test/mlir-tblgen/op-format.td | 38 +++++++++++----
mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp | 9 +++-
mlir/tools/mlir-tblgen/OpFormatGen.cpp | 51 +++++++++++++++++----
6 files changed, 109 insertions(+), 18 deletions(-)
diff --git a/mlir/include/mlir/IR/OpImplementation.h b/mlir/include/mlir/IR/OpImplementation.h
index 63dde74c33d519..45385e06c3c27b 100644
--- a/mlir/include/mlir/IR/OpImplementation.h
+++ b/mlir/include/mlir/IR/OpImplementation.h
@@ -475,6 +475,13 @@ class OpAsmPrinter : public AsmPrinter {
virtual void printOperand(Value value) = 0;
virtual void printOperand(Value value, raw_ostream &os) = 0;
+ /// Print a comma separated range of operation operands out of line to avoid
+ /// instantiating the range iteration in every generated operation printer.
+ void printOperands(OperandRange operands);
+
+ /// Print the types of a comma separated range of operation operands.
+ void printOperandTypes(ValueTypeRange<OperandRange> types);
+
/// Print a comma separated list of operands.
template <typename ContainerType>
void printOperands(const ContainerType &container) {
@@ -562,6 +569,17 @@ inline OpAsmPrinter &operator<<(OpAsmPrinter &p, Value value) {
return p;
}
+inline OpAsmPrinter &operator<<(OpAsmPrinter &p, OperandRange values) {
+ p.printOperands(values);
+ return p;
+}
+
+inline OpAsmPrinter &operator<<(OpAsmPrinter &p,
+ ValueTypeRange<OperandRange> types) {
+ p.printOperandTypes(types);
+ return p;
+}
+
template <typename T,
std::enable_if_t<std::is_convertible<T &, ValueRange>::value &&
!std::is_convertible<T &, Value &>::value,
diff --git a/mlir/lib/IR/AsmPrinter.cpp b/mlir/lib/IR/AsmPrinter.cpp
index f7e269867c9be2..f78d33338904b0 100644
--- a/mlir/lib/IR/AsmPrinter.cpp
+++ b/mlir/lib/IR/AsmPrinter.cpp
@@ -91,6 +91,15 @@ DialectAsmPrinter::~DialectAsmPrinter() = default;
OpAsmPrinter::~OpAsmPrinter() = default;
+void OpAsmPrinter::printOperands(OperandRange operands) {
+ llvm::interleaveComma(operands, getStream(),
+ [this](Value value) { printOperand(value); });
+}
+
+void OpAsmPrinter::printOperandTypes(ValueTypeRange<OperandRange> types) {
+ llvm::interleaveComma(types, *this);
+}
+
void OpAsmPrinter::printFunctionalType(Operation *op) {
auto &os = getStream();
os << '(';
diff --git a/mlir/test/mlir-tblgen/op-decl-and-defs.td b/mlir/test/mlir-tblgen/op-decl-and-defs.td
index 32c4bb88d188be..5b4b0b4bc0a494 100644
--- a/mlir/test/mlir-tblgen/op-decl-and-defs.td
+++ b/mlir/test/mlir-tblgen/op-decl-and-defs.td
@@ -100,6 +100,8 @@ def NS_AOp : NS_Op<"a_op", [IsolatedFromAbove, IsolatedFromAbove]> {
// CHECK: return ::llvm::StringLiteral("test.a_op");
// CHECK: }
// CHECK: ::mlir::Operation::operand_range getODSOperands(unsigned index) {
+// CHECK-NEXT: auto [start, length] = getODSOperandIndexAndLength(index);
+// CHECK-NEXT: return getOperation()->getOperands().slice(start, length);
// CHECK: ::mlir::TypedValue<::mlir::IntegerType> getA() {
// CHECK: ::mlir::Operation::operand_range getB() {
// CHECK: ::mlir::OpOperand &getAMutable() {
diff --git a/mlir/test/mlir-tblgen/op-format.td b/mlir/test/mlir-tblgen/op-format.td
index f7f0c1b212e044..979480c0f8f782 100644
--- a/mlir/test/mlir-tblgen/op-format.td
+++ b/mlir/test/mlir-tblgen/op-format.td
@@ -32,6 +32,7 @@ class TestStrictPropertiesFormat_Op<string fmt, list<Trait> traits = []>
// CHECK: // coverity[ARRAY_VS_SINGLETON]
// CHECK-NEXT: if (parser.resolveOperands(
// CHECK-LABEL: AttrDictContextOperand::print
+// CHECK: ::llvm::ArrayRef<::llvm::StringRef> elidedAttrs;
// CHECK: _odsPrinter.printOptionalAttrDict(_odsAttrs.getDictionary((*this)->getContext()).getValue(), elidedAttrs);
def AttrDictContextOperand : TestFormat_Op<[{
$context `:` type($context) attr-dict
@@ -43,10 +44,32 @@ def AttrDictDefaultInherentAttr : TestFormat_Op<[{
$attr attr-dict
}]>, Arguments<(ins I64Attr:$attr)>;
+// CHECK-LABEL: AttrDictElideDefaultAndFixedAttr::print
+// CHECK: ::llvm::SmallVector<::llvm::StringRef, 2> elidedAttrs = {"used"};
+// CHECK: elidedAttrs.push_back("attr");
+def AttrDictElideDefaultAndFixedAttr : TestFormat_Op<"$used attr-dict">,
+ Arguments<(ins I64Attr:$used,
+ DefaultValuedStrAttr<StrAttr, "default">:$attr)>;
+
+// CHECK-LABEL: AttrDictElideDefaultAttr::print
+// CHECK: ::llvm::SmallVector<::llvm::StringRef, 2> elidedAttrs = {};
+// CHECK: if(attr && (attr ==
+// CHECK: elidedAttrs.push_back("attr");
+def AttrDictElideDefaultAttr : TestFormat_Op<"attr-dict">,
+ Arguments<(ins DefaultValuedStrAttr<StrAttr, "default">:$attr)>;
+
+// CHECK-LABEL: AttrDictStrictDefaultAttr::print
+// CHECK-NOT: elidedAttrs
+// CHECK: _odsPrinter.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
+def AttrDictStrictDefaultAttr : TestStrictPropertiesFormat_Op<
+ "prop-dict attr-dict">,
+ Arguments<(ins DefaultValuedStrAttr<StrAttr, "default">:$attr)>;
+
// CHECK-LABEL: AttrDictStrictInferredSegmentAttr::parse
// CHECK: result.getOrAddProperties<AttrDictStrictInferredSegmentAttr::Properties>().segment_sizes = parser.getBuilder().getDenseI32ArrayAttr(inputsOperandGroupSizes);
// CHECK-LABEL: AttrDictStrictInferredSegmentAttr::print
-// CHECK: elidedAttrs.push_back("segment_sizes");
+// CHECK-NOT: elidedAttrs
+// CHECK: _odsPrinter.printOptionalAttrDict((*this)->getDiscardableAttrDictionary().getValue());
def AttrDictStrictInferredSegmentAttr : TestStrictPropertiesFormat_Op<[{
custom<Foo>($inputs, type($inputs)) attr-dict
}]>, Arguments<(ins VariadicOfVariadic<I64, "segment_sizes">:$inputs,
@@ -85,6 +108,7 @@ def AttrDictStrictPropDictInherentAttr : TestStrictPropertiesFormat_Op<[{
// CHECK: parseFoo({{.*}}, parser.getBuilder().getI1Type())
// CHECK-LABEL: CustomStringLiteralA::print
// CHECK: printFoo({{.*}}, ::mlir::Builder(getContext()).getI1Type())
+// CHECK: ::llvm::ArrayRef<::llvm::StringRef> elidedAttrs;
def CustomStringLiteralA : TestFormat_Op<[{
custom<Foo>("$_builder.getI1Type()") attr-dict
}]>;
@@ -329,7 +353,7 @@ def LegacyUnquotedBitEnumOp : TestFormat_Op<"$attr attr-dict">,
// CHECK: parser.parseOptionalOperand
// CHECK-LABEL: OptionalGroupA::print
// CHECK: if (!getA())
-// CHECK-NEXT: odsPrinter << ' ' << "?";
+// CHECK-NEXT: odsPrinter << " ?";
// CHECK-NEXT: else
// CHECK: odsPrinter << value;
def OptionalGroupA : TestFormat_Op<[{
@@ -343,9 +367,9 @@ def OptionalGroupA : TestFormat_Op<[{
// CHECK: parser.parseKeyword("bar")
// CHECK-LABEL: OptionalGroupB::print
// CHECK: if (!(getAAttr() && getAAttr() != ((false) ? ::mlir::OpBuilder((*this)->getContext()).getUnitAttr() : nullptr)))
-// CHECK-NEXT: odsPrinter << ' ' << "foo"
+// CHECK-NEXT: odsPrinter << " foo"
// CHECK-NEXT: else
-// CHECK-NEXT: odsPrinter << ' ' << "bar"
+// CHECK-NEXT: odsPrinter << " bar"
def OptionalGroupB : TestFormat_Op<[{
(`foo`) : (`bar` $a^)? attr-dict
}]>, Arguments<(ins UnitAttr:$a)>;
@@ -357,8 +381,7 @@ def OptionalGroupB : TestFormat_Op<[{
// CHECK-NEXT: odsPrinter << ' ';
// CHECK-NEXT: odsPrinter.printAttributeWithoutType(getAAttr());
// CHECK-NEXT: }
-// CHECK: elidedAttrs.push_back("a");
-// CHECK-NOT: elidedAttrs.push_back("a");
+// CHECK: static constexpr ::llvm::StringRef elidedAttrs[] = {"a"};
// CHECK: _odsPrinter.printOptionalAttrDict
def OptionalGroupC : TestFormat_Op<[{
($a^)? attr-dict
@@ -368,8 +391,7 @@ def OptionalGroupC : TestFormat_Op<[{
// CHECK: elidedProps.push_back("a");
// CHECK-NOT: elidedProps.push_back("a");
// CHECK: printProperties
-// CHECK: elidedAttrs.push_back("a");
-// CHECK-NOT: elidedAttrs.push_back("a");
+// CHECK: static constexpr ::llvm::StringRef elidedAttrs[] = {"a"};
// CHECK: _odsPrinter.printOptionalAttrDict
def OptionalGroupCPropDict : TestFormat_Op<[{
($a^)? prop-dict attr-dict
diff --git a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
index aa9ff159903bc6..95cb292bbe7fc6 100644
--- a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
@@ -2108,8 +2108,13 @@ generateNamedOperandGetters(const Operator &op, Class &opClass,
MethodParameter("unsigned", "index"));
ERROR_IF_PRUNED(m, "getODSOperands", op);
auto &body = m->body();
- body << formatv(valueRangeReturnCode, rangeBeginCall,
- "getODSOperandIndexAndLength(index)");
+ if (isGenericAdaptorBase) {
+ body << formatv(valueRangeReturnCode, rangeBeginCall,
+ "getODSOperandIndexAndLength(index)");
+ } else {
+ body << " auto [start, length] = getODSOperandIndexAndLength(index);\n"
+ " return getOperation()->getOperands().slice(start, length);\n";
+ }
// Then we emit nicer named getter methods by redirecting to the "sink" getter
// method.
diff --git a/mlir/tools/mlir-tblgen/OpFormatGen.cpp b/mlir/tools/mlir-tblgen/OpFormatGen.cpp
index becdcc825d5470..83982ca27912c8 100644
--- a/mlir/tools/mlir-tblgen/OpFormatGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpFormatGen.cpp
@@ -2723,14 +2723,49 @@ static void genPropDictPrinter(OperationFormat &fmt, Operator &op,
/// Generate the printer for the 'attr-dict' directive.
static void genAttrDictPrinter(OperationFormat &fmt, Operator &op,
MethodBody &body, bool withKeyword) {
- body << " ::llvm::SmallVector<::llvm::StringRef, 2> elidedAttrs;\n";
-
- genVariadicSegmentElision(fmt, op, body, "elidedAttrs");
+ // TODO: Remove the non-strict path when strict properties in assembly
+ // formats are always enabled.
+ if (fmt.useStrictPropertiesInAssemblyFormat) {
+ // Inherent attributes are printed by their bindings or prop-dict.
+ body << " _odsPrinter.printOptionalAttrDict"
+ << (withKeyword ? "WithKeyword" : "")
+ << "((*this)->getDiscardableAttrDictionary().getValue());\n";
+ return;
+ }
+ SmallVector<StringRef> fixedElidedAttrs;
+ if (!fmt.allOperands &&
+ op.getTrait("::mlir::OpTrait::AttrSizedOperandSegments"))
+ fixedElidedAttrs.push_back("operandSegmentSizes");
+ if (!fmt.allResultTypes &&
+ op.getTrait("::mlir::OpTrait::AttrSizedResultSegments"))
+ fixedElidedAttrs.push_back("resultSegmentSizes");
for (StringRef key : fmt.inferredAttributes.keys())
- body << " elidedAttrs.push_back(\"" << key << "\");\n";
+ fixedElidedAttrs.push_back(key);
for (const NamedAttribute *attr : fmt.usedAttributes)
- body << " elidedAttrs.push_back(\"" << attr->name << "\");\n";
+ fixedElidedAttrs.push_back(attr->name);
+
+ bool hasConditionalElision =
+ llvm::any_of(op.getAttributes(), [&](const NamedAttribute &namedAttr) {
+ return !fmt.usedAttributes.contains(&namedAttr) &&
+ !namedAttr.attr.isDerivedAttr() &&
+ namedAttr.attr.hasDefaultValue();
+ });
+ if (hasConditionalElision) {
+ body << " ::llvm::SmallVector<::llvm::StringRef, 2> elidedAttrs = {";
+ llvm::interleaveComma(fixedElidedAttrs, body, [&](StringRef name) {
+ body << "\"" << name << "\"";
+ });
+ body << "};\n";
+ } else if (fixedElidedAttrs.empty()) {
+ body << " ::llvm::ArrayRef<::llvm::StringRef> elidedAttrs;\n";
+ } else {
+ body << " static constexpr ::llvm::StringRef elidedAttrs[] = {";
+ llvm::interleaveComma(fixedElidedAttrs, body, [&](StringRef name) {
+ body << "\"" << name << "\"";
+ });
+ body << "};\n";
+ }
// Add code to check attributes for equality with their default values.
// Default-valued attributes will not be printed when their value matches the
@@ -2779,12 +2814,12 @@ static void genAttrDictPrinter(OperationFormat &fmt, Operator &op,
/// the previous element was a punctuation literal.
static void genLiteralPrinter(StringRef value, MethodBody &body,
bool &shouldEmitSpace, bool &lastWasPunctuation) {
- body << " _odsPrinter";
+ body << " _odsPrinter << \"";
// Don't insert a space for certain punctuation.
if (shouldEmitSpace && shouldEmitSpaceBefore(value, lastWasPunctuation))
- body << " << ' '";
- body << " << \"" << value << "\";\n";
+ body << ' ';
+ body << value << "\";\n";
// Insert a space after certain literals.
shouldEmitSpace =
More information about the Mlir-commits
mailing list