blob: 43fd1627d31a29b04cf98df2fb8377b356e3bdf1 [file] [log] [blame] [edit]
/*
* 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/branch-utils.h"
#include "ir/find_all.h"
namespace wasm {
namespace EHUtils {
// This returns three values, some of them as output parameters:
// - Return value: 'pop' expression (Expression*), when there is one in
// first-descendant line. If there's no such pop, it returns null.
// - isPopNested: Whether the discovered 'pop' is nested within a block
// - popPtr: 'pop' expression's pointer (Expression**), when there is one found
//
// When 'catchBody' itself is a 'pop', 'pop''s pointer is null, because there is
// no way to get the given expression's address. But that's fine because pop's
// pointer is only necessary (in handleBlockNestedPops) to fix it up when it is
// nested, and if 'catchBody' itself is a pop, we don't need to fix it up.
static Expression*
getFirstPop(Expression* catchBody, bool& isPopNested, Expression**& popPtr) {
Expression* firstChild = catchBody;
isPopNested = false;
popPtr = nullptr;
// When there are multiple expressions within a catch body, an implicit
// block is created within it for convenience purposes.
auto* implicitBlock = catchBody->dynCast<Block>();
// Go down the line for the first child until we reach a leaf. A pop should be
// in that first-decendant line.
Expression** firstChildPtr = nullptr;
while (true) {
if (firstChild->is<Pop>()) {
popPtr = firstChildPtr;
return firstChild;
}
if (Properties::isControlFlowStructure(firstChild)) {
if (auto* iff = firstChild->dynCast<If>()) {
// If's condition is a value child who comes before an 'if' instruction
// in binary, 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.
firstChild = iff->condition;
firstChildPtr = &iff->condition;
continue;
} else if (firstChild->is<Loop>()) {
// We don't allow the pop to be included in a loop, because it cannot be
// run more than once
return nullptr;
}
if (firstChild->is<Block>()) {
// If there are no branches that targets the implicit block, it will be
// removed when written back. But if there are branches that target the
// implicit block,
// (catch $e
// (block $l0
// (pop i32) ;; within a block!
// (br $l0)
// ...
// )
// This cannot be removed, so this is considered a nested pop (which we
// should fix).
if (firstChild == implicitBlock) {
if (BranchUtils::BranchSeeker::has(implicitBlock,
implicitBlock->name)) {
isPopNested = true;
}
} else {
isPopNested = true;
}
} else if (firstChild->is<Try>() || firstChild->is<TryTable>()) {
isPopNested = true;
} else {
WASM_UNREACHABLE("Unexpected control flow expression");
}
}
ChildIterator it(firstChild);
if (it.getNumChildren() == 0) {
return nullptr;
}
firstChildPtr = &*it.begin();
firstChild = *firstChildPtr;
}
}
bool containsValidDanglingPop(Expression* catchBody) {
bool isPopNested = false;
Expression** popPtr = nullptr;
auto* pop = getFirstPop(catchBody, isPopNested, popPtr);
return pop != nullptr && !isPopNested;
}
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 isPopNested = false;
Expression** popPtr = nullptr;
Expression* pop = getFirstPop(catchBody, isPopNested, popPtr);
assert(pop && "Pop has not been found in this catch");
// Change code like
// (catch $e
// ...
// (block
// (pop i32)
// )
// )
// into
// (catch $e
// (local.set $new
// (pop i32)
// )
// ...
// (block
// (local.get $new)
// )
// )
if (isPopNested) {
assert(popPtr);
Index newLocal = builder.addVar(func, pop->type);
try_->catchBodies[i] =
builder.makeSequence(builder.makeLocalSet(newLocal, pop), catchBody);
*popPtr = builder.makeLocalGet(newLocal, pop->type);
}
}
}
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