void visitFunction(Function* curr) { auto* optimized = optimize(curr->body, curr->result != none); if (optimized) { curr->body = optimized; } else { ExpressionManipulator::nop(curr->body); } if (curr->result == none && !EffectAnalyzer(getPassOptions(), curr->body).hasSideEffects()) { ExpressionManipulator::nop(curr->body); } }
void visitBlock(Block *curr) { // compress out nops and other dead code int skip = 0; auto& list = curr->list; size_t size = list.size(); bool needResize = false; for (size_t z = 0; z < size; z++) { auto* optimized = optimize(list[z], z == size - 1 && isConcreteWasmType(curr->type)); if (!optimized) { skip++; needResize = true; } else { if (optimized != list[z]) { list[z] = optimized; } if (skip > 0) { list[z - skip] = list[z]; } // if this is an unconditional br, the rest is dead code Break* br = list[z - skip]->dynCast<Break>(); Switch* sw = list[z - skip]->dynCast<Switch>(); if ((br && !br->condition) || sw) { auto* last = list.back(); list.resize(z - skip + 1); // if we removed the last one, and it was a return value, it must be returned if (list.back() != last && isConcreteWasmType(last->type)) { list.push_back(last); } needResize = false; break; } } } if (needResize) { list.resize(size - skip); } if (!curr->name.is()) { if (list.size() == 1) { // just one element. replace the block, either with it or with a nop if it's not needed if (isConcreteWasmType(curr->type) || EffectAnalyzer(getPassOptions(), list[0]).hasSideEffects()) { replaceCurrent(list[0]); } else { if (curr->type == unreachable) { ExpressionManipulator::convert<Block, Unreachable>(curr); } else { ExpressionManipulator::nop(curr); } } } else if (list.size() == 0) { ExpressionManipulator::nop(curr); } } }
void visitLoop(Loop* loop) { // We accumulate all the code we can move out, and will place it // in a block just preceding the loop. std::vector<Expression*> movedCode; // Accumulate effects of things we can't move out - things // we move out later must cross them, so we must verify it // is ok to do so. EffectAnalyzer effectsSoFar(getPassOptions()); // The loop's total effects also matter. For example, a store // in the loop means we can't move a load outside. // FIXME: we look at the loop "tail" area too, after the last // possible branch back, which can cause false positives // for bad effect interactions. EffectAnalyzer loopEffects(getPassOptions(), loop); // Note all the sets in each loop, and how many per index. Currently // EffectAnalyzer can't do that, and we need it to know if we // can move a set out of the loop (if there is another set // still there, we can't). Another possible option here is for // LocalGraph to track interfering sets. TODO // FIXME: also the loop tail issue from above. auto numLocals = getFunction()->getNumLocals(); std::vector<Index> numSetsForIndex(numLocals); std::fill(numSetsForIndex.begin(), numSetsForIndex.end(), 0); LoopSets loopSets; { FindAll<SetLocal> finder(loop); for (auto* set : finder.list) { numSetsForIndex[set->index]++; loopSets.insert(set); } } // Walk along the loop entrance, while all the code there // is executed unconditionally. That is the code we want to // move out - anything that might or might not be executed // may be best left alone anyhow. std::vector<Expression**> work; work.push_back(&loop->body); while (!work.empty()) { auto** currp = work.back(); work.pop_back(); auto* curr = *currp; // Look into blocks. if (auto* block = curr->dynCast<Block>()) { auto& list = block->list; Index i = list.size(); while (i > 0) { i--; work.push_back(&list[i]); } continue; // Note that if the block had a merge at the end, we would have seen // a branch to it anyhow, so we would stop before that point anyhow. } // If this may branch, we are done. EffectAnalyzer effects(getPassOptions(), curr); if (effects.branches) { break; } if (interestingToMove(curr)) { // Let's see if we can move this out. // Global side effects would prevent this - we might end up // executing them just once. // And we must also move across anything not moved out already, // so check for issues there too. // The rest of the loop's effects matter too, we must also // take into account global state like interacting loads and // stores. bool unsafeToMove = effects.hasGlobalSideEffects() || effectsSoFar.invalidates(effects) || (effects.noticesGlobalSideEffects() && loopEffects.hasGlobalSideEffects()); if (!unsafeToMove) { // So far so good. Check if our local dependencies are all // outside of the loop, in which case everything is good - // either they are before the loop and constant for us, or // they are after and don't matter. if (effects.localsRead.empty() || !hasGetDependingOnLoopSet(curr, loopSets)) { // We have checked if our gets are influenced by sets in the loop, and // must also check if our sets interfere with them. To do so, assume // temporarily that we are moving curr out; see if any sets remain for // its indexes. FindAll<SetLocal> currSets(curr); for (auto* set : currSets.list) { assert(numSetsForIndex[set->index] > 0); numSetsForIndex[set->index]--; } bool canMove = true; for (auto* set : currSets.list) { if (numSetsForIndex[set->index] > 0) { canMove = false; break; } } if (!canMove) { // We failed to move the code, undo those changes. for (auto* set : currSets.list) { numSetsForIndex[set->index]++; } } else { // We can move it! Leave the changes, move the code, and update // loopSets. movedCode.push_back(curr); *currp = Builder(*getModule()).makeNop(); for (auto* set : currSets.list) { loopSets.erase(set); } continue; } } } } // We did not move this item. Accumulate its effects. effectsSoFar.mergeIn(effects); } // If we moved the code out, finish up by emitting it // outside of the loop. // Note that this works with nested loops - after moving outside // of an inner loop, we can encounter it again in an outer loop, // and move it further outside, without requiring any extra pass. if (!movedCode.empty()) { // Finish the moving by emitting the code outside. Builder builder(*getModule()); auto* ret = builder.makeBlock(movedCode); ret->list.push_back(loop); ret->finalize(loop->type); replaceCurrent(ret); // Note that we do not need to modify the localGraph - we keep // each get in a position to be influenced by exactly the same // sets as before. } }
// returns nullptr if curr is dead, curr if it must stay as is, or another node if it can be replaced Expression* optimize(Expression* curr, bool resultUsed) { while (1) { 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::BreakId: case Expression::Id::SwitchId: case Expression::Id::CallId: case Expression::Id::CallImportId: case Expression::Id::CallIndirectId: case Expression::Id::SetLocalId: case Expression::Id::StoreId: case Expression::Id::ReturnId: case Expression::Id::SetGlobalId: case Expression::Id::HostId: case Expression::Id::UnreachableId: return curr; // always needed case Expression::Id::LoadId: { if (!resultUsed) { return curr->cast<Load>()->ptr; } return curr; } case Expression::Id::ConstId: case Expression::Id::GetLocalId: case Expression::Id::GetGlobalId: { 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 if (auto* unary = curr->dynCast<Unary>()) { if (EffectAnalyzer(getPassOptions(), unary->value).hasSideEffects()) { curr = unary->value; continue; } else { return nullptr; } } else if (auto* binary = curr->dynCast<Binary>()) { if (EffectAnalyzer(getPassOptions(), binary->left).hasSideEffects()) { if (EffectAnalyzer(getPassOptions(), binary->right).hasSideEffects()) { return curr; // leave them } else { curr = binary->left; continue; } } else { if (EffectAnalyzer(getPassOptions(), 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(), select->ifTrue).hasSideEffects()) { if (EffectAnalyzer(getPassOptions(), select->ifFalse).hasSideEffects()) { return curr; // leave them } else { if (EffectAnalyzer(getPassOptions(), select->condition).hasSideEffects()) { return curr; // leave them } else { curr = select->ifTrue; continue; } } } else { if (EffectAnalyzer(getPassOptions(), select->ifFalse).hasSideEffects()) { if (EffectAnalyzer(getPassOptions(), select->condition).hasSideEffects()) { return curr; // leave them } else { curr = select->ifFalse; continue; } } else { if (EffectAnalyzer(getPassOptions(), select->condition).hasSideEffects()) { curr = select->condition; continue; } else { return nullptr; } } } } } default: WASM_UNREACHABLE(); } } }