summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/passes/RemoveUnusedBrs.cpp26
-rw-r--r--test/lit/passes/remove-unused-brs-gc.wast84
2 files changed, 97 insertions, 13 deletions
diff --git a/src/passes/RemoveUnusedBrs.cpp b/src/passes/RemoveUnusedBrs.cpp
index 2452130cc..2ccf3c761 100644
--- a/src/passes/RemoveUnusedBrs.cpp
+++ b/src/passes/RemoveUnusedBrs.cpp
@@ -898,8 +898,34 @@ struct RemoveUnusedBrs : public WalkerPass<PostWalker<RemoveUnusedBrs>> {
auto glb = Type::getGreatestLowerBound(curr->castType, refType);
if (glb != Type::unreachable && glb != curr->castType) {
curr->castType = glb;
+ auto oldType = curr->type;
curr->finalize();
worked = true;
+
+ // We refined the castType, which may *un*-refine the BrOn itself.
+ // Imagine the castType was nullable before, then nulls would go on
+ // the branch, and so the BrOn could only flow out a non-nullable
+ // value, and that was its type. If we refine the castType to be
+ // non-nullable then nulls no longer go through, making the BrOn
+ // itself nullable. This should not normally happen, but can occur
+ // because we look at the fallthrough of the ref:
+ //
+ // (br_on_cast
+ // (local.tee $unrefined
+ // (refined
+ //
+ // That is, we may see a more refined type for our GLB computation
+ // than the wasm type system does, if a local.tee or such ends up
+ // unrefining the type.
+ //
+ // To check for this and fix it, see if we need a cast in order to be
+ // a subtype of the old type.
+ auto* rep = maybeCast(curr, oldType);
+ if (rep != curr) {
+ replaceCurrent(rep);
+ // Exit after doing so, leaving further work for other cycles.
+ return;
+ }
}
// Depending on what we know about the cast results, we may be able to
diff --git a/test/lit/passes/remove-unused-brs-gc.wast b/test/lit/passes/remove-unused-brs-gc.wast
index 42c2f4b1c..0a9e08858 100644
--- a/test/lit/passes/remove-unused-brs-gc.wast
+++ b/test/lit/passes/remove-unused-brs-gc.wast
@@ -13,7 +13,10 @@
(type $substruct (sub $struct (struct)))
)
- ;; CHECK: (func $br_on-if (type $7) (param $0 (ref struct))
+ ;; CHECK: (type $struct-nn (struct (field (ref any))))
+ (type $struct-nn (struct (field (ref any))))
+
+ ;; CHECK: (func $br_on-if (type $8) (param $0 (ref struct))
;; CHECK-NEXT: (block $label
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (select (result (ref struct))
@@ -48,7 +51,7 @@
)
)
- ;; CHECK: (func $br_on_cast (type $4) (result (ref $struct))
+ ;; CHECK: (func $br_on_cast (type $5) (result (ref $struct))
;; CHECK-NEXT: (local $struct (ref null $struct))
;; CHECK-NEXT: (block $block (result (ref $struct))
;; CHECK-NEXT: (drop
@@ -99,7 +102,7 @@
)
)
- ;; CHECK: (func $br_on_cast-fallthrough (type $4) (result (ref $struct))
+ ;; CHECK: (func $br_on_cast-fallthrough (type $5) (result (ref $struct))
;; CHECK-NEXT: (local $struct (ref null $struct))
;; CHECK-NEXT: (local $any anyref)
;; CHECK-NEXT: (block $block (result (ref $struct))
@@ -162,7 +165,7 @@
)
)
- ;; CHECK: (func $nested_br_on_cast (type $8) (result i31ref)
+ ;; CHECK: (func $nested_br_on_cast (type $9) (result i31ref)
;; CHECK-NEXT: (block $label$1 (result (ref i31))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (br $label$1
@@ -190,7 +193,7 @@
)
)
- ;; CHECK: (func $br_on_cast_unrelated (type $5) (result (ref null $struct))
+ ;; CHECK: (func $br_on_cast_unrelated (type $6) (result (ref null $struct))
;; CHECK-NEXT: (local $nullable-struct2 (ref null $struct2))
;; CHECK-NEXT: (block $block (result nullref)
;; CHECK-NEXT: (drop
@@ -244,7 +247,7 @@
)
)
- ;; CHECK: (func $br_on_cast_unrelated-fallthrough (type $5) (result (ref null $struct))
+ ;; CHECK: (func $br_on_cast_unrelated-fallthrough (type $6) (result (ref null $struct))
;; CHECK-NEXT: (local $any anyref)
;; CHECK-NEXT: (local $nullable-struct2 (ref null $struct2))
;; CHECK-NEXT: (block $block (result nullref)
@@ -254,8 +257,10 @@
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
- ;; CHECK-NEXT: (local.tee $any
- ;; CHECK-NEXT: (struct.new_default $struct2)
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.tee $any
+ ;; CHECK-NEXT: (struct.new_default $struct2)
+ ;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
@@ -285,7 +290,8 @@
)
)
(drop
- ;; Still not taken.
+ ;; Still not taken. Note that we start by flowing out a non-nullable value,
+ ;; and will add a cast to ensure we still do after optimization.
(br_on_cast $block anyref (ref null $struct)
(local.tee $any (struct.new $struct2))
)
@@ -543,7 +549,7 @@
)
)
- ;; CHECK: (func $br_on_cast-unreachable (type $6) (param $i31ref i31ref) (result anyref)
+ ;; CHECK: (func $br_on_cast-unreachable (type $7) (param $i31ref i31ref) (result anyref)
;; CHECK-NEXT: (block $block
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block
@@ -599,7 +605,7 @@
)
)
- ;; CHECK: (func $fallthrough-unreachable (type $6) (param $0 i31ref) (result anyref)
+ ;; CHECK: (func $fallthrough-unreachable (type $7) (param $0 i31ref) (result anyref)
;; CHECK-NEXT: (block $outer
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (block ;; (replaces unreachable RefCast we can't emit)
@@ -638,7 +644,7 @@
)
)
- ;; CHECK: (func $casts-are-costly (type $9) (param $x i32)
+ ;; CHECK: (func $casts-are-costly (type $10) (param $x i32)
;; CHECK-NEXT: (local $struct (ref null $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (if (result i32)
@@ -783,7 +789,7 @@
)
)
- ;; CHECK: (func $threading (type $10) (param $x anyref)
+ ;; CHECK: (func $threading (type $11) (param $x anyref)
;; CHECK-NEXT: (block $outer
;; CHECK-NEXT: (block $inner
;; CHECK-NEXT: (drop
@@ -806,4 +812,56 @@
)
)
)
+
+ ;; CHECK: (func $test (type $12) (param $x (ref any))
+ ;; CHECK-NEXT: (local $temp anyref)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block $block (result (ref $struct-nn))
+ ;; CHECK-NEXT: (struct.new $struct-nn
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (br_on_cast $block anyref (ref $struct-nn)
+ ;; CHECK-NEXT: (local.tee $temp
+ ;; CHECK-NEXT: (local.get $x)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $x (ref any))
+ (local $temp anyref)
+ ;; Read the inline comments blow from the bottom to the top (order of
+ ;; execution). Basically, the story is that the br_on_cast begins as
+ ;; flowing out a non-nullable type, since the cast allows nulls (so only a
+ ;; non-null can flow out). We can see that the br_on_cast receives a non-
+ ;; nullable value, even though it flows through a local.tee that un-refines
+ ;; it. Using the non-nullability, we can refine the cast type (type sent on
+ ;; the branch) to be non-nullable. But then the type of the br_on_cast itself
+ ;; becomes nullable, since nulls no longer get sent on the branch, which
+ ;; breaks the parent that must receive a non-nullable value.
+ ;;
+ ;; To fix this, we add a cast on the br's output, forcing it to the exact
+ ;; same type it had before.
+ (drop
+ (block $block (result anyref)
+ (struct.new $struct-nn ;; must provide a NON-
+ ;; nullable value for the
+ ;; struct field
+
+ (br_on_cast $block anyref (ref null $struct-nn) ;; GLB on the castType
+ ;; makes it non-nullable,
+ ;; which makes the type
+ ;; of the br_on_cast
+ ;; nullable
+
+ (local.tee $temp ;; nullable
+
+ (local.get $x) ;; non-nullable
+ )
+ )
+ )
+ )
+ )
+ )
)