/* * 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 (!iff) { return nullptr; } auto* condition = iff->condition->dynCast(); if (!(condition && condition->op == EqInt32)) { return nullptr; } auto* left = condition->left->dynCast(); if (!(left && left->index == labelIndex)) { return nullptr; } return iff; } static Index getCheckedLabelValue(If* iff) { return iff->condition->cast()->right->cast()->value.geti32(); } static LocalSet* isLabelSettingLocalSet(Expression* curr, Index labelIndex) { if (!curr) { return nullptr; } auto* set = curr->dynCast(); if (!set) { return nullptr; } if (set->index != labelIndex) { return nullptr; } return set; } static Index getSetLabelValue(LocalSet* set) { return set->value->cast()->value.geti32(); } struct LabelUseFinder : public PostWalker { Index labelIndex; std::map& checks; // label value => number of checks on it std::map& sets; // label value => number of sets to it LabelUseFinder(Index labelIndex, std::map& checks, std::map& 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> { bool isFunctionParallel() override { return true; } Pass* create() override { return new RelooperJumpThreading; } std::map labelChecks; std::map 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()) { 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 labelChecksInOrigin; std::map 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 labelChecksInIfTrue; std::map 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 { Index labelIndex; Index targetNum; Name targetName; void visitLocalSet(LocalSet* curr) { if (curr->index == labelIndex) { if (Index(curr->value->cast()->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()); } } }; // declare pass Pass* createRelooperJumpThreadingPass() { return new RelooperJumpThreading(); } } // namespace wasm