| ;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. |
| |
| ;; RUN: foreach %s %t wasm-opt -all --gufa-cast-all -S -o - | filecheck %s |
| |
| (module |
| ;; CHECK: (type $none_=>_none (func)) |
| (type $none_=>_none (func)) |
| |
| ;; CHECK: (type $A (sub (struct))) |
| (type $A (sub (struct))) |
| |
| ;; CHECK: (type $B (sub $A (struct))) |
| (type $B (sub $A (struct))) |
| |
| ;; CHECK: (type $3 (func (result i32))) |
| |
| ;; CHECK: (import "a" "b" (func $import (type $3) (result i32))) |
| (import "a" "b" (func $import (result i32))) |
| |
| ;; CHECK: (elem declare func $func $funcs) |
| |
| ;; CHECK: (export "export1" (func $ref)) |
| |
| ;; CHECK: (export "export2" (func $int)) |
| |
| ;; CHECK: (export "export3" (func $func)) |
| |
| ;; CHECK: (export "export4" (func $funcs)) |
| |
| ;; CHECK: (export "export5" (func $unreachable)) |
| |
| ;; CHECK: (func $ref (type $none_=>_none) |
| ;; CHECK-NEXT: (local $a (ref $A)) |
| ;; CHECK-NEXT: (local.set $a |
| ;; CHECK-NEXT: (struct.new_default $B) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref (exact $B)) |
| ;; CHECK-NEXT: (local.get $a) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $ref (export "export1") |
| (local $a (ref $A)) |
| (local.set $a |
| (struct.new $B) |
| ) |
| (drop |
| ;; We can infer that this contains B, and add a cast to that type. |
| (local.get $a) |
| ) |
| ) |
| |
| ;; CHECK: (func $int (type $none_=>_none) |
| ;; CHECK-NEXT: (local $a i32) |
| ;; CHECK-NEXT: (local.set $a |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $int (export "export2") |
| (local $a i32) |
| (local.set $a |
| (i32.const 1) |
| ) |
| (drop |
| ;; We can infer that this contains 1, but there is nothing to do regarding |
| ;; the type, which is not a reference. |
| (local.get $a) |
| ) |
| ) |
| |
| ;; CHECK: (func $func (type $none_=>_none) |
| ;; CHECK-NEXT: (local $a funcref) |
| ;; CHECK-NEXT: (local.set $a |
| ;; CHECK-NEXT: (ref.func $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.func $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $func (export "export3") (type $none_=>_none) |
| (local $a funcref) |
| (local.set $a |
| (ref.func $func) |
| ) |
| (drop |
| ;; We can infer that this contains a ref to $func, which we can apply |
| ;; here. We don't need to add a cast in addition to that, as the ref.func |
| ;; we add has the refined type already. |
| (local.get $a) |
| ) |
| ) |
| |
| ;; CHECK: (func $funcs (type $none_=>_none) |
| ;; CHECK-NEXT: (local $a funcref) |
| ;; CHECK-NEXT: (local.set $a |
| ;; CHECK-NEXT: (select (result (ref (exact $none_=>_none))) |
| ;; CHECK-NEXT: (ref.func $func) |
| ;; CHECK-NEXT: (ref.func $funcs) |
| ;; CHECK-NEXT: (call $import) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref (exact $none_=>_none)) |
| ;; CHECK-NEXT: (local.get $a) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $funcs (export "export4") (type $none_=>_none) |
| (local $a funcref) |
| (local.set $a |
| (select |
| (ref.func $func) |
| (ref.func $funcs) |
| (call $import) |
| ) |
| ) |
| (drop |
| ;; We can infer that this contains a ref to $func or $funcs, so all we |
| ;; can infer is the type, and we add a cast to $none_=>_none. |
| (local.get $a) |
| ) |
| ) |
| |
| ;; CHECK: (func $unreachable (type $none_=>_none) |
| ;; CHECK-NEXT: (local $a (ref $A)) |
| ;; CHECK-NEXT: (local.set $a |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $unreachable (export "export5") |
| (local $a (ref $A)) |
| (local.set $a |
| (unreachable) |
| ) |
| (drop |
| ;; We can infer that the type here is unreachable, and emit that in the |
| ;; IR. This checks we don't error on the inferred type not being a ref. |
| (local.get $a) |
| ) |
| ) |
| ) |
| |
| ;; Imported tags may be written to from places we do not see. |
| (module |
| ;; CHECK: (type $0 (func (param i32))) |
| |
| ;; CHECK: (type $1 (func)) |
| |
| ;; CHECK: (import "fuzzing-support" "throw" (func $throw (type $0) (param i32))) |
| (import "fuzzing-support" "throw" (func $throw (param i32))) |
| ;; CHECK: (import "fuzzing-support" "tag" (tag $tag (type $0) (param i32))) |
| (import "fuzzing-support" "tag" (tag $tag (param i32))) |
| |
| ;; CHECK: (export "func" (func $func)) |
| |
| ;; CHECK: (func $func (type $1) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block $block (result i32) |
| ;; CHECK-NEXT: (try_table (catch $tag $block) |
| ;; CHECK-NEXT: (call $throw |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (return) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $func (export "func") |
| (drop |
| ;; If we thought no i32 value could arrive here (if no exception were |
| ;; created of this tag) then we'd put an unreachable after it. As it is |
| ;; imported, a value might be there, so we do not. |
| (block $block (result i32) |
| (try_table (catch $tag $block) |
| (call $throw |
| (i32.const 1) |
| ) |
| ) |
| (return) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; As above, but with an exported tag. Also test a tag with multiple params. |
| (module |
| ;; CHECK: (type $0 (func (param i32 f64))) |
| |
| ;; CHECK: (type $1 (func (param i32))) |
| |
| ;; CHECK: (type $2 (func)) |
| |
| ;; CHECK: (type $3 (func (result i32 f64))) |
| |
| ;; CHECK: (import "fuzzing-support" "throw" (func $throw (type $1) (param i32))) |
| (import "fuzzing-support" "throw" (func $throw (param i32))) |
| |
| ;; CHECK: (tag $tag (type $0) (param i32 f64)) |
| (tag $tag (param i32 f64)) |
| |
| ;; CHECK: (export "func" (func $func)) |
| |
| ;; CHECK: (export "tag" (tag $tag)) |
| (export "tag" (tag $tag)) |
| |
| ;; CHECK: (func $func (type $2) |
| ;; CHECK-NEXT: (tuple.drop 2 |
| ;; CHECK-NEXT: (block $block (type $3) (result i32 f64) |
| ;; CHECK-NEXT: (try_table (catch $tag $block) |
| ;; CHECK-NEXT: (call $throw |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (return) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $func (export "func") |
| ;; Once more, we do not optimize to unreachable here. |
| (tuple.drop 2 |
| (block $block (result i32 f64) |
| (try_table (catch $tag $block) |
| (call $throw |
| (i32.const 1) |
| ) |
| ) |
| (return) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; Private tags are optimizable. |
| (module |
| ;; CHECK: (type $0 (func (param i32))) |
| |
| ;; CHECK: (type $1 (func)) |
| |
| ;; CHECK: (import "fuzzing-support" "throw" (func $throw (type $0) (param i32))) |
| (import "fuzzing-support" "throw" (func $throw (param i32))) |
| |
| ;; CHECK: (tag $tag (type $0) (param i32)) |
| (tag $tag (param i32)) |
| |
| ;; CHECK: (export "func" (func $func)) |
| |
| ;; CHECK: (func $func (type $1) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block $block (result i32) |
| ;; CHECK-NEXT: (try_table (catch $tag $block) |
| ;; CHECK-NEXT: (call $throw |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (return) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $func (export "func") |
| ;; The tag is neither imported nor exported, so we can optimize to |
| ;; unreachable. |
| (drop |
| (block $block (result i32) |
| (try_table (catch $tag $block) |
| (call $throw |
| (i32.const 1) |
| ) |
| ) |
| (return) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; Test pre-filtering. |
| (module |
| ;; CHECK: (type $A (func)) |
| (type $A (func)) |
| |
| ;; CHECK: (import "a" "b" (global $global i32)) |
| (import "a" "b" (global $global i32)) |
| |
| ;; CHECK: (elem declare func $test) |
| |
| ;; CHECK: (func $test (type $A) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref (exact $A))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block $block (result (ref (exact $A))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result (ref (exact $A))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (br_if $block |
| ;; CHECK-NEXT: (ref.func $test) |
| ;; CHECK-NEXT: (global.get $global) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.func $test) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (br_on_non_null $block |
| ;; CHECK-NEXT: (ref.null nofunc) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.func $test) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test (type $A) |
| ;; This block is declared as having type $A. Two values appear to reach it: |
| ;; one from a br that sends a ref.func, and one from a br_on_non_null which |
| ;; sends a null with the type (ref nofunc) (in practice that branch is not |
| ;; taken, of course, but GUFA does see all branches; later optimizations |
| ;; would optimize the branch away). |
| ;; |
| ;; We see the ref.func first, so the block $block begins with that content. |
| ;; Then we see the null arrive. Immediately combining the null with a |
| ;; ref.func would give a cone - the best shape we have that can allow both a |
| ;; null and a ref.func. If we later filter the result to the block, which is |
| ;; non-nullable, the cone becomes non-nullable too - but it is a cone now, |
| ;; and not the original ref.func, preventing us from applying the constant |
| ;; value of the ref.func in the output. Early filtering of the arriving |
| ;; content fixes this: the null is immediately filtered into nothing, since |
| ;; it is null and the location can only contain non-nullable contents. As a |
| ;; result, we can optimize the block (and the br_if) to return a ref.func. |
| (drop |
| (block $block (result (ref $A)) |
| (drop |
| (br_if $block |
| (ref.func $test) |
| (global.get $global) |
| ) |
| ) |
| (br_on_non_null $block |
| (ref.null nofunc) |
| ) |
| (unreachable) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; Do not refine uncastable types. |
| (module |
| (rec |
| ;; CHECK: (rec |
| ;; CHECK-NEXT: (type $cont (cont $none)) |
| (type $cont (cont $none)) |
| ;; CHECK: (type $none (func)) |
| (type $none (func)) |
| ) |
| |
| ;; CHECK: (type $2 (func (result contref))) |
| |
| ;; CHECK: (elem declare func $suspend) |
| |
| ;; CHECK: (func $suspend (type $none) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $suspend (type $none) |
| (nop) |
| ) |
| |
| ;; CHECK: (func $unrefine (type $2) (result contref) |
| ;; CHECK-NEXT: (block $label (result contref) |
| ;; CHECK-NEXT: (cont.new $cont |
| ;; CHECK-NEXT: (ref.func $suspend) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $unrefine (result contref) |
| ;; No cast should be added here. |
| (block $label (result contref) |
| (cont.new $cont |
| (ref.func $suspend) |
| ) |
| ) |
| ) |
| ) |
| |
| |
| ;; Regression test for bug where optimizing expressions containing a Pop could |
| ;; result in the Pop becoming invalidly nested inside a Block (created to |
| ;; preserve side effects), and the optimizer failed to run the nested pop fixup |
| ;; because it didn't mark the function as optimized. |
| (module |
| ;; CHECK: (type $array (sub (array anyref))) |
| (type $array (sub (array (ref null any)))) |
| ;; CHECK: (type $tag-sig (func (param (ref null $array)))) |
| (type $tag-sig (func (param (ref null $array)))) |
| ;; CHECK: (type $2 (func)) |
| |
| ;; CHECK: (tag $tag (type $tag-sig) (param (ref null $array))) |
| (tag $tag (type $tag-sig) (param (ref null $array))) |
| |
| ;; CHECK: (func $test (type $2) |
| ;; CHECK-NEXT: (local $0 (ref null $array)) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (try (result (ref i31)) |
| ;; CHECK-NEXT: (do |
| ;; CHECK-NEXT: (ref.i31 |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (catch $tag |
| ;; CHECK-NEXT: (local.set $0 |
| ;; CHECK-NEXT: (pop (ref null $array)) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (ref.i31 |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (block |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (local.get $0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test |
| (drop |
| (try (result (ref i31)) |
| (do |
| ;; This doesn't throw, so everything in the catch is unreachable. |
| (ref.i31 (i32.const 0)) |
| ) |
| (catch $tag |
| (ref.i31 |
| (ref.eq |
| (pop (ref null $array)) |
| (ref.i31 (i32.const 0)) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $struct (struct)) |
| (type $struct (struct)) |
| |
| ;; CHECK: (type $1 (func (result (ref $struct)))) |
| |
| ;; CHECK: (global $g (mut (ref null $struct)) (struct.new_default $struct)) |
| (global $g (mut (ref null $struct)) (struct.new $struct)) |
| |
| ;; CHECK: (func $test (type $1) (result (ref $struct)) |
| ;; CHECK-NEXT: (ref.cast (ref (exact $struct)) |
| ;; CHECK-NEXT: (block $block (result (ref (exact $struct))) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.cast (ref (exact $struct)) |
| ;; CHECK-NEXT: (br_on_cast $block (ref (exact $struct)) (ref (exact $struct)) |
| ;; CHECK-NEXT: (ref.cast (ref (exact $struct)) |
| ;; CHECK-NEXT: (global.get $g) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test (result (ref $struct)) |
| (block $block (result (ref $struct)) |
| (drop |
| (br_on_cast $block (ref null $struct) (ref $struct) |
| ;; This will be cast to (ref (exact $struct)). We must refinalize to |
| ;; update the br_on_cast output to (ref (exact $struct)) as well to |
| ;; maintain the validation condition that the cast output is a subtype |
| ;; of its input. |
| (global.get $g) |
| ) |
| ) |
| (unreachable) |
| ) |
| ) |
| ) |