diff options
author | Alon Zakai <azakai@google.com> | 2022-02-03 14:10:46 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-02-03 22:10:46 +0000 |
commit | 9800178d7cfdb336fb5bac34f0d9844639f3a9cc (patch) | |
tree | d196a7447d865a602cb00fa90f745bafd30aacb2 /test | |
parent | 4e8b85a8d3953234bbfa490cb1d4f5c32380fb3f (diff) | |
download | binaryen-9800178d7cfdb336fb5bac34f0d9844639f3a9cc.tar.gz binaryen-9800178d7cfdb336fb5bac34f0d9844639f3a9cc.tar.bz2 binaryen-9800178d7cfdb336fb5bac34f0d9844639f3a9cc.zip |
[Wasm GC] Fix TypeRefining corner case with uncreated types (#4500)
This pass ignores reads from structs - it only cares about writes (during a
create or a struct.set). That makes sense since we want to refine the type
of fields to more specific things based on what is actually written to them.
However, a corner case was missed: If we ignore reads, the pass may
"cleverly" optimize to something that is no longer valid to read from. How
that happens is if there is no info at all for a type - no sets or news, so all
we have is a read, which as mentioned before we ignore, so we think we
have nothing at all for that type, and can do arbitrary stuff with it. But then
the arbitrary replacement can be invalid to read from, say if it has fewer
fields.
To handle that, just emit an unreachable. If all we have is a get but no
new then there cannot be an instance here at all. (That's only true in a
closed world, of course, but this entire pass assumes that anyhow.)
Diffstat (limited to 'test')
-rw-r--r-- | test/lit/passes/type-refining.wast | 87 |
1 files changed, 87 insertions, 0 deletions
diff --git a/test/lit/passes/type-refining.wast b/test/lit/passes/type-refining.wast index eb33f058a..7f903d64e 100644 --- a/test/lit/passes/type-refining.wast +++ b/test/lit/passes/type-refining.wast @@ -768,3 +768,90 @@ ) ) ) + +(module + ;; There are two parallel type hierarchies here: "Outer", which are objects + ;; that have fields, that contain the "Inner" objects. + ;; + ;; Root-Outer -> Leaf1-Outer + ;; -> Leaf2-Outer + ;; + ;; Root-Inner -> Leaf1-Inner + ;; -> Leaf2-Inner + ;; + ;; Adding their contents, where X[Y] means X has a field of type Y: + ;; + ;; Root-Outer[Root-Inner] -> Leaf1-Outer[Leaf1-Inner] + ;; -> Leaf2-Outer[Leaf2-Inner] + + ;; CHECK: (type $Leaf2-Inner (struct_subtype $Root-Inner)) + (type $Leaf2-Inner (struct_subtype $Root-Inner)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $Leaf1-Outer (struct_subtype (field (ref $Leaf2-Inner)) $Root-Outer)) + (type $Leaf1-Outer (struct_subtype (field (ref $Leaf1-Inner)) $Root-Outer)) + + ;; CHECK: (type $Leaf2-Outer (struct_subtype (field (ref $Leaf2-Inner)) $Root-Outer)) + (type $Leaf2-Outer (struct_subtype (field (ref $Leaf2-Inner)) $Root-Outer)) + + ;; CHECK: (type $Root-Outer (struct_subtype (field (ref $Leaf2-Inner)) data)) + (type $Root-Outer (struct_subtype (field (ref $Root-Inner)) data)) + + ;; CHECK: (type $Root-Inner (struct_subtype data)) + (type $Root-Inner (struct_subtype data)) + + (type $Leaf1-Inner (struct_subtype (field i32) $Root-Inner)) + + ;; CHECK: (func $func (type $none_=>_none) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block ;; (replaces something unreachable we can't emit) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (ref.null $Leaf1-Outer) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $Leaf2-Outer + ;; CHECK-NEXT: (struct.new_default $Leaf2-Inner) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $func + (drop + ;; The situation here is that we have only a get for some types, and no + ;; other constraints. As we ignore gets, we work under no constraints at + ;; We then have to pick some type, so we pick the one used by our + ;; supertype - and the supertype might have picked up a type from another + ;; branch of the type tree, which is not a subtype of ours. + ;; + ;; In more detail, we never create an instance of $Leaf1-Outer, and we + ;; only have a get of its field. This optimization ignores the get (to not + ;; be limited by it). It will then optimize $Leaf1-Outer's field of + ;; $Leaf1-Inner (another struct for which we have no creation, and only a + ;; get) into $Leaf2-Inner, which is driven by the fact that we do have a + ;; creation of $Leaf2-Inner. But then this struct.get $Leaf1-Inner on field + ;; 0 is no longer valid, as we turn $Leaf1-Inner => $Leaf2-Inner, and + ;; $Leaf2-Inner has no field 0. To keep the module validating, we must not + ;; emit that. Instead, since there can be no instance of $Leaf1-Inner (as + ;; mentioned before, it is never created, nor anything that can be cast to + ;; it), we know this code is logically unreachable, and can emit an + ;; unreachable here. + (struct.get $Leaf1-Inner 0 + (struct.get $Leaf1-Outer 0 + (ref.null $Leaf1-Outer) + ) + ) + ) + (drop + (struct.new $Leaf2-Outer + (struct.new_default $Leaf2-Inner) + ) + ) + ) +) |