diff options
-rw-r--r-- | src/passes/RemoveUnusedBrs.cpp | 57 | ||||
-rw-r--r-- | test/passes/remove-unused-brs_all-features.txt | 75 | ||||
-rw-r--r-- | test/passes/remove-unused-brs_all-features.wast | 55 |
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)) + ) + ) + ) ) |