diff options
author | Alon Zakai <alonzakai@gmail.com> | 2016-09-14 21:28:43 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-09-14 21:28:43 -0700 |
commit | e567fa8675831e79f855cea2181fa58beb107e42 (patch) | |
tree | 14f1e37d27244b349e8ee34939119002f742748d /src/passes/RelooperJumpThreading.cpp | |
parent | 63b499e3ec9bbdf4e79ab6d9dc198299516e8aec (diff) | |
parent | af3bea2786fe62070522b7fd7add4290a4cb4e6d (diff) | |
download | binaryen-e567fa8675831e79f855cea2181fa58beb107e42.tar.gz binaryen-e567fa8675831e79f855cea2181fa58beb107e42.tar.bz2 binaryen-e567fa8675831e79f855cea2181fa58beb107e42.zip |
Merge pull request #695 from WebAssembly/opts
Get optimizer on par with emscripten asm.js optimizer
Diffstat (limited to 'src/passes/RelooperJumpThreading.cpp')
-rw-r--r-- | src/passes/RelooperJumpThreading.cpp | 248 |
1 files changed, 248 insertions, 0 deletions
diff --git a/src/passes/RelooperJumpThreading.cpp b/src/passes/RelooperJumpThreading.cpp new file mode 100644 index 000000000..7f74220d5 --- /dev/null +++ b/src/passes/RelooperJumpThreading.cpp @@ -0,0 +1,248 @@ +/* + * Copyright 2016 WebAssembly Community Group participants + * + * 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. + */ + +// Optimize relooper-generated label variable usage: add blocks and turn +// a label-set/break/label-check into a break into the new block. +// This assumes the very specific output the fastcomp relooper emits, +// including the name of the 'label' variable. + +#include "wasm.h" +#include "pass.h" +#include "ast_utils.h" + +namespace wasm { + +static Name LABEL("label"); + +// We need to use new label names, which we cannot create in parallel, so pre-create them + +const Index MAX_NAME_INDEX = 1000; + +std::vector<Name>* innerNames = nullptr; +std::vector<Name>* outerNames = nullptr; + +struct NameEnsurer { + NameEnsurer() { + assert(!innerNames); + assert(!outerNames); + innerNames = new std::vector<Name>; + outerNames = new std::vector<Name>; + for (Index i = 0; i < MAX_NAME_INDEX; i++) { + innerNames->push_back(Name(std::string("jumpthreading$inner$") + std::to_string(i))); + outerNames->push_back(Name(std::string("jumpthreading$outer$") + std::to_string(i))); + } + } +}; + +static If* isLabelCheckingIf(Expression* curr, Index labelIndex) { + if (!curr) return nullptr; + auto* iff = curr->dynCast<If>(); + if (!iff) return nullptr; + auto* condition = iff->condition->dynCast<Binary>(); + if (!(condition && condition->op == EqInt32)) return nullptr; + auto* left = condition->left->dynCast<GetLocal>(); + if (!(left && left->index == labelIndex)) return nullptr; + return iff; +} + +static Index getCheckedLabelValue(If* iff) { + return iff->condition->cast<Binary>()->right->cast<Const>()->value.geti32(); +} + +static SetLocal* isLabelSettingSetLocal(Expression* curr, Index labelIndex) { + if (!curr) return nullptr; + auto* set = curr->dynCast<SetLocal>(); + if (!set) return nullptr; + if (set->index != labelIndex) return nullptr; + return set; +} + +static Index getSetLabelValue(SetLocal* set) { + return set->value->cast<Const>()->value.geti32(); +} + +struct LabelUseFinder : public PostWalker<LabelUseFinder, Visitor<LabelUseFinder>> { + Index labelIndex; + std::map<Index, Index>& checks; // label value => number of checks on it + std::map<Index, Index>& sets; // label value => number of sets to it + + LabelUseFinder(Index labelIndex, std::map<Index, Index>& checks, std::map<Index, Index>& sets) : labelIndex(labelIndex), checks(checks), sets(sets) {} + + void visitIf(If* curr) { + if (isLabelCheckingIf(curr, labelIndex)) { + checks[getCheckedLabelValue(curr)]++; + } + } + + void visitSetLocal(SetLocal* curr) { + if (isLabelSettingSetLocal(curr, labelIndex)) { + sets[getSetLabelValue(curr)]++; + } + } +}; + +struct RelooperJumpThreading : public WalkerPass<ExpressionStackWalker<RelooperJumpThreading, Visitor<RelooperJumpThreading>>> { + bool isFunctionParallel() override { return true; } + + Pass* create() override { return new RelooperJumpThreading; } + + void prepareToRun(PassRunner* runner, Module* module) override { + static NameEnsurer ensurer; + } + + std::map<Index, Index> labelChecks; + std::map<Index, Index> labelSets; + + Index labelIndex; + Index newNameCounter = 0; + + void visitBlock(Block* curr) { + // look for the if label == X pattern + auto& list = curr->list; + if (list.size() == 0) return; + for (Index i = 0; i < list.size() - 1; i++) { + // once we see something that might be irreducible, we must skip that if and the rest of the dependents + bool irreducible = false; + Index origin = i; + for (Index j = i + 1; j < list.size(); j++) { + if (auto* iff = isLabelCheckingIf(list[j], labelIndex)) { + irreducible |= hasIrreducibleControlFlow(iff, list[origin]); + if (!irreducible) { + optimizeJumpsToLabelCheck(list[origin], iff); + ExpressionManipulator::nop(iff); + } + i++; + continue; + } + // if the next element is a block, it may be the holding block of label-checking ifs + if (auto* holder = list[j]->dynCast<Block>()) { + if (holder->list.size() > 0) { + if (If* iff = isLabelCheckingIf(holder->list[0], labelIndex)) { + irreducible |= hasIrreducibleControlFlow(iff, list[origin]); + if (!irreducible) { + // this is indeed a holder. we can process the ifs, and must also move + // the block to enclose the origin, so it is properly reachable + assert(holder->list.size() == 1); // must be size 1, a relooper multiple will have its own label, and is an if-else sequence and nothing more + optimizeJumpsToLabelCheck(list[origin], iff); + holder->list[0] = list[origin]; + list[origin] = holder; + // reuse the if as a nop + list[j] = iff; + ExpressionManipulator::nop(iff); + } + i++; + continue; + } + } + } + break; // we didn't see something we like, so stop here + } + } + } + + void doWalkFunction(Function* func) { + // if there isn't a label variable, nothing for us to do + if (func->localIndices.count(LABEL)) { + labelIndex = func->getLocalIndex(LABEL); + LabelUseFinder finder(labelIndex, labelChecks, labelSets); + finder.walk(func->body); + WalkerPass<ExpressionStackWalker<RelooperJumpThreading, Visitor<RelooperJumpThreading>>>::doWalkFunction(func); + } + } + +private: + + bool hasIrreducibleControlFlow(If* iff, Expression* origin) { + // Gather the checks in this if chain. If all the label values checked are only set in origin, + // then since origin is right before us, this is not irreducible - we can replace all sets + // in origin with jumps forward to us, and since there is nothing else, this is safe and complete. + // We must also have the property that there is just one check for the label value, as otherwise + // node splitting has complicated things. + std::map<Index, Index> labelChecksInOrigin; + std::map<Index, Index> labelSetsInOrigin; + LabelUseFinder finder(labelIndex, labelChecksInOrigin, labelSetsInOrigin); + finder.walk(origin); + while (iff) { + auto num = getCheckedLabelValue(iff); + assert(labelChecks[num] > 0); + if (labelChecks[num] > 1) return true; // checked more than once, somewhere in function + assert(labelChecksInOrigin[num] == 0); + if (labelSetsInOrigin[num] != labelSets[num]) { + assert(labelSetsInOrigin[num] < labelSets[num]); + return true; // label set somewhere outside of origin TODO: if set in the if body here, it might be safe in some cases + } + iff = isLabelCheckingIf(iff->ifFalse, labelIndex); + } + return false; + } + + // optimizes jumps to a label check + // * origin is where the jumps originate, and also where we should write our output + // * iff is the if + void optimizeJumpsToLabelCheck(Expression*& origin, If* iff) { + Index nameCounter = newNameCounter++; + if (nameCounter >= MAX_NAME_INDEX) { + std::cerr << "too many names in RelooperJumpThreading :(\n"; + return; + } + Index num = getCheckedLabelValue(iff); + // create a new block for this jump target + Builder builder(*getModule()); + // origin is where all jumps to this target must come from - the element right before this if + // we break out of inner to reach the target. instead of flowing out of normally, we break out of the outer, so we skip the target. + auto innerName = innerNames->at(nameCounter); + auto outerName = outerNames->at(nameCounter); + auto* ifFalse = iff->ifFalse; + // all assignments of label to the target can be replaced with breaks to the target, via innerName + struct JumpUpdater : public PostWalker<JumpUpdater, Visitor<JumpUpdater>> { + Index labelIndex; + Index targetNum; + Name targetName; + + void visitSetLocal(SetLocal* curr) { + if (curr->index == labelIndex) { + if (Index(curr->value->cast<Const>()->value.geti32()) == targetNum) { + replaceCurrent(Builder(*getModule()).makeBreak(targetName)); + } + } + } + }; + JumpUpdater updater; + updater.labelIndex = labelIndex; + updater.targetNum = num; + updater.targetName = innerName; + updater.setModule(getModule()); + updater.walk(origin); + // restructure code + auto* inner = builder.blockifyWithName(origin, innerName, builder.makeBreak(outerName)); + auto* outer = builder.makeSequence(inner, iff->ifTrue); + outer->name = outerName; + origin = outer; + // if another label value is checked here, handle that too + if (ifFalse) { + optimizeJumpsToLabelCheck(origin, ifFalse->cast<If>()); + } + } +}; + +// declare pass + +Pass *createRelooperJumpThreadingPass() { + return new RelooperJumpThreading(); +} + +} // namespace wasm + |