diff options
author | Alon Zakai <alonzakai@gmail.com> | 2017-02-06 16:24:33 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-02-06 16:24:33 -0800 |
commit | 985bcba6239792ebcb3808f1066ca6ea20ac5688 (patch) | |
tree | 8fdd04577f6b40f6f4a8c7851fa5dab83c26e413 | |
parent | 04fc050edf3eeff85a77910a4d6821bff59fade2 (diff) | |
download | binaryen-985bcba6239792ebcb3808f1066ca6ea20ac5688.tar.gz binaryen-985bcba6239792ebcb3808f1066ca6ea20ac5688.tar.bz2 binaryen-985bcba6239792ebcb3808f1066ca6ea20ac5688.zip |
Improve handling of implicit traps (#898)
* add --ignore-implicit-traps option, and by default do not ignore them, to properly preserve semantics
* implicit traps can be reordered, but are side effects and should not be removed
* add testing for --ignore-implicit-traps
22 files changed, 480 insertions, 164 deletions
diff --git a/auto_update_tests.py b/auto_update_tests.py index 7a14f6521..b74d07ba9 100755 --- a/auto_update_tests.py +++ b/auto_update_tests.py @@ -13,7 +13,7 @@ for asm in sorted(os.listdir('test')): cmd = [os.path.join('bin', 'asm2wasm'), os.path.join('test', asm)] wasm = asm.replace('.asm.js', '.fromasm') if not precise: - cmd += ['--imprecise'] + cmd += ['--imprecise', '--ignore-implicit-traps'] wasm += '.imprecise' if not opts: wasm += '.no-opts' @@ -99,7 +99,7 @@ for asm in tests: cmd = ASM2WASM + [os.path.join(options.binaryen_test, asm)] wasm = asm.replace('.asm.js', '.fromasm') if not precise: - cmd += ['--imprecise'] + cmd += ['--imprecise', '--ignore-implicit-traps'] wasm += '.imprecise' if not opts: wasm += '.no-opts' diff --git a/src/ast_utils.h b/src/ast_utils.h index 2a151d8f7..35ad96987 100644 --- a/src/ast_utils.h +++ b/src/ast_utils.h @@ -71,11 +71,13 @@ struct BreakSeeker : public PostWalker<BreakSeeker, Visitor<BreakSeeker>> { // TODO: optimize struct EffectAnalyzer : public PostWalker<EffectAnalyzer, Visitor<EffectAnalyzer>> { - EffectAnalyzer() {} - EffectAnalyzer(Expression *ast) { - analyze(ast); + EffectAnalyzer(PassOptions& passOptions, Expression *ast = nullptr) { + ignoreImplicitTraps = passOptions.ignoreImplicitTraps; + if (ast) analyze(ast); } + bool ignoreImplicitTraps; + void analyze(Expression *ast) { breakNames.clear(); walk(ast); @@ -91,12 +93,18 @@ struct EffectAnalyzer : public PostWalker<EffectAnalyzer, Visitor<EffectAnalyzer 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; } - bool hasAnything() { return branches || calls || accessesLocal() || readsMemory || writesMemory || accessesGlobal(); } + 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) { @@ -124,6 +132,10 @@ struct EffectAnalyzer : public PostWalker<EffectAnalyzer, Visitor<EffectAnalyzer 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; } @@ -189,8 +201,48 @@ struct EffectAnalyzer : public PostWalker<EffectAnalyzer, Visitor<EffectAnalyzer void visitSetGlobal(SetGlobal *curr) { globalsWritten.insert(curr->name); } - void visitLoad(Load *curr) { readsMemory = true; } - void visitStore(Store *curr) { writesMemory = true; } + 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; } diff --git a/src/pass.h b/src/pass.h index dc9159aa0..74579e162 100644 --- a/src/pass.h +++ b/src/pass.h @@ -200,6 +200,10 @@ public: return runner; } + PassOptions& getPassOptions() { + return runner->options; + } + void setPassRunner(PassRunner* runner_) { runner = runner_; } diff --git a/src/passes/CodePushing.cpp b/src/passes/CodePushing.cpp index f4beff1a8..55b15ac9d 100644 --- a/src/passes/CodePushing.cpp +++ b/src/passes/CodePushing.cpp @@ -83,9 +83,10 @@ class Pusher { ExpressionList& list; LocalAnalyzer& analyzer; std::vector<Index>& numGetsSoFar; + PassOptions& passOptions; public: - Pusher(Block* block, LocalAnalyzer& analyzer, std::vector<Index>& numGetsSoFar) : list(block->list), analyzer(analyzer), numGetsSoFar(numGetsSoFar) { + Pusher(Block* block, LocalAnalyzer& analyzer, std::vector<Index>& numGetsSoFar, PassOptions& passOptions) : list(block->list), analyzer(analyzer), numGetsSoFar(numGetsSoFar), passOptions(passOptions) { // Find an optimization segment: from the first pushable thing, to the first // point past which we want to push. We then push in that range before // continuing forward. @@ -119,7 +120,7 @@ private: // but also have no side effects, as it may not execute if pushed. if (analyzer.isSFA(index) && numGetsSoFar[index] == analyzer.getNumGets(index) && - !EffectAnalyzer(set->value).hasSideEffects()) { + !EffectAnalyzer(passOptions, set->value).hasSideEffects()) { return set; } return nullptr; @@ -146,8 +147,8 @@ private: // of earlier ones. Once we know all we can push, we push it all // in one pass, keeping the order of the pushables intact. assert(firstPushable != Index(-1) && pushPoint != Index(-1) && firstPushable < pushPoint); - EffectAnalyzer cumulativeEffects; // everything that matters if you want - // to be pushed past the pushPoint + EffectAnalyzer cumulativeEffects(passOptions); // everything that matters if you want + // to be pushed past the pushPoint cumulativeEffects.analyze(list[pushPoint]); cumulativeEffects.branches = false; // it is ok to ignore the branching here, // that is the crucial point of this opt @@ -158,9 +159,13 @@ private: if (pushable) { auto iter = pushableEffects.find(pushable); if (iter == pushableEffects.end()) { - pushableEffects.emplace(pushable, pushable); + iter = pushableEffects.emplace( + std::piecewise_construct, + std::forward_as_tuple(pushable), + std::forward_as_tuple(passOptions, pushable) + ).first; } - auto& effects = pushableEffects[pushable]; + auto& effects = iter->second; if (cumulativeEffects.invalidates(effects)) { // we can't push this, so further pushables must pass it cumulativeEffects.mergeIn(effects); @@ -248,7 +253,7 @@ struct CodePushing : public WalkerPass<PostWalker<CodePushing, Visitor<CodePushi // ordering invalidation issue, since if this isn't a loop, it's fine (we're not // used outside), and if it is, we hit the assign before any use (as we can't // push it past a use). - Pusher pusher(curr, analyzer, numGetsSoFar); + Pusher pusher(curr, analyzer, numGetsSoFar, getPassOptions()); } }; diff --git a/src/passes/MergeBlocks.cpp b/src/passes/MergeBlocks.cpp index 383837beb..f8d044212 100644 --- a/src/passes/MergeBlocks.cpp +++ b/src/passes/MergeBlocks.cpp @@ -216,9 +216,9 @@ struct MergeBlocks : public WalkerPass<PostWalker<MergeBlocks, Visitor<MergeBloc if (!child) return outer; if ((dependency1 && *dependency1) || (dependency2 && *dependency2)) { // there are dependencies, things we must be reordered through. make sure no problems there - EffectAnalyzer childEffects(child); - if (dependency1 && *dependency1 && EffectAnalyzer(*dependency1).invalidates(childEffects)) return outer; - if (dependency2 && *dependency2 && EffectAnalyzer(*dependency2).invalidates(childEffects)) return outer; + EffectAnalyzer childEffects(getPassOptions(), child); + if (dependency1 && *dependency1 && EffectAnalyzer(getPassOptions(), *dependency1).invalidates(childEffects)) return outer; + if (dependency2 && *dependency2 && EffectAnalyzer(getPassOptions(), *dependency2).invalidates(childEffects)) return outer; } if (auto* block = child->dynCast<Block>()) { if (!block->name.is() && block->list.size() >= 2) { @@ -278,7 +278,7 @@ struct MergeBlocks : public WalkerPass<PostWalker<MergeBlocks, Visitor<MergeBloc void handleCall(T* curr, Block* outer = nullptr) { for (Index i = 0; i < curr->operands.size(); i++) { outer = optimize(curr, curr->operands[i], outer); - if (EffectAnalyzer(curr->operands[i]).hasSideEffects()) return; + if (EffectAnalyzer(getPassOptions(), curr->operands[i]).hasSideEffects()) return; } } @@ -292,7 +292,7 @@ struct MergeBlocks : public WalkerPass<PostWalker<MergeBlocks, Visitor<MergeBloc void visitCallIndirect(CallIndirect* curr) { auto* outer = optimize(curr, curr->target); - if (EffectAnalyzer(curr->target).hasSideEffects()) return; + if (EffectAnalyzer(getPassOptions(), curr->target).hasSideEffects()) return; handleCall(curr, outer); } }; diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 5c1aa5fc3..f94285930 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -317,8 +317,8 @@ struct OptimizeInstructions : public WalkerPass<PostWalker<OptimizeInstructions, auto* condition = select->condition->dynCast<Unary>(); if (condition && condition->op == EqZInt32) { // flip select to remove eqz, if we can reorder - EffectAnalyzer ifTrue(select->ifTrue); - EffectAnalyzer ifFalse(select->ifFalse); + EffectAnalyzer ifTrue(getPassOptions(), select->ifTrue); + EffectAnalyzer ifFalse(getPassOptions(), select->ifFalse); if (!ifTrue.invalidates(ifFalse)) { select->condition = condition->value; std::swap(select->ifTrue, select->ifFalse); @@ -408,8 +408,8 @@ private: auto* left = binary->left; auto* right = binary->right; if (!Properties::emitsBoolean(left) || !Properties::emitsBoolean(right)) return nullptr; - auto leftEffects = EffectAnalyzer(left).hasSideEffects(); - auto rightEffects = EffectAnalyzer(right).hasSideEffects(); + auto leftEffects = EffectAnalyzer(getPassOptions(), left).hasSideEffects(); + auto rightEffects = EffectAnalyzer(getPassOptions(), right).hasSideEffects(); if (leftEffects && rightEffects) return nullptr; // both must execute // canonicalize with side effects, if any, happening on the left if (rightEffects) { diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 69a7c4ff1..b4b188416 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -28,11 +28,11 @@ namespace wasm { // to turn an if into a br-if, we must be able to reorder the // condition and possible value, and the possible value must // not have side effects (as they would run unconditionally) -static bool canTurnIfIntoBrIf(Expression* ifCondition, Expression* brValue) { +static bool canTurnIfIntoBrIf(Expression* ifCondition, Expression* brValue, PassOptions& options) { if (!brValue) return true; - EffectAnalyzer value(brValue); + EffectAnalyzer value(options, brValue); if (value.hasSideEffects()) return false; - return !EffectAnalyzer(ifCondition).invalidates(value); + return !EffectAnalyzer(options, ifCondition).invalidates(value); } struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<RemoveUnusedBrs>>> { @@ -146,7 +146,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R // if without an else. try to reduce if (condition) br => br_if (condition) Break* br = curr->ifTrue->dynCast<Break>(); if (br && !br->condition) { // TODO: if there is a condition, join them - if (canTurnIfIntoBrIf(curr->condition, br->value)) { + if (canTurnIfIntoBrIf(curr->condition, br->value, getPassOptions())) { br->condition = curr->condition; br->finalize(); replaceCurrent(Builder(*getModule()).dropIfConcretelyTyped(br)); @@ -263,7 +263,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R return false; } // if there is control flow, we must stop looking - if (EffectAnalyzer(curr).branches) { + if (EffectAnalyzer(getPassOptions(), curr).branches) { return false; } if (i == 0) return false; @@ -394,6 +394,9 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R // perform some final optimizations struct FinalOptimizer : public PostWalker<FinalOptimizer, Visitor<FinalOptimizer>> { bool selectify; + PassOptions& passOptions; + + FinalOptimizer(PassOptions& passOptions) : passOptions(passOptions) {} void visitBlock(Block* curr) { // if a block has an if br else br, we can un-conditionalize the latter, allowing @@ -406,7 +409,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R auto* iff = list[i]->dynCast<If>(); if (!iff || !iff->ifFalse || isConcreteWasmType(iff->type)) continue; // if it lacked an if-false, it would already be a br_if, as that's the easy case auto* ifTrueBreak = iff->ifTrue->dynCast<Break>(); - if (ifTrueBreak && !ifTrueBreak->condition && canTurnIfIntoBrIf(iff->condition, ifTrueBreak->value)) { + if (ifTrueBreak && !ifTrueBreak->condition && canTurnIfIntoBrIf(iff->condition, ifTrueBreak->value, passOptions)) { // we are an if-else where the ifTrue is a break without a condition, so we can do this ifTrueBreak->condition = iff->condition; ifTrueBreak->finalize(); @@ -416,7 +419,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R } // otherwise, perhaps we can flip the if auto* ifFalseBreak = iff->ifFalse->dynCast<Break>(); - if (ifFalseBreak && !ifFalseBreak->condition && canTurnIfIntoBrIf(iff->condition, ifFalseBreak->value)) { + if (ifFalseBreak && !ifFalseBreak->condition && canTurnIfIntoBrIf(iff->condition, ifFalseBreak->value, passOptions)) { ifFalseBreak->condition = Builder(*getModule()).makeUnary(EqZInt32, iff->condition); ifFalseBreak->finalize(); list[i] = Builder(*getModule()).dropIfConcretelyTyped(ifFalseBreak); @@ -435,7 +438,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R if (!br2 || !br2->condition) continue; if (br1->name == br2->name) { assert(!br1->value && !br2->value); - if (!EffectAnalyzer(br2->condition).hasSideEffects()) { + if (!EffectAnalyzer(passOptions, br2->condition).hasSideEffects()) { // it's ok to execute them both, do it Builder builder(*getModule()); br1->condition = builder.makeBinary(OrInt32, br1->condition, br2->condition); @@ -480,11 +483,11 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R if (curr->ifFalse && isConcreteWasmType(curr->ifTrue->type) && isConcreteWasmType(curr->ifFalse->type)) { // if with else, consider turning it into a select if there is no control flow // TODO: estimate cost - EffectAnalyzer condition(curr->condition); + EffectAnalyzer condition(passOptions, curr->condition); if (!condition.hasSideEffects()) { - EffectAnalyzer ifTrue(curr->ifTrue); + EffectAnalyzer ifTrue(passOptions, curr->ifTrue); if (!ifTrue.hasSideEffects()) { - EffectAnalyzer ifFalse(curr->ifFalse); + EffectAnalyzer ifFalse(passOptions, curr->ifFalse); if (!ifFalse.hasSideEffects()) { auto* select = getModule()->allocator.alloc<Select>(); select->condition = curr->condition; @@ -498,7 +501,7 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs, Visitor<R } } }; - FinalOptimizer finalOptimizer; + FinalOptimizer finalOptimizer(getPassOptions()); finalOptimizer.setModule(getModule()); finalOptimizer.selectify = getPassRunner()->options.shrinkLevel > 0; finalOptimizer.walkFunction(func); diff --git a/src/passes/SimplifyLocals.cpp b/src/passes/SimplifyLocals.cpp index 6509c9da5..8590fbe96 100644 --- a/src/passes/SimplifyLocals.cpp +++ b/src/passes/SimplifyLocals.cpp @@ -84,9 +84,7 @@ struct SimplifyLocals : public WalkerPass<LinearExecutionWalker<SimplifyLocals, Expression** item; EffectAnalyzer effects; - SinkableInfo(Expression** item) : item(item) { - effects.walk(*item); - } + SinkableInfo(Expression** item, PassOptions& passOptions) : item(item), effects(passOptions, *item) {} }; // a list of sinkables in a linear execution trace @@ -246,7 +244,7 @@ struct SimplifyLocals : public WalkerPass<LinearExecutionWalker<SimplifyLocals, static void visitPre(SimplifyLocals* self, Expression** currp) { Expression* curr = *currp; - EffectAnalyzer effects; + EffectAnalyzer effects(self->getPassOptions()); if (effects.checkPre(curr)) { self->checkInvalidations(effects); } @@ -274,7 +272,7 @@ struct SimplifyLocals : public WalkerPass<LinearExecutionWalker<SimplifyLocals, } } - EffectAnalyzer effects; + EffectAnalyzer effects(self->getPassOptions()); if (effects.checkPost(*currp)) { self->checkInvalidations(effects); } @@ -282,7 +280,7 @@ struct SimplifyLocals : public WalkerPass<LinearExecutionWalker<SimplifyLocals, if (set && self->canSink(set)) { Index index = set->index; assert(self->sinkables.count(index) == 0); - self->sinkables.emplace(std::make_pair(index, SinkableInfo(currp))); + self->sinkables.emplace(std::make_pair(index, SinkableInfo(currp, self->getPassOptions()))); } self->expressionStack.pop_back(); diff --git a/src/passes/Vacuum.cpp b/src/passes/Vacuum.cpp index 8d9bb421c..38e607c24 100644 --- a/src/passes/Vacuum.cpp +++ b/src/passes/Vacuum.cpp @@ -74,22 +74,22 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum, Visitor<Vacuum>> } // for unary, binary, and select, we need to check their arguments for side effects if (auto* unary = curr->dynCast<Unary>()) { - if (EffectAnalyzer(unary->value).hasSideEffects()) { + if (EffectAnalyzer(getPassOptions(), unary->value).hasSideEffects()) { curr = unary->value; continue; } else { return nullptr; } } else if (auto* binary = curr->dynCast<Binary>()) { - if (EffectAnalyzer(binary->left).hasSideEffects()) { - if (EffectAnalyzer(binary->right).hasSideEffects()) { + if (EffectAnalyzer(getPassOptions(), binary->left).hasSideEffects()) { + if (EffectAnalyzer(getPassOptions(), binary->right).hasSideEffects()) { return curr; // leave them } else { curr = binary->left; continue; } } else { - if (EffectAnalyzer(binary->right).hasSideEffects()) { + if (EffectAnalyzer(getPassOptions(), binary->right).hasSideEffects()) { curr = binary->right; continue; } else { @@ -99,11 +99,11 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum, Visitor<Vacuum>> } else { // TODO: if two have side effects, we could replace the select with say an add? auto* select = curr->cast<Select>(); - if (EffectAnalyzer(select->ifTrue).hasSideEffects()) { - if (EffectAnalyzer(select->ifFalse).hasSideEffects()) { + if (EffectAnalyzer(getPassOptions(), select->ifTrue).hasSideEffects()) { + if (EffectAnalyzer(getPassOptions(), select->ifFalse).hasSideEffects()) { return curr; // leave them } else { - if (EffectAnalyzer(select->condition).hasSideEffects()) { + if (EffectAnalyzer(getPassOptions(), select->condition).hasSideEffects()) { return curr; // leave them } else { curr = select->ifTrue; @@ -111,15 +111,15 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum, Visitor<Vacuum>> } } } else { - if (EffectAnalyzer(select->ifFalse).hasSideEffects()) { - if (EffectAnalyzer(select->condition).hasSideEffects()) { + if (EffectAnalyzer(getPassOptions(), select->ifFalse).hasSideEffects()) { + if (EffectAnalyzer(getPassOptions(), select->condition).hasSideEffects()) { return curr; // leave them } else { curr = select->ifFalse; continue; } } else { - if (EffectAnalyzer(select->condition).hasSideEffects()) { + if (EffectAnalyzer(getPassOptions(), select->condition).hasSideEffects()) { curr = select->condition; continue; } else { @@ -174,7 +174,7 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum, Visitor<Vacuum>> if (!curr->name.is()) { if (list.size() == 1) { // just one element. replace the block, either with it or with a nop if it's not needed - if (isConcreteWasmType(curr->type) || EffectAnalyzer(list[0]).hasSideEffects()) { + if (isConcreteWasmType(curr->type) || EffectAnalyzer(getPassOptions(), list[0]).hasSideEffects()) { replaceCurrent(list[0]); } else { if (curr->type == unreachable) { @@ -307,7 +307,7 @@ struct Vacuum : public WalkerPass<ExpressionStackWalker<Vacuum, Visitor<Vacuum>> } else { ExpressionManipulator::nop(curr->body); } - if (curr->result == none && !EffectAnalyzer(curr->body).hasSideEffects()) { + if (curr->result == none && !EffectAnalyzer(getPassOptions(), curr->body).hasSideEffects()) { ExpressionManipulator::nop(curr->body); } } diff --git a/src/tools/optimization-options.h b/src/tools/optimization-options.h index 7f3936d35..368e319a7 100644 --- a/src/tools/optimization-options.h +++ b/src/tools/optimization-options.h @@ -23,7 +23,6 @@ [&runOptimizationPasses, &passOptions](Options*, const std::string&) { passOptions.optimizeLevel = 2; passOptions.shrinkLevel = 1; - passOptions.ignoreImplicitTraps = true; runOptimizationPasses = true; }) .add("", "-O0", "execute no optimization passes", @@ -31,14 +30,12 @@ [&passOptions](Options*, const std::string&) { passOptions.optimizeLevel = 0; passOptions.shrinkLevel = 0; - passOptions.ignoreImplicitTraps = false; }) .add("", "-O1", "execute -O1 optimization passes", Options::Arguments::Zero, [&runOptimizationPasses, &passOptions](Options*, const std::string&) { passOptions.optimizeLevel = 1; passOptions.shrinkLevel = 0; - passOptions.ignoreImplicitTraps = true; runOptimizationPasses = true; }) .add("", "-O2", "execute -O2 optimization passes", @@ -46,7 +43,6 @@ [&runOptimizationPasses, &passOptions](Options*, const std::string&) { passOptions.optimizeLevel = 2; passOptions.shrinkLevel = 0; - passOptions.ignoreImplicitTraps = true; runOptimizationPasses = true; }) .add("", "-O3", "execute -O3 optimization passes", @@ -54,7 +50,6 @@ [&runOptimizationPasses, &passOptions](Options*, const std::string&) { passOptions.optimizeLevel = 3; passOptions.shrinkLevel = 0; - passOptions.ignoreImplicitTraps = true; runOptimizationPasses = true; }) .add("", "-Os", "execute default optimization passes, focusing on code size", @@ -62,7 +57,6 @@ [&runOptimizationPasses, &passOptions](Options*, const std::string&) { passOptions.optimizeLevel = 2; passOptions.shrinkLevel = 1; - passOptions.ignoreImplicitTraps = true; runOptimizationPasses = true; }) .add("", "-Oz", "execute default optimization passes, super-focusing on code size", @@ -70,7 +64,6 @@ [&runOptimizationPasses, &passOptions](Options*, const std::string&) { passOptions.optimizeLevel = 2; passOptions.shrinkLevel = 2; - passOptions.ignoreImplicitTraps = true; runOptimizationPasses = true; }) .add("--optimize-level", "-ol", "How much to focus on optimizing code", @@ -83,3 +76,9 @@ [&passOptions](Options* o, const std::string& argument) { passOptions.shrinkLevel = atoi(argument.c_str()); }) + .add("--ignore-implicit-traps", "-iit", "Optimize under the helpful assumption that no surprising traps occur (from load, div/mod, etc.)", + Options::Arguments::Zero, + [&runOptimizationPasses, &passOptions](Options*, const std::string&) { + passOptions.ignoreImplicitTraps = true; + }) + diff --git a/test/emcc_O2_hello_world.fromasm b/test/emcc_O2_hello_world.fromasm index 5ac3b57bb..1cc6427ef 100644 --- a/test/emcc_O2_hello_world.fromasm +++ b/test/emcc_O2_hello_world.fromasm @@ -1935,7 +1935,8 @@ ) ) (if - (select + (if i32 + (get_local $12) (i32.lt_u (get_local $3) (i32.sub @@ -1946,7 +1947,6 @@ ) ) (i32.const 0) - (get_local $12) ) (block (if @@ -3668,19 +3668,12 @@ ) ) (if - (select - (i32.and - (i32.lt_u - (get_local $13) - (get_local $20) - ) - (i32.ge_u - (get_local $13) - (get_local $46) + (if i32 + (if i32 + (i32.eq + (get_local $7) + (i32.const 203) ) - ) - (i32.const 0) - (select (i32.eqz (i32.and (i32.load offset=12 @@ -3690,11 +3683,18 @@ ) ) (i32.const 0) - (i32.eq - (get_local $7) - (i32.const 203) + ) + (i32.and + (i32.lt_u + (get_local $13) + (get_local $20) + ) + (i32.ge_u + (get_local $13) + (get_local $46) ) ) + (i32.const 0) ) (block (i32.store @@ -8117,7 +8117,11 @@ ) ) (set_local $15 - (select + (if i32 + (i32.eq + (get_local $17) + (i32.const 2) + ) (i32.const 0) (i32.sub (get_local $2) @@ -8125,10 +8129,6 @@ (get_local $16) ) ) - (i32.eq - (get_local $17) - (i32.const 2) - ) ) ) ) diff --git a/test/emcc_hello_world.fromasm b/test/emcc_hello_world.fromasm index 690c869ca..7dbe75147 100644 --- a/test/emcc_hello_world.fromasm +++ b/test/emcc_hello_world.fromasm @@ -1065,7 +1065,11 @@ ) ) (set_local $2 - (select + (if i32 + (i32.eq + (get_local $4) + (i32.const 2) + ) (i32.const 0) (i32.sub (get_local $2) @@ -1073,10 +1077,6 @@ (get_local $1) ) ) - (i32.eq - (get_local $4) - (i32.const 2) - ) ) ) ) @@ -4198,7 +4198,13 @@ ) ) ) - (select + (if f64 + (i32.eq + (i32.load8_s + (get_local $9) + ) + (i32.const 45) + ) (f64.neg (f64.add (get_local $15) @@ -4217,12 +4223,6 @@ ) (get_local $15) ) - (i32.eq - (i32.load8_s - (get_local $9) - ) - (i32.const 45) - ) ) ) ) @@ -6220,20 +6220,20 @@ ) ) (br_if $do-once115 - (i32.or - (i32.and - (get_local $17) - (i32.lt_s - (get_local $7) - (i32.const 1) - ) + (i32.and + (get_local $17) + (i32.lt_s + (get_local $7) + (i32.const 1) ) - (i32.and - (i32.load - (get_local $0) - ) - (i32.const 32) + ) + ) + (br_if $do-once115 + (i32.and + (i32.load + (get_local $0) ) + (i32.const 32) ) ) (drop @@ -13480,7 +13480,7 @@ (i32.eq (tee_local $5 (i32.and - (tee_local $8 + (tee_local $7 (i32.load (i32.add (get_local $0) @@ -13495,12 +13495,12 @@ ) (call $_abort) ) - (set_local $7 + (set_local $8 (i32.add (get_local $1) (tee_local $0 (i32.and - (get_local $8) + (get_local $7) (i32.const -8) ) ) @@ -13509,7 +13509,7 @@ (block $do-once (if (i32.and - (get_local $8) + (get_local $7) (i32.const 1) ) (block @@ -13521,6 +13521,11 @@ ) ) (block + (set_local $7 + (i32.load + (get_local $1) + ) + ) (if (i32.eqz (get_local $5) @@ -13534,11 +13539,7 @@ (get_local $1) (i32.sub (i32.const 0) - (tee_local $8 - (i32.load - (get_local $1) - ) - ) + (get_local $7) ) ) ) @@ -13548,7 +13549,7 @@ ) (set_local $0 (i32.add - (get_local $8) + (get_local $7) (get_local $0) ) ) @@ -13567,7 +13568,7 @@ (i32.load (tee_local $2 (i32.add - (get_local $7) + (get_local $8) (i32.const 4) ) ) @@ -13617,13 +13618,13 @@ ) (set_local $5 (i32.shr_u - (get_local $8) + (get_local $7) (i32.const 3) ) ) (if (i32.lt_u - (get_local $8) + (get_local $7) (i32.const 256) ) (block @@ -13778,7 +13779,7 @@ (i32.load (tee_local $4 (i32.add - (tee_local $8 + (tee_local $7 (i32.add (get_local $1) (i32.const 16) @@ -13793,11 +13794,11 @@ (if (tee_local $5 (i32.load - (get_local $8) + (get_local $7) ) ) (set_local $4 - (get_local $8) + (get_local $7) ) (block (set_local $6 @@ -13809,7 +13810,7 @@ ) (loop $while-in (if - (tee_local $8 + (tee_local $7 (i32.load (tee_local $10 (i32.add @@ -13821,7 +13822,7 @@ ) (block (set_local $5 - (get_local $8) + (get_local $7) ) (set_local $4 (get_local $10) @@ -13830,7 +13831,7 @@ ) ) (if - (tee_local $8 + (tee_local $7 (i32.load (tee_local $10 (i32.add @@ -13842,7 +13843,7 @@ ) (block (set_local $5 - (get_local $8) + (get_local $7) ) (set_local $4 (get_local $10) @@ -13883,7 +13884,7 @@ (if (i32.ne (i32.load - (tee_local $8 + (tee_local $7 (i32.add (get_local $10) (i32.const 12) @@ -13908,7 +13909,7 @@ ) (block (i32.store - (get_local $8) + (get_local $7) (get_local $4) ) (i32.store @@ -14044,7 +14045,7 @@ (get_local $12) ) (if - (tee_local $8 + (tee_local $7 (i32.load (tee_local $4 (i32.add @@ -14056,17 +14057,17 @@ ) (if (i32.lt_u - (get_local $8) + (get_local $7) (get_local $5) ) (call $_abort) (block (i32.store offset=16 (get_local $6) - (get_local $8) + (get_local $7) ) (i32.store offset=24 - (get_local $8) + (get_local $7) (get_local $6) ) ) @@ -14128,7 +14129,7 @@ (if (i32.ge_u (get_local $2) - (get_local $7) + (get_local $8) ) (call $_abort) ) @@ -14139,7 +14140,7 @@ (i32.load (tee_local $0 (i32.add - (get_local $7) + (get_local $8) (i32.const 4) ) ) @@ -14181,7 +14182,7 @@ (block (if (i32.eq - (get_local $7) + (get_local $8) (i32.load (i32.const 200) ) @@ -14231,7 +14232,7 @@ ) (if (i32.eq - (get_local $7) + (get_local $8) (i32.load (i32.const 196) ) @@ -14293,14 +14294,14 @@ (block (set_local $4 (i32.load offset=12 - (get_local $7) + (get_local $8) ) ) (if (i32.ne (tee_local $1 (i32.load offset=8 - (get_local $7) + (get_local $8) ) ) (tee_local $0 @@ -14331,7 +14332,7 @@ (i32.load offset=12 (get_local $1) ) - (get_local $7) + (get_local $8) ) (call $_abort) ) @@ -14392,7 +14393,7 @@ ) ) ) - (get_local $7) + (get_local $8) ) (set_local $14 (get_local $0) @@ -14413,7 +14414,7 @@ (block (set_local $6 (i32.load offset=24 - (get_local $7) + (get_local $8) ) ) (block $do-once6 @@ -14421,10 +14422,10 @@ (i32.eq (tee_local $0 (i32.load offset=12 - (get_local $7) + (get_local $8) ) ) - (get_local $7) + (get_local $8) ) (block (if @@ -14435,7 +14436,7 @@ (i32.add (tee_local $1 (i32.add - (get_local $7) + (get_local $8) (i32.const 16) ) ) @@ -14530,7 +14531,7 @@ (i32.lt_u (tee_local $4 (i32.load offset=8 - (get_local $7) + (get_local $8) ) ) (i32.load @@ -14549,7 +14550,7 @@ ) ) ) - (get_local $7) + (get_local $8) ) (call $_abort) ) @@ -14563,7 +14564,7 @@ ) ) ) - (get_local $7) + (get_local $8) ) (block (i32.store @@ -14588,14 +14589,14 @@ (block (if (i32.eq - (get_local $7) + (get_local $8) (i32.load (tee_local $0 (i32.add (i32.shl (tee_local $3 (i32.load offset=28 - (get_local $7) + (get_local $8) ) ) (i32.const 2) @@ -14654,7 +14655,7 @@ ) ) ) - (get_local $7) + (get_local $8) ) (i32.store (get_local $0) @@ -14692,7 +14693,7 @@ (i32.load (tee_local $0 (i32.add - (get_local $7) + (get_local $8) (i32.const 16) ) ) diff --git a/test/memorygrowth.fromasm b/test/memorygrowth.fromasm index d6800684d..e33525578 100644 --- a/test/memorygrowth.fromasm +++ b/test/memorygrowth.fromasm @@ -8170,7 +8170,11 @@ ) ) (set_local $15 - (select + (if i32 + (i32.eq + (get_local $17) + (i32.const 2) + ) (i32.const 0) (i32.sub (get_local $2) @@ -8178,10 +8182,6 @@ (get_local $16) ) ) - (i32.eq - (get_local $17) - (i32.const 2) - ) ) ) ) diff --git a/test/passes/code-pushing.txt b/test/passes/code-pushing_ignore-implicit-traps.txt index f09d0dfb5..f09d0dfb5 100644 --- a/test/passes/code-pushing.txt +++ b/test/passes/code-pushing_ignore-implicit-traps.txt diff --git a/test/passes/code-pushing.wast b/test/passes/code-pushing_ignore-implicit-traps.wast index e1cb30f4a..e1cb30f4a 100644 --- a/test/passes/code-pushing.wast +++ b/test/passes/code-pushing_ignore-implicit-traps.wast diff --git a/test/passes/optimize-instructions_optimize-level=2.txt b/test/passes/optimize-instructions_optimize-level=2_ignore-implicit-traps.txt index 243421e8c..243421e8c 100644 --- a/test/passes/optimize-instructions_optimize-level=2.txt +++ b/test/passes/optimize-instructions_optimize-level=2_ignore-implicit-traps.txt diff --git a/test/passes/optimize-instructions_optimize-level=2.wast b/test/passes/optimize-instructions_optimize-level=2_ignore-implicit-traps.wast index 7874907f3..7874907f3 100644 --- a/test/passes/optimize-instructions_optimize-level=2.wast +++ b/test/passes/optimize-instructions_optimize-level=2_ignore-implicit-traps.wast diff --git a/test/passes/remove-unused-brs_shrink-level=1.txt b/test/passes/remove-unused-brs_shrink-level=1.txt index 68015f73f..b81c12abe 100644 --- a/test/passes/remove-unused-brs_shrink-level=1.txt +++ b/test/passes/remove-unused-brs_shrink-level=1.txt @@ -4,7 +4,7 @@ (type $2 (func (result i32))) (memory $0 256 256) (func $b14 (type $2) (result i32) - (block $topmost i32 + (drop (select (block $block1 i32 (i32.const 12) @@ -15,6 +15,34 @@ (i32.const 1) ) ) + (drop + (if i32 + (i32.const 1) + (i32.load + (i32.const 10) + ) + (i32.const 27) + ) + ) + (drop + (if i32 + (i32.const 1) + (i32.rem_s + (i32.const 11) + (i32.const 12) + ) + (i32.const 27) + ) + ) + (drop + (if i32 + (i32.const 1) + (i32.trunc_u/f64 + (f64.const 12.34) + ) + (i32.const 27) + ) + ) ) (func $join-br_ifs (type $1) (block $out @@ -85,11 +113,11 @@ (nop) ) ) - (block $out80 - (br_if $out80 + (block $out83 + (br_if $out83 (i32.const 1) ) - (br_if $out80 + (br_if $out83 (call $b14) ) ) diff --git a/test/passes/remove-unused-brs_shrink-level=1.wast b/test/passes/remove-unused-brs_shrink-level=1.wast index 5a2234de6..e48ced178 100644 --- a/test/passes/remove-unused-brs_shrink-level=1.wast +++ b/test/passes/remove-unused-brs_shrink-level=1.wast @@ -3,8 +3,8 @@ (type $0 (func (param i32))) (type $1 (func)) (type $2 (func (result i32))) - (func $b14 (type $2) (result i32) - (block $topmost i32 + (func $b14 (type $2) + (drop (if i32 ;; with shrinking, this can become a select (i32.const 1) (block $block1 i32 @@ -15,6 +15,27 @@ ) ) ) + (drop + (if i32 + (i32.const 1) + (i32.load (i32.const 10)) ;; load may have side effects, unless ignored + (i32.const 27) + ) + ) + (drop + (if i32 + (i32.const 1) + (i32.rem_s (i32.const 11) (i32.const 12)) ;; rem may have side effects, unless ignored + (i32.const 27) + ) + ) + (drop + (if i32 + (i32.const 1) + (i32.trunc_u/f64 (f64.const 12.34)) ;; float to int may have side effects, unless ignored + (i32.const 27) + ) + ) ) (func $join-br_ifs (block $out diff --git a/test/passes/remove-unused-brs_shrink-level=1_ignore-implicit-traps.txt b/test/passes/remove-unused-brs_shrink-level=1_ignore-implicit-traps.txt new file mode 100644 index 000000000..35cfd53c8 --- /dev/null +++ b/test/passes/remove-unused-brs_shrink-level=1_ignore-implicit-traps.txt @@ -0,0 +1,125 @@ +(module + (type $0 (func (param i32))) + (type $1 (func)) + (type $2 (func (result i32))) + (memory $0 256 256) + (func $b14 (type $2) (result i32) + (drop + (select + (block $block1 i32 + (i32.const 12) + ) + (block $block3 i32 + (i32.const 27) + ) + (i32.const 1) + ) + ) + (drop + (select + (i32.load + (i32.const 10) + ) + (i32.const 27) + (i32.const 1) + ) + ) + (drop + (select + (i32.rem_s + (i32.const 11) + (i32.const 12) + ) + (i32.const 27) + (i32.const 1) + ) + ) + (drop + (select + (i32.trunc_u/f64 + (f64.const 12.34) + ) + (i32.const 27) + (i32.const 1) + ) + ) + ) + (func $join-br_ifs (type $1) + (block $out + (br_if $out + (i32.or + (i32.const 1) + (i32.const 2) + ) + ) + (nop) + (br_if $out + (i32.const 3) + ) + ) + (block $out2 + (block $out3 + (br_if $out2 + (i32.const 1) + ) + (br_if $out3 + (i32.const 2) + ) + (br_if $out2 + (i32.const 3) + ) + ) + (unreachable) + ) + (block $out4 + (block $out5 + (br_if $out4 + (i32.const 1) + ) + (br_if $out5 + (i32.or + (i32.const 2) + (i32.const 3) + ) + ) + (nop) + ) + (unreachable) + ) + (block $out6 + (block $out7 + (br_if $out6 + (i32.or + (i32.const 1) + (i32.const 2) + ) + ) + (nop) + (br_if $out7 + (i32.const 3) + ) + ) + (unreachable) + ) + (if + (i32.eqz + (i32.or + (call $b14) + (i32.const 0) + ) + ) + (block + (nop) + (nop) + ) + ) + (block $out83 + (br_if $out83 + (i32.const 1) + ) + (br_if $out83 + (call $b14) + ) + ) + ) +) diff --git a/test/passes/remove-unused-brs_shrink-level=1_ignore-implicit-traps.wast b/test/passes/remove-unused-brs_shrink-level=1_ignore-implicit-traps.wast new file mode 100644 index 000000000..e48ced178 --- /dev/null +++ b/test/passes/remove-unused-brs_shrink-level=1_ignore-implicit-traps.wast @@ -0,0 +1,80 @@ +(module + (memory 256 256) + (type $0 (func (param i32))) + (type $1 (func)) + (type $2 (func (result i32))) + (func $b14 (type $2) + (drop + (if i32 ;; with shrinking, this can become a select + (i32.const 1) + (block $block1 i32 + (i32.const 12) + ) + (block $block3 i32 + (i32.const 27) + ) + ) + ) + (drop + (if i32 + (i32.const 1) + (i32.load (i32.const 10)) ;; load may have side effects, unless ignored + (i32.const 27) + ) + ) + (drop + (if i32 + (i32.const 1) + (i32.rem_s (i32.const 11) (i32.const 12)) ;; rem may have side effects, unless ignored + (i32.const 27) + ) + ) + (drop + (if i32 + (i32.const 1) + (i32.trunc_u/f64 (f64.const 12.34)) ;; float to int may have side effects, unless ignored + (i32.const 27) + ) + ) + ) + (func $join-br_ifs + (block $out + (br_if $out (i32.const 1)) + (br_if $out (i32.const 2)) + (br_if $out (i32.const 3)) + ) + (block $out2 + (block $out3 + (br_if $out2 (i32.const 1)) + (br_if $out3 (i32.const 2)) + (br_if $out2 (i32.const 3)) + ) + (unreachable) + ) + (block $out4 + (block $out5 + (br_if $out4 (i32.const 1)) + (br_if $out5 (i32.const 2)) + (br_if $out5 (i32.const 3)) + ) + (unreachable) + ) + (block $out6 + (block $out7 + (br_if $out6 (i32.const 1)) + (br_if $out6 (i32.const 2)) + (br_if $out7 (i32.const 3)) + ) + (unreachable) + ) + (block $out8 + (br_if $out8 (call $b14)) ;; side effect + (br_if $out8 (i32.const 0)) + ) + (block $out8 + (br_if $out8 (i32.const 1)) + (br_if $out8 (call $b14)) ;; side effect + ) + ) +) + |