--- /dev/null
+//===- Format.h - Utilities for String Format -------------------*- C++ -*-===//
+//
+// Copyright 2019 The MLIR Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =============================================================================
+//
+// This file declares utilities for formatting strings. They are specially
+// tailored to the needs of TableGen'ing op definitions and rewrite rules,
+// so they are not expected to be used as widely applicable utilities.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef MLIR_TABLEGEN_FORMAT_H_
+#define MLIR_TABLEGEN_FORMAT_H_
+
+#include "mlir/Support/LLVM.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/Support/FormatVariadic.h"
+
+namespace mlir {
+namespace tblgen {
+
+/// Format context containing substitutions for special placeholders.
+///
+/// This context divides special placeholders into two categories: builtin ones
+/// and custom ones.
+///
+/// Builtin placeholders are baked into `FmtContext` and each one of them has a
+/// dedicated setter. They can be used in all dialects. Their names follow the
+/// convention of `$_<name>`. The rationale of the leading underscore is to
+/// avoid confusion and name collision: op arguments/attributes/results are
+/// named as $<name>, and we can potentially support referencing those entities
+/// directly in the format template in the future.
+//
+/// Custom ones are registered by dialect-specific TablGen backends and use the
+/// same unified setter.
+class FmtContext {
+public:
+ // Placeholder kinds
+ enum class PHKind : char {
+ None,
+ Custom, // For custom placeholders
+ Builder, // For the $_builder placeholder
+ Op, // For the $_op placeholder
+ Self, // For the $_self placeholder
+ };
+
+ FmtContext() = default;
+
+ // Setter for custom placeholders
+ FmtContext &addSubst(StringRef placeholder, Twine subst);
+
+ // Setters for builtin placeholders
+ FmtContext &withBuilder(Twine subst);
+ FmtContext &withOp(Twine subst);
+ FmtContext &withSelf(Twine subst);
+
+ Optional<StringRef> getSubstFor(PHKind placeholder) const;
+ Optional<StringRef> getSubstFor(StringRef placeholder) const;
+
+ static PHKind getPlaceHolderKind(StringRef str);
+
+private:
+ struct PHKindInfo : DenseMapInfo<PHKind> {
+ using CharInfo = DenseMapInfo<char>;
+
+ static inline PHKind getEmptyKey() {
+ return static_cast<PHKind>(CharInfo::getEmptyKey());
+ }
+ static inline PHKind getTombstoneKey() {
+ return static_cast<PHKind>(CharInfo::getTombstoneKey());
+ }
+ static unsigned getHashValue(const PHKind &val) {
+ return CharInfo::getHashValue(static_cast<char>(val));
+ }
+
+ static bool isEqual(const PHKind &lhs, const PHKind &rhs) {
+ return lhs == rhs;
+ }
+ };
+
+ llvm::SmallDenseMap<PHKind, std::string, 4, PHKindInfo> builtinSubstMap;
+ llvm::StringMap<std::string> customSubstMap;
+};
+
+/// Struct representing a replacement segment for the formatted string. It can
+/// be a segment of the formatting template (for `Literal`) or a replacement
+/// parameter (for `PositionalPH` and `SpecialPH`).
+struct FmtReplacement {
+ enum class Type { Empty, Literal, PositionalPH, SpecialPH };
+
+ FmtReplacement() = default;
+ explicit FmtReplacement(StringRef literal)
+ : type(Type::Literal), spec(literal) {}
+ FmtReplacement(StringRef spec, size_t index)
+ : type(Type::PositionalPH), spec(spec), index(index) {}
+ FmtReplacement(StringRef spec, FmtContext::PHKind placeholder)
+ : type(Type::SpecialPH), spec(spec), placeholder(placeholder) {}
+
+ Type type = Type::Empty;
+ StringRef spec;
+ size_t index = 0;
+ FmtContext::PHKind placeholder = FmtContext::PHKind::None;
+};
+
+class FmtObjectBase {
+private:
+ static std::pair<FmtReplacement, StringRef> splitFmtSegment(StringRef fmt);
+ static std::vector<FmtReplacement> parseFormatString(StringRef fmt);
+
+protected:
+ // The parameters are stored in a std::tuple, which does not provide runtime
+ // indexing capabilities. In order to enable runtime indexing, we use this
+ // structure to put the parameters into a std::vector. Since the parameters
+ // are not all the same type, we use some type-erasure by wrapping the
+ // parameters in a template class that derives from a non-template superclass.
+ // Essentially, we are converting a std::tuple<Derived<Ts...>> to a
+ // std::vector<Base*>.
+ struct CreateAdapters {
+ template <typename... Ts>
+ std::vector<llvm::detail::format_adapter *> operator()(Ts &... items) {
+ return std::vector<llvm::detail::format_adapter *>{&items...};
+ }
+ };
+
+ StringRef fmt;
+ const FmtContext *context;
+ std::vector<llvm::detail::format_adapter *> adapters;
+ std::vector<FmtReplacement> replacements;
+
+public:
+ FmtObjectBase(StringRef fmt, const FmtContext *ctx, size_t numParams)
+ : fmt(fmt), context(ctx), replacements(parseFormatString(fmt)) {}
+
+ FmtObjectBase(const FmtObjectBase &that) = delete;
+
+ FmtObjectBase(FmtObjectBase &&that)
+ : fmt(std::move(that.fmt)), context(that.context),
+ adapters(), // adapters are initialized by FmtObject
+ replacements(std::move(that.replacements)) {}
+
+ void format(llvm::raw_ostream &s) const;
+
+ std::string str() const {
+ std::string result;
+ llvm::raw_string_ostream s(result);
+ format(s);
+ return s.str();
+ }
+
+ template <unsigned N> SmallString<N> sstr() const {
+ SmallString<N> result;
+ llvm::raw_svector_ostream s(result);
+ format(s);
+ return result;
+ }
+
+ template <unsigned N> operator SmallString<N>() const { return sstr<N>(); }
+
+ operator std::string() const { return str(); }
+};
+
+template <typename Tuple> class FmtObject : public FmtObjectBase {
+ // Storage for the parameter adapters. Since the base class erases the type
+ // of the parameters, we have to own the storage for the parameters here, and
+ // have the base class store type-erased pointers into this tuple.
+ Tuple parameters;
+
+public:
+ FmtObject(StringRef fmt, const FmtContext *ctx, Tuple &¶ms)
+ : FmtObjectBase(fmt, ctx, std::tuple_size<Tuple>::value),
+ parameters(std::move(params)) {
+ adapters.reserve(std::tuple_size<Tuple>::value);
+ adapters = llvm::apply_tuple(CreateAdapters(), parameters);
+ }
+
+ FmtObject(FmtObject const &that) = delete;
+
+ FmtObject(FmtObject &&that)
+ : FmtObjectBase(std::move(that)), parameters(std::move(that.parameters)) {
+ adapters.reserve(that.adapters.size());
+ adapters = llvm::apply_tuple(CreateAdapters(), parameters);
+ }
+};
+
+/// Formats text by substituting placeholders in format string with replacement
+/// parameters.
+///
+/// There are two categories of placeholders accepted, both led by a '$' sign:
+///
+/// 1. Positional placeholder: $[0-9]+
+/// 2. Special placeholder: $[a-zA-Z_][a-zA-Z0-9_]*
+///
+/// Replacement parameters for positional placeholders are supplied as the
+/// `vals` parameter pack with 1:1 mapping. That is, $0 will be replaced by the
+/// first parameter in `vals`, $1 by the second one, and so on. Note that you
+/// can use the positional placeholders in any order and repeat any times, for
+/// example, "$2 $1 $1 $0" is accepted.
+///
+/// Replacement parameters for special placeholders are supplied using the `ctx`
+/// format context.
+///
+/// The `fmt` is recorded as a `StringRef` inside the returned `FmtObject`.
+/// The caller needs to make sure the underlying data is available when the
+/// `FmtObject` is used.
+///
+/// `ctx` accepts a nullptr if there is no special placeholder is used.
+///
+/// If no substitution is provided for a placeholder or any error happens during
+/// format string parsing or replacement, the placeholder will be outputted
+/// as-is with an additional marker '<no-subst-found>', to aid debugging.
+///
+/// To print a '$' literally, escape it with '$$'.
+///
+/// This utility function is inspired by LLVM formatv(), with modifications
+/// specially tailored for TableGen C++ generation usage:
+///
+/// 1. This utility use '$' instead of '{' and '}' for denoting the placeholder
+/// because '{' and '}' are frequently used in C++ code.
+/// 2. This utility does not support format layout because it is rarely needed
+/// in C++ code generation.
+template <typename... Ts>
+inline auto tgfmt(StringRef fmt, const FmtContext *ctx, Ts &&... vals)
+ -> FmtObject<decltype(std::make_tuple(
+ llvm::detail::build_format_adapter(std::forward<Ts>(vals))...))> {
+ using ParamTuple = decltype(std::make_tuple(
+ llvm::detail::build_format_adapter(std::forward<Ts>(vals))...));
+ return FmtObject<ParamTuple>(
+ fmt, ctx,
+ std::make_tuple(
+ llvm::detail::build_format_adapter(std::forward<Ts>(vals))...));
+}
+
+} // end namespace tblgen
+} // end namespace mlir
+
+#endif // MLIR_TABLEGEN_FORMAT_H_
Argument.cpp
Attribute.cpp
Constraint.cpp
+ Format.cpp
Operator.cpp
OpTrait.cpp
Pattern.cpp
--- /dev/null
+//===- Format.cpp - Utilities for String Format ---------------------------===//
+//
+// Copyright 2019 The MLIR Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =============================================================================
+//
+// This file defines utilities for formatting strings. They are specially
+// tailored to the needs of TableGen'ing op definitions and rewrite rules,
+// so they are not expected to be used as widely applicable utilities.
+//
+//===----------------------------------------------------------------------===//
+
+#include "mlir/TableGen/Format.h"
+
+using namespace mlir;
+using namespace mlir::tblgen;
+
+// Marker to indicate an error happened when replacing a placeholder.
+const char *const kMarkerForNoSubst = "<no-subst-found>";
+
+FmtContext &tblgen::FmtContext::addSubst(StringRef placeholder, Twine subst) {
+ customSubstMap[placeholder] = subst.str();
+ return *this;
+}
+
+FmtContext &tblgen::FmtContext::withBuilder(Twine subst) {
+ builtinSubstMap[PHKind::Builder] = subst.str();
+ return *this;
+}
+
+FmtContext &tblgen::FmtContext::withOp(Twine subst) {
+ builtinSubstMap[PHKind::Op] = subst.str();
+ return *this;
+}
+
+FmtContext &tblgen::FmtContext::withSelf(Twine subst) {
+ builtinSubstMap[PHKind::Self] = subst.str();
+ return *this;
+}
+
+Optional<StringRef>
+tblgen::FmtContext::getSubstFor(FmtContext::PHKind placeholder) const {
+ if (placeholder == FmtContext::PHKind::None ||
+ placeholder == FmtContext::PHKind::Custom)
+ return {};
+ auto it = builtinSubstMap.find(placeholder);
+ if (it == builtinSubstMap.end())
+ return {};
+ return StringRef(it->second);
+}
+
+Optional<StringRef>
+tblgen::FmtContext::getSubstFor(StringRef placeholder) const {
+ auto it = customSubstMap.find(placeholder);
+ if (it == customSubstMap.end())
+ return {};
+ return StringRef(it->second);
+}
+
+FmtContext::PHKind tblgen::FmtContext::getPlaceHolderKind(StringRef str) {
+ return llvm::StringSwitch<FmtContext::PHKind>(str)
+ .Case("_builder", FmtContext::PHKind::Builder)
+ .Case("_op", FmtContext::PHKind::Op)
+ .Case("_self", FmtContext::PHKind::Self)
+ .Case("", FmtContext::PHKind::None)
+ .Default(FmtContext::PHKind::Custom);
+}
+
+std::pair<FmtReplacement, StringRef>
+tblgen::FmtObjectBase::splitFmtSegment(StringRef fmt) {
+ size_t begin = fmt.find_first_of('$');
+ if (begin == StringRef::npos) {
+ // No placeholders: the whole format string should be returned as a
+ // literal string.
+ return {FmtReplacement{fmt}, StringRef()};
+ }
+ if (begin != 0) {
+ // The first placeholder is not at the beginning: we can split the format
+ // string into a literal string and the rest.
+ return {FmtReplacement{fmt.substr(0, begin)}, fmt.substr(begin)};
+ }
+
+ // The first placeholder is at the beginning
+
+ if (fmt.size() == 1) {
+ // The whole format string just contains '$': treat as literal.
+ return {FmtReplacement{fmt}, StringRef()};
+ }
+
+ // Allow escaping dollar with '$$'
+ if (fmt[1] == '$') {
+ return {FmtReplacement{fmt.substr(0, 1)}, fmt.substr(2)};
+ }
+
+ // First try to see if it's a positional placeholder, and then handle special
+ // placeholders.
+
+ size_t end = fmt.find_if_not([](char c) { return std::isdigit(c); }, 1);
+ if (end != 1) {
+ // We have a positional placeholder. Parse the index.
+ size_t index = 0;
+ if (fmt.substr(1, end - 1).consumeInteger(0, index)) {
+ llvm_unreachable("invalid replacement sequence index");
+ }
+
+ if (end == StringRef::npos) {
+ // All the remaining characters are part of the positional placeholder.
+ return {FmtReplacement{fmt, index}, StringRef()};
+ }
+ return {FmtReplacement{fmt.substr(0, end), index}, fmt.substr(end)};
+ }
+
+ end = fmt.find_if_not([](char c) { return std::isalnum(c) || c == '_'; }, 1);
+ auto placeholder = FmtContext::getPlaceHolderKind(fmt.substr(1, end - 1));
+ if (end == StringRef::npos) {
+ // All the remaining characters are part of the special placeholder.
+ return {FmtReplacement{fmt, placeholder}, StringRef()};
+ }
+ return {FmtReplacement{fmt.substr(0, end), placeholder}, fmt.substr(end)};
+}
+
+std::vector<FmtReplacement> FmtObjectBase::parseFormatString(StringRef fmt) {
+ std::vector<FmtReplacement> replacements;
+ FmtReplacement repl;
+ while (!fmt.empty()) {
+ std::tie(repl, fmt) = splitFmtSegment(fmt);
+ if (repl.type != FmtReplacement::Type::Empty)
+ replacements.push_back(repl);
+ }
+ return replacements;
+}
+
+void FmtObjectBase::format(raw_ostream &s) const {
+ for (auto &repl : replacements) {
+ if (repl.type == FmtReplacement::Type::Empty)
+ continue;
+
+ if (repl.type == FmtReplacement::Type::Literal) {
+ s << repl.spec;
+ continue;
+ }
+
+ if (repl.type == FmtReplacement::Type::SpecialPH) {
+ if (repl.placeholder == FmtContext::PHKind::None) {
+ s << repl.spec;
+ } else if (!context) {
+ // We need the context to replace special placeholders.
+ s << repl.spec << kMarkerForNoSubst;
+ } else {
+ Optional<StringRef> subst;
+ if (repl.placeholder == FmtContext::PHKind::Custom) {
+ // Skip the leading '$' sign for the custom placeholder
+ subst = context->getSubstFor(repl.spec.substr(1));
+ } else {
+ subst = context->getSubstFor(repl.placeholder);
+ }
+ if (subst)
+ s << *subst;
+ else
+ s << repl.spec << kMarkerForNoSubst;
+ }
+ continue;
+ }
+
+ assert(repl.type == FmtReplacement::Type::PositionalPH);
+
+ if (repl.index >= adapters.size()) {
+ s << repl.spec << kMarkerForNoSubst;
+ continue;
+ }
+ adapters[repl.index]->format(s, /*Options=*/"");
+ }
+}
add_subdirectory(Dialect)
add_subdirectory(IR)
add_subdirectory(Pass)
+add_subdirectory(TableGen)
--- /dev/null
+add_mlir_unittest(MLIRTableGenTests
+ FormatTest.cpp
+)
+target_link_libraries(MLIRDialectTests
+ PRIVATE LLVMMLIRTableGen)
--- /dev/null
+//===- FormatTest.cpp - TableGen Format Utility Tests ---------------------===//
+//
+// Copyright 2019 The MLIR Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+// =============================================================================
+
+#include "mlir/TableGen/Format.h"
+#include "gmock/gmock.h"
+
+using mlir::tblgen::FmtContext;
+using mlir::tblgen::tgfmt;
+using ::testing::StrEq;
+
+TEST(FormatTest, EmptyFmtStr) {
+ FmtContext ctx;
+ std::string result = tgfmt("", &ctx);
+ EXPECT_TRUE(result.empty());
+}
+
+// Allow extra unused positional parameters
+TEST(FormatTest, EmptyFmtStrExtraParams) {
+ FmtContext ctx;
+ std::string result = tgfmt("", &ctx, "a", "b", "c");
+ EXPECT_TRUE(result.empty());
+}
+
+// Allow unused placeholder substitution in context
+TEST(FormatTest, EmptyFmtStrPopulatedCtx) {
+ FmtContext ctx;
+ ctx.withBuilder("builder");
+ std::string result = tgfmt("", &ctx);
+ EXPECT_TRUE(result.empty());
+}
+
+TEST(FormatTest, LiteralFmtStr) {
+ FmtContext ctx;
+ std::string result = tgfmt("void foo {}", &ctx);
+ EXPECT_THAT(result, StrEq("void foo {}"));
+}
+
+// Print single dollar literally
+TEST(FormatTest, AdjacentDollar) {
+ FmtContext ctx;
+ std::string result = tgfmt("$", &ctx);
+ EXPECT_THAT(result, StrEq("$"));
+}
+
+// Print dangling dollar literally
+TEST(FormatTest, DanglingDollar) {
+ FmtContext ctx;
+ std::string result = tgfmt("foo bar baz$", &ctx);
+ EXPECT_THAT(result, StrEq("foo bar baz$"));
+}
+
+// Allow escape dollars with '$$'
+TEST(FormatTest, EscapeDollars) {
+ FmtContext ctx;
+ std::string result =
+ tgfmt("$$ $$$$ $$$0 $$$_self", &ctx.withSelf("self"), "-0");
+ EXPECT_THAT(result, StrEq("$ $$ $-0 $self"));
+}
+
+TEST(FormatTest, PositionalFmtStr) {
+ FmtContext ctx;
+ std::string b = "b";
+ int c = 42;
+ char d = 'd';
+ std::string result = tgfmt("$0 $1 $2 $3", &ctx, "a", b, c + 1, d);
+ EXPECT_THAT(result, StrEq("a b 43 d"));
+}
+
+// Output the placeholder if missing substitution
+TEST(FormatTest, PositionalFmtStrMissingParams) {
+ FmtContext ctx;
+ std::string result = tgfmt("$0 %1 $2", &ctx);
+ EXPECT_THAT(result, StrEq("$0<no-subst-found> %1 $2<no-subst-found>"));
+}
+
+// Allow flexible reference of positional parameters
+TEST(FormatTest, PositionalFmtStrFlexibleRef) {
+ FmtContext ctx;
+ std::string result = tgfmt("$2 $0 $2", &ctx, "a", "b", "c");
+ EXPECT_THAT(result, StrEq("c a c"));
+}
+
+TEST(FormatTest, PositionalFmtStrNoWhitespace) {
+ FmtContext ctx;
+ std::string result = tgfmt("foo$0bar", &ctx, "-");
+ EXPECT_THAT(result, StrEq("foo-bar"));
+}
+
+TEST(FormatTest, PlaceHolderFmtStrWithSelf) {
+ FmtContext ctx;
+ std::string result = tgfmt("$_self", &ctx.withSelf("sss"));
+ EXPECT_THAT(result, StrEq("sss"));
+}
+
+TEST(FormatTest, PlaceHolderFmtStrWithBuilder) {
+ FmtContext ctx;
+
+ std::string result = tgfmt("$_builder", &ctx.withBuilder("bbb"));
+ EXPECT_THAT(result, StrEq("bbb"));
+}
+
+TEST(FormatTest, PlaceHolderFmtStrWithOp) {
+ FmtContext ctx;
+ std::string result = tgfmt("$_op", &ctx.withOp("ooo"));
+ EXPECT_THAT(result, StrEq("ooo"));
+}
+
+TEST(FormatTest, PlaceHolderMissingCtx) {
+ std::string result = tgfmt("$_op", nullptr);
+ EXPECT_THAT(result, StrEq("$_op<no-subst-found>"));
+}
+
+TEST(FormatTest, PlaceHolderMissingSubst) {
+ FmtContext ctx;
+ std::string result = tgfmt("$_op", &ctx.withBuilder("builder"));
+ EXPECT_THAT(result, StrEq("$_op<no-subst-found>"));
+}
+
+// Test commonly used delimiters in C++
+TEST(FormatTest, PlaceHolderFmtStrDelimiter) {
+ FmtContext ctx;
+ ctx.addSubst("m", "");
+ std::string result = tgfmt("$m{$m($m[$m]$m)$m}$m|", &ctx);
+ EXPECT_THAT(result, StrEq("{([])}|"));
+}
+
+// Test allowed characters in placeholder symbol
+TEST(FormatTest, CustomPlaceHolderFmtStrPlaceHolderChars) {
+ FmtContext ctx;
+ ctx.addSubst("m", "0 ");
+ ctx.addSubst("m1", "1 ");
+ ctx.addSubst("m2C", "2 ");
+ ctx.addSubst("M_3", "3 ");
+ std::string result = tgfmt("$m$m1$m2C$M_3", &ctx);
+ EXPECT_THAT(result, StrEq("0 1 2 3 "));
+}
+
+TEST(FormatTest, CustomPlaceHolderFmtStrUnregisteredPlaceHolders) {
+ FmtContext ctx;
+ std::string result = tgfmt("foo($awesome, $param)", &ctx);
+ EXPECT_THAT(result,
+ StrEq("foo($awesome<no-subst-found>, $param<no-subst-found>)"));
+}
+
+TEST(FormatTest, MixedFmtStr) {
+ FmtContext ctx;
+ ctx.withBuilder("bbb");
+
+ std::string result = tgfmt("$_builder.build($_self, {$0, $1})",
+ &ctx.withSelf("sss"), "a", "b");
+ EXPECT_THAT(result, StrEq("bbb.build(sss, {a, b})"));
+}