summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2021-10-26 11:38:18 -0700
committerGitHub <noreply@github.com>2021-10-26 18:38:18 +0000
commitc0e17235ad0a6da2100835e7719be6295028698a (patch)
tree2deb935ec596b4ab1ef3472517593363c4b496ac /test
parentf1f4acfa7c69f14d415a3fe0eef8439c9bde1cb4 (diff)
downloadbinaryen-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.wast157
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)
+ )
+)