diff options
author | Alon Zakai <alonzakai@gmail.com> | 2017-10-24 20:36:28 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-10-24 20:36:28 -0700 |
commit | 47c9021401a69d407a3e730012824cae75dd66f9 (patch) | |
tree | f03c6077a08f5eb404f31ca415c121415f35934c /src/ir/effects.h | |
parent | 93f5f167d7674e87f634eca1f5980baee4de5053 (diff) | |
download | binaryen-47c9021401a69d407a3e730012824cae75dd66f9.tar.gz binaryen-47c9021401a69d407a3e730012824cae75dd66f9.tar.bz2 binaryen-47c9021401a69d407a3e730012824cae75dd66f9.zip |
notation change: AST => IR (#1245)
The IR is indeed a tree, but not an "abstract syntax tree" since there is no language for which it is the syntax (except in the most trivial and meaningless sense).
Diffstat (limited to 'src/ir/effects.h')
-rw-r--r-- | src/ir/effects.h | 278 |
1 files changed, 278 insertions, 0 deletions
diff --git a/src/ir/effects.h b/src/ir/effects.h new file mode 100644 index 000000000..98911d451 --- /dev/null +++ b/src/ir/effects.h @@ -0,0 +1,278 @@ +/* + * 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_ir_effects_h +#define wasm_ir_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_ir_effects_h |