summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorAlon Zakai <azakai@google.com>2024-06-20 15:24:14 -0700
committerGitHub <noreply@github.com>2024-06-20 15:24:14 -0700
commitc3089b3b553536ece3b1d6a9cffe82cda1b813e5 (patch)
tree21c27a8dd7515dc4f04e6cd6391bd728e50d8729 /test
parent1079a9e34599e65ee25fb5f32caa57bd21737593 (diff)
downloadbinaryen-c3089b3b553536ece3b1d6a9cffe82cda1b813e5.tar.gz
binaryen-c3089b3b553536ece3b1d6a9cffe82cda1b813e5.tar.bz2
binaryen-c3089b3b553536ece3b1d6a9cffe82cda1b813e5.zip
GlobalStructInference: Un-nest struct.news in globals when that is helpful (#6688)
If we have (global $g (struct.new $S (i32.const 1) (struct.new $T ..) (ref.func $f) )) then before this PR if we wanted to read the middle field we'd stop, as it is non-constant. However, we can un-nest it, making it constant: (global $g.unnested (struct.new $T ..)) (global $g (struct.new $S (i32.const 1) (global.get $g.unnested) (ref.func $f) )) Now the field is a global.get of an immutable global, which is constant. Using this technique we can handle anything in a struct field, constant or not. The cost of adding a global is likely offset by the benefit of being able to refer to it directly, as that opens up more opportunities later. Concretely, this replaces the constant values we look for in GSI with a variant over constants or expressions (we do still want to group constants, as multiple globals with the same constant field can be treated as a whole). And we note cases where we need to un-nest, and handle those at the end.
Diffstat (limited to 'test')
-rw-r--r--test/lit/passes/gsi.wast417
1 files changed, 412 insertions, 5 deletions
diff --git a/test/lit/passes/gsi.wast b/test/lit/passes/gsi.wast
index 15afea5ed..57d8137f4 100644
--- a/test/lit/passes/gsi.wast
+++ b/test/lit/passes/gsi.wast
@@ -841,18 +841,22 @@
)
)
-;; One global has a non-constant field, so we cannot optimize.
+;; One global has a non-constant field. We can still optimize, if we move that
+;; field out into another global, that is, if we un-nest it. The select will
+;; then pick either the constant or a global.get of the new un-nested global.
(module
;; CHECK: (type $struct (struct (field i32)))
(type $struct (struct i32))
;; CHECK: (type $1 (func (param (ref null $struct))))
+ ;; CHECK: (global $global1.unnested.0 i32 (i32.add
+ ;; CHECK-NEXT: (i32.const 41)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: ))
+
;; CHECK: (global $global1 (ref $struct) (struct.new $struct
- ;; CHECK-NEXT: (i32.add
- ;; CHECK-NEXT: (i32.const 41)
- ;; CHECK-NEXT: (i32.const 1)
- ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.get $global1.unnested.0)
;; CHECK-NEXT: ))
(global $global1 (ref $struct) (struct.new $struct
(i32.add
@@ -870,6 +874,409 @@
;; CHECK: (func $test (type $1) (param $struct (ref null $struct))
;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (global.get $global1.unnested.0)
+ ;; CHECK-NEXT: (i32.const 1337)
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.get $global1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $struct (ref null $struct))
+ (drop
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+ )
+)
+
+;; As above, but with the globals flipped. Now the second global has a non-
+;; constant field.
+(module
+ ;; CHECK: (type $struct (struct (field i32)))
+ (type $struct (struct i32))
+
+ ;; CHECK: (type $1 (func (param (ref null $struct))))
+
+ ;; CHECK: (global $global2.unnested.0 i32 (i32.add
+ ;; CHECK-NEXT: (i32.const 41)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: ))
+
+ ;; CHECK: (global $global1 (ref $struct) (struct.new $struct
+ ;; CHECK-NEXT: (i32.const 1337)
+ ;; CHECK-NEXT: ))
+ (global $global1 (ref $struct) (struct.new $struct
+ (i32.const 1337)
+ ))
+
+ ;; CHECK: (global $global2 (ref $struct) (struct.new $struct
+ ;; CHECK-NEXT: (global.get $global2.unnested.0)
+ ;; CHECK-NEXT: ))
+ (global $global2 (ref $struct) (struct.new $struct
+ (i32.add
+ (i32.const 41)
+ (i32.const 1)
+ )
+ ))
+
+ ;; CHECK: (func $test (type $1) (param $struct (ref null $struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (i32.const 1337)
+ ;; CHECK-NEXT: (global.get $global2.unnested.0)
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.get $global1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $struct (ref null $struct))
+ (drop
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+ )
+)
+
+;; As above, but now both globals have non-constant fields. We un-nest both.
+(module
+ ;; CHECK: (type $struct (struct (field i32)))
+ (type $struct (struct i32))
+
+ ;; CHECK: (type $1 (func (param (ref null $struct))))
+
+ ;; CHECK: (global $global1.unnested.0 i32 (i32.add
+ ;; CHECK-NEXT: (i32.const 13)
+ ;; CHECK-NEXT: (i32.const 37)
+ ;; CHECK-NEXT: ))
+
+ ;; CHECK: (global $global2.unnested.0 i32 (i32.add
+ ;; CHECK-NEXT: (i32.const 41)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: ))
+
+ ;; CHECK: (global $global1 (ref $struct) (struct.new $struct
+ ;; CHECK-NEXT: (global.get $global1.unnested.0)
+ ;; CHECK-NEXT: ))
+ (global $global1 (ref $struct) (struct.new $struct
+ (i32.add
+ (i32.const 13)
+ (i32.const 37)
+ )
+ ))
+
+ ;; CHECK: (global $global2 (ref $struct) (struct.new $struct
+ ;; CHECK-NEXT: (global.get $global2.unnested.0)
+ ;; CHECK-NEXT: ))
+ (global $global2 (ref $struct) (struct.new $struct
+ (i32.add
+ (i32.const 41)
+ (i32.const 1)
+ )
+ ))
+
+ ;; CHECK: (func $test (type $1) (param $struct (ref null $struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (global.get $global1.unnested.0)
+ ;; CHECK-NEXT: (global.get $global2.unnested.0)
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.get $global1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $struct (ref null $struct))
+ (drop
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+ )
+)
+
+;; Multiple and overlapping un-nesting situations.
+(module
+ ;; CHECK: (type $struct (struct (field i32) (field i32)))
+ (type $struct (struct i32 i32))
+
+ ;; CHECK: (type $1 (func (param (ref null $struct))))
+
+ ;; CHECK: (global $global1.unnested.0 i32 (i32.add
+ ;; CHECK-NEXT: (i32.const 13)
+ ;; CHECK-NEXT: (i32.const 37)
+ ;; CHECK-NEXT: ))
+
+ ;; CHECK: (global $global2.unnested.0 i32 (i32.add
+ ;; CHECK-NEXT: (i32.const 41)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: ))
+
+ ;; CHECK: (global $global1.unnested.1 i32 (i32.add
+ ;; CHECK-NEXT: (i32.const 99)
+ ;; CHECK-NEXT: (i32.const 1)
+ ;; CHECK-NEXT: ))
+
+ ;; CHECK: (global $global1 (ref $struct) (struct.new $struct
+ ;; CHECK-NEXT: (global.get $global1.unnested.0)
+ ;; CHECK-NEXT: (global.get $global1.unnested.1)
+ ;; CHECK-NEXT: ))
+ (global $global1 (ref $struct) (struct.new $struct
+ (i32.add
+ (i32.const 13)
+ (i32.const 37)
+ )
+ (i32.add
+ (i32.const 99)
+ (i32.const 1)
+ )
+ ))
+
+ ;; CHECK: (global $global2.unnested.1 i32 (i32.add
+ ;; CHECK-NEXT: (i32.const 100)
+ ;; CHECK-NEXT: (i32.const 2)
+ ;; CHECK-NEXT: ))
+
+ ;; CHECK: (global $global2 (ref $struct) (struct.new $struct
+ ;; CHECK-NEXT: (global.get $global2.unnested.0)
+ ;; CHECK-NEXT: (global.get $global2.unnested.1)
+ ;; CHECK-NEXT: ))
+ (global $global2 (ref $struct) (struct.new $struct
+ (i32.add
+ (i32.const 41)
+ (i32.const 1)
+ )
+ (i32.add
+ (i32.const 100)
+ (i32.const 2)
+ )
+ ))
+
+ ;; CHECK: (func $test (type $1) (param $struct (ref null $struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (global.get $global1.unnested.0)
+ ;; CHECK-NEXT: (global.get $global2.unnested.0)
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.get $global1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (global.get $global1.unnested.0)
+ ;; CHECK-NEXT: (global.get $global2.unnested.0)
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.get $global1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $struct (ref null $struct))
+ ;; We only need to un-nest once for these two.
+ (drop
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+ (drop
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+ )
+
+ ;; CHECK: (func $test2 (type $1) (param $struct (ref null $struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (global.get $global1.unnested.0)
+ ;; CHECK-NEXT: (global.get $global2.unnested.0)
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.get $global1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (global.get $global1.unnested.1)
+ ;; CHECK-NEXT: (global.get $global2.unnested.1)
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.get $global1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (select
+ ;; CHECK-NEXT: (global.get $global1.unnested.1)
+ ;; CHECK-NEXT: (global.get $global2.unnested.1)
+ ;; CHECK-NEXT: (ref.eq
+ ;; CHECK-NEXT: (ref.as_non_null
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (global.get $global1)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test2 (param $struct (ref null $struct))
+ ;; Add another get of 0 in another function.
+ (drop
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+ ;; Add gets of the second field in the struct.
+ (drop
+ (struct.get $struct 1
+ (local.get $struct)
+ )
+ )
+ (drop
+ (struct.get $struct 1
+ (local.get $struct)
+ )
+ )
+ )
+)
+
+;; Three globals with non-constant fields. We do not optimize as we cannot pick
+;; between three values with a single comparison.
+(module
+ ;; CHECK: (type $struct (struct (field i32)))
+ (type $struct (struct i32))
+
+ ;; CHECK: (type $1 (func (param (ref null $struct))))
+
+ ;; CHECK: (global $global1 (ref $struct) (struct.new $struct
+ ;; CHECK-NEXT: (i32.add
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $global1 (ref $struct) (struct.new $struct
+ (i32.add
+ (i32.const 42)
+ (i32.const 0)
+ )
+ ))
+
+ ;; CHECK: (global $global2 (ref $struct) (struct.new $struct
+ ;; CHECK-NEXT: (i32.add
+ ;; CHECK-NEXT: (i32.const 1337)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $global2 (ref $struct) (struct.new $struct
+ (i32.add
+ (i32.const 1337)
+ (i32.const 0)
+ )
+ ))
+
+ ;; CHECK: (global $global3 (ref $struct) (struct.new $struct
+ ;; CHECK-NEXT: (i32.add
+ ;; CHECK-NEXT: (i32.const 99999)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $global3 (ref $struct) (struct.new $struct
+ (i32.add
+ (i32.const 99999)
+ (i32.const 0)
+ )
+ ))
+
+ ;; CHECK: (func $test (type $1) (param $struct (ref null $struct))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (struct.get $struct 0
+ ;; CHECK-NEXT: (local.get $struct)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $test (param $struct (ref null $struct))
+ (drop
+ (struct.get $struct 0
+ (local.get $struct)
+ )
+ )
+ )
+)
+
+;; As above, but now two of the three's non-constant fields are identical. That
+;; does not help us: they are still non-constant, and we do nothing. (But, other
+;; passes might simplify things by un-nesting the identical code.)
+(module
+ ;; CHECK: (type $struct (struct (field i32)))
+ (type $struct (struct i32))
+
+ ;; CHECK: (type $1 (func (param (ref null $struct))))
+
+ ;; CHECK: (global $global1 (ref $struct) (struct.new $struct
+ ;; CHECK-NEXT: (i32.add
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $global1 (ref $struct) (struct.new $struct
+ (i32.add
+ (i32.const 42)
+ (i32.const 0)
+ )
+ ))
+
+ ;; CHECK: (global $global2 (ref $struct) (struct.new $struct
+ ;; CHECK-NEXT: (i32.add
+ ;; CHECK-NEXT: (i32.const 42)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $global2 (ref $struct) (struct.new $struct
+ (i32.add
+ (i32.const 42)
+ (i32.const 0)
+ )
+ ))
+
+ ;; CHECK: (global $global3 (ref $struct) (struct.new $struct
+ ;; CHECK-NEXT: (i32.add
+ ;; CHECK-NEXT: (i32.const 99999)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: ))
+ (global $global3 (ref $struct) (struct.new $struct
+ (i32.add
+ (i32.const 99999)
+ (i32.const 0)
+ )
+ ))
+
+ ;; CHECK: (func $test (type $1) (param $struct (ref null $struct))
+ ;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 0
;; CHECK-NEXT: (local.get $struct)
;; CHECK-NEXT: )