summaryrefslogtreecommitdiff
path: root/test/lit
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2023-09-26 16:45:15 -0700
committerGitHub <noreply@github.com>2023-09-26 16:45:15 -0700
commitb2af87928a8c39e9c6ebedc748412c15c95c56ab (patch)
treefb346bffb528b0edca8b33f8df96178cfefc459e /test/lit
parent3dd65a9a2231cb85b3a92a528a08213ce50c2bd8 (diff)
downloadbinaryen-b2af87928a8c39e9c6ebedc748412c15c95c56ab.tar.gz
binaryen-b2af87928a8c39e9c6ebedc748412c15c95c56ab.tar.bz2
binaryen-b2af87928a8c39e9c6ebedc748412c15c95c56ab.zip
ConstantFieldPropagation: Fully handle copies (#5969)
If we see A->f0 = A->f0 then we might be copying fields not only between instances of A but also of any subtypes of A, and so if some subtype has value x then that x might now have reached any other subtype of A (even in a sibling type, so long as A is their parent). We already thought we were handling that, but the mechanism we used to do so (copying New info to Set info, and letting Set info propagate) was not enough. Also add a small constructor to save the work of computing subTypes again. Add TODOs for some cases that we could optimize regarding copies but do not, yet.
Diffstat (limited to 'test/lit')
-rw-r--r--test/lit/passes/cfp.wast324
1 files changed, 324 insertions, 0 deletions
diff --git a/test/lit/passes/cfp.wast b/test/lit/passes/cfp.wast
index e3bd4b049..38e17c578 100644
--- a/test/lit/passes/cfp.wast
+++ b/test/lit/passes/cfp.wast
@@ -2292,3 +2292,327 @@
)
)
)
+
+;; A type with two subtypes. A copy on the parent can affect either child.
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32)))))
+ (type $A (sub (struct (field (mut i32)))))
+
+ ;; CHECK: (type $B1 (sub $A (struct (field (mut i32)))))
+ (type $B1 (sub $A (struct (field (mut i32)))))
+
+ ;; CHECK: (type $B2 (sub $A (struct (field (mut i32)))))
+ (type $B2 (sub $A (struct (field (mut i32)))))
+ )
+
+ ;; CHECK: (type $3 (func (param (ref null $A) (ref null $B1) (ref null $B2))))
+
+ ;; CHECK: (func $test (type $3) (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $A
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $B1
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $B2
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.set $A 0
+ ;; CHECK-NEXT: (local.get $A)
+ ;; CHECK-NEXT: (struct.get $A 0
+ ;; CHECK-NEXT: (local.get $A)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $A 0
+ ;; CHECK-NEXT: (local.get $A)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B1 0
+ ;; CHECK-NEXT: (local.get $B1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B2 0
+ ;; CHECK-NEXT: (local.get $B2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2))
+ ;; A and B1 agree on their value in their construction.
+ (drop
+ (struct.new $A
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $B1
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $B2
+ (i32.const 20) ;; this value is different from the others
+ )
+ )
+
+ ;; Copy on $A
+ (struct.set $A 0
+ (local.get $A)
+ (struct.get $A 0
+ (local.get $A)
+ )
+ )
+
+ ;; $A might read either child, so we can't infer.
+ (drop
+ (struct.get $A 0
+ (local.get $A)
+ )
+ )
+ ;; $B1 should be only able to read 10, but the copy opens the possibility
+ ;; of 20, so we can't optimize.
+ (drop
+ (struct.get $B1 0
+ (local.get $B1)
+ )
+ )
+ ;; As with $B1, the copy stops us.
+ (drop
+ (struct.get $B2 0
+ (local.get $B2)
+ )
+ )
+ )
+)
+
+;; As above, but now the copy is on B1.
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32)))))
+ (type $A (sub (struct (field (mut i32)))))
+
+ ;; CHECK: (type $B1 (sub $A (struct (field (mut i32)))))
+ (type $B1 (sub $A (struct (field (mut i32)))))
+
+ ;; CHECK: (type $B2 (sub $A (struct (field (mut i32)))))
+ (type $B2 (sub $A (struct (field (mut i32)))))
+ )
+
+ ;; CHECK: (type $3 (func (param (ref null $A) (ref null $B1) (ref null $B2))))
+
+ ;; CHECK: (func $test (type $3) (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $A
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $B1
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $B2
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.set $B1 0
+ ;; CHECK-NEXT: (local.get $B1)
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $B1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $A 0
+ ;; CHECK-NEXT: (local.get $A)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $B1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B2 0
+ ;; CHECK-NEXT: (local.get $B2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2))
+ (drop
+ (struct.new $A
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $B1
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $B2
+ (i32.const 20)
+ )
+ )
+
+ ;; This changed from $A to $B1.
+ (struct.set $B1 0
+ (local.get $B1)
+ (struct.get $B1 0
+ (local.get $B1)
+ )
+ )
+
+ ;; This might still read $B1 or $B2, with different values, so we can't
+ ;; optimize.
+ (drop
+ (struct.get $A 0
+ (local.get $A)
+ )
+ )
+ ;; The copy can only refer to $B1, so we can optimize here.
+ (drop
+ (struct.get $B1 0
+ (local.get $B1)
+ )
+ )
+ ;; The copy can't refer to a $B2, so we can optimize here. TODO
+ (drop
+ (struct.get $B2 0
+ (local.get $B2)
+ )
+ )
+ )
+)
+
+;; As above, but now the copy is on B2.
+(module
+ (rec
+ ;; CHECK: (rec
+ ;; CHECK-NEXT: (type $A (sub (struct (field (mut i32)))))
+ (type $A (sub (struct (field (mut i32)))))
+
+ ;; CHECK: (type $B1 (sub $A (struct (field (mut i32)))))
+ (type $B1 (sub $A (struct (field (mut i32)))))
+
+ ;; CHECK: (type $B2 (sub $A (struct (field (mut i32)))))
+ (type $B2 (sub $A (struct (field (mut i32)))))
+ )
+
+ ;; CHECK: (type $3 (func (param (ref null $A) (ref null $B1) (ref null $B2))))
+
+ ;; CHECK: (func $test (type $3) (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $A
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $B1
+ ;; CHECK-NEXT: (i32.const 10)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.new $B2
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (struct.set $B2 0
+ ;; CHECK-NEXT: (local.get $B2)
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $B2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $A 0
+ ;; CHECK-NEXT: (local.get $A)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $B1 0
+ ;; CHECK-NEXT: (local.get $B1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (block (result i32)
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $B2)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (i32.const 20)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $A (ref null $A)) (param $B1 (ref null $B1)) (param $B2 (ref null $B2))
+ (drop
+ (struct.new $A
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $B1
+ (i32.const 10)
+ )
+ )
+ (drop
+ (struct.new $B2
+ (i32.const 20)
+ )
+ )
+
+ ;; This changed from $A to $B2.
+ (struct.set $B2 0
+ (local.get $B2)
+ (struct.get $B2 0
+ (local.get $B2)
+ )
+ )
+
+ ;; This might still read $B1 or $B2, with different values, so we can't
+ ;; optimize.
+ (drop
+ (struct.get $A 0
+ (local.get $A)
+ )
+ )
+ ;; The copy can't refer to a $B1, so we can optimize here. TODO
+ (drop
+ (struct.get $B1 0
+ (local.get $B1)
+ )
+ )
+ ;; $B2 is copied to itself, and nothing else, so we can optimize here.
+ (drop
+ (struct.get $B2 0
+ (local.get $B2)
+ )
+ )
+ )
+)