summaryrefslogtreecommitdiff
path: root/utils
diff options
context:
space:
mode:
authorQuentin Colombet <qcolombet@apple.com>2017-12-18 19:47:41 +0000
committerQuentin Colombet <qcolombet@apple.com>2017-12-18 19:47:41 +0000
commitd3fbd022be5a5aae1f6a8bbbded44c41ac231cce (patch)
tree119cfb573f2060c70a89db81e32b5d0a7dc1968b /utils
parentd1fe0c4fdfbddbbae168f72c2a19b1ef5d57c7ad (diff)
[TableGen][GlobalISel] Optimize MatchTable for faster instruction selection
*** Context *** Prior to this patchw, the table generated for matching instruction was straight forward but highly inefficient. Basically, each pattern generates its own set of self contained checks and actions. E.g., TableGen generated: // First pattern CheckNumOperand 3 CheckOpcode G_ADD ... Build ADDrr // Second pattern CheckNumOperand 3 CheckOpcode G_ADD ... Build ADDri // Third pattern CheckNumOperand 3 CheckOpcode G_SUB ... Build SUBrr *** Problem *** Because of that generation, a *lot* of check were redundant between each pattern and were checked every single time until we reach the pattern that matches. E.g., Taking the previous table, let say we are matching a G_SUB, that means we were going to check all the rules for G_ADD before looking at the G_SUB rule. In particular we are going to do: check 3 operands; PASS check G_ADD; FAIL ; Next rule check 3 operands; PASS (but we already knew that!) check G_ADD; FAIL (well it is still not true) ; Next rule check 3 operands; PASS (really!!) check G_SUB; PASS (at last :P) *** Proposed Solution *** This patch introduces a concept of group of rules (GroupMatcher) that share some predicates and only get checked once for the whole group. This patch only creates groups with one nesting level. Conceptually there is nothing preventing us for having deeper nest level. However, the current implementation is not smart enough to share the recording (aka capturing) of values. That limits its ability to do more sharing. For the given example the current patch will generate: // First group CheckOpcode G_ADD // First pattern CheckNumOperand 3 ... Build ADDrr // Second pattern CheckNumOperand 3 ... Build ADDri // Second group CheckOpcode G_SUB // Third pattern CheckNumOperand 3 ... Build SUBrr But if we allowed several nesting level, it could create a sub group for the checknumoperand 3. (We would need to call optimizeRules on the rules within a group.) *** Result *** With only one level of nesting, the instruction selection pass is up to 4x faster. For instance, one instruction now takes 500 checks, instead of 24k! With more nesting we could get in the tens I believe. Differential Revision: https://reviews.llvm.org/D39034 rdar://problem/34670699 git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@321017 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'utils')
-rw-r--r--utils/TableGen/GlobalISelEmitter.cpp211
1 files changed, 194 insertions, 17 deletions
diff --git a/utils/TableGen/GlobalISelEmitter.cpp b/utils/TableGen/GlobalISelEmitter.cpp
index 88558894097..b80f0235506 100644
--- a/utils/TableGen/GlobalISelEmitter.cpp
+++ b/utils/TableGen/GlobalISelEmitter.cpp
@@ -74,10 +74,14 @@ static cl::opt<std::string> UseCoverageFile(
cl::desc("Specify file to retrieve coverage information from"),
cl::cat(GlobalISelEmitterCat));
+static cl::opt<bool> OptimizeMatchTable(
+ "optimize-match-table",
+ cl::desc("Generate an optimized version of the match table"),
+ cl::init(true), cl::cat(GlobalISelEmitterCat));
+
namespace {
//===- Helper functions ---------------------------------------------------===//
-
/// Get the name of the enum value used to number the predicate function.
std::string getEnumNameForPredicate(const TreePredicateFn &Predicate) {
return "GIPFP_" + Predicate.getImmTypeIdentifier().str() + "_" +
@@ -563,9 +567,38 @@ MatchTable &operator<<(MatchTable &Table, const MatchTableRecord &Value) {
class OperandMatcher;
class MatchAction;
+class PredicateMatcher;
+class RuleMatcher;
+
+class Matcher {
+public:
+ virtual ~Matcher() = default;
+ virtual void emit(MatchTable &Table) = 0;
+};
+
+class GroupMatcher : public Matcher {
+ SmallVector<std::unique_ptr<PredicateMatcher>, 8> Conditions;
+ SmallVector<Matcher *, 8> Rules;
+
+public:
+ void addCondition(std::unique_ptr<PredicateMatcher> &&Predicate) {
+ Conditions.emplace_back(std::move(Predicate));
+ }
+ void addRule(Matcher &Rule) { Rules.push_back(&Rule); }
+ const std::unique_ptr<PredicateMatcher> &conditions_back() const {
+ return Conditions.back();
+ }
+ bool lastConditionMatches(const PredicateMatcher &Predicate) const;
+ bool conditions_empty() const { return Conditions.empty(); }
+ void clear() {
+ Conditions.clear();
+ Rules.clear();
+ }
+ void emit(MatchTable &Table) override;
+};
/// Generates code to check that a match rule matches.
-class RuleMatcher {
+class RuleMatcher : public Matcher {
public:
using ActionVec = std::vector<std::unique_ptr<MatchAction>>;
using action_iterator = ActionVec::iterator;
@@ -705,7 +738,7 @@ public:
void emitCaptureOpcodes(MatchTable &Table);
- void emit(MatchTable &Table);
+ void emit(MatchTable &Table) override;
/// Compare the priority of this object and B.
///
@@ -716,11 +749,16 @@ public:
/// matcher.
unsigned countRendererFns() const;
+ std::unique_ptr<PredicateMatcher> forgetFirstCondition();
+
// FIXME: Remove this as soon as possible
- InstructionMatcher &insnmatcher_front() const { return *Matchers.front(); }
+ InstructionMatcher &insnmatchers_front() const { return *Matchers.front(); }
unsigned allocateOutputInsnID() { return NextOutputInsnID++; }
unsigned allocateTempRegID() { return NextTempRegID++; }
+
+ bool insnmatchers_empty() const { return Matchers.empty(); }
+ void insnmatchers_pop_front() { Matchers.erase(Matchers.begin()); }
};
uint64_t RuleMatcher::NextRuleID = 0;
@@ -757,6 +795,13 @@ public:
typename PredicateVec::size_type predicates_size() const {
return Predicates.size();
}
+ bool predicates_empty() const { return Predicates.empty(); }
+
+ std::unique_ptr<PredicateTy> predicates_pop_front() {
+ std::unique_ptr<PredicateTy> Front = std::move(Predicates.front());
+ Predicates.erase(Predicates.begin());
+ return Front;
+ }
/// Emit MatchTable opcodes that tests whether all the predicates are met.
template <class... Args>
@@ -1513,6 +1558,9 @@ public:
iterator_range<OperandVec::const_iterator> operands() const {
return make_range(operands_begin(), operands_end());
}
+ bool operands_empty() const { return Operands.empty(); }
+
+ void pop_front() { Operands.erase(Operands.begin()); }
/// Emit MatchTable opcodes to check the shape of the match and capture
/// instructions into the MIs table.
@@ -2535,6 +2583,40 @@ private:
TreePatternNode *fixupPatternNode(TreePatternNode *N);
void fixupPatternTrees(TreePattern *P);
+
+ /// Takes a sequence of \p Rules and group them based on the predicates
+ /// they share. \p StorageGroupMatcher is used as a memory container
+ /// for the the group that are created as part of this process.
+ /// The optimization process does not change the relative order of
+ /// the rules. In particular, we don't try to share predicates if
+ /// that means reordering the rules (e.g., we won't group R1 and R3
+ /// in the following example as it would imply reordering R2 and R3
+ /// => R1 p1, R2 p2, R3 p1).
+ ///
+ /// What this optimization does looks like:
+ /// Output without optimization:
+ /// \verbatim
+ /// # R1
+ /// # predicate A
+ /// # predicate B
+ /// ...
+ /// # R2
+ /// # predicate A // <-- effectively this is going to be checked twice.
+ /// // Once in R1 and once in R2.
+ /// # predicate C
+ /// \endverbatim
+ /// Output with optimization:
+ /// \verbatim
+ /// # Group1_2
+ /// # predicate A // <-- Check is now shared.
+ /// # R1
+ /// # predicate B
+ /// # R2
+ /// # predicate C
+ /// \endverbatim
+ std::vector<Matcher *> optimizeRules(
+ std::vector<RuleMatcher> &Rules,
+ std::vector<std::unique_ptr<GroupMatcher>> &StorageGroupMatcher);
};
void GlobalISelEmitter::gatherNodeEquivs() {
@@ -3469,6 +3551,38 @@ void GlobalISelEmitter::emitImmPredicates(
OS << "};\n";
}
+std::vector<Matcher *> GlobalISelEmitter::optimizeRules(
+ std::vector<RuleMatcher> &Rules,
+ std::vector<std::unique_ptr<GroupMatcher>> &StorageGroupMatcher) {
+ std::vector<Matcher *> OptRules;
+ // Start with a stupid grouping for now.
+ std::unique_ptr<GroupMatcher> CurrentGroup = make_unique<GroupMatcher>();
+ assert(CurrentGroup->conditions_empty());
+ unsigned NbGroup = 0;
+ for (RuleMatcher &Rule : Rules) {
+ std::unique_ptr<PredicateMatcher> Predicate = Rule.forgetFirstCondition();
+ if (!CurrentGroup->conditions_empty() &&
+ !CurrentGroup->lastConditionMatches(*Predicate)) {
+ // Start a new group.
+ ++NbGroup;
+ OptRules.push_back(CurrentGroup.get());
+ StorageGroupMatcher.emplace_back(std::move(CurrentGroup));
+ CurrentGroup = make_unique<GroupMatcher>();
+ assert(CurrentGroup->conditions_empty());
+ }
+ if (CurrentGroup->conditions_empty())
+ CurrentGroup->addCondition(std::move(Predicate));
+ CurrentGroup->addRule(Rule);
+ }
+ if (!CurrentGroup->conditions_empty()) {
+ ++NbGroup;
+ OptRules.push_back(CurrentGroup.get());
+ StorageGroupMatcher.emplace_back(std::move(CurrentGroup));
+ }
+ DEBUG(dbgs() << "NbGroup: " << NbGroup << "\n");
+ return OptRules;
+}
+
void GlobalISelEmitter::run(raw_ostream &OS) {
if (!UseCoverageFile.empty()) {
RuleCoverage = CodeGenCoverage();
@@ -3519,17 +3633,6 @@ void GlobalISelEmitter::run(raw_ostream &OS) {
Rules.push_back(std::move(MatcherOrErr.get()));
}
- std::stable_sort(Rules.begin(), Rules.end(),
- [&](const RuleMatcher &A, const RuleMatcher &B) {
- if (A.isHigherPriorityThan(B)) {
- assert(!B.isHigherPriorityThan(A) && "Cannot be more important "
- "and less important at "
- "the same time");
- return true;
- }
- return false;
- });
-
std::vector<Record *> ComplexPredicates =
RK.getAllDerivedDefinitions("GIComplexOperandMatcher");
std::sort(ComplexPredicates.begin(), ComplexPredicates.end(),
@@ -3708,9 +3811,28 @@ void GlobalISelEmitter::run(raw_ostream &OS) {
<< " State.MIs.clear();\n"
<< " State.MIs.push_back(&I);\n\n";
+ std::stable_sort(Rules.begin(), Rules.end(), [&](const RuleMatcher &A,
+ const RuleMatcher &B) {
+ if (A.isHigherPriorityThan(B)) {
+ assert(!B.isHigherPriorityThan(A) && "Cannot be more important "
+ "and less important at "
+ "the same time");
+ return true;
+ }
+ return false;
+ });
+ std::vector<std::unique_ptr<GroupMatcher>> StorageGroupMatcher;
+
+ std::vector<Matcher *> OptRules;
+ if (OptimizeMatchTable)
+ OptRules = optimizeRules(Rules, StorageGroupMatcher);
+ else
+ for (Matcher &Rule : Rules)
+ OptRules.push_back(&Rule);
+
MatchTable Table(0);
- for (auto &Rule : Rules) {
- Rule.emit(Table);
+ for (Matcher *Rule : OptRules) {
+ Rule->emit(Table);
++NumPatternEmitted;
}
Table << MatchTable::Opcode("GIM_Reject") << MatchTable::LineBreak;
@@ -3827,6 +3949,61 @@ void GlobalISelEmitter::fixupPatternTrees(TreePattern *P) {
}
}
+std::unique_ptr<PredicateMatcher> RuleMatcher::forgetFirstCondition() {
+ assert(!insnmatchers_empty() &&
+ "Trying to forget something that does not exist");
+
+ InstructionMatcher &Matcher = insnmatchers_front();
+ std::unique_ptr<PredicateMatcher> Condition;
+ if (!Matcher.predicates_empty())
+ Condition = Matcher.predicates_pop_front();
+ if (!Condition) {
+ // If there is no more predicate on the instruction itself, look at its
+ // operands.
+ assert(!Matcher.operands_empty() &&
+ "Empty instruction should have been discarded");
+ OperandMatcher &OpMatcher = **Matcher.operands_begin();
+ assert(!OpMatcher.predicates_empty() && "no operand constraint");
+ Condition = OpMatcher.predicates_pop_front();
+ // If this operand is free of constraints, rip it off.
+ if (OpMatcher.predicates_empty())
+ Matcher.pop_front();
+ }
+ // Rip the instruction off when it is empty.
+ if (Matcher.operands_empty() && Matcher.predicates_empty())
+ insnmatchers_pop_front();
+ return Condition;
+}
+
+bool GroupMatcher::lastConditionMatches(
+ const PredicateMatcher &Predicate) const {
+ const auto &LastCondition = conditions_back();
+ return Predicate.isIdentical(*LastCondition);
+}
+
+void GroupMatcher::emit(MatchTable &Table) {
+ unsigned LabelID = Table.allocateLabelID();
+ if (!conditions_empty()) {
+ Table << MatchTable::Opcode("GIM_Try", +1)
+ << MatchTable::Comment("On fail goto")
+ << MatchTable::JumpTarget(LabelID) << MatchTable::LineBreak;
+ for (auto &Condition : Conditions)
+ Condition->emitPredicateOpcodes(
+ Table, *static_cast<RuleMatcher *>(*Rules.begin()));
+ }
+ // Emit the conditions.
+ // Then checks apply the rules.
+ for (const auto &Rule : Rules)
+ Rule->emit(Table);
+ // If we don't succeeded for that block, that means we are not going to select
+ // this instruction.
+ if (!conditions_empty()) {
+ Table << MatchTable::Opcode("GIM_Reject") << MatchTable::LineBreak;
+ Table << MatchTable::Opcode("GIR_Done", -1) << MatchTable::LineBreak
+ << MatchTable::Label(LabelID);
+ }
+}
+
unsigned OperandMatcher::getInsnVarID() const { return Insn.getVarID(); }
} // end anonymous namespace