[mlir][OpFormat] Add support for emitting newlines from the custom format of an operation

This revision adds a new `printNewline` hook to OpAsmPrinter that allows for printing a newline within the custom format of an operation, that is then indented to the start of the operation. Support for the declarative assembly format is also added, in the form of a `\n` literal.

Differential Revision: https://reviews.llvm.org/D93151
This commit is contained in:
River Riddle 2020-12-14 11:53:34 -08:00
parent 0936655bac
commit c234b65cef
7 changed files with 93 additions and 19 deletions

View File

@ -646,6 +646,30 @@ The following are the set of valid punctuation:
`:`, `,`, `=`, `<`, `>`, `(`, `)`, `{`, `}`, `[`, `]`, `->`, `?`, `+`, `*` `:`, `,`, `=`, `<`, `>`, `(`, `)`, `{`, `}`, `[`, `]`, `->`, `?`, `+`, `*`
The following are valid whitespace punctuation:
`\n`, ` `
The `\n` literal emits a newline an indents to the start of the operation. An
example is shown below:
```tablegen
let assemblyFormat = [{
`{` `\n` ` ` ` ` `this_is_on_a_newline` `\n` `}` attr-dict
}];
```
```mlir
%results = my.operation {
this_is_on_a_newline
}
```
An empty literal \`\` may be used to remove a space that is inserted implicitly
after certain literal elements, such as `)`/`]`/etc. For example, "`]`" may
result in an output of `]` it is not the last element in the format. "`]` \`\`"
would trim the trailing space in this situation.
#### Variables #### Variables
A variable is an entity that has been registered on the operation itself, i.e. A variable is an entity that has been registered on the operation itself, i.e.

View File

@ -36,6 +36,10 @@ public:
virtual ~OpAsmPrinter(); virtual ~OpAsmPrinter();
virtual raw_ostream &getStream() const = 0; virtual raw_ostream &getStream() const = 0;
/// Print a newline and indent the printer to the start of the current
/// operation.
virtual void printNewline() = 0;
/// Print implementations for various things an operation contains. /// Print implementations for various things an operation contains.
virtual void printOperand(Value value) = 0; virtual void printOperand(Value value) = 0;
virtual void printOperand(Value value, raw_ostream &os) = 0; virtual void printOperand(Value value, raw_ostream &os) = 0;

View File

@ -429,6 +429,7 @@ private:
/// The following are hooks of `OpAsmPrinter` that are not necessary for /// The following are hooks of `OpAsmPrinter` that are not necessary for
/// determining potential aliases. /// determining potential aliases.
void printAffineMapOfSSAIds(AffineMapAttr, ValueRange) override {} void printAffineMapOfSSAIds(AffineMapAttr, ValueRange) override {}
void printNewline() override {}
void printOperand(Value) override {} void printOperand(Value) override {}
void printOperand(Value, raw_ostream &os) override { void printOperand(Value, raw_ostream &os) override {
// Users expect the output string to have at least the prefixed % to signal // Users expect the output string to have at least the prefixed % to signal
@ -2218,6 +2219,13 @@ public:
/// Return the current stream of the printer. /// Return the current stream of the printer.
raw_ostream &getStream() const override { return os; } raw_ostream &getStream() const override { return os; }
/// Print a newline and indent the printer to the start of the current
/// operation.
void printNewline() override {
os << newLine;
os.indent(currentIndent);
}
/// Print the given type. /// Print the given type.
void printType(Type type) override { ModulePrinter::printType(type); } void printType(Type type) override { ModulePrinter::printType(type); }

View File

@ -1393,7 +1393,8 @@ def AsmDialectInterfaceOp : TEST_Op<"asm_dialect_interface_op"> {
def FormatLiteralOp : TEST_Op<"format_literal_op"> { def FormatLiteralOp : TEST_Op<"format_literal_op"> {
let assemblyFormat = [{ let assemblyFormat = [{
`keyword_$.` `->` `:` `,` `=` `<` `>` `(` `)` `[` `]` `` `(` ` ` `)` `?` `+` `*` attr-dict `keyword_$.` `->` `:` `,` `=` `<` `>` `(` `)` `[` `]` `` `(` ` ` `)`
`?` `+` `*` `{` `\n` `}` attr-dict
}]; }];
} }

View File

@ -309,7 +309,7 @@ def LiteralInvalidB : TestFormat_Op<"literal_invalid_b", [{
}]>; }]>;
// CHECK-NOT: error // CHECK-NOT: error
def LiteralValid : TestFormat_Op<"literal_valid", [{ def LiteralValid : TestFormat_Op<"literal_valid", [{
`_` `:` `,` `=` `<` `>` `(` `)` `[` `]` `?` `+` `*` ` ` `` `->` `abc$._` `_` `:` `,` `=` `<` `>` `(` `)` `[` `]` `?` `+` `*` ` ` `` `->` `\n` `abc$._`
attr-dict attr-dict
}]>; }]>;

View File

@ -7,8 +7,10 @@
// CHECK: %[[MEMREF:.*]] = // CHECK: %[[MEMREF:.*]] =
%memref = "foo.op"() : () -> (memref<1xf64>) %memref = "foo.op"() : () -> (memref<1xf64>)
// CHECK: test.format_literal_op keyword_$. -> :, = <> () []( ) ? + * {foo.some_attr} // CHECK: test.format_literal_op keyword_$. -> :, = <> () []( ) ? + * {
test.format_literal_op keyword_$. -> :, = <> () []( ) ? + * {foo.some_attr} // CHECK-NEXT: } {foo.some_attr}
test.format_literal_op keyword_$. -> :, = <> () []( ) ? + * {
} {foo.some_attr}
// CHECK: test.format_attr_op 10 // CHECK: test.format_attr_op 10
// CHECK-NOT: {attr // CHECK-NOT: {attr

View File

@ -58,7 +58,8 @@ public:
/// This element is a literal. /// This element is a literal.
Literal, Literal,
/// This element prints or omits a space. It is ignored by the parser. /// This element is a whitespace.
Newline,
Space, Space,
/// This element is an variable value. /// This element is an variable value.
@ -296,14 +297,35 @@ bool LiteralElement::isValidLiteral(StringRef value) {
} }
//===----------------------------------------------------------------------===// //===----------------------------------------------------------------------===//
// SpaceElement // WhitespaceElement
namespace { namespace {
/// This class represents a whitespace element, e.g. newline or space. It's a
/// literal that is printed but never parsed.
class WhitespaceElement : public Element {
public:
WhitespaceElement(Kind kind) : Element{kind} {}
static bool classof(const Element *element) {
Kind kind = element->getKind();
return kind == Kind::Newline || kind == Kind::Space;
}
};
/// This class represents an instance of a newline element. It's a literal that
/// prints a newline. It is ignored by the parser.
class NewlineElement : public WhitespaceElement {
public:
NewlineElement() : WhitespaceElement(Kind::Newline) {}
static bool classof(const Element *element) {
return element->getKind() == Kind::Newline;
}
};
/// This class represents an instance of a space element. It's a literal that /// This class represents an instance of a space element. It's a literal that
/// prints or omits printing a space. It is ignored by the parser. /// prints or omits printing a space. It is ignored by the parser.
class SpaceElement : public Element { class SpaceElement : public WhitespaceElement {
public: public:
SpaceElement(bool value) : Element{Kind::Space}, value(value) {} SpaceElement(bool value) : WhitespaceElement(Kind::Space), value(value) {}
static bool classof(const Element *element) { static bool classof(const Element *element) {
return element->getKind() == Kind::Space; return element->getKind() == Kind::Space;
} }
@ -347,7 +369,8 @@ private:
std::vector<std::unique_ptr<Element>> elements; std::vector<std::unique_ptr<Element>> elements;
/// The index of the element that acts as the anchor for the optional group. /// The index of the element that acts as the anchor for the optional group.
unsigned anchor; unsigned anchor;
/// The index of the first element that is parsed (is not a SpaceElement). /// The index of the first element that is parsed (is not a
/// WhitespaceElement).
unsigned parseStart; unsigned parseStart;
}; };
} // end anonymous namespace } // end anonymous namespace
@ -1098,8 +1121,8 @@ void OperationFormat::genElementParser(Element *element, OpMethodBody &body,
genLiteralParser(literal->getLiteral(), body); genLiteralParser(literal->getLiteral(), body);
body << ")\n return ::mlir::failure();\n"; body << ")\n return ::mlir::failure();\n";
/// Spaces. /// Whitespaces.
} else if (isa<SpaceElement>(element)) { } else if (isa<WhitespaceElement>(element)) {
// Nothing to parse. // Nothing to parse.
/// Arguments. /// Arguments.
@ -1620,6 +1643,11 @@ void OperationFormat::genElementPrinter(Element *element, OpMethodBody &body,
return genLiteralPrinter(literal->getLiteral(), body, shouldEmitSpace, return genLiteralPrinter(literal->getLiteral(), body, shouldEmitSpace,
lastWasPunctuation); lastWasPunctuation);
// Emit a whitespace element.
if (NewlineElement *newline = dyn_cast<NewlineElement>(element)) {
body << " p.printNewline();\n";
return;
}
if (SpaceElement *space = dyn_cast<SpaceElement>(element)) if (SpaceElement *space = dyn_cast<SpaceElement>(element))
return genSpacePrinter(space->getValue(), body, shouldEmitSpace, return genSpacePrinter(space->getValue(), body, shouldEmitSpace,
lastWasPunctuation); lastWasPunctuation);
@ -2272,9 +2300,10 @@ LogicalResult FormatParser::verifyAttributes(
for (auto &nextItPair : iteratorStack) { for (auto &nextItPair : iteratorStack) {
ElementsIterT nextIt = nextItPair.first, nextE = nextItPair.second; ElementsIterT nextIt = nextItPair.first, nextE = nextItPair.second;
for (; nextIt != nextE; ++nextIt) { for (; nextIt != nextE; ++nextIt) {
// Skip any trailing spaces, attribute dictionaries, or optional groups. // Skip any trailing whitespace, attribute dictionaries, or optional
if (isa<SpaceElement>(*nextIt) || isa<AttrDictDirective>(*nextIt) || // groups.
isa<OptionalElement>(*nextIt)) if (isa<WhitespaceElement>(*nextIt) ||
isa<AttrDictDirective>(*nextIt) || isa<OptionalElement>(*nextIt))
continue; continue;
// We are only interested in `:` literals. // We are only interested in `:` literals.
@ -2600,6 +2629,11 @@ LogicalResult FormatParser::parseLiteral(std::unique_ptr<Element> &element) {
element = std::make_unique<SpaceElement>(!value.empty()); element = std::make_unique<SpaceElement>(!value.empty());
return ::mlir::success(); return ::mlir::success();
} }
// The parsed literal is a newline element.
if (value == "\\n") {
element = std::make_unique<NewlineElement>();
return ::mlir::success();
}
// Check that the parsed literal is valid. // Check that the parsed literal is valid.
if (!LiteralElement::isValidLiteral(value)) if (!LiteralElement::isValidLiteral(value))
@ -2635,8 +2669,9 @@ LogicalResult FormatParser::parseOptional(std::unique_ptr<Element> &element,
// The first parsable element of the group must be able to be parsed in an // The first parsable element of the group must be able to be parsed in an
// optional fashion. // optional fashion.
auto parseBegin = llvm::find_if_not( auto parseBegin = llvm::find_if_not(elements, [](auto &element) {
elements, [](auto &element) { return isa<SpaceElement>(element.get()); }); return isa<WhitespaceElement>(element.get());
});
Element *firstElement = parseBegin->get(); Element *firstElement = parseBegin->get();
if (!isa<AttributeVariable>(firstElement) && if (!isa<AttributeVariable>(firstElement) &&
!isa<LiteralElement>(firstElement) && !isa<LiteralElement>(firstElement) &&
@ -2718,9 +2753,9 @@ LogicalResult FormatParser::parseOptionalChildElement(
// a check here. // a check here.
return ::mlir::success(); return ::mlir::success();
}) })
// Literals, spaces, custom directives, and type directives may be used, // Literals, whitespace, custom directives, and type directives may be
// but they can't anchor the group. // used, but they can't anchor the group.
.Case<LiteralElement, SpaceElement, CustomDirective, .Case<LiteralElement, WhitespaceElement, CustomDirective,
FunctionalTypeDirective, OptionalElement, TypeRefDirective, FunctionalTypeDirective, OptionalElement, TypeRefDirective,
TypeDirective>([&](Element *) { TypeDirective>([&](Element *) {
if (isAnchor) if (isAnchor)