;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.

;; RUN: foreach %s %t wasm-opt --precompute-propagate -all -S -o - | filecheck %s

(module
  ;; CHECK:      (type $struct-imm (struct (field i32)))
  (type $struct-imm (struct i32))

  ;; CHECK:      (type $struct-mut (struct (field (mut i32))))
  (type $struct-mut (struct (mut i32)))

  ;; CHECK:      (type $array-mut (array (mut i32)))

  ;; CHECK:      (type $array-imm (array i32))
  (type $array-imm (array i32))

  (type $array-mut (array (mut i32)))

  ;; CHECK:      (func $propagate-struct (type $2)
  ;; CHECK-NEXT:  (local $ref-imm (ref null $struct-imm))
  ;; CHECK-NEXT:  (local $ref-mut (ref null $struct-mut))
  ;; CHECK-NEXT:  (local.set $ref-imm
  ;; CHECK-NEXT:   (struct.new $struct-imm
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $ref-mut
  ;; CHECK-NEXT:   (struct.new $struct-mut
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (struct.get $struct-mut 0
  ;; CHECK-NEXT:    (local.get $ref-mut)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $propagate-struct
    (local $ref-imm (ref null $struct-imm))
    (local $ref-mut (ref null $struct-mut))
    ;; We can propagate from an immutable field of a struct created in this
    ;; function.
    (local.set $ref-imm
      (struct.new $struct-imm
        (i32.const 1)
      )
    )
    (call $helper
      (struct.get $struct-imm 0
        (local.get $ref-imm)
      )
    )
    ;; But the same thing on a mutable field fails.
    (local.set $ref-mut
      (struct.new $struct-mut
        (i32.const 1)
      )
    )
    (call $helper
      (struct.get $struct-mut 0
        (local.get $ref-mut)
      )
    )
  )

  ;; CHECK:      (func $propagate-array (type $2)
  ;; CHECK-NEXT:  (local $ref-imm (ref null $array-imm))
  ;; CHECK-NEXT:  (local $ref-mut (ref null $array-mut))
  ;; CHECK-NEXT:  (local.set $ref-imm
  ;; CHECK-NEXT:   (array.new_fixed $array-imm 3
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:    (i32.const 20)
  ;; CHECK-NEXT:    (i32.const 30)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (i32.const 30)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (i32.const 3)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $ref-mut
  ;; CHECK-NEXT:   (array.new_fixed $array-mut 3
  ;; CHECK-NEXT:    (i32.const 10)
  ;; CHECK-NEXT:    (i32.const 20)
  ;; CHECK-NEXT:    (i32.const 30)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (array.get $array-mut
  ;; CHECK-NEXT:    (local.get $ref-mut)
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (i32.const 3)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $propagate-array
    (local $ref-imm (ref null $array-imm))
    (local $ref-mut (ref null $array-mut))
    ;; We can propagate from a slot in an immutable array created in this
    ;; function, and also get the length.
    (local.set $ref-imm
      (array.new_fixed $array-imm 3
        (i32.const 10)
        (i32.const 20)
        (i32.const 30)
      )
    )
    (call $helper
      (array.get $array-imm  ;; this returns 30
        (local.get $ref-imm)
        (i32.const 2)
      )
    )
    (call $helper
      (array.len ;; this returns 3
        (local.get $ref-imm)
      )
    )
    ;; But the same thing on a mutable array fails.
    (local.set $ref-mut
      (array.new_fixed $array-mut 3
        (i32.const 10)
        (i32.const 20)
        (i32.const 30)
      )
    )
    (call $helper
      (array.get $array-mut
        (local.get $ref-mut)
        (i32.const 2)
      )
    )
    ;; We can, however, optimize array.len in both cases as that is an
    ;; immutable property.
    (call $helper
      (array.len ;; this returns 3
        (local.get $ref-mut)
      )
    )
  )

  ;; CHECK:      (func $non-constant (type $1) (param $param i32)
  ;; CHECK-NEXT:  (local $ref (ref null $struct-imm))
  ;; CHECK-NEXT:  (local.set $ref
  ;; CHECK-NEXT:   (struct.new $struct-imm
  ;; CHECK-NEXT:    (local.get $param)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (struct.get $struct-imm 0
  ;; CHECK-NEXT:    (local.get $ref)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $non-constant (param $param i32)
    (local $ref (ref null $struct-imm))
    (local.set $ref
      (struct.new $struct-imm
        ;; This value is not constant, so we have nothing to propagate.
        (local.get $param)
      )
    )
    (call $helper
      (struct.get $struct-imm 0
        (local.get $ref)
      )
    )
  )

  ;; CHECK:      (func $unreachable (type $2)
  ;; CHECK-NEXT:  (local $ref-imm (ref null $struct-imm))
  ;; CHECK-NEXT:  (local.tee $ref-imm
  ;; CHECK-NEXT:   (block ;; (replaces unreachable StructNew we can't emit)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (struct.get $struct-imm 0
  ;; CHECK-NEXT:    (local.get $ref-imm)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreachable
    (local $ref-imm (ref null $struct-imm))
    ;; Test we do not error on an unreachable value.
    (local.set $ref-imm
      (struct.new $struct-imm
        (unreachable)
      )
    )
    (call $helper
      (struct.get $struct-imm 0
        (local.get $ref-imm)
      )
    )
  )

  ;; CHECK:      (func $param (type $6) (param $ref-imm (ref null $struct-imm))
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (struct.get $struct-imm 0
  ;; CHECK-NEXT:    (local.get $ref-imm)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $param (param $ref-imm (ref null $struct-imm))
    ;; Test we ignore a param value, whose data we do not know.
    (call $helper
      (struct.get $struct-imm 0
        (local.get $ref-imm)
      )
    )
  )

  ;; CHECK:      (func $local-null (type $2)
  ;; CHECK-NEXT:  (local $ref-imm (ref null $struct-imm))
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (block ;; (replaces unreachable StructGet we can't emit)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (ref.null none)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $local-null
    (local $ref-imm (ref null $struct-imm))
    ;; Test we ignore a local value that is null.
    ;; TODO: this could be precomputed to an unreachable
    (call $helper
      (struct.get $struct-imm 0
        (local.get $ref-imm)
      )
    )
  )

  ;; CHECK:      (func $local-unknown (type $1) (param $x i32)
  ;; CHECK-NEXT:  (local $ref-imm (ref null $struct-imm))
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (local.set $ref-imm
  ;; CHECK-NEXT:     (struct.new $struct-imm
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (else
  ;; CHECK-NEXT:    (local.set $ref-imm
  ;; CHECK-NEXT:     (struct.new $struct-imm
  ;; CHECK-NEXT:      (i32.const 2)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (struct.get $struct-imm 0
  ;; CHECK-NEXT:    (local.get $ref-imm)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $local-unknown (param $x i32)
    (local $ref-imm (ref null $struct-imm))
    ;; Do not propagate if a local has more than one possible struct.new with
    ;; different values.
    (if
      (local.get $x)
      (then
        (local.set $ref-imm
          (struct.new $struct-imm
            (i32.const 1)
          )
        )
      )
      (else
        (local.set $ref-imm
          (struct.new $struct-imm
            (i32.const 2)
          )
        )
      )
    )
    (call $helper
      (struct.get $struct-imm 0
        (local.get $ref-imm)
      )
    )
  )

  ;; CHECK:      (func $local-unknown-ref-same-value (type $1) (param $x i32)
  ;; CHECK-NEXT:  (local $ref-imm (ref null $struct-imm))
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (local.set $ref-imm
  ;; CHECK-NEXT:     (struct.new $struct-imm
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (else
  ;; CHECK-NEXT:    (local.set $ref-imm
  ;; CHECK-NEXT:     (struct.new $struct-imm
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (struct.get $struct-imm 0
  ;; CHECK-NEXT:    (local.get $ref-imm)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $local-unknown-ref-same-value (param $x i32)
    (local $ref-imm (ref null $struct-imm))
    ;; As above, but the two different refs have the same value, so we can in
    ;; theory optimize. However, atm we do nothing here, as the analysis stops
    ;; when it sees it cannot propagate the local value (the ref, which has two
    ;; possible values).
    (if
      (local.get $x)
      (then
        (local.set $ref-imm
          (struct.new $struct-imm
            (i32.const 1)
          )
        )
      )
      (else
        (local.set $ref-imm
          (struct.new $struct-imm
            (i32.const 1)
          )
        )
      )
    )
    (call $helper
      (struct.get $struct-imm 0
        (local.get $ref-imm)
      )
    )
  )

  ;; CHECK:      (func $propagate-multi-refs (type $1) (param $x i32)
  ;; CHECK-NEXT:  (local $ref-imm (ref null $struct-imm))
  ;; CHECK-NEXT:  (if
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (then
  ;; CHECK-NEXT:    (local.set $ref-imm
  ;; CHECK-NEXT:     (struct.new $struct-imm
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (call $helper
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (else
  ;; CHECK-NEXT:    (local.set $ref-imm
  ;; CHECK-NEXT:     (struct.new $struct-imm
  ;; CHECK-NEXT:      (i32.const 2)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (call $helper
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $propagate-multi-refs (param $x i32)
    (local $ref-imm (ref null $struct-imm))
    ;; Propagate more than once in a function, using the same local that is
    ;; reused.
    (if
      (local.get $x)
      (then
        (block
          (local.set $ref-imm
            (struct.new $struct-imm
              (i32.const 1)
            )
          )
          (call $helper
            (struct.get $struct-imm 0
              (local.get $ref-imm)
            )
          )
        )
      )
      (else
        (block
          (local.set $ref-imm
            (struct.new $struct-imm
              (i32.const 2)
            )
          )
          (call $helper
            (struct.get $struct-imm 0
              (local.get $ref-imm)
            )
          )
        )
      )
    )
  )

  ;; CHECK:      (func $propagate-multi-values (type $1) (param $x i32)
  ;; CHECK-NEXT:  (local $ref-imm (ref null $struct-imm))
  ;; CHECK-NEXT:  (local.set $ref-imm
  ;; CHECK-NEXT:   (struct.new $struct-imm
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $propagate-multi-values (param $x i32)
    (local $ref-imm (ref null $struct-imm))
    ;; Propagate a ref's value more than once
    (local.set $ref-imm
      (struct.new $struct-imm
        (i32.const 1)
      )
    )
    (call $helper
      (struct.get $struct-imm 0
        (local.get $ref-imm)
      )
    )
    (call $helper
      (struct.get $struct-imm 0
        (local.get $ref-imm)
      )
    )
    (call $helper
      (struct.get $struct-imm 0
        (local.get $ref-imm)
      )
    )
  )

  ;; CHECK:      (func $helper (type $1) (param $0 i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $helper (param i32))
)

(module
  ;; One field is immutable, the other is not, so we can only propagate the
  ;; former.
  ;; CHECK:      (type $struct (struct (field (mut i32)) (field i32)))
  (type $struct (struct (mut i32) i32))

  ;; CHECK:      (func $propagate (type $1)
  ;; CHECK-NEXT:  (local $ref (ref null $struct))
  ;; CHECK-NEXT:  (local.set $ref
  ;; CHECK-NEXT:   (struct.new $struct
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (struct.get $struct 0
  ;; CHECK-NEXT:    (local.get $ref)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (i32.const 2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $propagate
    (local $ref (ref null $struct))
    ;; We can propagate from an immutable field of a struct created in this
    ;; function.
    (local.set $ref
      (struct.new $struct
        (i32.const 1)
        (i32.const 2)
      )
    )
    (call $helper
      (struct.get $struct 0
        (local.get $ref)
      )
    )
    (call $helper
      (struct.get $struct 1
        (local.get $ref)
      )
    )
  )

  ;; CHECK:      (func $helper (type $2) (param $0 i32)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $helper (param i32))
)

(module
  ;; Create an immutable vtable in an immutable field, which lets us read from
  ;; it.

  ;; CHECK:      (type $vtable (struct (field funcref)))
  (type $vtable (struct funcref))
  ;; CHECK:      (type $object (struct (field (ref $vtable))))
  (type $object (struct (ref $vtable)))

  ;; CHECK:      (func $nested-creations (type $2)
  ;; CHECK-NEXT:  (local $ref (ref null $object))
  ;; CHECK-NEXT:  (local.set $ref
  ;; CHECK-NEXT:   (struct.new $object
  ;; CHECK-NEXT:    (struct.new $vtable
  ;; CHECK-NEXT:     (ref.func $nested-creations)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (ref.func $nested-creations)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $nested-creations
    (local $ref (ref null $object))
    ;; Create an object with a reference to another object, and propagate
    ;; through both of them to a constant value, which saves two struct.gets.
    (local.set $ref
      (struct.new $object
        (struct.new $vtable
          (ref.func $nested-creations)
        )
      )
    )
    (call $helper
      (struct.get $vtable 0
        (struct.get $object 0
          (local.get $ref)
        )
      )
    )
  )

  ;; CHECK:      (func $helper (type $3) (param $0 funcref)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $helper (param funcref))
)

(module
  ;; As above, but make $vtable not immutable, which prevents optimization.

  ;; CHECK:      (type $vtable (struct (field (mut funcref))))
  (type $vtable (struct (mut funcref)))
  ;; CHECK:      (type $object (struct (field (ref $vtable))))
  (type $object (struct (ref $vtable)))

  ;; CHECK:      (func $nested-creations (type $2)
  ;; CHECK-NEXT:  (local $ref (ref null $object))
  ;; CHECK-NEXT:  (local.set $ref
  ;; CHECK-NEXT:   (struct.new $object
  ;; CHECK-NEXT:    (struct.new $vtable
  ;; CHECK-NEXT:     (ref.func $nested-creations)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (struct.get $vtable 0
  ;; CHECK-NEXT:    (struct.get $object 0
  ;; CHECK-NEXT:     (local.get $ref)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $nested-creations
    (local $ref (ref null $object))
    (local.set $ref
      (struct.new $object
        (struct.new $vtable
          (ref.func $nested-creations)
        )
      )
    )
    (call $helper
      (struct.get $vtable 0
        ;; Note that we *can* precompute the first struct.get here, but there
        ;; is no constant expression we can emit for it, so we do nothing.
        (struct.get $object 0
          (local.get $ref)
        )
      )
    )
  )

  ;; CHECK:      (func $helper (type $3) (param $0 funcref)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $helper (param funcref))
)


(module
  ;; As above, but make $object not immutable, which prevents optimization.

  ;; CHECK:      (type $vtable (struct (field funcref)))
  (type $vtable (struct funcref))
  ;; CHECK:      (type $object (struct (field (mut (ref $vtable)))))
  (type $object (struct (mut (ref $vtable))))

  ;; CHECK:      (func $nested-creations (type $2)
  ;; CHECK-NEXT:  (local $ref (ref null $object))
  ;; CHECK-NEXT:  (local.set $ref
  ;; CHECK-NEXT:   (struct.new $object
  ;; CHECK-NEXT:    (struct.new $vtable
  ;; CHECK-NEXT:     (ref.func $nested-creations)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (struct.get $vtable 0
  ;; CHECK-NEXT:    (struct.get $object 0
  ;; CHECK-NEXT:     (local.get $ref)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $nested-creations
    (local $ref (ref null $object))
    (local.set $ref
      (struct.new $object
        (struct.new $vtable
          (ref.func $nested-creations)
        )
      )
    )
    (call $helper
      (struct.get $vtable 0
        (struct.get $object 0
          (local.get $ref)
        )
      )
    )
  )

  ;; CHECK:      (func $helper (type $3) (param $0 funcref)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $helper (param funcref))
)

(module
  ;; Create an immutable vtable in an immutable global, which we can optimize
  ;; with.

  ;; CHECK:      (type $vtable (struct (field funcref)))
  (type $vtable (struct funcref))
  ;; CHECK:      (type $object (struct (field (ref $vtable))))
  (type $object (struct (ref $vtable)))

  ;; CHECK:      (global $vtable (ref $vtable) (struct.new $vtable
  ;; CHECK-NEXT:  (ref.func $nested-creations)
  ;; CHECK-NEXT: ))
  (global $vtable (ref $vtable)
    (struct.new $vtable
      (ref.func $nested-creations)
    )
  )

  ;; CHECK:      (func $nested-creations (type $2)
  ;; CHECK-NEXT:  (local $ref (ref null $object))
  ;; CHECK-NEXT:  (local.set $ref
  ;; CHECK-NEXT:   (struct.new $object
  ;; CHECK-NEXT:    (global.get $vtable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (ref.func $nested-creations)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $nested-creations
    (local $ref (ref null $object))
    (local.set $ref
      (struct.new $object
        (global.get $vtable)
      )
    )
    (call $helper
      (struct.get $vtable 0
        (struct.get $object 0
          (local.get $ref)
        )
      )
    )
  )

  ;; CHECK:      (func $helper (type $3) (param $0 funcref)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $helper (param funcref))
)

(module
  ;; Create an immutable vtable in an mutable global, whose mutability prevents
  ;; optimization.

  ;; CHECK:      (type $vtable (struct (field funcref)))
  (type $vtable (struct funcref))
  ;; CHECK:      (type $object (struct (field (ref $vtable))))
  (type $object (struct (ref $vtable)))

  ;; CHECK:      (global $vtable (mut (ref $vtable)) (struct.new $vtable
  ;; CHECK-NEXT:  (ref.func $nested-creations)
  ;; CHECK-NEXT: ))
  (global $vtable (mut (ref $vtable))
    (struct.new $vtable
      (ref.func $nested-creations)
    )
  )

  ;; CHECK:      (func $nested-creations (type $2)
  ;; CHECK-NEXT:  (local $ref (ref null $object))
  ;; CHECK-NEXT:  (local.set $ref
  ;; CHECK-NEXT:   (struct.new $object
  ;; CHECK-NEXT:    (global.get $vtable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (struct.get $vtable 0
  ;; CHECK-NEXT:    (struct.get $object 0
  ;; CHECK-NEXT:     (local.get $ref)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $nested-creations
    (local $ref (ref null $object))
    (local.set $ref
      (struct.new $object
        (global.get $vtable)
      )
    )
    (call $helper
      (struct.get $vtable 0
        (struct.get $object 0
          (local.get $ref)
        )
      )
    )
  )

  ;; CHECK:      (func $helper (type $3) (param $0 funcref)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $helper (param funcref))
)

(module
  ;; Create an immutable vtable in an immutable global, but using an array
  ;; instead of a struct.

  ;; CHECK:      (type $vtable (array funcref))
  (type $vtable (array funcref))
  ;; CHECK:      (type $object (struct (field (ref $vtable))))
  (type $object (struct (ref $vtable)))

  ;; CHECK:      (global $vtable (ref $vtable) (array.new_fixed $vtable 1
  ;; CHECK-NEXT:  (ref.func $nested-creations)
  ;; CHECK-NEXT: ))
  (global $vtable (ref $vtable)
    (array.new_fixed $vtable 1
      (ref.func $nested-creations)
    )
  )

  ;; CHECK:      (func $nested-creations (type $2) (param $param i32)
  ;; CHECK-NEXT:  (local $ref (ref null $object))
  ;; CHECK-NEXT:  (local.set $ref
  ;; CHECK-NEXT:   (struct.new $object
  ;; CHECK-NEXT:    (global.get $vtable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (ref.func $nested-creations)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (array.get $vtable
  ;; CHECK-NEXT:    (struct.get $object 0
  ;; CHECK-NEXT:     (local.get $ref)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.get $param)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $nested-creations (param $param i32)
    (local $ref (ref null $object))
    (local.set $ref
      (struct.new $object
        (global.get $vtable)
      )
    )
    (call $helper
      (array.get $vtable
        (struct.get $object 0
          (local.get $ref)
        )
        (i32.const 0)
      )
    )
    ;; The second operation here uses a param for the array index, which is not
    ;; constant.
    (call $helper
      (array.get $vtable
        (struct.get $object 0
          (local.get $ref)
        )
        (local.get $param)
      )
    )
  )

  ;; CHECK:      (func $helper (type $3) (param $0 funcref)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $helper (param funcref))
)

(module
  ;; A j2wasm-like itable pattern: An itable is an array of (possibly-null)
  ;; data that is filled with vtables of different types. On usage, we do a
  ;; cast of the vtable type.

  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $itable (array structref))
    (type $itable (array (ref null struct)))

    ;; CHECK:       (type $object (struct (field (ref $itable))))
    (type $object (struct (ref $itable)))

    ;; CHECK:       (type $vtable-0 (struct (field funcref)))
    (type $vtable-0 (struct funcref))

    ;; CHECK:       (type $vtable-1 (struct (field funcref)))
    (type $vtable-1 (struct funcref))
  )

  ;; CHECK:      (global $itable (ref $itable) (array.new_fixed $itable 2
  ;; CHECK-NEXT:  (struct.new $vtable-0
  ;; CHECK-NEXT:   (ref.func $nested-creations)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.new $vtable-1
  ;; CHECK-NEXT:   (ref.func $helper)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: ))
  (global $itable (ref $itable)
    (array.new_fixed $itable 2
      (struct.new $vtable-0
        (ref.func $nested-creations)
      )
      (struct.new $vtable-1
        (ref.func $helper)
      )
    )
  )

  ;; CHECK:      (func $nested-creations (type $4)
  ;; CHECK-NEXT:  (local $ref (ref null $object))
  ;; CHECK-NEXT:  (local.set $ref
  ;; CHECK-NEXT:   (struct.new $object
  ;; CHECK-NEXT:    (global.get $itable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (ref.func $nested-creations)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (call $helper
  ;; CHECK-NEXT:   (ref.func $helper)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $nested-creations
    (local $ref (ref null $object))
    (local.set $ref
      (struct.new $object
        (global.get $itable)
      )
    )
    ;; We can precompute all these operations away into the final constants.
    (call $helper
      (struct.get $vtable-0 0
        (ref.cast (ref null $vtable-0)
          (array.get $itable
            (struct.get $object 0
              (local.get $ref)
            )
            (i32.const 0)
          )
        )
      )
    )
    (call $helper
      (struct.get $vtable-1 0
        (ref.cast (ref null $vtable-1)
          (array.get $itable
            (struct.get $object 0
              (local.get $ref)
            )
            (i32.const 1)
          )
        )
      )
    )
  )

  ;; CHECK:      (func $helper (type $5) (param $0 funcref)
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT: )
  (func $helper (param funcref))
)