diff options
author | Heejin Ahn <aheejin@gmail.com> | 2020-02-05 14:37:53 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-02-05 14:37:53 -0800 |
commit | 7be22c4d68270573ee010938aa8cd06be89e54d2 (patch) | |
tree | ef9607bef85aabb344234484818ffc29f2235919 | |
parent | 33f92aa06fe5de7bcf9f6b7fe2e74ba5e8e1e782 (diff) | |
download | binaryen-7be22c4d68270573ee010938aa8cd06be89e54d2.tar.gz binaryen-7be22c4d68270573ee010938aa8cd06be89e54d2.tar.bz2 binaryen-7be22c4d68270573ee010938aa8cd06be89e54d2.zip |
Add EH support for OptimizeInstructions (#2608)
- Adds support for `Try` in `optimizeBoolean` function
- Adds support for `Try` in `getFallThrough` function
- Adds approximate cost values for instructions in EH and reference
types proposals.
-rw-r--r-- | src/ir/cost.h | 12 | ||||
-rw-r--r-- | src/ir/properties.h | 30 | ||||
-rw-r--r-- | src/passes/AvoidReinterprets.cpp | 29 | ||||
-rw-r--r-- | src/passes/OptimizeInstructions.cpp | 20 | ||||
-rw-r--r-- | src/passes/Precompute.cpp | 3 | ||||
-rw-r--r-- | test/passes/optimize-instructions_all-features.txt | 50 | ||||
-rw-r--r-- | test/passes/optimize-instructions_all-features.wast | 38 | ||||
-rw-r--r-- | test/passes/remove-unused-names_optimize-instructions_all-features.txt | 104 | ||||
-rw-r--r-- | test/passes/remove-unused-names_optimize-instructions_all-features.wast | 78 |
9 files changed, 309 insertions, 55 deletions
diff --git a/src/ir/cost.h b/src/ir/cost.h index 37f2611dd..e89fb9d17 100644 --- a/src/ir/cost.h +++ b/src/ir/cost.h @@ -726,6 +726,18 @@ struct CostAnalyzer : public Visitor<CostAnalyzer, Index> { Index visitDrop(Drop* curr) { return visit(curr->value); } Index visitReturn(Return* curr) { return maybeVisit(curr->value); } Index visitHost(Host* curr) { return 100; } + Index visitRefNull(RefNull* curr) { return 1; } + Index visitRefIsNull(RefIsNull* curr) { return 1; } + Index visitRefFunc(RefFunc* curr) { return 1; } + Index visitTry(Try* curr) { + // We assume no exception will be thrown in most cases + return visit(curr->body); + } + Index visitThrow(Throw* curr) { return 100; } + Index visitRethrow(Rethrow* curr) { return 100; } + Index visitBrOnExn(BrOnExn* curr) { + return 1 + visit(curr->exnref) + curr->sent.size(); + } Index visitNop(Nop* curr) { return 0; } Index visitUnreachable(Unreachable* curr) { return 0; } }; diff --git a/src/ir/properties.h b/src/ir/properties.h index 01e78563d..1dbb0e096 100644 --- a/src/ir/properties.h +++ b/src/ir/properties.h @@ -18,6 +18,8 @@ #define wasm_ir_properties_h #include "ir/bits.h" +#include "ir/effects.h" +#include "ir/iteration.h" #include "wasm.h" namespace wasm { @@ -68,6 +70,10 @@ inline bool isNamedControlFlow(Expression* curr) { return false; } +inline bool isConstantExpression(const Expression* curr) { + return curr->is<Const>() || curr->is<RefNull>() || curr->is<RefFunc>(); +} + // Check if an expression is a sign-extend, and if so, returns the value // that is extended, otherwise nullptr inline Expression* getSignExtValue(Expression* curr) { @@ -153,7 +159,9 @@ inline Index getZeroExtBits(Expression* curr) { // Returns a falling-through value, that is, it looks through a local.tee // and other operations that receive a value and let it flow through them. -inline Expression* getFallthrough(Expression* curr) { +inline Expression* getFallthrough(Expression* curr, + const PassOptions& passOptions, + FeatureSet features) { // If the current node is unreachable, there is no value // falling through. if (curr->type == Type::unreachable) { @@ -161,36 +169,36 @@ inline Expression* getFallthrough(Expression* curr) { } if (auto* set = curr->dynCast<LocalSet>()) { if (set->isTee()) { - return getFallthrough(set->value); + return getFallthrough(set->value, passOptions, features); } } else if (auto* block = curr->dynCast<Block>()) { // if no name, we can't be broken to, and then can look at the fallthrough if (!block->name.is() && block->list.size() > 0) { - return getFallthrough(block->list.back()); + return getFallthrough(block->list.back(), passOptions, features); } } else if (auto* loop = curr->dynCast<Loop>()) { - return getFallthrough(loop->body); + return getFallthrough(loop->body, passOptions, features); } else if (auto* iff = curr->dynCast<If>()) { if (iff->ifFalse) { // Perhaps just one of the two actually returns. if (iff->ifTrue->type == Type::unreachable) { - return getFallthrough(iff->ifFalse); + return getFallthrough(iff->ifFalse, passOptions, features); } else if (iff->ifFalse->type == Type::unreachable) { - return getFallthrough(iff->ifTrue); + return getFallthrough(iff->ifTrue, passOptions, features); } } } else if (auto* br = curr->dynCast<Break>()) { if (br->condition && br->value) { - return getFallthrough(br->value); + return getFallthrough(br->value, passOptions, features); + } + } else if (auto* tryy = curr->dynCast<Try>()) { + if (!EffectAnalyzer(passOptions, features, tryy->body).throws) { + return getFallthrough(tryy->body, passOptions, features); } } return curr; } -inline bool isConstantExpression(const Expression* curr) { - return curr->is<Const>() || curr->is<RefNull>() || curr->is<RefFunc>(); -} - } // namespace Properties } // namespace wasm diff --git a/src/passes/AvoidReinterprets.cpp b/src/passes/AvoidReinterprets.cpp index 4df75dd15..91a20912d 100644 --- a/src/passes/AvoidReinterprets.cpp +++ b/src/passes/AvoidReinterprets.cpp @@ -36,7 +36,10 @@ static bool canReplaceWithReinterpret(Load* load) { load->bytes == load->type.getByteSize(); } -static Load* getSingleLoad(LocalGraph* localGraph, LocalGet* get) { +static Load* getSingleLoad(LocalGraph* localGraph, + LocalGet* get, + const PassOptions& passOptions, + FeatureSet features) { std::set<LocalGet*> seen; seen.insert(get); while (1) { @@ -48,7 +51,7 @@ static Load* getSingleLoad(LocalGraph* localGraph, LocalGet* get) { if (!set) { return nullptr; } - auto* value = Properties::getFallthrough(set->value); + auto* value = Properties::getFallthrough(set->value, passOptions, features); if (auto* parentGet = value->dynCast<LocalGet>()) { if (seen.count(parentGet)) { // We are in a cycle of gets, in unreachable code. @@ -98,9 +101,12 @@ struct AvoidReinterprets : public WalkerPass<PostWalker<AvoidReinterprets>> { void visitUnary(Unary* curr) { if (isReinterpret(curr)) { + FeatureSet features = getModule()->features; if (auto* get = - Properties::getFallthrough(curr->value)->dynCast<LocalGet>()) { - if (auto* load = getSingleLoad(localGraph, get)) { + Properties::getFallthrough(curr->value, getPassOptions(), features) + ->dynCast<LocalGet>()) { + if (auto* load = + getSingleLoad(localGraph, get, getPassOptions(), features)) { auto& info = infos[load]; info.reinterpreted = true; } @@ -130,22 +136,27 @@ struct AvoidReinterprets : public WalkerPass<PostWalker<AvoidReinterprets>> { std::map<Load*, Info>& infos; LocalGraph* localGraph; Module* module; + const PassOptions& passOptions; FinalOptimizer(std::map<Load*, Info>& infos, LocalGraph* localGraph, - Module* module) - : infos(infos), localGraph(localGraph), module(module) {} + Module* module, + const PassOptions& passOptions) + : infos(infos), localGraph(localGraph), module(module), + passOptions(passOptions) {} void visitUnary(Unary* curr) { if (isReinterpret(curr)) { - auto* value = Properties::getFallthrough(curr->value); + auto* value = Properties::getFallthrough( + curr->value, passOptions, module->features); if (auto* load = value->dynCast<Load>()) { // A reinterpret of a load - flip it right here if we can. if (canReplaceWithReinterpret(load)) { replaceCurrent(makeReinterpretedLoad(load, load->ptr)); } } else if (auto* get = value->dynCast<LocalGet>()) { - if (auto* load = getSingleLoad(localGraph, get)) { + if (auto* load = getSingleLoad( + localGraph, get, passOptions, module->features)) { auto iter = infos.find(load); if (iter != infos.end()) { auto& info = iter->second; @@ -188,7 +199,7 @@ struct AvoidReinterprets : public WalkerPass<PostWalker<AvoidReinterprets>> { ptr, load->type.reinterpret()); } - } finalOptimizer(infos, localGraph, getModule()); + } finalOptimizer(infos, localGraph, getModule(), getPassOptions()); finalOptimizer.walk(func->body); } diff --git a/src/passes/OptimizeInstructions.cpp b/src/passes/OptimizeInstructions.cpp index 90abed825..3e3863e95 100644 --- a/src/passes/OptimizeInstructions.cpp +++ b/src/passes/OptimizeInstructions.cpp @@ -200,8 +200,11 @@ struct LocalInfo { struct LocalScanner : PostWalker<LocalScanner> { std::vector<LocalInfo>& localInfo; + const PassOptions& passOptions; - LocalScanner(std::vector<LocalInfo>& localInfo) : localInfo(localInfo) {} + LocalScanner(std::vector<LocalInfo>& localInfo, + const PassOptions& passOptions) + : localInfo(localInfo), passOptions(passOptions) {} void doWalkFunction(Function* func) { // prepare @@ -236,7 +239,8 @@ struct LocalScanner : PostWalker<LocalScanner> { return; } // an integer var, worth processing - auto* value = Properties::getFallthrough(curr->value); + auto* value = Properties::getFallthrough( + curr->value, passOptions, getModule()->features); auto& info = localInfo[curr->index]; info.maxBits = std::max(info.maxBits, getMaxBits(value, this)); auto signExtBits = LocalInfo::kUnknown; @@ -289,7 +293,8 @@ struct OptimizeInstructions void doWalkFunction(Function* func) { // first, scan locals { - LocalScanner scanner(localInfo); + LocalScanner scanner(localInfo, getPassOptions()); + scanner.setModule(getModule()); scanner.walkFunction(func); } // main walk @@ -347,7 +352,9 @@ struct OptimizeInstructions Index extraShifts; auto bits = Properties::getAlmostSignExtBits(binary, extraShifts); if (extraShifts == 0) { - if (auto* load = Properties::getFallthrough(ext)->dynCast<Load>()) { + if (auto* load = + Properties::getFallthrough(ext, getPassOptions(), features) + ->dynCast<Load>()) { // pattern match a load of 8 bits and a sign extend using a shl of // 24 then shr_s of 24 as well, etc. if (LoadUtils::canBeSigned(load) && @@ -984,6 +991,11 @@ private: } else if (auto* select = boolean->dynCast<Select>()) { select->ifTrue = optimizeBoolean(select->ifTrue); select->ifFalse = optimizeBoolean(select->ifFalse); + } else if (auto* tryy = boolean->dynCast<Try>()) { + if (tryy->type == Type::i32) { + tryy->body = optimizeBoolean(tryy->body); + tryy->catchBody = optimizeBoolean(tryy->catchBody); + } } // TODO: recurse into br values? return boolean; diff --git a/src/passes/Precompute.cpp b/src/passes/Precompute.cpp index 21393c1cf..3a886166c 100644 --- a/src/passes/Precompute.cpp +++ b/src/passes/Precompute.cpp @@ -327,7 +327,8 @@ private: continue; // already known constant } auto value = setValues[set] = - precomputeValue(Properties::getFallthrough(set->value)); + precomputeValue(Properties::getFallthrough( + set->value, getPassOptions(), getModule()->features)); if (value.isConcrete()) { for (auto* get : localGraph.setInfluences[set]) { work.insert(get); diff --git a/test/passes/optimize-instructions_all-features.txt b/test/passes/optimize-instructions_all-features.txt index 53c28e896..1df99cc21 100644 --- a/test/passes/optimize-instructions_all-features.txt +++ b/test/passes/optimize-instructions_all-features.txt @@ -13,6 +13,7 @@ (type $i32_i64_f32_f64_=>_none (func (param i32 i64 f32 f64))) (type $i32_i64_f64_i32_=>_none (func (param i32 i64 f64 i32))) (type $none_=>_f64 (func (result f64))) + (type $none_=>_anyref (func (result anyref))) (memory $0 0) (export "load-off-2" (func $load-off-2)) (func $f (; 0 ;) (param $i1 i32) (param $i2 i64) @@ -216,6 +217,18 @@ (i32.const 123) (nop) ) + (if + (try (result i32) + (i32.const 123) + (catch + (drop + (exnref.pop) + ) + (i32.const 456) + ) + ) + (nop) + ) (drop (select (i32.const 102) @@ -436,7 +449,7 @@ ) (drop (i32.and - (block $block3 (result i32) + (block $block4 (result i32) (i32.const -6) ) (local.get $x) @@ -444,7 +457,7 @@ ) (drop (i32.and - (block $block4 (result i32) + (block $block5 (result i32) (i32.const 5) ) (loop $loop-in (result i32) @@ -454,20 +467,20 @@ ) (drop (i32.and - (block $block6 (result i32) + (block $block7 (result i32) (i32.const 8) ) - (loop $loop-in5 (result i32) + (loop $loop-in6 (result i32) (i32.const 7) ) ) ) (drop (i32.and - (block $block8 (result i32) + (block $block9 (result i32) (i32.const 10) ) - (loop $loop-in7 (result i32) + (loop $loop-in8 (result i32) (call $and-pos1) (i32.const 9) ) @@ -475,22 +488,22 @@ ) (drop (i32.and - (block $block10 (result i32) + (block $block11 (result i32) (call $and-pos1) (i32.const 12) ) - (loop $loop-in9 (result i32) + (loop $loop-in10 (result i32) (i32.const 11) ) ) ) (drop (i32.and - (loop $loop-in11 (result i32) + (loop $loop-in12 (result i32) (call $and-pos1) (i32.const 13) ) - (block $block12 (result i32) + (block $block13 (result i32) (call $and-pos1) (i32.const 14) ) @@ -498,11 +511,11 @@ ) (drop (i32.and - (block $block13 (result i32) + (block $block14 (result i32) (call $and-pos1) (i32.const 14) ) - (loop $loop-in14 (result i32) + (loop $loop-in15 (result i32) (call $and-pos1) (i32.const 13) ) @@ -510,7 +523,7 @@ ) (drop (i32.and - (block $block15 (result i32) + (block $block16 (result i32) (i32.const 15) ) (local.get $x) @@ -518,7 +531,7 @@ ) (drop (i32.and - (block $block16 (result i32) + (block $block17 (result i32) (i32.const 15) ) (local.get $x) @@ -3319,6 +3332,9 @@ (unreachable) ) ) + (func $if-arms-subtype (; 78 ;) (result anyref) + (ref.null) + ) ) (module (type $none_=>_none (func)) @@ -3337,9 +3353,3 @@ ) ) ) -(module - (type $none_=>_anyref (func (result anyref))) - (func $test (; 0 ;) (result anyref) - (ref.null) - ) -) diff --git a/test/passes/optimize-instructions_all-features.wast b/test/passes/optimize-instructions_all-features.wast index 75993effa..5078e74c5 100644 --- a/test/passes/optimize-instructions_all-features.wast +++ b/test/passes/optimize-instructions_all-features.wast @@ -238,6 +238,26 @@ ) (nop) ) + (if + (try (result i32) + (i32.eqz + (i32.eqz + (i32.const 123) + ) + ) + (catch + (drop + (exnref.pop) + ) + (i32.eqz + (i32.eqz + (i32.const 456) + ) + ) + ) + ) + (nop) + ) (drop (select (i32.const 101) @@ -3766,6 +3786,14 @@ (unreachable) ) ) + ;; Tests when if arms are subtype of if's type + (func $if-arms-subtype (result anyref) + (if (result anyref) + (i32.const 0) + (ref.null) + (ref.null) + ) + ) ) (module (import "env" "memory" (memory $0 (shared 256 256))) @@ -3783,13 +3811,3 @@ ) ) ) -(module - ;; Tests when if arms are subtype of if's type - (func $test (result anyref) - (if (result anyref) - (i32.const 0) - (ref.null) - (ref.null) - ) - ) -) diff --git a/test/passes/remove-unused-names_optimize-instructions_all-features.txt b/test/passes/remove-unused-names_optimize-instructions_all-features.txt new file mode 100644 index 000000000..03c30c6c3 --- /dev/null +++ b/test/passes/remove-unused-names_optimize-instructions_all-features.txt @@ -0,0 +1,104 @@ +(module + (type $none_=>_none (func)) + (type $i32_=>_none (func (param i32))) + (event $e (attr 0) (param i32)) + (func $dummy (; 0 ;) + (nop) + ) + (func $getFallthrough (; 1 ;) + (local $x0 i32) + (local $x1 i32) + (local $x2 i32) + (local $x3 i32) + (local.set $x0 + (try (result i32) + (i32.const 1) + (catch + (drop + (exnref.pop) + ) + (i32.const 3) + ) + ) + ) + (drop + (local.get $x0) + ) + (local.set $x1 + (try (result i32) + (block (result i32) + (call $dummy) + (i32.const 1) + ) + (catch + (drop + (exnref.pop) + ) + (i32.const 3) + ) + ) + ) + (drop + (i32.and + (local.get $x1) + (i32.const 7) + ) + ) + (local.set $x2 + (try (result i32) + (block (result i32) + (try + (throw $e + (i32.const 0) + ) + (catch + (drop + (exnref.pop) + ) + ) + ) + (i32.const 1) + ) + (catch + (drop + (exnref.pop) + ) + (i32.const 3) + ) + ) + ) + (drop + (local.get $x2) + ) + (local.set $x3 + (try (result i32) + (block (result i32) + (try + (nop) + (catch + (drop + (exnref.pop) + ) + (throw $e + (i32.const 0) + ) + ) + ) + (i32.const 1) + ) + (catch + (drop + (exnref.pop) + ) + (i32.const 3) + ) + ) + ) + (drop + (i32.and + (local.get $x3) + (i32.const 7) + ) + ) + ) +) diff --git a/test/passes/remove-unused-names_optimize-instructions_all-features.wast b/test/passes/remove-unused-names_optimize-instructions_all-features.wast new file mode 100644 index 000000000..bb8f90074 --- /dev/null +++ b/test/passes/remove-unused-names_optimize-instructions_all-features.wast @@ -0,0 +1,78 @@ +(module + (func $dummy) + (event $e (attr 0) (param i32)) + + (func $getFallthrough ;; unit tests for Properties::getFallthrough + (local $x0 i32) + (local $x1 i32) + (local $x2 i32) + (local $x3 i32) + + ;; try - try body does not throw, can + (local.set $x0 + (try (result i32) + (i32.const 1) + (catch + (drop (exnref.pop)) + (i32.const 3) + ) + ) + ) + (drop (i32.and (local.get $x0) (i32.const 7))) + + ;; try - try body may throw, can't + (local.set $x1 + (try (result i32) + (block (result i32) + (call $dummy) + (i32.const 1) + ) + (catch + (drop (exnref.pop)) + (i32.const 3) + ) + ) + ) + (drop (i32.and (local.get $x1) (i32.const 7))) + + ;; nested try - inner try may throw but will be caught by inner catch, can + (local.set $x2 + (try (result i32) + (block (result i32) + (try + (throw $e (i32.const 0)) + (catch + (drop (exnref.pop)) + ) + ) + (i32.const 1) + ) + (catch + (drop (exnref.pop)) + (i32.const 3) + ) + ) + ) + (drop (i32.and (local.get $x2) (i32.const 7))) + + ;; nested try - inner catch may throw, can't + (local.set $x3 + (try (result i32) + (block (result i32) + (try + (catch + (drop (exnref.pop)) + (throw $e (i32.const 0)) + ) + ) + (i32.const 1) + ) + (catch + (drop (exnref.pop)) + (i32.const 3) + ) + ) + ) + (drop (i32.and (local.get $x3) (i32.const 7))) + ) +) |