| /* |
| * 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. |
| */ |
| |
| // |
| // Removes obviously unneeded code |
| // |
| |
| #include <ir/block-utils.h> |
| #include <ir/effects.h> |
| #include <ir/literal-utils.h> |
| #include <ir/type-updating.h> |
| #include <ir/utils.h> |
| #include <pass.h> |
| #include <wasm-builder.h> |
| #include <wasm.h> |
| |
| namespace wasm { |
| |
| struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum>> { |
| bool isFunctionParallel() override { return true; } |
| |
| Pass* create() override { return new Vacuum; } |
| |
| TypeUpdater typeUpdater; |
| |
| Expression* replaceCurrent(Expression* expression) { |
| auto* old = getCurrent(); |
| super::replaceCurrent(expression); |
| // also update the type updater |
| typeUpdater.noteReplacement(old, expression); |
| return expression; |
| } |
| |
| void doWalkFunction(Function* func) { |
| typeUpdater.walk(func->body); |
| walk(func->body); |
| } |
| |
| // Returns nullptr if curr is dead, curr if it must stay as is, or another |
| // node if it can be replaced. Takes into account: |
| // * The result may be used or unused. |
| // * The type may or may not matter (a drop can drop anything, for example). |
| Expression* optimize(Expression* curr, bool resultUsed, bool typeMatters) { |
| FeatureSet features = getModule()->features; |
| auto type = curr->type; |
| // An unreachable node must not be changed. |
| if (type == Type::unreachable) { |
| return curr; |
| } |
| // We iterate on possible replacements. If a replacement changes the type, |
| // stop and go back. |
| auto* prev = curr; |
| while (1) { |
| if (typeMatters && curr->type != type) { |
| return prev; |
| } |
| prev = curr; |
| switch (curr->_id) { |
| case Expression::Id::NopId: |
| return nullptr; // never needed |
| |
| case Expression::Id::BlockId: |
| return curr; // not always needed, but handled in visitBlock() |
| case Expression::Id::IfId: |
| return curr; // not always needed, but handled in visitIf() |
| case Expression::Id::LoopId: |
| return curr; // not always needed, but handled in visitLoop() |
| case Expression::Id::DropId: |
| return curr; // not always needed, but handled in visitDrop() |
| case Expression::Id::TryId: |
| return curr; // not always needed, but handled in visitTry() |
| |
| case Expression::Id::BreakId: |
| case Expression::Id::SwitchId: |
| case Expression::Id::BrOnExnId: |
| case Expression::Id::CallId: |
| case Expression::Id::CallIndirectId: |
| case Expression::Id::LocalSetId: |
| case Expression::Id::StoreId: |
| case Expression::Id::ReturnId: |
| case Expression::Id::GlobalSetId: |
| case Expression::Id::HostId: |
| case Expression::Id::UnreachableId: |
| return curr; // always needed |
| |
| case Expression::Id::LoadId: { |
| // it is ok to remove a load if the result is not used, and it has no |
| // side effects (the load itself may trap, if we are not ignoring such |
| // things) |
| auto* load = curr->cast<Load>(); |
| if (!resultUsed && !EffectAnalyzer(getPassOptions(), features, curr) |
| .hasSideEffects()) { |
| if (!typeMatters || load->ptr->type == type) { |
| return load->ptr; |
| } |
| } |
| return curr; |
| } |
| case Expression::Id::ConstId: |
| case Expression::Id::LocalGetId: |
| case Expression::Id::GlobalGetId: { |
| if (!resultUsed) { |
| return nullptr; |
| } |
| return curr; |
| } |
| |
| case Expression::Id::UnaryId: |
| case Expression::Id::BinaryId: |
| case Expression::Id::SelectId: { |
| if (resultUsed) { |
| return curr; // used, keep it |
| } |
| // for unary, binary, and select, we need to check their arguments for |
| // side effects, as well as the node itself, as some unaries and |
| // binaries have implicit traps |
| if (auto* unary = curr->dynCast<Unary>()) { |
| EffectAnalyzer tester(getPassOptions(), features); |
| tester.visitUnary(unary); |
| if (tester.hasSideEffects()) { |
| return curr; |
| } |
| if (EffectAnalyzer(getPassOptions(), features, unary->value) |
| .hasSideEffects()) { |
| curr = unary->value; |
| continue; |
| } else { |
| return nullptr; |
| } |
| } else if (auto* binary = curr->dynCast<Binary>()) { |
| EffectAnalyzer tester(getPassOptions(), features); |
| tester.visitBinary(binary); |
| if (tester.hasSideEffects()) { |
| return curr; |
| } |
| if (EffectAnalyzer(getPassOptions(), features, binary->left) |
| .hasSideEffects()) { |
| if (EffectAnalyzer(getPassOptions(), features, binary->right) |
| .hasSideEffects()) { |
| return curr; // leave them |
| } else { |
| curr = binary->left; |
| continue; |
| } |
| } else { |
| if (EffectAnalyzer(getPassOptions(), features, binary->right) |
| .hasSideEffects()) { |
| curr = binary->right; |
| continue; |
| } else { |
| return nullptr; |
| } |
| } |
| } else { |
| // TODO: if two have side effects, we could replace the select with |
| // say an add? |
| auto* select = curr->cast<Select>(); |
| if (EffectAnalyzer(getPassOptions(), features, select->ifTrue) |
| .hasSideEffects()) { |
| if (EffectAnalyzer(getPassOptions(), features, select->ifFalse) |
| .hasSideEffects()) { |
| return curr; // leave them |
| } else { |
| if (EffectAnalyzer( |
| getPassOptions(), features, select->condition) |
| .hasSideEffects()) { |
| return curr; // leave them |
| } else { |
| curr = select->ifTrue; |
| continue; |
| } |
| } |
| } else { |
| if (EffectAnalyzer(getPassOptions(), features, select->ifFalse) |
| .hasSideEffects()) { |
| if (EffectAnalyzer( |
| getPassOptions(), features, select->condition) |
| .hasSideEffects()) { |
| return curr; // leave them |
| } else { |
| curr = select->ifFalse; |
| continue; |
| } |
| } else { |
| if (EffectAnalyzer( |
| getPassOptions(), features, select->condition) |
| .hasSideEffects()) { |
| curr = select->condition; |
| continue; |
| } else { |
| return nullptr; |
| } |
| } |
| } |
| } |
| } |
| |
| default: |
| return curr; // assume needed |
| } |
| } |
| } |
| |
| void visitBlock(Block* curr) { |
| // compress out nops and other dead code |
| int skip = 0; |
| auto& list = curr->list; |
| size_t size = list.size(); |
| for (size_t z = 0; z < size; z++) { |
| auto* child = list[z]; |
| // The last element may be used. |
| bool used = |
| z == size - 1 && curr->type.isConcrete() && |
| ExpressionAnalyzer::isResultUsed(expressionStack, getFunction()); |
| auto* optimized = optimize(child, used, true); |
| if (!optimized) { |
| if (child->type.isConcrete()) { |
| // We can't just skip a final concrete element, even if it isn't used. |
| // Instead, replace it with something that's easy to optimize out (for |
| // example, code-folding can merge out identical zeros at the end of |
| // if arms). |
| optimized = LiteralUtils::makeZero(child->type, *getModule()); |
| } else if (child->type == Type::unreachable) { |
| // Don't try to optimize out an unreachable child (dce can do that |
| // properly). |
| optimized = child; |
| } |
| } |
| if (!optimized) { |
| typeUpdater.noteRecursiveRemoval(child); |
| skip++; |
| } else { |
| if (optimized != child) { |
| typeUpdater.noteReplacement(child, optimized); |
| list[z] = optimized; |
| } |
| if (skip > 0) { |
| list[z - skip] = list[z]; |
| list[z] = nullptr; |
| } |
| // if this is unreachable, the rest is dead code |
| if (list[z - skip]->type == Type::unreachable && z < size - 1) { |
| for (Index i = z - skip + 1; i < list.size(); i++) { |
| auto* remove = list[i]; |
| if (remove) { |
| typeUpdater.noteRecursiveRemoval(remove); |
| } |
| } |
| list.resize(z - skip + 1); |
| typeUpdater.maybeUpdateTypeToUnreachable(curr); |
| skip = 0; // nothing more to do on the list |
| break; |
| } |
| } |
| } |
| if (skip > 0) { |
| list.resize(size - skip); |
| typeUpdater.maybeUpdateTypeToUnreachable(curr); |
| } |
| // the block may now be a trivial one that we can get rid of and just leave |
| // its contents |
| replaceCurrent(BlockUtils::simplifyToContents(curr, this)); |
| } |
| |
| void visitIf(If* curr) { |
| // if the condition is a constant, just apply it |
| // we can just return the ifTrue or ifFalse. |
| if (auto* value = curr->condition->dynCast<Const>()) { |
| Expression* child; |
| if (value->value.getInteger()) { |
| child = curr->ifTrue; |
| if (curr->ifFalse) { |
| typeUpdater.noteRecursiveRemoval(curr->ifFalse); |
| } |
| } else { |
| if (curr->ifFalse) { |
| child = curr->ifFalse; |
| typeUpdater.noteRecursiveRemoval(curr->ifTrue); |
| } else { |
| typeUpdater.noteRecursiveRemoval(curr); |
| ExpressionManipulator::nop(curr); |
| return; |
| } |
| } |
| replaceCurrent(child); |
| return; |
| } |
| // if the condition is unreachable, just return it |
| if (curr->condition->type == Type::unreachable) { |
| typeUpdater.noteRecursiveRemoval(curr->ifTrue); |
| if (curr->ifFalse) { |
| typeUpdater.noteRecursiveRemoval(curr->ifFalse); |
| } |
| replaceCurrent(curr->condition); |
| return; |
| } |
| // from here on, we can assume the condition executed |
| if (curr->ifFalse) { |
| if (curr->ifFalse->is<Nop>()) { |
| curr->ifFalse = nullptr; |
| } else if (curr->ifTrue->is<Nop>()) { |
| curr->ifTrue = curr->ifFalse; |
| curr->ifFalse = nullptr; |
| curr->condition = |
| Builder(*getModule()).makeUnary(EqZInt32, curr->condition); |
| } else if (curr->ifTrue->is<Drop>() && curr->ifFalse->is<Drop>()) { |
| // instead of dropping both sides, drop the if, if they are the same |
| // type |
| auto* left = curr->ifTrue->cast<Drop>()->value; |
| auto* right = curr->ifFalse->cast<Drop>()->value; |
| if (left->type == right->type) { |
| curr->ifTrue = left; |
| curr->ifFalse = right; |
| curr->finalize(); |
| replaceCurrent(Builder(*getModule()).makeDrop(curr)); |
| } |
| } |
| } else { |
| // no else |
| if (curr->ifTrue->is<Nop>()) { |
| // no nothing |
| replaceCurrent(Builder(*getModule()).makeDrop(curr->condition)); |
| } |
| } |
| } |
| |
| void visitLoop(Loop* curr) { |
| if (curr->body->is<Nop>()) { |
| ExpressionManipulator::nop(curr); |
| } |
| } |
| |
| void visitDrop(Drop* curr) { |
| // optimize the dropped value, maybe leaving nothing |
| curr->value = optimize(curr->value, false, false); |
| if (curr->value == nullptr) { |
| ExpressionManipulator::nop(curr); |
| return; |
| } |
| // a drop of a tee is a set |
| if (auto* set = curr->value->dynCast<LocalSet>()) { |
| assert(set->isTee()); |
| set->makeSet(); |
| replaceCurrent(set); |
| return; |
| } |
| // if we are dropping a block's return value, we might be able to remove it |
| // entirely |
| if (auto* block = curr->value->dynCast<Block>()) { |
| auto* last = block->list.back(); |
| // note that the last element may be concrete but not the block, if the |
| // block has an unreachable element in the middle, making the block |
| // unreachable despite later elements and in particular the last |
| if (last->type.isConcrete() && block->type == last->type) { |
| last = optimize(last, false, false); |
| if (!last) { |
| // we may be able to remove this, if there are no brs |
| bool canPop = true; |
| if (block->name.is()) { |
| BranchUtils::BranchSeeker seeker(block->name); |
| Expression* temp = block; |
| seeker.walk(temp); |
| if (seeker.found && seeker.valueType != Type::none) { |
| canPop = false; |
| } |
| } |
| if (canPop) { |
| block->list.back() = last; |
| block->list.pop_back(); |
| block->type = Type::none; |
| // we don't need the drop anymore, let's see what we have left in |
| // the block |
| if (block->list.size() > 1) { |
| replaceCurrent(block); |
| } else if (block->list.size() == 1) { |
| replaceCurrent(block->list[0]); |
| } else { |
| ExpressionManipulator::nop(curr); |
| } |
| return; |
| } |
| } |
| } |
| } |
| // sink a drop into an arm of an if-else if the other arm ends in an |
| // unreachable, as it if is a branch, this can make that branch optimizable |
| // and more vaccuming possible |
| auto* iff = curr->value->dynCast<If>(); |
| if (iff && iff->ifFalse && iff->type.isConcrete()) { |
| // reuse the drop in both cases |
| if (iff->ifTrue->type == Type::unreachable && |
| iff->ifFalse->type.isConcrete()) { |
| curr->value = iff->ifFalse; |
| iff->ifFalse = curr; |
| iff->type = Type::none; |
| replaceCurrent(iff); |
| } else if (iff->ifFalse->type == Type::unreachable && |
| iff->ifTrue->type.isConcrete()) { |
| curr->value = iff->ifTrue; |
| iff->ifTrue = curr; |
| iff->type = Type::none; |
| replaceCurrent(iff); |
| } |
| } |
| } |
| |
| void visitTry(Try* curr) { |
| // If try's body does not throw, the whole try-catch can be replaced with |
| // the try's body. |
| if (!EffectAnalyzer(getPassOptions(), getModule()->features, curr->body) |
| .throws) { |
| replaceCurrent(curr->body); |
| } |
| } |
| |
| void visitFunction(Function* curr) { |
| auto* optimized = |
| optimize(curr->body, curr->sig.results != Type::none, true); |
| if (optimized) { |
| curr->body = optimized; |
| } else { |
| ExpressionManipulator::nop(curr->body); |
| } |
| if (curr->sig.results == Type::none && |
| !EffectAnalyzer(getPassOptions(), getModule()->features, curr->body) |
| .hasSideEffects()) { |
| ExpressionManipulator::nop(curr->body); |
| } |
| } |
| }; |
| |
| Pass* createVacuumPass() { return new Vacuum(); } |
| |
| } // namespace wasm |