diff options
author | Alon Zakai <azakai@google.com> | 2021-10-26 11:38:18 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-10-26 18:38:18 +0000 |
commit | c0e17235ad0a6da2100835e7719be6295028698a (patch) | |
tree | 2deb935ec596b4ab1ef3472517593363c4b496ac /test | |
parent | f1f4acfa7c69f14d415a3fe0eef8439c9bde1cb4 (diff) | |
download | binaryen-c0e17235ad0a6da2100835e7719be6295028698a.tar.gz binaryen-c0e17235ad0a6da2100835e7719be6295028698a.tar.bz2 binaryen-c0e17235ad0a6da2100835e7719be6295028698a.zip |
GlobalTypeOptimization: Handle side effects in removed fields in struct.new (#4263)
If struct.new operands have side effects, and we are removing the operand
as the field is removed, we must keep the side effects. To handle that, store
all the operands in locals and read from the locals, and then removing a
local.get is always safe to do, and nothing has been reordered:
(struct.new
(A)
(side effect) ;; this field will be removed
(B)
)
=>
(local.set $a (A))
(local.set $t (side effect))
(local.set $b (B))
(struct.new
(local.get $a)
(local.get $b)
)
Later passes can remove unneeded local operations etc.
This is necessary before enabling this pass, as this corner case occurs on
j2wasm.
Diffstat (limited to 'test')
-rw-r--r-- | test/lit/passes/gto-removals.wast | 157 |
1 files changed, 157 insertions, 0 deletions
diff --git a/test/lit/passes/gto-removals.wast b/test/lit/passes/gto-removals.wast index 2247285ca..4f7402e11 100644 --- a/test/lit/passes/gto-removals.wast +++ b/test/lit/passes/gto-removals.wast @@ -387,3 +387,160 @@ ) ) ) + +(module + ;; A new with side effects + + ;; CHECK: (type $struct (struct_subtype (field i32) (field (rtt $struct)) data)) + (type $struct (struct i32 f64 (ref any) (rtt $struct))) + + ;; CHECK: (type $ref|any|_=>_none (func_subtype (param (ref any)) func)) + + ;; CHECK: (type $none_=>_none (func_subtype func)) + + ;; CHECK: (type $i32_=>_i32 (func_subtype (param i32) (result i32) func)) + + ;; CHECK: (type $i32_=>_f64 (func_subtype (param i32) (result f64) func)) + + ;; CHECK: (type $i32_=>_ref|any| (func_subtype (param i32) (result (ref any)) func)) + + ;; CHECK: (func $gets (param $x (ref any)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 0 + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.get $struct 1 + ;; CHECK-NEXT: (ref.null $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $gets (param $x (ref any)) + ;; Gets to keep certain fields alive. + (drop + (struct.get $struct 0 + (ref.null $struct) + ) + ) + (drop + (struct.get $struct 3 + (ref.null $struct) + ) + ) + ) + + ;; CHECK: (func $new-side-effect + ;; CHECK-NEXT: (local $0 i32) + ;; CHECK-NEXT: (local $1 f64) + ;; CHECK-NEXT: (local $2 anyref) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block (result (ref $struct)) + ;; CHECK-NEXT: (local.set $0 + ;; CHECK-NEXT: (call $helper0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $1 + ;; CHECK-NEXT: (call $helper1 + ;; CHECK-NEXT: (i32.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (local.set $2 + ;; CHECK-NEXT: (call $helper2 + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (local.get $0) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $new-side-effect + ;; The 2nd&3rd fields here will be removed, since those fields have no + ;; reads. They has side effects, though, so the operands will be saved in + ;; locals. Note that we can't save the rtt.canon in locals, but it has + ;; no effects, and we leave such arguments as they are. + ;; Note also that one of the fields is non-nullable, and we need to use a + ;; nullable local for it. + (drop + (struct.new $struct + (call $helper0 (i32.const 0)) + (call $helper1 (i32.const 1)) + (call $helper2 (i32.const 2)) + (rtt.canon $struct) + ) + ) + ) + + ;; CHECK: (func $new-unreachable + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (block + ;; CHECK-NEXT: (i32.const 2) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: (call $helper2 + ;; CHECK-NEXT: (i32.const 3) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $new-unreachable + ;; Another case with side effects. We stop at the unreachable param before + ;; it, however. + (drop + (struct.new $struct + (i32.const 2) + (unreachable) + (call $helper2 (i32.const 3)) + (rtt.canon $struct) + ) + ) + ) + + ;; CHECK: (func $new-side-effect-in-kept (param $any (ref any)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (struct.new $struct + ;; CHECK-NEXT: (call $helper0 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (rtt.canon $struct) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + (func $new-side-effect-in-kept (param $any (ref any)) + ;; Side effects appear in fields that we do *not* remove. In that case, + ;; we do not need to use locals. + (drop + (struct.new $struct + (call $helper0 (i32.const 0)) + (f64.const 3.14159) + (local.get $any) + (rtt.canon $struct) + ) + ) + ) + + ;; CHECK: (func $helper0 (param $x i32) (result i32) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $helper0 (param $x i32) (result i32) + (unreachable) + ) + + ;; CHECK: (func $helper1 (param $x i32) (result f64) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $helper1 (param $x i32) (result f64) + (unreachable) + ) + + ;; CHECK: (func $helper2 (param $x i32) (result (ref any)) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + (func $helper2 (param $x i32) (result (ref any)) + (unreachable) + ) +) |