From dfd46ac598842c56759a776ae449c85c48031f00 Mon Sep 17 00:00:00 2001 From: Thomas Lively Date: Wed, 20 May 2026 21:12:40 -0700 Subject: [PATCH] Fix block-nested Pop in GUFA In GUFA, when optimizing RefEq or RefTest expressions, they are replaced by constants if the optimizer can prove the two sides have no intersection. However, if the expression contains side effects (like a Pop), getDroppedChildrenAndAppend is used to preserve them, which wraps the side-effect expressions in a Block. If the Pop is nested inside this Block, it becomes invalidly nested within the catch block, which violates validation rules. GUFA has a fixup pass (EHUtils::handleBlockNestedPops) designed to fix this by spilling the Pop to a local, but it was not being run because visitRefEq and visitRefTest failed to set the `optimized = true` flag. Fix the issue by setting `optimized = true` when RefEq or RefTest are optimized, ensuring the post-optimization cleanups (including the nested pop fixup) are executed. Add a regression test to gufa-cast-all.wast. --- src/passes/GUFA.cpp | 2 + test/lit/passes/gufa-cast-all.wast | 62 ++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/src/passes/GUFA.cpp b/src/passes/GUFA.cpp index a4567aaea6d..715b36db852 100644 --- a/src/passes/GUFA.cpp +++ b/src/passes/GUFA.cpp @@ -239,6 +239,7 @@ struct GUFAOptimizer auto* result = Builder(*getModule()).makeConst(Literal(int32_t(0))); replaceCurrent(getDroppedChildrenAndAppend( curr, *getModule(), getPassOptions(), result)); + optimized = true; } } @@ -260,6 +261,7 @@ struct GUFAOptimizer auto* last = Builder(*getModule()).makeConst(Literal(int32_t(result))); replaceCurrent(getDroppedChildrenAndAppend( curr, *getModule(), getPassOptions(), last)); + optimized = true; }; if (!PossibleContents::haveIntersection(refContents, intendedContents)) { diff --git a/test/lit/passes/gufa-cast-all.wast b/test/lit/passes/gufa-cast-all.wast index 28248674883..bffd88e7714 100644 --- a/test/lit/passes/gufa-cast-all.wast +++ b/test/lit/passes/gufa-cast-all.wast @@ -392,3 +392,65 @@ ) ) + +;; Regression test for bug where optimizing expressions containing a Pop could +;; result in the Pop becoming invalidly nested inside a Block (created to +;; preserve side effects), and the optimizer failed to run the nested pop fixup +;; because it didn't mark the function as optimized. +(module + ;; CHECK: (type $array (sub (array anyref))) + (type $array (sub (array (ref null any)))) + ;; CHECK: (type $tag-sig (func (param (ref null $array)))) + (type $tag-sig (func (param (ref null $array)))) + ;; CHECK: (type $2 (func)) + + ;; CHECK: (tag $tag (type $tag-sig) (param (ref null $array))) + (tag $tag (type $tag-sig) (param (ref null $array))) + + ;; CHECK: (func $test (type $2) + ;; CHECK-NEXT: (local $0 (ref null $array)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (try (result (ref i31)) + ;; CHECK-NEXT: (do + ;; CHECK-NEXT: (ref.i31 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (catch $tag + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (pop (ref null $array)) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (ref.i31 + ;; CHECK-NEXT: (block (result i32) + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $test + (drop + (try (result (ref i31)) + (do + ;; This doesn't throw, so everything in the catch is unreachable. + (ref.i31 (i32.const 0)) + ) + (catch $tag + (ref.i31 + (ref.eq + (pop (ref null $array)) + (ref.i31 (i32.const 0)) + ) + ) + ) + ) + ) + ) +)