[llvm] 5286180 - [WebAssembly] Fix RegStackify and ExplicitLocals to handle multivalue

Thomas Lively via llvm-commits llvm-commits at lists.llvm.org
Tue Feb 18 14:56:16 PST 2020


Author: Thomas Lively
Date: 2020-02-18T14:56:09-08:00
New Revision: 52861809994c9199ceb45b98d982ab736a376e67

URL: https://github.com/llvm/llvm-project/commit/52861809994c9199ceb45b98d982ab736a376e67
DIFF: https://github.com/llvm/llvm-project/commit/52861809994c9199ceb45b98d982ab736a376e67.diff

LOG: [WebAssembly] Fix RegStackify and ExplicitLocals to handle multivalue

Summary:
There is still room for improvement in the handling of multivalue
nodes in both passes, but the current algorithm is at least correct
and optimizes some simpler cases. In order to make future
optimizations of these passes easier and build confidence that the
current algorithms are correct, this CL also adds a script that
automatically and exhaustively generates interesting multivalue test
cases.

Reviewers: aheejin, dschuff

Subscribers: sbc100, jgravelle-google, hiraditya, sunfish, llvm-commits

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D72902

Added: 
    llvm/test/CodeGen/WebAssembly/multivalue-stackify.ll
    llvm/test/CodeGen/WebAssembly/multivalue-stackify.py

Modified: 
    llvm/lib/Target/WebAssembly/WebAssemblyExplicitLocals.cpp
    llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp
    llvm/test/CodeGen/WebAssembly/multivalue.ll

Removed: 
    


