diff options
Diffstat (limited to 'src/ast/effects.h')
-rw-r--r-- | src/ast/effects.h | 278 |
1 files changed, 0 insertions, 278 deletions
diff --git a/src/ast/effects.h b/src/ast/effects.h deleted file mode 100644 index 1ae27d2a7..000000000 --- a/src/ast/effects.h +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright 2017 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. - */ - -#ifndef wasm_ast_effects_h -#define wasm_ast_effects_h - -namespace wasm { - -// Look for side effects, including control flow -// TODO: optimize - -struct EffectAnalyzer : public PostWalker<EffectAnalyzer> { - EffectAnalyzer(PassOptions& passOptions, Expression *ast = nullptr) { - ignoreImplicitTraps = passOptions.ignoreImplicitTraps; - debugInfo = passOptions.debugInfo; - if (ast) analyze(ast); - } - - bool ignoreImplicitTraps; - bool debugInfo; - - void analyze(Expression *ast) { - breakNames.clear(); - walk(ast); - // if we are left with breaks, they are external - if (breakNames.size() > 0) branches = true; - } - - bool branches = false; // branches out of this expression, returns, infinite loops, etc - bool calls = false; - std::set<Index> localsRead; - std::set<Index> localsWritten; - std::set<Name> globalsRead; - std::set<Name> globalsWritten; - bool readsMemory = false; - bool writesMemory = false; - bool implicitTrap = false; // a load or div/rem, which may trap. we ignore trap - // differences, so it is ok to reorder these, but we can't - // remove them, as they count as side effects, and we - // can't move them in a way that would cause other noticeable - // (global) side effects - bool isAtomic = false; // An atomic load/store/RMW/Cmpxchg or an operator that - // has a defined ordering wrt atomics (e.g. grow_memory) - - bool accessesLocal() { return localsRead.size() + localsWritten.size() > 0; } - bool accessesGlobal() { return globalsRead.size() + globalsWritten.size() > 0; } - bool accessesMemory() { return calls || readsMemory || writesMemory; } - bool hasGlobalSideEffects() { return calls || globalsWritten.size() > 0 || writesMemory || isAtomic; } - bool hasSideEffects() { return hasGlobalSideEffects() || localsWritten.size() > 0 || branches || implicitTrap; } - bool hasAnything() { return branches || calls || accessesLocal() || readsMemory || writesMemory || accessesGlobal() || implicitTrap || isAtomic; } - - // checks if these effects would invalidate another set (e.g., if we write, we invalidate someone that reads, they can't be moved past us) - bool invalidates(EffectAnalyzer& other) { - if (branches || other.branches - || ((writesMemory || calls) && other.accessesMemory()) - || (accessesMemory() && (other.writesMemory || other.calls))) { - return true; - } - // All atomics are sequentially consistent for now, and ordered wrt other - // memory references. - if ((isAtomic && other.accessesMemory()) || - (other.isAtomic && accessesMemory())) { - return true; - } - for (auto local : localsWritten) { - if (other.localsWritten.count(local) || other.localsRead.count(local)) { - return true; - } - } - for (auto local : localsRead) { - if (other.localsWritten.count(local)) return true; - } - if ((accessesGlobal() && other.calls) || (other.accessesGlobal() && calls)) { - return true; - } - for (auto global : globalsWritten) { - if (other.globalsWritten.count(global) || other.globalsRead.count(global)) { - return true; - } - } - for (auto global : globalsRead) { - if (other.globalsWritten.count(global)) return true; - } - // we are ok to reorder implicit traps, but not conditionalize them - if ((implicitTrap && other.branches) || (other.implicitTrap && branches)) { - return true; - } - // we can't reorder an implicit trap in a way that alters global state - if ((implicitTrap && other.hasGlobalSideEffects()) || (other.implicitTrap && hasGlobalSideEffects())) { - return true; - } - return false; - } - - void mergeIn(EffectAnalyzer& other) { - branches = branches || other.branches; - calls = calls || other.calls; - readsMemory = readsMemory || other.readsMemory; - writesMemory = writesMemory || other.writesMemory; - for (auto i : other.localsRead) localsRead.insert(i); - for (auto i : other.localsWritten) localsWritten.insert(i); - for (auto i : other.globalsRead) globalsRead.insert(i); - for (auto i : other.globalsWritten) globalsWritten.insert(i); - } - - // the checks above happen after the node's children were processed, in the order of execution - // we must also check for control flow that happens before the children, i.e., loops - bool checkPre(Expression* curr) { - if (curr->is<Loop>()) { - branches = true; - return true; - } - return false; - } - - bool checkPost(Expression* curr) { - visit(curr); - if (curr->is<Loop>()) { - branches = true; - } - return hasAnything(); - } - - std::set<Name> breakNames; - - void visitBreak(Break *curr) { - breakNames.insert(curr->name); - } - void visitSwitch(Switch *curr) { - for (auto name : curr->targets) { - breakNames.insert(name); - } - breakNames.insert(curr->default_); - } - void visitBlock(Block* curr) { - if (curr->name.is()) breakNames.erase(curr->name); // these were internal breaks - } - void visitLoop(Loop* curr) { - if (curr->name.is()) breakNames.erase(curr->name); // these were internal breaks - // if the loop is unreachable, then there is branching control flow: - // (1) if the body is unreachable because of a (return), uncaught (br) etc., then we - // already noted branching, so it is ok to mark it again (if we have *caught* - // (br)s, then they did not lead to the loop body being unreachable). - // (same logic applies to blocks) - // (2) if the loop is unreachable because it only has branches up to the loop - // top, but no way to get out, then it is an infinite loop, and we consider - // that a branching side effect (note how the same logic does not apply to - // blocks). - if (curr->type == unreachable) { - branches = true; - } - } - - void visitCall(Call *curr) { calls = true; } - void visitCallImport(CallImport *curr) { - calls = true; - if (debugInfo) { - // debugInfo call imports must be preserved very strongly, do not - // move code around them - branches = true; // ! - } - } - void visitCallIndirect(CallIndirect *curr) { calls = true; } - void visitGetLocal(GetLocal *curr) { - localsRead.insert(curr->index); - } - void visitSetLocal(SetLocal *curr) { - localsWritten.insert(curr->index); - } - void visitGetGlobal(GetGlobal *curr) { - globalsRead.insert(curr->name); - } - void visitSetGlobal(SetGlobal *curr) { - globalsWritten.insert(curr->name); - } - void visitLoad(Load *curr) { - readsMemory = true; - isAtomic |= curr->isAtomic; - if (!ignoreImplicitTraps) implicitTrap = true; - } - void visitStore(Store *curr) { - writesMemory = true; - isAtomic |= curr->isAtomic; - if (!ignoreImplicitTraps) implicitTrap = true; - } - void visitAtomicRMW(AtomicRMW* curr) { - readsMemory = true; - writesMemory = true; - isAtomic = true; - if (!ignoreImplicitTraps) implicitTrap = true; - } - void visitAtomicCmpxchg(AtomicCmpxchg* curr) { - readsMemory = true; - writesMemory = true; - isAtomic = true; - if (!ignoreImplicitTraps) implicitTrap = true; - } - void visitAtomicWait(AtomicWait* curr) { - readsMemory = true; - // AtomicWait doesn't strictly write memory, but it does modify the waiters - // list associated with the specified address, which we can think of as a - // write. - writesMemory = true; - isAtomic = true; - if (!ignoreImplicitTraps) implicitTrap = true; - } - void visitAtomicWake(AtomicWake* curr) { - // AtomicWake doesn't strictly write memory, but it does modify the waiters - // list associated with the specified address, which we can think of as a - // write. - readsMemory = true; - writesMemory = true; - isAtomic = true; - if (!ignoreImplicitTraps) implicitTrap = true; - }; - void visitUnary(Unary *curr) { - if (!ignoreImplicitTraps) { - switch (curr->op) { - case TruncSFloat32ToInt32: - case TruncSFloat32ToInt64: - case TruncUFloat32ToInt32: - case TruncUFloat32ToInt64: - case TruncSFloat64ToInt32: - case TruncSFloat64ToInt64: - case TruncUFloat64ToInt32: - case TruncUFloat64ToInt64: { - implicitTrap = true; - break; - } - default: {} - } - } - } - void visitBinary(Binary *curr) { - if (!ignoreImplicitTraps) { - switch (curr->op) { - case DivSInt32: - case DivUInt32: - case RemSInt32: - case RemUInt32: - case DivSInt64: - case DivUInt64: - case RemSInt64: - case RemUInt64: { - implicitTrap = true; - break; - } - default: {} - } - } - } - void visitReturn(Return *curr) { branches = true; } - void visitHost(Host *curr) { - calls = true; - // grow_memory modifies the set of valid addresses, and thus can be modeled as modifying memory - writesMemory = true; - // Atomics are also sequentially consistent with grow_memory. - isAtomic = true; - } - void visitUnreachable(Unreachable *curr) { branches = true; } -}; - -} // namespace wasm - -#endif // wasm_ast_effects_h |