summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2024-11-05 15:49:50 -0800
committerGitHub <noreply@github.com>2024-11-05 15:49:50 -0800
commit92917c28ea6ef017e71a8a97eb364ed20bc52fe2 (patch)
tree549bddb482ed85933edf6f6b28978203f64c722d /test
parent320867a7c61432691faa62892d5fd89e4f6bae6a (diff)
downloadbinaryen-92917c28ea6ef017e71a8a97eb364ed20bc52fe2.tar.gz
binaryen-92917c28ea6ef017e71a8a97eb364ed20bc52fe2.tar.bz2
binaryen-92917c28ea6ef017e71a8a97eb364ed20bc52fe2.zip
[GC] Fix ConstantFieldPropagation on incompatible types (#7054)
CFP is less precise than GUFA, in particular, when it flows around types then it does not consider what field it is flowing them to, and its core data structure is "if a struct.get is done on this type's field, what can be read?". To see the issue this PR fixes, assume we have A / \ B C Then if we see struct.set $C, we know that can be read by a struct.get $A (we can store a reference to a C in such a local/param/etc.), so we propagate the value of that set to A. And, in general, anything in A can appear in B (say, if we see a copy, a struct.set of struct.get that operates on types A, then one of the sides might be a B), so we propagate from A to B. But now we have propagated something from C to B, which might be of an incompatible type. This cannot cause runtime issues, as it just means we are propagating more than we should, and will end up with less-useful results. But it can break validation if no other value is possible but one with an incompatible type, as we'd replace a struct.get $B with a value that only makes sense for C. (The qualifier "no other value is possible" was added in the previous sentence because if another one is possible then we'd end up with too many values to infer anything, and not optimize at all, avoiding any error.)
Diffstat (limited to 'test')
-rw-r--r--test/lit/passes/cfp.wast116
1 files changed, 116 insertions, 0 deletions
diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast
index d386e7f34..461baa373 100644
--- a/test/lit/passes/cfp.wast
+++ b/test/lit/passes/cfp.wast
@@ -2710,3 +2710,119 @@
)
)
)
+
+;; $C is created with two values for its field: a global.get, and a copy from
+;; another $C, which does not expand the set of possible values. We should not
+;; get confused about $B, its sibling, which is never created, and whose field
+;; has an incompatible type.
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $X (sub (struct)))
+ (type $X (sub (struct)))
+ ;; CHECK: (type $Y (sub final $X (struct)))
+ (type $Y (sub final $X (struct)))
+ ;; CHECK: (type $Z (sub final $X (struct)))
+ (type $Z (sub final $X (struct)))
+
+ ;; CHECK: (type $A (sub (struct (field (ref null $X)))))
+ (type $A (sub (struct (field (ref null $X)))))
+ ;; CHECK: (type $B (sub final $A (struct (field (ref null $Y)))))
+ (type $B (sub final $A (struct (field (ref null $Y)))))
+ ;; CHECK: (type $C (sub final $A (struct (field (ref null $Z)))))
+ (type $C (sub final $A (struct (field (ref null $Z)))))
+ )
+
+ ;; CHECK: (type $6 (func))
+
+ ;; CHECK: (type $7 (func (param (ref null $C))))
+
+ ;; CHECK: (type $8 (func (param (ref null $A)) (result (ref null $X))))
+
+ ;; CHECK: (type $9 (func (param (ref null $B)) (result (ref null $Y))))
+
+ ;; CHECK: (global $global (ref null $Z) (struct.new_default $Z))
+ (global $global (ref null $Z) (struct.new_default $Z))
+
+ ;; CHECK: (func $new (type $6)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $C
+ ;; CHECK-NEXT: (global.get $global)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $new
+ (drop
+ (struct.new $C
+ (global.get $global)
+ )
+ )
+ )
+
+ ;; CHECK: (func $copy (type $7) (param $C (ref null $C))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $C
+ ;; CHECK-NEXT: (block (result (ref null $Z))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $C)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.get $global)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $copy (param $C (ref null $C))
+ ;; The struct.get here can be optimized to a global.get.
+ (drop
+ (struct.new $C
+ (struct.get $C 0
+ (local.get $C)
+ )
+ )
+ )
+ )
+
+ ;; CHECK: (func $get-A (type $8) (param $A (ref null $A)) (result (ref null $X))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $A)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.get $global)
+ ;; CHECK-NEXT: )
+ (func $get-A (param $A (ref null $A)) (result (ref null $X))
+ ;; The struct.get here can be optimized to a global.get. While we never
+ ;; create an $A, a $C might be referred to by an $A reference, and the
+ ;; global.get is the only possible value.
+ (struct.get $A 0
+ (local.get $A)
+ )
+ )
+
+ ;; CHECK: (func $get-B (type $9) (param $B (ref null $B)) (result (ref null $Y))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $B)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (block
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (global.get $global)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (unreachable)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $get-B (param $B (ref null $B)) (result (ref null $Y))
+ ;; This should not be optimized to a global.get: no $B is created, and we
+ ;; cannot refer to anything that is actually created. If we mistakenly
+ ;; thought this field can contain the global.get (as we do for the parent
+ ;; $A) then we would error here: $B's field contains $Y, but the global is
+ ;; is of a sibling type $Z. Instead, we can add an unreachable here, as no
+ ;; valid value is possible.
+ (struct.get $B 0
+ (local.get $B)
+ )
+ )
+)