diff options
author | Alon Zakai <azakai@google.com> | 2023-09-26 16:45:15 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-26 16:45:15 -0700 |
commit | b2af87928a8c39e9c6ebedc748412c15c95c56ab (patch) | |
tree | fb346bffb528b0edca8b33f8df96178cfefc459e /test/lit | |
parent | 3dd65a9a2231cb85b3a92a528a08213ce50c2bd8 (diff) | |
download | binaryen-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.wast | 324 |
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) + ) + ) + ) +) |