llvm-project/mlir/lib/TableGen/Predicate.cpp

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

380 lines
14 KiB
C++
Raw Normal View History

//===- Predicate.cpp - Predicate class ------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Wrapper around predicates defined in TableGen.
//
//===----------------------------------------------------------------------===//
#include "mlir/TableGen/Predicate.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/StringExtras.h"
[llvm] Cleanup header dependencies in ADT and Support The cleanup was manual, but assisted by "include-what-you-use". It consists in 1. Removing unused forward declaration. No impact expected. 2. Removing unused headers in .cpp files. No impact expected. 3. Removing unused headers in .h files. This removes implicit dependencies and is generally considered a good thing, but this may break downstream builds. I've updated llvm, clang, lld, lldb and mlir deps, and included a list of the modification in the second part of the commit. 4. Replacing header inclusion by forward declaration. This has the same impact as 3. Notable changes: - llvm/Support/TargetParser.h no longer includes llvm/Support/AArch64TargetParser.h nor llvm/Support/ARMTargetParser.h - llvm/Support/TypeSize.h no longer includes llvm/Support/WithColor.h - llvm/Support/YAMLTraits.h no longer includes llvm/Support/Regex.h - llvm/ADT/SmallVector.h no longer includes llvm/Support/MemAlloc.h nor llvm/Support/ErrorHandling.h You may need to add some of these headers in your compilation units, if needs be. As an hint to the impact of the cleanup, running clang++ -E -Iinclude -I../llvm/include ../llvm/lib/Support/*.cpp -std=c++14 -fno-rtti -fno-exceptions | wc -l before: 8000919 lines after: 7917500 lines Reduced dependencies also helps incremental rebuilds and is more ccache friendly, something not shown by the above metric :-) Discourse thread on the topic: https://llvm.discourse.group/t/include-what-you-use-include-cleanup/5831
2022-01-20 19:55:14 +08:00
#include "llvm/ADT/StringSwitch.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/TableGen/Error.h"
#include "llvm/TableGen/Record.h"
using namespace mlir;
using namespace tblgen;
TableGen: extract TypeConstraints from Type MLIR has support for type-polymorphic instructions, i.e. instructions that may take arguments of different types. For example, standard arithmetic operands take scalars, vectors or tensors. In order to express such instructions in TableGen, we need to be able to verify that a type object satisfies certain constraints, but we don't need to construct an instance of this type. The existing TableGen definition of Type requires both. Extract out a TypeConstraint TableGen class to define restrictions on types. Define the Type TableGen class as a subclass of TypeConstraint for consistency. Accept records of the TypeConstraint class instead of the Type class as values in the Arguments class when defining operators. Replace the predicate logic TableGen class based on conjunctive normal form with the predicate logic classes allowing for abitrary combinations of predicates using Boolean operators (AND/OR/NOT). The combination is implemented using simple string rewriting of C++ expressions and, therefore, respects the short-circuit evaluation order. No logic simplification is performed at the TableGen level so all expressions must be valid C++. Maintaining CNF using TableGen only would have been complicated when one needed to introduce top-level disjunction. It is also unclear if it could lead to a significantly simpler emitted C++ code. In the future, we may replace inplace predicate string combination with a tree structure that can be simplified in TableGen's C++ driver. Combined, these changes allow one to express traits like ArgumentsAreFloatLike directly in TableGen instead of relying on C++ trait classes. PiperOrigin-RevId: 229398247
2019-01-16 02:42:21 +08:00
// Construct a Predicate from a record.
Pred::Pred(const llvm::Record *record) : def(record) {
TableGen: extract TypeConstraints from Type MLIR has support for type-polymorphic instructions, i.e. instructions that may take arguments of different types. For example, standard arithmetic operands take scalars, vectors or tensors. In order to express such instructions in TableGen, we need to be able to verify that a type object satisfies certain constraints, but we don't need to construct an instance of this type. The existing TableGen definition of Type requires both. Extract out a TypeConstraint TableGen class to define restrictions on types. Define the Type TableGen class as a subclass of TypeConstraint for consistency. Accept records of the TypeConstraint class instead of the Type class as values in the Arguments class when defining operators. Replace the predicate logic TableGen class based on conjunctive normal form with the predicate logic classes allowing for abitrary combinations of predicates using Boolean operators (AND/OR/NOT). The combination is implemented using simple string rewriting of C++ expressions and, therefore, respects the short-circuit evaluation order. No logic simplification is performed at the TableGen level so all expressions must be valid C++. Maintaining CNF using TableGen only would have been complicated when one needed to introduce top-level disjunction. It is also unclear if it could lead to a significantly simpler emitted C++ code. In the future, we may replace inplace predicate string combination with a tree structure that can be simplified in TableGen's C++ driver. Combined, these changes allow one to express traits like ArgumentsAreFloatLike directly in TableGen instead of relying on C++ trait classes. PiperOrigin-RevId: 229398247
2019-01-16 02:42:21 +08:00
assert(def->isSubClassOf("Pred") &&
"must be a subclass of TableGen 'Pred' class");
}
TableGen: extract TypeConstraints from Type MLIR has support for type-polymorphic instructions, i.e. instructions that may take arguments of different types. For example, standard arithmetic operands take scalars, vectors or tensors. In order to express such instructions in TableGen, we need to be able to verify that a type object satisfies certain constraints, but we don't need to construct an instance of this type. The existing TableGen definition of Type requires both. Extract out a TypeConstraint TableGen class to define restrictions on types. Define the Type TableGen class as a subclass of TypeConstraint for consistency. Accept records of the TypeConstraint class instead of the Type class as values in the Arguments class when defining operators. Replace the predicate logic TableGen class based on conjunctive normal form with the predicate logic classes allowing for abitrary combinations of predicates using Boolean operators (AND/OR/NOT). The combination is implemented using simple string rewriting of C++ expressions and, therefore, respects the short-circuit evaluation order. No logic simplification is performed at the TableGen level so all expressions must be valid C++. Maintaining CNF using TableGen only would have been complicated when one needed to introduce top-level disjunction. It is also unclear if it could lead to a significantly simpler emitted C++ code. In the future, we may replace inplace predicate string combination with a tree structure that can be simplified in TableGen's C++ driver. Combined, these changes allow one to express traits like ArgumentsAreFloatLike directly in TableGen instead of relying on C++ trait classes. PiperOrigin-RevId: 229398247
2019-01-16 02:42:21 +08:00
// Construct a Predicate from an initializer.
Pred::Pred(const llvm::Init *init) {
TableGen: extract TypeConstraints from Type MLIR has support for type-polymorphic instructions, i.e. instructions that may take arguments of different types. For example, standard arithmetic operands take scalars, vectors or tensors. In order to express such instructions in TableGen, we need to be able to verify that a type object satisfies certain constraints, but we don't need to construct an instance of this type. The existing TableGen definition of Type requires both. Extract out a TypeConstraint TableGen class to define restrictions on types. Define the Type TableGen class as a subclass of TypeConstraint for consistency. Accept records of the TypeConstraint class instead of the Type class as values in the Arguments class when defining operators. Replace the predicate logic TableGen class based on conjunctive normal form with the predicate logic classes allowing for abitrary combinations of predicates using Boolean operators (AND/OR/NOT). The combination is implemented using simple string rewriting of C++ expressions and, therefore, respects the short-circuit evaluation order. No logic simplification is performed at the TableGen level so all expressions must be valid C++. Maintaining CNF using TableGen only would have been complicated when one needed to introduce top-level disjunction. It is also unclear if it could lead to a significantly simpler emitted C++ code. In the future, we may replace inplace predicate string combination with a tree structure that can be simplified in TableGen's C++ driver. Combined, these changes allow one to express traits like ArgumentsAreFloatLike directly in TableGen instead of relying on C++ trait classes. PiperOrigin-RevId: 229398247
2019-01-16 02:42:21 +08:00
if (const auto *defInit = dyn_cast_or_null<llvm::DefInit>(init))
def = defInit->getDef();
}
std::string Pred::getCondition() const {
// Static dispatch to subclasses.
if (def->isSubClassOf("CombinedPred"))
return static_cast<const CombinedPred *>(this)->getConditionImpl();
if (def->isSubClassOf("CPred"))
return static_cast<const CPred *>(this)->getConditionImpl();
llvm_unreachable("Pred::getCondition must be overridden in subclasses");
}
bool Pred::isCombined() const {
return def && def->isSubClassOf("CombinedPred");
}
ArrayRef<SMLoc> Pred::getLoc() const { return def->getLoc(); }
CPred::CPred(const llvm::Record *record) : Pred(record) {
assert(def->isSubClassOf("CPred") &&
"must be a subclass of Tablegen 'CPred' class");
}
CPred::CPred(const llvm::Init *init) : Pred(init) {
assert((!def || def->isSubClassOf("CPred")) &&
"must be a subclass of Tablegen 'CPred' class");
}
// Get condition of the C Predicate.
std::string CPred::getConditionImpl() const {
TableGen: extract TypeConstraints from Type MLIR has support for type-polymorphic instructions, i.e. instructions that may take arguments of different types. For example, standard arithmetic operands take scalars, vectors or tensors. In order to express such instructions in TableGen, we need to be able to verify that a type object satisfies certain constraints, but we don't need to construct an instance of this type. The existing TableGen definition of Type requires both. Extract out a TypeConstraint TableGen class to define restrictions on types. Define the Type TableGen class as a subclass of TypeConstraint for consistency. Accept records of the TypeConstraint class instead of the Type class as values in the Arguments class when defining operators. Replace the predicate logic TableGen class based on conjunctive normal form with the predicate logic classes allowing for abitrary combinations of predicates using Boolean operators (AND/OR/NOT). The combination is implemented using simple string rewriting of C++ expressions and, therefore, respects the short-circuit evaluation order. No logic simplification is performed at the TableGen level so all expressions must be valid C++. Maintaining CNF using TableGen only would have been complicated when one needed to introduce top-level disjunction. It is also unclear if it could lead to a significantly simpler emitted C++ code. In the future, we may replace inplace predicate string combination with a tree structure that can be simplified in TableGen's C++ driver. Combined, these changes allow one to express traits like ArgumentsAreFloatLike directly in TableGen instead of relying on C++ trait classes. PiperOrigin-RevId: 229398247
2019-01-16 02:42:21 +08:00
assert(!isNull() && "null predicate does not have a condition");
return std::string(def->getValueAsString("predExpr"));
}
CombinedPred::CombinedPred(const llvm::Record *record) : Pred(record) {
assert(def->isSubClassOf("CombinedPred") &&
"must be a subclass of Tablegen 'CombinedPred' class");
}
CombinedPred::CombinedPred(const llvm::Init *init) : Pred(init) {
assert((!def || def->isSubClassOf("CombinedPred")) &&
"must be a subclass of Tablegen 'CombinedPred' class");
}
const llvm::Record *CombinedPred::getCombinerDef() const {
assert(def->getValue("kind") && "CombinedPred must have a value 'kind'");
return def->getValueAsDef("kind");
}
std::vector<llvm::Record *> CombinedPred::getChildren() const {
assert(def->getValue("children") &&
"CombinedPred must have a value 'children'");
return def->getValueAsListOfDefs("children");
}
namespace {
// Kinds of nodes in a logical predicate tree.
enum class PredCombinerKind {
Leaf,
And,
Or,
Not,
SubstLeaves,
Concat,
// Special kinds that are used in simplification.
False,
True
};
// A node in a logical predicate tree.
struct PredNode {
PredCombinerKind kind;
const Pred *predicate;
SmallVector<PredNode *, 4> children;
std::string expr;
// Prefix and suffix are used by ConcatPred.
std::string prefix;
std::string suffix;
};
} // namespace
// Get a predicate tree node kind based on the kind used in the predicate
// TableGen record.
static PredCombinerKind getPredCombinerKind(const Pred &pred) {
if (!pred.isCombined())
return PredCombinerKind::Leaf;
const auto &combinedPred = static_cast<const CombinedPred &>(pred);
return StringSwitch<PredCombinerKind>(
combinedPred.getCombinerDef()->getName())
.Case("PredCombinerAnd", PredCombinerKind::And)
.Case("PredCombinerOr", PredCombinerKind::Or)
.Case("PredCombinerNot", PredCombinerKind::Not)
.Case("PredCombinerSubstLeaves", PredCombinerKind::SubstLeaves)
.Case("PredCombinerConcat", PredCombinerKind::Concat);
}
namespace {
// Substitution<pattern, replacement>.
using Subst = std::pair<StringRef, StringRef>;
} // namespace
/// Perform the given substitutions on 'str' in-place.
static void performSubstitutions(std::string &str,
ArrayRef<Subst> substitutions) {
// Apply all parent substitutions from innermost to outermost.
for (const auto &subst : llvm::reverse(substitutions)) {
auto pos = str.find(std::string(subst.first));
while (pos != std::string::npos) {
str.replace(pos, subst.first.size(), std::string(subst.second));
// Skip the newly inserted substring, which itself may consider the
// pattern to match.
pos += subst.second.size();
// Find the next possible match position.
pos = str.find(std::string(subst.first), pos);
}
}
}
// Build the predicate tree starting from the top-level predicate, which may
// have children, and perform leaf substitutions inplace. Note that after
// substitution, nodes are still pointing to the original TableGen record.
// All nodes are created within "allocator".
static PredNode *
buildPredicateTree(const Pred &root,
llvm::SpecificBumpPtrAllocator<PredNode> &allocator,
ArrayRef<Subst> substitutions) {
auto *rootNode = allocator.Allocate();
new (rootNode) PredNode;
rootNode->kind = getPredCombinerKind(root);
rootNode->predicate = &root;
if (!root.isCombined()) {
rootNode->expr = root.getCondition();
performSubstitutions(rootNode->expr, substitutions);
return rootNode;
}
// If the current combined predicate is a leaf substitution, append it to the
// list before continuing.
auto allSubstitutions = llvm::to_vector<4>(substitutions);
if (rootNode->kind == PredCombinerKind::SubstLeaves) {
const auto &substPred = static_cast<const SubstLeavesPred &>(root);
allSubstitutions.push_back(
{substPred.getPattern(), substPred.getReplacement()});
// If the current predicate is a ConcatPred, record the prefix and suffix.
} else if (rootNode->kind == PredCombinerKind::Concat) {
const auto &concatPred = static_cast<const ConcatPred &>(root);
rootNode->prefix = std::string(concatPred.getPrefix());
performSubstitutions(rootNode->prefix, substitutions);
rootNode->suffix = std::string(concatPred.getSuffix());
performSubstitutions(rootNode->suffix, substitutions);
}
// Build child subtrees.
auto combined = static_cast<const CombinedPred &>(root);
for (const auto *record : combined.getChildren()) {
auto *childTree =
buildPredicateTree(Pred(record), allocator, allSubstitutions);
rootNode->children.push_back(childTree);
}
return rootNode;
}
// Simplify a predicate tree rooted at "node" using the predicates that are
// known to be true(false). For AND(OR) combined predicates, if any of the
// children is known to be false(true), the result is also false(true).
// Furthermore, for AND(OR) combined predicates, children that are known to be
// true(false) don't have to be checked dynamically.
static PredNode *
propagateGroundTruth(PredNode *node,
const llvm::SmallPtrSetImpl<Pred *> &knownTruePreds,
const llvm::SmallPtrSetImpl<Pred *> &knownFalsePreds) {
// If the current predicate is known to be true or false, change the kind of
// the node and return immediately.
if (knownTruePreds.count(node->predicate) != 0) {
node->kind = PredCombinerKind::True;
node->children.clear();
return node;
}
if (knownFalsePreds.count(node->predicate) != 0) {
node->kind = PredCombinerKind::False;
node->children.clear();
return node;
}
// If the current node is a substitution, stop recursion now.
// The expressions in the leaves below this node were rewritten, but the nodes
// still point to the original predicate records. While the original
// predicate may be known to be true or false, it is not necessarily the case
// after rewriting.
// TODO: we can support ground truth for rewritten
// predicates by either (a) having our own unique'ing of the predicates
// instead of relying on TableGen record pointers or (b) taking ground truth
// values optionally prefixed with a list of substitutions to apply, e.g.
// "predX is true by itself as well as predSubY leaf substitution had been
// applied to it".
if (node->kind == PredCombinerKind::SubstLeaves) {
return node;
}
// Otherwise, look at child nodes.
// Move child nodes into some local variable so that they can be optimized
// separately and re-added if necessary.
llvm::SmallVector<PredNode *, 4> children;
std::swap(node->children, children);
for (auto &child : children) {
// First, simplify the child. This maintains the predicate as it was.
auto *simplifiedChild =
propagateGroundTruth(child, knownTruePreds, knownFalsePreds);
// Just add the child if we don't know how to simplify the current node.
if (node->kind != PredCombinerKind::And &&
node->kind != PredCombinerKind::Or) {
node->children.push_back(simplifiedChild);
continue;
}
// Second, based on the type define which known values of child predicates
// immediately collapse this predicate to a known value, and which others
// may be safely ignored.
// OR(..., True, ...) = True
// OR(..., False, ...) = OR(..., ...)
// AND(..., False, ...) = False
// AND(..., True, ...) = AND(..., ...)
auto collapseKind = node->kind == PredCombinerKind::And
? PredCombinerKind::False
: PredCombinerKind::True;
auto eraseKind = node->kind == PredCombinerKind::And
? PredCombinerKind::True
: PredCombinerKind::False;
const auto &collapseList =
node->kind == PredCombinerKind::And ? knownFalsePreds : knownTruePreds;
const auto &eraseList =
node->kind == PredCombinerKind::And ? knownTruePreds : knownFalsePreds;
if (simplifiedChild->kind == collapseKind ||
collapseList.count(simplifiedChild->predicate) != 0) {
node->kind = collapseKind;
node->children.clear();
return node;
}
if (simplifiedChild->kind == eraseKind ||
eraseList.count(simplifiedChild->predicate) != 0) {
continue;
}
node->children.push_back(simplifiedChild);
}
return node;
}
// Combine a list of predicate expressions using a binary combiner. If a list
// is empty, return "init".
static std::string combineBinary(ArrayRef<std::string> children,
const std::string &combiner,
std::string init) {
if (children.empty())
return init;
auto size = children.size();
if (size == 1)
return children.front();
std::string str;
llvm::raw_string_ostream os(str);
os << '(' << children.front() << ')';
for (unsigned i = 1; i < size; ++i) {
os << ' ' << combiner << " (" << children[i] << ')';
}
return os.str();
}
// Prepend negation to the only condition in the predicate expression list.
static std::string combineNot(ArrayRef<std::string> children) {
assert(children.size() == 1 && "expected exactly one child predicate of Neg");
return (Twine("!(") + children.front() + Twine(')')).str();
}
// Recursively traverse the predicate tree in depth-first post-order and build
// the final expression.
static std::string getCombinedCondition(const PredNode &root) {
// Immediately return for non-combiner predicates that don't have children.
if (root.kind == PredCombinerKind::Leaf)
return root.expr;
if (root.kind == PredCombinerKind::True)
return "true";
if (root.kind == PredCombinerKind::False)
return "false";
// Recurse into children.
llvm::SmallVector<std::string, 4> childExpressions;
childExpressions.reserve(root.children.size());
for (const auto &child : root.children)
childExpressions.push_back(getCombinedCondition(*child));
// Combine the expressions based on the predicate node kind.
if (root.kind == PredCombinerKind::And)
return combineBinary(childExpressions, "&&", "true");
if (root.kind == PredCombinerKind::Or)
return combineBinary(childExpressions, "||", "false");
if (root.kind == PredCombinerKind::Not)
return combineNot(childExpressions);
if (root.kind == PredCombinerKind::Concat) {
assert(childExpressions.size() == 1 &&
"ConcatPred should only have one child");
return root.prefix + childExpressions.front() + root.suffix;
}
// Substitutions were applied before so just ignore them.
if (root.kind == PredCombinerKind::SubstLeaves) {
assert(childExpressions.size() == 1 &&
"substitution predicate must have one child");
return childExpressions[0];
}
llvm::PrintFatalError(root.predicate->getLoc(), "unsupported predicate kind");
}
std::string CombinedPred::getConditionImpl() const {
llvm::SpecificBumpPtrAllocator<PredNode> allocator;
auto *predicateTree = buildPredicateTree(*this, allocator, {});
predicateTree =
propagateGroundTruth(predicateTree,
/*knownTruePreds=*/llvm::SmallPtrSet<Pred *, 2>(),
/*knownFalsePreds=*/llvm::SmallPtrSet<Pred *, 2>());
return getCombinedCondition(*predicateTree);
}
StringRef SubstLeavesPred::getPattern() const {
return def->getValueAsString("pattern");
}
StringRef SubstLeavesPred::getReplacement() const {
return def->getValueAsString("replacement");
}
StringRef ConcatPred::getPrefix() const {
return def->getValueAsString("prefix");
}
StringRef ConcatPred::getSuffix() const {
return def->getValueAsString("suffix");
}