;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited.
;; RUN: foreach %s %t wasm-opt --gto --closed-world -all -S -o - | filecheck %s

(module
  ;; A struct with a field that is never read or written, so it can be
  ;; removed.

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct)))
  (type $struct (sub (struct (field (mut funcref)))))

  ;; CHECK:       (type $1 (func (param (ref $struct))))

  ;; CHECK:      (func $func (type $1) (param $x (ref $struct))
  ;; CHECK-NEXT: )
  (func $func (param $x (ref $struct))
  )
)

(module
  ;; A write does not keep a field from being removed.

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct)))
  (type $struct (sub (struct (field (mut funcref)))))

  ;; CHECK:       (type $1 (func (param (ref $struct))))

  ;; CHECK:      (func $func (type $1) (param $x (ref $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (block (result (ref $struct))
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (ref.null nofunc)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (param $x (ref $struct))
    ;; The fields of this set will be dropped, as we do not need to perform
    ;; the write.
    (struct.set $struct 0
      (local.get $x)
      (ref.null func)
    )
  )
)

(module
  ;; A new does not keep a field from being removed.

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct)))
  (type $struct (sub (struct (field (mut funcref)))))

  ;; CHECK:       (type $1 (func (param (ref $struct))))

  ;; CHECK:      (func $func (type $1) (param $x (ref $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (param $x (ref $struct))
    ;; The fields in this new will be removed.
    (drop
      (struct.new $struct
        (ref.null func)
      )
    )
  )
)

(module
  ;; A new_default does not keep a field from being removed.

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct)))
  (type $struct (sub (struct (field (mut funcref)))))

  ;; CHECK:       (type $1 (func (param (ref $struct))))

  ;; CHECK:      (func $func (type $1) (param $x (ref $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default $struct)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (param $x (ref $struct))
    ;; The fields in this new will be removed.
    (drop
      (struct.new_default $struct
      )
    )
  )
)

