diff options
author | Quentin Colombet <qcolombet@apple.com> | 2017-12-18 19:47:41 +0000 |
---|---|---|
committer | Quentin Colombet <qcolombet@apple.com> | 2017-12-18 19:47:41 +0000 |
commit | d3fbd022be5a5aae1f6a8bbbded44c41ac231cce (patch) | |
tree | 119cfb573f2060c70a89db81e32b5d0a7dc1968b /utils | |
parent | d1fe0c4fdfbddbbae168f72c2a19b1ef5d57c7ad (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.cpp | 211 |
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 |