;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
;; RUN: wasm-opt %s --tuple-optimization -all -S -o - | filecheck %s

(module
  ;; CHECK:      (func $just-set (type $0)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (local.set $1
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $2
  ;; CHECK-NEXT:   (i32.const 2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $just-set
    (local $tuple (tuple i32 i32))
    ;; This tuple local can be optimized into separate locals per element. The
    ;; tuple local itself then has no uses and other passes will remove it.
    (local.set $tuple
      (tuple.make 2
        (i32.const 1)
        (i32.const 2)
      )
    )
  )

  ;; CHECK:      (func $just-get (type $0)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $just-get
    (local $tuple (tuple i32 i32))
    ;; The default value of the tuple elements is used here in the new locals we
    ;; add.
    (drop
      (tuple.extract 2 0
        (local.get $tuple)
      )
    )
    (drop
      (tuple.extract 2 1
        (local.get $tuple)
      )
    )
  )

  ;; CHECK:      (func $just-get-bad (type $1) (result i32 i32)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (tuple.extract 2 0
  ;; CHECK-NEXT:    (local.get $tuple)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (tuple.extract 2 1
  ;; CHECK-NEXT:    (local.get $tuple)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.get $tuple)
  ;; CHECK-NEXT: )
  (func $just-get-bad (result i32 i32)
    (local $tuple (tuple i32 i32))
    (drop
      (tuple.extract 2 0
        (local.get $tuple)
      )
    )
    (drop
      (tuple.extract 2 1
        (local.get $tuple)
      )
    )
    ;; This get is not used by something we can handle (it escapes from the
    ;; function), so we should not try to optimize this tuple.
    (local.get $tuple)
  )

  ;; CHECK:      (func $set-and-gets (type $0)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $1
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $2
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $1)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $set-and-gets
    (local $tuple (tuple i32 i32))
    (local.set $tuple
      (tuple.make 2
        (i32.const 1)
        (i32.const 2)
      )
    )
    (drop
      (tuple.extract 2 0
        (local.get $tuple)
      )
    )
    (drop
      (tuple.extract 2 1
        (local.get $tuple)
      )
    )
    ;; Add another get for more coverage
    (drop
      (tuple.extract 2 0
        (local.get $tuple)
      )
    )
  )

  ;; CHECK:      (func $tee (type $0)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local $tuple2 (tuple i32 i32))
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (local $3 i32)
  ;; CHECK-NEXT:  (local $4 i32)
  ;; CHECK-NEXT:  (local $5 i32)
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (block
  ;; CHECK-NEXT:    (local.set $4
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.set $5
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $2
  ;; CHECK-NEXT:    (local.get $4)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $3
  ;; CHECK-NEXT:    (local.get $5)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $3)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $4)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $5)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $tee
    (local $tuple (tuple i32 i32))
    (local $tuple2 (tuple i32 i32))
    (local.set $tuple
      (local.tee $tuple2
        (tuple.make 2
          (i32.const 1)
          (i32.const 2)
        )
      )
    )
    ;; Read the first tuple.
    (drop
      (tuple.extract 2 0
        (local.get $tuple)
      )
    )
    (drop
      (tuple.extract 2 1
        (local.get $tuple)
      )
    )
    ;; Read the second tuple.
    (drop
      (tuple.extract 2 0
        (local.get $tuple2)
      )
    )
    (drop
      (tuple.extract 2 1
        (local.get $tuple2)
      )
    )
  )

  ;; CHECK:      (func $just-tee (type $0)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (local.set $1
  ;; CHECK-NEXT:      (i32.const 1)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.set $2
  ;; CHECK-NEXT:      (i32.const 2)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.get $1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $just-tee
    (local $tuple (tuple i32 i32))
    (drop
      (tuple.extract 2 0
        (local.tee $tuple
          (tuple.make 2
            (i32.const 1)
            (i32.const 2)
          )
        )
      )
    )
  )

  ;; CHECK:      (func $just-tee-bad (type $1) (result i32 i32)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local.tee $tuple
  ;; CHECK-NEXT:   (tuple.make 2
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $just-tee-bad (result i32 i32)
    (local $tuple (tuple i32 i32))
    ;; This tee goes somewhere we cannot handle, so we do not optimize here.
    (local.tee $tuple
      (tuple.make 2
        (i32.const 1)
        (i32.const 2)
      )
    )
  )

  ;; CHECK:      (func $no-uses (type $0)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local $tuple2 (tuple i32 i32))
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (local $3 i32)
  ;; CHECK-NEXT:  (local $4 i32)
  ;; CHECK-NEXT:  (local $5 i32)
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $4
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $5
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $2
  ;; CHECK-NEXT:   (local.get $4)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $3
  ;; CHECK-NEXT:   (local.get $5)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $no-uses
    (local $tuple (tuple i32 i32))
    (local $tuple2 (tuple i32 i32))
    ;; The set has no uses, and the tee only has an immediate use. We can
    ;; still optimize both.
    (local.set $tuple
      (local.tee $tuple2
        (tuple.make 2
          (i32.const 1)
          (i32.const 2)
        )
      )
    )
  )

  ;; CHECK:      (func $corruption-tee (type $1) (result i32 i32)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local $tuple2 (tuple i32 i32))
  ;; CHECK-NEXT:  (local.set $tuple
  ;; CHECK-NEXT:   (local.tee $tuple2
  ;; CHECK-NEXT:    (tuple.make 2
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.get $tuple2)
  ;; CHECK-NEXT: )
  (func $corruption-tee (result i32 i32)
    (local $tuple (tuple i32 i32))
    (local $tuple2 (tuple i32 i32))
    ;; As above, but the tee's tuple is bad and it prevents the other from
    ;; being optimized too, due to the copy between them.
    (local.set $tuple
      (local.tee $tuple2
        (tuple.make 2
          (i32.const 1)
          (i32.const 2)
        )
      )
    )
    (local.get $tuple2)
  )

  ;; CHECK:      (func $corruption-set (type $1) (result i32 i32)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local $tuple2 (tuple i32 i32))
  ;; CHECK-NEXT:  (local.set $tuple
  ;; CHECK-NEXT:   (local.tee $tuple2
  ;; CHECK-NEXT:    (tuple.make 2
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.get $tuple)
  ;; CHECK-NEXT: )
  (func $corruption-set (result i32 i32)
    (local $tuple (tuple i32 i32))
    (local $tuple2 (tuple i32 i32))
    ;; As above, but now the set is bad.
    (local.set $tuple
      (local.tee $tuple2
        (tuple.make 2
          (i32.const 1)
          (i32.const 2)
        )
      )
    )
    (local.get $tuple) ;; this changed from $tuple2; same outcome.
  )

  ;; CHECK:      (func $set-after-set (type $0)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local $tuple2 (tuple i32 i32))
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (local $3 i32)
  ;; CHECK-NEXT:  (local $4 i32)
  ;; CHECK-NEXT:  (local $5 i32)
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $2
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $3
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $4
  ;; CHECK-NEXT:    (local.get $2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $5
  ;; CHECK-NEXT:    (local.get $3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $2
  ;; CHECK-NEXT:    (local.get $2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $3
  ;; CHECK-NEXT:    (local.get $3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $set-after-set
    (local $tuple (tuple i32 i32))
    (local $tuple2 (tuple i32 i32))
    ;; We can optimize both these tuples.
    (local.set $tuple
      (tuple.make 2
        (i32.const 1)
        (i32.const 2)
      )
    )
    (local.set $tuple2
      (local.get $tuple)
    )
    ;; Add a copy of a tuple to itself for extra coverage.
    (local.set $tuple
      (local.get $tuple)
    )
  )

  ;; CHECK:      (func $corruption-first-set (type $1) (result i32 i32)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local $tuple2 (tuple i32 i32))
  ;; CHECK-NEXT:  (local.set $tuple
  ;; CHECK-NEXT:   (tuple.make 2
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $tuple2
  ;; CHECK-NEXT:   (local.get $tuple)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.get $tuple)
  ;; CHECK-NEXT: )
  (func $corruption-first-set (result i32 i32)
    (local $tuple (tuple i32 i32))
    (local $tuple2 (tuple i32 i32))
    (local.set $tuple
      (tuple.make 2
        (i32.const 1)
        (i32.const 2)
      )
    )
    (local.set $tuple2
      (local.get $tuple)
    )
    ;; This local.get prevents both locals from being optimized.
    (local.get $tuple)
  )

  ;; CHECK:      (func $corruption-second-set (type $1) (result i32 i32)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local $tuple2 (tuple i32 i32))
  ;; CHECK-NEXT:  (local.set $tuple
  ;; CHECK-NEXT:   (tuple.make 2
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $tuple2
  ;; CHECK-NEXT:   (local.get $tuple)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.get $tuple2)
  ;; CHECK-NEXT: )
  (func $corruption-second-set (result i32 i32)
    (local $tuple (tuple i32 i32))
    (local $tuple2 (tuple i32 i32))
    (local.set $tuple
      (tuple.make 2
        (i32.const 1)
        (i32.const 2)
      )
    )
    (local.set $tuple2
      (local.get $tuple)
    )
    (local.get $tuple2) ;; this changed from $tuple; same outcome.
  )

  ;; CHECK:      (func $other (type $0)
  ;; CHECK-NEXT:  (local $other f64)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local.set $other
  ;; CHECK-NEXT:   (local.tee $other
  ;; CHECK-NEXT:    (local.get $other)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $other
    ;; A non-tuple local and all operations on it should be ignored.
    (local $other f64)
    ;; A tuple local with no uses at all should be ignored.
    (local $tuple (tuple i32 i32))
    (local.set $other
      (local.tee $other
        (local.get $other)
      )
    )
  )

  ;; CHECK:      (func $make-extract-no-local (type $0)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (tuple.extract 2 0
  ;; CHECK-NEXT:    (tuple.make 2
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $make-extract-no-local
    ;; Tuple operations without locals. We do nothing here; other passes can
    ;; help on this kind of thing.
    (drop
      (tuple.extract 2 0
        (tuple.make 2
          (i32.const 1)
          (i32.const 2)
        )
      )
    )
  )

  ;; CHECK:      (func $make-extract-no-local-but-other (type $0)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local $1 i32)
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $1
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $2
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (tuple.extract 2 0
  ;; CHECK-NEXT:    (tuple.make 2
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $make-extract-no-local-but-other
    (local $tuple (tuple i32 i32))
    (local.set $tuple
      (tuple.make 2
        (i32.const 1)
        (i32.const 2)
      )
    )
    ;; The code below is as in the previous testcase, but now before us there
    ;; is an unrelated local that can be optimized. We should remain as before.
    (drop
      (tuple.extract 2 0
        (tuple.make 2
          (i32.const 1)
          (i32.const 2)
        )
      )
    )
  )

  ;; CHECK:      (func $set-of-block (type $0)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local.set $tuple
  ;; CHECK-NEXT:   (block (type $1) (result i32 i32)
  ;; CHECK-NEXT:    (tuple.make 2
  ;; CHECK-NEXT:     (i32.const 1)
  ;; CHECK-NEXT:     (i32.const 2)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $set-of-block
    (local $tuple (tuple i32 i32))
    ;; We do not handle blocks yet, so this is not optimized.
    (local.set $tuple
      (block (result i32 i32)
        (tuple.make 2
          (i32.const 1)
          (i32.const 2)
        )
      )
    )
  )

  ;; CHECK:      (func $unreachability (type $0)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local $nontuple f64)
  ;; CHECK-NEXT:  (local.set $tuple
  ;; CHECK-NEXT:   (tuple.make 2
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.tee $tuple
  ;; CHECK-NEXT:   (unreachable)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (tuple.extract 2 0
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (tuple.extract 2 1
  ;; CHECK-NEXT:    (local.tee $tuple
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.tee $tuple
  ;; CHECK-NEXT:    (local.tee $nontuple
  ;; CHECK-NEXT:     (unreachable)
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreachability
    (local $tuple (tuple i32 i32))
    (local $nontuple f64)
    (local.set $tuple
      (tuple.make 2
        (i32.const 1)
        (i32.const 2)
      )
    )
    ;; We should not error here, and do nothing.
    (local.set $tuple
      (unreachable)
    )
    (drop
      (tuple.extract 2 0
        (unreachable)
      )
    )
    (drop
      (tuple.extract 2 1
        (local.tee $tuple
          (unreachable)
        )
      )
    )
    ;; Teeing a nontuple into a tuple is allowed in unreachable code (the input
    ;; to the outer tee is simply unreachable, so there is no checking that we
    ;; are copying a local into another local of a compatible type). We should
    ;; not error here.
    (drop
      (local.tee $tuple
        (local.tee $nontuple
          (unreachable)
        )
      )
    )
  )

  ;; CHECK:      (func $tee-chain (type $0)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local $tuple2 (tuple i32 i32))
  ;; CHECK-NEXT:  (local $tuple3 (tuple i32 i32))
  ;; CHECK-NEXT:  (local $3 i32)
  ;; CHECK-NEXT:  (local $4 i32)
  ;; CHECK-NEXT:  (local $5 i32)
  ;; CHECK-NEXT:  (local $6 i32)
  ;; CHECK-NEXT:  (local $7 i32)
  ;; CHECK-NEXT:  (local $8 i32)
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (block (result i32)
  ;; CHECK-NEXT:    (block
  ;; CHECK-NEXT:     (block
  ;; CHECK-NEXT:      (block
  ;; CHECK-NEXT:       (local.set $7
  ;; CHECK-NEXT:        (i32.const 1)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:       (local.set $8
  ;; CHECK-NEXT:        (i32.const 2)
  ;; CHECK-NEXT:       )
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (local.set $5
  ;; CHECK-NEXT:       (local.get $7)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:      (local.set $6
  ;; CHECK-NEXT:       (local.get $8)
  ;; CHECK-NEXT:      )
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.set $3
  ;; CHECK-NEXT:      (local.get $5)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:     (local.set $4
  ;; CHECK-NEXT:      (local.get $6)
  ;; CHECK-NEXT:     )
  ;; CHECK-NEXT:    )
  ;; CHECK-NEXT:    (local.get $3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $tee-chain
    (local $tuple (tuple i32 i32))
    (local $tuple2 (tuple i32 i32))
    (local $tuple3 (tuple i32 i32))
    (drop
      (tuple.extract 2 0
        (local.tee $tuple
          (local.tee $tuple2
            (local.tee $tuple3
              (tuple.make 2
                (i32.const 1)
                (i32.const 2)
              )
            )
          )
        )
      )
    )
  )

  ;; CHECK:      (func $chain-3 (type $0)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32 i32))
  ;; CHECK-NEXT:  (local $tuple2 (tuple i32 i32 i32))
  ;; CHECK-NEXT:  (local $tuple3 (tuple i32 i32 i32))
  ;; CHECK-NEXT:  (local $3 i32)
  ;; CHECK-NEXT:  (local $4 i32)
  ;; CHECK-NEXT:  (local $5 i32)
  ;; CHECK-NEXT:  (local $6 i32)
  ;; CHECK-NEXT:  (local $7 i32)
  ;; CHECK-NEXT:  (local $8 i32)
  ;; CHECK-NEXT:  (local $9 i32)
  ;; CHECK-NEXT:  (local $10 i32)
  ;; CHECK-NEXT:  (local $11 i32)
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $3
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $4
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $5
  ;; CHECK-NEXT:    (i32.const 3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $6
  ;; CHECK-NEXT:    (local.get $3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $7
  ;; CHECK-NEXT:    (local.get $4)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $8
  ;; CHECK-NEXT:    (local.get $5)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $9
  ;; CHECK-NEXT:    (local.get $6)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $10
  ;; CHECK-NEXT:    (local.get $7)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $11
  ;; CHECK-NEXT:    (local.get $8)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $3)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $7)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $11)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $chain-3
    (local $tuple (tuple i32 i32 i32))
    (local $tuple2 (tuple i32 i32 i32))
    (local $tuple3 (tuple i32 i32 i32))
    ;; A chain of 3 copied tuples.
    (local.set $tuple
      (tuple.make 3
        (i32.const 1)
        (i32.const 2)
        (i32.const 3)
      )
    )
    (local.set $tuple2
      (local.get $tuple)
    )
    (local.set $tuple3
      (local.get $tuple2)
    )
    ;; Read from each.
    (drop
      (tuple.extract 3 0
        (local.get $tuple)
      )
    )
    (drop
      (tuple.extract 3 1
        (local.get $tuple2)
      )
    )
    (drop
      (tuple.extract 3 2
        (local.get $tuple3)
      )
    )
  )

  ;; CHECK:      (func $chain-3-corruption (type $2) (result i32 i32 i32)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32 i32))
  ;; CHECK-NEXT:  (local $tuple2 (tuple i32 i32 i32))
  ;; CHECK-NEXT:  (local $tuple3 (tuple i32 i32 i32))
  ;; CHECK-NEXT:  (local.set $tuple
  ;; CHECK-NEXT:   (tuple.make 3
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:    (i32.const 3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $tuple2
  ;; CHECK-NEXT:   (local.get $tuple)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $tuple3
  ;; CHECK-NEXT:   (local.get $tuple2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (tuple.extract 3 0
  ;; CHECK-NEXT:    (local.get $tuple)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (tuple.extract 3 1
  ;; CHECK-NEXT:    (local.get $tuple2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (tuple.extract 3 2
  ;; CHECK-NEXT:    (local.get $tuple3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.get $tuple)
  ;; CHECK-NEXT: )
  (func $chain-3-corruption (result i32 i32 i32)
    (local $tuple (tuple i32 i32 i32))
    (local $tuple2 (tuple i32 i32 i32))
    (local $tuple3 (tuple i32 i32 i32))
    ;; As above, but a get at the very end prevents the entire chain from being
    ;; optimized.
    (local.set $tuple
      (tuple.make 3
        (i32.const 1)
        (i32.const 2)
        (i32.const 3)
      )
    )
    (local.set $tuple2
      (local.get $tuple)
    )
    (local.set $tuple3
      (local.get $tuple2)
    )
    ;; Read from each.
    (drop
      (tuple.extract 3 0
        (local.get $tuple)
      )
    )
    (drop
      (tuple.extract 3 1
        (local.get $tuple2)
      )
    )
    (drop
      (tuple.extract 3 2
        (local.get $tuple3)
      )
    )
    (local.get $tuple) ;; this was added
  )

  ;; CHECK:      (func $set-call (type $1) (result i32 i32)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i32))
  ;; CHECK-NEXT:  (local.set $tuple
  ;; CHECK-NEXT:   (call $set-call)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (tuple.extract 2 0
  ;; CHECK-NEXT:    (local.get $tuple)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (tuple.make 2
  ;; CHECK-NEXT:   (i32.const 1)
  ;; CHECK-NEXT:   (i32.const 2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $set-call (result i32 i32)
    (local $tuple (tuple i32 i32))
    ;; Setting from a call prevents optimization.
    (local.set $tuple
      (call $set-call)
    )
    (drop
      (tuple.extract 2 0
        (local.get $tuple)
      )
    )
    (tuple.make 2
      (i32.const 1)
      (i32.const 2)
    )
  )

  ;; CHECK:      (func $two-2-three (type $0)
  ;; CHECK-NEXT:  (local $tuple2 (tuple i32 i32))
  ;; CHECK-NEXT:  (local $tuple3 (tuple i32 i32 i32))
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (local $3 i32)
  ;; CHECK-NEXT:  (local $4 i32)
  ;; CHECK-NEXT:  (local $5 i32)
  ;; CHECK-NEXT:  (local $6 i32)
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $2
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $3
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $4
  ;; CHECK-NEXT:    (local.get $2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $5
  ;; CHECK-NEXT:    (local.get $3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $6
  ;; CHECK-NEXT:    (i32.const 3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $3)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $6)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $two-2-three
    (local $tuple2 (tuple i32 i32))
    (local $tuple3 (tuple i32 i32 i32))
    (local.set $tuple2
      (tuple.make 2
        (i32.const 1)
        (i32.const 2)
      )
    )
    (local.set $tuple3
      (tuple.make 3
        (tuple.extract 2 0
          (local.get $tuple2)
        )
        (tuple.extract 2 1
          (local.get $tuple2)
        )
        (i32.const 3)
      )
    )
    ;; Read from each.
    (drop
      (tuple.extract 2 1
        (local.get $tuple2)
      )
    )
    (drop
      (tuple.extract 3 2
        (local.get $tuple3)
      )
    )
  )

  ;; CHECK:      (func $three-2-two (type $0)
  ;; CHECK-NEXT:  (local $tuple2 (tuple i32 i32))
  ;; CHECK-NEXT:  (local $tuple3 (tuple i32 i32 i32))
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (local $3 i32)
  ;; CHECK-NEXT:  (local $4 i32)
  ;; CHECK-NEXT:  (local $5 i32)
  ;; CHECK-NEXT:  (local $6 i32)
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $4
  ;; CHECK-NEXT:    (i32.const 1)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $5
  ;; CHECK-NEXT:    (i32.const 2)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $6
  ;; CHECK-NEXT:    (i32.const 3)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $2
  ;; CHECK-NEXT:    (local.get $4)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $3
  ;; CHECK-NEXT:    (local.get $5)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $3)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (drop
  ;; CHECK-NEXT:   (local.get $6)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $three-2-two
    (local $tuple2 (tuple i32 i32))
    (local $tuple3 (tuple i32 i32 i32))
    (local.set $tuple3
      (tuple.make 3
        (i32.const 1)
        (i32.const 2)
        (i32.const 3)
      )
    )
    (local.set $tuple2
      (tuple.make 2
        (tuple.extract 3 0
          (local.get $tuple3)
        )
        (tuple.extract 3 1
          (local.get $tuple3)
        )
      )
    )
    ;; Read from each.
    (drop
      (tuple.extract 2 1
        (local.get $tuple2)
      )
    )
    (drop
      (tuple.extract 3 2
        (local.get $tuple3)
      )
    )
  )

  ;; CHECK:      (func $tuple.element.subtyping (type $0)
  ;; CHECK-NEXT:  (local $tuple_null (tuple i32 nullref))
  ;; CHECK-NEXT:  (local $tuple_eq (tuple i32 eqref))
  ;; CHECK-NEXT:  (local $2 i32)
  ;; CHECK-NEXT:  (local $3 nullref)
  ;; CHECK-NEXT:  (local $4 i32)
  ;; CHECK-NEXT:  (local $5 eqref)
  ;; CHECK-NEXT:  (block
  ;; CHECK-NEXT:   (local.set $2
  ;; CHECK-NEXT:    (i32.const 0)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:   (local.set $3
  ;; CHECK-NEXT:    (ref.null none)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $4
  ;; CHECK-NEXT:   (local.get $2)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT:  (local.set $5
  ;; CHECK-NEXT:   (local.get $3)
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $tuple.element.subtyping
    (local $tuple_null (tuple i32 nullref))
    (local $tuple_eq (tuple i32 eqref))
    ;; The tee emits a nullref in the second element, which is written to an
    ;; element of eqref. That is, the source and the target do not have
    ;; identical type, which we need to properly handle and not error.
    (local.set $tuple_eq
      (local.tee $tuple_null
        (tuple.make 2
          (i32.const 0)
          (ref.null none)
        )
      )
    )
  )

  ;; CHECK:      (func $unreachable.tuple.extract (type $3) (result i32)
  ;; CHECK-NEXT:  (local $tuple (tuple i32 i64))
  ;; CHECK-NEXT:  (local $non-tuple i32)
  ;; CHECK-NEXT:  (tuple.extract 2 0
  ;; CHECK-NEXT:   (local.tee $non-tuple
  ;; CHECK-NEXT:    (unreachable)
  ;; CHECK-NEXT:   )
  ;; CHECK-NEXT:  )
  ;; CHECK-NEXT: )
  (func $unreachable.tuple.extract (result i32)
    (local $tuple (tuple i32 i64))
    (local $non-tuple i32)
    (tuple.extract 2 0
      (local.tee $non-tuple
        (unreachable)
      )
    )
  )
)