(module
  ;; A read *does* keep a field from being removed.

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field funcref))))
  (type $struct (sub (struct (field (mut funcref)))))

  ;; CHECK:       (type $1 (func (param (ref $struct))))

  ;; CHECK:      (func $func (type $1) (param $x (ref $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $struct 0
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (param $x (ref $struct))
    (drop
      (struct.get $struct 0
        (local.get $x)
      )
    )
  )
)

(module
  ;; Different struct types with different situations: some fields are read,
  ;; some written, and some both. (Note that this also tests the interaction
  ;; of removing with the immutability inference that --gto does.)

  ;; A struct with all fields marked mutable.
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $imm-struct (sub (struct (field $rw i32) (field $rw-2 i32))))

  ;; CHECK:       (type $1 (func (param (ref $imm-struct))))

  ;; CHECK:       (type $mut-struct (sub (struct (field $r i32) (field $rw (mut i32)) (field $r-2 i32) (field $rw-2 (mut i32)))))
  (type $mut-struct (sub (struct (field $r (mut i32)) (field $w (mut i32)) (field $rw (mut i32)) (field $r-2 (mut i32)) (field $w-2 (mut i32)) (field $rw-2 (mut i32)))))

  ;; A similar struct but with all fields marked immutable, and the only
  ;; writes are from during creation (so all fields are at least writeable).
  (type $imm-struct (sub (struct (field $w i32) (field $rw i32) (field $w-2 i32) (field $rw-2 i32))))

  ;; CHECK:       (type $3 (func (param (ref $mut-struct))))

  ;; CHECK:      (func $func-mut (type $3) (param $x (ref $mut-struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $mut-struct $r
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (block (result (ref $mut-struct))
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $mut-struct $rw
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $mut-struct $rw
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $mut-struct $r-2
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (block (result (ref $mut-struct))
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (i32.const 2)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $mut-struct $rw-2
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (i32.const 3)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $mut-struct $rw-2
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func-mut (param $x (ref $mut-struct))
    ;; $r is only read
    (drop
      (struct.get $mut-struct $r
        (local.get $x)
      )
    )
    ;; $w is only written
    (struct.set $mut-struct $w
      (local.get $x)
      (i32.const 0)
    )
    ;; $rw is both
    (struct.set $mut-struct $rw
      (local.get $x)
      (i32.const 1)
    )
    (drop
      (struct.get $mut-struct $rw
        (local.get $x)
      )
    )
    ;; The same, for the $*-2 fields
    (drop
      (struct.get $mut-struct $r-2
        (local.get $x)
      )
    )
    (struct.set $mut-struct $w-2
      (local.get $x)
      (i32.const 2)
    )
    (struct.set $mut-struct $rw-2
      (local.get $x)
      (i32.const 3)
    )
    (drop
      (struct.get $mut-struct $rw-2
        (local.get $x)
      )
    )
  )

  ;; CHECK:      (func $func-imm (type $1) (param $x (ref $imm-struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $imm-struct
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:    (i32.const 3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $imm-struct $rw
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $imm-struct $rw-2
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func-imm (param $x (ref $imm-struct))
    ;; create an instance
    (drop
      (struct.new $imm-struct
        (i32.const 0)
        (i32.const 1)
        (i32.const 2)
        (i32.const 3)
      )
    )
    ;; $rw and $rw-2 are also read
    (drop
      (struct.get $imm-struct $rw
        (local.get $x)
      )
    )
    (drop
      (struct.get $imm-struct $rw-2
        (local.get $x)
      )
    )
  )
)

(module
  ;; A vtable-like structure created in a global location. Only some of the
  ;; fields are accessed.

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func))

  ;; CHECK:       (type $vtable (sub (struct (field $v1 funcref) (field $v2 funcref))))
  (type $vtable (sub (struct (field $v0 funcref) (field $v1 funcref) (field $v2 funcref) (field $v3 funcref) (field $v4 funcref))))

  ;; CHECK:      (global $vtable (ref $vtable) (struct.new $vtable
  ;; CHECK-NEXT:  (ref.func $func-1)
  ;; CHECK-NEXT:  (ref.func $func-2)
  ;; CHECK-NEXT: ))
  (global $vtable (ref $vtable) (struct.new $vtable
    (ref.func $func-0)
    (ref.func $func-1)
    (ref.func $func-2)
    (ref.func $func-3)
    (ref.func $func-4)
  ))

  ;; CHECK:      (func $test (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $vtable $v1
  ;; CHECK-NEXT:    (global.get $vtable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $vtable $v2
  ;; CHECK-NEXT:    (global.get $vtable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    ;; To differ from previous tests, do not read the very first field.
    (drop
      (struct.get $vtable 1
        (global.get $vtable)
      )
    )
    ;; To differ from previous tests, do reads in two adjacent fields.
    (drop
      (struct.get $vtable 2
        (global.get $vtable)
      )
    )
    ;; To differ from previous tests, do not read the very last field, and the
    ;; one before it.
  )

  ;; CHECK:      (func $func-0 (type $0)
  ;; CHECK-NEXT: )
  (func $func-0)
  ;; CHECK:      (func $func-1 (type $0)
  ;; CHECK-NEXT: )
  (func $func-1)
  ;; CHECK:      (func $func-2 (type $0)
  ;; CHECK-NEXT: )
  (func $func-2)
  ;; CHECK:      (func $func-3 (type $0)
  ;; CHECK-NEXT: )
  (func $func-3)
  ;; CHECK:      (func $func-4 (type $0)
  ;; CHECK-NEXT: )
  (func $func-4)
)

(module
  ;; Similar to the above, but with different types in each field, to verify
  ;; that we emit valid code and are not confused by the names being right
  ;; by coincidence.


  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func))

  ;; CHECK:       (type $vtable (sub (struct (field $v1 i64) (field $v2 f32))))
  (type $vtable (sub (struct (field $v0 i32) (field $v1 i64) (field $v2 f32) (field $v3 f64) (field $v4 anyref))))

  ;; CHECK:      (global $vtable (ref $vtable) (struct.new $vtable
  ;; CHECK-NEXT:  (i64.const 1)
  ;; CHECK-NEXT:  (f32.const 2.200000047683716)
  ;; CHECK-NEXT: ))
  (global $vtable (ref $vtable) (struct.new $vtable
    (i32.const 0)
    (i64.const 1)
    (f32.const 2.2)
    (f64.const 3.3)
    (ref.null none)
  ))

  ;; CHECK:      (func $test (type $0)
  ;; CHECK-NEXT:  (local $i64 i64)
  ;; CHECK-NEXT:  (local $f32 f32)
  ;; CHECK-NEXT:  (local.set $i64
  ;; CHECK-NEXT:   (struct.get $vtable $v1
  ;; CHECK-NEXT:    (global.get $vtable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $f32
  ;; CHECK-NEXT:   (struct.get $vtable $v2
  ;; CHECK-NEXT:    (global.get $vtable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    (local $i64 i64)
    (local $f32 f32)
    (local.set $i64
      (struct.get $vtable 1
        (global.get $vtable)
      )
    )
    (local.set $f32
      (struct.get $vtable 2
        (global.get $vtable)
      )
    )
  )
)

(module
  ;; A new with side effects

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func (param i32) (result (ref any))))

  ;; CHECK:       (type $1 (func (param i32) (result f64)))

  ;; CHECK:       (type $2 (func (param i32) (result i32)))

  ;; CHECK:       (type $3 (func (param (ref any))))

  ;; CHECK:       (type $4 (func))

  ;; CHECK:       (type $struct (struct (field i32)))
  (type $struct (struct i32 f64 (ref any)))


  ;; CHECK:       (type $6 (func (param (ref any) (ref null $struct))))

  ;; CHECK:      (global $imm-i32 i32 (i32.const 1234))
  (global $imm-i32 i32 (i32.const 1234))

  ;; CHECK:      (global $mut-i32 (mut i32) (i32.const 5678))
  (global $mut-i32 (mut i32) (i32.const 5678))

  ;; CHECK:      (func $gets (type $6) (param $x (ref any)) (param $struct (ref null $struct))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $struct 0
  ;; CHECK-NEXT:    (local.get $struct)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $gets (param $x (ref any)) (param $struct (ref null $struct))
    ;; Gets to keep certain fields alive.
    (drop
      (struct.get $struct 0
        (local.get $struct)
      )
    )
  )

  ;; CHECK:      (func $new-side-effect (type $4)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local $1 f64)
  ;; CHECK-NEXT:  (local $2 (ref any))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref (exact $struct)))
  ;; CHECK-NEXT:    (local.set $0
  ;; CHECK-NEXT:     (call $helper0
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.set $1
  ;; CHECK-NEXT:     (call $helper1
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.set $2
  ;; CHECK-NEXT:     (call $helper2
  ;; CHECK-NEXT:      (i32.const 2)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (struct.new $struct
  ;; CHECK-NEXT:     (local.get $0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $new-side-effect
    ;; The 2nd&3rd fields here will be removed, since those fields have no
    ;; reads. They have side effects, though, so the operands will be saved in
    ;; locals. Note that one of the fields is non-nullable, and we need to use a
    ;; nullable local for it.
    (drop
      (struct.new $struct
        (call $helper0 (i32.const 0))
        (call $helper1 (i32.const 1))
        (call $helper2 (i32.const 2))
      )
    )
  )

  ;; CHECK:      (func $new-side-effect-global-imm (type $4)
  ;; CHECK-NEXT:  (local $0 f64)
  ;; CHECK-NEXT:  (local $1 (ref any))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref (exact $struct)))
  ;; CHECK-NEXT:    (local.set $0
  ;; CHECK-NEXT:     (call $helper1
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.set $1
  ;; CHECK-NEXT:     (call $helper2
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (struct.new $struct
  ;; CHECK-NEXT:     (global.get $imm-i32)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $new-side-effect-global-imm
    ;; As above, the 2nd&3rd fields here will be removed. The first field does
    ;; a global.get, which has effects, but those effects do not interact with
    ;; anything else (since it is an immutable global), so we do not need a
    ;; local for it.
    (drop
      (struct.new $struct
        (global.get $imm-i32)
        (call $helper1 (i32.const 0))
        (call $helper2 (i32.const 1))
      )
    )
  )

  ;; CHECK:      (func $new-side-effect-global-mut (type $4)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local $1 f64)
  ;; CHECK-NEXT:  (local $2 (ref any))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref (exact $struct)))
  ;; CHECK-NEXT:    (local.set $0
  ;; CHECK-NEXT:     (global.get $mut-i32)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.set $1
  ;; CHECK-NEXT:     (call $helper1
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.set $2
  ;; CHECK-NEXT:     (call $helper2
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (struct.new $struct
  ;; CHECK-NEXT:     (local.get $0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $new-side-effect-global-mut
    ;; As above, but the global is mutable, so we will use a local: the calls
    ;; might alter that global, in theory.
    (drop
      (struct.new $struct
        (global.get $mut-i32)
        (call $helper1 (i32.const 0))
        (call $helper2 (i32.const 1))
      )
    )
  )

  ;; CHECK:      (func $new-unreachable (type $4)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block ;; (replaces unreachable StructNew we can't emit)
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (call $helper2
  ;; CHECK-NEXT:      (i32.const 3)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $new-unreachable
    ;; Another case with side effects. We stop at the unreachable param before
    ;; it, however.
    (drop
      (struct.new $struct
        (i32.const 2)
        (unreachable)
        (call $helper2 (i32.const 3))
      )
    )
  )

  ;; CHECK:      (func $new-side-effect-in-kept (type $3) (param $any (ref any))
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result (ref (exact $struct)))
  ;; CHECK-NEXT:    (local.set $1
  ;; CHECK-NEXT:     (call $helper0
  ;; CHECK-NEXT:      (i32.const 0)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (struct.new $struct
  ;; CHECK-NEXT:     (local.get $1)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $new-side-effect-in-kept (param $any (ref any))
    ;; Side effects appear in fields that we do *not* remove. We do not need to
    ;; use locals here, but for simplicity we do, and rely on later opts.
    (drop
      (struct.new $struct
        (call $helper0 (i32.const 0))
        (f64.const 3.14159)
        (local.get $any)
      )
    )
  )

  ;; CHECK:      (func $helper0 (type $2) (param $x i32) (result i32)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $helper0 (param $x i32) (result i32)
    (unreachable)
  )

  ;; CHECK:      (func $helper1 (type $1) (param $x i32) (result f64)
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $helper1 (param $x i32) (result f64)
    (unreachable)
  )

  ;; CHECK:      (func $helper2 (type $0) (param $x i32) (result (ref any))
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $helper2 (param $x i32) (result (ref any))
    (unreachable)
  )
)

;; We can remove fields if they are only used in subtypes, because we can
;; reorder the fields in the super and re-add them in the sub, appending on top
;; of the now-shorter super.
(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $parent (sub (struct (field i64))))
  (type $parent (sub (struct (field i32) (field i64) (field f32) (field f64))))

  ;; CHECK:       (type $child (sub $parent (struct (field i64) (field i32) (field f32) (field f64) (field anyref))))
  (type $child (sub $parent (struct (field i32) (field i64) (field f32) (field f64) (field anyref))))

  ;; CHECK:       (type $2 (func (param (ref $parent) (ref $child))))

  ;; CHECK:      (func $func (type $2) (param $x (ref $parent)) (param $y (ref $child))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $parent 0
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $child 1
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $child 2
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $child 3
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $child 4
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (param $x (ref $parent)) (param $y (ref $child))
    ;; The parent has fields 0, 1, 2, 3 and the child adds 4.
    ;; Use only field 1 in the parent, and all the rest in the child. We can
    ;; reorder field 1 to the start of the parent (flipping its position with
    ;; field 0) and then remove all the fields but the now-first. The child
    ;; keeps all fields, but is reordered.
    (drop (struct.get $parent 1 (local.get $x)))
    (drop (struct.get $child  0 (local.get $y)))
    (drop (struct.get $child  2 (local.get $y)))
    (drop (struct.get $child  3 (local.get $y)))
    (drop (struct.get $child  4 (local.get $y)))
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $parent (sub (struct (field i64) (field (mut f32)))))
  (type $parent (sub (struct (field (mut i32)) (field (mut i64)) (field (mut f32)) (field (mut f64)))))

  ;; CHECK:       (type $child (sub $parent (struct (field i64) (field (mut f32)) (field i32) (field f64) (field anyref))))
  (type $child (sub $parent (struct (field (mut i32)) (field (mut i64)) (field (mut f32)) (field (mut f64)) (field (mut anyref)))))

  ;; CHECK:       (type $2 (func (param (ref $parent) (ref $child))))

  ;; CHECK:      (func $func (type $2) (param $x (ref $parent)) (param $y (ref $child))
  ;; CHECK-NEXT:  (struct.set $parent 1
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (f32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $parent 0
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $child 2
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $child 1
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $child 3
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $child 4
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (param $x (ref $parent)) (param $y (ref $child))
    ;; As above, but add a write in the parent of field 2. That prevents us from
    ;; removing it from the parent.
    (struct.set $parent 2 (local.get $x) (f32.const 0))

    (drop (struct.get $parent 1 (local.get $x)))
    (drop (struct.get $child  0 (local.get $y)))
    (drop (struct.get $child  2 (local.get $y)))
    (drop (struct.get $child  3 (local.get $y)))
    (drop (struct.get $child  4 (local.get $y)))
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $parent (sub (struct (field i64) (field (mut f32)))))
  (type $parent (sub (struct (field (mut i32)) (field (mut i64)) (field (mut f32)) (field (mut f64)))))

  ;; CHECK:       (type $child (sub $parent (struct (field i64) (field (mut f32)) (field i32) (field anyref))))
  (type $child (sub $parent (struct (field (mut i32)) (field (mut i64)) (field (mut f32)) (field (mut f64)) (field (mut anyref)))))

  ;; CHECK:       (type $2 (func (param (ref $parent) (ref $child))))

  ;; CHECK:      (func $func (type $2) (param $x (ref $parent)) (param $y (ref $child))
  ;; CHECK-NEXT:  (struct.set $parent 1
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (f32.const 0)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $parent 0
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $child 2
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $child 1
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $child 3
  ;; CHECK-NEXT:    (local.get $y)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (param $x (ref $parent)) (param $y (ref $child))
    ;; As above, but now we remove fields in the child as well: 3 is not used.
    (struct.set $parent 2 (local.get $x) (f32.const 0))

    (drop (struct.get $parent 1 (local.get $x)))
    (drop (struct.get $child  0 (local.get $y)))
    (drop (struct.get $child  2 (local.get $y)))
    ;; the read of 3 was removed here.
    (drop (struct.get $child  4 (local.get $y)))
  )
)

;; A parent with two children, and there are only reads of the parent. Those
;; reads might be of data of either child, of course (as a refernce to the
;; parent might point to them), so we cannot optimize here.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $parent (sub (struct (field i32))))
    (type $parent (sub (struct (field i32))))
    ;; CHECK:       (type $child1 (sub $parent (struct (field i32))))
    (type $child1 (sub $parent (struct (field i32))))
    ;; CHECK:       (type $child2 (sub $parent (struct (field i32))))
    (type $child2 (sub $parent (struct (field i32))))
  )

  ;; CHECK:      (type $3 (func (param (ref $parent) (ref $child1) (ref $child2))))

  ;; CHECK:      (func $func (type $3) (param $parent (ref $parent)) (param $child1 (ref $child1)) (param $child2 (ref $child2))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $parent 0
  ;; CHECK-NEXT:    (local.get $parent)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (param $parent (ref $parent)) (param $child1 (ref $child1)) (param $child2 (ref $child2))
    (drop (struct.get $parent 0 (local.get $parent)))
  )
)

;; As above, but now the read is just of one child. We can remove the field
;; from the parent and the other child.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $parent (sub (struct)))
    (type $parent (sub (struct (field i32))))

    ;; CHECK:       (type $child2 (sub $parent (struct)))

    ;; CHECK:       (type $child1 (sub $parent (struct (field i32))))
    (type $child1 (sub $parent (struct (field i32))))
    (type $child2 (sub $parent (struct (field i32))))
  )

  ;; CHECK:       (type $3 (func (param (ref $parent) (ref $child1) (ref $child2))))

  ;; CHECK:      (func $func (type $3) (param $parent (ref $parent)) (param $child1 (ref $child1)) (param $child2 (ref $child2))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $child1 0
  ;; CHECK-NEXT:    (local.get $child1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (param $parent (ref $parent)) (param $child1 (ref $child1)) (param $child2 (ref $child2))
    (drop (struct.get $child1 0 (local.get $child1)))
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $0 (func (result (ref $"{mut:i8}"))))

  ;; CHECK:       (type $1 (func (result i32)))

  ;; CHECK:       (type $2 (func))

  ;; CHECK:       (type $"{mut:i8}" (sub (struct)))
  (type $"{mut:i8}" (sub (struct (field (mut i8)))))

  ;; CHECK:       (type $4 (func (param (ref null $"{mut:i8}"))))

  ;; CHECK:      (func $unreachable-set (type $4) (param $"{mut:i8}" (ref null $"{mut:i8}"))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (block (result (ref null $"{mut:i8}"))
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (call $helper-i32)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $"{mut:i8}")
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreachable-set (param $"{mut:i8}" (ref null $"{mut:i8}"))
    ;; The struct type has no reads, so we want to remove all of the sets of it.
    ;; This struct.set will trap on null, but first the call must run. When we
    ;; optimize here we should be careful to not emit something with different
    ;; ordering (naively emitting ref.as_non_null on the reference would trap
    ;; before the call, so we must reorder).
    (struct.set $"{mut:i8}" 0
      (local.get $"{mut:i8}")
      (call $helper-i32)
    )
  )

  ;; CHECK:      (func $unreachable-set-2 (type $4) (param $"{mut:i8}" (ref null $"{mut:i8}"))
  ;; CHECK-NEXT:  (block $block
  ;; CHECK-NEXT:   (drop
  ;; CHECK-NEXT:    (ref.as_non_null
  ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (local.get $"{mut:i8}")
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (drop
  ;; CHECK-NEXT:       (br $block)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreachable-set-2 (param $"{mut:i8}" (ref null $"{mut:i8}"))
    ;; As above, but the side effects now are a br. Again, the br must happen
    ;; before the trap (in fact, the br will skip the trap here).
    (block $block
      (struct.set $"{mut:i8}" 0
        (local.get $"{mut:i8}")
        (br $block)
      )
    )
  )

  ;; CHECK:      (func $unreachable-set-2b (type $4) (param $"{mut:i8}" (ref null $"{mut:i8}"))
  ;; CHECK-NEXT:  (nop)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (local.get $"{mut:i8}")
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (unreachable)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreachable-set-2b (param $"{mut:i8}" (ref null $"{mut:i8}"))
    ;; As above, but with an unreachable instead of a br. We add a nop here so
    ;; that we are inside of a block, and then validation would fail if we do
    ;; not keep the type of the replacement for the struct.set identical to the
    ;; struct.set. That is, the type must remain unreachable.
    (nop)
    (struct.set $"{mut:i8}" 0
      (local.get $"{mut:i8}")
      (unreachable)
    )
  )

  ;; CHECK:      (func $unreachable-set-3 (type $2)
  ;; CHECK-NEXT:  (local $0 (ref $"{mut:i8}"))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (block (result (ref $"{mut:i8}"))
  ;; CHECK-NEXT:     (local.set $0
  ;; CHECK-NEXT:      (call $helper-ref)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (call $helper-i32)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreachable-set-3
    ;; As above, but now we have side effects in both children.
    (block
      (struct.set $"{mut:i8}" 0
        (call $helper-ref)
        (call $helper-i32)
      )
    )
  )

  ;; CHECK:      (func $helper-i32 (type $1) (result i32)
  ;; CHECK-NEXT:  (i32.const 1)
  ;; CHECK-NEXT: )
  (func $helper-i32 (result i32)
    (i32.const 1)
  )

  ;; CHECK:      (func $helper-ref (type $0) (result (ref $"{mut:i8}"))
  ;; CHECK-NEXT:  (unreachable)
  ;; CHECK-NEXT: )
  (func $helper-ref (result (ref $"{mut:i8}"))
    (unreachable)
  )
)

(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct)))
  (type $struct (sub (struct (field anyref) (field i32) (field f32) (field f64))))

  ;; CHECK:       (type $1 (func (result (ref $struct))))

  ;; CHECK:      (func $func (type $1) (result (ref $struct))
  ;; CHECK-NEXT:  (local $0 (ref $struct))
  ;; CHECK-NEXT:  (local $1 f64)
  ;; CHECK-NEXT:  (local.set $0
  ;; CHECK-NEXT:   (call $func)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $1
  ;; CHECK-NEXT:   (block (result f64)
  ;; CHECK-NEXT:    (if
  ;; CHECK-NEXT:     (i32.const 0)
  ;; CHECK-NEXT:     (then
  ;; CHECK-NEXT:      (unreachable)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (f64.const 30)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.new_default $struct)
  ;; CHECK-NEXT: )
  (func $func (result (ref $struct))
    ;; The fields can be removed here, but the effects must be preserved before
    ;; the struct.new. The consts in the middle can vanish entirely.
    (struct.new $struct
      (call $func)
      (i32.const 10)
      (f32.const 20)
      (block (result f64)
        (if
          (i32.const 0)
          (then
            (unreachable)
          )
        )
        (f64.const 30)
      )
    )
  )
)

;; A parent with two children, with fields used in various combinations.
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $A (sub (struct (field i64) (field eqref) (field nullref))))
    (type $A (sub (struct (field i32) (field i64) (field f32) (field f64) (field anyref) (field eqref) (field nullref))))

    ;; CHECK:       (type $C (sub $A (struct (field i64) (field eqref) (field nullref) (field f64) (field anyref))))
    (type $C (sub $A (struct (field i32) (field i64) (field f32) (field f64) (field anyref) (field eqref) (field nullref))))

    ;; CHECK:       (type $B (sub $A (struct (field i64) (field eqref) (field nullref) (field f32) (field anyref))))
    (type $B (sub $A (struct (field i32) (field i64) (field f32) (field f64) (field anyref) (field eqref) (field nullref))))
  )

  ;; CHECK:       (type $3 (func (param anyref)))

  ;; CHECK:      (func $func (type $3) (param $x anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $A 0
  ;; CHECK-NEXT:    (ref.cast (ref $A)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $B 3
  ;; CHECK-NEXT:    (ref.cast (ref $B)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $C 3
  ;; CHECK-NEXT:    (ref.cast (ref $C)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $B 4
  ;; CHECK-NEXT:    (ref.cast (ref $B)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $C 4
  ;; CHECK-NEXT:    (ref.cast (ref $C)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $A 1
  ;; CHECK-NEXT:    (ref.cast (ref $A)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $C 1
  ;; CHECK-NEXT:    (ref.cast (ref $C)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $A 2
  ;; CHECK-NEXT:    (ref.cast (ref $A)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $B 2
  ;; CHECK-NEXT:    (ref.cast (ref $B)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (param $x anyref)
    ;; Field 0 (i32)     is used nowhere.
    ;; Field 1 (i64)     is used only in $A.
    ;; Field 2 (f32)     is used only in $B.
    ;; Field 3 (f64)     is used only in $C.
    ;; Field 4 (anyref)  is used only in $B and $C.
    ;; Field 5 (eqref)   is used only in $A and $C.
    ;; Field 6 (nullref) is used only in $A and $B.
    ;; As a result:
    ;;   * A can keep only fields 1, 5, 6 (i64, eqref, nullref).
    ;;   * B keeps A's fields, and appends 2, 4 (f32, anyref).
    ;;   * C keeps A's fields, and appends 3, 4 (f64, anyref).

    (drop (struct.get $A 1 (ref.cast (ref $A) (local.get $x))))

    (drop (struct.get $B 2 (ref.cast (ref $B) (local.get $x))))

    (drop (struct.get $C 3 (ref.cast (ref $C) (local.get $x))))

    (drop (struct.get $B 4 (ref.cast (ref $B) (local.get $x))))
    (drop (struct.get $C 4 (ref.cast (ref $C) (local.get $x))))

    (drop (struct.get $A 5 (ref.cast (ref $A) (local.get $x))))
    (drop (struct.get $C 5 (ref.cast (ref $C) (local.get $x))))

    (drop (struct.get $A 6 (ref.cast (ref $A) (local.get $x))))
    (drop (struct.get $B 6 (ref.cast (ref $B) (local.get $x))))
  )
)

;; As above, but instead of $A having children $B, $C, now they are a chain,
;;   $A :> $B :> $C
;; $C must now also include $B's fields (specifically field 2, the f32).
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $A (sub (struct (field i64) (field eqref) (field nullref))))
    (type $A (sub (struct (field i32) (field i64) (field f32) (field f64) (field anyref) (field eqref) (field nullref))))

    ;; CHECK:       (type $B (sub $A (struct (field i64) (field eqref) (field nullref) (field f32) (field anyref))))
    (type $B (sub $A (struct (field i32) (field i64) (field f32) (field f64) (field anyref) (field eqref) (field nullref))))

    ;; CHECK:       (type $C (sub $B (struct (field i64) (field eqref) (field nullref) (field f32) (field anyref) (field f64))))
    (type $C (sub $B (struct (field i32) (field i64) (field f32) (field f64) (field anyref) (field eqref) (field nullref))))
  )

  ;; CHECK:       (type $3 (func (param anyref)))

  ;; CHECK:      (func $func (type $3) (param $x anyref)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $A 0
  ;; CHECK-NEXT:    (ref.cast (ref $A)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $B 3
  ;; CHECK-NEXT:    (ref.cast (ref $B)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $C 5
  ;; CHECK-NEXT:    (ref.cast (ref $C)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $B 4
  ;; CHECK-NEXT:    (ref.cast (ref $B)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $C 4
  ;; CHECK-NEXT:    (ref.cast (ref $C)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $A 1
  ;; CHECK-NEXT:    (ref.cast (ref $A)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $C 1
  ;; CHECK-NEXT:    (ref.cast (ref $C)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $A 2
  ;; CHECK-NEXT:    (ref.cast (ref $A)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $B 2
  ;; CHECK-NEXT:    (ref.cast (ref $B)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (param $x anyref)
    ;; Same uses as before.

    (drop (struct.get $A 1 (ref.cast (ref $A) (local.get $x))))

    (drop (struct.get $B 2 (ref.cast (ref $B) (local.get $x))))

    (drop (struct.get $C 3 (ref.cast (ref $C) (local.get $x))))

    (drop (struct.get $B 4 (ref.cast (ref $B) (local.get $x))))
    (drop (struct.get $C 4 (ref.cast (ref $C) (local.get $x))))

    (drop (struct.get $A 5 (ref.cast (ref $A) (local.get $x))))
    (drop (struct.get $C 5 (ref.cast (ref $C) (local.get $x))))

    (drop (struct.get $A 6 (ref.cast (ref $A) (local.get $x))))
    (drop (struct.get $B 6 (ref.cast (ref $B) (local.get $x))))
  )
)

;; The parent $A is an empty struct, with nothing to remove. See we do not error
;; here.
(module
  ;; CHECK:      (type $A (sub (struct)))
  (type $A (sub (struct)))

  ;; CHECK:      (type $B (sub $A (struct)))
  (type $B (sub $A (struct)))

  ;; CHECK:      (type $2 (func (param (ref $B))))

  ;; CHECK:      (func $func (type $2) (param $x (ref $B))
  ;; CHECK-NEXT: )
  (func $func (param $x (ref $B))
    ;; Use $B in a param to keep it alive, and lead us to process it and $A.
  )
)

;; As above, but now $B has fields to remove.
(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $A (sub (struct)))
  (type $A (sub (struct)))

  ;; CHECK:       (type $B (sub $A (struct)))
  (type $B (sub $A (struct (field i32) (field i64))))

  ;; CHECK:       (type $2 (func (param (ref $B))))

  ;; CHECK:      (func $func (type $2) (param $x (ref $B))
  ;; CHECK-NEXT: )
  (func $func (param $x (ref $B))
  )
)

;; As above, but now $B's fields are used.
(module
  ;; CHECK:      (type $A (sub (struct)))
  (type $A (sub (struct)))

  ;; CHECK:      (type $B (sub $A (struct (field i32) (field i64))))
  (type $B (sub $A (struct (field i32) (field i64))))

  ;; CHECK:      (type $2 (func (param (ref $B))))

  ;; CHECK:      (func $func (type $2) (param $x (ref $B))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $B 0
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $B 1
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (param $x (ref $B))
    (drop (struct.get $B 0 (local.get $x)))
    (drop (struct.get $B 1 (local.get $x)))
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $A (sub (struct (field i32))))
    (type $A (sub (struct (field i32))))
    ;; CHECK:       (type $B (sub $A (struct (field i32))))
    (type $B (sub $A (struct (field i32) (field f64))))
  )

  ;; CHECK:       (type $2 (func))

  ;; CHECK:      (func $test (type $2)
  ;; CHECK-NEXT:  (local $x (ref null $A))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $A 0
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $B 0
  ;; CHECK-NEXT:    (ref.cast (ref $B)
  ;; CHECK-NEXT:     (local.get $x)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    (local $x (ref null $A))
    ;; We cannot remove anything from $A, but we can from $B. That $A is
    ;; unchanged should not confuse us.
    (drop
      (struct.get $A 0
        (local.get $x)
      )
    )
    ;; $B reads field 0, but not its new field 1.
    (drop
      (struct.get $B 0
        (ref.cast (ref $B)
          (local.get $x)
        )
      )
    )
  )
)

(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $A (sub (struct)))
    (type $A (sub (struct (field i32))))
    ;; CHECK:       (type $B (sub $A (struct (field i32) (field f64))))
    (type $B (sub $A (struct (field i32) (field f64))))
  )

  ;; CHECK:       (type $2 (func))

  ;; CHECK:      (func $test (type $2)
  ;; CHECK-NEXT:  (local $x (ref null $B))
  ;; CHECK-NEXT:  (local.set $x
  ;; CHECK-NEXT:   (struct.new $B
  ;; CHECK-NEXT:    (i32.const 42)
  ;; CHECK-NEXT:    (f64.const 3.14159)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $B 0
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.get $B 1
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $test
    (local $x (ref null $B))
    ;; We can remove everything from $A, but nothing from $B. That $A changes
    ;; entirely, and $B changes not at all, should not cause any errors.
    (local.set $x
      (struct.new $B
        (i32.const 42)
        (f64.const 3.14159)
      )
    )
    (drop
      (struct.get $B 0
        (local.get $x)
      )
    )
    (drop
      (struct.get $B 1
        (local.get $x)
      )
    )
  )
)

;; Public types cannot be optimized. The function type here is public as the
;; function is exported, and so the entire rec group is public, and cannot be
;; modified. We cannot even optimize $child3 which is outside of the rec group,
;; because its parent is inside. However, we can optimize $unrelated which is
;; unrelated to them (and so we can remove the field there).
(module
  (rec
    ;; CHECK:      (rec
    ;; CHECK-NEXT:  (type $parent (sub (struct (field (ref func)))))
    (type $parent (sub (struct (field (ref func)))))
    ;; CHECK:       (type $child1 (sub $parent (struct (field (ref func)))))
    (type $child1 (sub $parent (struct (field (ref func)))))
    ;; CHECK:       (type $child2 (sub $parent (struct (field (ref func)))))
    (type $child2 (sub $parent (struct (field (ref func)))))

    ;; CHECK:       (type $func (func (param (ref $child2))))
    (type $func (func (param $child2 (ref $child2))))
  )

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $unrelated (sub (struct)))

  ;; CHECK:       (type $child3 (sub $parent (struct (field (ref func)))))
  (type $child3 (sub $parent (struct (field (ref func)))))

  (type $unrelated (sub (struct (field (ref func)))))

  ;; CHECK:      (elem declare func $func)

  ;; CHECK:      (export "func" (func $func))

  ;; CHECK:      (func $func (type $func) (param $child2 (ref $child2))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $parent
  ;; CHECK-NEXT:    (ref.func $func)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $child1
  ;; CHECK-NEXT:    (ref.func $func)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $child2
  ;; CHECK-NEXT:    (ref.func $func)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new $child3
  ;; CHECK-NEXT:    (ref.func $func)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (struct.new_default $unrelated)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (export "func") (type $func) (param $child2 (ref $child2))
    ;; Create all the types. Note that the value here is non-nullable, as is the
    ;; field, so if we remove the field by mistake in GTO but leave it during
    ;; TypeUpdater, we'd error (on providing a default value for a non-nullable
    ;; field).
    (drop
      (struct.new $parent
        (ref.func $func)
      )
    )
    (drop
      (struct.new $child1
        (ref.func $func)
      )
    )
    (drop
      (struct.new $child2
        (ref.func $func)
      )
    )
    (drop
      (struct.new $child3
        (ref.func $func)
      )
    )
    ;; We can optimize this one, and no other.
    (drop
      (struct.new $unrelated
        (ref.func $func)
      )
    )
  )
)

;; The type $A is public because it is on an exported global. As a result we
;; cannot remove the unused i32 field from its child or grandchild.
(module
  ;; CHECK:      (type $A (sub (struct (field (mut i32)))))
  (type $A (sub (struct (field (mut i32)))))
  ;; CHECK:      (type $B (sub $A (struct (field (mut i32)))))
  (type $B (sub $A (struct (field (mut i32)))))
  ;; CHECK:      (type $C (sub $B (struct (field (mut i32)))))
  (type $C (sub $B (struct (field (mut i32)))))

  ;; Use $C so it isn't removed trivially, which also keeps $B alive as its
  ;; super.
  ;; CHECK:      (global $global (ref $A) (struct.new_default $C))
  (global $global (ref $A) (struct.new_default $C))

  ;; CHECK:      (export "global" (global $global))
  (export "global" (global $global))
)

;; As above, but now there is an f64 field on $C that can be removed, since it
;; is not on the parents.
(module
  ;; CHECK:      (type $A (sub (struct (field (mut i32)))))
  (type $A (sub (struct (field (mut i32)))))
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $B (sub $A (struct (field (mut i32)))))
  (type $B (sub $A (struct (field (mut i32)))))
  ;; CHECK:       (type $C (sub $B (struct (field (mut i32)))))
  (type $C (sub $B (struct (field (mut i32)) (field (mut f64)))))

  ;; CHECK:      (global $global (ref $A) (struct.new_default $C))
  (global $global (ref $A) (struct.new_default $C))

  ;; CHECK:      (export "global" (global $global))
  (export "global" (global $global))
)

;; As above, but the f64 field is now on $B as well. We can still remove it.
(module
  ;; CHECK:      (type $A (sub (struct (field (mut i32)))))
  (type $A (sub (struct (field (mut i32)))))
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $B (sub $A (struct (field (mut i32)))))
  (type $B (sub $A (struct (field (mut i32)) (field (mut f64)))))
  ;; CHECK:       (type $C (sub $B (struct (field (mut i32)))))
  (type $C (sub $B (struct (field (mut i32)) (field (mut f64)))))

  ;; CHECK:      (global $global (ref $A) (struct.new_default $C))
  (global $global (ref $A) (struct.new_default $C))

  ;; CHECK:      (export "global" (global $global))
  (export "global" (global $global))
)

;; As above, but now $B is public as well. Now we cannot remove the f64.
(module
  ;; CHECK:      (type $A (sub (struct (field (mut i32)))))
  (type $A (sub (struct (field (mut i32)))))
  ;; CHECK:      (type $B (sub $A (struct (field (mut i32)) (field (mut f64)))))
  (type $B (sub $A (struct (field (mut i32)) (field (mut f64)))))
  ;; CHECK:      (type $C (sub $B (struct (field (mut i32)) (field (mut f64)))))
  (type $C (sub $B (struct (field (mut i32)) (field (mut f64)))))

  ;; CHECK:      (global $global (ref $A) (struct.new_default $C))
  (global $global (ref $A) (struct.new_default $C))

  ;; CHECK:      (global $globalB (ref $B) (struct.new_default $C))
  (global $globalB (ref $B) (struct.new_default $C))

  ;; CHECK:      (export "global" (global $global))
  (export "global" (global $global))

  ;; CHECK:      (export "globalB" (global $globalB))
  (export "globalB" (global $globalB))
)

;; Removed atomic sets needs special handling.
(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $A (shared (struct)))
  (type $A (shared (struct (mut i32))))

  ;; CHECK:       (type $1 (func (param (ref $A))))

  ;; CHECK:      (func $sets (type $1) (param $0 (ref $A))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (block (result (ref $A))
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (block (result (ref $A))
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (ref.as_non_null
  ;; CHECK-NEXT:    (block (result (ref $A))
  ;; CHECK-NEXT:     (drop
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.get $0)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $sets (param (ref $A))
    ;; Normal set is optimizable.
    (struct.set $A 0
      (local.get 0)
      (i32.const 1)
    )
    ;; Release set is optimizable without a fence because there is no get to
    ;; synchronize with.
    (struct.atomic.set acqrel $A 0
      (local.get 0)
      (i32.const 1)
    )
    ;; Same with a seqcst set.
    (struct.atomic.set $A 0
      (local.get 0)
      (i32.const 1)
    )
  )
)

;; A struct with a pop, which requires EH fixups to avoid popping in a nested
;; block.
(module
  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (struct))

  ;; CHECK:       (type $1 (func))

  ;; CHECK:       (type $i32 (func (param i32)))
  (type $i32 (func (param i32)))

  (type $struct (struct (field (mut i32))))

  ;; CHECK:      (tag $tag (type $i32) (param i32))
  (tag $tag (type $i32) (param i32))

  ;; CHECK:      (func $func (type $1)
  ;; CHECK-NEXT:  (local $0 i32)
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (try
  ;; CHECK-NEXT:   (do
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (catch $tag
  ;; CHECK-NEXT:    (local.set $1
  ;; CHECK-NEXT:     (pop i32)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (drop
  ;; CHECK-NEXT:     (block (result (ref (exact $struct)))
  ;; CHECK-NEXT:      (local.set $0
  ;; CHECK-NEXT:       (local.get $1)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (struct.new_default $struct)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func
    (try
      (do
      )
      (catch $tag
        (drop
          (struct.new $struct
            (pop i32)
          )
        )
      )
    )
  )
)

(module
  ;; A struct with partial names: only some fields are named. We should still
  ;; update names properly when removing and reordering.

  ;; CHECK:      (rec
  ;; CHECK-NEXT:  (type $struct (sub (struct (field (mut i32)) (field $named (mut i32)))))
  (type $struct (sub (struct (field (mut i32)) (field (mut i32)) (field $named (mut i32)) (field (mut i32)) (field (mut i32)))))

  ;; CHECK:       (type $1 (func (param (ref $struct))))

  ;; CHECK:      (func $func (type $1) (param $x (ref $struct))
  ;; CHECK-NEXT:  (struct.set $struct 0
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (struct.get $struct 0
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (struct.set $struct $named
  ;; CHECK-NEXT:   (local.get $x)
  ;; CHECK-NEXT:   (struct.get $struct $named
  ;; CHECK-NEXT:    (local.get $x)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $func (param $x (ref $struct))
    ;; Use fields 1 and 2.
    (struct.set $struct 1
      (local.get $x)
      (struct.get $struct 1
        (local.get $x)
      )
    )
    (struct.set $struct 2
      (local.get $x)
      (struct.get $struct 2
        (local.get $x)
      )
    )
  )
)

