blob: ca058e2be64ee20997fe9f9045274edc6f764cdd [file] [log] [blame] [edit]
/*
* Copyright 2018 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/branch-utils.h"
#include "ir/find_all.h"
#include "ir/utils.h"
namespace wasm {
static Type getValueType(Expression* value) {
return value ? value->type : none;
}
namespace {
// Handles a branch fixup for visitBlock: if the branch goes to the
// target name, give it a value which is unreachable.
template<typename T>
void handleBranchForVisitBlock(T* curr, Name name, Module* module) {
if (BranchUtils::getUniqueTargets(curr).count(name)) {
assert(!curr->value);
Builder builder(*module);
curr->value = builder.makeUnreachable();
}
}
} // anonymous namespace
void ReFinalize::visitBlock(Block* curr) {
if (curr->list.size() == 0) {
curr->type = none;
return;
}
auto old = curr->type;
// do this quickly, without any validation
// last element determines type
curr->type = curr->list.back()->type;
// if concrete, it doesn't matter if we have an unreachable child, and we
// don't need to look at breaks
if (isConcreteType(curr->type)) {
// make sure our branches make sense for us - we may have just made
// ourselves concrete for a value flowing out, while branches did not send a
// value. such branches could not have been actually taken before, that is,
// there were in unreachable code, but we do still need to fix them up here.
if (!isConcreteType(old)) {
auto iter = breakValues.find(curr->name);
if (iter != breakValues.end()) {
// there is a break to here
auto type = iter->second;
if (type == none) {
// we need to fix this up. set the values to unreachables
for (auto* br : FindAll<Break>(curr).list) {
handleBranchForVisitBlock(br, curr->name, getModule());
}
for (auto* sw : FindAll<Switch>(curr).list) {
handleBranchForVisitBlock(sw, curr->name, getModule());
}
// and we need to propagate that type out, re-walk
ReFinalize fixer;
fixer.setModule(getModule());
Expression* temp = curr;
fixer.walk(temp);
assert(temp == curr);
}
}
}
return;
}
// otherwise, we have no final fallthrough element to determine the type,
// could be determined by breaks
if (curr->name.is()) {
auto iter = breakValues.find(curr->name);
if (iter != breakValues.end()) {
// there is a break to here
auto type = iter->second;
assert(type != unreachable); // we would have removed such branches
curr->type = type;
return;
}
}
if (curr->type == unreachable) {
return;
}
// type is none, but we might be unreachable
if (curr->type == none) {
for (auto* child : curr->list) {
if (child->type == unreachable) {
curr->type = unreachable;
break;
}
}
}
}
void ReFinalize::visitIf(If* curr) { curr->finalize(); }
void ReFinalize::visitLoop(Loop* curr) { curr->finalize(); }
void ReFinalize::visitBreak(Break* curr) {
curr->finalize();
auto valueType = getValueType(curr->value);
if (valueType == unreachable) {
replaceUntaken(curr->value, curr->condition);
} else {
updateBreakValueType(curr->name, valueType);
}
}
void ReFinalize::visitSwitch(Switch* curr) {
curr->finalize();
auto valueType = getValueType(curr->value);
if (valueType == unreachable) {
replaceUntaken(curr->value, curr->condition);
} else {
for (auto target : curr->targets) {
updateBreakValueType(target, valueType);
}
updateBreakValueType(curr->default_, valueType);
}
}
void ReFinalize::visitCall(Call* curr) { curr->finalize(); }
void ReFinalize::visitCallIndirect(CallIndirect* curr) { curr->finalize(); }
void ReFinalize::visitGetLocal(GetLocal* curr) { curr->finalize(); }
void ReFinalize::visitSetLocal(SetLocal* curr) { curr->finalize(); }
void ReFinalize::visitGetGlobal(GetGlobal* curr) { curr->finalize(); }
void ReFinalize::visitSetGlobal(SetGlobal* curr) { curr->finalize(); }
void ReFinalize::visitLoad(Load* curr) { curr->finalize(); }
void ReFinalize::visitStore(Store* curr) { curr->finalize(); }
void ReFinalize::visitAtomicRMW(AtomicRMW* curr) { curr->finalize(); }
void ReFinalize::visitAtomicCmpxchg(AtomicCmpxchg* curr) { curr->finalize(); }
void ReFinalize::visitAtomicWait(AtomicWait* curr) { curr->finalize(); }
void ReFinalize::visitAtomicNotify(AtomicNotify* curr) { curr->finalize(); }
void ReFinalize::visitSIMDExtract(SIMDExtract* curr) { curr->finalize(); }
void ReFinalize::visitSIMDReplace(SIMDReplace* curr) { curr->finalize(); }
void ReFinalize::visitSIMDShuffle(SIMDShuffle* curr) { curr->finalize(); }
void ReFinalize::visitSIMDBitselect(SIMDBitselect* curr) { curr->finalize(); }
void ReFinalize::visitSIMDShift(SIMDShift* curr) { curr->finalize(); }
void ReFinalize::visitMemoryInit(MemoryInit* curr) { curr->finalize(); }
void ReFinalize::visitDataDrop(DataDrop* curr) { curr->finalize(); }
void ReFinalize::visitMemoryCopy(MemoryCopy* curr) { curr->finalize(); }
void ReFinalize::visitMemoryFill(MemoryFill* curr) { curr->finalize(); }
void ReFinalize::visitConst(Const* curr) { curr->finalize(); }
void ReFinalize::visitUnary(Unary* curr) { curr->finalize(); }
void ReFinalize::visitBinary(Binary* curr) { curr->finalize(); }
void ReFinalize::visitSelect(Select* curr) { curr->finalize(); }
void ReFinalize::visitDrop(Drop* curr) { curr->finalize(); }
void ReFinalize::visitReturn(Return* curr) { curr->finalize(); }
void ReFinalize::visitHost(Host* curr) { curr->finalize(); }
void ReFinalize::visitNop(Nop* curr) { curr->finalize(); }
void ReFinalize::visitUnreachable(Unreachable* curr) { curr->finalize(); }
void ReFinalize::visitFunction(Function* curr) {
// we may have changed the body from unreachable to none, which might be bad
// if the function has a return value
if (curr->result != none && curr->body->type == none) {
Builder builder(*getModule());
curr->body = builder.blockify(curr->body, builder.makeUnreachable());
}
}
void ReFinalize::visitFunctionType(FunctionType* curr) { WASM_UNREACHABLE(); }
void ReFinalize::visitExport(Export* curr) { WASM_UNREACHABLE(); }
void ReFinalize::visitGlobal(Global* curr) { WASM_UNREACHABLE(); }
void ReFinalize::visitTable(Table* curr) { WASM_UNREACHABLE(); }
void ReFinalize::visitMemory(Memory* curr) { WASM_UNREACHABLE(); }
void ReFinalize::visitModule(Module* curr) { WASM_UNREACHABLE(); }
void ReFinalize::updateBreakValueType(Name name, Type type) {
if (type != unreachable || breakValues.count(name) == 0) {
breakValues[name] = type;
}
}
// Replace an untaken branch/switch with an unreachable value.
// A condition may also exist and may or may not be unreachable.
void ReFinalize::replaceUntaken(Expression* value, Expression* condition) {
assert(value->type == unreachable);
auto* replacement = value;
if (condition) {
Builder builder(*getModule());
// Even if we have
// (block
// (unreachable)
// (i32.const 1)
// )
// we want the block type to be unreachable. That is valid as
// the value is unreachable, and necessary since the type of
// the condition did not have an impact before (the break/switch
// type was unreachable), and might not fit in.
if (isConcreteType(condition->type)) {
condition = builder.makeDrop(condition);
}
replacement = builder.makeSequence(value, condition);
assert(replacement->type);
}
replaceCurrent(replacement);
}
} // namespace wasm