diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/lit/help/wasm-opt.test | 3 | ||||
-rw-r--r-- | test/lit/help/wasm2js.test | 3 | ||||
-rw-r--r-- | test/lit/passes/type-merging.wast | 237 | ||||
-rw-r--r-- | test/lit/passes/type-ssa_and_merging.wast | 147 |
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)) + ) +) |