blob: 37cd7f4bae643ed4f118fcbff570880e8413bea5 [file] [log] [blame] [edit]
/*
* 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