From 9c8f0604dc33af26dd5dd6b3c85b78e475a2d2ef Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 10 Jul 2026 11:22:34 -0700 Subject: [PATCH 1/8] ConstraintAnalysis: Do not apply constraints in unreachable code --- src/passes/ConstraintAnalysis.cpp | 8 +++++--- test/lit/passes/constraint-analysis.wast | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 25173ee8d9f..caf665ede1a 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -187,12 +187,14 @@ struct ConstraintAnalysis // of course not needed at this stage.) auto& constraints = block->contents.startConstraints; for (auto** currp : block->contents.actions) { - applyToConstraints(*currp, constraints); - if (constraints.unreachable) { + if (!constraints.unreachable) { + applyToConstraints(*currp, constraints); + optimizeExpression(currp, constraints); + } else { + // This is unreachable code: just mark it so. *currp = Builder(*getModule()).makeUnreachable(); refinalize = true; } - optimizeExpression(currp, constraints); } } diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 5b51766a9a8..6e060d48868 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -3472,4 +3472,27 @@ (br $loop) ) ) + + ;; CHECK: (func $unreachable-loop (type $1) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $unreachable-loop (type $1) + ;; OPTIN-NEXT: (local $x i32) + ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: ) + (func $unreachable-loop + (local $x i32) + ;; The entire loop is unreachable. The control flow here should not cause any + ;; internal errors, and we can just optimize this to unreachable. + (unreachable) + (local.set $x + (loop $loop (result i32) + (i32.const 0) + ) + ) + ) ) + From d9456a3e41b574e6f99b7a659aea7574973df615 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 10 Jul 2026 11:31:03 -0700 Subject: [PATCH 2/8] ConstraintAnalysis: Fix assertion on trivial info --- src/ir/constraint.cpp | 6 +--- test/lit/passes/constraint-analysis.wast | 36 ++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/ir/constraint.cpp b/src/ir/constraint.cpp index 8a192bbd6de..6332f8a5224 100644 --- a/src/ir/constraint.cpp +++ b/src/ir/constraint.cpp @@ -288,11 +288,7 @@ void BasicBlockConstraintMap::set(Index index, const Constraint& c) { // Clear the old state. eraseStaleRefs(index); - map[index].setProvesNothing(); - // Note that this last line puts us in a temporarily invalid state: we do not - // normally store a constraint of proves-nothing in the map. Doing it this - // way, until approximateAnd adds the constraint, is more efficient than - // erasing and re-adding. + map.erase(index); // Apply the constraint. approximateAnd(index, c); diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 6e060d48868..34271c2d296 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -3494,5 +3494,41 @@ ) ) ) + + ;; CHECK: (func $set-silly (type $0) (param $x i32) + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (local.get $x) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (if + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (then + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; OPTIN: (func $set-silly (type $0) (param $x i32) + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (local.get $x) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (if + ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: (then + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + (func $set-silly (param $x i32) + ;; Setting x to x adds no information, and we do not store it in the IR. This + ;; must not lead to internal errors (we have asserts on not storing + ;; "proves-nothing" in the internal map, and if we wrote this, it would be + ;; that). + (local.set $x + (local.get $x) + ) + ;; Add some control flow to exercise the assert. + (if + (i32.const 0) + (then + ) + ) + ) ) From 2a4c8dcae1226065a4ddc0507a27fe5cc49f1db4 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 10 Jul 2026 12:58:30 -0700 Subject: [PATCH 3/8] start --- test/lit/passes/constraint-analysis-eh.wast | 23 +++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 test/lit/passes/constraint-analysis-eh.wast diff --git a/test/lit/passes/constraint-analysis-eh.wast b/test/lit/passes/constraint-analysis-eh.wast new file mode 100644 index 00000000000..5f3cb73d41f --- /dev/null +++ b/test/lit/passes/constraint-analysis-eh.wast @@ -0,0 +1,23 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. + +;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s + +(module + (tag $tag (param i32)) + + (func $0 + (local $x i32) + (try + (do + ) + (catch $tag + ;; This code is unreachable: no throw exists that can reach it. However, it + ;; is invalid to remove a pop, as that would not validate, so we keep it. + (local.set $x + (pop i32) + ) + ) + ) + ) +) + From da78ba99ad63e21dac7e65e611cb0fe27b4b74ec Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 10 Jul 2026 13:00:56 -0700 Subject: [PATCH 4/8] work --- src/passes/ConstraintAnalysis.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index 25173ee8d9f..cb315735a05 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -187,12 +187,18 @@ struct ConstraintAnalysis // of course not needed at this stage.) auto& constraints = block->contents.startConstraints; for (auto** currp : block->contents.actions) { - applyToConstraints(*currp, constraints); - if (constraints.unreachable) { - *currp = Builder(*getModule()).makeUnreachable(); + if (!constraints.unreachable) { + applyToConstraints(*currp, constraints); + optimizeExpression(currp, constraints); + } else { + // This is unreachable code: just mark it so. + *currp = getDroppedChildrenAndAppend(*currp, + *getModule(), + getPassOptions(), + Builder(*getModule()).makeUnreachable(), + DropMode::IgnoreParentEffects); refinalize = true; } - optimizeExpression(currp, constraints); } } From 79a1f3c2831665160c79f64cd4bb21efbe3e6bc0 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 10 Jul 2026 13:01:10 -0700 Subject: [PATCH 5/8] finish --- test/lit/passes/constraint-analysis-eh.wast | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/lit/passes/constraint-analysis-eh.wast b/test/lit/passes/constraint-analysis-eh.wast index 5f3cb73d41f..247495ac141 100644 --- a/test/lit/passes/constraint-analysis-eh.wast +++ b/test/lit/passes/constraint-analysis-eh.wast @@ -3,8 +3,22 @@ ;; RUN: wasm-opt %s --constraint-analysis -all -S -o - | filecheck %s (module + ;; CHECK: (tag $tag (type $0) (param i32)) (tag $tag (param i32)) + ;; CHECK: (func $0 (type $1) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (try + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) (func $0 (local $x i32) (try From 25abfe7d4002ab02f40ecfe4bf87a349547992a3 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 10 Jul 2026 13:15:58 -0700 Subject: [PATCH 6/8] fix --- src/passes/ConstraintAnalysis.cpp | 2 + test/lit/passes/constraint-analysis-eh.wast | 44 ++++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index cb315735a05..d9dd09a2b62 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -25,6 +25,7 @@ #include "cfg/cfg-traversal.h" #include "ir/constraint.h" #include "ir/drop.h" +#include "ir/eh-utils.h" #include "ir/literal-utils.h" #include "ir/local-graph.h" #include "ir/properties.h" @@ -204,6 +205,7 @@ struct ConstraintAnalysis if (refinalize) { ReFinalize().walkFunctionInModule(getFunction(), getModule()); + EHUtils::handleBlockNestedPops(getFunction(), *getModule()); } } diff --git a/test/lit/passes/constraint-analysis-eh.wast b/test/lit/passes/constraint-analysis-eh.wast index 247495ac141..5afec40fab6 100644 --- a/test/lit/passes/constraint-analysis-eh.wast +++ b/test/lit/passes/constraint-analysis-eh.wast @@ -6,7 +6,7 @@ ;; CHECK: (tag $tag (type $0) (param i32)) (tag $tag (param i32)) - ;; CHECK: (func $0 (type $1) + ;; CHECK: (func $pop-unreachable (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (try ;; CHECK-NEXT: (do @@ -19,7 +19,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - (func $0 + (func $pop-unreachable (local $x i32) (try (do @@ -33,5 +33,45 @@ ) ) ) + + ;; CHECK: (func $pop-unreachable-result (type $2) (result i32) + ;; CHECK-NEXT: (local $x i32) + ;; CHECK-NEXT: (local $1 i32) + ;; CHECK-NEXT: (try (result i32) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (i32.const 10) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (pop i32) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 20) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $pop-unreachable-result (result i32) + (local $x i32) + ;; As above, but now with a result. This adds a block we need to fix up EH + ;; for. + (try (result i32) + (do + (i32.const 10) + ) + (catch $tag + (local.set $x + (pop i32) + ) + (i32.const 20) + ) + ) + ) ) From 2281c8651bd4cd76db538c895556900c9649a475 Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 10 Jul 2026 13:51:09 -0700 Subject: [PATCH 7/8] work --- test/lit/passes/constraint-analysis-eh.wast | 7 ++++--- test/lit/passes/constraint-analysis.wast | 21 ++++++++++++++++++--- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/test/lit/passes/constraint-analysis-eh.wast b/test/lit/passes/constraint-analysis-eh.wast index 5afec40fab6..47e9cb27713 100644 --- a/test/lit/passes/constraint-analysis-eh.wast +++ b/test/lit/passes/constraint-analysis-eh.wast @@ -12,7 +12,7 @@ ;; CHECK-NEXT: (do ;; CHECK-NEXT: ) ;; CHECK-NEXT: (catch $tag - ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (pop i32) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) @@ -26,7 +26,8 @@ ) (catch $tag ;; This code is unreachable: no throw exists that can reach it. However, it - ;; is invalid to remove a pop, as that would not validate, so we keep it. + ;; is invalid to remove a pop, as that would not validate, so we keep it. We + ;; also keep the set, as it may be needed for non-nullable local validation. (local.set $x (pop i32) ) @@ -47,7 +48,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: (block (result i32) ;; CHECK-NEXT: (block - ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.set $x ;; CHECK-NEXT: (local.get $1) ;; CHECK-NEXT: ) ;; CHECK-NEXT: (unreachable) diff --git a/test/lit/passes/constraint-analysis.wast b/test/lit/passes/constraint-analysis.wast index 34271c2d296..e26197dfbb3 100644 --- a/test/lit/passes/constraint-analysis.wast +++ b/test/lit/passes/constraint-analysis.wast @@ -3476,17 +3476,32 @@ ;; CHECK: (func $unreachable-loop (type $1) ;; CHECK-NEXT: (local $x i32) ;; CHECK-NEXT: (unreachable) - ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (local.set $x + ;; CHECK-NEXT: (loop $loop (result i32) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; OPTIN: (func $unreachable-loop (type $1) ;; OPTIN-NEXT: (local $x i32) ;; OPTIN-NEXT: (unreachable) - ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: (block + ;; OPTIN-NEXT: (local.set $x + ;; OPTIN-NEXT: (loop $loop (result i32) + ;; OPTIN-NEXT: (i32.const 0) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: ) + ;; OPTIN-NEXT: (unreachable) + ;; OPTIN-NEXT: ) ;; OPTIN-NEXT: ) (func $unreachable-loop (local $x i32) ;; The entire loop is unreachable. The control flow here should not cause any - ;; internal errors, and we can just optimize this to unreachable. + ;; internal errors, and we can just optimize this to unreachable (though we + ;; keep the local.set, because of non-nullable local validation). (unreachable) (local.set $x (loop $loop (result i32) From 88d63b8c68293f957bf765b3460b67d17701ce5a Mon Sep 17 00:00:00 2001 From: Alon Zakai Date: Fri, 10 Jul 2026 13:51:16 -0700 Subject: [PATCH 8/8] format --- src/passes/ConstraintAnalysis.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/passes/ConstraintAnalysis.cpp b/src/passes/ConstraintAnalysis.cpp index a132104d2e0..78d9e71df51 100644 --- a/src/passes/ConstraintAnalysis.cpp +++ b/src/passes/ConstraintAnalysis.cpp @@ -193,10 +193,11 @@ struct ConstraintAnalysis optimizeExpression(currp, constraints); } else { // This is unreachable code: just mark it so. - *currp = getDroppedChildrenAndAppend(*currp, - *getModule(), - getPassOptions(), - Builder(*getModule()).makeUnreachable()); + *currp = getDroppedChildrenAndAppend( + *currp, + *getModule(), + getPassOptions(), + Builder(*getModule()).makeUnreachable()); refinalize = true; } }