| /* |
| * 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 "ir/manipulation.h" |
| #include "ir/utils.h" |
| #include "pass.h" |
| #include "wasm.h" |
| |
| namespace wasm { |
| |
| static Name LABEL("label"); |
| |
| static Name getInnerName(int i) { |
| return Name(std::string("__rjti$") + std::to_string(i)); |
| } |
| |
| static Name getOuterName(int i) { |
| return Name(std::string("__rjto$") + 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<LocalGet>(); |
| 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 LocalSet* isLabelSettingLocalSet(Expression* curr, Index labelIndex) { |
| if (!curr) { |
| return nullptr; |
| } |
| auto* set = curr->dynCast<LocalSet>(); |
| if (!set) { |
| return nullptr; |
| } |
| if (set->index != labelIndex) { |
| return nullptr; |
| } |
| return set; |
| } |
| |
| static Index getSetLabelValue(LocalSet* set) { |
| return set->value->cast<Const>()->value.geti32(); |
| } |
| |
| struct LabelUseFinder : public PostWalker<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 visitLocalSet(LocalSet* curr) { |
| if (isLabelSettingLocalSet(curr, labelIndex)) { |
| sets[getSetLabelValue(curr)]++; |
| } |
| } |
| }; |
| |
| struct RelooperJumpThreading |
| : public WalkerPass<ExpressionStackWalker<RelooperJumpThreading>> { |
| bool isFunctionParallel() override { return true; } |
| |
| Pass* create() override { return new RelooperJumpThreading; } |
| |
| 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 |
| |
| // must be size 1, a relooper multiple will have its own label, |
| // and is an if-else sequence and nothing more |
| assert(holder->list.size() == 1); |
| 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); |
| super::doWalkFunction(func); |
| } |
| } |
| |
| void visitFunction(Function* curr) { |
| // we may alter types |
| ReFinalize().walkFunctionInModule(curr, getModule()); |
| } |
| |
| 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]); |
| // the label is set outside of the origin |
| // if the only other location is inside the if body, then it is ok - it |
| // must be in a loop and returning to the top of the loop body, so we |
| // don't need to do anything for that label setting anyhow |
| std::map<Index, Index> labelChecksInIfTrue; |
| std::map<Index, Index> labelSetsInIfTrue; |
| LabelUseFinder finder( |
| labelIndex, labelChecksInIfTrue, labelSetsInIfTrue); |
| finder.walk(iff->ifTrue); |
| if (labelSetsInOrigin[num] + labelSetsInIfTrue[num] < labelSets[num]) { |
| // label set somewhere we can't see now, could be irreducible control |
| // flow |
| // TODO: one case where this happens is instead of an if-chain, we |
| // have ifs and a switch on label|0, in separate elements. |
| // perhaps not emitting switches on label|0 in the relooper |
| // would avoid that. |
| return true; |
| } |
| } |
| 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++; |
| 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 = getInnerName(nameCounter); |
| auto outerName = getOuterName(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> { |
| Index labelIndex; |
| Index targetNum; |
| Name targetName; |
| |
| void visitLocalSet(LocalSet* 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 |