From 4382077e693ff2eed79f08a360b85f79ff83da97 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 26 Feb 2026 12:34:33 -0800 Subject: [PATCH 1/6] opt --- src/passes/RemoveUnusedBrs.cpp | 41 +++++++++++--- test/lit/passes/remove-unused-brs_trap.wast | 63 +++++++++++++++++++++ 2 files changed, 96 insertions(+), 8 deletions(-) create mode 100644 test/lit/passes/remove-unused-brs_trap.wast diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 1a6d355ff5e..376fdd3aecb 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -1214,6 +1214,8 @@ struct RemoveUnusedBrs : public WalkerPass> { for (auto target : relevantTargets) { labelToBranches[target].push_back(curr); } + // TODO: If all but one target trap, in TNH we can unconditionally jump + // there. } void visitBlock(Block* curr) { @@ -1231,14 +1233,37 @@ struct RemoveUnusedBrs : public WalkerPass> { } } } else if (list.size() == 2) { - // if this block has two children, a child-block and a simple jump, - // then jumps to child-block can be replaced with jumps to the new - // target - auto* child = list[0]->dynCast(); - auto* jump = list[1]->dynCast(); - if (child && child->name.is() && jump && - ExpressionAnalyzer::isSimple(jump)) { - redirectBranches(child, jump->name); + // With two items, we look for this form: + // + // (block ;; curr + // (block $child ;; child + // ) + // after + // ) + // + // Anything branching to the child will end up in that second + // instruction |after|. + if (auto* child = list[0]->dynCast()) { + if (child->name) { + if (auto* jump = list[1]->dynCast()) { + if (ExpressionAnalyzer::isSimple(jump)) { + // Jumps to the child can skip ahead to where the child jumps. + redirectBranches(child, jump->name); + } + } else if (list[1]->dynCast()) { + // Unconditional jumps to a trap can just trap. + for (auto* branch : labelToBranches[child->name]) { + if (auto* br = branch->dynCast()) { + if (ExpressionAnalyzer::isSimple(br)) { + // It is safe to just modify the br in-place because it is + // only jumping here, so this the last place that will + // read it. + ExpressionManipulator::unreachable(br); + } + } + } + } + } } } } diff --git a/test/lit/passes/remove-unused-brs_trap.wast b/test/lit/passes/remove-unused-brs_trap.wast new file mode 100644 index 00000000000..ef1f546f09b --- /dev/null +++ b/test/lit/passes/remove-unused-brs_trap.wast @@ -0,0 +1,63 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. +;; RUN: wasm-opt %s --remove-unused-brs -all -S -o - | filecheck %s + +(module + ;; CHECK: (func $jump-to-trap (type $0) (param $x i32) (result i32) + ;; CHECK-NEXT: (block $trap + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (br_if $trap + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (else + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (return + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $jump-to-trap (param $x i32) (result i32) + (block $trap + (call $func) ;; add effects to avoid other opts + ;; This might go to the trap outside, but might not, so we do nothing. + (br_if $trap + (local.get $x) + ) + (call $func) + (if + (local.get $x) + (then + (call $func) + (return (i32.const 0)) + ) + (else + (call $func) + (call $func) + ;; This goes to a trap, and we can just turn it into a trap. + (br $trap) + ) + ) + (return (i32.const 1)) + ) + (unreachable) + ) + + ;; CHECK: (func $func (type $1) + ;; CHECK-NEXT: ) + (func $func + ;; Helper for above. + ) +) From 55062fda1499d554eb227dac5ebfb30f16ba5311 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 26 Feb 2026 15:42:55 -0800 Subject: [PATCH 2/6] comment --- src/passes/RemoveUnusedBrs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 376fdd3aecb..4be6b762573 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -1215,7 +1215,7 @@ struct RemoveUnusedBrs : public WalkerPass> { labelToBranches[target].push_back(curr); } // TODO: If all but one target trap, in TNH we can unconditionally jump - // there. + // to the non-trapping place. } void visitBlock(Block* curr) { From b8d68c158309bb1a24afdbc1801b50683c0818fc Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 26 Feb 2026 15:51:17 -0800 Subject: [PATCH 3/6] work --- src/passes/RemoveUnusedBrs.cpp | 3 +- test/lit/passes/remove-unused-brs_trap.wast | 58 +++++++++++++++------ 2 files changed, 45 insertions(+), 16 deletions(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 4be6b762573..55a7d32e477 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -1247,7 +1247,8 @@ struct RemoveUnusedBrs : public WalkerPass> { if (child->name) { if (auto* jump = list[1]->dynCast()) { if (ExpressionAnalyzer::isSimple(jump)) { - // Jumps to the child can skip ahead to where the child jumps. + // Unconditional jumps to the child can skip ahead to where + // the child jumps. redirectBranches(child, jump->name); } } else if (list[1]->dynCast()) { diff --git a/test/lit/passes/remove-unused-brs_trap.wast b/test/lit/passes/remove-unused-brs_trap.wast index ef1f546f09b..255b9d9c51c 100644 --- a/test/lit/passes/remove-unused-brs_trap.wast +++ b/test/lit/passes/remove-unused-brs_trap.wast @@ -2,24 +2,27 @@ ;; RUN: wasm-opt %s --remove-unused-brs -all -S -o - | filecheck %s (module - ;; CHECK: (func $jump-to-trap (type $0) (param $x i32) (result i32) + ;; CHECK: (import "a" "b" (func $import (type $0))) + (import "a" "b" (func $import)) + + ;; CHECK: (func $jump-to-trap (type $1) (param $x i32) (result i32) ;; CHECK-NEXT: (block $trap - ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: (br_if $trap ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: ) - ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: (if ;; CHECK-NEXT: (local.get $x) ;; CHECK-NEXT: (then - ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: (return ;; CHECK-NEXT: (i32.const 0) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (else - ;; CHECK-NEXT: (call $func) - ;; CHECK-NEXT: (call $func) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (call $import) ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) @@ -31,22 +34,21 @@ ;; CHECK-NEXT: ) (func $jump-to-trap (param $x i32) (result i32) (block $trap - (call $func) ;; add effects to avoid other opts + (call $import) ;; add effects to avoid other opts ;; This might go to the trap outside, but might not, so we do nothing. (br_if $trap (local.get $x) ) - (call $func) + (call $import) (if (local.get $x) (then - (call $func) + (call $import) (return (i32.const 0)) ) (else - (call $func) - (call $func) - ;; This goes to a trap, and we can just turn it into a trap. + (call $import) + (call $import) (br $trap) ) ) @@ -55,9 +57,35 @@ (unreachable) ) - ;; CHECK: (func $func (type $1) + ;; CHECK: (func $dispatch (type $2) (param $x i32) + ;; CHECK-NEXT: (block $trap + ;; CHECK-NEXT: (block $mid + ;; CHECK-NEXT: (block $top + ;; CHECK-NEXT: (br_table $top $mid $trap + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (call $import) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) ;; CHECK-NEXT: ) - (func $func - ;; Helper for above. + (func $dispatch (export "a") (param $x i32) + ;; A realistic example with br_table (which also avoids other opts from + ;; getting in the way). + (block $trap + (block $mid + (block $top + (br_table $top $mid $trap (local.get $x)) + ) + (call $import) ;; add effects to avoid other opts + ;; This goes to a trap, and we can just turn it into a trap. + (br $trap) + ) + (call $import) + ) + (unreachable) ) ) From 4f0885facf9c4370a8ea8ea4eddb5a0a6f610b95 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 26 Feb 2026 16:14:59 -0800 Subject: [PATCH 4/6] comment --- test/lit/passes/remove-unused-brs_trap.wast | 1 + 1 file changed, 1 insertion(+) diff --git a/test/lit/passes/remove-unused-brs_trap.wast b/test/lit/passes/remove-unused-brs_trap.wast index 255b9d9c51c..5bf47201b35 100644 --- a/test/lit/passes/remove-unused-brs_trap.wast +++ b/test/lit/passes/remove-unused-brs_trap.wast @@ -49,6 +49,7 @@ (else (call $import) (call $import) + ;; This goes to a trap, and we can just turn it into a trap. (br $trap) ) ) From 82441de8882abbac2a83bd88596c985d8c093b8e Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 26 Feb 2026 16:32:31 -0800 Subject: [PATCH 5/6] update.existing.test --- test/passes/remove-unused-brs_enable-multivalue.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/passes/remove-unused-brs_enable-multivalue.txt b/test/passes/remove-unused-brs_enable-multivalue.txt index 4a6384b221b..0bcfd5e08cb 100644 --- a/test/passes/remove-unused-brs_enable-multivalue.txt +++ b/test/passes/remove-unused-brs_enable-multivalue.txt @@ -2396,7 +2396,7 @@ (unreachable) ) (then - (br $label$3) + (unreachable) ) ) ) From 86dd3abbd945b008cc14344be1fcb8c821cbbe5c Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Thu, 26 Feb 2026 16:51:46 -0800 Subject: [PATCH 6/6] comment --- src/passes/RemoveUnusedBrs.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp index 55a7d32e477..d081c51539e 100644 --- a/src/passes/RemoveUnusedBrs.cpp +++ b/src/passes/RemoveUnusedBrs.cpp @@ -1242,7 +1242,8 @@ struct RemoveUnusedBrs : public WalkerPass> { // ) // // Anything branching to the child will end up in that second - // instruction |after|. + // instruction |after|. That lets us optimize the cases where |after| + // is a br or an unreachable. if (auto* child = list[0]->dynCast()) { if (child->name) { if (auto* jump = list[1]->dynCast()) {