| /* |
| * 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 dead, i.e. unreachable, code. |
| // |
| // We keep a record of when control flow is reachable. When it isn't, we |
| // kill (turn into unreachable). We then fold away entire unreachable |
| // expressions. |
| // |
| // When dead code causes an operation to not happen, like a store, a call |
| // or an add, we replace with a block with a list of what does happen. |
| // That isn't necessarily smaller, but blocks are friendlier to other |
| // optimizations: blocks can be merged and eliminated, and they clearly |
| // have no side effects. |
| // |
| |
| #include "ir/eh-utils.h" |
| #include "ir/iteration.h" |
| #include "ir/properties.h" |
| #include "ir/type-updating.h" |
| #include "pass.h" |
| #include "vector" |
| #include "wasm-builder.h" |
| #include "wasm.h" |
| |
| namespace wasm { |
| |
| struct DeadCodeElimination |
| : public WalkerPass< |
| PostWalker<DeadCodeElimination, |
| UnifiedExpressionVisitor<DeadCodeElimination>>> { |
| bool isFunctionParallel() override { return true; } |
| |
| // This pass removes dead code, which can only help validation (a dead |
| // local.get might have prevented validation). |
| bool requiresNonNullableLocalFixups() override { return false; } |
| |
| std::unique_ptr<Pass> create() override { |
| return std::make_unique<DeadCodeElimination>(); |
| } |
| |
| // as we remove code, we must keep the types of other nodes valid |
| TypeUpdater typeUpdater; |
| |
| // Information used to decide whether we need EH fixups at the end |
| bool hasPop = false; // Do we have a 'pop' in this function? |
| bool addedBlock = false; // Have we added blocks in this function? |
| |
| Expression* replaceCurrent(Expression* expression) { |
| auto* old = getCurrent(); |
| if (old == expression) { |
| return expression; |
| } |
| 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); |
| } |
| |
| void visitExpression(Expression* curr) { |
| if (curr->is<Pop>()) { |
| hasPop = true; |
| } |
| |
| if (!Properties::isControlFlowStructure(curr)) { |
| // Control flow structures require special handling, but others are |
| // simple. |
| if (curr->type == Type::unreachable) { |
| // This may be dead code. Check if there is an unreachable child. |
| bool hasUnreachableChild = false; |
| for (auto* child : ChildIterator(curr)) { |
| if (child->type == Type::unreachable) { |
| hasUnreachableChild = true; |
| break; |
| } |
| } |
| if (hasUnreachableChild) { |
| // This is indeed unreachable code, made unreachable by that child. |
| Builder builder(*getModule()); |
| std::vector<Expression*> remainingChildren; |
| bool afterUnreachable = false; |
| for (auto* child : ChildIterator(curr)) { |
| if (afterUnreachable) { |
| typeUpdater.noteRecursiveRemoval(child); |
| continue; |
| } |
| if (child->type == Type::unreachable) { |
| remainingChildren.push_back(child); |
| afterUnreachable = true; |
| } else { |
| remainingChildren.push_back(builder.makeDrop(child)); |
| } |
| } |
| if (remainingChildren.size() == 1) { |
| replaceCurrent(remainingChildren[0]); |
| } else { |
| addedBlock = true; |
| replaceCurrent(builder.makeBlock(remainingChildren)); |
| } |
| } |
| } |
| return; |
| } |
| // This is a control flow structure. |
| if (auto* block = curr->dynCast<Block>()) { |
| auto& list = block->list; |
| // The index from which to remove, which is one after the first |
| // unreachable instruction. Note that 0 is not a valid value, so we can |
| // use it as such. |
| Index removeFromHere = 0; |
| for (Index i = 0; i < list.size(); i++) { |
| if (list[i]->type == Type::unreachable) { |
| removeFromHere = i + 1; |
| break; |
| } |
| } |
| if (removeFromHere != 0) { |
| for (Index i = removeFromHere; i < list.size(); i++) { |
| typeUpdater.noteRecursiveRemoval(list[i]); |
| } |
| list.resize(removeFromHere); |
| if (list.size() == 1 && list[0]->is<Unreachable>()) { |
| replaceCurrent(list[0]); |
| return; |
| } |
| } |
| // Finally, if there is no need for a concrete type (which is when there |
| // is one marked, but nothing breaks to it, and also the block does not |
| // have a concrete value flowing out) then remove it, which may allow |
| // more reduction. |
| if (block->type.isConcrete() && list.back()->type == Type::unreachable && |
| !typeUpdater.hasBreaks(block)) { |
| typeUpdater.changeType(block, Type::unreachable); |
| } |
| } else if (auto* iff = curr->dynCast<If>()) { |
| if (iff->condition->type == Type::unreachable) { |
| typeUpdater.noteRecursiveRemoval(iff->ifTrue); |
| if (iff->ifFalse) { |
| typeUpdater.noteRecursiveRemoval(iff->ifFalse); |
| } |
| replaceCurrent(iff->condition); |
| return; |
| } |
| // If both arms are unreachable, there is no need for a concrete type, |
| // which may allow more reduction. |
| if (iff->type != Type::unreachable && iff->ifFalse && |
| iff->ifTrue->type == Type::unreachable && |
| iff->ifFalse->type == Type::unreachable) { |
| typeUpdater.changeType(iff, Type::unreachable); |
| } |
| } else if (auto* loop = curr->dynCast<Loop>()) { |
| // The loop body may have unreachable type if it branches back to the |
| // loop top, for example. The only case we look for here is where we've |
| // already removed the entire body as dead code. |
| if (loop->body->is<Unreachable>()) { |
| replaceCurrent(loop->body); |
| } |
| } else if (auto* tryy = curr->dynCast<Try>()) { |
| // If both try body and catch body are unreachable, there is no need for a |
| // concrete type, which may allow more reduction. |
| bool allCatchesUnreachable = true; |
| for (auto* catchBody : tryy->catchBodies) { |
| allCatchesUnreachable &= catchBody->type == Type::unreachable; |
| } |
| if (tryy->type != Type::unreachable && |
| tryy->body->type == Type::unreachable && allCatchesUnreachable) { |
| typeUpdater.changeType(tryy, Type::unreachable); |
| } |
| } else if (auto* tryTable = curr->dynCast<TryTable>()) { |
| // try_table can finish normally only if its body finishes normally. |
| if (tryTable->type != Type::unreachable && |
| tryTable->body->type == Type::unreachable) { |
| typeUpdater.changeType(tryTable, Type::unreachable); |
| } |
| } else { |
| WASM_UNREACHABLE("unimplemented DCE control flow structure"); |
| } |
| } |
| |
| void visitFunction(Function* curr) { |
| // We conservatively run the EH pop fixup if this function has a 'pop' and |
| // if we have ever added blocks in the optimization, which may have moved |
| // pops into the blocks. |
| if (hasPop && addedBlock) { |
| EHUtils::handleBlockNestedPops(curr, *getModule()); |
| } |
| } |
| }; |
| |
| Pass* createDeadCodeEliminationPass() { return new DeadCodeElimination(); } |
| |
| } // namespace wasm |