;; 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)
  )
)
