Block* optimize(Expression* curr, Expression*& child, Block* outer = nullptr, Expression** dependency1 = nullptr, Expression** dependency2 = nullptr) { if (!child) return outer; if (dependency1 && *dependency1 && EffectAnalyzer(*dependency1).hasSideEffects()) return outer; if (dependency2 && *dependency2 && EffectAnalyzer(*dependency2).hasSideEffects()) return outer; if (auto* block = child->dynCast<Block>()) { if (!block->name.is() && block->list.size() >= 2) { child = block->list.back(); if (outer == nullptr) { // reuse the block, move it out block->list.back() = curr; block->finalize(); // last block element was our input, and is now our output, which may differ TODO optimize replaceCurrent(block); return block; } else { // append to an existing outer block assert(outer->list.back() == curr); outer->list.pop_back(); for (Index i = 0; i < block->list.size() - 1; i++) { outer->list.push_back(block->list[i]); } outer->list.push_back(curr); } } } return outer; }
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 visitCallIndirect(CallIndirect* curr) { auto* outer = optimize(curr, curr->target); if (EffectAnalyzer(curr->target).hasSideEffects()) return; handleCall(curr, outer); }
void handleCall(T* curr, Block* outer = nullptr) { for (Index i = 0; i < curr->operands.size(); i++) { outer = optimize(curr, curr->operands[i], outer); if (EffectAnalyzer(curr->operands[i]).hasSideEffects()) return; } }
// 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(); } } }