| ;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited. |
| |
| ;; RUN: foreach %s %t wasm-opt --optimize-instructions -all -S -o - | filecheck %s |
| |
| ;; Idempotent-marked functions can be assumed to always return the same value. |
| |
| (module |
| ;; CHECK: (import "a" "b" (func $import (type $2) (result f32))) |
| (import "a" "b" (func $import (result f32))) |
| |
| ;; CHECK: (@binaryen.idempotent) |
| ;; CHECK-NEXT: (func $idempotent (type $0) (param $x f32) (result f32) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| (@binaryen.idempotent) |
| (func $idempotent (param $x f32) (result f32) |
| ;; This function is idempotent: same inputs, same outputs. |
| (local.get $x) |
| ) |
| |
| ;; CHECK: (func $potent (type $0) (param $x f32) (result f32) |
| ;; CHECK-NEXT: (call $import) |
| ;; CHECK-NEXT: ) |
| (func $potent (param $x f32) (result f32) |
| ;; This function is not idempotent - anything might happen here. |
| (call $import) |
| ) |
| |
| ;; CHECK: (func $test-abs (type $1) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (f32.mul |
| ;; CHECK-NEXT: (call $idempotent |
| ;; CHECK-NEXT: (f32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $idempotent |
| ;; CHECK-NEXT: (f32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (f32.abs |
| ;; CHECK-NEXT: (f32.mul |
| ;; CHECK-NEXT: (call $potent |
| ;; CHECK-NEXT: (f32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $potent |
| ;; CHECK-NEXT: (f32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (f32.abs |
| ;; CHECK-NEXT: (f32.mul |
| ;; CHECK-NEXT: (call $idempotent |
| ;; CHECK-NEXT: (f32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $idempotent |
| ;; CHECK-NEXT: (f32.const 20) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test-abs |
| ;; These calls are identical, since the second returns the same. We can |
| ;; remove the abs, as multiplying a value by itself is non-negative anyhow. |
| (drop |
| (f32.abs |
| (f32.mul |
| (call $idempotent |
| (f32.const 10) |
| ) |
| (call $idempotent |
| (f32.const 10) |
| ) |
| ) |
| ) |
| ) |
| ;; But here we can do nothing, as we lack idempotency. |
| (drop |
| (f32.abs |
| (f32.mul |
| (call $potent |
| (f32.const 10) |
| ) |
| (call $potent |
| (f32.const 10) |
| ) |
| ) |
| ) |
| ) |
| ;; Here we fail as well, as while we have idempotency, the params differ. |
| (drop |
| (f32.abs |
| (f32.mul |
| (call $idempotent |
| (f32.const 10) |
| ) |
| (call $idempotent |
| (f32.const 20) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $test-abs-calls (type $1) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (f32.mul |
| ;; CHECK-NEXT: (@binaryen.idempotent) |
| ;; CHECK-NEXT: (call $potent |
| ;; CHECK-NEXT: (f32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (@binaryen.idempotent) |
| ;; CHECK-NEXT: (call $potent |
| ;; CHECK-NEXT: (f32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (f32.abs |
| ;; CHECK-NEXT: (f32.mul |
| ;; CHECK-NEXT: (@binaryen.idempotent) |
| ;; CHECK-NEXT: (call $potent |
| ;; CHECK-NEXT: (f32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $potent |
| ;; CHECK-NEXT: (f32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (f32.mul |
| ;; CHECK-NEXT: (call $potent |
| ;; CHECK-NEXT: (f32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (@binaryen.idempotent) |
| ;; CHECK-NEXT: (call $potent |
| ;; CHECK-NEXT: (f32.const 10) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test-abs-calls |
| ;; Here we succeed, as the calls (as opposed to the functions) are marked |
| ;; idempotent. |
| (drop |
| (f32.abs |
| (f32.mul |
| (@binaryen.idempotent) |
| (call $potent |
| (f32.const 10) |
| ) |
| (@binaryen.idempotent) |
| (call $potent |
| (f32.const 10) |
| ) |
| ) |
| ) |
| ) |
| ;; Only one is marked. Marking the first is not enough for us to optimize. |
| (drop |
| (f32.abs |
| (f32.mul |
| (@binaryen.idempotent) |
| (call $potent |
| (f32.const 10) |
| ) |
| (call $potent |
| (f32.const 10) |
| ) |
| ) |
| ) |
| ) |
| ;; Marking the second *is* enough for us to optimize. |
| (drop |
| (f32.abs |
| (f32.mul |
| (call $potent |
| (f32.const 10) |
| ) |
| (@binaryen.idempotent) |
| (call $potent |
| (f32.const 10) |
| ) |
| ) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; References. |
| (module |
| ;; CHECK: (type $struct (struct)) |
| (type $struct (struct)) |
| |
| ;; CHECK: (import "a" "b" (func $import (type $3) (result eqref))) |
| (import "a" "b" (func $import (result eqref))) |
| |
| ;; CHECK: (global $g1 (ref $struct) (struct.new_default $struct)) |
| (global $g1 (ref $struct) (struct.new $struct)) |
| |
| ;; CHECK: (global $g2 (ref $struct) (struct.new_default $struct)) |
| (global $g2 (ref $struct) (struct.new $struct)) |
| |
| ;; CHECK: (global $g-mut (mut (ref $struct)) (struct.new_default $struct)) |
| (global $g-mut (mut (ref $struct)) (struct.new $struct)) |
| |
| ;; CHECK: (@binaryen.idempotent) |
| ;; CHECK-NEXT: (func $idempotent (type $1) (param $x eqref) (result eqref) |
| ;; CHECK-NEXT: (local.get $x) |
| ;; CHECK-NEXT: ) |
| (@binaryen.idempotent) |
| (func $idempotent (param $x eqref) (result eqref) |
| ;; This function is idempotent: same inputs, same outputs. |
| (local.get $x) |
| ) |
| |
| ;; CHECK: (func $potent (type $1) (param $x eqref) (result eqref) |
| ;; CHECK-NEXT: (call $import) |
| ;; CHECK-NEXT: ) |
| (func $potent (param $x eqref) (result eqref) |
| ;; This function is not idempotent - anything might happen here. |
| (call $import) |
| ) |
| |
| ;; CHECK: (func $test-ref.eq (type $2) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (block (result i32) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (call $idempotent |
| ;; CHECK-NEXT: (global.get $g1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (call $idempotent |
| ;; CHECK-NEXT: (global.get $g1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (i32.const 1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (call $potent |
| ;; CHECK-NEXT: (global.get $g1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $potent |
| ;; CHECK-NEXT: (global.get $g1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (call $idempotent |
| ;; CHECK-NEXT: (global.get $g1) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $idempotent |
| ;; CHECK-NEXT: (global.get $g2) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (call $idempotent |
| ;; CHECK-NEXT: (global.get $g-mut) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $idempotent |
| ;; CHECK-NEXT: (global.get $g-mut) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test-ref.eq |
| ;; These calls are identical, since the second returns the same. This |
| ;; results in 1. |
| (drop |
| (ref.eq |
| (call $idempotent |
| (global.get $g1) |
| ) |
| (call $idempotent |
| (global.get $g1) |
| ) |
| ) |
| ) |
| ;; We cannot optimize without idempotency. |
| (drop |
| (ref.eq |
| (call $potent |
| (global.get $g1) |
| ) |
| (call $potent |
| (global.get $g1) |
| ) |
| ) |
| ) |
| ;; We cannot optimize here either - we have idempotency, but params differ. |
| (drop |
| (ref.eq |
| (call $idempotent |
| (global.get $g1) |
| ) |
| (call $idempotent |
| (global.get $g2) |
| ) |
| ) |
| ) |
| ;; We cannot optimize here either - we have idempotency, but the global |
| ;; read is mutable, so the first call might modify it, making it different |
| ;; the second time it is read. |
| (drop |
| (ref.eq |
| (call $idempotent |
| (global.get $g-mut) |
| ) |
| (call $idempotent |
| (global.get $g-mut) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $test-ref.eq-nested-calls (type $2) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (call $idempotent |
| ;; CHECK-NEXT: (call $get-struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $idempotent |
| ;; CHECK-NEXT: (call $get-struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (drop |
| ;; CHECK-NEXT: (ref.eq |
| ;; CHECK-NEXT: (call $idempotent |
| ;; CHECK-NEXT: (@binaryen.idempotent) |
| ;; CHECK-NEXT: (call $get-struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: (call $idempotent |
| ;; CHECK-NEXT: (@binaryen.idempotent) |
| ;; CHECK-NEXT: (call $get-struct) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| ;; CHECK-NEXT: ) |
| (func $test-ref.eq-nested-calls |
| ;; We cannot optimize here - we have idempotency, but the children |
| ;; have effects themselves so we can't tell if they are equal. |
| (drop |
| (ref.eq |
| (call $idempotent |
| (call $get-struct) |
| ) |
| (call $idempotent |
| (call $get-struct) |
| ) |
| ) |
| ) |
| ;; Marking those children as idempotent should help, but we do not handle |
| ;; that yet. TODO |
| (drop |
| (ref.eq |
| (call $idempotent |
| (@binaryen.idempotent) |
| (call $get-struct) |
| ) |
| (call $idempotent |
| (@binaryen.idempotent) |
| (call $get-struct) |
| ) |
| ) |
| ) |
| ) |
| |
| ;; CHECK: (func $get-struct (type $4) (result (ref null $struct)) |
| ;; CHECK-NEXT: (unreachable) |
| ;; CHECK-NEXT: ) |
| (func $get-struct (result (ref null $struct)) |
| ;; Helper for above |
| (unreachable) |
| ) |
| ) |