summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/RemoveUnusedBrs.cpp57
-rw-r--r--test/passes/remove-unused-brs_all-features.txt75
-rw-r--r--test/passes/remove-unused-brs_all-features.wast55
3 files changed, 184 insertions, 3 deletions
diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp
index eaf681a08..3bf56838e 100644
--- a/src/passes/RemoveUnusedBrs.cpp
+++ b/src/passes/RemoveUnusedBrs.cpp
@@ -380,6 +380,60 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> {
// later down, see visitLocalSet.
}
+ void visitBrOn(BrOn* curr) {
+ // Ignore unreachable BrOns which we cannot improve anyhow.
+ if (curr->type == Type::unreachable) {
+ return;
+ }
+
+ // If the type provides enough information we may be able to know if this
+ // br is taken or not. If so, the br_on* may be unneeded. First, check for a
+ // possible null which would prevent such an optimization.
+ auto refType = curr->ref->type;
+ if (refType.isNullable()) {
+ return;
+ }
+
+ // Nulls are not possible, so specialization may be achievable, either
+ // removing the br_on* entirely or replacing it with a br.
+ auto replaceWithBr = [&]() {
+ replaceCurrent(Builder(*getModule()).makeBreak(curr->name, curr->ref));
+ anotherCycle = true;
+ };
+
+ switch (curr->op) {
+ case BrOnNull: {
+ // This cannot be null, so the br is never taken, and the non-null value
+ // flows through.
+ replaceCurrent(curr->ref);
+ anotherCycle = true;
+ break;
+ }
+ case BrOnCast: {
+ // Casts can only be done at runtime, using RTTs.
+ break;
+ }
+ case BrOnFunc: {
+ if (refType.isFunction()) {
+ replaceWithBr();
+ }
+ break;
+ }
+ case BrOnData: {
+ if (refType.isData()) {
+ replaceWithBr();
+ }
+ break;
+ }
+ case BrOnI31: {
+ if (refType.getHeapType() == HeapType::i31) {
+ replaceWithBr();
+ }
+ break;
+ }
+ }
+ }
+
// override scan to add a pre and a post check task to all nodes
static void scan(RemoveUnusedBrs* self, Expression** currp) {
self->pushTask(visitAny, currp);
@@ -657,12 +711,11 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> {
if (!flow->value) {
// return => nop
ExpressionManipulator::nop(flow);
- anotherCycle = true;
} else {
// return with value => value
*flows[i] = flow->value;
- anotherCycle = true;
}
+ anotherCycle = true;
}
flows.clear();
// optimize loops (we don't do it while tracking flows, as they can
diff --git a/test/passes/remove-unused-brs_all-features.txt b/test/passes/remove-unused-brs_all-features.txt
index f8308c65f..0c830cbc0 100644
--- a/test/passes/remove-unused-brs_all-features.txt
+++ b/test/passes/remove-unused-brs_all-features.txt
@@ -2,11 +2,13 @@
(type $struct (struct (field (ref null $vector))))
(type $vector (array (mut i32)))
(type $i32_=>_none (func (param i32)))
+ (type $ref|func|_=>_none (func (param (ref func))))
(type $none_=>_i32 (func (result i32)))
(type $none_=>_f64 (func (result f64)))
(type $i32_=>_funcref (func (param i32) (result funcref)))
(type $none_=>_ref?|$struct| (func (result (ref null $struct))))
- (elem declare func $i32_=>_none $none_=>_i32)
+ (import "out" "log" (func $log (param i32)))
+ (elem declare func $br_on-to-br $i32_=>_none $none_=>_i32)
(func $foo (result (ref null $struct))
(if (result (ref null $struct))
(i32.const 1)
@@ -50,4 +52,75 @@
(local.get $x)
)
)
+ (func $br_on-to-br (param $func (ref func))
+ (call $log
+ (i32.const 0)
+ )
+ (block $null
+ (drop
+ (ref.func $br_on-to-br)
+ )
+ (call $log
+ (i32.const 1)
+ )
+ )
+ (call $log
+ (i32.const 2)
+ )
+ (drop
+ (block $func (result funcref)
+ (drop
+ (br $func
+ (ref.func $br_on-to-br)
+ )
+ )
+ (call $log
+ (i32.const 3)
+ )
+ (ref.func $br_on-to-br)
+ )
+ )
+ (call $log
+ (i32.const 4)
+ )
+ (drop
+ (block $data (result dataref)
+ (drop
+ (br $data
+ (array.new_default_with_rtt $vector
+ (i32.const 1)
+ (rtt.canon $vector)
+ )
+ )
+ )
+ (call $log
+ (i32.const 5)
+ )
+ (array.new_default_with_rtt $vector
+ (i32.const 2)
+ (rtt.canon $vector)
+ )
+ )
+ )
+ (call $log
+ (i32.const 6)
+ )
+ (drop
+ (block $i31 (result i31ref)
+ (drop
+ (br $i31
+ (i31.new
+ (i32.const 42)
+ )
+ )
+ )
+ (call $log
+ (i32.const 7)
+ )
+ (i31.new
+ (i32.const 1337)
+ )
+ )
+ )
+ )
)
diff --git a/test/passes/remove-unused-brs_all-features.wast b/test/passes/remove-unused-brs_all-features.wast
index 1da00291d..4f66f2fdd 100644
--- a/test/passes/remove-unused-brs_all-features.wast
+++ b/test/passes/remove-unused-brs_all-features.wast
@@ -1,6 +1,7 @@
(module
(type $vector (array (mut i32)))
(type $struct (struct (field (ref null $vector))))
+ (import "out" "log" (func $log (param i32)))
(func $foo (result (ref null $struct))
(if (result (ref null $struct))
(i32.const 1)
@@ -53,4 +54,58 @@
(ref.func $i32_=>_none)
)
)
+
+ (func $br_on-to-br (param $func (ref func))
+ (call $log (i32.const 0))
+ (block $null
+ ;; a non-null reference is not null, and the br is never taken
+ (drop
+ (br_on_null $null (ref.func $br_on-to-br))
+ )
+ (call $log (i32.const 1))
+ )
+ (call $log (i32.const 2))
+ (drop
+ (block $func (result funcref)
+ ;; a non-null function reference means we always take the br
+ (drop
+ (br_on_func $func (ref.func $br_on-to-br))
+ )
+ (call $log (i32.const 3))
+ (ref.func $br_on-to-br)
+ )
+ )
+ (call $log (i32.const 4))
+ (drop
+ (block $data (result dataref)
+ ;; a non-null data reference means we always take the br
+ (drop
+ (br_on_data $data
+ (array.new_default_with_rtt $vector
+ (i32.const 1)
+ (rtt.canon $vector)
+ )
+ )
+ )
+ (call $log (i32.const 5))
+ (array.new_default_with_rtt $vector
+ (i32.const 2)
+ (rtt.canon $vector)
+ )
+ )
+ )
+ (call $log (i32.const 6))
+ (drop
+ (block $i31 (result i31ref)
+ ;; a non-null i31 reference means we always take the br
+ (drop
+ (br_on_i31 $i31
+ (i31.new (i32.const 42))
+ )
+ )
+ (call $log (i32.const 7))
+ (i31.new (i32.const 1337))
+ )
+ )
+ )
)