################################################################################
diff  --git a/llvm/lib/Target/WebAssembly/WebAssemblyExplicitLocals.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyExplicitLocals.cpp
index 6ca711394380..4da97b6e7393 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyExplicitLocals.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyExplicitLocals.cpp
@@ -178,11 +178,18 @@ static MVT typeForRegClass(const TargetRegisterClass *RC) {
 /// start of the expression tree.
 static MachineInstr *findStartOfTree(MachineOperand &MO,
                                      MachineRegisterInfo &MRI,
-                                     WebAssemblyFunctionInfo &MFI) {
+                                     const WebAssemblyFunctionInfo &MFI) {
   Register Reg = MO.getReg();
   assert(MFI.isVRegStackified(Reg));
   MachineInstr *Def = MRI.getVRegDef(Reg);
 
+  // If this instruction has any non-stackified defs, it is the start
+  for (auto DefReg : Def->defs()) {
+    if (!MFI.isVRegStackified(DefReg.getReg())) {
+      return Def;
+    }
+  }
+
   // Find the first stackified use and proceed from there.
   for (MachineOperand &DefMO : Def->explicit_uses()) {
     if (!DefMO.isReg())
@@ -243,6 +250,12 @@ bool WebAssemblyExplicitLocals::runOnMachineFunction(MachineFunction &MF) {
       if (MI.isDebugInstr() || MI.isLabel())
         continue;
 
+      if (MI.getOpcode() == WebAssembly::IMPLICIT_DEF) {
+        MI.eraseFromParent();
+        Changed = true;
+        continue;
+      }
+
       // Replace tee instructions with local.tee. The 
diff erence is that tee
       // instructions have two defs, while local.tee instructions have one def
       // and an index of a local to write to.
@@ -279,20 +292,13 @@ bool WebAssemblyExplicitLocals::runOnMachineFunction(MachineFunction &MF) {
         continue;
       }
 
-      // Insert local.sets for any defs that aren't stackified yet. Currently
-      // we handle at most one def.
-      assert(MI.getDesc().getNumDefs() <= 1);
-      if (MI.getDesc().getNumDefs() == 1) {
-        Register OldReg = MI.getOperand(0).getReg();
+      // Insert local.sets for any defs that aren't stackified yet.
+      for (auto &Def : MI.defs()) {
+        Register OldReg = Def.getReg();
         if (!MFI.isVRegStackified(OldReg)) {
           const TargetRegisterClass *RC = MRI.getRegClass(OldReg);
           Register NewReg = MRI.createVirtualRegister(RC);
           auto InsertPt = std::next(MI.getIterator());
-          if (MI.getOpcode() == WebAssembly::IMPLICIT_DEF) {
-            MI.eraseFromParent();
-            Changed = true;
-            continue;
-          }
           if (UseEmpty[Register::virtReg2Index(OldReg)]) {
             unsigned Opc = getDropOpcode(RC);
             MachineInstr *Drop =
@@ -310,11 +316,11 @@ bool WebAssemblyExplicitLocals::runOnMachineFunction(MachineFunction &MF) {
                 .addImm(LocalId)
                 .addReg(NewReg);
           }
-          MI.getOperand(0).setReg(NewReg);
           // This register operand of the original instruction is now being used
           // by the inserted drop or local.set instruction, so make it not dead
           // yet.
-          MI.getOperand(0).setIsDead(false);
+          Def.setReg(NewReg);
+          Def.setIsDead(false);
           MFI.stackifyVReg(NewReg);
           Changed = true;
         }

diff  --git a/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp b/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp
index e49034467f73..ad4a95ccfc4a 100644
--- a/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp
+++ b/llvm/lib/Target/WebAssembly/WebAssemblyRegStackify.cpp
@@ -36,6 +36,7 @@
 #include "llvm/CodeGen/Passes.h"
 #include "llvm/Support/Debug.h"
 #include "llvm/Support/raw_ostream.h"
+#include <iterator>
 using namespace llvm;
 
 #define DEBUG_TYPE "wasm-reg-stackify"
@@ -120,6 +121,7 @@ static void convertImplicitDefToConstZero(MachineInstr *MI,
         Type::getDoubleTy(MF.getFunction().getContext())));
     MI->addOperand(MachineOperand::CreateFPImm(Val));
   } else if (RegClass == &WebAssembly::V128RegClass) {
+    // TODO: Replace this with v128.const 0 once that is supported in V8
     Register TempReg = MRI.createVirtualRegister(&WebAssembly::I32RegClass);
     MI->setDesc(TII->get(WebAssembly::SPLAT_v4i32));
     MI->addOperand(MachineOperand::CreateReg(TempReg, false));
@@ -312,25 +314,59 @@ static bool hasOneUse(unsigned Reg, MachineInstr *Def, MachineRegisterInfo &MRI,
 // walking the block.
 // TODO: Compute memory dependencies in a way that uses AliasAnalysis to be
 // more precise.
-static bool isSafeToMove(const MachineInstr *Def, const MachineInstr *Insert,
-                         AliasAnalysis &AA, const MachineRegisterInfo &MRI) {
-  assert(Def->getParent() == Insert->getParent());
+static bool isSafeToMove(const MachineOperand *Def, const MachineOperand *Use,
+                         const MachineInstr *Insert, AliasAnalysis &AA,
+                         const WebAssemblyFunctionInfo &MFI,
+                         const MachineRegisterInfo &MRI) {
+  const MachineInstr *DefI = Def->getParent();
+  const MachineInstr *UseI = Use->getParent();
+  assert(DefI->getParent() == Insert->getParent());
+  assert(UseI->getParent() == Insert->getParent());
+
+  // The first def of a multivalue instruction can be stackified by moving,
+  // since the later defs can always be placed into locals if necessary. Later
+  // defs can only be stackified if all previous defs are already stackified
+  // since ExplicitLocals will not know how to place a def in a local if a
+  // subsequent def is stackified. But only one def can be stackified by moving
+  // the instruction, so it must be the first one.
+  //
+  // TODO: This could be loosened to be the first *live* def, but care would
+  // have to be taken to ensure the drops of the initial dead defs can be
+  // placed. This would require checking that no previous defs are used in the
+  // same instruction as subsequent defs.
+  if (Def != DefI->defs().begin())
+    return false;
+
+  // If any subsequent def is used prior to the current value by the same
+  // instruction in which the current value is used, we cannot
+  // stackify. Stackifying in this case would require that def moving below the
+  // current def in the stack, which cannot be achieved, even with locals.
+  for (const auto &SubsequentDef : drop_begin(DefI->defs(), 1)) {
+    for (const auto &PriorUse : UseI->uses()) {
+      if (&PriorUse == Use)
+        break;
+      if (PriorUse.isReg() && SubsequentDef.getReg() == PriorUse.getReg())
+        return false;
+    }
+  }
+
+  // If moving is a semantic nop, it is always allowed
+  const MachineBasicBlock *MBB = DefI->getParent();
+  auto NextI = std::next(MachineBasicBlock::const_iterator(DefI));
+  for (auto E = MBB->end(); NextI != E && NextI->isDebugInstr(); ++NextI)
+    ;
+  if (NextI == Insert)
+    return true;
 
   // 'catch' and 'extract_exception' should be the first instruction of a BB and
   // cannot move.
-  if (Def->getOpcode() == WebAssembly::CATCH ||
-      Def->getOpcode() == WebAssembly::EXTRACT_EXCEPTION_I32) {
-    const MachineBasicBlock *MBB = Def->getParent();
-    auto NextI = std::next(MachineBasicBlock::const_iterator(Def));
-    for (auto E = MBB->end(); NextI != E && NextI->isDebugInstr(); ++NextI)
-      ;
-    if (NextI != Insert)
-      return false;
-  }
+  if (DefI->getOpcode() == WebAssembly::CATCH ||
+      DefI->getOpcode() == WebAssembly::EXTRACT_EXCEPTION_I32)
+    return false;
 
   // Check for register dependencies.
   SmallVector<unsigned, 4> MutableRegisters;
-  for (const MachineOperand &MO : Def->operands()) {
+  for (const MachineOperand &MO : DefI->operands()) {
     if (!MO.isReg() || MO.isUndef())
       continue;
     Register Reg = MO.getReg();
@@ -360,7 +396,7 @@ static bool isSafeToMove(const MachineInstr *Def, const MachineInstr *Insert,
   }
 
   bool Read = false, Write = false, Effects = false, StackPointer = false;
-  query(*Def, AA, Read, Write, Effects, StackPointer);
+  query(*DefI, AA, Read, Write, Effects, StackPointer);
 
   // If the instruction does not access memory and has no side effects, it has
   // no additional dependencies.
@@ -368,8 +404,8 @@ static bool isSafeToMove(const MachineInstr *Def, const MachineInstr *Insert,
   if (!Read && !Write && !Effects && !StackPointer && !HasMutableRegisters)
     return true;
 
-  // Scan through the intervening instructions between Def and Insert.
-  MachineBasicBlock::const_iterator D(Def), I(Insert);
+  // Scan through the intervening instructions between DefI and Insert.
+  MachineBasicBlock::const_iterator D(DefI), I(Insert);
   for (--I; I != D; --I) {
     bool InterveningRead = false;
     bool InterveningWrite = false;
@@ -800,32 +836,32 @@ bool WebAssemblyRegStackify::runOnMachineFunction(MachineFunction &MF) {
       CommutingState Commuting;
       TreeWalkerState TreeWalker(Insert);
       while (!TreeWalker.done()) {
-        MachineOperand &Op = TreeWalker.pop();
+        MachineOperand &Use = TreeWalker.pop();
 
         // We're only interested in explicit virtual register operands.
-        if (!Op.isReg())
+        if (!Use.isReg())
           continue;
 
-        Register Reg = Op.getReg();
-        assert(Op.isUse() && "explicit_uses() should only iterate over uses");
-        assert(!Op.isImplicit() &&
+        Register Reg = Use.getReg();
+        assert(Use.isUse() && "explicit_uses() should only iterate over uses");
+        assert(!Use.isImplicit() &&
                "explicit_uses() should only iterate over explicit operands");
         if (Register::isPhysicalRegister(Reg))
           continue;
 
         // Identify the definition for this register at this point.
-        MachineInstr *Def = getVRegDef(Reg, Insert, MRI, LIS);
-        if (!Def)
+        MachineInstr *DefI = getVRegDef(Reg, Insert, MRI, LIS);
+        if (!DefI)
           continue;
 
         // Don't nest an INLINE_ASM def into anything, because we don't have
         // constraints for $pop outputs.
-        if (Def->isInlineAsm())
+        if (DefI->isInlineAsm())
           continue;
 
         // Argument instructions represent live-in registers and not real
         // instructions.
-        if (WebAssembly::isArgument(Def->getOpcode()))
+        if (WebAssembly::isArgument(DefI->getOpcode()))
           continue;
 
         // Currently catch's return value register cannot be stackified, because
@@ -842,34 +878,38 @@ bool WebAssemblyRegStackify::runOnMachineFunction(MachineFunction &MF) {
         // register should be assigned to a local to be propagated across
         // 'block' boundary now.
         //
-        // TODO Fix this once we support the multi-value proposal.
-        if (Def->getOpcode() == WebAssembly::CATCH)
+        // TODO: Fix this once we support the multivalue blocks
+        if (DefI->getOpcode() == WebAssembly::CATCH)
           continue;
 
+        MachineOperand *Def = DefI->findRegisterDefOperand(Reg);
+        assert(Def != nullptr);
+
         // Decide which strategy to take. Prefer to move a single-use value
         // over cloning it, and prefer cloning over introducing a tee.
         // For moving, we require the def to be in the same block as the use;
         // this makes things simpler (LiveIntervals' handleMove function only
         // supports intra-block moves) and it's MachineSink's job to catch all
         // the sinking opportunities anyway.
-        bool SameBlock = Def->getParent() == &MBB;
-        bool CanMove = SameBlock && isSafeToMove(Def, Insert, AA, MRI) &&
+        bool SameBlock = DefI->getParent() == &MBB;
+        bool CanMove = SameBlock &&
+                       isSafeToMove(Def, &Use, Insert, AA, MFI, MRI) &&
                        !TreeWalker.isOnStack(Reg);
-        if (CanMove && hasOneUse(Reg, Def, MRI, MDT, LIS)) {
-          Insert = moveForSingleUse(Reg, Op, Def, MBB, Insert, LIS, MFI, MRI);
+        if (CanMove && hasOneUse(Reg, DefI, MRI, MDT, LIS)) {
+          Insert = moveForSingleUse(Reg, Use, DefI, MBB, Insert, LIS, MFI, MRI);
 
           // If we are removing the frame base reg completely, remove the debug
           // info as well.
           // TODO: Encode this properly as a stackified value.
           if (MFI.isFrameBaseVirtual() && MFI.getFrameBaseVreg() == Reg)
             MFI.clearFrameBaseVreg();
-        } else if (shouldRematerialize(*Def, AA, TII)) {
+        } else if (shouldRematerialize(*DefI, AA, TII)) {
           Insert =
-              rematerializeCheapDef(Reg, Op, *Def, MBB, Insert->getIterator(),
+              rematerializeCheapDef(Reg, Use, *DefI, MBB, Insert->getIterator(),
                                     LIS, MFI, MRI, TII, TRI);
-        } else if (CanMove &&
-                   oneUseDominatesOtherUses(Reg, Op, MBB, MRI, MDT, LIS, MFI)) {
-          Insert = moveAndTeeForMultiUse(Reg, Op, Def, MBB, Insert, LIS, MFI,
+        } else if (CanMove && oneUseDominatesOtherUses(Reg, Use, MBB, MRI, MDT,
+                                                       LIS, MFI)) {
+          Insert = moveAndTeeForMultiUse(Reg, Use, DefI, MBB, Insert, LIS, MFI,
                                          MRI, TII);
         } else {
           // We failed to stackify the operand. If the problem was ordering
@@ -880,6 +920,25 @@ bool WebAssemblyRegStackify::runOnMachineFunction(MachineFunction &MF) {
           continue;
         }
 
+        // Stackifying a multivalue def may unlock in-place stackification of
+        // subsequent defs. TODO: Handle the case where the consecutive uses are
+        // not all in the same instruction.
+        auto *SubsequentDef = DefI->defs().begin();
+        auto *SubsequentUse = &Use;
+        while (SubsequentDef != DefI->defs().end() &&
+               SubsequentUse != Use.getParent()->uses().end()) {
+          if (!SubsequentDef->isReg() || !SubsequentUse->isReg())
+            break;
+          unsigned DefReg = SubsequentDef->getReg();
+          unsigned UseReg = SubsequentUse->getReg();
+          // TODO: This single-use restriction could be relaxed by using tees
+          if (DefReg != UseReg || !MRI.hasOneUse(DefReg))
+            break;
+          MFI.stackifyVReg(DefReg);
+          ++SubsequentDef;
+          ++SubsequentUse;
+        }
+
         // If the instruction we just stackified is an IMPLICIT_DEF, convert it
         // to a constant 0 so that the def is explicit, and the push/pop
         // correspondence is maintained.
@@ -917,18 +976,20 @@ bool WebAssemblyRegStackify::runOnMachineFunction(MachineFunction &MF) {
     for (MachineInstr &MI : MBB) {
       if (MI.isDebugInstr())
         continue;
-      for (MachineOperand &MO : reverse(MI.explicit_operands())) {
+      for (MachineOperand &MO : reverse(MI.explicit_uses())) {
         if (!MO.isReg())
           continue;
         Register Reg = MO.getReg();
-
-        if (MFI.isVRegStackified(Reg)) {
-          if (MO.isDef())
-            Stack.push_back(Reg);
-          else
-            assert(Stack.pop_back_val() == Reg &&
-                   "Register stack pop should be paired with a push");
-        }
+        if (MFI.isVRegStackified(Reg))
+          assert(Stack.pop_back_val() == Reg &&
+                 "Register stack pop should be paired with a push");
+      }
+      for (MachineOperand &MO : MI.defs()) {
+        if (!MO.isReg())
+          continue;
+        Register Reg = MO.getReg();
+        if (MFI.isVRegStackified(Reg))
+          Stack.push_back(MO.getReg());
       }
     }
     // TODO: Generalize this code to support keeping values on the stack across

diff  --git a/llvm/test/CodeGen/WebAssembly/multivalue-stackify.ll b/llvm/test/CodeGen/WebAssembly/multivalue-stackify.ll
new file mode 100644
index 000000000000..2ec0dcf037c9
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/multivalue-stackify.ll
@@ -0,0 +1,3255 @@
+; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py
+; NOTE: Test functions have been generated by multivalue-stackify.py.
+
+; RUN: llc < %s -verify-machineinstrs -mattr=+multivalue | FileCheck %s
+
+; Test that the multivalue stackification works
+
+target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
+target triple = "wasm32-unknown-unknown"
+
+declare {i32} @op_0_to_1()
+declare {i32, i32} @op_0_to_2()
+declare {i32, i32, i32} @op_0_to_3()
+declare void @op_1_to_0(i32 %t0)
+declare {i32} @op_1_to_1(i32 %t0)
+declare {i32, i32} @op_1_to_2(i32 %t0)
+declare {i32, i32, i32} @op_1_to_3(i32 %t0)
+declare void @op_2_to_0(i32 %t0, i32 %t1)
+declare {i32} @op_2_to_1(i32 %t0, i32 %t1)
+declare {i32, i32} @op_2_to_2(i32 %t0, i32 %t1)
+declare {i32, i32, i32} @op_2_to_3(i32 %t0, i32 %t1)
+
+define void @f2() {
+; CHECK-LABEL: f2:
+; CHECK:         .functype f2 () -> ()
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  ret void
+}
+
+define void @f3() {
+; CHECK-LABEL: f3:
+; CHECK:         .functype f3 () -> ()
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  ret void
+}
+
+define void @f12() {
+; CHECK-LABEL: f12:
+; CHECK:         .functype f12 () -> ()
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t1)
+  ret void
+}
+
+define void @f13() {
+; CHECK-LABEL: f13:
+; CHECK:         .functype f13 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t1)
+  ret void
+}
+
+define void @f14() {
+; CHECK-LABEL: f14:
+; CHECK:         .functype f14 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.tee 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  ret void
+}
+
+define void @f15() {
+; CHECK-LABEL: f15:
+; CHECK:         .functype f15 () -> ()
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  ret void
+}
+
+define void @f16() {
+; CHECK-LABEL: f16:
+; CHECK:         .functype f16 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  ret void
+}
+
+define void @f17() {
+; CHECK-LABEL: f17:
+; CHECK:         .functype f17 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  ret void
+}
+
+define void @f25() {
+; CHECK-LABEL: f25:
+; CHECK:         .functype f25 () -> ()
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t1)
+  ret void
+}
+
+define void @f26() {
+; CHECK-LABEL: f26:
+; CHECK:         .functype f26 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t1)
+  ret void
+}
+
+define void @f27() {
+; CHECK-LABEL: f27:
+; CHECK:         .functype f27 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t1)
+  ret void
+}
+
+define void @f28() {
+; CHECK-LABEL: f28:
+; CHECK:         .functype f28 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.tee 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  ret void
+}
+
+define void @f29() {
+; CHECK-LABEL: f29:
+; CHECK:         .functype f29 () -> ()
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  ret void
+}
+
+define void @f30() {
+; CHECK-LABEL: f30:
+; CHECK:         .functype f30 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  ret void
+}
+
+define void @f31() {
+; CHECK-LABEL: f31:
+; CHECK:         .functype f31 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  ret void
+}
+
+define void @f32() {
+; CHECK-LABEL: f32:
+; CHECK:         .functype f32 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  ret void
+}
+
+define void @f33() {
+; CHECK-LABEL: f33:
+; CHECK:         .functype f33 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  ret void
+}
+
+define void @f34() {
+; CHECK-LABEL: f34:
+; CHECK:         .functype f34 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  ret void
+}
+
+define void @f35() {
+; CHECK-LABEL: f35:
+; CHECK:         .functype f35 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  ret void
+}
+
+define void @f36() {
+; CHECK-LABEL: f36:
+; CHECK:         .functype f36 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  ret void
+}
+
+define void @f129() {
+; CHECK-LABEL: f129:
+; CHECK:         .functype f129 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t2)
+  ret void
+}
+
+define void @f131() {
+; CHECK-LABEL: f131:
+; CHECK:         .functype f131 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32} %t0, 0
+  %t3 = extractvalue {i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f132() {
+; CHECK-LABEL: f132:
+; CHECK:         .functype f132 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32} %t0, 1
+  %t3 = extractvalue {i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f133() {
+; CHECK-LABEL: f133:
+; CHECK:         .functype f133 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32} %t0, 1
+  %t3 = extractvalue {i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f141() {
+; CHECK-LABEL: f141:
+; CHECK:         .functype f141 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t2)
+  ret void
+}
+
+define void @f143() {
+; CHECK-LABEL: f143:
+; CHECK:         .functype f143 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32} %t0, 0
+  %t3 = extractvalue {i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f144() {
+; CHECK-LABEL: f144:
+; CHECK:         .functype f144 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32} %t0, 0
+  %t3 = extractvalue {i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f145() {
+; CHECK-LABEL: f145:
+; CHECK:         .functype f145 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32} %t0, 1
+  %t3 = extractvalue {i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f155() {
+; CHECK-LABEL: f155:
+; CHECK:         .functype f155 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f159() {
+; CHECK-LABEL: f159:
+; CHECK:         .functype f159 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f167() {
+; CHECK-LABEL: f167:
+; CHECK:         .functype f167 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f168() {
+; CHECK-LABEL: f168:
+; CHECK:         .functype f168 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f171() {
+; CHECK-LABEL: f171:
+; CHECK:         .functype f171 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f180() {
+; CHECK-LABEL: f180:
+; CHECK:         .functype f180 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f181() {
+; CHECK-LABEL: f181:
+; CHECK:         .functype f181 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f183() {
+; CHECK-LABEL: f183:
+; CHECK:         .functype f183 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f193() {
+; CHECK-LABEL: f193:
+; CHECK:         .functype f193 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f195() {
+; CHECK-LABEL: f195:
+; CHECK:         .functype f195 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32} @op_0_to_2()
+  %t1 = extractvalue {i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f291() {
+; CHECK-LABEL: f291:
+; CHECK:         .functype f291 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t2)
+  ret void
+}
+
+define void @f292() {
+; CHECK-LABEL: f292:
+; CHECK:         .functype f292 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t2)
+  ret void
+}
+
+define void @f294() {
+; CHECK-LABEL: f294:
+; CHECK:         .functype f294 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f295() {
+; CHECK-LABEL: f295:
+; CHECK:         .functype f295 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f296() {
+; CHECK-LABEL: f296:
+; CHECK:         .functype f296 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f297() {
+; CHECK-LABEL: f297:
+; CHECK:         .functype f297 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f298() {
+; CHECK-LABEL: f298:
+; CHECK:         .functype f298 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f299() {
+; CHECK-LABEL: f299:
+; CHECK:         .functype f299 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f300() {
+; CHECK-LABEL: f300:
+; CHECK:         .functype f300 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f301() {
+; CHECK-LABEL: f301:
+; CHECK:         .functype f301 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f302() {
+; CHECK-LABEL: f302:
+; CHECK:         .functype f302 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t2)
+  ret void
+}
+
+define void @f304() {
+; CHECK-LABEL: f304:
+; CHECK:         .functype f304 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t2)
+  ret void
+}
+
+define void @f305() {
+; CHECK-LABEL: f305:
+; CHECK:         .functype f305 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f306() {
+; CHECK-LABEL: f306:
+; CHECK:         .functype f306 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f307() {
+; CHECK-LABEL: f307:
+; CHECK:         .functype f307 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f308() {
+; CHECK-LABEL: f308:
+; CHECK:         .functype f308 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f310() {
+; CHECK-LABEL: f310:
+; CHECK:         .functype f310 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f311() {
+; CHECK-LABEL: f311:
+; CHECK:         .functype f311 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f312() {
+; CHECK-LABEL: f312:
+; CHECK:         .functype f312 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f313() {
+; CHECK-LABEL: f313:
+; CHECK:         .functype f313 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f314() {
+; CHECK-LABEL: f314:
+; CHECK:         .functype f314 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t2)
+  ret void
+}
+
+define void @f315() {
+; CHECK-LABEL: f315:
+; CHECK:         .functype f315 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t2)
+  ret void
+}
+
+define void @f317() {
+; CHECK-LABEL: f317:
+; CHECK:         .functype f317 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f318() {
+; CHECK-LABEL: f318:
+; CHECK:         .functype f318 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f319() {
+; CHECK-LABEL: f319:
+; CHECK:         .functype f319 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f320() {
+; CHECK-LABEL: f320:
+; CHECK:         .functype f320 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f321() {
+; CHECK-LABEL: f321:
+; CHECK:         .functype f321 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f322() {
+; CHECK-LABEL: f322:
+; CHECK:         .functype f322 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f323() {
+; CHECK-LABEL: f323:
+; CHECK:         .functype f323 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f324() {
+; CHECK-LABEL: f324:
+; CHECK:         .functype f324 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t1)
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t2, i32 %t3)
+  ret void
+}
+
+define void @f327() {
+; CHECK-LABEL: f327:
+; CHECK:         .functype f327 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f328() {
+; CHECK-LABEL: f328:
+; CHECK:         .functype f328 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f333() {
+; CHECK-LABEL: f333:
+; CHECK:         .functype f333 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f334() {
+; CHECK-LABEL: f334:
+; CHECK:         .functype f334 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 2
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f336() {
+; CHECK-LABEL: f336:
+; CHECK:         .functype f336 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 2
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  %t4 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f337() {
+; CHECK-LABEL: f337:
+; CHECK:         .functype f337 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  %t4 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f338() {
+; CHECK-LABEL: f338:
+; CHECK:         .functype f338 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f339() {
+; CHECK-LABEL: f339:
+; CHECK:         .functype f339 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f340() {
+; CHECK-LABEL: f340:
+; CHECK:         .functype f340 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f343() {
+; CHECK-LABEL: f343:
+; CHECK:         .functype f343 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 2
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f344() {
+; CHECK-LABEL: f344:
+; CHECK:         .functype f344 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f346() {
+; CHECK-LABEL: f346:
+; CHECK:         .functype f346 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f347() {
+; CHECK-LABEL: f347:
+; CHECK:         .functype f347 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 2
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  %t4 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f348() {
+; CHECK-LABEL: f348:
+; CHECK:         .functype f348 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  %t4 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f349() {
+; CHECK-LABEL: f349:
+; CHECK:         .functype f349 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  %t4 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f350() {
+; CHECK-LABEL: f350:
+; CHECK:         .functype f350 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f351() {
+; CHECK-LABEL: f351:
+; CHECK:         .functype f351 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f352() {
+; CHECK-LABEL: f352:
+; CHECK:         .functype f352 () -> ()
+; CHECK-NEXT:    .local i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f354() {
+; CHECK-LABEL: f354:
+; CHECK:         .functype f354 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 2
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f356() {
+; CHECK-LABEL: f356:
+; CHECK:         .functype f356 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.tee 2
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f357() {
+; CHECK-LABEL: f357:
+; CHECK:         .functype f357 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f358() {
+; CHECK-LABEL: f358:
+; CHECK:         .functype f358 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f359() {
+; CHECK-LABEL: f359:
+; CHECK:         .functype f359 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.tee 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  %t4 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f360() {
+; CHECK-LABEL: f360:
+; CHECK:         .functype f360 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 0
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  %t4 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f362() {
+; CHECK-LABEL: f362:
+; CHECK:         .functype f362 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f363() {
+; CHECK-LABEL: f363:
+; CHECK:         .functype f363 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f364() {
+; CHECK-LABEL: f364:
+; CHECK:         .functype f364 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f366() {
+; CHECK-LABEL: f366:
+; CHECK:         .functype f366 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f367() {
+; CHECK-LABEL: f367:
+; CHECK:         .functype f367 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f370() {
+; CHECK-LABEL: f370:
+; CHECK:         .functype f370 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f371() {
+; CHECK-LABEL: f371:
+; CHECK:         .functype f371 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  %t4 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f372() {
+; CHECK-LABEL: f372:
+; CHECK:         .functype f372 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  %t4 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f373() {
+; CHECK-LABEL: f373:
+; CHECK:         .functype f373 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  %t4 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f374() {
+; CHECK-LABEL: f374:
+; CHECK:         .functype f374 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f376() {
+; CHECK-LABEL: f376:
+; CHECK:         .functype f376 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f377() {
+; CHECK-LABEL: f377:
+; CHECK:         .functype f377 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f379() {
+; CHECK-LABEL: f379:
+; CHECK:         .functype f379 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f383() {
+; CHECK-LABEL: f383:
+; CHECK:         .functype f383 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  %t4 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f385() {
+; CHECK-LABEL: f385:
+; CHECK:         .functype f385 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  %t4 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f386() {
+; CHECK-LABEL: f386:
+; CHECK:         .functype f386 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f387() {
+; CHECK-LABEL: f387:
+; CHECK:         .functype f387 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f388() {
+; CHECK-LABEL: f388:
+; CHECK:         .functype f388 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f389() {
+; CHECK-LABEL: f389:
+; CHECK:         .functype f389 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f390() {
+; CHECK-LABEL: f390:
+; CHECK:         .functype f390 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f391() {
+; CHECK-LABEL: f391:
+; CHECK:         .functype f391 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f392() {
+; CHECK-LABEL: f392:
+; CHECK:         .functype f392 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f395() {
+; CHECK-LABEL: f395:
+; CHECK:         .functype f395 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  %t4 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f396() {
+; CHECK-LABEL: f396:
+; CHECK:         .functype f396 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 1
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  %t4 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f398() {
+; CHECK-LABEL: f398:
+; CHECK:         .functype f398 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f399() {
+; CHECK-LABEL: f399:
+; CHECK:         .functype f399 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f400() {
+; CHECK-LABEL: f400:
+; CHECK:         .functype f400 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f402() {
+; CHECK-LABEL: f402:
+; CHECK:         .functype f402 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f403() {
+; CHECK-LABEL: f403:
+; CHECK:         .functype f403 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f404() {
+; CHECK-LABEL: f404:
+; CHECK:         .functype f404 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f405() {
+; CHECK-LABEL: f405:
+; CHECK:         .functype f405 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f406() {
+; CHECK-LABEL: f406:
+; CHECK:         .functype f406 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f408() {
+; CHECK-LABEL: f408:
+; CHECK:         .functype f408 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  %t4 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f410() {
+; CHECK-LABEL: f410:
+; CHECK:         .functype f410 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f411() {
+; CHECK-LABEL: f411:
+; CHECK:         .functype f411 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f412() {
+; CHECK-LABEL: f412:
+; CHECK:         .functype f412 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f413() {
+; CHECK-LABEL: f413:
+; CHECK:         .functype f413 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f414() {
+; CHECK-LABEL: f414:
+; CHECK:         .functype f414 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f415() {
+; CHECK-LABEL: f415:
+; CHECK:         .functype f415 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f416() {
+; CHECK-LABEL: f416:
+; CHECK:         .functype f416 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f418() {
+; CHECK-LABEL: f418:
+; CHECK:         .functype f418 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f419() {
+; CHECK-LABEL: f419:
+; CHECK:         .functype f419 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 2
+  %t4 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f422() {
+; CHECK-LABEL: f422:
+; CHECK:         .functype f422 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f423() {
+; CHECK-LABEL: f423:
+; CHECK:         .functype f423 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_1_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_1_to_0(i32 %t3)
+  ret void
+}
+
+define void @f425() {
+; CHECK-LABEL: f425:
+; CHECK:         .functype f425 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f426() {
+; CHECK-LABEL: f426:
+; CHECK:         .functype f426 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 0
+  %t4 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f428() {
+; CHECK-LABEL: f428:
+; CHECK:         .functype f428 () -> ()
+; CHECK-NEXT:    .local i32, i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 2
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    local.get 2
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32, i32} %t0, 0
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+
+define void @f429() {
+; CHECK-LABEL: f429:
+; CHECK:         .functype f429 () -> ()
+; CHECK-NEXT:    .local i32, i32
+; CHECK-NEXT:  # %bb.0:
+; CHECK-NEXT:    call op_0_to_3
+; CHECK-NEXT:    local.set 1
+; CHECK-NEXT:    local.set 0
+; CHECK-NEXT:    drop
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    local.get 1
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    local.get 0
+; CHECK-NEXT:    call op_2_to_0
+; CHECK-NEXT:    # fallthrough-return
+  %t0 = call {i32, i32, i32} @op_0_to_3()
+  %t1 = extractvalue {i32, i32, i32} %t0, 2
+  %t2 = extractvalue {i32, i32, i32} %t0, 2
+  call void @op_2_to_0(i32 %t1, i32 %t2)
+  %t3 = extractvalue {i32, i32, i32} %t0, 1
+  %t4 = extractvalue {i32, i32, i32} %t0, 1
+  call void @op_2_to_0(i32 %t3, i32 %t4)
+  ret void
+}
+

diff  --git a/llvm/test/CodeGen/WebAssembly/multivalue-stackify.py b/llvm/test/CodeGen/WebAssembly/multivalue-stackify.py
new file mode 100755
index 000000000000..18424261292e
--- /dev/null
+++ b/llvm/test/CodeGen/WebAssembly/multivalue-stackify.py
@@ -0,0 +1,219 @@
+#!/usr/bin/env python3
+
+"""A test case generator for register stackification.
+
+This script exhaustively generates small linear SSA programs, then filters them
+based on heuristics designed to keep interesting multivalue test cases and
+prints them as LLVM IR functions in a FileCheck test file.
+
+The output of this script is meant to be used in conjunction with
+update_llc_test_checks.py.
+
+  ```
+  ./multivalue-stackify.py > multivalue-stackify.ll
+  ../../../utils/update_llc_test_checks.py multivalue-stackify.ll
+  ```
+
+Programs are represented internally as lists of operations, where each operation
+is a pair of tuples, the first of which specifies the operation's uses and the
+second of which specifies its defs.
+
+TODO: Before embarking on a rewrite of the register stackifier, an abstract
+interpreter should be written to automatically check that the test assertions
+generated by update_llc_test_checks.py have the same semantics as the functions
+generated by this script. Once that is done, exhaustive testing can be done by
+making `is_interesting` return True.
+"""
+
+
+from itertools import product
+from collections import deque
+
+
+MAX_PROGRAM_OPS = 4
+MAX_PROGRAM_DEFS = 3
+MAX_OP_USES = 2
+
+
+def get_num_defs(program):
+  num_defs = 0
+  for _, defs in program:
+    num_defs += len(defs)
+  return num_defs
+
+
+def possible_ops(program):
+  program_defs = get_num_defs(program)
+  for num_defs in range(MAX_PROGRAM_DEFS - program_defs + 1):
+    for num_uses in range(MAX_OP_USES + 1):
+      if num_defs == 0 and num_uses == 0:
+        continue
+      for uses in product(range(program_defs), repeat=num_uses):
+        yield uses, tuple(program_defs + i for i in range(num_defs))
+
+
+def generate_programs():
+  queue = deque()
+  queue.append([])
+  program_id = 0
+  while True:
+    program = queue.popleft()
+    if len(program) == MAX_PROGRAM_OPS:
+      break
+    for op in possible_ops(program):
+      program_id += 1
+      new_program = program + [op]
+      queue.append(new_program)
+      yield program_id, new_program
+
+
+def get_num_terminal_ops(program):
+  num_terminal_ops = 0
+  for _, defs in program:
+    if len(defs) == 0:
+      num_terminal_ops += 1
+  return num_terminal_ops
+
+
+def get_max_uses(program):
+  num_uses = [0] * MAX_PROGRAM_DEFS
+  for uses, _ in program:
+    for u in uses:
+      num_uses[u] += 1
+  return max(num_uses)
+
+
+def has_unused_op(program):
+  used = [False] * MAX_PROGRAM_DEFS
+  for uses, defs in program[::-1]:
+    if defs and all(not used[d] for d in defs):
+      return True
+    for u in uses:
+      used[u] = True
+  return False
+
+
+def has_multivalue_use(program):
+  is_multi = [False] * MAX_PROGRAM_DEFS
+  for uses, defs in program:
+    if any(is_multi[u] for u in uses):
+      return True
+    if len(defs) >= 2:
+      for d in defs:
+        is_multi[d] = True
+  return False
+
+
+def has_mvp_use(program):
+  is_mvp = [False] * MAX_PROGRAM_DEFS
+  for uses, defs in program:
+    if uses and all(is_mvp[u] for u in uses):
+      return True
+    if len(defs) <= 1:
+      if any(is_mvp[u] for u in uses):
+        return True
+      for d in defs:
+        is_mvp[d] = True
+  return False
+
+
+def is_interesting(program):
+  # Allow only multivalue single-op programs
+  if len(program) == 1:
+    return len(program[0][1]) > 1
+
+  # Reject programs where the last two instructions are identical
+  if len(program) >= 2 and program[-1][0] == program[-2][0]:
+    return False
+
+  # Reject programs with too many ops that don't produce values
+  if get_num_terminal_ops(program) > 2:
+    return False
+
+  # The third use of a value is no more interesting than the second
+  if get_max_uses(program) >= 3:
+    return False
+
+  # Reject nontrivial programs that have unused instructions
+  if has_unused_op(program):
+    return False
+
+  # Reject programs that have boring MVP uses of MVP defs
+  if has_mvp_use(program):
+    return False
+
+  # Otherwise if it has multivalue usage it is interesting
+  return has_multivalue_use(program)
+
+
+def make_llvm_type(num_defs):
+  if num_defs == 0:
+    return 'void'
+  else:
+    return '{' + ', '.join(['i32'] * num_defs) + '}'
+
+
+def make_llvm_op_name(num_uses, num_defs):
+  return f'op_{num_uses}_to_{num_defs}'
+
+
+def make_llvm_args(first_use, num_uses):
+  return ', '.join([f'i32 %t{first_use + i}' for i in range(num_uses)])
+
+
+def print_llvm_program(program, name):
+  tmp = 0
+  def_data = []
+  print(f'define void @{name}() {{')
+  for uses, defs in program:
+    first_arg = tmp
+    # Extract operands
+    for use in uses:
+      ret_type, var, idx = def_data[use]
+      print(f'  %t{tmp} = extractvalue {ret_type} %t{var}, {idx}')
+      tmp += 1
+    # Print instruction
+    assignment = ''
+    if len(defs) > 0:
+      assignment = f'%t{tmp} = '
+      result_var = tmp
+      tmp += 1
+    ret_type = make_llvm_type(len(defs))
+    op_name = make_llvm_op_name(len(uses), len(defs))
+    args = make_llvm_args(first_arg, len(uses))
+    print(f'  {assignment}call {ret_type} @{op_name}({args})')
+    # Update def_data
+    for i in range(len(defs)):
+      def_data.append((ret_type, result_var, i))
+  print('  ret void')
+  print('}')
+
+
+def print_header():
+  print('; NOTE: Test functions have been generated by multivalue-stackify.py.')
+  print()
+  print('; RUN: llc < %s -verify-machineinstrs -mattr=+multivalue',
+        '| FileCheck %s')
+  print()
+  print('; Test that the multivalue stackification works')
+  print()
+  print('target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"')
+  print('target triple = "wasm32-unknown-unknown"')
+  print()
+  for num_uses in range(MAX_OP_USES + 1):
+    for num_defs in range(MAX_PROGRAM_DEFS + 1):
+      if num_uses == 0 and num_defs == 0:
+        continue
+      ret_type = make_llvm_type(num_defs)
+      op_name = make_llvm_op_name(num_uses, num_defs)
+      args = make_llvm_args(0, num_uses)
+      print(f'declare {ret_type} @{op_name}({args})')
+  print()
+
+
+if __name__ == '__main__':
+  print_header()
+  for i, program in generate_programs():
+    if is_interesting(program):
+      print_llvm_program(program, 'f' + str(i))
+      print()

diff  --git a/llvm/test/CodeGen/WebAssembly/multivalue.ll b/llvm/test/CodeGen/WebAssembly/multivalue.ll
index 0e4cb0847640..9b5a236416c4 100644
--- a/llvm/test/CodeGen/WebAssembly/multivalue.ll
+++ b/llvm/test/CodeGen/WebAssembly/multivalue.ll
@@ -8,6 +8,10 @@ target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128"
 target triple = "wasm32-unknown-unknown"
 
 %pair = type { i32, i64 }
+%rpair = type { i64, i32 }
+
+declare void @use_i32(i32)
+declare void @use_i64(i64)
 
 ; CHECK-LABEL: pair_const:
 ; CHECK-NEXT: .functype pair_const () -> (i32, i64)
@@ -27,12 +31,12 @@ define %pair @pair_ident(%pair %p) {
   ret %pair %p
 }
 
-;; TODO: Multivalue calls are a WIP and the following test cases do
-;; not necessarily produce correct output. For now just check that
-;; they do not crash.
-
 ; CHECK-LABEL: pair_call:
 ; CHECK-NEXT: .functype pair_call () -> ()
+; CHECK-NEXT: call pair_const{{$}}
+; CHECK-NEXT: drop{{$}}
+; CHECK-NEXT: drop{{$}}
+; CHECK-NEXT: end_function{{$}}
 define void @pair_call() {
   %p = call %pair @pair_const()
   ret void
@@ -40,6 +44,8 @@ define void @pair_call() {
 
 ; CHECK-LABEL: pair_call_return:
 ; CHECK-NEXT: .functype pair_call_return () -> (i32, i64)
+; CHECK-NEXT: call pair_const{{$}}
+; CHECK-NEXT: end_function{{$}}
 define %pair @pair_call_return() {
   %p = call %pair @pair_const()
   ret %pair %p
@@ -47,7 +53,9 @@ define %pair @pair_call_return() {
 
 ; CHECK-LABEL: pair_call_indirect:
 ; CHECK-NEXT: .functype pair_call_indirect (i32) -> (i32, i64)
-; CHECK: call_indirect () -> (i32, i64){{$}}
+; CHECK-NEXT: local.get 0{{$}}
+; CHECK-NEXT: call_indirect () -> (i32, i64){{$}}
+; CHECK-NEXT: end_function{{$}}
 define %pair @pair_call_indirect(%pair()* %f) {
   %p = call %pair %f()
   ret %pair %p
@@ -55,6 +63,8 @@ define %pair @pair_call_indirect(%pair()* %f) {
 
 ; CHECK-LABEL: pair_tail_call:
 ; CHECK-NEXT: .functype pair_tail_call () -> (i32, i64)
+; CHECK-NEXT: return_call pair_const{{$}}
+; CHECK-NEXT: end_function{{$}}
 define %pair @pair_tail_call() {
   %p = musttail call %pair @pair_const()
   ret %pair %p
@@ -62,6 +72,9 @@ define %pair @pair_tail_call() {
 
 ; CHECK-LABEL: pair_call_return_first:
 ; CHECK-NEXT: .functype pair_call_return_first () -> (i32)
+; CHECK-NEXT: call pair_const{{$}}
+; CHECK-NEXT: drop{{$}}
+; CHECK-NEXT: end_function{{$}}
 define i32 @pair_call_return_first() {
   %p = call %pair @pair_const()
   %v = extractvalue %pair %p, 0
@@ -70,20 +83,142 @@ define i32 @pair_call_return_first() {
 
 ; CHECK-LABEL: pair_call_return_second:
 ; CHECK-NEXT: .functype pair_call_return_second () -> (i64)
+; CHECK-NEXT: .local i64{{$}}
+; CHECK-NEXT: call pair_const{{$}}
+; CHECK-NEXT: local.set 0{{$}}
+; CHECK-NEXT: drop{{$}}
+; CHECK-NEXT: local.get 0{{$}}
+; CHECK-NEXT: end_function{{$}}
 define i64 @pair_call_return_second() {
   %p = call %pair @pair_const()
   %v = extractvalue %pair %p, 1
   ret i64 %v
 }
 
+; CHECK-LABEL: pair_call_use_first:
+; CHECK-NEXT: .functype pair_call_use_first () -> ()
+; CHECK-NEXT: call pair_const{{$}}
+; CHECK-NEXT: drop{{$}}
+; CHECK-NEXT: call use_i32{{$}}
+; CHECK-NEXT: end_function{{$}}
+define void @pair_call_use_first() {
+  %p = call %pair @pair_const()
+  %v = extractvalue %pair %p, 0
+  call void @use_i32(i32 %v)
+  ret void
+}
+
+; CHECK-LABEL: pair_call_use_second:
+; CHECK-NEXT: .functype pair_call_use_second () -> ()
+; CHECK-NEXT: .local i64
+; CHECK-NEXT: call pair_const{{$}}
+; CHECK-NEXT: local.set 0{{$}}
+; CHECK-NEXT: drop{{$}}
+; CHECK-NEXT: local.get 0{{$}}
+; CHECK-NEXT: call use_i64{{$}}
+; CHECK-NEXT: end_function{{$}}
+define void @pair_call_use_second() {
+  %p = call %pair @pair_const()
+  %v = extractvalue %pair %p, 1
+  call void @use_i64(i64 %v)
+  ret void
+}
+
+; CHECK-LABEL: pair_call_use_first_return_second:
+; CHECK-NEXT: .functype pair_call_use_first_return_second () -> (i64)
+; CHECK-NEXT: .local i64{{$}}
+; CHECK-NEXT: call pair_const{{$}}
+; CHECK-NEXT: local.set 0{{$}}
+; CHECK-NEXT: call use_i32{{$}}
+; CHECK-NEXT: local.get 0{{$}}
+; CHECK-NEXT: end_function{{$}}
+define i64 @pair_call_use_first_return_second() {
+  %p = call %pair @pair_const()
+  %v = extractvalue %pair %p, 0
+  call void @use_i32(i32 %v)
+  %r = extractvalue %pair %p, 1
+  ret i64 %r
+}
+
+; CHECK-LABEL: pair_call_use_second_return_first:
+; CHECK-NEXT: .functype pair_call_use_second_return_first () -> (i32)
+; CHECK-NEXT: .local i32, i64{{$}}
+; CHECK-NEXT: call pair_const{{$}}
+; CHECK-NEXT: local.set 1{{$}}
+; CHECK-NEXT: local.set 0{{$}}
+; CHECK-NEXT: local.get 1{{$}}
+; CHECK-NEXT: call use_i64{{$}}
+; CHECK-NEXT: local.get 0{{$}}
+; CHECK-NEXT: end_function{{$}}
+define i32 @pair_call_use_second_return_first() {
+  %p = call %pair @pair_const()
+  %v = extractvalue %pair %p, 1
+  call void @use_i64(i64 %v)
+  %r = extractvalue %pair %p, 0
+  ret i32 %r
+}
 
 ; CHECK-LABEL: pair_pass_through:
 ; CHECK-NEXT: .functype pair_pass_through (i32, i64) -> (i32, i64)
+; CHECK-NEXT: local.get 0
+; CHECK-NEXT: local.get 1
+; CHECK-NEXT: call pair_ident{{$}}
+; CHECK-NEXT: end_function{{$}}
 define %pair @pair_pass_through(%pair %p) {
   %r = call %pair @pair_ident(%pair %p)
   ret %pair %r
 }
 
+; CHECK-LABEL: pair_swap:
+; CHECK-NEXT: .functype pair_swap (i32, i64) -> (i64, i32)
+; CHECK-NEXT: local.get 1{{$}}
+; CHECK-NEXT: local.get 0{{$}}
+; CHECK-NEXT: end_function{{$}}
+define %rpair @pair_swap(%pair %p) {
+  %first = extractvalue %pair %p, 0
+  %second = extractvalue %pair %p, 1
+  %r1 = insertvalue %rpair undef, i32 %first, 1
+  %r2 = insertvalue %rpair %r1, i64 %second, 0
+  ret %rpair %r2
+}
+
+; CHECK-LABEL: pair_call_swap:
+; CHECK-NEXT: .functype pair_call_swap () -> (i64, i32)
+; CHECK-NEXT: .local i32, i64{{$}}
+; CHECK-NEXT: call pair_const{{$}}
+; CHECK-NEXT: local.set 1{{$}}
+; CHECK-NEXT: local.set 0{{$}}
+; CHECK-NEXT: local.get 1{{$}}
+; CHECK-NEXT: local.get 0{{$}}
+; CHECK-NEXT: end_function{{$}}
+define %rpair @pair_call_swap() {
+  %p = call %pair @pair_const()
+  %first = extractvalue %pair %p, 0
+  %second = extractvalue %pair %p, 1
+  %r1 = insertvalue %rpair undef, i32 %first, 1
+  %r2 = insertvalue %rpair %r1, i64 %second, 0
+  ret %rpair %r2
+}
+
+; CHECK-LABEL: pair_pass_through_swap:
+; CHECK-NEXT: .functype pair_pass_through_swap (i32, i64) -> (i64, i32)
+; CHECK-NEXT: local.get 0{{$}}
+; CHECK-NEXT: local.get 1{{$}}
+; CHECK-NEXT: call pair_ident{{$}}
+; CHECK-NEXT: local.set 1{{$}}
+; CHECK-NEXT: local.set 0{{$}}
+; CHECK-NEXT: local.get 1{{$}}
+; CHECK-NEXT: local.get 0{{$}}
+; CHECK-NEXT: end_function{{$}}
+define %rpair @pair_pass_through_swap(%pair %p) {
+  %p1 = call %pair @pair_ident(%pair %p)
+  %first = extractvalue %pair %p1, 0
+  %second = extractvalue %pair %p1, 1
+  %r1 = insertvalue %rpair undef, i32 %first, 1
+  %r2 = insertvalue %rpair %r1, i64 %second, 0
+  ret %rpair %r2
+}
+
 ; CHECK-LABEL: minimal_loop:
 ; CHECK-NEXT: .functype minimal_loop (i32) -> (i32, i64)
 ; CHECK-NEXT: .LBB{{[0-9]+}}_1:
@@ -91,6 +226,7 @@ define %pair @pair_pass_through(%pair %p) {
 ; CHECK-NEXT: br 0{{$}}
 ; CHECK-NEXT: .LBB{{[0-9]+}}_2:
 ; CHECK-NEXT: end_loop{{$}}
+; CHECK-NEXT: end_function{{$}}
 define %pair @minimal_loop(i32* %p) {
 entry:
   br label %loop
@@ -138,3 +274,23 @@ loop:
 ; OBJ-NEXT:         ParamTypes:      []
 ; OBJ-NEXT:         ReturnTypes:
 ; OBJ-NEXT:           - I64
+; OBJ-NEXT:       - Index:           6
+; OBJ-NEXT:         ParamTypes:
+; OBJ-NEXT:           - I32
+; OBJ-NEXT:         ReturnTypes:     []
+; OBJ-NEXT:       - Index:           7
+; OBJ-NEXT:         ParamTypes:
+; OBJ-NEXT:           - I64
+; OBJ-NEXT:         ReturnTypes:     []
+; OBJ-NEXT:       - Index:           8
+; OBJ-NEXT:         ParamTypes:
+; OBJ-NEXT:           - I32
+; OBJ-NEXT:           - I64
+; OBJ-NEXT:         ReturnTypes:
+; OBJ-NEXT:           - I64
+; OBJ-NEXT:           - I32
+; OBJ-NEXT:       - Index:           9
+; OBJ-NEXT:         ParamTypes:      []
+; OBJ-NEXT:         ReturnTypes:
+; OBJ-NEXT:           - I64
+; OBJ-NEXT:           - I32


        


More information about the llvm-commits mailing list