summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/lit/help/wasm-opt.test3
-rw-r--r--test/lit/help/wasm2js.test3
-rw-r--r--test/lit/passes/type-merging.wast237
-rw-r--r--test/lit/passes/type-ssa_and_merging.wast147
4 files changed, 390 insertions, 0 deletions
diff --git a/test/lit/help/wasm-opt.test b/test/lit/help/wasm-opt.test
index 0d7c053d1..a5c21af8b 100644
--- a/test/lit/help/wasm-opt.test
+++ b/test/lit/help/wasm-opt.test
@@ -457,6 +457,9 @@
;; CHECK-NEXT: --trap-mode-js replace trapping operations with
;; CHECK-NEXT: js semantics
;; CHECK-NEXT:
+;; CHECK-NEXT: --type-merging merge types to their supertypes
+;; CHECK-NEXT: where possible
+;; CHECK-NEXT:
;; CHECK-NEXT: --type-refining apply more specific subtypes to
;; CHECK-NEXT: type fields where possible
;; CHECK-NEXT:
diff --git a/test/lit/help/wasm2js.test b/test/lit/help/wasm2js.test
index 124cd0bbe..3073bf2f9 100644
--- a/test/lit/help/wasm2js.test
+++ b/test/lit/help/wasm2js.test
@@ -416,6 +416,9 @@
;; CHECK-NEXT: --trap-mode-js replace trapping operations with
;; CHECK-NEXT: js semantics
;; CHECK-NEXT:
+;; CHECK-NEXT: --type-merging merge types to their supertypes
+;; CHECK-NEXT: where possible
+;; CHECK-NEXT:
;; CHECK-NEXT: --type-refining apply more specific subtypes to
;; CHECK-NEXT: type fields where possible
;; CHECK-NEXT:
diff --git a/test/lit/passes/type-merging.wast b/test/lit/passes/type-merging.wast
new file mode 100644
index 000000000..4df8d7e88
--- /dev/null
+++ b/test/lit/passes/type-merging.wast
@@ -0,0 +1,237 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt --nominal --type-merging -all -S -o - | filecheck %s
+
+(module
+ ;; CHECK: (type $A (struct (field i32)))
+ (type $A (struct_subtype (field i32) data))
+ (type $B (struct_subtype (field i32) $A))
+ ;; CHECK: (type $D (struct_subtype (field i32) $A))
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (type $C (struct_subtype (field i32) (field f64) $A))
+ (type $C (struct_subtype (field i32) (field f64) $A))
+ (type $D (struct_subtype (field i32) $A))
+
+ ;; CHECK: (func $foo (type $none_=>_none)
+ ;; CHECK-NEXT: (local $a (ref null $A))
+ ;; CHECK-NEXT: (local $b (ref null $A))
+ ;; CHECK-NEXT: (local $c (ref null $C))
+ ;; CHECK-NEXT: (local $d (ref null $D))
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast_static $A
+ ;; CHECK-NEXT: (local.get $a)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: (drop
+ ;; CHECK-NEXT: (ref.cast_static $D
+ ;; CHECK-NEXT: (local.get $a)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $foo
+ ;; $A will remain the same.
+ (local $a (ref null $A))
+ ;; $B can be merged into $A.
+ (local $b (ref null $B))
+ ;; $C cannot because it adds a field.
+ (local $c (ref null $C))
+ ;; $D cannot because it has a cast.
+ (local $d (ref null $D))
+
+ ;; A cast of $A has no effect.
+ (drop
+ (ref.cast_static $A
+ (local.get $a)
+ )
+ )
+ ;; A cast of $D prevents it from being merged.
+ (drop
+ (ref.cast_static $D
+ (local.get $a)
+ )
+ )
+ )
+)
+
+;; Multiple levels of merging.
+(module
+ ;; CHECK: (type $A (struct (field i32)))
+ (type $A (struct_subtype (field i32) data))
+ (type $B (struct_subtype (field i32) $A))
+ (type $C (struct_subtype (field i32) $B))
+ ;; CHECK: (type $D (struct_subtype (field i32) (field f64) $A))
+ (type $D (struct_subtype (field i32) (field f64) $A))
+ (type $E (struct_subtype (field i32) (field f64) $D))
+ (type $F (struct_subtype (field i32) (field f64) $E))
+ (type $G (struct_subtype (field i32) (field f64) $F))
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (func $foo (type $none_=>_none)
+ ;; CHECK-NEXT: (local $a (ref null $A))
+ ;; CHECK-NEXT: (local $b (ref null $A))
+ ;; CHECK-NEXT: (local $c (ref null $A))
+ ;; CHECK-NEXT: (local $d (ref null $D))
+ ;; CHECK-NEXT: (local $e (ref null $D))
+ ;; CHECK-NEXT: (local $f (ref null $D))
+ ;; CHECK-NEXT: (local $g (ref null $D))
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo
+ (local $a (ref null $A))
+ ;; $B can be merged into $A.
+ (local $b (ref null $B))
+ ;; $C can be merged into $B, so it will merge into $A.
+ (local $c (ref null $C))
+ ;; $D cannot be merged into $A as it adds a field.
+ (local $d (ref null $D))
+ ;; $E can be merged into $D.
+ (local $e (ref null $E))
+ ;; $F can be merged into $E, so it will merge into $D.
+ (local $f (ref null $F))
+ ;; $G can be merged into $F, so it will merge into $D.
+ (local $g (ref null $G))
+ )
+)
+
+;; As above but now $D is a subtype of $C, so there is a single subtype chain
+;; in which we have two "merge points" that things get merged into. The results
+;; should remain the same as before, everything merged into either $A or $D.
+(module
+ ;; CHECK: (type $A (struct (field i32)))
+ (type $A (struct_subtype (field i32) data))
+ ;; CHECK: (type $B (struct_subtype (field i32) $A))
+ (type $B (struct_subtype (field i32) $A))
+ ;; CHECK: (type $C (struct_subtype (field i32) $B))
+ (type $C (struct_subtype (field i32) $B))
+ ;; CHECK: (type $D (struct_subtype (field i32) (field f64) $C))
+ (type $D (struct_subtype (field i32) (field f64) $C)) ;; this line changed
+ (type $E (struct_subtype (field i32) (field f64) $D))
+ (type $F (struct_subtype (field i32) (field f64) $E))
+ (type $G (struct_subtype (field i32) (field f64) $F))
+
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (func $foo (type $none_=>_none)
+ ;; CHECK-NEXT: (local $a (ref null $A))
+ ;; CHECK-NEXT: (local $b (ref null $A))
+ ;; CHECK-NEXT: (local $c (ref null $A))
+ ;; CHECK-NEXT: (local $d (ref null $D))
+ ;; CHECK-NEXT: (local $e (ref null $D))
+ ;; CHECK-NEXT: (local $f (ref null $D))
+ ;; CHECK-NEXT: (local $g (ref null $D))
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo
+ (local $a (ref null $A))
+ (local $b (ref null $B))
+ (local $c (ref null $C))
+ (local $d (ref null $D))
+ (local $e (ref null $E))
+ (local $f (ref null $F))
+ (local $g (ref null $G))
+ )
+)
+
+(module
+ ;; CHECK: (type $A (struct (field (ref null $A))))
+ (type $A (struct_subtype (field (ref null $A)) data))
+ (type $B (struct_subtype (field (ref null $A)) $A))
+ ;; CHECK: (type $none_=>_none (func))
+
+ ;; CHECK: (type $C (struct_subtype (field (ref null $A)) $A))
+ (type $C (struct_subtype (field (ref null $B)) $A))
+
+ ;; CHECK: (func $foo (type $none_=>_none)
+ ;; CHECK-NEXT: (local $a (ref null $A))
+ ;; CHECK-NEXT: (local $b (ref null $A))
+ ;; CHECK-NEXT: (local $c (ref null $C))
+ ;; CHECK-NEXT: (nop)
+ ;; CHECK-NEXT: )
+ (func $foo
+ ;; $A will remain the same.
+ (local $a (ref null $A))
+ ;; $B can be merged into $A.
+ (local $b (ref null $B))
+ ;; $C refines the field, so it cannot be merged. However, separately, in
+ ;; the type definition of $C, its field of type $B should become $A. That
+ ;; is, $B should no longer be used anywhere.
+ (local $c (ref null $C))
+ )
+)
+
+;; Check that we refinalize properly.
+(module
+ ;; CHECK: (type $A (struct ))
+ (type $A (struct))
+ (type $B (struct_subtype $A))
+
+ ;; CHECK: (type $none_=>_ref?|$A| (func (result (ref null $A))))
+
+ ;; CHECK: (func $returner (type $none_=>_ref?|$A|) (result (ref null $A))
+ ;; CHECK-NEXT: (local $local (ref null $A))
+ ;; CHECK-NEXT: (local.get $local)
+ ;; CHECK-NEXT: )
+ (func $returner (result (ref null $B))
+ (local $local (ref null $B))
+
+ ;; After we change the local to use type $A, we need to update the local.get's
+ ;; type as well, or we will error.
+ (local.get $local)
+ )
+)
+
+;; Test some real-world patterns, including fields to ignore, links between
+;; merged types, etc.
+;;
+;; The result here is that we will merge $A$to-merge into $A, and $D$to-merge
+;; into $D. While doing so we must update the fields and the expressions that
+;; they appear in, and not error.
+(module
+ ;; CHECK: (type $C (struct (field (mut i32))))
+
+ ;; CHECK: (type $D (struct_subtype (field (mut i32)) (field (mut i32)) $C))
+
+ ;; CHECK: (type $I (array (mut (ref null $C))))
+ (type $I (array (mut (ref null $C))))
+ (type $C (struct (field (mut i32))))
+ (type $D (struct_subtype (field (mut i32)) (field (mut i32)) $C))
+ (type $E (struct_subtype (field (mut i32)) (field (mut i32)) $D))
+ (type $F (struct_subtype (field (mut i32)) (field (mut i32)) $E))
+ (type $D$to-merge (struct_subtype (field (mut i32)) (field (mut i32)) $F))
+ ;; CHECK: (type $G (func (param (ref $C)) (result (ref $D))))
+ (type $G (func (param (ref $C)) (result (ref $D))))
+ ;; CHECK: (type $H (struct_subtype (field (mut i32)) (field (mut i32)) (field (mut (ref null $D))) $D))
+ (type $H (struct_subtype (field (mut i32)) (field (mut i32)) (field (mut (ref null $E))) $D))
+ ;; CHECK: (type $A (struct_subtype (field (mut i32)) (field (mut i32)) (field (mut (ref null $D))) (field (mut i64)) (field (mut (ref null $I))) $H))
+ (type $A (struct_subtype (field (mut i32)) (field (mut i32)) (field (mut (ref null $E))) (field (mut i64)) (field (mut (ref null $I))) $H))
+ (type $A$to-merge (struct_subtype (field (mut i32)) (field (mut i32)) (field (mut (ref null $E))) (field (mut i64)) (field (mut (ref null $I))) $A))
+
+ ;; CHECK: (global $global$0 (ref $D) (struct.new $D
+ ;; CHECK-NEXT: (i32.const 1705)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: ))
+ (global $global$0 (ref $F) (struct.new $D$to-merge
+ (i32.const 1705)
+ (i32.const 0)
+ ))
+ ;; CHECK: (func $0 (type $G) (param $0 (ref $C)) (result (ref $D))
+ ;; CHECK-NEXT: (struct.new $A
+ ;; CHECK-NEXT: (i32.const 1685)
+ ;; CHECK-NEXT: (i32.const 0)
+ ;; CHECK-NEXT: (global.get $global$0)
+ ;; CHECK-NEXT: (i64.const 0)
+ ;; CHECK-NEXT: (array.init_static $I)
+ ;; CHECK-NEXT: )
+ ;; CHECK-NEXT: )
+ (func $0 (type $G) (param $0 (ref $C)) (result (ref $D))
+ (struct.new $A$to-merge
+ (i32.const 1685)
+ (i32.const 0)
+ (global.get $global$0)
+ (i64.const 0)
+ (array.init_static $I)
+ )
+ )
+)
diff --git a/test/lit/passes/type-ssa_and_merging.wast b/test/lit/passes/type-ssa_and_merging.wast
new file mode 100644
index 000000000..40322bfcb
--- /dev/null
+++ b/test/lit/passes/type-ssa_and_merging.wast
@@ -0,0 +1,147 @@
+;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
+;; RUN: foreach %s %t wasm-opt --nominal --gufa -Os -all -S -o - | filecheck %s --check-prefix NOP
+;; RUN: foreach %s %t wasm-opt --nominal --type-ssa --gufa -Os --type-merging -all -S -o - | filecheck %s --check-prefix YES
+
+;; Show that the combination of type-ssa and type-merging can find things that
+;; otherwise cannot be optimized. NOP will fail to optimize something that YES
+;; can.
+
+(module
+ ;; NOP: (type $A (struct (field (mut i32))))
+ ;; YES: (type $none_=>_i32 (func (result i32)))
+
+ ;; YES: (type $A (struct (field (mut i32))))
+ (type $A (struct_subtype (field (mut i32)) data))
+
+ ;; NOP: (type $none_=>_i32 (func (result i32)))
+
+ ;; NOP: (type $ref|$A|_=>_i32 (func (param (ref $A)) (result i32)))
+
+ ;; NOP: (import "a" "b" (func $import (result i32)))
+ ;; YES: (type $ref|$A|_=>_none (func (param (ref $A))))
+
+ ;; YES: (import "a" "b" (func $import (result i32)))
+ (import "a" "b" (func $import (result i32)))
+
+ ;; NOP: (export "main1" (func $main1))
+
+ ;; NOP: (export "main2" (func $main2))
+
+ ;; NOP: (func $main1 (type $none_=>_i32) (; has Stack IR ;) (result i32)
+ ;; NOP-NEXT: (call $get-a-1
+ ;; NOP-NEXT: (struct.new $A
+ ;; NOP-NEXT: (i32.const 42)
+ ;; NOP-NEXT: )
+ ;; NOP-NEXT: )
+ ;; NOP-NEXT: )
+ ;; YES: (export "main1" (func $main1))
+
+ ;; YES: (export "main2" (func $main2))
+
+ ;; YES: (func $main1 (type $none_=>_i32) (result i32)
+ ;; YES-NEXT: (call $get-a-1
+ ;; YES-NEXT: (struct.new $A
+ ;; YES-NEXT: (i32.const 42)
+ ;; YES-NEXT: )
+ ;; YES-NEXT: )
+ ;; YES-NEXT: (i32.const 42)
+ ;; YES-NEXT: )
+ (func $main1 (export "main1") (result i32)
+ ;; YES can infer a result here, 42.
+ (call $get-a-1
+ (struct.new $A (i32.const 42))
+ )
+ )
+
+ ;; NOP: (func $main2 (type $none_=>_i32) (; has Stack IR ;) (result i32)
+ ;; NOP-NEXT: (call $get-a-2
+ ;; NOP-NEXT: (struct.new $A
+ ;; NOP-NEXT: (i32.const 1337)
+ ;; NOP-NEXT: )
+ ;; NOP-NEXT: )
+ ;; NOP-NEXT: )
+ ;; YES: (func $main2 (type $none_=>_i32) (result i32)
+ ;; YES-NEXT: (call $get-a-2
+ ;; YES-NEXT: (struct.new $A
+ ;; YES-NEXT: (i32.const 1337)
+ ;; YES-NEXT: )
+ ;; YES-NEXT: )
+ ;; YES-NEXT: (i32.const 1337)
+ ;; YES-NEXT: )
+ (func $main2 (export "main2") (result i32)
+ ;; YES can infer a result here, 1337.
+ (call $get-a-2
+ (struct.new $A (i32.const 1337))
+ )
+ )
+
+ ;; NOP: (func $get-a-1 (type $ref|$A|_=>_i32) (; has Stack IR ;) (param $0 (ref $A)) (result i32)
+ ;; NOP-NEXT: (if
+ ;; NOP-NEXT: (call $import)
+ ;; NOP-NEXT: (return
+ ;; NOP-NEXT: (call $get-a-1
+ ;; NOP-NEXT: (local.get $0)
+ ;; NOP-NEXT: )
+ ;; NOP-NEXT: )
+ ;; NOP-NEXT: )
+ ;; NOP-NEXT: (struct.get $A 0
+ ;; NOP-NEXT: (local.get $0)
+ ;; NOP-NEXT: )
+ ;; NOP-NEXT: )
+ ;; YES: (func $get-a-1 (type $ref|$A|_=>_none) (param $0 (ref $A))
+ ;; YES-NEXT: (if
+ ;; YES-NEXT: (call $import)
+ ;; YES-NEXT: (call $get-a-1
+ ;; YES-NEXT: (local.get $0)
+ ;; YES-NEXT: )
+ ;; YES-NEXT: )
+ ;; YES-NEXT: )
+ (func $get-a-1 (param $ref (ref $A)) (result i32)
+ ;; YES infers the result and applies it in the caller, so nothing is
+ ;; returned any more (but we do keep the possibly infinite recursion, which
+ ;; is necessary to avoid inlining making this testcase trivial even in NOP).
+ (if
+ (call $import)
+ (return
+ (call $get-a-1
+ (local.get $ref)
+ )
+ )
+ )
+ (struct.get $A 0 (local.get 0))
+ )
+
+ ;; NOP: (func $get-a-2 (type $ref|$A|_=>_i32) (; has Stack IR ;) (param $0 (ref $A)) (result i32)
+ ;; NOP-NEXT: (if
+ ;; NOP-NEXT: (call $import)
+ ;; NOP-NEXT: (return
+ ;; NOP-NEXT: (call $get-a-2
+ ;; NOP-NEXT: (local.get $0)
+ ;; NOP-NEXT: )
+ ;; NOP-NEXT: )
+ ;; NOP-NEXT: )
+ ;; NOP-NEXT: (struct.get $A 0
+ ;; NOP-NEXT: (local.get $0)
+ ;; NOP-NEXT: )
+ ;; NOP-NEXT: )
+ ;; YES: (func $get-a-2 (type $ref|$A|_=>_none) (param $0 (ref $A))
+ ;; YES-NEXT: (if
+ ;; YES-NEXT: (call $import)
+ ;; YES-NEXT: (call $get-a-2
+ ;; YES-NEXT: (local.get $0)
+ ;; YES-NEXT: )
+ ;; YES-NEXT: )
+ ;; YES-NEXT: )
+ (func $get-a-2 (param $ref (ref $A)) (result i32)
+ ;; Parallel to the above.
+ (if
+ (call $import)
+ (return
+ (call $get-a-2
+ (local.get $ref)
+ )
+ )
+ )
+ (struct.get $A 0 (local.get 0))
+ )
+)