/* * Copyright 2022 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. */ #include "ir/iteration.h" #include "ir/local-structural-dominance.h" #include "support/small_vector.h" namespace wasm { LocalStructuralDominance::LocalStructuralDominance(Function* func, Module& wasm, Mode mode) { if (!wasm.features.hasReferenceTypes()) { // No references, so nothing to look at. return; } bool hasRefVar = false; for (auto var : func->vars) { for (auto type : var) { if (type.isRef()) { hasRefVar = true; break; } } } if (!hasRefVar) { return; } if (mode == NonNullableOnly) { bool hasNonNullableVar = false; for (auto var : func->vars) { for (auto type : var) { // Check if we have any non-nullable vars (or tuple vars with // non-nullable elements) at all. if (type.isNonNullable()) { hasNonNullableVar = true; break; } } } if (!hasNonNullableVar) { return; } } struct Scanner : public PostWalker { std::set& nonDominatingIndices; // The locals that have been set, and so at the current time, they // structurally dominate. std::vector localsSet; Scanner(Function* func, Mode mode, std::set& nonDominatingIndices) : nonDominatingIndices(nonDominatingIndices) { localsSet.resize(func->getNumLocals()); // Parameters always dominate. for (Index i = 0; i < func->getNumParams(); i++) { localsSet[i] = true; } for (Index i = func->getNumParams(); i < func->getNumLocals(); i++) { auto localType = func->getLocalType(i); bool interesting = false; for (auto type : localType) { if (type.isRef() && (mode == All || type.isNonNullable())) { interesting = true; break; } } // Mark locals we don't need to care about as "set". We never do any // work for such a local. if (!interesting) { localsSet[i] = true; } } // Note that we do not need to start a scope for the function body. // Logically there is a scope there, but there is no code after it, so // there is nothing to clean up when that scope exits, so we may as well // not even create a scope. Just start walking the body now. walk(func->body); } using Locals = SmallVector; // When we exit a control flow scope, we must undo the locals that it set. std::vector cleanupStack; static void doBeginScope(Scanner* self, Expression** currp) { self->cleanupStack.emplace_back(); } static void doEndScope(Scanner* self, Expression** currp) { for (auto index : self->cleanupStack.back()) { assert(self->localsSet[index]); self->localsSet[index] = false; } self->cleanupStack.pop_back(); } static void doLocalSet(Scanner* self, Expression** currp) { auto index = (*currp)->cast()->index; if (!self->localsSet[index]) { // This local is now set until the end of this scope. self->localsSet[index] = true; // If we are not in the topmost scope, note this for later cleanup. if (!self->cleanupStack.empty()) { self->cleanupStack.back().push_back(index); } } } static void scan(Scanner* self, Expression** currp) { // Use a loop to avoid recursing on the last child - we can just go // straight into a loop iteration for it. while (1) { Expression* curr = *currp; switch (curr->_id) { case Expression::Id::InvalidId: WASM_UNREACHABLE("bad id"); // local.get can just be visited immediately, as it has no children. case Expression::Id::LocalGetId: { auto index = curr->cast()->index; if (!self->localsSet[index]) { self->nonDominatingIndices.insert(index); } return; } case Expression::Id::LocalSetId: { auto* set = curr->cast(); if (!self->localsSet[set->index]) { self->pushTask(doLocalSet, currp); } // Immediately continue in the loop. currp = &set->value; continue; } // Control flow structures. case Expression::Id::BlockId: { auto* block = curr->cast(); // Blocks with no name are never emitted in the binary format, so do // not create a scope for them. if (block->name.is()) { self->pushTask(Scanner::doEndScope, currp); } auto& list = block->list; for (int i = int(list.size()) - 1; i >= 0; i--) { self->pushTask(Scanner::scan, &list[i]); } if (block->name.is()) { // Just call the task immediately. doBeginScope(self, currp); } return; } case Expression::Id::IfId: { if (curr->cast()->ifFalse) { self->pushTask(Scanner::doEndScope, currp); self->maybePushTask(Scanner::scan, &curr->cast()->ifFalse); self->pushTask(Scanner::doBeginScope, currp); } self->pushTask(Scanner::doEndScope, currp); self->pushTask(Scanner::scan, &curr->cast()->ifTrue); self->pushTask(Scanner::doBeginScope, currp); // Immediately continue in the loop. currp = &curr->cast()->condition; continue; } case Expression::Id::LoopId: { self->pushTask(Scanner::doEndScope, currp); // Just call the task immediately. doBeginScope(self, currp); // Immediately continue in the loop. currp = &curr->cast()->body; continue; } case Expression::Id::TryId: { auto& list = curr->cast()->catchBodies; for (int i = int(list.size()) - 1; i >= 0; i--) { self->pushTask(Scanner::doEndScope, currp); self->pushTask(Scanner::scan, &list[i]); self->pushTask(Scanner::doBeginScope, currp); } self->pushTask(Scanner::doEndScope, currp); // Just call the task immediately. doBeginScope(self, currp); // Immediately continue in the loop. currp = &curr->cast()->body; continue; } case Expression::Id::TryTableId: { self->pushTask(Scanner::doEndScope, currp); // Just call the task immediately. doBeginScope(self, currp); // Immediately continue in the try_table. currp = &curr->cast()->body; continue; } default: { // Control flow structures have been handled. This is an expression, // which we scan normally. assert(!Properties::isControlFlowStructure(curr)); PostWalker::scan(self, currp); return; } } } } // Only local.set needs to be visited. void pushTask(TaskFunc func, Expression** currp) { // Visits to anything but a set can be ignored, so only very specific // tasks need to actually be pushed here. In particular, we don't want to // push tasks to call doVisit* when those callbacks do nothing. if (func == scan || func == doLocalSet || func == doBeginScope || func == doEndScope) { PostWalker::pushTask(func, currp); } } void maybePushTask(TaskFunc func, Expression** currp) { if (*currp) { pushTask(func, currp); } } }; Scanner(func, mode, nonDominatingIndices); } } // namespace wasm