| ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. |
| ;; RUN: foreach %s %t wasm-opt -all --closed-world --generate-global-effects --vacuum -S -o - | filecheck %s |
| |
| ;; Tests for aggregating effects from indirect calls in GlobalEffects when |
| ;; --closed-world is true. Some more complicated tests are in |
| ;; global-effects-closed-world-simplify-locals.wast. |
| |
| (module |
| (table 1 1 funcref) |
| |
| ;; CHECK: (type $nopType (func (param i32))) |
| (type $nopType (func (param i32))) |
| |
| ;; CHECK: (func $nop (type $nopType) (param $0 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $nop (export "nop") (type $nopType) |
| (nop) |
| ) |
| |
| ;; CHECK: (func $calls-nop-via-ref (type $1) (param $ref (ref $nopType)) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $calls-nop-via-ref (param $ref (ref $nopType)) |
| ;; This can only possibly be a nop in closed-world. |
| ;; The equivalent for call_indirect is tested in |
| ;; test/lit/passes/global-effects-closed-world-tnh.wast. |
| (call_ref $nopType (i32.const 1) (local.get $ref)) |
| ) |
| |
| ;; CHECK: (func $calls-nop-via-nullable-ref (type $2) (param $ref (ref null $nopType)) |
| ;; CHECK-NEXT: (call_ref $nopType |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $calls-nop-via-nullable-ref (param $ref (ref null $nopType)) |
| (call_ref $nopType (i32.const 1) (local.get $ref)) |
| ) |
| |
| ;; CHECK: (func $call-indirect-nop (type $3) |
| ;; CHECK-NEXT: (call_indirect $0 (type $nopType) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $call-indirect-nop |
| ;; The call body is guaranteed to have no effects, however the call_indirect |
| ;; itself may trap if the index is out of bounds or if the function we |
| ;; lookup doesn't match $nopType. The call can't be optimized out. |
| ;; This could be optimized out if --traps-never-happens or |
| ;; --ignore-implicit-traps (in fewer cases) is set. See |
| ;; global-effects-closed-world-tnh.wast and |
| ;; global-effects-closed-world-ignore-implicit-traps.wast. |
| (call_indirect (type $nopType) (i32.const 1) (i32.const 0)) |
| ) |
| ) |
| |
| (module |
| (table 1 1 funcref) |
| |
| ;; CHECK: (type $maybe-has-effects (func (param i32))) |
| (type $maybe-has-effects (func (param i32))) |
| |
| ;; CHECK: (func $unreachable (type $maybe-has-effects) (param $0 i32) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $unreachable (export "unreachable") (type $maybe-has-effects) (param i32) |
| (unreachable) |
| ) |
| |
| ;; CHECK: (func $nop (type $maybe-has-effects) (param $0 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $nop (export "nop") (type $maybe-has-effects) (param i32) |
| (nop) |
| ) |
| |
| ;; CHECK: (func $calls-effectful-function-via-ref (type $1) (param $ref (ref $maybe-has-effects)) |
| ;; CHECK-NEXT: (call_ref $maybe-has-effects |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $calls-effectful-function-via-ref (param $ref (ref $maybe-has-effects)) |
| ;; This may be a nop or it may trap depending on the ref |
| ;; We don't know so don't optimize it out. |
| (call_ref $maybe-has-effects (i32.const 1) (local.get $ref)) |
| ) |
| |
| ;; CHECK: (func $call-indirect-effectful-function (type $2) |
| ;; CHECK-NEXT: (call_indirect $0 (type $maybe-has-effects) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $call-indirect-effectful-function |
| ;; As above, this may trap or be a nop depending on what's in the table. |
| ;; In addition, this may trap due to a type mismatch of index out of bounds. |
| ;; Since we don't know any of these things, don't optimize the call out. |
| (call_indirect (type $maybe-has-effects) (i32.const 1) (i32.const 1)) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $uninhabited (func (param i32))) |
| (type $uninhabited (func (param i32))) |
| |
| ;; CHECK: (func $calls-uninhabited (type $1) (param $ref (ref $uninhabited)) |
| ;; CHECK-NEXT: (call_ref $uninhabited |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $calls-uninhabited (param $ref (ref $uninhabited)) |
| ;; There's no function with this type, so it's impossible to create a ref to |
| ;; call this function with. If this code is reached, it must trap. |
| ;; TODO: Optimize this to (unreachable) by creating a 'must trap' effect. |
| (call_ref $uninhabited (i32.const 1) (local.get $ref)) |
| ) |
| |
| ;; CHECK: (func $calls-nullable-uninhabited (type $2) (param $ref (ref null $uninhabited)) |
| ;; CHECK-NEXT: (call_ref $uninhabited |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $calls-nullable-uninhabited (param $ref (ref null $uninhabited)) |
| ;; This must be null, so it's guaranteed to trap and can't be optimized out. |
| ;; TODO: Optimize this to (unreachable) by creating a 'must trap' effect. |
| (call_ref $uninhabited (i32.const 1) (local.get $ref)) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $super (sub (func))) |
| (type $super (sub (func))) |
| ;; Subtype |
| ;; CHECK: (type $sub (sub $super (func))) |
| (type $sub (sub $super (func))) |
| |
| ;; CHECK: (func $nop-with-supertype (type $super) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $nop-with-supertype (export "nop-with-supertype") (type $super) |
| ) |
| |
| ;; CHECK: (func $effectful-with-subtype (type $sub) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $effectful-with-subtype (export "effectful-with-subtype") (type $sub) |
| (unreachable) |
| ) |
| |
| ;; CHECK: (func $calls-ref-with-supertype (type $2) (param $func (ref $super)) |
| ;; CHECK-NEXT: (call_ref $super |
| ;; CHECK-NEXT: (local.get $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $calls-ref-with-supertype (param $func (ref $super)) |
| ;; Check that we account for subtyping correctly. |
| ;; $super has no effects (i.e. the union of all effects of functions with |
| ;; this type is empty). However, $sub does have effects, and we can call_ref |
| ;; with that subtype, so we need to include the unreachable effect and we |
| ;; can't optimize out this call. |
| (call_ref $super (local.get $func)) |
| ) |
| |
| ;; CHECK: (func $calls-ref-with-exact-supertype (type $3) (param $func (ref (exact $super))) |
| ;; CHECK-NEXT: (call_ref $super |
| ;; CHECK-NEXT: (local.get $func) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $calls-ref-with-exact-supertype (param $func (ref (exact $super))) |
| ;; Same as above but this time our reference is the exact supertype |
| ;; so we know not to aggregate effects from the subtype. |
| ;; TODO: this case doesn't optimize today. Add exact ref support in the pass. |
| (call_ref $super (local.get $func)) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $only-has-effects-in-not-addressable-function (func (param i32))) |
| (type $only-has-effects-in-not-addressable-function (func (param i32))) |
| |
| ;; CHECK: (func $nop (type $only-has-effects-in-not-addressable-function) (param $0 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $nop (export "nop") (type $only-has-effects-in-not-addressable-function) (param i32) |
| ) |
| |
| ;; CHECK: (func $has-effects-but-not-exported (type $only-has-effects-in-not-addressable-function) (param $0 i32) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $has-effects-but-not-exported (type $only-has-effects-in-not-addressable-function) (param i32) |
| (unreachable) |
| ) |
| |
| ;; CHECK: (func $calls-type-with-effects-but-not-addressable (type $1) (param $ref (ref $only-has-effects-in-not-addressable-function)) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $calls-type-with-effects-but-not-addressable (param $ref (ref $only-has-effects-in-not-addressable-function)) |
| ;; The type $has-effects-but-not-exported doesn't have an address because |
| ;; it's not exported and it's never the target of a ref.func. |
| ;; So the call_ref's only potential target is $nop which has no effects. |
| (call_ref $only-has-effects-in-not-addressable-function (i32.const 1) (local.get $ref)) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $unreachable-via-direct-call (func (param i32))) |
| (type $unreachable-via-direct-call (func (param i32))) |
| |
| ;; CHECK: (elem declare func $calls-unreachable) |
| |
| ;; CHECK: (func $unreachable (type $0) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $unreachable |
| (unreachable) |
| ) |
| |
| ;; CHECK: (func $calls-unreachable (type $unreachable-via-direct-call) (param $0 i32) |
| ;; CHECK-NEXT: (call $unreachable) |
| ;; CHECK-NEXT: ) |
| (func $calls-unreachable (export "calls-unreachable") (param i32) |
| (call $unreachable) |
| ) |
| |
| ;; CHECK: (func $calls-unreachable-via-ref-and-direct-call-transtively (type $0) |
| ;; CHECK-NEXT: (call_ref $unreachable-via-direct-call |
| ;; CHECK-NEXT: (i32.const 0) |
| ;; CHECK-NEXT: (ref.func $calls-unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $calls-unreachable-via-ref-and-direct-call-transtively |
| (call_ref $unreachable-via-direct-call (i32.const 0) (ref.func $calls-unreachable)) |
| ) |
| |
| ;; CHECK: (func $f (type $0) |
| ;; CHECK-NEXT: (call $calls-unreachable-via-ref-and-direct-call-transtively) |
| ;; CHECK-NEXT: ) |
| (func $f |
| ;; Test that we can analyze longer call chains containing both indirect and |
| ;; direct calls. In this case the call chain hits an unreachable via an |
| ;; indirect call, then direct call, so we can't optimize this out. |
| (call $calls-unreachable-via-ref-and-direct-call-transtively) |
| ) |
| ) |
| |
| (module |
| ;; CHECK: (type $t (func (param i32))) |
| (type $t (func (param i32))) |
| |
| ;; (import "" "" (func $imported-func (type $t))) |
| ;; CHECK: (import "" "" (func $imported-func (type $t) (param i32))) |
| (import "" "" (func $imported-func (type $t))) |
| |
| (elem declare $imported-func) |
| |
| ;; CHECK: (func $nop (type $t) (param $0 i32) |
| ;; CHECK-NEXT: (nop) |
| ;; CHECK-NEXT: ) |
| (func $nop (param i32) |
| ) |
| |
| ;; CHECK: (func $indirect-calls (type $1) (param $ref (ref $t)) |
| ;; CHECK-NEXT: (call_ref $t |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: (local.get $ref) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $indirect-calls (param $ref (ref $t)) |
| ;; This might end up calling an imported function, |
| ;; so we don't know anything about effects here |
| (call_ref $t (i32.const 1) (local.get $ref)) |
| ) |
| ) |
| |
| (module |
| (type $t (func (param i32))) |
| ;; CHECK: (func $calls-unreachable (type $0) |
| ;; CHECK-NEXT: (block ;; (replaces unreachable CallRef we can't emit) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $calls-unreachable (export "calls-unreachable") |
| ;; $t looks like it has no effects, but unreachable is passed in, |
| ;; so preserve the trap. |
| (call_ref $t (unreachable)) |
| ) |
| ) |