/* * 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* innerNames = nullptr; std::vector* outerNames = nullptr; struct NameEnsurer { NameEnsurer() { assert(!innerNames); assert(!outerNames); innerNames = new std::vector; outerNames = new std::vector; 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))); } } }; struct RelooperJumpThreading : public WalkerPass>> { bool isFunctionParallel() override { return true; } Pass* create() override { return new RelooperJumpThreading; } void prepareToRun(PassRunner* runner, Module* module) override { static NameEnsurer ensurer; } 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++) { Index origin = i; for (Index j = i + 1; j < list.size(); j++) { if (auto* iff = isLabelCheckingIf(list[j])) { 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])) { // 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); WalkerPass>>::doWalkFunction(func); } } private: If* isLabelCheckingIf(Expression* curr) { 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; } // 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 = iff->condition->cast()->right->cast()->value.geti32(); // 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> { Index labelIndex; Index targetNum; Name targetName; void visitSetLocal(SetLocal* 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