diff options
-rw-r--r-- | src/passes/MergeBlocks.cpp | 56 | ||||
-rw-r--r-- | test/lit/passes/merge-blocks-eh.wast | 215 |
2 files changed, 267 insertions, 4 deletions
diff --git a/src/passes/MergeBlocks.cpp b/src/passes/MergeBlocks.cpp index 147ba9f45..e0c5e575a 100644 --- a/src/passes/MergeBlocks.cpp +++ b/src/passes/MergeBlocks.cpp @@ -83,9 +83,11 @@ namespace wasm { -// Looks for reasons we can't remove the values from breaks to an origin -// For example, if there is a switch targeting us, we can't do it - we can't -// remove the value from other targets +// Looks for reasons we can't remove the values from breaks to an origin. This +// is run when we know the value sent to that block is dropped, so the value is +// not needed, but some corner cases stop us (for example, if there is a switch +// targeting us, we can't do it - we can't remove the value from the switch's +// other targets). struct ProblemFinder : public ControlFlowWalker<ProblemFinder, UnifiedExpressionVisitor<ProblemFinder>> { @@ -123,6 +125,39 @@ struct ProblemFinder return; } + if (auto* tryy = curr->dynCast<TryTable>()) { + auto num = tryy->catchTags.size(); + for (Index i = 0; i < num; i++) { + if (tryy->catchDests[i] == origin) { + // This try_table branches to the origin we care about. We know the + // value being sent to the block is dropped, so we'd like to stop + // anything from being sent to it. We can stop a ref from being sent, + // so if that is enough to remove all the values, then we can + // optimize here. In other words, if this is a catch_all_ref (which + // can only send a ref) or this is a catch_ref of a specific tag that + // has no contents (so if we remove the ref, nothing remains), then we + // can optimize, but if this is is a catch of a tag *with* contents + // then those contents stop us. + // + // TODO: We could also support cases where the target block has + // multiple values, and remove just ref at the end. That might + // make more sense in TupleOptimization though as it would need + // to track uses of parts of a tuple. + if (!tryy->catchTags[i] || + getModule()->getTag(tryy->catchTags[i])->sig.params.size() == 0) { + // There must be a ref here, otherwise there is no value being sent + // at all, and we should not be running ProblemFinder at all. + assert(tryy->catchRefs[i]); + } else { + // Anything else is a problem. + foundProblem = true; + return; + } + } + } + return; + } + // Any other branch type - switch, br_on, etc. - is not handled yet. BranchUtils::operateOnScopeNameUses(curr, [&](Name& name) { if (name == origin) { @@ -174,6 +209,18 @@ struct BreakValueDropper : public ControlFlowWalker<BreakValueDropper> { replaceCurrent(curr->value); } } + + void visitTryTable(TryTable* curr) { + auto num = curr->catchTags.size(); + for (Index i = 0; i < num; i++) { + if (curr->catchDests[i] == origin) { + // Remove the existing ref being sent. + assert(curr->catchRefs[i]); + curr->catchRefs[i] = false; + curr->sentTypes[i] = Type::none; + } + } + } }; // Checks for code after an unreachable element. @@ -223,7 +270,8 @@ static bool optimizeDroppedBlock(Drop* drop, drop->finalize(); block->list.back() = drop; } - block->finalize(); + // Remove the old type, which was from the value we just removed. + block->finalize(Type::none); return true; } diff --git a/test/lit/passes/merge-blocks-eh.wast b/test/lit/passes/merge-blocks-eh.wast new file mode 100644 index 000000000..e53f601fd --- /dev/null +++ b/test/lit/passes/merge-blocks-eh.wast @@ -0,0 +1,215 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s -all --merge-blocks -all -S -o - | filecheck %s + +(module + ;; CHECK: (import "a" "b" (func $import (type $0))) + (import "a" "b" (func $import)) + + ;; CHECK: (tag $empty) + (tag $empty) + + ;; CHECK: (tag $i32 (param i32)) + (tag $i32 (param i32)) + + ;; CHECK: (tag $exnref (param exnref)) + (tag $exnref (param exnref)) + + ;; CHECK: (func $drop-block-try_catch_all_ref (type $0) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch_all $catch) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block-try_catch_all_ref + ;; This block is dropped, so the try_table's exnref value can be removed + ;; by replacing catch_all_ref with catch_all. + (drop + (block $catch (result exnref) + (try_table (catch_all_ref $catch) + (call $import) + (unreachable) + ) + ) + ) + ) + + ;; CHECK: (func $drop-block-try_catch_ref (type $0) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch $empty $catch) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block-try_catch_ref + ;; As above, but with catch_ref instead of catch_all_ref. We can still + ;; optimize. + (drop + (block $catch (result exnref) + (try_table (catch_ref $empty $catch) + (call $import) + (unreachable) + ) + ) + ) + ) + + ;; CHECK: (func $drop-block-try_catch_multi (type $0) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch $empty $catch) (catch_all $catch) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block-try_catch_multi + ;; As above, but with two catches, both of whom can be optimized. + (drop + (block $catch (result exnref) + (try_table (catch_ref $empty $catch) (catch_all_ref $catch) + (call $import) + (unreachable) + ) + ) + ) + ) + + ;; CHECK: (func $drop-block-try_catch_all_i32 (type $0) + ;; CHECK-NEXT: (tuple.drop 2 + ;; CHECK-NEXT: (block $catch (type $1) (result i32 exnref) + ;; CHECK-NEXT: (try_table (catch_ref $i32 $catch) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block-try_catch_all_i32 + ;; Send a tag value + exnref. We don't handle this yet TODO + (tuple.drop 2 + (block $catch (result i32 exnref) + (try_table (catch_ref $i32 $catch) + (call $import) + (unreachable) + ) + ) + ) + ) + + ;; CHECK: (func $drop-block-try_catch_multi_partial (type $0) + ;; CHECK-NEXT: (tuple.drop 2 + ;; CHECK-NEXT: (block $outer (type $1) (result i32 exnref) + ;; CHECK-NEXT: (block $inner + ;; CHECK-NEXT: (try_table (catch_ref $i32 $outer) (catch_all $inner) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block-try_catch_multi_partial + ;; Two catches, one of which we can optimize, but not both. + (tuple.drop 2 + (block $outer (result i32 exnref) + (drop + (block $inner (result exnref) + (try_table (catch_ref $i32 $outer) (catch_all_ref $inner) + (call $import) + (unreachable) + ) + ) + ) + (unreachable) + ) + ) + ) + + ;; CHECK: (func $drop-block_try_catch_multi-fail (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $catch (result exnref) + ;; CHECK-NEXT: (try_table (catch $exnref $catch) (catch_all_ref $catch) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block_try_catch_multi-fail + (drop + ;; This block is sent an exnref in two ways: once as the contents of a tag + ;; and once as the ref of a catch_all_ref. We can remove the latter but + ;; not the former, and they go to the same block, so we cannot optimize. + (block $catch (result exnref) + (try_table (catch $exnref $catch) (catch_all_ref $catch) + (call $import) + (unreachable) + ) + ) + ) + ) + + ;; CHECK: (func $drop-block-try_catch_all (type $0) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch_all $catch) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block-try_catch_all + ;; Without _ref, there is nothing to optimize (and we should not error). + ;; Also since there is no ref, there is no drop anyhow. + (block $catch + (try_table (catch_all $catch) + (call $import) + (unreachable) + ) + ) + ) + + ;; CHECK: (func $drop-block-try_catch (type $0) + ;; CHECK-NEXT: (block $catch + ;; CHECK-NEXT: (try_table (catch $empty $catch) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block-try_catch + ;; As above, but with a catch instead of catch_all. + (block $catch + (try_table (catch $empty $catch) + (call $import) + (unreachable) + ) + ) + ) + + ;; CHECK: (func $drop-block-try_catch_i32 (type $0) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block $catch (result i32) + ;; CHECK-NEXT: (try_table (catch $i32 $catch) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $drop-block-try_catch_i32 + ;; Send an i32 without a ref. We can't optimize here, as we can't prevent + ;; the i32 from being sent. + (drop + (block $catch (result i32) + (try_table (catch $i32 $catch) + (call $import) + (unreachable) + ) + ) + ) + ) +) + |