| /* | |
| * 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<Scanner> { | |
| std::set<Index>& nonDominatingIndices; | |
| // The locals that have been set, and so at the current time, they | |
| // structurally dominate. | |
| std::vector<bool> localsSet; | |
| Scanner(Function* func, Mode mode, std::set<Index>& 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<Index, 5>; | |
| // When we exit a control flow scope, we must undo the locals that it set. | |
| std::vector<Locals> 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<LocalSet>()->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<LocalGet>()->index; | |
| if (!self->localsSet[index]) { | |
| self->nonDominatingIndices.insert(index); | |
| } | |
| return; | |
| } | |
| case Expression::Id::LocalSetId: { | |
| auto* set = curr->cast<LocalSet>(); | |
| 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<Block>(); | |
| // 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<If>()->ifFalse) { | |
| self->pushTask(Scanner::doEndScope, currp); | |
| self->maybePushTask(Scanner::scan, &curr->cast<If>()->ifFalse); | |
| self->pushTask(Scanner::doBeginScope, currp); | |
| } | |
| self->pushTask(Scanner::doEndScope, currp); | |
| self->pushTask(Scanner::scan, &curr->cast<If>()->ifTrue); | |
| self->pushTask(Scanner::doBeginScope, currp); | |
| // Immediately continue in the loop. | |
| currp = &curr->cast<If>()->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<Loop>()->body; | |
| continue; | |
| } | |
| case Expression::Id::TryId: { | |
| auto& list = curr->cast<Try>()->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<Try>()->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<TryTable>()->body; | |
| continue; | |
| } | |
| default: { | |
| // Control flow structures have been handled. This is an expression, | |
| // which we scan normally. | |
| assert(!Properties::isControlFlowStructure(curr)); | |
| PostWalker<Scanner>::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<Scanner>::pushTask(func, currp); | |
| } | |
| } | |
| void maybePushTask(TaskFunc func, Expression** currp) { | |
| if (*currp) { | |
| pushTask(func, currp); | |
| } | |
| } | |
| }; | |
| Scanner(func, mode, nonDominatingIndices); | |
| } | |
| } // namespace wasm |