diff options
-rw-r--r-- | src/asm2wasm.h | 7 | ||||
-rw-r--r-- | src/ast/block-utils.h | 5 | ||||
-rw-r--r-- | src/ast/branch-utils.h | 75 | ||||
-rw-r--r-- | src/ast/effects.h | 215 | ||||
-rw-r--r-- | src/ast_utils.h | 243 | ||||
-rw-r--r-- | src/passes/CodePushing.cpp | 2 | ||||
-rw-r--r-- | src/passes/DeadCodeElimination.cpp | 4 | ||||
-rw-r--r-- | src/passes/LocalCSE.cpp | 2 | ||||
-rw-r--r-- | src/passes/MergeBlocks.cpp | 3 | ||||
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 3 | ||||
-rw-r--r-- | src/passes/RemoveUnusedBrs.cpp | 6 | ||||
-rw-r--r-- | src/passes/SimplifyLocals.cpp | 2 | ||||
-rw-r--r-- | src/passes/Vacuum.cpp | 9 | ||||
-rw-r--r-- | src/wasm-binary.h | 1 | ||||
-rw-r--r-- | src/wasm/wasm-binary.cpp | 7 | ||||
-rw-r--r-- | src/wasm/wasm-s-parser.cpp | 4 | ||||
-rw-r--r-- | src/wasm/wasm.cpp | 4 | ||||
-rw-r--r-- | test/passes/vacuum.txt | 14 | ||||
-rw-r--r-- | test/passes/vacuum.wast | 14 |
19 files changed, 352 insertions, 268 deletions
diff --git a/src/asm2wasm.h b/src/asm2wasm.h index 4395054f6..b71561d4d 100644 --- a/src/asm2wasm.h +++ b/src/asm2wasm.h @@ -32,6 +32,7 @@ #include "pass.h" #include "parsing.h" #include "ast_utils.h" +#include "ast/branch-utils.h" #include "wasm-builder.h" #include "wasm-emscripten.h" #include "wasm-module-building.h" @@ -2335,9 +2336,9 @@ Function* Asm2WasmBuilder::processFunction(Ref ast) { nameMapper.popLabelName(more); nameMapper.popLabelName(stop); // if we never continued, we don't need a loop - BreakSeeker breakSeeker(more); - breakSeeker.walk(child); - if (breakSeeker.found == 0) { + BranchUtils::BranchSeeker seeker(more); + seeker.walk(child); + if (seeker.found == 0) { auto block = allocator.alloc<Block>(); block->list.push_back(child); if (isConcreteWasmType(child->type)) { diff --git a/src/ast/block-utils.h b/src/ast/block-utils.h index 7b1b9dcff..56c11f240 100644 --- a/src/ast/block-utils.h +++ b/src/ast/block-utils.h @@ -19,7 +19,8 @@ #include "literal.h" #include "wasm.h" -#include "ast_utils.h" +#include "ast/branch-utils.h" +#include "ast/effects.h" namespace wasm { @@ -29,7 +30,7 @@ namespace BlockUtils { template<typename T> inline Expression* simplifyToContents(Block* block, T* parent, bool allowTypeChange = false) { auto& list = block->list; - if (list.size() == 1 && !BreakSeeker::has(list[0], block->name)) { + if (list.size() == 1 && !BranchUtils::BranchSeeker::hasNamed(list[0], block->name)) { // just one element. try to replace the block auto* singleton = list[0]; auto sideEffects = EffectAnalyzer(parent->getPassOptions(), singleton).hasSideEffects(); diff --git a/src/ast/branch-utils.h b/src/ast/branch-utils.h index 853b998af..77ec70753 100644 --- a/src/ast/branch-utils.h +++ b/src/ast/branch-utils.h @@ -100,6 +100,81 @@ inline std::set<Name> getBranchTargets(Expression* ast) { return scanner.targets; } +// Finds if there are branches targeting a name. Note that since names are +// unique in our IR, we just need to look for the name, and do not need +// to analyze scoping. +// By default we ignore untaken branches. You can set named to +// note those as well, then any named branch is noted, even if untaken +struct BranchSeeker : public PostWalker<BranchSeeker> { + Name target; + bool named = false; + + Index found; + WasmType valueType; + + BranchSeeker(Name target) : target(target), found(0) {} + + void noteFound(Expression* value) { + found++; + if (found == 1) valueType = unreachable; + if (!value) valueType = none; + else if (value->type != unreachable) valueType = value->type; + } + + void visitBreak(Break *curr) { + if (!named) { + // ignore an unreachable break + if (curr->condition && curr->condition->type == unreachable) return; + if (curr->value && curr->value->type == unreachable) return; + } + // check the break + if (curr->name == target) noteFound(curr->value); + } + + void visitSwitch(Switch *curr) { + if (!named) { + // ignore an unreachable switch + if (curr->condition->type == unreachable) return; + if (curr->value && curr->value->type == unreachable) return; + } + // check the switch + for (auto name : curr->targets) { + if (name == target) noteFound(curr->value); + } + if (curr->default_ == target) noteFound(curr->value); + } + + static bool has(Expression* tree, Name target) { + if (!target.is()) return false; + BranchSeeker seeker(target); + seeker.walk(tree); + return seeker.found > 0; + } + + static Index count(Expression* tree, Name target) { + if (!target.is()) return 0; + BranchSeeker seeker(target); + seeker.walk(tree); + return seeker.found; + } + + static bool hasNamed(Expression* tree, Name target) { + if (!target.is()) return false; + BranchSeeker seeker(target); + seeker.named = true; + seeker.walk(tree); + return seeker.found > 0; + } + + static Index countNamed(Expression* tree, Name target) { + if (!target.is()) return 0; + BranchSeeker seeker(target); + seeker.named = true; + seeker.walk(tree); + return seeker.found; + } +}; + } // namespace BranchUtils } // namespace wasm diff --git a/src/ast/effects.h b/src/ast/effects.h new file mode 100644 index 000000000..a7a5d8fb2 --- /dev/null +++ b/src/ast/effects.h @@ -0,0 +1,215 @@ +/* + * 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 + 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, and we + // also allow reordering them with other effects + // (so a trap may occur later or earlier, if it is + // going to occur anyhow), but we can't remove them, + // they count as side effects + + bool accessesLocal() { return localsRead.size() + localsWritten.size() > 0; } + bool accessesGlobal() { return globalsRead.size() + globalsWritten.size() > 0; } + bool accessesMemory() { return calls || readsMemory || writesMemory; } + bool hasSideEffects() { return calls || localsWritten.size() > 0 || writesMemory || branches || globalsWritten.size() > 0 || implicitTrap; } + bool hasAnything() { return branches || calls || accessesLocal() || readsMemory || writesMemory || accessesGlobal() || implicitTrap; } + + // 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; + } + 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; + } + 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 + } + + 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; + if (!ignoreImplicitTraps) implicitTrap = true; + } + void visitStore(Store *curr) { + writesMemory = 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; + } + 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; + } + default: {} + } + } + } + void visitReturn(Return *curr) { branches = true; } + void visitHost(Host *curr) { calls = true; } + void visitUnreachable(Unreachable *curr) { branches = true; } +}; + +} // namespace wasm + +#endif // wasm_ast_effects_h + diff --git a/src/ast_utils.h b/src/ast_utils.h index 83818daa8..253da8050 100644 --- a/src/ast_utils.h +++ b/src/ast_utils.h @@ -25,247 +25,6 @@ namespace wasm { -// Finds if there are breaks targeting a name. Note that since names are -// unique in our IR, we just need to look for the name, and do not need -// to analyze scoping. -struct BreakSeeker : public PostWalker<BreakSeeker> { - Name target; - Index found; - WasmType valueType; - - BreakSeeker(Name target) : target(target), found(0) {} - - void noteFound(Expression* value) { - found++; - if (found == 1) valueType = unreachable; - if (!value) valueType = none; - else if (value->type != unreachable) valueType = value->type; - } - - void visitBreak(Break *curr) { - // ignore an unreachable break - if (curr->condition && curr->condition->type == unreachable) return; - if (curr->value && curr->value->type == unreachable) return; - // check the break - if (curr->name == target) noteFound(curr->value); - } - - void visitSwitch(Switch *curr) { - // ignore an unreachable switch - if (curr->condition->type == unreachable) return; - if (curr->value && curr->value->type == unreachable) return; - // check the switch - for (auto name : curr->targets) { - if (name == target) noteFound(curr->value); - } - if (curr->default_ == target) noteFound(curr->value); - } - - static bool has(Expression* tree, Name target) { - if (!target.is()) return false; - BreakSeeker breakSeeker(target); - breakSeeker.walk(tree); - return breakSeeker.found > 0; - } - - static Index count(Expression* tree, Name target) { - if (!target.is()) return 0; - BreakSeeker breakSeeker(target); - breakSeeker.walk(tree); - return breakSeeker.found; - } -}; - -// 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 - 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, and we - // also allow reordering them with other effects - // (so a trap may occur later or earlier, if it is - // going to occur anyhow), but we can't remove them, - // they count as side effects - - bool accessesLocal() { return localsRead.size() + localsWritten.size() > 0; } - bool accessesGlobal() { return globalsRead.size() + globalsWritten.size() > 0; } - bool accessesMemory() { return calls || readsMemory || writesMemory; } - bool hasSideEffects() { return calls || localsWritten.size() > 0 || writesMemory || branches || globalsWritten.size() > 0 || implicitTrap; } - bool hasAnything() { return branches || calls || accessesLocal() || readsMemory || writesMemory || accessesGlobal() || implicitTrap; } - - // 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; - } - 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; - } - 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 - } - - 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; - if (!ignoreImplicitTraps) implicitTrap = true; - } - void visitStore(Store *curr) { - writesMemory = 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; - } - 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; - } - default: {} - } - } - } - void visitReturn(Return *curr) { branches = true; } - void visitHost(Host *curr) { calls = true; } - void visitUnreachable(Unreachable *curr) { branches = true; } -}; - // Measure the size of an AST struct Measurer : public PostWalker<Measurer, UnifiedExpressionVisitor<Measurer>> { @@ -303,7 +62,7 @@ struct ExpressionAnalyzer { if (auto* br = curr->dynCast<Break>()) { if (!br->condition) return true; } else if (auto* block = curr->dynCast<Block>()) { - if (block->list.size() > 0 && obviouslyDoesNotFlowOut(block->list.back()) && !BreakSeeker::has(block, block->name)) return true; + if (block->list.size() > 0 && obviouslyDoesNotFlowOut(block->list.back()) && !BranchUtils::BranchSeeker::has(block, block->name)) return true; } return false; } diff --git a/src/passes/CodePushing.cpp b/src/passes/CodePushing.cpp index 2876228ee..e73794f5a 100644 --- a/src/passes/CodePushing.cpp +++ b/src/passes/CodePushing.cpp @@ -21,8 +21,8 @@ #include <wasm.h> #include <pass.h> -#include <ast_utils.h> #include <wasm-builder.h> +#include <ast/effects.h> namespace wasm { diff --git a/src/passes/DeadCodeElimination.cpp b/src/passes/DeadCodeElimination.cpp index 209180e23..321bc0f9a 100644 --- a/src/passes/DeadCodeElimination.cpp +++ b/src/passes/DeadCodeElimination.cpp @@ -30,9 +30,9 @@ #include <wasm.h> #include <pass.h> -#include <ast_utils.h> #include <wasm-builder.h> #include <ast/block-utils.h> +#include <ast/branch-utils.h> #include <ast/type-updating.h> namespace wasm { @@ -184,7 +184,7 @@ struct DeadCodeElimination : public WalkerPass<PostWalker<DeadCodeElimination>> if (curr->name.is()) { reachableBreaks.erase(curr->name); } - if (isUnreachable(curr->body) && !BreakSeeker::has(curr->body, curr->name)) { + if (isUnreachable(curr->body) && !BranchUtils::BranchSeeker::hasNamed(curr->body, curr->name)) { replaceCurrent(curr->body); return; } diff --git a/src/passes/LocalCSE.cpp b/src/passes/LocalCSE.cpp index 6d48a1107..a3e0dff76 100644 --- a/src/passes/LocalCSE.cpp +++ b/src/passes/LocalCSE.cpp @@ -29,7 +29,7 @@ #include <wasm-builder.h> #include <wasm-traversal.h> #include <pass.h> -#include <ast_utils.h> +#include <ast/effects.h> #include <ast/hashed.h> namespace wasm { diff --git a/src/passes/MergeBlocks.cpp b/src/passes/MergeBlocks.cpp index 1ec8843d6..8ffbb345d 100644 --- a/src/passes/MergeBlocks.cpp +++ b/src/passes/MergeBlocks.cpp @@ -63,8 +63,9 @@ #include <wasm.h> #include <pass.h> -#include <ast_utils.h> #include <wasm-builder.h> +#include <ast_utils.h> +#include <ast/effects.h> namespace wasm { diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 0e7c34564..d1b419a06 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -26,8 +26,9 @@ #include <support/threads.h> #include <ast_utils.h> #include <ast/cost.h> -#include <ast/properties.h> +#include <ast/effects.h> #include <ast/manipulation.h> +#include <ast/properties.h> namespace wasm { diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 1fa996ae6..572105187 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -21,6 +21,8 @@ #include <wasm.h> #include <pass.h> #include <ast_utils.h> +#include <ast/branch-utils.h> +#include <ast/effects.h> #include <wasm-builder.h> namespace wasm { @@ -257,7 +259,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { // so only do it if it looks useful, which it definitely is if // (a) $somewhere is straight out (so the br out vanishes), and // (b) this br_if is the only branch to that block (so the block will vanish) - if (brIf->name == block->name && BreakSeeker::count(block, block->name) == 1) { + if (brIf->name == block->name && BranchUtils::BranchSeeker::count(block, block->name) == 1) { // note that we could drop the last element here, it is a br we know for sure is removable, // but telling stealSlice to steal all to the end is more efficient, it can just truncate. list[i] = builder.makeIf(brIf->condition, builder.makeBreak(brIf->name), builder.stealSlice(block, i + 1, list.size())); @@ -447,7 +449,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> { auto* br = list[0]->dynCast<Break>(); if (br && br->condition && br->name == curr->name) { assert(!br->value); // can't, it would be dropped or last in the block - if (BreakSeeker::count(curr, curr->name) == 1) { + if (BranchUtils::BranchSeeker::count(curr, curr->name) == 1) { // no other breaks to that name, so we can do this Builder builder(*getModule()); replaceCurrent(builder.makeIf( diff --git a/src/passes/SimplifyLocals.cpp b/src/passes/SimplifyLocals.cpp index 4c896dbba..2d4a02337 100644 --- a/src/passes/SimplifyLocals.cpp +++ b/src/passes/SimplifyLocals.cpp @@ -45,8 +45,8 @@ #include <wasm-builder.h> #include <wasm-traversal.h> #include <pass.h> -#include <ast_utils.h> #include <ast/count.h> +#include <ast/effects.h> #include <ast/manipulation.h> namespace wasm { diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index 64c7f276a..4adfc1289 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -20,9 +20,9 @@ #include <wasm.h> #include <pass.h> -#include <ast_utils.h> #include <wasm-builder.h> #include <ast/block-utils.h> +#include <ast/effects.h> #include <ast/type-updating.h> namespace wasm { @@ -272,10 +272,11 @@ struct Vacuum : public WalkerPass<PostWalker<Vacuum>> { // we may be able to remove this, if there are no brs bool canPop = true; if (block->name.is()) { - BreakSeeker breakSeeker(block->name); + BranchUtils::BranchSeeker seeker(block->name); + seeker.named = true; Expression* temp = block; - breakSeeker.walk(temp); - if (breakSeeker.found && breakSeeker.valueType != none) { + seeker.walk(temp); + if (seeker.found && seeker.valueType != none) { canPop = false; } } diff --git a/src/wasm-binary.h b/src/wasm-binary.h index 6b984642c..ca3d69989 100644 --- a/src/wasm-binary.h +++ b/src/wasm-binary.h @@ -30,7 +30,6 @@ #include "asmjs/shared-constants.h" #include "asm_v_wasm.h" #include "wasm-builder.h" -#include "ast_utils.h" #include "parsing.h" #include "wasm-validator.h" diff --git a/src/wasm/wasm-binary.cpp b/src/wasm/wasm-binary.cpp index efbd82e8b..2df7f2e26 100644 --- a/src/wasm/wasm-binary.cpp +++ b/src/wasm/wasm-binary.cpp @@ -14,10 +14,11 @@ * limitations under the License. */ -#include "wasm-binary.h" - #include <fstream> + #include "support/bits.h" +#include "wasm-binary.h" +#include "ast/branch-utils.h" namespace wasm { @@ -544,7 +545,7 @@ void WasmBinaryWriter::recurse(Expression*& curr) { } static bool brokenTo(Block* block) { - return block->name.is() && BreakSeeker::has(block, block->name); + return block->name.is() && BranchUtils::BranchSeeker::has(block, block->name); } void WasmBinaryWriter::visitBlock(Block *curr) { diff --git a/src/wasm/wasm-s-parser.cpp b/src/wasm/wasm-s-parser.cpp index 2fede28fc..e991f71de 100644 --- a/src/wasm/wasm-s-parser.cpp +++ b/src/wasm/wasm-s-parser.cpp @@ -22,7 +22,7 @@ #include "asm_v_wasm.h" #include "asmjs/shared-constants.h" -#include "ast_utils.h" +#include "ast/branch-utils.h" #include "shared-constants.h" #include "wasm-binary.h" #include "wasm-builder.h" @@ -1262,7 +1262,7 @@ Expression* SExpressionWasmBuilder::makeIf(Element& s) { ret->finalize(type); nameMapper.popLabelName(label); // create a break target if we must - if (BreakSeeker::has(ret, label)) { + if (BranchUtils::BranchSeeker::hasNamed(ret, label)) { auto* block = allocator.alloc<Block>(); block->name = label; block->list.push_back(ret); diff --git a/src/wasm/wasm.cpp b/src/wasm/wasm.cpp index d331ba3a2..ec53ab25a 100644 --- a/src/wasm/wasm.cpp +++ b/src/wasm/wasm.cpp @@ -16,7 +16,7 @@ #include "wasm.h" #include "wasm-traversal.h" -#include "ast_utils.h" +#include "ast/branch-utils.h" namespace wasm { @@ -175,7 +175,7 @@ static void handleUnreachable(Block* block) { for (auto* child : block->list) { if (child->type == unreachable) { // there is an unreachable child, so we are unreachable, unless we have a break - BreakSeeker seeker(block->name); + BranchUtils::BranchSeeker seeker(block->name); Expression* expr = block; seeker.walk(expr); if (!seeker.found) { diff --git a/test/passes/vacuum.txt b/test/passes/vacuum.txt index ec6e3ad44..30474280e 100644 --- a/test/passes/vacuum.txt +++ b/test/passes/vacuum.txt @@ -5,6 +5,7 @@ (type $3 (func (result i32))) (type $4 (func (param i32 f64 i32 i32))) (type $FUNCSIG$i (func (result i32))) + (type $6 (func (result f64))) (import "env" "int" (func $int (result i32))) (global $Int i32 (i32.const 0)) (memory $0 256 256) @@ -221,4 +222,17 @@ ) ) ) + (func $leave-block-even-if-br-not-taken (type $6) (result f64) + (block $label$0 + (f64.store align=1 + (i32.const 879179022) + (br_if $label$0 + (loop $label$9 + (br $label$9) + ) + (i32.const 677803374) + ) + ) + ) + ) ) diff --git a/test/passes/vacuum.wast b/test/passes/vacuum.wast index 20bbfe625..594554846 100644 --- a/test/passes/vacuum.wast +++ b/test/passes/vacuum.wast @@ -520,4 +520,18 @@ ) ) ) + (func $leave-block-even-if-br-not-taken (result f64) + (block $label$0 (result f64) + (f64.store align=1 + (i32.const 879179022) + (br_if $label$0 + (loop $label$9 + (br $label$9) + ) + (i32.const 677803374) + ) + ) + (f64.const 2097914503796645752267195e31) + ) + ) ) |