/*
* Copyright 2021 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/eh-utils.h"
#include "ir/find_all.h"
#include "ir/iteration.h"
#include "ir/properties.h"
#include "wasm-builder.h"
namespace wasm {
namespace EHUtils {
// Finds the first 'pop' in the given catch body that does not belong to an
// inner try's catch, and sets `isInvalid` to true if the pop is enclosed in a
// control flow structure (other than an unnamed block) or is not in the catch
// body's first-descendant line.
static Expression** findPopPtr(Expression*& catchBody,
bool& isInvalid,
bool stopIfInvalid = false) {
struct PopFinder : PostWalker<PopFinder> {
bool isInvalid = false;
bool stopIfInvalid = false;
bool pushedTask = false;
Expression** popPtr = nullptr;
PopFinder(Expression*& catchBody, bool stopIfInvalid)
: stopIfInvalid(stopIfInvalid) {
walk(catchBody);
}
void pushTask(TaskFunc func, Expression** currp) {
pushedTask = true;
PostWalker<PopFinder>::pushTask(func, currp);
}
void maybePushTask(TaskFunc func, Expression** currp) {
if (*currp) {
pushTask(func, currp);
}
}
static void scan(PopFinder* self, Expression** currp) {
if (self->popPtr || (self->stopIfInvalid && self->isInvalid)) {
return;
}
Expression* curr = *currp;
if (curr->is<Pop>()) {
self->popPtr = currp;
return;
}
if (auto* try_ = curr->dynCast<Try>()) {
// It is neither valid for a pop to appear inside a try body (or any
// other control flow structure that will be emitted in the binary), nor
// after the try.
self->isInvalid = true;
if (!self->stopIfInvalid) {
// Do not walk into inner catch bodies; pops in inner catch bodies
// belong to the inner catches.
self->pushTask(scan, &try_->body);
}
return;
}
if (!self->isInvalid && Properties::isControlFlowStructure(curr)) {
if (curr->is<If>()) {
// If's condition is a value child who comes before an 'if'
// instruction in binary, so it is fine if a 'pop' is in there. We
// don't allow a 'pop' to be in an 'if''s then or else body because
// they are not first descendants. PostWalker scans the condition
// first, and if the pop is not in the condition, reaching the end of
// the condition's first-descendant line will set `isInvalid` before
// the arms are scanned.
} else if (auto* block = curr->dynCast<Block>()) {
// Unnamed blocks are not emitted in the binary format, so a 'pop' at
// the beginning of an unnamed block is valid. Named blocks may be
// emitted if targeted by branches, so treat any 'pop' inside a named
// block as invalid.
if (block->name.is()) {
self->isInvalid = true;
}
} else {
assert(curr->is<Loop>() || curr->is<TryTable>());
self->isInvalid = true;
}
}
if (self->stopIfInvalid && self->isInvalid) {
return;
}
// If PostWalker::scan doesn't push a task to scan any children, then we
// must have reached a leaf node. The first leaf reached is the leftmost
// leaf, and the only valid place for a Pop to appear. We already know
// this is not a Pop, so if this is a leaf, then any later Pop will be
// invalid.
self->pushedTask = false;
PostWalker<PopFinder>::scan(self, currp);
if (!self->pushedTask) {
self->isInvalid = true;
}
}
} finder(catchBody, stopIfInvalid);
isInvalid = finder.isInvalid;
return finder.popPtr;
}
bool containsValidDanglingPop(Expression* catchBody) {
bool isInvalid = false;
auto* popPtr = findPopPtr(catchBody, isInvalid, /*stopIfInvalid=*/true);
return popPtr != nullptr && !isInvalid;
}
void handleBlockNestedPop(Try* try_, Function* func, Module& wasm) {
Builder builder(wasm);
for (Index i = 0; i < try_->catchTags.size(); i++) {
Name tagName = try_->catchTags[i];
auto* tag = wasm.getTag(tagName);
if (tag->params() == Type::none) {
continue;
}
auto*& catchBody = try_->catchBodies[i];
bool isInvalid = false;
Expression** popPtr = findPopPtr(catchBody, isInvalid);
assert(popPtr && "Pop has not been found in this catch");
// Change code like
// (catch $e
// ...
// (block $name
// (pop i32)
// )
// )
// into
// (catch $e
// (local.set $new
// (pop i32)
// )
// ...
// (block $name
// (local.get $new)
// )
// )
if (isInvalid) {
Expression* pop = *popPtr;
Index newLocal = builder.addVar(func, pop->type);
*popPtr = builder.makeLocalGet(newLocal, pop->type);
catchBody =
builder.makeSequence(builder.makeLocalSet(newLocal, pop), catchBody);
}
}
}
void handleBlockNestedPops(Function* func, Module& wasm, FeaturePolicy policy) {
if (policy == FeaturePolicy::SkipIfNoEH &&
!wasm.features.hasExceptionHandling()) {
return;
}
FindAll<Try> trys(func->body);
for (auto* try_ : trys.list) {
handleBlockNestedPop(try_, func, wasm);
}
}
Pop* findPop(Expression* expr) {
auto pops = findPops(expr);
if (pops.size() == 0) {
return nullptr;
}
assert(pops.size() == 1);
return pops[0];
}
SmallVector<Pop*, 1> findPops(Expression* expr) {
SmallVector<Pop*, 1> pops;
SmallVector<Expression*, 8> work;
work.push_back(expr);
while (!work.empty()) {
auto* curr = work.back();
work.pop_back();
if (auto* pop = curr->dynCast<Pop>()) {
pops.push_back(pop);
} else if (auto* try_ = curr->dynCast<Try>()) {
// We don't go into inner catch bodies; pops in inner catch bodies
// belong to the inner catches
work.push_back(try_->body);
} else {
for (auto* child : ChildIterator(curr)) {
work.push_back(child);
}
}
}
return pops;
};
} // namespace EHUtils
} // namespace wasm