diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/ir/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/ir/ReFinalize.cpp | 198 | ||||
-rw-r--r-- | src/ir/branch-utils.h | 6 | ||||
-rw-r--r-- | src/ir/utils.h | 162 |
4 files changed, 242 insertions, 125 deletions
diff --git a/src/ir/CMakeLists.txt b/src/ir/CMakeLists.txt index 607207968..e89ece6a2 100644 --- a/src/ir/CMakeLists.txt +++ b/src/ir/CMakeLists.txt @@ -2,5 +2,6 @@ SET(ir_SOURCES ExpressionAnalyzer.cpp ExpressionManipulator.cpp LocalGraph.cpp + ReFinalize.cpp ) ADD_LIBRARY(ir STATIC ${ir_SOURCES}) diff --git a/src/ir/ReFinalize.cpp b/src/ir/ReFinalize.cpp new file mode 100644 index 000000000..31140837f --- /dev/null +++ b/src/ir/ReFinalize.cpp @@ -0,0 +1,198 @@ +/* + * 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::visitAtomicWake(AtomicWake* 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 + diff --git a/src/ir/branch-utils.h b/src/ir/branch-utils.h index 6f9299bf5..50c71e8c9 100644 --- a/src/ir/branch-utils.h +++ b/src/ir/branch-utils.h @@ -45,6 +45,12 @@ inline bool isBranchReachable(Expression* expr) { WASM_UNREACHABLE(); } +inline std::set<Name> getUniqueTargets(Break* br) { + std::set<Name> ret; + ret.insert(br->name); + return ret; +} + inline std::set<Name> getUniqueTargets(Switch* sw) { std::set<Name> ret; for (auto target : sw->targets) { diff --git a/src/ir/utils.h b/src/ir/utils.h index 8b4e028f4..a4082b6bc 100644 --- a/src/ir/utils.h +++ b/src/ir/utils.h @@ -112,136 +112,48 @@ struct ReFinalize : public WalkerPass<PostWalker<ReFinalize, OverriddenVisitor<R std::map<Name, Type> breakValues; - void visitBlock(Block* curr) { - if (curr->list.size() == 0) { - curr->type = none; - return; - } - // 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)) 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 visitIf(If* curr) { curr->finalize(); } - void visitLoop(Loop* curr) { curr->finalize(); } - void visitBreak(Break* curr) { - curr->finalize(); - auto valueType = getValueType(curr->value); - if (valueType == unreachable) { - replaceUntaken(curr->value, curr->condition); - } else { - updateBreakValueType(curr->name, valueType); - } - } - void 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 visitCall(Call* curr) { curr->finalize(); } - void visitCallIndirect(CallIndirect* curr) { curr->finalize(); } - void visitGetLocal(GetLocal* curr) { curr->finalize(); } - void visitSetLocal(SetLocal* curr) { curr->finalize(); } - void visitGetGlobal(GetGlobal* curr) { curr->finalize(); } - void visitSetGlobal(SetGlobal* curr) { curr->finalize(); } - void visitLoad(Load* curr) { curr->finalize(); } - void visitStore(Store* curr) { curr->finalize(); } - void visitAtomicRMW(AtomicRMW* curr) { curr->finalize(); } - void visitAtomicCmpxchg(AtomicCmpxchg* curr) { curr->finalize(); } - void visitAtomicWait(AtomicWait* curr) { curr->finalize(); } - void visitAtomicWake(AtomicWake* curr) { curr->finalize(); } - void visitConst(Const* curr) { curr->finalize(); } - void visitUnary(Unary* curr) { curr->finalize(); } - void visitBinary(Binary* curr) { curr->finalize(); } - void visitSelect(Select* curr) { curr->finalize(); } - void visitDrop(Drop* curr) { curr->finalize(); } - void visitReturn(Return* curr) { curr->finalize(); } - void visitHost(Host* curr) { curr->finalize(); } - void visitNop(Nop* curr) { curr->finalize(); } - void visitUnreachable(Unreachable* curr) { curr->finalize(); } - - void 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 visitFunctionType(FunctionType* curr) { WASM_UNREACHABLE(); } - void visitExport(Export* curr) { WASM_UNREACHABLE(); } - void visitGlobal(Global* curr) { WASM_UNREACHABLE(); } - void visitTable(Table* curr) { WASM_UNREACHABLE(); } - void visitMemory(Memory* curr) { WASM_UNREACHABLE(); } - void visitModule(Module* curr) { WASM_UNREACHABLE(); } + void visitBlock(Block* curr); + void visitIf(If* curr); + void visitLoop(Loop* curr); + void visitBreak(Break* curr); + void visitSwitch(Switch* curr); + void visitCall(Call* curr); + void visitCallIndirect(CallIndirect* curr); + void visitGetLocal(GetLocal* curr); + void visitSetLocal(SetLocal* curr); + void visitGetGlobal(GetGlobal* curr); + void visitSetGlobal(SetGlobal* curr); + void visitLoad(Load* curr); + void visitStore(Store* curr); + void visitAtomicRMW(AtomicRMW* curr); + void visitAtomicCmpxchg(AtomicCmpxchg* curr); + void visitAtomicWait(AtomicWait* curr); + void visitAtomicWake(AtomicWake* curr); + void visitConst(Const* curr); + void visitUnary(Unary* curr); + void visitBinary(Binary* curr); + void visitSelect(Select* curr); + void visitDrop(Drop* curr); + void visitReturn(Return* curr); + void visitHost(Host* curr); + void visitNop(Nop* curr); + void visitUnreachable(Unreachable* curr); + + void visitFunction(Function* curr); + + void visitFunctionType(FunctionType* curr); + void visitExport(Export* curr); + void visitGlobal(Global* curr); + void visitTable(Table* curr); + void visitMemory(Memory* curr); + void visitModule(Module* curr); private: - Type getValueType(Expression* value) { - return value ? value->type : none; - } - - void updateBreakValueType(Name name, Type type) { - if (type != unreachable || breakValues.count(name) == 0) { - breakValues[name] = type; - } - } + void updateBreakValueType(Name name, Type type); // Replace an untaken branch/switch with an unreachable value. // A condition may also exist and may or may not be unreachable. - void 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); - } + void replaceUntaken(Expression* value, Expression* condition); }; // Re-finalize a single node. This is slow, if you want to refinalize |