diff options
-rw-r--r-- | src/passes/RemoveUnusedBrs.cpp | 26 | ||||
-rw-r--r-- | test/lit/passes/remove-unused-brs-gc.wast | 84 |
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 + ) + ) + ) + ) + ) + ) ) |