[clang] [Win32][Coroutine] Fix inalloca arguments lifetime in coroutines (PR #212331)

via cfe-commits cfe-commits at lists.llvm.org
Sat Aug 1 04:31:03 PDT 2026


https://github.com/etiennep-chromium updated https://github.com/llvm/llvm-project/pull/212331

>From 5413ef689946031f8abb8b56fd8fe05f47052efb Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Mon, 27 Jul 2026 19:14:00 +0000
Subject: [PATCH 01/26] [Win32][Coroutine] Fix inalloca arguments lifetime in
 coroutines

On Win32 x86, arguments passed by value with non-trivial constructors/destructors
require `inalloca`. If a coroutine suspends during the evaluation of these
arguments, the stack frame is popped, destroying the already constructed arguments.

This fix detects when a call inside a coroutine requires `inalloca` and contains
suspend points in its arguments. In this case, we:
1. Force all arguments of the call to be evaluated into temporaries in the
   coroutine frame (which survive suspension).
2. Delay the `llvm.stacksave` to the end of argument evaluation (after resume).
3. Allocate the `inalloca` struct on the stack after resume and copy the
   temporaries into it.
4. Emit lifetime markers (`llvm.lifetime.start`/`llvm.lifetime.end`) for the
   `inalloca` alloca to prevent it from being moved to the coroutine frame
   by CoroSplit.
5. Safely clean up the temporaries in the frame.

If an argument is non-copyable/non-movable, we now emit a compilation error.

BUG=LLVM#59382
TAG=agy
CONV=f4ad8337-ec22-4db0-a3a2-b3c51fb6619b
---
 clang/lib/CodeGen/CGCall.cpp | 221 ++++++++++++++++++++++++++---------
 clang/lib/CodeGen/CGCall.h   |   5 +
 2 files changed, 171 insertions(+), 55 deletions(-)

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index c0e2456891e9d..b86e509dde0d2 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -27,7 +27,9 @@
 #include "clang/AST/Decl.h"
 #include "clang/AST/DeclCXX.h"
 #include "clang/AST/DeclObjC.h"
+#include "clang/AST/ExprCXX.h"
 #include "clang/AST/RecordLayout.h"
+#include "clang/AST/RecursiveASTVisitor.h"
 #include "clang/Basic/CodeGenOptions.h"
 #include "clang/Basic/TargetInfo.h"
 #include "clang/CodeGen/CGFunctionInfo.h"
@@ -4788,6 +4790,27 @@ static bool isObjCMethodWithTypeParams(const ObjCMethodDecl *method) {
 }
 #endif
 
+namespace {
+class CoroSuspendFinder : public RecursiveASTVisitor<CoroSuspendFinder> {
+public:
+  bool FoundSuspend = false;
+  bool VisitCoawaitExpr(CoawaitExpr *) {
+    FoundSuspend = true;
+    return false; // Stop traversal
+  }
+  bool VisitCoyieldExpr(CoyieldExpr *) {
+    FoundSuspend = true;
+    return false; // Stop traversal
+  }
+};
+} // namespace
+
+static bool containsCoroSuspend(const Expr *E) {
+  CoroSuspendFinder Finder;
+  Finder.TraverseStmt(const_cast<Expr *>(E));
+  return Finder.FoundSuspend;
+}
+
 /// EmitCallArgs - Emit call arguments for a function.
 void CodeGenFunction::EmitCallArgs(
     CallArgList &Args, PrototypeWrapper Prototype,
@@ -4886,11 +4909,25 @@ void CodeGenFunction::EmitCallArgs(
       std::swap(Args.back(), *(&Args.back() - 1));
   };
 
-  // Insert a stack save if we're going to need any inalloca args.
   if (hasInAllocaArgs(CGM, ExplicitCC, ArgTypes)) {
     assert(getTarget().getTriple().getArch() == llvm::Triple::x86 &&
            "inalloca only supported on x86");
-    Args.allocateArgumentMemory(*this);
+    if (isCoroutine()) {
+      bool CallHasSuspend = false;
+      for (const Expr *A : ArgRange) {
+        bool hasSuspend = containsCoroSuspend(A);
+        if (hasSuspend) {
+          CallHasSuspend = true;
+          break;
+        }
+      }
+      if (CallHasSuspend) {
+        Args.setForceWriteback(true);
+      }
+    }
+    if (!Args.shouldForceWriteback()) {
+      Args.allocateArgumentMemory(*this);
+    }
   }
 
   // Evaluate each argument in the appropriate order.
@@ -4926,6 +4963,10 @@ void CodeGenFunction::EmitCallArgs(
     }
   }
 
+  if (Args.shouldForceWriteback()) {
+    Args.allocateArgumentMemory(*this);
+  }
+
   if (!LeftToRight) {
     // Un-reverse the arguments we just evaluated so they match up with the LLVM
     // IR function.
@@ -5023,39 +5064,42 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
   // In the Microsoft C++ ABI, aggregate arguments are destructed by the callee.
   // However, we still have to push an EH-only cleanup in case we unwind before
   // we make it to the call.
-  if (type->isRecordType() &&
-      type->castAsRecordDecl()->isParamDestroyedInCallee()) {
-    // If we're using inalloca, use the argument memory.  Otherwise, use a
-    // temporary.
-    AggValueSlot Slot = args.isUsingInAlloca()
-                            ? createPlaceholderSlot(*this, type)
-                            : CreateAggTemp(type, "agg.tmp");
-
-    bool DestroyedInCallee = true, NeedsCleanup = true;
-    if (const auto *RD = type->getAsCXXRecordDecl())
-      DestroyedInCallee = RD->hasNonTrivialDestructor();
-    else
-      NeedsCleanup = type.isDestructedType();
-
-    if (DestroyedInCallee)
-      Slot.setExternallyDestructed();
-
-    EmitAggExpr(E, Slot);
-    RValue RV = Slot.asRValue();
-    args.add(RV, type);
-
-    if (DestroyedInCallee && NeedsCleanup) {
-      // Create a no-op GEP between the placeholder and the cleanup so we can
-      // RAUW it successfully.  It also serves as a marker of the first
-      // instruction where the cleanup is active.
-      pushFullExprCleanup<DestroyUnpassedArg>(NormalAndEHCleanup,
-                                              Slot.getAddress(), type);
-      // This unreachable is a temporary marker which will be removed later.
-      llvm::Instruction *IsActive =
-          Builder.CreateFlagLoad(llvm::Constant::getNullValue(Int8PtrTy));
-      args.addArgCleanupDeactivation(EHStack.stable_begin(), IsActive);
+  if (type->isRecordType()) {
+    bool paramDestroyed = type->castAsRecordDecl()->isParamDestroyedInCallee();
+    if (paramDestroyed) {
+      // If we're using inalloca, use the argument memory.  Otherwise, use a
+      // temporary.
+      bool usePlaceholder =
+          args.isUsingInAlloca() && !args.shouldForceWriteback();
+      AggValueSlot Slot = usePlaceholder ? createPlaceholderSlot(*this, type)
+                                         : CreateAggTemp(type, "agg.tmp");
+
+      bool DestroyedInCallee = true, NeedsCleanup = true;
+      if (const auto *RD = type->getAsCXXRecordDecl())
+        DestroyedInCallee = RD->hasNonTrivialDestructor();
+      else
+        NeedsCleanup = type.isDestructedType();
+
+      if (DestroyedInCallee && !args.shouldForceWriteback())
+        Slot.setExternallyDestructed();
+
+      EmitAggExpr(E, Slot);
+      RValue RV = Slot.asRValue();
+      args.add(RV, type);
+
+      if (DestroyedInCallee && NeedsCleanup && !args.shouldForceWriteback()) {
+        // Create a no-op GEP between the placeholder and the cleanup so we can
+        // RAUW it successfully.  It also serves as a marker of the first
+        // instruction where the cleanup is active.
+        pushFullExprCleanup<DestroyUnpassedArg>(NormalAndEHCleanup,
+                                                Slot.getAddress(), type);
+        // This unreachable is a temporary marker which will be removed later.
+        llvm::Instruction *IsActive =
+            Builder.CreateFlagLoad(llvm::Constant::getNullValue(Int8PtrTy));
+        args.addArgCleanupDeactivation(EHStack.stable_begin(), IsActive);
+      }
+      return;
     }
-    return;
   }
 
   if (HasAggregateEvalKind && isa<ImplicitCastExpr>(E) &&
@@ -5404,9 +5448,13 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
     llvm::Instruction *IP = CallArgs.getStackBase();
     llvm::AllocaInst *AI;
     if (IP) {
-      IP = IP->getNextNode();
-      AI = new llvm::AllocaInst(ArgStruct, DL.getAllocaAddrSpace(), "argmem",
-                                IP->getIterator());
+      if (llvm::Instruction *Next = IP->getNextNode()) {
+        AI = new llvm::AllocaInst(ArgStruct, DL.getAllocaAddrSpace(), "argmem",
+                                  Next->getIterator());
+      } else {
+        AI = new llvm::AllocaInst(ArgStruct, DL.getAllocaAddrSpace(), "argmem",
+                                  IP->getParent());
+      }
     } else {
       AI = CreateTempAlloca(ArgStruct, "argmem");
     }
@@ -5415,6 +5463,9 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
     AI->setUsedWithInAlloca(true);
     assert(AI->isUsedWithInAlloca() && !AI->isStaticAlloca());
     ArgMemory = RawAddress(AI, ArgStruct, Align);
+    if (isCoroutine()) {
+      EmitLifetimeStart(AI);
+    }
   }
 
   ClangToLLVMArgMapping IRFunctionArgs(CGM.getContext(), CallInfo);
@@ -5498,26 +5549,83 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
         RawAddress Addr = I->hasLValue()
                               ? I->getKnownLValue().getAddress()
                               : I->getKnownRValue().getAggregateAddress();
-        llvm::Instruction *Placeholder =
-            cast<llvm::Instruction>(Addr.getPointer());
-
-        if (!ArgInfo.getInAllocaIndirect()) {
-          // Replace the placeholder with the appropriate argument slot GEP.
-          CGBuilderTy::InsertPoint IP = Builder.saveIP();
-          Builder.SetInsertPoint(Placeholder);
-          Addr = Builder.CreateStructGEP(ArgMemory,
-                                         ArgInfo.getInAllocaFieldIndex());
-          Builder.restoreIP(IP);
+        if (CallArgs.shouldForceWriteback()) {
+          RawAddress Dest = RawAddress::invalid();
+          if (!ArgInfo.getInAllocaIndirect()) {
+            Dest = Builder.CreateStructGEP(ArgMemory,
+                                           ArgInfo.getInAllocaFieldIndex());
+          } else {
+            Dest = CreateMemTemp(info_it->type, "inalloca.indirect.tmp");
+            Address ArgSlot = Builder.CreateStructGEP(
+                ArgMemory, ArgInfo.getInAllocaFieldIndex());
+            Builder.CreateStore(Dest.getPointer(), ArgSlot);
+          }
+          const CXXRecordDecl *RD = info_it->type->getAsCXXRecordDecl();
+          if (RD && !RD->isTriviallyCopyable()) {
+            const CXXConstructorDecl *CopyCtor = nullptr;
+            const CXXConstructorDecl *MoveCtor = nullptr;
+            for (const CXXConstructorDecl *C : RD->ctors()) {
+              if (C->isDeleted())
+                continue;
+              if (C->isMoveConstructor()) {
+                MoveCtor = C;
+              } else if (C->isCopyConstructor()) {
+                if (!CopyCtor || C->getParamDecl(0)
+                                     ->getType()
+                                     ->getPointeeType()
+                                     .isConstQualified())
+                  CopyCtor = C;
+              }
+            }
+            const CXXConstructorDecl *Ctor = MoveCtor ? MoveCtor : CopyCtor;
+            if (!Ctor || Ctor->isDeleted()) {
+              CGM.Error(Loc, "coroutine argument must be copyable or movable "
+                             "on this target");
+              EmitAggregateCopy(MakeAddrLValue(Dest, info_it->type),
+                                MakeAddrLValue(Addr, info_it->type),
+                                info_it->type, AggValueSlot::DoesNotOverlap);
+            } else {
+              CallArgList CtorArgs;
+              llvm::Value *ThisPtr = getAsNaturalPointerTo(
+                  Dest, Ctor->getThisType()->getPointeeType());
+              CtorArgs.add(RValue::get(ThisPtr), Ctor->getThisType());
+              QualType ParamTy = Ctor->getParamDecl(0)->getType();
+              llvm::Value *SrcPtr =
+                  getAsNaturalPointerTo(Addr, ParamTy->getPointeeType());
+              CtorArgs.add(RValue::get(SrcPtr), ParamTy);
+              EmitCXXConstructorCall(Ctor, Ctor_Complete,
+                                     /*ForVirtualBase*/ false,
+                                     /*Delegating*/ false, Dest, CtorArgs,
+                                     AggValueSlot::DoesNotOverlap, Loc,
+                                     /*NewPointerIsChecked*/ false);
+            }
+          } else {
+            EmitAggregateCopy(MakeAddrLValue(Dest, info_it->type),
+                              MakeAddrLValue(Addr, info_it->type),
+                              info_it->type, AggValueSlot::DoesNotOverlap);
+          }
         } else {
-          // For indirect things such as overaligned structs, replace the
-          // placeholder with a regular aggregate temporary alloca. Store the
-          // address of this alloca into the struct.
-          Addr = CreateMemTemp(info_it->type, "inalloca.indirect.tmp");
-          Address ArgSlot = Builder.CreateStructGEP(
-              ArgMemory, ArgInfo.getInAllocaFieldIndex());
-          Builder.CreateStore(Addr.getPointer(), ArgSlot);
+          llvm::Instruction *Placeholder =
+              cast<llvm::Instruction>(Addr.getPointer());
+
+          if (!ArgInfo.getInAllocaIndirect()) {
+            // Replace the placeholder with the appropriate argument slot GEP.
+            CGBuilderTy::InsertPoint IP = Builder.saveIP();
+            Builder.SetInsertPoint(Placeholder);
+            Addr = Builder.CreateStructGEP(ArgMemory,
+                                           ArgInfo.getInAllocaFieldIndex());
+            Builder.restoreIP(IP);
+          } else {
+            // For indirect things such as overaligned structs, replace the
+            // placeholder with a regular aggregate temporary alloca. Store the
+            // address of this alloca into the struct.
+            Addr = CreateMemTemp(info_it->type, "inalloca.indirect.tmp");
+            Address ArgSlot = Builder.CreateStructGEP(
+                ArgMemory, ArgInfo.getInAllocaFieldIndex());
+            Builder.CreateStore(Addr.getPointer(), ArgSlot);
+          }
+          deferPlaceholderReplacement(Placeholder, Addr.getPointer());
         }
-        deferPlaceholderReplacement(Placeholder, Addr.getPointer());
       } else if (ArgInfo.getInAllocaIndirect()) {
         // Make a temporary alloca and store the address of it into the argument
         // struct.
@@ -6289,6 +6397,9 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
   // The stack cleanup for inalloca arguments has to run out of the normal
   // lexical order, so deactivate it and run it manually here.
   CallArgs.freeArgumentMemory(*this);
+  if (isCoroutine() && ArgMemory.isValid()) {
+    EmitLifetimeEnd(ArgMemory.getPointer());
+  }
 
   // Extract the return value.
   RValue Ret;
diff --git a/clang/lib/CodeGen/CGCall.h b/clang/lib/CodeGen/CGCall.h
index 145992652934f..488296439eb0c 100644
--- a/clang/lib/CodeGen/CGCall.h
+++ b/clang/lib/CodeGen/CGCall.h
@@ -357,6 +357,9 @@ class CallArgList : public SmallVector<CallArg, 8> {
     std::reverse(Writebacks.begin(), Writebacks.end());
   }
 
+  bool shouldForceWriteback() const { return ForceWriteback; }
+  void setForceWriteback(bool V) { ForceWriteback = V; }
+
 private:
   SmallVector<Writeback, 1> Writebacks;
 
@@ -367,6 +370,8 @@ class CallArgList : public SmallVector<CallArg, 8> {
 
   /// The stacksave call.  It dominates all of the argument evaluation.
   llvm::CallInst *StackBase = nullptr;
+
+  bool ForceWriteback = false;
 };
 
 /// FunctionArgList - Type for representing both the decl and type

>From 37df429465f43951864ccc41667d02d329d1af6c Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Mon, 27 Jul 2026 20:32:56 +0000
Subject: [PATCH 02/26] CR-efriedma-quic

---
 clang/lib/CodeGen/CGCall.cpp        | 257 ++++++++++++++--------------
 clang/lib/CodeGen/CGCall.h          |   5 -
 clang/lib/CodeGen/CGExpr.cpp        |   5 +
 clang/lib/CodeGen/CGExprAgg.cpp     |  50 ++++++
 clang/lib/CodeGen/CodeGenFunction.h |  12 ++
 clang/lib/Sema/SemaExpr.cpp         |  65 +++++++
 6 files changed, 260 insertions(+), 134 deletions(-)

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index b86e509dde0d2..0312b456df6cd 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -4811,6 +4811,46 @@ static bool containsCoroSuspend(const Expr *E) {
   return Finder.FoundSuspend;
 }
 
+static const MaterializeTemporaryExpr *getMTEToPreEvaluate(const Expr *E) {
+  while (true) {
+    E = E->IgnoreParens();
+    if (auto *Cleanups = dyn_cast<ExprWithCleanups>(E)) {
+      E = Cleanups->getSubExpr();
+      continue;
+    }
+    if (auto *Bind = dyn_cast<CXXBindTemporaryExpr>(E)) {
+      E = Bind->getSubExpr();
+      continue;
+    }
+    break;
+  }
+
+  if (auto *CE = dyn_cast<CXXConstructExpr>(E)) {
+    if (CE->getNumArgs() > 0) {
+      const Expr *Arg = CE->getArg(0);
+      while (true) {
+        Arg = Arg->IgnoreParens();
+        if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(Arg))
+          return MTE;
+        if (auto *Cleanups = dyn_cast<ExprWithCleanups>(Arg)) {
+          Arg = Cleanups->getSubExpr();
+          continue;
+        }
+        if (auto *Bind = dyn_cast<CXXBindTemporaryExpr>(Arg)) {
+          Arg = Bind->getSubExpr();
+          continue;
+        }
+        if (auto *ICE = dyn_cast<ImplicitCastExpr>(Arg)) {
+          Arg = ICE->getSubExpr();
+          continue;
+        }
+        break;
+      }
+    }
+  }
+  return nullptr;
+}
+
 /// EmitCallArgs - Emit call arguments for a function.
 void CodeGenFunction::EmitCallArgs(
     CallArgList &Args, PrototypeWrapper Prototype,
@@ -4909,27 +4949,46 @@ void CodeGenFunction::EmitCallArgs(
       std::swap(Args.back(), *(&Args.back() - 1));
   };
 
-  if (hasInAllocaArgs(CGM, ExplicitCC, ArgTypes)) {
-    assert(getTarget().getTriple().getArch() == llvm::Triple::x86 &&
-           "inalloca only supported on x86");
-    if (isCoroutine()) {
-      bool CallHasSuspend = false;
-      for (const Expr *A : ArgRange) {
-        bool hasSuspend = containsCoroSuspend(A);
-        if (hasSuspend) {
-          CallHasSuspend = true;
-          break;
-        }
+  bool CallHasSuspend = false;
+  if (isCoroutine()) {
+    for (const Expr *A : ArgRange) {
+      if (containsCoroSuspend(A)) {
+        CallHasSuspend = true;
+        break;
       }
-      if (CallHasSuspend) {
-        Args.setForceWriteback(true);
+    }
+  }
+
+  SmallVector<const MaterializeTemporaryExpr *, 4> MTEsToPreEvaluate;
+  if (CallHasSuspend && hasInAllocaArgs(CGM, ExplicitCC, ArgTypes)) {
+    for (const Expr *A : ArgRange) {
+      if (auto *MTE = getMTEToPreEvaluate(A)) {
+        MTEsToPreEvaluate.push_back(MTE);
       }
     }
-    if (!Args.shouldForceWriteback()) {
-      Args.allocateArgumentMemory(*this);
+  }
+
+  if (!MTEsToPreEvaluate.empty()) {
+    if (LeftToRight) {
+      for (const MaterializeTemporaryExpr *MTE : MTEsToPreEvaluate) {
+        LValue LV = EmitMaterializeTemporaryExpr(MTE);
+        PreEvaluatedMaterializedTemporaries[MTE] = LV;
+      }
+    } else {
+      for (int i = MTEsToPreEvaluate.size() - 1; i >= 0; --i) {
+        const MaterializeTemporaryExpr *MTE = MTEsToPreEvaluate[i];
+        LValue LV = EmitMaterializeTemporaryExpr(MTE);
+        PreEvaluatedMaterializedTemporaries[MTE] = LV;
+      }
     }
   }
 
+  if (hasInAllocaArgs(CGM, ExplicitCC, ArgTypes)) {
+    assert(getTarget().getTriple().getArch() == llvm::Triple::x86 &&
+           "inalloca only supported on x86");
+    Args.allocateArgumentMemory(*this);
+  }
+
   // Evaluate each argument in the appropriate order.
   size_t CallArgsStart = Args.size();
   for (unsigned I = 0, E = ArgTypes.size(); I != E; ++I) {
@@ -4963,10 +5022,6 @@ void CodeGenFunction::EmitCallArgs(
     }
   }
 
-  if (Args.shouldForceWriteback()) {
-    Args.allocateArgumentMemory(*this);
-  }
-
   if (!LeftToRight) {
     // Un-reverse the arguments we just evaluated so they match up with the LLVM
     // IR function.
@@ -4975,6 +5030,10 @@ void CodeGenFunction::EmitCallArgs(
     // Reverse the writebacks to match the MSVC ABI.
     Args.reverseWritebacks();
   }
+
+  for (const auto *MTE : MTEsToPreEvaluate) {
+    PreEvaluatedMaterializedTemporaries.erase(MTE);
+  }
 }
 
 namespace {
@@ -5064,42 +5123,39 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
   // In the Microsoft C++ ABI, aggregate arguments are destructed by the callee.
   // However, we still have to push an EH-only cleanup in case we unwind before
   // we make it to the call.
-  if (type->isRecordType()) {
-    bool paramDestroyed = type->castAsRecordDecl()->isParamDestroyedInCallee();
-    if (paramDestroyed) {
-      // If we're using inalloca, use the argument memory.  Otherwise, use a
-      // temporary.
-      bool usePlaceholder =
-          args.isUsingInAlloca() && !args.shouldForceWriteback();
-      AggValueSlot Slot = usePlaceholder ? createPlaceholderSlot(*this, type)
-                                         : CreateAggTemp(type, "agg.tmp");
-
-      bool DestroyedInCallee = true, NeedsCleanup = true;
-      if (const auto *RD = type->getAsCXXRecordDecl())
-        DestroyedInCallee = RD->hasNonTrivialDestructor();
-      else
-        NeedsCleanup = type.isDestructedType();
-
-      if (DestroyedInCallee && !args.shouldForceWriteback())
-        Slot.setExternallyDestructed();
-
-      EmitAggExpr(E, Slot);
-      RValue RV = Slot.asRValue();
-      args.add(RV, type);
-
-      if (DestroyedInCallee && NeedsCleanup && !args.shouldForceWriteback()) {
-        // Create a no-op GEP between the placeholder and the cleanup so we can
-        // RAUW it successfully.  It also serves as a marker of the first
-        // instruction where the cleanup is active.
-        pushFullExprCleanup<DestroyUnpassedArg>(NormalAndEHCleanup,
-                                                Slot.getAddress(), type);
-        // This unreachable is a temporary marker which will be removed later.
-        llvm::Instruction *IsActive =
-            Builder.CreateFlagLoad(llvm::Constant::getNullValue(Int8PtrTy));
-        args.addArgCleanupDeactivation(EHStack.stable_begin(), IsActive);
-      }
-      return;
+  if (type->isRecordType() &&
+      type->castAsRecordDecl()->isParamDestroyedInCallee()) {
+    // If we're using inalloca, use the argument memory.  Otherwise, use a
+    // temporary.
+    AggValueSlot Slot = args.isUsingInAlloca()
+                            ? createPlaceholderSlot(*this, type)
+                            : CreateAggTemp(type, "agg.tmp");
+
+    bool DestroyedInCallee = true, NeedsCleanup = true;
+    if (const auto *RD = type->getAsCXXRecordDecl())
+      DestroyedInCallee = RD->hasNonTrivialDestructor();
+    else
+      NeedsCleanup = type.isDestructedType();
+
+    if (DestroyedInCallee)
+      Slot.setExternallyDestructed();
+
+    EmitAggExpr(E, Slot);
+    RValue RV = Slot.asRValue();
+    args.add(RV, type);
+
+    if (DestroyedInCallee && NeedsCleanup) {
+      // Create a no-op GEP between the placeholder and the cleanup so we can
+      // RAUW it successfully.  It also serves as a marker of the first
+      // instruction where the cleanup is active.
+      pushFullExprCleanup<DestroyUnpassedArg>(NormalAndEHCleanup,
+                                              Slot.getAddress(), type);
+      // This unreachable is a temporary marker which will be removed later.
+      llvm::Instruction *IsActive =
+          Builder.CreateFlagLoad(llvm::Constant::getNullValue(Int8PtrTy));
+      args.addArgCleanupDeactivation(EHStack.stable_begin(), IsActive);
     }
+    return;
   }
 
   if (HasAggregateEvalKind && isa<ImplicitCastExpr>(E) &&
@@ -5549,83 +5605,26 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
         RawAddress Addr = I->hasLValue()
                               ? I->getKnownLValue().getAddress()
                               : I->getKnownRValue().getAggregateAddress();
-        if (CallArgs.shouldForceWriteback()) {
-          RawAddress Dest = RawAddress::invalid();
-          if (!ArgInfo.getInAllocaIndirect()) {
-            Dest = Builder.CreateStructGEP(ArgMemory,
-                                           ArgInfo.getInAllocaFieldIndex());
-          } else {
-            Dest = CreateMemTemp(info_it->type, "inalloca.indirect.tmp");
-            Address ArgSlot = Builder.CreateStructGEP(
-                ArgMemory, ArgInfo.getInAllocaFieldIndex());
-            Builder.CreateStore(Dest.getPointer(), ArgSlot);
-          }
-          const CXXRecordDecl *RD = info_it->type->getAsCXXRecordDecl();
-          if (RD && !RD->isTriviallyCopyable()) {
-            const CXXConstructorDecl *CopyCtor = nullptr;
-            const CXXConstructorDecl *MoveCtor = nullptr;
-            for (const CXXConstructorDecl *C : RD->ctors()) {
-              if (C->isDeleted())
-                continue;
-              if (C->isMoveConstructor()) {
-                MoveCtor = C;
-              } else if (C->isCopyConstructor()) {
-                if (!CopyCtor || C->getParamDecl(0)
-                                     ->getType()
-                                     ->getPointeeType()
-                                     .isConstQualified())
-                  CopyCtor = C;
-              }
-            }
-            const CXXConstructorDecl *Ctor = MoveCtor ? MoveCtor : CopyCtor;
-            if (!Ctor || Ctor->isDeleted()) {
-              CGM.Error(Loc, "coroutine argument must be copyable or movable "
-                             "on this target");
-              EmitAggregateCopy(MakeAddrLValue(Dest, info_it->type),
-                                MakeAddrLValue(Addr, info_it->type),
-                                info_it->type, AggValueSlot::DoesNotOverlap);
-            } else {
-              CallArgList CtorArgs;
-              llvm::Value *ThisPtr = getAsNaturalPointerTo(
-                  Dest, Ctor->getThisType()->getPointeeType());
-              CtorArgs.add(RValue::get(ThisPtr), Ctor->getThisType());
-              QualType ParamTy = Ctor->getParamDecl(0)->getType();
-              llvm::Value *SrcPtr =
-                  getAsNaturalPointerTo(Addr, ParamTy->getPointeeType());
-              CtorArgs.add(RValue::get(SrcPtr), ParamTy);
-              EmitCXXConstructorCall(Ctor, Ctor_Complete,
-                                     /*ForVirtualBase*/ false,
-                                     /*Delegating*/ false, Dest, CtorArgs,
-                                     AggValueSlot::DoesNotOverlap, Loc,
-                                     /*NewPointerIsChecked*/ false);
-            }
-          } else {
-            EmitAggregateCopy(MakeAddrLValue(Dest, info_it->type),
-                              MakeAddrLValue(Addr, info_it->type),
-                              info_it->type, AggValueSlot::DoesNotOverlap);
-          }
+        llvm::Instruction *Placeholder =
+            cast<llvm::Instruction>(Addr.getPointer());
+
+        if (!ArgInfo.getInAllocaIndirect()) {
+          // Replace the placeholder with the appropriate argument slot GEP.
+          CGBuilderTy::InsertPoint IP = Builder.saveIP();
+          Builder.SetInsertPoint(Placeholder);
+          Addr = Builder.CreateStructGEP(ArgMemory,
+                                         ArgInfo.getInAllocaFieldIndex());
+          Builder.restoreIP(IP);
         } else {
-          llvm::Instruction *Placeholder =
-              cast<llvm::Instruction>(Addr.getPointer());
-
-          if (!ArgInfo.getInAllocaIndirect()) {
-            // Replace the placeholder with the appropriate argument slot GEP.
-            CGBuilderTy::InsertPoint IP = Builder.saveIP();
-            Builder.SetInsertPoint(Placeholder);
-            Addr = Builder.CreateStructGEP(ArgMemory,
-                                           ArgInfo.getInAllocaFieldIndex());
-            Builder.restoreIP(IP);
-          } else {
-            // For indirect things such as overaligned structs, replace the
-            // placeholder with a regular aggregate temporary alloca. Store the
-            // address of this alloca into the struct.
-            Addr = CreateMemTemp(info_it->type, "inalloca.indirect.tmp");
-            Address ArgSlot = Builder.CreateStructGEP(
-                ArgMemory, ArgInfo.getInAllocaFieldIndex());
-            Builder.CreateStore(Addr.getPointer(), ArgSlot);
-          }
-          deferPlaceholderReplacement(Placeholder, Addr.getPointer());
+          // For indirect things such as overaligned structs, replace the
+          // placeholder with a regular aggregate temporary alloca. Store the
+          // address of this alloca into the struct.
+          Addr = CreateMemTemp(info_it->type, "inalloca.indirect.tmp");
+          Address ArgSlot = Builder.CreateStructGEP(
+              ArgMemory, ArgInfo.getInAllocaFieldIndex());
+          Builder.CreateStore(Addr.getPointer(), ArgSlot);
         }
+        deferPlaceholderReplacement(Placeholder, Addr.getPointer());
       } else if (ArgInfo.getInAllocaIndirect()) {
         // Make a temporary alloca and store the address of it into the argument
         // struct.
diff --git a/clang/lib/CodeGen/CGCall.h b/clang/lib/CodeGen/CGCall.h
index 488296439eb0c..145992652934f 100644
--- a/clang/lib/CodeGen/CGCall.h
+++ b/clang/lib/CodeGen/CGCall.h
@@ -357,9 +357,6 @@ class CallArgList : public SmallVector<CallArg, 8> {
     std::reverse(Writebacks.begin(), Writebacks.end());
   }
 
-  bool shouldForceWriteback() const { return ForceWriteback; }
-  void setForceWriteback(bool V) { ForceWriteback = V; }
-
 private:
   SmallVector<Writeback, 1> Writebacks;
 
@@ -370,8 +367,6 @@ class CallArgList : public SmallVector<CallArg, 8> {
 
   /// The stacksave call.  It dominates all of the argument evaluation.
   llvm::CallInst *StackBase = nullptr;
-
-  bool ForceWriteback = false;
 };
 
 /// FunctionArgList - Type for representing both the decl and type
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 23802cdeb4811..bd08e056798e3 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -513,6 +513,11 @@ static bool isAAPCS(const TargetInfo &TargetInfo) {
 
 LValue CodeGenFunction::
 EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *M) {
+  auto It = PreEvaluatedMaterializedTemporaries.find(M);
+  if (It != PreEvaluatedMaterializedTemporaries.end()) {
+    return It->second;
+  }
+
   const Expr *E = M->getSubExpr();
 
   assert((!M->getExtendingDecl() || !isa<VarDecl>(M->getExtendingDecl()) ||
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index 3a4291719da74..9d58c3e6aa36d 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -754,8 +754,58 @@ void AggExprEmitter::EmitArrayInit(Address DestPtr, llvm::ArrayType *AType,
 //                            Visitor Methods
 //===----------------------------------------------------------------------===//
 
+static void emitNonTrivialCopyOrMove(CodeGenFunction &CGF, AggValueSlot Dest,
+                                     LValue SrcLV, QualType Type) {
+  const CXXRecordDecl *RD = Type->getAsCXXRecordDecl();
+  assert(RD && "Type must be a CXXRecordDecl");
+
+  const CXXConstructorDecl *CopyCtor = nullptr;
+  const CXXConstructorDecl *MoveCtor = nullptr;
+  for (const CXXConstructorDecl *C : RD->ctors()) {
+    if (C->isDeleted())
+      continue;
+    if (C->isMoveConstructor()) {
+      MoveCtor = C;
+    } else if (C->isCopyConstructor()) {
+      if (!CopyCtor ||
+          C->getParamDecl(0)->getType()->getPointeeType().isConstQualified())
+        CopyCtor = C;
+    }
+  }
+
+  const CXXConstructorDecl *Ctor = MoveCtor ? MoveCtor : CopyCtor;
+  assert(Ctor && "No copy or move constructor found");
+
+  CallArgList CtorArgs;
+  llvm::Value *ThisPtr = CGF.getAsNaturalPointerTo(
+      Dest.getAddress(), Ctor->getThisType()->getPointeeType());
+  CtorArgs.add(RValue::get(ThisPtr), Ctor->getThisType());
+
+  QualType ParamTy = Ctor->getParamDecl(0)->getType();
+  llvm::Value *SrcPtr =
+      CGF.getAsNaturalPointerTo(SrcLV.getAddress(), ParamTy->getPointeeType());
+  CtorArgs.add(RValue::get(SrcPtr), ParamTy);
+
+  CGF.EmitCXXConstructorCall(Ctor, Ctor_Complete,
+                             /*ForVirtualBase*/ false,
+                             /*Delegating*/ false, Dest.getAddress(), CtorArgs,
+                             AggValueSlot::DoesNotOverlap, SourceLocation(),
+                             /*NewPointerIsChecked*/ false);
+}
+
 void AggExprEmitter::VisitMaterializeTemporaryExpr(
     MaterializeTemporaryExpr *E) {
+  if (CGF.hasPreEvaluatedTemporary(E)) {
+    LValue SrcLV = CGF.getPreEvaluatedTemporary(E);
+    QualType Type = E->getType();
+    const CXXRecordDecl *RD = Type->getAsCXXRecordDecl();
+    if (RD && !RD->isTriviallyCopyable()) {
+      emitNonTrivialCopyOrMove(CGF, Dest, SrcLV, Type);
+    } else {
+      EmitFinalDestCopy(Type, SrcLV);
+    }
+    return;
+  }
   Visit(E->getSubExpr());
 }
 
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index fd474c09044ef..9ed5e9b532b62 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -1772,6 +1772,8 @@ class CodeGenFunction : public CodeGenTypeCache {
   /// expressions.
   llvm::DenseMap<const OpaqueValueExpr *, LValue> OpaqueLValues;
   llvm::DenseMap<const OpaqueValueExpr *, RValue> OpaqueRValues;
+  llvm::DenseMap<const MaterializeTemporaryExpr *, LValue>
+      PreEvaluatedMaterializedTemporaries;
 
   // VLASizeMap - This keeps track of the associated size for each VLA type.
   // We track this by the size expression rather than the type itself because
@@ -3098,6 +3100,16 @@ class CodeGenFunction : public CodeGenTypeCache {
   /// already been emitted.
   bool isOpaqueValueEmitted(const OpaqueValueExpr *E);
 
+  bool hasPreEvaluatedTemporary(const MaterializeTemporaryExpr *M) const {
+    return PreEvaluatedMaterializedTemporaries.count(M);
+  }
+  LValue getPreEvaluatedTemporary(const MaterializeTemporaryExpr *M) {
+    auto It = PreEvaluatedMaterializedTemporaries.find(M);
+    assert(It != PreEvaluatedMaterializedTemporaries.end() &&
+           "MTE is not pre-evaluated");
+    return It->second;
+  }
+
   /// Get the index of the current ArrayInitLoopExpr, if any.
   llvm::Value *getArrayInitIndex() { return ArrayInitIndex; }
 
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 9fd8c6a0a5451..632049ca354c7 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6209,6 +6209,55 @@ Sema::ConvertArgumentsForCall(CallExpr *Call, Expr *Fn,
   return false;
 }
 
+namespace {
+struct CoroSuspendFinder : DynamicRecursiveASTVisitor {
+  bool FoundSuspend = false;
+
+  CoroSuspendFinder() { ShouldVisitImplicitCode = true; }
+
+  bool VisitCoawaitExpr(CoawaitExpr *E) override {
+    FoundSuspend = true;
+    return false; // Stop traversal
+  }
+  bool VisitCoyieldExpr(CoyieldExpr *E) override {
+    FoundSuspend = true;
+    return false; // Stop traversal
+  }
+};
+} // namespace
+
+static bool containsCoroSuspend(const Expr *E) {
+  if (!E)
+    return false;
+  CoroSuspendFinder Finder;
+  Finder.TraverseStmt(const_cast<Expr *>(E));
+  return Finder.FoundSuspend;
+}
+
+static bool isWin32InAllocaRecord(ASTContext &Context, QualType Ty) {
+  const RecordType *RT = Ty->getAs<RecordType>();
+  if (!RT)
+    return false;
+  const CXXRecordDecl *RD = dyn_cast<CXXRecordDecl>(RT->getDecl());
+  if (!RD)
+    return false;
+
+  const llvm::Triple &Triple = Context.getTargetInfo().getTriple();
+  if (Triple.getArch() != llvm::Triple::x86 || !Triple.isOSWindows())
+    return false;
+  if (!Context.getTargetInfo().getCXXABI().isMicrosoft())
+    return false;
+
+  if (RD->canPassInRegisters())
+    return false;
+
+  TypeInfo Info = Context.getTypeInfo(Context.getCanonicalTagType(RD));
+  if (Info.isAlignRequired() && Info.Align > 4)
+    return false; // passed indirectly
+
+  return true;
+}
+
 bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
                                   const FunctionProtoType *Proto,
                                   unsigned FirstParam, ArrayRef<Expr *> Args,
@@ -6216,6 +6265,15 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
                                   VariadicCallType CallType, bool AllowExplicit,
                                   bool IsListInitialization) {
   unsigned NumParams = Proto->getNumParams();
+  bool CallHasSuspend = false;
+  if (getCurFunction() && getCurFunction()->isCoroutine()) {
+    for (const Expr *A : Args) {
+      if (containsCoroSuspend(A)) {
+        CallHasSuspend = true;
+        break;
+      }
+    }
+  }
   bool Invalid = false;
   size_t ArgIx = 0;
   // Continue to check argument types (even if we have too few/many args).
@@ -6275,6 +6333,13 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
                         : diag::warn_obt_discarded_at_function_boundary)
             << Arg->getType() << ProtoArgType;
       }
+      if (CallHasSuspend && Arg->isPRValue() &&
+          isWin32InAllocaRecord(Context, ProtoArgType)) {
+        ExprResult Materialized =
+            CreateMaterializeTemporaryExpr(Arg->getType(), Arg, false);
+        if (!Materialized.isInvalid())
+          Arg = Materialized.get();
+      }
 
       ExprResult ArgE = PerformCopyInitialization(
           Entity, SourceLocation(), Arg, IsListInitialization, AllowExplicit);

>From d134ed5ed5be8f70248b3be2682cef0becfdeb55 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Wed, 29 Jul 2026 10:49:40 +0000
Subject: [PATCH 03/26] Self review

---
 clang/include/clang/AST/Expr.h  |  4 +++
 clang/lib/AST/Expr.cpp          | 16 +++++++++++
 clang/lib/CodeGen/CGCall.cpp    | 27 ++++--------------
 clang/lib/CodeGen/CGExprAgg.cpp | 49 +++------------------------------
 clang/lib/CodeGen/CGExprCXX.cpp | 11 +++++++-
 clang/lib/Sema/SemaExpr.cpp     | 27 +-----------------
 6 files changed, 40 insertions(+), 94 deletions(-)

diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index a0ab599fa82d2..7ba25ea1295f8 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -247,6 +247,10 @@ class Expr : public ValueStmt {
     return static_cast<bool>(getDependence() & ExprDependence::Error);
   }
 
+  /// Whether this expression contains a coroutine suspend point
+  /// (co_await or co_yield).
+  bool containsCoroutineSuspendPoints() const;
+
   /// getExprLoc - Return the preferred location for the arrow when diagnosing
   /// a problem with a generic expression.
   SourceLocation getExprLoc() const LLVM_READONLY;
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 64d61dbc3d128..91d7fe0ffec70 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -3986,6 +3986,22 @@ bool Expr::HasSideEffects(const ASTContext &Ctx,
   return false;
 }
 
+static bool containsCoroutineSuspendPoints(const Stmt *S) {
+  if (!S)
+    return false;
+  if (isa<CoawaitExpr>(S) || isa<CoyieldExpr>(S) ||
+      isa<DependentCoawaitExpr>(S))
+    return true;
+  for (const Stmt *Child : S->children())
+    if (Child && containsCoroutineSuspendPoints(Child))
+      return true;
+  return false;
+}
+
+bool Expr::containsCoroutineSuspendPoints() const {
+  return ::containsCoroutineSuspendPoints(this);
+}
+
 FPOptions Expr::getFPFeaturesInEffect(const LangOptions &LO) const {
   if (auto Call = dyn_cast<CallExpr>(this))
     return Call->getFPFeaturesInEffect(LO);
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 0312b456df6cd..4cebb5b735dca 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -4790,27 +4790,6 @@ static bool isObjCMethodWithTypeParams(const ObjCMethodDecl *method) {
 }
 #endif
 
-namespace {
-class CoroSuspendFinder : public RecursiveASTVisitor<CoroSuspendFinder> {
-public:
-  bool FoundSuspend = false;
-  bool VisitCoawaitExpr(CoawaitExpr *) {
-    FoundSuspend = true;
-    return false; // Stop traversal
-  }
-  bool VisitCoyieldExpr(CoyieldExpr *) {
-    FoundSuspend = true;
-    return false; // Stop traversal
-  }
-};
-} // namespace
-
-static bool containsCoroSuspend(const Expr *E) {
-  CoroSuspendFinder Finder;
-  Finder.TraverseStmt(const_cast<Expr *>(E));
-  return Finder.FoundSuspend;
-}
-
 static const MaterializeTemporaryExpr *getMTEToPreEvaluate(const Expr *E) {
   while (true) {
     E = E->IgnoreParens();
@@ -4952,7 +4931,7 @@ void CodeGenFunction::EmitCallArgs(
   bool CallHasSuspend = false;
   if (isCoroutine()) {
     for (const Expr *A : ArgRange) {
-      if (containsCoroSuspend(A)) {
+      if (A && A->containsCoroutineSuspendPoints()) {
         CallHasSuspend = true;
         break;
       }
@@ -5517,6 +5496,10 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
     auto Align = CallInfo.getArgStructAlignment();
     AI->setAlignment(Align.getAsAlign());
     AI->setUsedWithInAlloca(true);
+    if (isCoroutine()) {
+      AI->setMetadata(llvm::LLVMContext::MD_coro_outside_frame,
+                      llvm::MDNode::get(CGM.getLLVMContext(), {}));
+    }
     assert(AI->isUsedWithInAlloca() && !AI->isStaticAlloca());
     ArgMemory = RawAddress(AI, ArgStruct, Align);
     if (isCoroutine()) {
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index 9d58c3e6aa36d..fe4f15c2ad882 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -754,56 +754,15 @@ void AggExprEmitter::EmitArrayInit(Address DestPtr, llvm::ArrayType *AType,
 //                            Visitor Methods
 //===----------------------------------------------------------------------===//
 
-static void emitNonTrivialCopyOrMove(CodeGenFunction &CGF, AggValueSlot Dest,
-                                     LValue SrcLV, QualType Type) {
-  const CXXRecordDecl *RD = Type->getAsCXXRecordDecl();
-  assert(RD && "Type must be a CXXRecordDecl");
-
-  const CXXConstructorDecl *CopyCtor = nullptr;
-  const CXXConstructorDecl *MoveCtor = nullptr;
-  for (const CXXConstructorDecl *C : RD->ctors()) {
-    if (C->isDeleted())
-      continue;
-    if (C->isMoveConstructor()) {
-      MoveCtor = C;
-    } else if (C->isCopyConstructor()) {
-      if (!CopyCtor ||
-          C->getParamDecl(0)->getType()->getPointeeType().isConstQualified())
-        CopyCtor = C;
-    }
-  }
-
-  const CXXConstructorDecl *Ctor = MoveCtor ? MoveCtor : CopyCtor;
-  assert(Ctor && "No copy or move constructor found");
-
-  CallArgList CtorArgs;
-  llvm::Value *ThisPtr = CGF.getAsNaturalPointerTo(
-      Dest.getAddress(), Ctor->getThisType()->getPointeeType());
-  CtorArgs.add(RValue::get(ThisPtr), Ctor->getThisType());
-
-  QualType ParamTy = Ctor->getParamDecl(0)->getType();
-  llvm::Value *SrcPtr =
-      CGF.getAsNaturalPointerTo(SrcLV.getAddress(), ParamTy->getPointeeType());
-  CtorArgs.add(RValue::get(SrcPtr), ParamTy);
-
-  CGF.EmitCXXConstructorCall(Ctor, Ctor_Complete,
-                             /*ForVirtualBase*/ false,
-                             /*Delegating*/ false, Dest.getAddress(), CtorArgs,
-                             AggValueSlot::DoesNotOverlap, SourceLocation(),
-                             /*NewPointerIsChecked*/ false);
-}
-
 void AggExprEmitter::VisitMaterializeTemporaryExpr(
     MaterializeTemporaryExpr *E) {
   if (CGF.hasPreEvaluatedTemporary(E)) {
     LValue SrcLV = CGF.getPreEvaluatedTemporary(E);
     QualType Type = E->getType();
-    const CXXRecordDecl *RD = Type->getAsCXXRecordDecl();
-    if (RD && !RD->isTriviallyCopyable()) {
-      emitNonTrivialCopyOrMove(CGF, Dest, SrcLV, Type);
-    } else {
-      EmitFinalDestCopy(Type, SrcLV);
-    }
+    assert((!Type->getAsCXXRecordDecl() ||
+            Type->getAsCXXRecordDecl()->isTriviallyCopyable()) &&
+           "Non-trivially copyable MTE should not be visited as aggregate when pre-evaluated");
+    EmitFinalDestCopy(Type, SrcLV);
     return;
   }
   Visit(E->getSubExpr());
diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index 82300c3ede183..03d551dda62db 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -624,7 +624,16 @@ void CodeGenFunction::EmitCXXConstructExpr(const CXXConstructExpr *E,
     return;
 
   // Elide the constructor if we're constructing from a temporary.
-  if (getLangOpts().ElideConstructors && E->isElidable()) {
+  bool DisableElision = false;
+  if (E->getNumArgs() > 0) {
+    if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(
+            E->getArg(0)->IgnoreImpCasts())) {
+      if (hasPreEvaluatedTemporary(MTE))
+        DisableElision = true;
+    }
+  }
+
+  if (getLangOpts().ElideConstructors && E->isElidable() && !DisableElision) {
     // FIXME: This only handles the simplest case, where the source object
     //        is passed directly as the first argument to the constructor.
     //        This should also handle stepping though implicit casts and
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 632049ca354c7..5777efbf958f1 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6209,31 +6209,6 @@ Sema::ConvertArgumentsForCall(CallExpr *Call, Expr *Fn,
   return false;
 }
 
-namespace {
-struct CoroSuspendFinder : DynamicRecursiveASTVisitor {
-  bool FoundSuspend = false;
-
-  CoroSuspendFinder() { ShouldVisitImplicitCode = true; }
-
-  bool VisitCoawaitExpr(CoawaitExpr *E) override {
-    FoundSuspend = true;
-    return false; // Stop traversal
-  }
-  bool VisitCoyieldExpr(CoyieldExpr *E) override {
-    FoundSuspend = true;
-    return false; // Stop traversal
-  }
-};
-} // namespace
-
-static bool containsCoroSuspend(const Expr *E) {
-  if (!E)
-    return false;
-  CoroSuspendFinder Finder;
-  Finder.TraverseStmt(const_cast<Expr *>(E));
-  return Finder.FoundSuspend;
-}
-
 static bool isWin32InAllocaRecord(ASTContext &Context, QualType Ty) {
   const RecordType *RT = Ty->getAs<RecordType>();
   if (!RT)
@@ -6268,7 +6243,7 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
   bool CallHasSuspend = false;
   if (getCurFunction() && getCurFunction()->isCoroutine()) {
     for (const Expr *A : Args) {
-      if (containsCoroSuspend(A)) {
+      if (A && A->containsCoroutineSuspendPoints()) {
         CallHasSuspend = true;
         break;
       }

>From a28bee92fcf5fbf3121cb5ec58aee0bd4766d1a8 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Wed, 29 Jul 2026 11:05:18 +0000
Subject: [PATCH 04/26] Self review

---
 clang/lib/CodeGen/CGCall.cpp | 12 +-----------
 1 file changed, 1 insertion(+), 11 deletions(-)

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 4cebb5b735dca..e87f332f34b76 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -4928,18 +4928,8 @@ void CodeGenFunction::EmitCallArgs(
       std::swap(Args.back(), *(&Args.back() - 1));
   };
 
-  bool CallHasSuspend = false;
-  if (isCoroutine()) {
-    for (const Expr *A : ArgRange) {
-      if (A && A->containsCoroutineSuspendPoints()) {
-        CallHasSuspend = true;
-        break;
-      }
-    }
-  }
-
   SmallVector<const MaterializeTemporaryExpr *, 4> MTEsToPreEvaluate;
-  if (CallHasSuspend && hasInAllocaArgs(CGM, ExplicitCC, ArgTypes)) {
+  if (isCoroutine() && hasInAllocaArgs(CGM, ExplicitCC, ArgTypes)) {
     for (const Expr *A : ArgRange) {
       if (auto *MTE = getMTEToPreEvaluate(A)) {
         MTEsToPreEvaluate.push_back(MTE);

>From 3cfb63fea8960d31f15aefc5612964a2309caf64 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Wed, 29 Jul 2026 11:15:23 +0000
Subject: [PATCH 05/26] Add test

---
 .../CodeGenCoroutines/coro-win32-inalloca.cpp | 93 +++++++++++++++++++
 1 file changed, 93 insertions(+)
 create mode 100644 clang/test/CodeGenCoroutines/coro-win32-inalloca.cpp

diff --git a/clang/test/CodeGenCoroutines/coro-win32-inalloca.cpp b/clang/test/CodeGenCoroutines/coro-win32-inalloca.cpp
new file mode 100644
index 0000000000000..34d1d0d81d943
--- /dev/null
+++ b/clang/test/CodeGenCoroutines/coro-win32-inalloca.cpp
@@ -0,0 +1,93 @@
+// RUN: %clang_cc1 -std=c++20 -triple=i686-pc-windows-msvc -emit-llvm -o - %s -disable-llvm-passes | FileCheck %s
+
+namespace std {
+template <typename R, typename... Args>
+struct coroutine_traits {
+  using promise_type = typename R::promise_type;
+};
+
+template <class Promise = void> struct coroutine_handle {
+  coroutine_handle() = default;
+  static coroutine_handle from_address(void *) noexcept;
+};
+template <> struct coroutine_handle<void> {
+  static coroutine_handle from_address(void *) noexcept;
+  coroutine_handle() = default;
+  template <class PromiseType>
+  coroutine_handle(coroutine_handle<PromiseType>) noexcept;
+};
+} // namespace std
+
+struct suspend_always {
+  bool await_ready() noexcept { return false; }
+  void await_suspend(std::coroutine_handle<>) noexcept {}
+  void await_resume() noexcept {}
+};
+
+struct suspend_never {
+  bool await_ready() noexcept { return true; }
+  void await_suspend(std::coroutine_handle<>) noexcept {}
+  void await_resume() noexcept {}
+};
+
+struct task {
+  struct promise_type {
+    task get_return_object() { return {}; }
+    suspend_never initial_suspend() { return {}; }
+    suspend_never final_suspend() noexcept { return {}; }
+    void return_void() {}
+    void unhandled_exception() {}
+  };
+};
+
+struct Noisy {
+  int val;
+  Noisy(int v);
+  Noisy(const Noisy&) = delete;
+  Noisy(Noisy&& o) noexcept;
+  ~Noisy();
+};
+
+struct Awaiter {
+  bool await_ready() noexcept { return false; }
+  void await_suspend(std::coroutine_handle<>) noexcept {}
+  Noisy await_resume() noexcept;
+};
+
+void consume_two(Noisy x, Noisy y);
+
+// CHECK-LABEL: define dso_local void @"?my_task@@YA?AUtask@@XZ"(
+task my_task() {
+  // CHECK: %[[MTE_Y:.+]] = alloca %struct.Noisy,
+  // CHECK: %[[MTE_X:.+]] = alloca %struct.Noisy,
+  
+  // Evaluate Noisy(42) before suspend:
+  // CHECK: call x86_thiscallcc noundef ptr @"??0Noisy@@QAE at H@Z"(ptr {{[^,]*}} %[[MTE_Y]], i32 noundef 42)
+  
+  // Suspend for co_await Awaiter{}:
+  // CHECK: call void @llvm.coro.await.suspend.void(
+  // CHECK: call i8 @llvm.coro.suspend(
+
+  // After resume:
+  // CHECK: call x86_thiscallcc void @"?await_resume at Awaiter@@QAE?AUNoisy@@XZ"(ptr {{[^,]*}} %{{.*}}, ptr dead_on_unwind writable sret(%struct.Noisy) align 4 %[[MTE_X]])
+  
+  // Allocate inalloca:
+  // CHECK: %[[STACKSAVE:.+]] = call ptr @llvm.stacksave.p0()
+  // CHECK: %[[ARGMEM:.+]] = alloca inalloca <{ %struct.Noisy, %struct.Noisy }>, align 4, !coro.outside.frame ![[METADATA_NUM:[0-9]+]]
+  
+  // Move y (pre-evaluated Noisy(42)) to inalloca:
+  // CHECK: %[[GEP_Y:.+]] = getelementptr inbounds nuw <{ %struct.Noisy, %struct.Noisy }>, ptr %[[ARGMEM]], i32 0, i32 1
+  // CHECK: call x86_thiscallcc noundef ptr @"??0Noisy@@QAE@$$QAU0@@Z"(ptr {{[^,]*}} %[[GEP_Y]], ptr noundef nonnull align 4 dereferenceable(4) %[[MTE_Y]])
+  
+  // Move x (co_await Awaiter{} result) to inalloca:
+  // CHECK: %[[GEP_X:.+]] = getelementptr inbounds nuw <{ %struct.Noisy, %struct.Noisy }>, ptr %[[ARGMEM]], i32 0, i32 0
+  // CHECK: call x86_thiscallcc noundef ptr @"??0Noisy@@QAE@$$QAU0@@Z"(ptr {{[^,]*}} %[[GEP_X]], ptr noundef nonnull align 4 dereferenceable(4) %[[MTE_X]])
+
+  // Lifetime start and call:
+  // CHECK: call void @llvm.lifetime.start.p0(ptr %[[ARGMEM]])
+  // CHECK: call void @"?consume_two@@YAXUNoisy@@0 at Z"(ptr inalloca(<{ %struct.Noisy, %struct.Noisy }>) %[[ARGMEM]])
+  
+  consume_two(co_await Awaiter{}, Noisy(42));
+}
+
+// CHECK: ![[METADATA_NUM]] = !{}

>From aa554816facaeb02bb60783fa180c1a20529fc6b Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Wed, 29 Jul 2026 11:22:21 +0000
Subject: [PATCH 06/26] Format

---
 clang/lib/CodeGen/CGExprAgg.cpp | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index 1eb0b65e1f491..0c0dde5aa5178 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -782,7 +782,8 @@ void AggExprEmitter::VisitMaterializeTemporaryExpr(
     QualType Type = E->getType();
     assert((!Type->getAsCXXRecordDecl() ||
             Type->getAsCXXRecordDecl()->isTriviallyCopyable()) &&
-           "Non-trivially copyable MTE should not be visited as aggregate when pre-evaluated");
+           "Non-trivially copyable MTE should not be visited as aggregate when "
+           "pre-evaluated");
     EmitFinalDestCopy(Type, SrcLV);
     return;
   }

>From 2dff9384a3d53b87cd49a819326f6346531fb745 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Wed, 29 Jul 2026 18:32:12 +0000
Subject: [PATCH 07/26] Fix lambda expressions

---
 clang/lib/AST/Expr.cpp      | 9 +++++++++
 clang/lib/Sema/SemaExpr.cpp | 3 ++-
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 22a697fe09327..0bcd38ae97450 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -4004,6 +4004,15 @@ static bool containsCoroutineSuspendPoints(const Stmt *S) {
   if (isa<CoawaitExpr>(S) || isa<CoyieldExpr>(S) ||
       isa<DependentCoawaitExpr>(S))
     return true;
+
+  if (auto *LE = dyn_cast<LambdaExpr>(S)) {
+    for (const Expr *Init : LE->capture_inits()) {
+      if (Init && containsCoroutineSuspendPoints(Init))
+        return true;
+    }
+    return false;
+  }
+
   for (const Stmt *Child : S->children())
     if (Child && containsCoroutineSuspendPoints(Child))
       return true;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 5f71404fb8c5f..15475c2ec8773 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6350,7 +6350,8 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
             << Arg->getType() << ProtoArgType;
       }
       if (CallHasSuspend && Arg->isPRValue() &&
-          isWin32InAllocaRecord(Context, ProtoArgType)) {
+          (isWin32InAllocaRecord(Context, ProtoArgType) ||
+           Arg->containsCoroutineSuspendPoints())) {
         ExprResult Materialized =
             CreateMaterializeTemporaryExpr(Arg->getType(), Arg, false);
         if (!Materialized.isInvalid())

>From b5c45875f35e3c67fc72c591d66444d8874d8778 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Wed, 29 Jul 2026 18:58:54 +0000
Subject: [PATCH 08/26] Add AST node

---
 clang/include/clang/AST/ComputeDependence.h   |  2 +
 clang/include/clang/AST/ExprCXX.h             | 49 +++++++++++++++++++
 clang/include/clang/AST/RecursiveASTVisitor.h |  7 +++
 clang/include/clang/Basic/StmtNodes.td        |  1 +
 .../include/clang/Serialization/ASTBitCodes.h |  1 +
 clang/lib/AST/ComputeDependence.cpp           |  5 ++
 clang/lib/AST/ExprCXX.cpp                     |  6 +++
 clang/lib/AST/ExprConstant.cpp                |  1 +
 clang/lib/AST/StmtPrinter.cpp                 |  5 ++
 clang/lib/AST/StmtProfile.cpp                 |  5 ++
 clang/lib/CodeGen/CGCall.cpp                  | 47 ++----------------
 clang/lib/CodeGen/CGExprAgg.cpp               |  7 +++
 clang/lib/CodeGen/CGExprComplex.cpp           |  5 ++
 clang/lib/CodeGen/CGExprScalar.cpp            |  5 ++
 clang/lib/Sema/SemaExpr.cpp                   | 20 +++++---
 clang/lib/Sema/TreeTransform.h                |  6 +++
 clang/lib/Serialization/ASTReaderStmt.cpp     | 11 +++++
 clang/lib/Serialization/ASTWriterStmt.cpp     |  8 +++
 18 files changed, 143 insertions(+), 48 deletions(-)

diff --git a/clang/include/clang/AST/ComputeDependence.h b/clang/include/clang/AST/ComputeDependence.h
index 3a3c86842501a..0da1e99bf523c 100644
--- a/clang/include/clang/AST/ComputeDependence.h
+++ b/clang/include/clang/AST/ComputeDependence.h
@@ -69,6 +69,7 @@ class PackIndexingExpr;
 class SubstNonTypeTemplateParmExpr;
 class CoroutineSuspendExpr;
 class DependentCoawaitExpr;
+class CoroutineSuspendParameterBypassExpr;
 class CXXNewExpr;
 class CXXPseudoDestructorExpr;
 class OverloadExpr;
@@ -161,6 +162,7 @@ ExprDependence computeDependence(PackIndexingExpr *E);
 ExprDependence computeDependence(SubstNonTypeTemplateParmExpr *E);
 ExprDependence computeDependence(CoroutineSuspendExpr *E);
 ExprDependence computeDependence(DependentCoawaitExpr *E);
+ExprDependence computeDependence(CoroutineSuspendParameterBypassExpr *E);
 ExprDependence computeDependence(CXXNewExpr *E);
 ExprDependence computeDependence(CXXPseudoDestructorExpr *E);
 ExprDependence computeDependence(OverloadExpr *E, bool KnownDependent,
diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index d3d3b9c6d6326..65af878212ff7 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -5554,6 +5554,55 @@ class CXXReflectExpr : public Expr {
 
 /// Helper that selects an expression from an InitListExpr depending on the
 /// current expansion index. See 'CXXExpansionStmtPattern' for how this is used.
+/// Represents a temporary that needs to be pre-evaluated in a coroutine
+/// because it is passed to a call that contains suspend points, and we
+/// want to delay inalloca allocation until after all suspends.
+class CoroutineSuspendParameterBypassExpr : public Expr {
+  friend class ASTStmtReader;
+
+  Stmt *SubExpr = nullptr;
+  Stmt *MoveExpr = nullptr;
+
+  CoroutineSuspendParameterBypassExpr(Expr *SubExpr, Expr *MoveExpr)
+      : Expr(CoroutineSuspendParameterBypassExprClass, MoveExpr->getType(),
+             MoveExpr->getValueKind(), MoveExpr->getObjectKind()),
+        SubExpr(SubExpr), MoveExpr(MoveExpr) {
+    setDependence(computeDependence(this));
+  }
+
+public:
+  CoroutineSuspendParameterBypassExpr(EmptyShell Empty)
+      : Expr(CoroutineSuspendParameterBypassExprClass, Empty) {}
+
+  static CoroutineSuspendParameterBypassExpr *
+  Create(const ASTContext &C, Expr *SubExpr, Expr *MoveExpr);
+
+  Expr *getSubExpr() { return cast<Expr>(SubExpr); }
+  const Expr *getSubExpr() const { return cast<Expr>(SubExpr); }
+  void setSubExpr(Expr *E) { SubExpr = E; }
+
+  Expr *getMoveExpr() { return cast<Expr>(MoveExpr); }
+  const Expr *getMoveExpr() const { return cast<Expr>(MoveExpr); }
+  void setMoveExpr(Expr *E) { MoveExpr = E; }
+
+  SourceLocation getBeginLoc() const LLVM_READONLY {
+    return SubExpr->getBeginLoc();
+  }
+  SourceLocation getEndLoc() const LLVM_READONLY {
+    return MoveExpr->getEndLoc();
+  }
+
+  static bool classof(const Stmt *T) {
+    return T->getStmtClass() == CoroutineSuspendParameterBypassExprClass;
+  }
+
+  child_range children() { return child_range(&MoveExpr, &MoveExpr + 1); }
+
+  const_child_range children() const {
+    return const_child_range(&MoveExpr, &MoveExpr + 1);
+  }
+};
+
 class CXXExpansionSelectExpr : public Expr {
   friend class ASTStmtReader;
 
diff --git a/clang/include/clang/AST/RecursiveASTVisitor.h b/clang/include/clang/AST/RecursiveASTVisitor.h
index cdf8a71d54cc9..2fae8e87ee917 100644
--- a/clang/include/clang/AST/RecursiveASTVisitor.h
+++ b/clang/include/clang/AST/RecursiveASTVisitor.h
@@ -3163,6 +3163,13 @@ DEF_TRAVERSE_STMT(CoyieldExpr, {
   }
 })
 
+DEF_TRAVERSE_STMT(CoroutineSuspendParameterBypassExpr, {
+  if (!getDerived().shouldVisitImplicitCode()) {
+    TRY_TO_TRAVERSE_OR_ENQUEUE_STMT(S->getSubExpr());
+    ShouldVisitChildren = false;
+  }
+})
+
 DEF_TRAVERSE_STMT(ConceptSpecializationExpr, {
   TRY_TO(TraverseConceptReference(S->getConceptReference()));
 })
diff --git a/clang/include/clang/Basic/StmtNodes.td b/clang/include/clang/Basic/StmtNodes.td
index f5fa397c92ef3..4c1dd6ba01661 100644
--- a/clang/include/clang/Basic/StmtNodes.td
+++ b/clang/include/clang/Basic/StmtNodes.td
@@ -181,6 +181,7 @@ def CoroutineSuspendExpr : StmtNode<Expr, 1>;
 def CoawaitExpr : StmtNode<CoroutineSuspendExpr>;
 def DependentCoawaitExpr : StmtNode<Expr>;
 def CoyieldExpr : StmtNode<CoroutineSuspendExpr>;
+def CoroutineSuspendParameterBypassExpr : StmtNode<Expr>;
 
 // C++20 Concepts expressions
 def ConceptSpecializationExpr : StmtNode<Expr>;
diff --git a/clang/include/clang/Serialization/ASTBitCodes.h b/clang/include/clang/Serialization/ASTBitCodes.h
index 671341488278e..b57472e56f832 100644
--- a/clang/include/clang/Serialization/ASTBitCodes.h
+++ b/clang/include/clang/Serialization/ASTBitCodes.h
@@ -2063,6 +2063,7 @@ enum StmtCode {
   EXPR_COAWAIT,
   EXPR_COYIELD,
   EXPR_DEPENDENT_COAWAIT,
+  EXPR_COROUTINE_SUSPEND_PARAMETER_BYPASS,
 
   // FixedPointLiteral
   EXPR_FIXEDPOINT_LITERAL,
diff --git a/clang/lib/AST/ComputeDependence.cpp b/clang/lib/AST/ComputeDependence.cpp
index a819bb6dec599..1cc40fb158cfb 100644
--- a/clang/lib/AST/ComputeDependence.cpp
+++ b/clang/lib/AST/ComputeDependence.cpp
@@ -344,6 +344,11 @@ ExprDependence clang::computeDependence(CXXBindTemporaryExpr *E) {
   return E->getSubExpr()->getDependence();
 }
 
+ExprDependence
+clang::computeDependence(CoroutineSuspendParameterBypassExpr *E) {
+  return E->getMoveExpr()->getDependence();
+}
+
 ExprDependence clang::computeDependence(CXXScalarValueInitExpr *E) {
   auto D = toExprDependenceForImpliedType(E->getType()->getDependence());
   if (auto *TSI = E->getTypeSourceInfo())
diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp
index 6c1cde6540d85..6ccad263760f8 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -1132,6 +1132,12 @@ CXXBindTemporaryExpr *CXXBindTemporaryExpr::Create(const ASTContext &C,
   return new (C) CXXBindTemporaryExpr(Temp, SubExpr);
 }
 
+CoroutineSuspendParameterBypassExpr *
+CoroutineSuspendParameterBypassExpr::Create(const ASTContext &C, Expr *SubExpr,
+                                            Expr *MoveExpr) {
+  return new (C) CoroutineSuspendParameterBypassExpr(SubExpr, MoveExpr);
+}
+
 CXXTemporaryObjectExpr::CXXTemporaryObjectExpr(
     CXXConstructorDecl *Cons, QualType Ty, TypeSourceInfo *TSI,
     ArrayRef<Expr *> Args, SourceRange ParenOrBraceRange,
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 9d69de2a7c6fd..5a0ed07d21e97 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -22378,6 +22378,7 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
   case Expr::CXXParenListInitExprClass:
   case Expr::HLSLOutArgExprClass:
   case Expr::CXXExpansionSelectExprClass:
+  case Expr::CoroutineSuspendParameterBypassExprClass:
     return ICEDiag(IK_NotICE, E->getBeginLoc());
 
   case Expr::MemberExprClass: {
diff --git a/clang/lib/AST/StmtPrinter.cpp b/clang/lib/AST/StmtPrinter.cpp
index 877191d456b35..2f29325bab682 100644
--- a/clang/lib/AST/StmtPrinter.cpp
+++ b/clang/lib/AST/StmtPrinter.cpp
@@ -2386,6 +2386,11 @@ void StmtPrinter::VisitCXXBindTemporaryExpr(CXXBindTemporaryExpr *Node) {
   PrintExpr(Node->getSubExpr());
 }
 
+void StmtPrinter::VisitCoroutineSuspendParameterBypassExpr(
+    CoroutineSuspendParameterBypassExpr *Node) {
+  PrintExpr(Node->getSubExpr());
+}
+
 void StmtPrinter::VisitCXXTemporaryObjectExpr(CXXTemporaryObjectExpr *Node) {
   Node->getType().print(OS, Policy);
   if (Node->isStdInitListInitialization())
diff --git a/clang/lib/AST/StmtProfile.cpp b/clang/lib/AST/StmtProfile.cpp
index 00c132f1ed9e0..dd9b7c06cd78c 100644
--- a/clang/lib/AST/StmtProfile.cpp
+++ b/clang/lib/AST/StmtProfile.cpp
@@ -2175,6 +2175,11 @@ void StmtProfiler::VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *S) {
          const_cast<CXXDestructorDecl *>(S->getTemporary()->getDestructor()));
 }
 
+void StmtProfiler::VisitCoroutineSuspendParameterBypassExpr(
+    const CoroutineSuspendParameterBypassExpr *S) {
+  VisitExpr(S);
+}
+
 void StmtProfiler::VisitCXXConstructExpr(const CXXConstructExpr *S) {
   VisitExpr(S);
   VisitDecl(S->getConstructor());
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 17f9c8abd7497..6cb6d7e249ac7 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5031,46 +5031,6 @@ static bool isObjCMethodWithTypeParams(const ObjCMethodDecl *method) {
 }
 #endif
 
-static const MaterializeTemporaryExpr *getMTEToPreEvaluate(const Expr *E) {
-  while (true) {
-    E = E->IgnoreParens();
-    if (auto *Cleanups = dyn_cast<ExprWithCleanups>(E)) {
-      E = Cleanups->getSubExpr();
-      continue;
-    }
-    if (auto *Bind = dyn_cast<CXXBindTemporaryExpr>(E)) {
-      E = Bind->getSubExpr();
-      continue;
-    }
-    break;
-  }
-
-  if (auto *CE = dyn_cast<CXXConstructExpr>(E)) {
-    if (CE->getNumArgs() > 0) {
-      const Expr *Arg = CE->getArg(0);
-      while (true) {
-        Arg = Arg->IgnoreParens();
-        if (auto *MTE = dyn_cast<MaterializeTemporaryExpr>(Arg))
-          return MTE;
-        if (auto *Cleanups = dyn_cast<ExprWithCleanups>(Arg)) {
-          Arg = Cleanups->getSubExpr();
-          continue;
-        }
-        if (auto *Bind = dyn_cast<CXXBindTemporaryExpr>(Arg)) {
-          Arg = Bind->getSubExpr();
-          continue;
-        }
-        if (auto *ICE = dyn_cast<ImplicitCastExpr>(Arg)) {
-          Arg = ICE->getSubExpr();
-          continue;
-        }
-        break;
-      }
-    }
-  }
-  return nullptr;
-}
-
 /// EmitCallArgs - Emit call arguments for a function.
 void CodeGenFunction::EmitCallArgs(
     CallArgList &Args, PrototypeWrapper Prototype,
@@ -5172,8 +5132,11 @@ void CodeGenFunction::EmitCallArgs(
   SmallVector<const MaterializeTemporaryExpr *, 4> MTEsToPreEvaluate;
   if (isCoroutine() && hasInAllocaArgs(CGM, ExplicitCC, ArgTypes)) {
     for (const Expr *A : ArgRange) {
-      if (auto *MTE = getMTEToPreEvaluate(A)) {
-        MTEsToPreEvaluate.push_back(MTE);
+      if (auto *Bypass = dyn_cast<CoroutineSuspendParameterBypassExpr>(A)) {
+        if (auto *MTE =
+                dyn_cast<MaterializeTemporaryExpr>(Bypass->getSubExpr())) {
+          MTEsToPreEvaluate.push_back(MTE);
+        }
       }
     }
   }
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index 0c0dde5aa5178..87673afc287aa 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -200,6 +200,8 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
   void VisitCXXTypeidExpr(CXXTypeidExpr *E) { EmitAggLoadOfLValue(E); }
   void VisitMaterializeTemporaryExpr(MaterializeTemporaryExpr *E);
   void VisitOpaqueValueExpr(OpaqueValueExpr *E);
+  void VisitCoroutineSuspendParameterBypassExpr(
+      CoroutineSuspendParameterBypassExpr *E);
 
   void VisitPseudoObjectExpr(PseudoObjectExpr *E) {
     if (E->isGLValue()) {
@@ -798,6 +800,11 @@ void AggExprEmitter::VisitOpaqueValueExpr(OpaqueValueExpr *e) {
     EmitFinalDestCopy(e->getType(), CGF.getOrCreateOpaqueLValueMapping(e));
 }
 
+void AggExprEmitter::VisitCoroutineSuspendParameterBypassExpr(
+    CoroutineSuspendParameterBypassExpr *E) {
+  Visit(E->getMoveExpr());
+}
+
 void AggExprEmitter::VisitCompoundLiteralExpr(CompoundLiteralExpr *E) {
   if (Dest.isPotentiallyAliased()) {
     // Just emit a load of the lvalue + a copy, because our compound literal
diff --git a/clang/lib/CodeGen/CGExprComplex.cpp b/clang/lib/CodeGen/CGExprComplex.cpp
index 350cbd18c7ed7..7c08d9eaff87d 100644
--- a/clang/lib/CodeGen/CGExprComplex.cpp
+++ b/clang/lib/CodeGen/CGExprComplex.cpp
@@ -169,6 +169,11 @@ class ComplexExprEmitter
     return CGF.getOrCreateOpaqueRValueMapping(E).getComplexVal();
   }
 
+  ComplexPairTy VisitCoroutineSuspendParameterBypassExpr(
+      CoroutineSuspendParameterBypassExpr *E) {
+    return Visit(E->getMoveExpr());
+  }
+
   ComplexPairTy VisitPseudoObjectExpr(PseudoObjectExpr *E) {
     return CGF.EmitPseudoObjectRValue(E).getComplexVal();
   }
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 8783b43846434..d3064d2498a03 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -600,6 +600,11 @@ class ScalarExprEmitter
     return CGF.getOrCreateOpaqueRValueMapping(E).getScalarVal();
   }
 
+  Value *VisitCoroutineSuspendParameterBypassExpr(
+      CoroutineSuspendParameterBypassExpr *E) {
+    return Visit(E->getMoveExpr());
+  }
+
   Value *VisitOpenACCAsteriskSizeExpr(OpenACCAsteriskSizeExpr *E) {
     llvm_unreachable("Codegen for this isn't defined/implemented");
   }
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 15475c2ec8773..be204239c72ba 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6349,21 +6349,29 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
                         : diag::warn_obt_discarded_at_function_boundary)
             << Arg->getType() << ProtoArgType;
       }
-      if (CallHasSuspend && Arg->isPRValue() &&
-          (isWin32InAllocaRecord(Context, ProtoArgType) ||
-           Arg->containsCoroutineSuspendPoints())) {
+      bool NeedsBypass = CallHasSuspend && Arg->isPRValue() &&
+                         (isWin32InAllocaRecord(Context, ProtoArgType) ||
+                          Arg->containsCoroutineSuspendPoints());
+      Expr *PreBypassArg = Arg;
+      if (NeedsBypass) {
         ExprResult Materialized =
             CreateMaterializeTemporaryExpr(Arg->getType(), Arg, false);
         if (!Materialized.isInvalid())
-          Arg = Materialized.get();
+          PreBypassArg = Materialized.get();
       }
 
-      ExprResult ArgE = PerformCopyInitialization(
-          Entity, SourceLocation(), Arg, IsListInitialization, AllowExplicit);
+      ExprResult ArgE =
+          PerformCopyInitialization(Entity, SourceLocation(), PreBypassArg,
+                                    IsListInitialization, AllowExplicit);
       if (ArgE.isInvalid())
         return true;
 
       Arg = ArgE.getAs<Expr>();
+
+      if (NeedsBypass) {
+        Arg = CoroutineSuspendParameterBypassExpr::Create(Context, PreBypassArg,
+                                                          Arg);
+      }
     } else {
       assert(Param && "can't use default arguments without a known callee");
 
diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h
index 0725664a4e050..b8563c29df616 100644
--- a/clang/lib/Sema/TreeTransform.h
+++ b/clang/lib/Sema/TreeTransform.h
@@ -15964,6 +15964,12 @@ TreeTransform<Derived>::TransformCXXBindTemporaryExpr(CXXBindTemporaryExpr *E) {
   return getDerived().TransformExpr(E->getSubExpr());
 }
 
+template <typename Derived>
+ExprResult TreeTransform<Derived>::TransformCoroutineSuspendParameterBypassExpr(
+    CoroutineSuspendParameterBypassExpr *E) {
+  return getDerived().TransformExpr(E->getSubExpr());
+}
+
 /// Transform a C++ expression that contains cleanups that should
 /// be run after the expression is evaluated.
 ///
diff --git a/clang/lib/Serialization/ASTReaderStmt.cpp b/clang/lib/Serialization/ASTReaderStmt.cpp
index 6cde6c1816dc7..313719d16d293 100644
--- a/clang/lib/Serialization/ASTReaderStmt.cpp
+++ b/clang/lib/Serialization/ASTReaderStmt.cpp
@@ -512,6 +512,13 @@ void ASTStmtReader::VisitDependentCoawaitExpr(DependentCoawaitExpr *E) {
     SubExpr = Record.readSubStmt();
 }
 
+void ASTStmtReader::VisitCoroutineSuspendParameterBypassExpr(
+    CoroutineSuspendParameterBypassExpr *E) {
+  VisitExpr(E);
+  E->setSubExpr(cast_or_null<Expr>(Record.readSubExpr()));
+  E->setMoveExpr(cast_or_null<Expr>(Record.readSubExpr()));
+}
+
 void ASTStmtReader::VisitCapturedStmt(CapturedStmt *S) {
   VisitStmt(S);
   Record.skipInts(1);
@@ -4555,6 +4562,10 @@ Stmt *ASTReader::ReadStmtFromStream(ModuleFile &F) {
       S = new (Context) DependentCoawaitExpr(Empty);
       break;
 
+    case EXPR_COROUTINE_SUSPEND_PARAMETER_BYPASS:
+      S = new (Context) CoroutineSuspendParameterBypassExpr(Empty);
+      break;
+
     case EXPR_CONCEPT_SPECIALIZATION: {
       S = new (Context) ConceptSpecializationExpr(Empty);
       break;
diff --git a/clang/lib/Serialization/ASTWriterStmt.cpp b/clang/lib/Serialization/ASTWriterStmt.cpp
index 10443d42df8c0..a4b993740186c 100644
--- a/clang/lib/Serialization/ASTWriterStmt.cpp
+++ b/clang/lib/Serialization/ASTWriterStmt.cpp
@@ -487,6 +487,14 @@ void ASTStmtWriter::VisitDependentCoawaitExpr(DependentCoawaitExpr *E) {
   Code = serialization::EXPR_DEPENDENT_COAWAIT;
 }
 
+void ASTStmtWriter::VisitCoroutineSuspendParameterBypassExpr(
+    CoroutineSuspendParameterBypassExpr *E) {
+  VisitExpr(E);
+  Record.AddStmt(E->getSubExpr());
+  Record.AddStmt(E->getMoveExpr());
+  Code = serialization::EXPR_COROUTINE_SUSPEND_PARAMETER_BYPASS;
+}
+
 static void
 addConstraintSatisfaction(ASTRecordWriter &Record,
                           const ASTConstraintSatisfaction &Satisfaction) {

>From 0dfc46147430299b4a16809b0a804da2fba7de94 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Wed, 29 Jul 2026 19:16:47 +0000
Subject: [PATCH 09/26] WIPO

---
 clang/include/clang/AST/ExprCXX.h    | 4 ++--
 clang/lib/AST/Expr.cpp               | 1 +
 clang/lib/AST/ExprClassification.cpp | 4 ++++
 clang/lib/AST/ItaniumMangle.cpp      | 1 +
 clang/lib/CodeGen/CGExprComplex.cpp  | 1 -
 clang/lib/Sema/SemaExceptionSpec.cpp | 4 ++++
 clang/lib/Sema/SemaExpr.cpp          | 3 +--
 7 files changed, 13 insertions(+), 5 deletions(-)

diff --git a/clang/include/clang/AST/ExprCXX.h b/clang/include/clang/AST/ExprCXX.h
index 65af878212ff7..0724e73ba4975 100644
--- a/clang/include/clang/AST/ExprCXX.h
+++ b/clang/include/clang/AST/ExprCXX.h
@@ -5552,8 +5552,6 @@ class CXXReflectExpr : public Expr {
   }
 };
 
-/// Helper that selects an expression from an InitListExpr depending on the
-/// current expansion index. See 'CXXExpansionStmtPattern' for how this is used.
 /// Represents a temporary that needs to be pre-evaluated in a coroutine
 /// because it is passed to a call that contains suspend points, and we
 /// want to delay inalloca allocation until after all suspends.
@@ -5603,6 +5601,8 @@ class CoroutineSuspendParameterBypassExpr : public Expr {
   }
 };
 
+/// Helper that selects an expression from an InitListExpr depending on the
+/// current expansion index. See 'CXXExpansionStmtPattern' for how this is used.
 class CXXExpansionSelectExpr : public Expr {
   friend class ASTStmtReader;
 
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 0bcd38ae97450..9a34e48e41a59 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -3844,6 +3844,7 @@ bool Expr::HasSideEffects(const ASTContext &Ctx,
   case CXXStdInitializerListExprClass:
   case SubstNonTypeTemplateParmExprClass:
   case MaterializeTemporaryExprClass:
+  case CoroutineSuspendParameterBypassExprClass:
   case ShuffleVectorExprClass:
   case ConvertVectorExprClass:
   case AsTypeExprClass:
diff --git a/clang/lib/AST/ExprClassification.cpp b/clang/lib/AST/ExprClassification.cpp
index ef071cdef66b6..0abb595ba2d3d 100644
--- a/clang/lib/AST/ExprClassification.cpp
+++ b/clang/lib/AST/ExprClassification.cpp
@@ -233,6 +233,10 @@ static Cl::Kinds ClassifyInternal(ASTContext &Ctx, const Expr *E) {
   case Expr::ConstantExprClass:
     return ClassifyInternal(Ctx, cast<ConstantExpr>(E)->getSubExpr());
 
+  case Expr::CoroutineSuspendParameterBypassExprClass:
+    return ClassifyInternal(
+        Ctx, cast<CoroutineSuspendParameterBypassExpr>(E)->getMoveExpr());
+
     // Next come the complicated cases.
   case Expr::SubstNonTypeTemplateParmExprClass:
     return ClassifyInternal(Ctx,
diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index f8e6b898be250..4480a3bf9bbf6 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -5011,6 +5011,7 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
   case Expr::CXXInheritedCtorInitExprClass:
   case Expr::CXXParenListInitExprClass:
   case Expr::CXXExpansionSelectExprClass:
+  case Expr::CoroutineSuspendParameterBypassExprClass:
     llvm_unreachable("unexpected statement kind");
 
   case Expr::ConstantExprClass:
diff --git a/clang/lib/CodeGen/CGExprComplex.cpp b/clang/lib/CodeGen/CGExprComplex.cpp
index 7c08d9eaff87d..53ce4d61d485e 100644
--- a/clang/lib/CodeGen/CGExprComplex.cpp
+++ b/clang/lib/CodeGen/CGExprComplex.cpp
@@ -173,7 +173,6 @@ class ComplexExprEmitter
       CoroutineSuspendParameterBypassExpr *E) {
     return Visit(E->getMoveExpr());
   }
-
   ComplexPairTy VisitPseudoObjectExpr(PseudoObjectExpr *E) {
     return CGF.EmitPseudoObjectRValue(E).getComplexVal();
   }
diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp
index 0ed5c35c40c1a..ba91a335a7fbb 100644
--- a/clang/lib/Sema/SemaExceptionSpec.cpp
+++ b/clang/lib/Sema/SemaExceptionSpec.cpp
@@ -1240,6 +1240,10 @@ CanThrowResult Sema::canThrow(const Stmt *S) {
     return mergeCanThrow(CT, canSubStmtsThrow(*this, BTE));
   }
 
+  case Expr::CoroutineSuspendParameterBypassExprClass:
+    return canThrow(
+        cast<CoroutineSuspendParameterBypassExpr>(S)->getMoveExpr());
+
   case Expr::PseudoObjectExprClass: {
     auto *POE = cast<PseudoObjectExpr>(S);
     CanThrowResult CT = CT_Cannot;
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index be204239c72ba..1d3171f3186f9 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6350,8 +6350,7 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
             << Arg->getType() << ProtoArgType;
       }
       bool NeedsBypass = CallHasSuspend && Arg->isPRValue() &&
-                         (isWin32InAllocaRecord(Context, ProtoArgType) ||
-                          Arg->containsCoroutineSuspendPoints());
+                         isWin32InAllocaRecord(Context, ProtoArgType);
       Expr *PreBypassArg = Arg;
       if (NeedsBypass) {
         ExprResult Materialized =

>From 233ebbaa554c60a1c4be5492df35c189e8fca404 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Wed, 29 Jul 2026 19:20:27 +0000
Subject: [PATCH 10/26] CR-efriedma-quic

---
 clang/include/clang/AST/ASTNodeTraverser.h    |  9 ++-
 .../test/AST/ast-dump-coroutine-inalloca.cpp  | 64 +++++++++++++++++++
 .../coroutine-inalloca-deleted-copy.cpp       | 41 ++++++++++++
 3 files changed, 113 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/AST/ast-dump-coroutine-inalloca.cpp
 create mode 100644 clang/test/SemaCXX/coroutine-inalloca-deleted-copy.cpp

diff --git a/clang/include/clang/AST/ASTNodeTraverser.h b/clang/include/clang/AST/ASTNodeTraverser.h
index a8a73c5b72d33..9627212ec09b8 100644
--- a/clang/include/clang/AST/ASTNodeTraverser.h
+++ b/clang/include/clang/AST/ASTNodeTraverser.h
@@ -160,7 +160,8 @@ class ASTNodeTraverser
       // Some statements have custom mechanisms for dumping their children.
       if (isa<DeclStmt, GenericSelectionExpr, RequiresExpr,
               OpenACCWaitConstruct, SYCLKernelCallStmt,
-              UnresolvedSYCLKernelCallStmt>(S))
+              UnresolvedSYCLKernelCallStmt,
+              CoroutineSuspendParameterBypassExpr>(S))
         return;
 
       if (Traversal == TK_IgnoreUnlessSpelledInSource &&
@@ -775,6 +776,12 @@ class ASTNodeTraverser
         dumpTemplateArgumentLoc(ArgLoc);
   }
 
+  void VisitCoroutineSuspendParameterBypassExpr(
+      const CoroutineSuspendParameterBypassExpr *Node) {
+    Visit(Node->getSubExpr(), "bypass_sub_expr");
+    Visit(Node->getMoveExpr(), "bypass_move_expr");
+  }
+
   void VisitUsingShadowDecl(const UsingShadowDecl *D) {
     Visit(D->getTargetDecl());
   }
diff --git a/clang/test/AST/ast-dump-coroutine-inalloca.cpp b/clang/test/AST/ast-dump-coroutine-inalloca.cpp
new file mode 100644
index 0000000000000..221c7bf47789d
--- /dev/null
+++ b/clang/test/AST/ast-dump-coroutine-inalloca.cpp
@@ -0,0 +1,64 @@
+// RUN: %clang_cc1 -triple i686-pc-windows-msvc -std=c++20 -ast-dump -ast-dump-filter test -Wno-coroutines-unsupported-target %s | FileCheck %s
+
+namespace std {
+template <typename R, typename... Args>
+struct coroutine_traits {
+  using promise_type = typename R::promise_type;
+};
+
+template <class Promise = void> struct coroutine_handle {
+  coroutine_handle() = default;
+  static coroutine_handle from_address(void *) noexcept;
+};
+template <> struct coroutine_handle<void> {
+  static coroutine_handle from_address(void *) noexcept;
+  coroutine_handle() = default;
+  template <class PromiseType>
+  coroutine_handle(coroutine_handle<PromiseType>) noexcept;
+};
+struct suspend_never {
+  bool await_ready() noexcept { return true; }
+  void await_suspend(coroutine_handle<>) noexcept {}
+  void await_resume() noexcept {}
+};
+} // namespace std
+
+struct Noisy {
+  int val;
+  Noisy(int v);
+  Noisy(const Noisy&);
+  Noisy(Noisy&&) noexcept;
+  ~Noisy();
+};
+
+struct Awaiter {
+  bool await_ready() noexcept { return false; }
+  void await_suspend(std::coroutine_handle<>) noexcept {}
+  Noisy await_resume() noexcept;
+};
+
+struct task {
+  struct promise_type {
+    task get_return_object() { return {}; }
+    std::suspend_never initial_suspend() { return {}; }
+    std::suspend_never final_suspend() noexcept { return {}; }
+    void return_void() {}
+    void unhandled_exception() {}
+  };
+};
+
+void consume(Noisy x); // passed by inalloca
+
+task test() {
+  consume(co_await Awaiter{});
+}
+
+// CHECK:      CallExpr {{.*}} 'void'
+// CHECK-NEXT: | |-ImplicitCastExpr {{.*}} 'void (*)(Noisy)' <FunctionToPointerDecay>
+// CHECK-NEXT: | | `-DeclRefExpr {{.*}} 'void (Noisy)' lvalue Function {{.*}} 'consume'
+// CHECK-NEXT: | `-CoroutineSuspendParameterBypassExpr {{.*}} 'Noisy'
+// CHECK-NEXT: |   |-bypass_sub_expr: MaterializeTemporaryExpr {{.*}} 'Noisy' xvalue
+// CHECK:          `-bypass_move_expr: CXXBindTemporaryExpr {{.*}} 'Noisy'
+// CHECK-NEXT:       `-CXXConstructExpr {{.*}} 'Noisy' 'void (Noisy &&) __attribute__((thiscall)) noexcept' elidable
+// CHECK-NEXT:         `-MaterializeTemporaryExpr {{.*}} 'Noisy' xvalue
+// CHECK-NEXT:           `-CoawaitExpr {{.*}} 'Noisy'
diff --git a/clang/test/SemaCXX/coroutine-inalloca-deleted-copy.cpp b/clang/test/SemaCXX/coroutine-inalloca-deleted-copy.cpp
new file mode 100644
index 0000000000000..b26e4ae585442
--- /dev/null
+++ b/clang/test/SemaCXX/coroutine-inalloca-deleted-copy.cpp
@@ -0,0 +1,41 @@
+// RUN: %clang_cc1 -std=c++20 -triple i686-pc-windows-msvc -verify -Wno-coroutines-unsupported-target %s
+
+#include "Inputs/std-coroutine.h"
+
+struct Noisy {
+  int val;
+  Noisy(int v);
+  Noisy(const Noisy&) = delete;
+  Noisy(Noisy&& o) = delete; // expected-note 2 {{'Noisy' has been explicitly marked deleted here}}
+  ~Noisy();
+};
+
+struct Awaiter {
+  bool await_ready() noexcept { return false; }
+  void await_suspend(std::coroutine_handle<>) noexcept {}
+  int await_resume() noexcept;
+};
+
+struct NoisyAwaiter {
+  bool await_ready() noexcept { return false; }
+  void await_suspend(std::coroutine_handle<>) noexcept {}
+  Noisy await_resume() noexcept;
+};
+
+struct task {
+  struct promise_type {
+    task get_return_object() { return {}; }
+    std::suspend_never initial_suspend() { return {}; }
+    std::suspend_never final_suspend() noexcept { return {}; }
+    void return_void() {}
+    void unhandled_exception() {}
+  };
+};
+
+void consume(Noisy x); // expected-note 2 {{passing argument to parameter 'x' here}}
+
+task my_coroutine() {
+  consume(Noisy(42)); // OK, no suspend, no bypass
+  consume(co_await NoisyAwaiter{}); // expected-error {{call to deleted constructor of 'Noisy'}}
+  consume(Noisy(co_await Awaiter{})); // expected-error {{call to deleted constructor of 'Noisy'}}
+}

>From 2e14cd377e73623f0f0c253dd5c59d863cc6af93 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Wed, 29 Jul 2026 19:41:06 +0000
Subject: [PATCH 11/26] Fix

---
 clang/lib/CodeGen/CGCall.cpp                         | 7 +++++++
 clang/test/CodeGenCoroutines/coro-win32-inalloca.cpp | 6 +++---
 2 files changed, 10 insertions(+), 3 deletions(-)

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 6cb6d7e249ac7..daa14913bf10e 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5725,7 +5725,14 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
     assert(AI->isUsedWithInAlloca() && !AI->isStaticAlloca());
     ArgMemory = RawAddress(AI, ArgStruct, Align);
     if (isCoroutine()) {
+      CGBuilderTy::InsertPoint SavedIP = Builder.saveIP();
+      if (llvm::Instruction *Next = AI->getNextNode()) {
+        Builder.SetInsertPoint(Next);
+      } else {
+        Builder.SetInsertPoint(AI->getParent());
+      }
       EmitLifetimeStart(AI);
+      Builder.restoreIP(SavedIP);
     }
   }
 
diff --git a/clang/test/CodeGenCoroutines/coro-win32-inalloca.cpp b/clang/test/CodeGenCoroutines/coro-win32-inalloca.cpp
index 34d1d0d81d943..48776e2be864d 100644
--- a/clang/test/CodeGenCoroutines/coro-win32-inalloca.cpp
+++ b/clang/test/CodeGenCoroutines/coro-win32-inalloca.cpp
@@ -71,9 +71,10 @@ task my_task() {
   // After resume:
   // CHECK: call x86_thiscallcc void @"?await_resume at Awaiter@@QAE?AUNoisy@@XZ"(ptr {{[^,]*}} %{{.*}}, ptr dead_on_unwind writable sret(%struct.Noisy) align 4 %[[MTE_X]])
   
-  // Allocate inalloca:
+  // Allocate inalloca and start its lifetime:
   // CHECK: %[[STACKSAVE:.+]] = call ptr @llvm.stacksave.p0()
   // CHECK: %[[ARGMEM:.+]] = alloca inalloca <{ %struct.Noisy, %struct.Noisy }>, align 4, !coro.outside.frame ![[METADATA_NUM:[0-9]+]]
+  // CHECK: call void @llvm.lifetime.start.p0(ptr %[[ARGMEM]])
   
   // Move y (pre-evaluated Noisy(42)) to inalloca:
   // CHECK: %[[GEP_Y:.+]] = getelementptr inbounds nuw <{ %struct.Noisy, %struct.Noisy }>, ptr %[[ARGMEM]], i32 0, i32 1
@@ -83,8 +84,7 @@ task my_task() {
   // CHECK: %[[GEP_X:.+]] = getelementptr inbounds nuw <{ %struct.Noisy, %struct.Noisy }>, ptr %[[ARGMEM]], i32 0, i32 0
   // CHECK: call x86_thiscallcc noundef ptr @"??0Noisy@@QAE@$$QAU0@@Z"(ptr {{[^,]*}} %[[GEP_X]], ptr noundef nonnull align 4 dereferenceable(4) %[[MTE_X]])
 
-  // Lifetime start and call:
-  // CHECK: call void @llvm.lifetime.start.p0(ptr %[[ARGMEM]])
+  // Call:
   // CHECK: call void @"?consume_two@@YAXUNoisy@@0 at Z"(ptr inalloca(<{ %struct.Noisy, %struct.Noisy }>) %[[ARGMEM]])
   
   consume_two(co_await Awaiter{}, Noisy(42));

>From 84ce22b109641898e5a4530bc52457230e6fd669 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Wed, 29 Jul 2026 19:43:16 +0000
Subject: [PATCH 12/26] Simplify

---
 clang/lib/CodeGen/CGCall.cpp | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index daa14913bf10e..f9000e5c2e908 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5726,11 +5726,7 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
     ArgMemory = RawAddress(AI, ArgStruct, Align);
     if (isCoroutine()) {
       CGBuilderTy::InsertPoint SavedIP = Builder.saveIP();
-      if (llvm::Instruction *Next = AI->getNextNode()) {
-        Builder.SetInsertPoint(Next);
-      } else {
-        Builder.SetInsertPoint(AI->getParent());
-      }
+      Builder.SetInsertPoint(AI->getParent(), std::next(AI->getIterator()));
       EmitLifetimeStart(AI);
       Builder.restoreIP(SavedIP);
     }

>From 86d0e604a842a3ff39367126aa2811ce7c6d842b Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Wed, 29 Jul 2026 21:18:17 +0000
Subject: [PATCH 13/26] WIP

---
 clang/lib/AST/ExprCXX.cpp                     |  22 ++++
 clang/lib/CodeGen/CGCall.cpp                  | 103 +++++++++++++-----
 clang/lib/CodeGen/CGCall.h                    |   5 +
 clang/lib/Sema/SemaExpr.cpp                   |  76 +++++++++----
 .../CodeGenCoroutines/coro-win32-inalloca.cpp |   8 +-
 5 files changed, 160 insertions(+), 54 deletions(-)

diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp
index 6ccad263760f8..c17d8cf5d9b81 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -1132,9 +1132,31 @@ CXXBindTemporaryExpr *CXXBindTemporaryExpr::Create(const ASTContext &C,
   return new (C) CXXBindTemporaryExpr(Temp, SubExpr);
 }
 
+static void disableCopyElision(Expr *E) {
+  if (auto *EWC = dyn_cast<ExprWithCleanups>(E))
+    E = EWC->getSubExpr();
+  if (auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E))
+    E = BTE->getSubExpr();
+  if (auto *CCE = dyn_cast<CXXConstructExpr>(E)) {
+    if (CCE->isElidable())
+      CCE->setElidable(false);
+  }
+}
+
+static Expr *stripTemporaryBinding(Expr *E) {
+  if (!E) return nullptr;
+  if (auto *EWC = dyn_cast<ExprWithCleanups>(E))
+    return stripTemporaryBinding(EWC->getSubExpr());
+  if (auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E))
+    return stripTemporaryBinding(BTE->getSubExpr());
+  return E;
+}
+
 CoroutineSuspendParameterBypassExpr *
 CoroutineSuspendParameterBypassExpr::Create(const ASTContext &C, Expr *SubExpr,
                                             Expr *MoveExpr) {
+  disableCopyElision(MoveExpr);
+  MoveExpr = stripTemporaryBinding(MoveExpr);
   return new (C) CoroutineSuspendParameterBypassExpr(SubExpr, MoveExpr);
 }
 
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index f9000e5c2e908..810bf0ab99965 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5204,9 +5204,6 @@ void CodeGenFunction::EmitCallArgs(
     Args.reverseWritebacks();
   }
 
-  for (const auto *MTE : MTEsToPreEvaluate) {
-    PreEvaluatedMaterializedTemporaries.erase(MTE);
-  }
 }
 
 namespace {
@@ -5283,16 +5280,36 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
     return;
   }
 
+  bool IsBypassed = false;
+  const Expr *MoveExpr = nullptr;
+  const Expr *SubExpr = E;
+  if (auto *Bypass = dyn_cast<CoroutineSuspendParameterBypassExpr>(E)) {
+    IsBypassed = true;
+    MoveExpr = Bypass->getMoveExpr();
+    SubExpr = Bypass->getSubExpr();
+  }
+
   assert(type->isReferenceType() == E->isGLValue() &&
          "reference binding to unmaterialized r-value!");
 
   if (E->isGLValue()) {
     assert(E->getObjectKind() == OK_Ordinary);
-    return args.add(EmitReferenceBindingToExpr(E), type);
+    RValue RV = EmitReferenceBindingToExpr(SubExpr);
+    args.add(RV, type);
+    if (IsBypassed)
+      args.back().setMoveExpr(MoveExpr);
+    return;
   }
 
   bool HasAggregateEvalKind = hasAggregateEvaluationKind(type);
 
+  if (IsBypassed && HasAggregateEvalKind) {
+    LValue LV = getPreEvaluatedTemporary(cast<MaterializeTemporaryExpr>(SubExpr));
+    args.addUncopiedAggregate(LV, type);
+    args.back().setMoveExpr(MoveExpr);
+    return;
+  }
+
   // In the Microsoft C++ ABI, aggregate arguments are destructed by the callee.
   // However, we still have to push an EH-only cleanup in case we unwind before
   // we make it to the call.
@@ -5313,7 +5330,7 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
     if (DestroyedInCallee)
       Slot.setExternallyDestructed();
 
-    EmitAggExpr(E, Slot);
+    EmitAggExpr(SubExpr, Slot);
     RValue RV = Slot.asRValue();
     args.add(RV, type);
 
@@ -5332,19 +5349,21 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
   }
 
   if (HasAggregateEvalKind) {
-    auto *ICE = dyn_cast<ImplicitCastExpr>(E);
+    auto *ICE = dyn_cast<ImplicitCastExpr>(SubExpr);
     if (ICE && ICE->getCastKind() == CK_LValueToRValue &&
         ICE->getSubExpr()->getType().getAddressSpace() !=
             LangAS::hlsl_constant &&
         !type->isArrayParameterType() && !type.isNonTrivialToPrimitiveCopy()) {
-      LValue L = EmitLValue(cast<CastExpr>(E)->getSubExpr());
+      LValue L = EmitLValue(cast<CastExpr>(SubExpr)->getSubExpr());
       assert(L.isSimple());
       args.addUncopiedAggregate(L, type);
       return;
     }
   }
 
-  args.add(EmitAnyExprToTemp(E), type);
+  args.add(EmitAnyExprToTemp(SubExpr), type);
+  if (IsBypassed)
+    args.back().setMoveExpr(MoveExpr);
 }
 
 QualType CodeGenFunction::getVarArgType(const Expr *Arg) {
@@ -5653,6 +5672,7 @@ static unsigned getMaxVectorWidth(const llvm::Type *Ty) {
   return MaxVectorWidth;
 }
 
+
 RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
                                  const CGCallee &Callee,
                                  ReturnValueSlot ReturnValue,
@@ -5815,30 +5835,53 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
       assert(NumIRArgs == 0);
       assert(getTarget().getTriple().getArch() == llvm::Triple::x86);
       if (I->isAggregate()) {
-        RawAddress Addr = I->hasLValue()
-                              ? I->getKnownLValue().getAddress()
-                              : I->getKnownRValue().getAggregateAddress();
-        llvm::Instruction *Placeholder =
-            cast<llvm::Instruction>(Addr.getPointer());
-
-        if (!ArgInfo.getInAllocaIndirect()) {
-          // Replace the placeholder with the appropriate argument slot GEP.
-          CGBuilderTy::InsertPoint IP = Builder.saveIP();
-          Builder.SetInsertPoint(Placeholder);
-          Addr = Builder.CreateStructGEP(ArgMemory,
-                                         ArgInfo.getInAllocaFieldIndex());
-          Builder.restoreIP(IP);
+        if (I->isBypassed()) {
+          if (!ArgInfo.getInAllocaIndirect()) {
+            Address Addr = Builder.CreateStructGEP(ArgMemory,
+                                                   ArgInfo.getInAllocaFieldIndex());
+            AggValueSlot Slot = AggValueSlot::forAddr(
+                Addr, I->Ty.getQualifiers(),
+                AggValueSlot::IsNotDestructed, AggValueSlot::DoesNotNeedGCBarriers,
+                AggValueSlot::IsNotAliased, AggValueSlot::DoesNotOverlap);
+            EmitAggExpr(I->getMoveExpr(), Slot);
+          } else {
+            RawAddress Addr =
+                CreateMemTempWithoutCast(info_it->type, "inalloca.indirect.tmp");
+            AggValueSlot Slot = AggValueSlot::forAddr(
+                Addr, I->Ty.getQualifiers(),
+                AggValueSlot::IsNotDestructed, AggValueSlot::DoesNotNeedGCBarriers,
+                AggValueSlot::IsNotAliased, AggValueSlot::DoesNotOverlap);
+            EmitAggExpr(I->getMoveExpr(), Slot);
+            Address ArgSlot = Builder.CreateStructGEP(
+                ArgMemory, ArgInfo.getInAllocaFieldIndex());
+            Builder.CreateStore(Addr.getPointer(), ArgSlot);
+          }
         } else {
-          // For indirect things such as overaligned structs, replace the
-          // placeholder with a regular aggregate temporary alloca. Store the
-          // address of this alloca into the struct.
-          Addr =
-              CreateMemTempWithoutCast(info_it->type, "inalloca.indirect.tmp");
-          Address ArgSlot = Builder.CreateStructGEP(
-              ArgMemory, ArgInfo.getInAllocaFieldIndex());
-          Builder.CreateStore(Addr.getPointer(), ArgSlot);
+          RawAddress Addr = I->hasLValue()
+                                ? I->getKnownLValue().getAddress()
+                                : I->getKnownRValue().getAggregateAddress();
+          llvm::Instruction *Placeholder =
+              cast<llvm::Instruction>(Addr.getPointer());
+
+          if (!ArgInfo.getInAllocaIndirect()) {
+            // Replace the placeholder with the appropriate argument slot GEP.
+            CGBuilderTy::InsertPoint IP = Builder.saveIP();
+            Builder.SetInsertPoint(Placeholder);
+            Addr = Builder.CreateStructGEP(ArgMemory,
+                                           ArgInfo.getInAllocaFieldIndex());
+            Builder.restoreIP(IP);
+          } else {
+            // For indirect things such as overaligned structs, replace the
+            // placeholder with a regular aggregate temporary alloca. Store the
+            // address of this alloca into the struct.
+            Addr =
+                CreateMemTempWithoutCast(info_it->type, "inalloca.indirect.tmp");
+            Address ArgSlot = Builder.CreateStructGEP(
+                ArgMemory, ArgInfo.getInAllocaFieldIndex());
+            Builder.CreateStore(Addr.getPointer(), ArgSlot);
+          }
+          deferPlaceholderReplacement(Placeholder, Addr.getPointer());
         }
-        deferPlaceholderReplacement(Placeholder, Addr.getPointer());
       } else if (ArgInfo.getInAllocaIndirect()) {
         // Make a temporary alloca and store the address of it into the argument
         // struct.
diff --git a/clang/lib/CodeGen/CGCall.h b/clang/lib/CodeGen/CGCall.h
index 5df7f0d08d070..4e21c6c878f2d 100644
--- a/clang/lib/CodeGen/CGCall.h
+++ b/clang/lib/CodeGen/CGCall.h
@@ -239,6 +239,7 @@ struct CallArg {
   /// A data-flow flag to make sure getRValue and/or copyInto are not
   /// called twice for duplicated IR emission.
   mutable bool IsUsed;
+  const Expr *MoveExpr = nullptr;
 
 public:
   QualType Ty;
@@ -269,6 +270,10 @@ struct CallArg {
   bool isAggregate() const { return HasLV || RV.isAggregate(); }
 
   void copyInto(CodeGenFunction &CGF, Address A) const;
+
+  bool isBypassed() const { return MoveExpr != nullptr; }
+  void setMoveExpr(const Expr *E) { MoveExpr = E; }
+  const Expr *getMoveExpr() const { return MoveExpr; }
 };
 
 /// CallArgList - Type for representing both the value and type of
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 1d3171f3186f9..4a5b94bffc986 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6273,6 +6273,31 @@ static bool isWin32InAllocaRecord(ASTContext &Context, QualType Ty) {
 
   return true;
 }
+static ExprResult InitializeAndBypassArg(Sema &S, const InitializedEntity &Entity,
+                                         Expr *Arg, bool NeedsBypass,
+                                         bool IsListInitialization,
+                                         bool AllowExplicit) {
+  Expr *PreBypassArg = Arg;
+  if (NeedsBypass) {
+    ExprResult Materialized =
+        S.CreateMaterializeTemporaryExpr(Arg->getType(), Arg, false);
+    if (Materialized.isInvalid())
+      return ExprError();
+    PreBypassArg = Materialized.get();
+  }
+
+  ExprResult ArgE =
+      S.PerformCopyInitialization(Entity, SourceLocation(), PreBypassArg,
+                                  IsListInitialization, AllowExplicit);
+  if (ArgE.isInvalid())
+    return ExprError();
+
+  Expr *Result = ArgE.getAs<Expr>();
+  if (NeedsBypass) {
+    Result = CoroutineSuspendParameterBypassExpr::Create(S.Context, PreBypassArg, Result);
+  }
+  return Result;
+}
 
 bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
                                   const FunctionProtoType *Proto,
@@ -6290,6 +6315,21 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
       }
     }
   }
+  bool CallUsesInAlloca = false;
+  for (unsigned i = 0; i < NumParams; i++) {
+    if (isWin32InAllocaRecord(Context, Proto->getParamType(i))) {
+      CallUsesInAlloca = true;
+      break;
+    }
+  }
+  if (!CallUsesInAlloca && Proto->isVariadic()) {
+    for (size_t i = NumParams; i < Args.size(); i++) {
+      if (Args[i] && isWin32InAllocaRecord(Context, Args[i]->getType())) {
+        CallUsesInAlloca = true;
+        break;
+      }
+    }
+  }
   bool Invalid = false;
   size_t ArgIx = 0;
   // Continue to check argument types (even if we have too few/many args).
@@ -6349,28 +6389,13 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
                         : diag::warn_obt_discarded_at_function_boundary)
             << Arg->getType() << ProtoArgType;
       }
-      bool NeedsBypass = CallHasSuspend && Arg->isPRValue() &&
-                         isWin32InAllocaRecord(Context, ProtoArgType);
-      Expr *PreBypassArg = Arg;
-      if (NeedsBypass) {
-        ExprResult Materialized =
-            CreateMaterializeTemporaryExpr(Arg->getType(), Arg, false);
-        if (!Materialized.isInvalid())
-          PreBypassArg = Materialized.get();
-      }
-
-      ExprResult ArgE =
-          PerformCopyInitialization(Entity, SourceLocation(), PreBypassArg,
-                                    IsListInitialization, AllowExplicit);
+      bool NeedsBypass = CallUsesInAlloca && CallHasSuspend &&
+                         (!ProtoArgType->isReferenceType() || Arg->isPRValue());
+      ExprResult ArgE = InitializeAndBypassArg(*this, Entity, Arg, NeedsBypass,
+                                               IsListInitialization, AllowExplicit);
       if (ArgE.isInvalid())
         return true;
-
       Arg = ArgE.getAs<Expr>();
-
-      if (NeedsBypass) {
-        Arg = CoroutineSuspendParameterBypassExpr::Create(Context, PreBypassArg,
-                                                          Arg);
-      }
     } else {
       assert(Param && "can't use default arguments without a known callee");
 
@@ -6410,7 +6435,18 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
       for (Expr *A : Args.slice(ArgIx)) {
         ExprResult Arg = DefaultVariadicArgumentPromotion(A, CallType, FDecl);
         Invalid |= Arg.isInvalid();
-        AllArgs.push_back(Arg.get());
+        if (!Arg.isInvalid()) {
+          Expr *E = Arg.get();
+          bool NeedsBypass = CallUsesInAlloca && CallHasSuspend && E->isPRValue();
+          if (NeedsBypass) {
+            InitializedEntity ArgEntity =
+                InitializedEntity::InitializeTemporary(E->getType());
+            ExprResult ArgE = InitializeAndBypassArg(*this, ArgEntity, E, true, false, false);
+            if (!ArgE.isInvalid())
+              E = ArgE.getAs<Expr>();
+          }
+          AllArgs.push_back(E);
+        }
       }
     }
 
diff --git a/clang/test/CodeGenCoroutines/coro-win32-inalloca.cpp b/clang/test/CodeGenCoroutines/coro-win32-inalloca.cpp
index 48776e2be864d..e70884ec51241 100644
--- a/clang/test/CodeGenCoroutines/coro-win32-inalloca.cpp
+++ b/clang/test/CodeGenCoroutines/coro-win32-inalloca.cpp
@@ -77,12 +77,12 @@ task my_task() {
   // CHECK: call void @llvm.lifetime.start.p0(ptr %[[ARGMEM]])
   
   // Move y (pre-evaluated Noisy(42)) to inalloca:
-  // CHECK: %[[GEP_Y:.+]] = getelementptr inbounds nuw <{ %struct.Noisy, %struct.Noisy }>, ptr %[[ARGMEM]], i32 0, i32 1
-  // CHECK: call x86_thiscallcc noundef ptr @"??0Noisy@@QAE@$$QAU0@@Z"(ptr {{[^,]*}} %[[GEP_Y]], ptr noundef nonnull align 4 dereferenceable(4) %[[MTE_Y]])
+  // CHECK-DAG: %[[GEP_Y:.+]] = getelementptr inbounds nuw <{ %struct.Noisy, %struct.Noisy }>, ptr %[[ARGMEM]], i32 0, i32 1
+  // CHECK-DAG: call x86_thiscallcc noundef ptr @"??0Noisy@@QAE@$$QAU0@@Z"(ptr {{[^,]*}} %[[GEP_Y]], ptr noundef nonnull align 4 dereferenceable(4) %[[MTE_Y]])
   
   // Move x (co_await Awaiter{} result) to inalloca:
-  // CHECK: %[[GEP_X:.+]] = getelementptr inbounds nuw <{ %struct.Noisy, %struct.Noisy }>, ptr %[[ARGMEM]], i32 0, i32 0
-  // CHECK: call x86_thiscallcc noundef ptr @"??0Noisy@@QAE@$$QAU0@@Z"(ptr {{[^,]*}} %[[GEP_X]], ptr noundef nonnull align 4 dereferenceable(4) %[[MTE_X]])
+  // CHECK-DAG: %[[GEP_X:.+]] = getelementptr inbounds nuw <{ %struct.Noisy, %struct.Noisy }>, ptr %[[ARGMEM]], i32 0, i32 0
+  // CHECK-DAG: call x86_thiscallcc noundef ptr @"??0Noisy@@QAE@$$QAU0@@Z"(ptr {{[^,]*}} %[[GEP_X]], ptr noundef nonnull align 4 dereferenceable(4) %[[MTE_X]])
 
   // Call:
   // CHECK: call void @"?consume_two@@YAXUNoisy@@0 at Z"(ptr inalloca(<{ %struct.Noisy, %struct.Noisy }>) %[[ARGMEM]])

>From f402468439603e36e1f33e8d65c6f0c4e16f6588 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Wed, 29 Jul 2026 21:39:18 +0000
Subject: [PATCH 14/26] Simplify

---
 clang/lib/AST/ExprCXX.cpp    | 22 ----------------------
 clang/lib/CodeGen/CGCall.cpp | 12 ++++++------
 clang/lib/Sema/SemaExpr.cpp  | 15 +++++++++++++++
 3 files changed, 21 insertions(+), 28 deletions(-)

diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp
index c17d8cf5d9b81..6ccad263760f8 100644
--- a/clang/lib/AST/ExprCXX.cpp
+++ b/clang/lib/AST/ExprCXX.cpp
@@ -1132,31 +1132,9 @@ CXXBindTemporaryExpr *CXXBindTemporaryExpr::Create(const ASTContext &C,
   return new (C) CXXBindTemporaryExpr(Temp, SubExpr);
 }
 
-static void disableCopyElision(Expr *E) {
-  if (auto *EWC = dyn_cast<ExprWithCleanups>(E))
-    E = EWC->getSubExpr();
-  if (auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E))
-    E = BTE->getSubExpr();
-  if (auto *CCE = dyn_cast<CXXConstructExpr>(E)) {
-    if (CCE->isElidable())
-      CCE->setElidable(false);
-  }
-}
-
-static Expr *stripTemporaryBinding(Expr *E) {
-  if (!E) return nullptr;
-  if (auto *EWC = dyn_cast<ExprWithCleanups>(E))
-    return stripTemporaryBinding(EWC->getSubExpr());
-  if (auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E))
-    return stripTemporaryBinding(BTE->getSubExpr());
-  return E;
-}
-
 CoroutineSuspendParameterBypassExpr *
 CoroutineSuspendParameterBypassExpr::Create(const ASTContext &C, Expr *SubExpr,
                                             Expr *MoveExpr) {
-  disableCopyElision(MoveExpr);
-  MoveExpr = stripTemporaryBinding(MoveExpr);
   return new (C) CoroutineSuspendParameterBypassExpr(SubExpr, MoveExpr);
 }
 
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 810bf0ab99965..93effd8ce1e7f 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5840,17 +5840,17 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
             Address Addr = Builder.CreateStructGEP(ArgMemory,
                                                    ArgInfo.getInAllocaFieldIndex());
             AggValueSlot Slot = AggValueSlot::forAddr(
-                Addr, I->Ty.getQualifiers(),
-                AggValueSlot::IsNotDestructed, AggValueSlot::DoesNotNeedGCBarriers,
-                AggValueSlot::IsNotAliased, AggValueSlot::DoesNotOverlap);
+                Addr, I->Ty.getQualifiers(), AggValueSlot::IsDestructed,
+                AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsNotAliased,
+                AggValueSlot::DoesNotOverlap);
             EmitAggExpr(I->getMoveExpr(), Slot);
           } else {
             RawAddress Addr =
                 CreateMemTempWithoutCast(info_it->type, "inalloca.indirect.tmp");
             AggValueSlot Slot = AggValueSlot::forAddr(
-                Addr, I->Ty.getQualifiers(),
-                AggValueSlot::IsNotDestructed, AggValueSlot::DoesNotNeedGCBarriers,
-                AggValueSlot::IsNotAliased, AggValueSlot::DoesNotOverlap);
+                Addr, I->Ty.getQualifiers(), AggValueSlot::IsDestructed,
+                AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsNotAliased,
+                AggValueSlot::DoesNotOverlap);
             EmitAggExpr(I->getMoveExpr(), Slot);
             Address ArgSlot = Builder.CreateStructGEP(
                 ArgMemory, ArgInfo.getInAllocaFieldIndex());
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 4a5b94bffc986..339d533e27743 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6273,6 +6273,17 @@ static bool isWin32InAllocaRecord(ASTContext &Context, QualType Ty) {
 
   return true;
 }
+static void disableCopyElision(Expr *E) {
+  if (auto *EWC = dyn_cast<ExprWithCleanups>(E))
+    E = EWC->getSubExpr();
+  if (auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E))
+    E = BTE->getSubExpr();
+  if (auto *CCE = dyn_cast<CXXConstructExpr>(E)) {
+    if (CCE->isElidable())
+      CCE->setElidable(false);
+  }
+}
+
 static ExprResult InitializeAndBypassArg(Sema &S, const InitializedEntity &Entity,
                                          Expr *Arg, bool NeedsBypass,
                                          bool IsListInitialization,
@@ -6294,6 +6305,10 @@ static ExprResult InitializeAndBypassArg(Sema &S, const InitializedEntity &Entit
 
   Expr *Result = ArgE.getAs<Expr>();
   if (NeedsBypass) {
+    bool IsAggregate =
+        Arg->getType()->isRecordType() || Arg->getType()->isAnyComplexType();
+    if (IsAggregate)
+      disableCopyElision(Result);
     Result = CoroutineSuspendParameterBypassExpr::Create(S.Context, PreBypassArg, Result);
   }
   return Result;

>From ff912388b23ae82943cb7aff25f3f85c8869f8f2 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Wed, 29 Jul 2026 21:44:57 +0000
Subject: [PATCH 15/26] Nit

---
 clang/lib/CodeGen/CGCall.cpp | 48 ++++++++++++++++++------------------
 1 file changed, 24 insertions(+), 24 deletions(-)

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 93effd8ce1e7f..8677f509acdd8 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5856,32 +5856,32 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
                 ArgMemory, ArgInfo.getInAllocaFieldIndex());
             Builder.CreateStore(Addr.getPointer(), ArgSlot);
           }
+          break;
+        }
+        RawAddress Addr = I->hasLValue()
+                              ? I->getKnownLValue().getAddress()
+                              : I->getKnownRValue().getAggregateAddress();
+        llvm::Instruction *Placeholder =
+            cast<llvm::Instruction>(Addr.getPointer());
+
+        if (!ArgInfo.getInAllocaIndirect()) {
+          // Replace the placeholder with the appropriate argument slot GEP.
+          CGBuilderTy::InsertPoint IP = Builder.saveIP();
+          Builder.SetInsertPoint(Placeholder);
+          Addr = Builder.CreateStructGEP(ArgMemory,
+                                         ArgInfo.getInAllocaFieldIndex());
+          Builder.restoreIP(IP);
         } else {
-          RawAddress Addr = I->hasLValue()
-                                ? I->getKnownLValue().getAddress()
-                                : I->getKnownRValue().getAggregateAddress();
-          llvm::Instruction *Placeholder =
-              cast<llvm::Instruction>(Addr.getPointer());
-
-          if (!ArgInfo.getInAllocaIndirect()) {
-            // Replace the placeholder with the appropriate argument slot GEP.
-            CGBuilderTy::InsertPoint IP = Builder.saveIP();
-            Builder.SetInsertPoint(Placeholder);
-            Addr = Builder.CreateStructGEP(ArgMemory,
-                                           ArgInfo.getInAllocaFieldIndex());
-            Builder.restoreIP(IP);
-          } else {
-            // For indirect things such as overaligned structs, replace the
-            // placeholder with a regular aggregate temporary alloca. Store the
-            // address of this alloca into the struct.
-            Addr =
-                CreateMemTempWithoutCast(info_it->type, "inalloca.indirect.tmp");
-            Address ArgSlot = Builder.CreateStructGEP(
-                ArgMemory, ArgInfo.getInAllocaFieldIndex());
-            Builder.CreateStore(Addr.getPointer(), ArgSlot);
-          }
-          deferPlaceholderReplacement(Placeholder, Addr.getPointer());
+          // For indirect things such as overaligned structs, replace the
+          // placeholder with a regular aggregate temporary alloca. Store the
+          // address of this alloca into the struct.
+          Addr =
+              CreateMemTempWithoutCast(info_it->type, "inalloca.indirect.tmp");
+          Address ArgSlot = Builder.CreateStructGEP(
+              ArgMemory, ArgInfo.getInAllocaFieldIndex());
+          Builder.CreateStore(Addr.getPointer(), ArgSlot);
         }
+        deferPlaceholderReplacement(Placeholder, Addr.getPointer());
       } else if (ArgInfo.getInAllocaIndirect()) {
         // Make a temporary alloca and store the address of it into the argument
         // struct.

>From c4ab6917c2fd1d84fa21b9bd4102269132b0d401 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Wed, 29 Jul 2026 21:46:37 +0000
Subject: [PATCH 16/26] Nit

---
 clang/lib/CodeGen/CGCall.cpp | 28 +++++++++++++---------------
 1 file changed, 13 insertions(+), 15 deletions(-)

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 8677f509acdd8..a6b9e49bc25c5 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5836,25 +5836,23 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
       assert(getTarget().getTriple().getArch() == llvm::Triple::x86);
       if (I->isAggregate()) {
         if (I->isBypassed()) {
+          Address Addr = Address::invalid();
           if (!ArgInfo.getInAllocaIndirect()) {
-            Address Addr = Builder.CreateStructGEP(ArgMemory,
-                                                   ArgInfo.getInAllocaFieldIndex());
-            AggValueSlot Slot = AggValueSlot::forAddr(
-                Addr, I->Ty.getQualifiers(), AggValueSlot::IsDestructed,
-                AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsNotAliased,
-                AggValueSlot::DoesNotOverlap);
-            EmitAggExpr(I->getMoveExpr(), Slot);
+            Addr = Builder.CreateStructGEP(ArgMemory,
+                                           ArgInfo.getInAllocaFieldIndex());
           } else {
-            RawAddress Addr =
-                CreateMemTempWithoutCast(info_it->type, "inalloca.indirect.tmp");
-            AggValueSlot Slot = AggValueSlot::forAddr(
-                Addr, I->Ty.getQualifiers(), AggValueSlot::IsDestructed,
-                AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsNotAliased,
-                AggValueSlot::DoesNotOverlap);
-            EmitAggExpr(I->getMoveExpr(), Slot);
+            Addr = CreateMemTempWithoutCast(info_it->type,
+                                            "inalloca.indirect.tmp");
+          }
+          AggValueSlot Slot = AggValueSlot::forAddr(
+              Addr, I->Ty.getQualifiers(), AggValueSlot::IsDestructed,
+              AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsNotAliased,
+              AggValueSlot::DoesNotOverlap);
+          EmitAggExpr(I->getMoveExpr(), Slot);
+          if (ArgInfo.getInAllocaIndirect()) {
             Address ArgSlot = Builder.CreateStructGEP(
                 ArgMemory, ArgInfo.getInAllocaFieldIndex());
-            Builder.CreateStore(Addr.getPointer(), ArgSlot);
+            Builder.CreateStore(Addr.emitRawPointer(*this), ArgSlot);
           }
           break;
         }

>From 9eba38aefbb32df70b2e85c9ad1ca5ff3e576a15 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Thu, 30 Jul 2026 17:40:02 +0000
Subject: [PATCH 17/26] Fix format

---
 clang/lib/CodeGen/CGCall.cpp |  5 ++---
 clang/lib/Sema/SemaExpr.cpp  | 16 ++++++++++------
 2 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index a6b9e49bc25c5..4d97e8c9150ec 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5203,7 +5203,6 @@ void CodeGenFunction::EmitCallArgs(
     // Reverse the writebacks to match the MSVC ABI.
     Args.reverseWritebacks();
   }
-
 }
 
 namespace {
@@ -5304,7 +5303,8 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
   bool HasAggregateEvalKind = hasAggregateEvaluationKind(type);
 
   if (IsBypassed && HasAggregateEvalKind) {
-    LValue LV = getPreEvaluatedTemporary(cast<MaterializeTemporaryExpr>(SubExpr));
+    LValue LV =
+        getPreEvaluatedTemporary(cast<MaterializeTemporaryExpr>(SubExpr));
     args.addUncopiedAggregate(LV, type);
     args.back().setMoveExpr(MoveExpr);
     return;
@@ -5672,7 +5672,6 @@ static unsigned getMaxVectorWidth(const llvm::Type *Ty) {
   return MaxVectorWidth;
 }
 
-
 RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
                                  const CGCallee &Callee,
                                  ReturnValueSlot ReturnValue,
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 339d533e27743..4e7e8bc9aeb8a 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6284,7 +6284,8 @@ static void disableCopyElision(Expr *E) {
   }
 }
 
-static ExprResult InitializeAndBypassArg(Sema &S, const InitializedEntity &Entity,
+static ExprResult InitializeAndBypassArg(Sema &S,
+                                         const InitializedEntity &Entity,
                                          Expr *Arg, bool NeedsBypass,
                                          bool IsListInitialization,
                                          bool AllowExplicit) {
@@ -6309,7 +6310,8 @@ static ExprResult InitializeAndBypassArg(Sema &S, const InitializedEntity &Entit
         Arg->getType()->isRecordType() || Arg->getType()->isAnyComplexType();
     if (IsAggregate)
       disableCopyElision(Result);
-    Result = CoroutineSuspendParameterBypassExpr::Create(S.Context, PreBypassArg, Result);
+    Result = CoroutineSuspendParameterBypassExpr::Create(S.Context,
+                                                         PreBypassArg, Result);
   }
   return Result;
 }
@@ -6406,8 +6408,8 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
       }
       bool NeedsBypass = CallUsesInAlloca && CallHasSuspend &&
                          (!ProtoArgType->isReferenceType() || Arg->isPRValue());
-      ExprResult ArgE = InitializeAndBypassArg(*this, Entity, Arg, NeedsBypass,
-                                               IsListInitialization, AllowExplicit);
+      ExprResult ArgE = InitializeAndBypassArg(
+          *this, Entity, Arg, NeedsBypass, IsListInitialization, AllowExplicit);
       if (ArgE.isInvalid())
         return true;
       Arg = ArgE.getAs<Expr>();
@@ -6452,11 +6454,13 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
         Invalid |= Arg.isInvalid();
         if (!Arg.isInvalid()) {
           Expr *E = Arg.get();
-          bool NeedsBypass = CallUsesInAlloca && CallHasSuspend && E->isPRValue();
+          bool NeedsBypass =
+              CallUsesInAlloca && CallHasSuspend && E->isPRValue();
           if (NeedsBypass) {
             InitializedEntity ArgEntity =
                 InitializedEntity::InitializeTemporary(E->getType());
-            ExprResult ArgE = InitializeAndBypassArg(*this, ArgEntity, E, true, false, false);
+            ExprResult ArgE =
+                InitializeAndBypassArg(*this, ArgEntity, E, true, false, false);
             if (!ArgE.isInvalid())
               E = ArgE.getAs<Expr>();
           }

>From 23c69d1a13ab7e3dd3210661023635afdc68a082 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Thu, 30 Jul 2026 18:07:36 +0000
Subject: [PATCH 18/26] Self review

---
 clang/lib/Sema/SemaExpr.cpp | 100 +++++++++++++++++++-----------------
 1 file changed, 52 insertions(+), 48 deletions(-)

diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 4e7e8bc9aeb8a..6af55aa26c38d 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6273,6 +6273,33 @@ static bool isWin32InAllocaRecord(ASTContext &Context, QualType Ty) {
 
   return true;
 }
+
+static bool callHasSuspend(Sema &S, ArrayRef<Expr *> Args) {
+  if (S.getCurFunction() && S.getCurFunction()->isCoroutine()) {
+    for (const Expr *A : Args) {
+      if (A && A->containsCoroutineSuspendPoints())
+        return true;
+    }
+  }
+  return false;
+}
+
+static bool usesInAlloca(ASTContext &Context, const FunctionProtoType *Proto,
+                         ArrayRef<Expr *> Args) {
+  unsigned NumParams = Proto->getNumParams();
+  for (unsigned i = 0; i < NumParams; i++) {
+    if (isWin32InAllocaRecord(Context, Proto->getParamType(i)))
+      return true;
+  }
+  if (Proto->isVariadic()) {
+    for (size_t i = NumParams; i < Args.size(); i++) {
+      if (Args[i] && isWin32InAllocaRecord(Context, Args[i]->getType()))
+        return true;
+    }
+  }
+  return false;
+}
+
 static void disableCopyElision(Expr *E) {
   if (auto *EWC = dyn_cast<ExprWithCleanups>(E))
     E = EWC->getSubExpr();
@@ -6286,17 +6313,13 @@ static void disableCopyElision(Expr *E) {
 
 static ExprResult InitializeAndBypassArg(Sema &S,
                                          const InitializedEntity &Entity,
-                                         Expr *Arg, bool NeedsBypass,
-                                         bool IsListInitialization,
+                                         Expr *Arg, bool IsListInitialization,
                                          bool AllowExplicit) {
-  Expr *PreBypassArg = Arg;
-  if (NeedsBypass) {
-    ExprResult Materialized =
-        S.CreateMaterializeTemporaryExpr(Arg->getType(), Arg, false);
-    if (Materialized.isInvalid())
-      return ExprError();
-    PreBypassArg = Materialized.get();
-  }
+  ExprResult Materialized =
+      S.CreateMaterializeTemporaryExpr(Arg->getType(), Arg, false);
+  if (Materialized.isInvalid())
+    return ExprError();
+  Expr *PreBypassArg = Materialized.get();
 
   ExprResult ArgE =
       S.PerformCopyInitialization(Entity, SourceLocation(), PreBypassArg,
@@ -6305,14 +6328,12 @@ static ExprResult InitializeAndBypassArg(Sema &S,
     return ExprError();
 
   Expr *Result = ArgE.getAs<Expr>();
-  if (NeedsBypass) {
-    bool IsAggregate =
-        Arg->getType()->isRecordType() || Arg->getType()->isAnyComplexType();
-    if (IsAggregate)
-      disableCopyElision(Result);
-    Result = CoroutineSuspendParameterBypassExpr::Create(S.Context,
-                                                         PreBypassArg, Result);
-  }
+  bool IsAggregate =
+      Arg->getType()->isRecordType() || Arg->getType()->isAnyComplexType();
+  if (IsAggregate)
+    disableCopyElision(Result);
+  Result = CoroutineSuspendParameterBypassExpr::Create(S.Context, PreBypassArg,
+                                                       Result);
   return Result;
 }
 
@@ -6323,30 +6344,8 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
                                   VariadicCallType CallType, bool AllowExplicit,
                                   bool IsListInitialization) {
   unsigned NumParams = Proto->getNumParams();
-  bool CallHasSuspend = false;
-  if (getCurFunction() && getCurFunction()->isCoroutine()) {
-    for (const Expr *A : Args) {
-      if (A && A->containsCoroutineSuspendPoints()) {
-        CallHasSuspend = true;
-        break;
-      }
-    }
-  }
-  bool CallUsesInAlloca = false;
-  for (unsigned i = 0; i < NumParams; i++) {
-    if (isWin32InAllocaRecord(Context, Proto->getParamType(i))) {
-      CallUsesInAlloca = true;
-      break;
-    }
-  }
-  if (!CallUsesInAlloca && Proto->isVariadic()) {
-    for (size_t i = NumParams; i < Args.size(); i++) {
-      if (Args[i] && isWin32InAllocaRecord(Context, Args[i]->getType())) {
-        CallUsesInAlloca = true;
-        break;
-      }
-    }
-  }
+  bool CallNeedsBypass =
+      callHasSuspend(*this, Args) && usesInAlloca(Context, Proto, Args);
   bool Invalid = false;
   size_t ArgIx = 0;
   // Continue to check argument types (even if we have too few/many args).
@@ -6406,10 +6405,16 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
                         : diag::warn_obt_discarded_at_function_boundary)
             << Arg->getType() << ProtoArgType;
       }
-      bool NeedsBypass = CallUsesInAlloca && CallHasSuspend &&
+      bool NeedsBypass = CallNeedsBypass &&
                          (!ProtoArgType->isReferenceType() || Arg->isPRValue());
-      ExprResult ArgE = InitializeAndBypassArg(
-          *this, Entity, Arg, NeedsBypass, IsListInitialization, AllowExplicit);
+      ExprResult ArgE;
+      if (NeedsBypass) {
+        ArgE = InitializeAndBypassArg(*this, Entity, Arg, IsListInitialization,
+                                      AllowExplicit);
+      } else {
+        ArgE = PerformCopyInitialization(Entity, SourceLocation(), Arg,
+                                         IsListInitialization, AllowExplicit);
+      }
       if (ArgE.isInvalid())
         return true;
       Arg = ArgE.getAs<Expr>();
@@ -6454,13 +6459,12 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
         Invalid |= Arg.isInvalid();
         if (!Arg.isInvalid()) {
           Expr *E = Arg.get();
-          bool NeedsBypass =
-              CallUsesInAlloca && CallHasSuspend && E->isPRValue();
+          bool NeedsBypass = CallNeedsBypass && E->isPRValue();
           if (NeedsBypass) {
             InitializedEntity ArgEntity =
                 InitializedEntity::InitializeTemporary(E->getType());
             ExprResult ArgE =
-                InitializeAndBypassArg(*this, ArgEntity, E, true, false, false);
+                InitializeAndBypassArg(*this, ArgEntity, E, false, false);
             if (!ArgE.isInvalid())
               E = ArgE.getAs<Expr>();
           }

>From 5f04c00cea4a81f10bed3e1160aba6cd6f8272ab Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Thu, 30 Jul 2026 18:35:14 +0000
Subject: [PATCH 19/26] Fix

---
 clang/test/AST/ast-dump-coroutine-inalloca.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/clang/test/AST/ast-dump-coroutine-inalloca.cpp b/clang/test/AST/ast-dump-coroutine-inalloca.cpp
index 221c7bf47789d..1111494a4f8c7 100644
--- a/clang/test/AST/ast-dump-coroutine-inalloca.cpp
+++ b/clang/test/AST/ast-dump-coroutine-inalloca.cpp
@@ -59,6 +59,6 @@ task test() {
 // CHECK-NEXT: | `-CoroutineSuspendParameterBypassExpr {{.*}} 'Noisy'
 // CHECK-NEXT: |   |-bypass_sub_expr: MaterializeTemporaryExpr {{.*}} 'Noisy' xvalue
 // CHECK:          `-bypass_move_expr: CXXBindTemporaryExpr {{.*}} 'Noisy'
-// CHECK-NEXT:       `-CXXConstructExpr {{.*}} 'Noisy' 'void (Noisy &&) __attribute__((thiscall)) noexcept' elidable
+// CHECK-NEXT:       `-CXXConstructExpr {{.*}} 'Noisy' 'void (Noisy &&) __attribute__((thiscall)) noexcept'
 // CHECK-NEXT:         `-MaterializeTemporaryExpr {{.*}} 'Noisy' xvalue
 // CHECK-NEXT:           `-CoawaitExpr {{.*}} 'Noisy'

>From 3124696ac9753aa2202a90762f1499b3efe44602 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Thu, 30 Jul 2026 18:52:00 +0000
Subject: [PATCH 20/26] Fix

---
 clang/include/clang/AST/IgnoreExpr.h         | 3 +++
 clang/lib/StaticAnalyzer/Core/ExprEngine.cpp | 1 +
 clang/tools/libclang/CXCursor.cpp            | 1 +
 3 files changed, 5 insertions(+)

diff --git a/clang/include/clang/AST/IgnoreExpr.h b/clang/include/clang/AST/IgnoreExpr.h
index 3b50b8115c6b8..25fd47c174477 100644
--- a/clang/include/clang/AST/IgnoreExpr.h
+++ b/clang/include/clang/AST/IgnoreExpr.h
@@ -109,6 +109,9 @@ inline Expr *IgnoreImplicitSingleStep(Expr *E) {
   if (auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E))
     return BTE->getSubExpr();
 
+  if (auto *Bypass = dyn_cast<CoroutineSuspendParameterBypassExpr>(E))
+    return Bypass->getSubExpr();
+
   return E;
 }
 
diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
index 7669b65818272..ddafe5448bf6a 100644
--- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
+++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp
@@ -1707,6 +1707,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
     case Stmt::PackIndexingExprClass:
     case Stmt::SubstNonTypeTemplateParmPackExprClass:
     case Stmt::FunctionParmPackExprClass:
+    case Stmt::CoroutineSuspendParameterBypassExprClass:
     case Stmt::CoroutineBodyStmtClass:
     case Stmt::CoawaitExprClass:
     case Stmt::DependentCoawaitExprClass:
diff --git a/clang/tools/libclang/CXCursor.cpp b/clang/tools/libclang/CXCursor.cpp
index fb0b3c1502574..ca35c44b38842 100644
--- a/clang/tools/libclang/CXCursor.cpp
+++ b/clang/tools/libclang/CXCursor.cpp
@@ -308,6 +308,7 @@ CXCursor cxcursor::MakeCXCursor(const Stmt *S, const Decl *Parent,
   case Stmt::CoawaitExprClass:
   case Stmt::DependentCoawaitExprClass:
   case Stmt::CoyieldExprClass:
+  case Stmt::CoroutineSuspendParameterBypassExprClass:
   case Stmt::CXXBindTemporaryExprClass:
   case Stmt::CXXDefaultArgExprClass:
   case Stmt::CXXDefaultInitExprClass:

>From 74ec8cd958c82ba56b2d3a3b5dfab7f143601274 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Fri, 31 Jul 2026 12:58:36 +0000
Subject: [PATCH 21/26] CR-efriedma-quic

---
 clang/include/clang/AST/Expr.h                |  3 -
 clang/lib/AST/Expr.cpp                        | 26 +--------
 clang/lib/CodeGen/CGCall.cpp                  |  3 +
 clang/lib/CodeGen/CGExprAgg.cpp               |  1 +
 clang/lib/CodeGen/CGExprComplex.cpp           |  1 +
 clang/lib/CodeGen/CGExprScalar.cpp            |  1 +
 clang/lib/CodeGen/CodeGenFunction.h           | 11 ++++
 clang/lib/Sema/SemaExpr.cpp                   | 58 +++++++++++++------
 .../test/AST/ast-dump-coroutine-inalloca.cpp  |  5 +-
 9 files changed, 61 insertions(+), 48 deletions(-)

diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 9a6347d08f790..6c258d51f6241 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -247,9 +247,6 @@ class Expr : public ValueStmt {
     return static_cast<bool>(getDependence() & ExprDependence::Error);
   }
 
-  /// Whether this expression contains a coroutine suspend point
-  /// (co_await or co_yield).
-  bool containsCoroutineSuspendPoints() const;
 
   /// getExprLoc - Return the preferred location for the arrow when diagnosing
   /// a problem with a generic expression.
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index 9a34e48e41a59..e2682a445be6d 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -3999,30 +3999,6 @@ bool Expr::HasSideEffects(const ASTContext &Ctx,
   return false;
 }
 
-static bool containsCoroutineSuspendPoints(const Stmt *S) {
-  if (!S)
-    return false;
-  if (isa<CoawaitExpr>(S) || isa<CoyieldExpr>(S) ||
-      isa<DependentCoawaitExpr>(S))
-    return true;
-
-  if (auto *LE = dyn_cast<LambdaExpr>(S)) {
-    for (const Expr *Init : LE->capture_inits()) {
-      if (Init && containsCoroutineSuspendPoints(Init))
-        return true;
-    }
-    return false;
-  }
-
-  for (const Stmt *Child : S->children())
-    if (Child && containsCoroutineSuspendPoints(Child))
-      return true;
-  return false;
-}
-
-bool Expr::containsCoroutineSuspendPoints() const {
-  return ::containsCoroutineSuspendPoints(this);
-}
 
 FPOptions Expr::getFPFeaturesInEffect(const LangOptions &LO) const {
   if (auto Call = dyn_cast<CallExpr>(this))
@@ -5204,6 +5180,8 @@ const OpaqueValueExpr *OpaqueValueExpr::findInCopyConstruct(const Expr *e) {
     e = ewc->getSubExpr();
   if (const MaterializeTemporaryExpr *m = dyn_cast<MaterializeTemporaryExpr>(e))
     e = m->getSubExpr();
+  if (const CXXBindTemporaryExpr *bte = dyn_cast<CXXBindTemporaryExpr>(e))
+    e = bte->getSubExpr();
   e = cast<CXXConstructExpr>(e)->getArg(0);
   while (const ImplicitCastExpr *ice = dyn_cast<ImplicitCastExpr>(e))
     e = ice->getSubExpr();
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 4d97e8c9150ec..2bad5327dbb10 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5847,6 +5847,9 @@ RValue CodeGenFunction::EmitCall(const CGFunctionInfo &CallInfo,
               Addr, I->Ty.getQualifiers(), AggValueSlot::IsDestructed,
               AggValueSlot::DoesNotNeedGCBarriers, AggValueSlot::IsNotAliased,
               AggValueSlot::DoesNotOverlap);
+          OpaqueValueMapping OVM(
+              *this, OpaqueValueExpr::findInCopyConstruct(I->getMoveExpr()),
+              I->getKnownLValue());
           EmitAggExpr(I->getMoveExpr(), Slot);
           if (ArgInfo.getInAllocaIndirect()) {
             Address ArgSlot = Builder.CreateStructGEP(
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index 87673afc287aa..b826e70126f50 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -802,6 +802,7 @@ void AggExprEmitter::VisitOpaqueValueExpr(OpaqueValueExpr *e) {
 
 void AggExprEmitter::VisitCoroutineSuspendParameterBypassExpr(
     CoroutineSuspendParameterBypassExpr *E) {
+  CodeGenFunction::CoroutineSuspendParameterBypassMapping Mapping(CGF, E);
   Visit(E->getMoveExpr());
 }
 
diff --git a/clang/lib/CodeGen/CGExprComplex.cpp b/clang/lib/CodeGen/CGExprComplex.cpp
index 53ce4d61d485e..9ad4737c36024 100644
--- a/clang/lib/CodeGen/CGExprComplex.cpp
+++ b/clang/lib/CodeGen/CGExprComplex.cpp
@@ -171,6 +171,7 @@ class ComplexExprEmitter
 
   ComplexPairTy VisitCoroutineSuspendParameterBypassExpr(
       CoroutineSuspendParameterBypassExpr *E) {
+    CodeGenFunction::CoroutineSuspendParameterBypassMapping Mapping(CGF, E);
     return Visit(E->getMoveExpr());
   }
   ComplexPairTy VisitPseudoObjectExpr(PseudoObjectExpr *E) {
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index d3064d2498a03..cfd23aee9bf42 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -602,6 +602,7 @@ class ScalarExprEmitter
 
   Value *VisitCoroutineSuspendParameterBypassExpr(
       CoroutineSuspendParameterBypassExpr *E) {
+    CodeGenFunction::CoroutineSuspendParameterBypassMapping Mapping(CGF, E);
     return Visit(E->getMoveExpr());
   }
 
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 4517b03be8bee..4b3d9bb934225 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -3118,6 +3118,17 @@ class CodeGenFunction : public CodeGenTypeCache {
     return It->second;
   }
 
+  class CoroutineSuspendParameterBypassMapping {
+    OpaqueValueMapping OVM;
+
+  public:
+    CoroutineSuspendParameterBypassMapping(
+        CodeGenFunction &CGF, const CoroutineSuspendParameterBypassExpr *E)
+        : OVM(CGF, OpaqueValueExpr::findInCopyConstruct(E->getMoveExpr()),
+              CGF.getPreEvaluatedTemporary(
+                  cast<MaterializeTemporaryExpr>(E->getSubExpr()))) {}
+  };
+
   /// Get the index of the current ArrayInitLoopExpr, if any.
   llvm::Value *getArrayInitIndex() { return ArrayInitIndex; }
 
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 6af55aa26c38d..cae4770b87e97 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6274,10 +6274,41 @@ static bool isWin32InAllocaRecord(ASTContext &Context, QualType Ty) {
   return true;
 }
 
+namespace {
+class CoroutineSuspendPointDetector
+    : public ConstEvaluatedExprVisitor<CoroutineSuspendPointDetector> {
+  using Base = ConstEvaluatedExprVisitor<CoroutineSuspendPointDetector>;
+
+public:
+  bool Suspends = false;
+
+  CoroutineSuspendPointDetector(const ASTContext &Ctx) : Base(Ctx) {}
+
+  void Visit(const Expr *E) {
+    if (!E || Suspends)
+      return;
+    Base::Visit(E);
+  }
+
+  void VisitCoawaitExpr(const CoawaitExpr *E) { Suspends = true; }
+  void VisitCoyieldExpr(const CoyieldExpr *E) { Suspends = true; }
+  void VisitDependentCoawaitExpr(const DependentCoawaitExpr *E) {
+    Suspends = true;
+  }
+};
+} // namespace
+
+static bool containsCoroutineSuspendPoints(const ASTContext &Ctx,
+                                           const Expr *E) {
+  CoroutineSuspendPointDetector Detector(Ctx);
+  Detector.Visit(E);
+  return Detector.Suspends;
+}
+
 static bool callHasSuspend(Sema &S, ArrayRef<Expr *> Args) {
   if (S.getCurFunction() && S.getCurFunction()->isCoroutine()) {
     for (const Expr *A : Args) {
-      if (A && A->containsCoroutineSuspendPoints())
+      if (A && containsCoroutineSuspendPoints(S.Context, A))
         return true;
     }
   }
@@ -6300,17 +6331,6 @@ static bool usesInAlloca(ASTContext &Context, const FunctionProtoType *Proto,
   return false;
 }
 
-static void disableCopyElision(Expr *E) {
-  if (auto *EWC = dyn_cast<ExprWithCleanups>(E))
-    E = EWC->getSubExpr();
-  if (auto *BTE = dyn_cast<CXXBindTemporaryExpr>(E))
-    E = BTE->getSubExpr();
-  if (auto *CCE = dyn_cast<CXXConstructExpr>(E)) {
-    if (CCE->isElidable())
-      CCE->setElidable(false);
-  }
-}
-
 static ExprResult InitializeAndBypassArg(Sema &S,
                                          const InitializedEntity &Entity,
                                          Expr *Arg, bool IsListInitialization,
@@ -6321,17 +6341,17 @@ static ExprResult InitializeAndBypassArg(Sema &S,
     return ExprError();
   Expr *PreBypassArg = Materialized.get();
 
-  ExprResult ArgE =
-      S.PerformCopyInitialization(Entity, SourceLocation(), PreBypassArg,
-                                  IsListInitialization, AllowExplicit);
+  auto *OVE = new (S.Context)
+      OpaqueValueExpr(PreBypassArg->getExprLoc(), PreBypassArg->getType(),
+                      PreBypassArg->getValueKind(),
+                      PreBypassArg->getObjectKind(), PreBypassArg);
+
+  ExprResult ArgE = S.PerformCopyInitialization(
+      Entity, SourceLocation(), OVE, IsListInitialization, AllowExplicit);
   if (ArgE.isInvalid())
     return ExprError();
 
   Expr *Result = ArgE.getAs<Expr>();
-  bool IsAggregate =
-      Arg->getType()->isRecordType() || Arg->getType()->isAnyComplexType();
-  if (IsAggregate)
-    disableCopyElision(Result);
   Result = CoroutineSuspendParameterBypassExpr::Create(S.Context, PreBypassArg,
                                                        Result);
   return Result;
diff --git a/clang/test/AST/ast-dump-coroutine-inalloca.cpp b/clang/test/AST/ast-dump-coroutine-inalloca.cpp
index 1111494a4f8c7..b7b75f2bdbff6 100644
--- a/clang/test/AST/ast-dump-coroutine-inalloca.cpp
+++ b/clang/test/AST/ast-dump-coroutine-inalloca.cpp
@@ -60,5 +60,6 @@ task test() {
 // CHECK-NEXT: |   |-bypass_sub_expr: MaterializeTemporaryExpr {{.*}} 'Noisy' xvalue
 // CHECK:          `-bypass_move_expr: CXXBindTemporaryExpr {{.*}} 'Noisy'
 // CHECK-NEXT:       `-CXXConstructExpr {{.*}} 'Noisy' 'void (Noisy &&) __attribute__((thiscall)) noexcept'
-// CHECK-NEXT:         `-MaterializeTemporaryExpr {{.*}} 'Noisy' xvalue
-// CHECK-NEXT:           `-CoawaitExpr {{.*}} 'Noisy'
+// CHECK-NEXT:         `-OpaqueValueExpr {{.*}} 'Noisy' xvalue
+// CHECK-NEXT:           `-MaterializeTemporaryExpr {{.*}} 'Noisy' xvalue
+// CHECK-NEXT:             `-CoawaitExpr {{.*}} 'Noisy'

>From 3aa8f6b5d46445e03065fc6b8b81cc00200b88eb Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Fri, 31 Jul 2026 13:04:29 +0000
Subject: [PATCH 22/26] Nit

---
 clang/include/clang/AST/Expr.h | 1 -
 clang/lib/AST/Expr.cpp         | 1 -
 2 files changed, 2 deletions(-)

diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 6c258d51f6241..f95f87cc4e8e0 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -247,7 +247,6 @@ class Expr : public ValueStmt {
     return static_cast<bool>(getDependence() & ExprDependence::Error);
   }
 
-
   /// getExprLoc - Return the preferred location for the arrow when diagnosing
   /// a problem with a generic expression.
   SourceLocation getExprLoc() const LLVM_READONLY;
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index e2682a445be6d..4e49074beb18e 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -3999,7 +3999,6 @@ bool Expr::HasSideEffects(const ASTContext &Ctx,
   return false;
 }
 
-
 FPOptions Expr::getFPFeaturesInEffect(const LangOptions &LO) const {
   if (auto Call = dyn_cast<CallExpr>(this))
     return Call->getFPFeaturesInEffect(LO);

>From ffc69b54cc9c891605b84b5979e3160bfe35a466 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Fri, 31 Jul 2026 13:14:04 +0000
Subject: [PATCH 23/26] Nit

---
 clang/lib/CodeGen/CGExprCXX.cpp | 11 +----------
 clang/lib/Sema/SemaExpr.cpp     |  1 +
 2 files changed, 2 insertions(+), 10 deletions(-)

diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp
index 7410e2bc18f51..e400a5c5a49c5 100644
--- a/clang/lib/CodeGen/CGExprCXX.cpp
+++ b/clang/lib/CodeGen/CGExprCXX.cpp
@@ -637,16 +637,7 @@ void CodeGenFunction::EmitCXXConstructExpr(const CXXConstructExpr *E,
     return;
 
   // Elide the constructor if we're constructing from a temporary.
-  bool DisableElision = false;
-  if (E->getNumArgs() > 0) {
-    if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(
-            E->getArg(0)->IgnoreImpCasts())) {
-      if (hasPreEvaluatedTemporary(MTE))
-        DisableElision = true;
-    }
-  }
-
-  if (getLangOpts().ElideConstructors && E->isElidable() && !DisableElision) {
+  if (getLangOpts().ElideConstructors && E->isElidable()) {
     // FIXME: This only handles the simplest case, where the source object
     //        is passed directly as the first argument to the constructor.
     //        This should also handle stepping though implicit casts and
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index cae4770b87e97..f45de35b6e4a0 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -6437,6 +6437,7 @@ bool Sema::GatherArgumentsForCall(SourceLocation CallLoc, FunctionDecl *FDecl,
       }
       if (ArgE.isInvalid())
         return true;
+
       Arg = ArgE.getAs<Expr>();
     } else {
       assert(Param && "can't use default arguments without a known callee");

>From 6b7483294bfcd93b99104a6cb2d123523396ca11 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Sat, 1 Aug 2026 10:45:05 +0000
Subject: [PATCH 24/26] CR-efriedma-quic

---
 clang/lib/CodeGen/CGCall.cpp                  | 27 ++++---
 clang/lib/CodeGen/CGExpr.cpp                  |  4 --
 clang/lib/CodeGen/CGExprAgg.cpp               | 11 ---
 clang/lib/CodeGen/CGExprComplex.cpp           |  1 -
 clang/lib/CodeGen/CGExprScalar.cpp            |  1 -
 clang/lib/CodeGen/CodeGenFunction.h           | 20 ------
 .../Modules/coroutine-bypass-inalloca.cpp     | 71 +++++++++++++++++++
 7 files changed, 87 insertions(+), 48 deletions(-)
 create mode 100644 clang/test/Modules/coroutine-bypass-inalloca.cpp

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index 2bad5327dbb10..aefcb309cfdc9 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5129,29 +5129,34 @@ void CodeGenFunction::EmitCallArgs(
       std::swap(Args.back(), *(&Args.back() - 1));
   };
 
-  SmallVector<const MaterializeTemporaryExpr *, 4> MTEsToPreEvaluate;
+  SmallVector<const CoroutineSuspendParameterBypassExpr *, 4>
+      BypassesToPreEvaluate;
   if (isCoroutine() && hasInAllocaArgs(CGM, ExplicitCC, ArgTypes)) {
     for (const Expr *A : ArgRange) {
       if (auto *Bypass = dyn_cast<CoroutineSuspendParameterBypassExpr>(A)) {
-        if (auto *MTE =
-                dyn_cast<MaterializeTemporaryExpr>(Bypass->getSubExpr())) {
-          MTEsToPreEvaluate.push_back(MTE);
-        }
+        BypassesToPreEvaluate.push_back(Bypass);
       }
     }
   }
 
-  if (!MTEsToPreEvaluate.empty()) {
+  SmallVector<std::unique_ptr<OpaqueValueMapping>, 4> Mappings;
+  if (!BypassesToPreEvaluate.empty()) {
     if (LeftToRight) {
-      for (const MaterializeTemporaryExpr *MTE : MTEsToPreEvaluate) {
+      for (const CoroutineSuspendParameterBypassExpr *Bypass :
+           BypassesToPreEvaluate) {
+        const auto *MTE = cast<MaterializeTemporaryExpr>(Bypass->getSubExpr());
         LValue LV = EmitMaterializeTemporaryExpr(MTE);
-        PreEvaluatedMaterializedTemporaries[MTE] = LV;
+        Mappings.push_back(std::make_unique<OpaqueValueMapping>(
+            *this, Bypass->getOpaqueValue(), LV));
       }
     } else {
-      for (int i = MTEsToPreEvaluate.size() - 1; i >= 0; --i) {
-        const MaterializeTemporaryExpr *MTE = MTEsToPreEvaluate[i];
+      for (int i = BypassesToPreEvaluate.size() - 1; i >= 0; --i) {
+        const CoroutineSuspendParameterBypassExpr *Bypass =
+            BypassesToPreEvaluate[i];
+        const auto *MTE = cast<MaterializeTemporaryExpr>(Bypass->getSubExpr());
         LValue LV = EmitMaterializeTemporaryExpr(MTE);
-        PreEvaluatedMaterializedTemporaries[MTE] = LV;
+        Mappings.push_back(std::make_unique<OpaqueValueMapping>(
+            *this, Bypass->getOpaqueValue(), LV));
       }
     }
   }
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 2d05121c24f4f..044e67db91681 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -521,10 +521,6 @@ static bool isAAPCS(const TargetInfo &TargetInfo) {
 
 LValue CodeGenFunction::
 EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *M) {
-  auto It = PreEvaluatedMaterializedTemporaries.find(M);
-  if (It != PreEvaluatedMaterializedTemporaries.end()) {
-    return It->second;
-  }
 
   const Expr *E = M->getSubExpr();
 
diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp
index b826e70126f50..454417e73f86b 100644
--- a/clang/lib/CodeGen/CGExprAgg.cpp
+++ b/clang/lib/CodeGen/CGExprAgg.cpp
@@ -779,16 +779,6 @@ void AggExprEmitter::EmitArrayInit(Address DestPtr, llvm::ArrayType *AType,
 
 void AggExprEmitter::VisitMaterializeTemporaryExpr(
     MaterializeTemporaryExpr *E) {
-  if (CGF.hasPreEvaluatedTemporary(E)) {
-    LValue SrcLV = CGF.getPreEvaluatedTemporary(E);
-    QualType Type = E->getType();
-    assert((!Type->getAsCXXRecordDecl() ||
-            Type->getAsCXXRecordDecl()->isTriviallyCopyable()) &&
-           "Non-trivially copyable MTE should not be visited as aggregate when "
-           "pre-evaluated");
-    EmitFinalDestCopy(Type, SrcLV);
-    return;
-  }
   Visit(E->getSubExpr());
 }
 
@@ -802,7 +792,6 @@ void AggExprEmitter::VisitOpaqueValueExpr(OpaqueValueExpr *e) {
 
 void AggExprEmitter::VisitCoroutineSuspendParameterBypassExpr(
     CoroutineSuspendParameterBypassExpr *E) {
-  CodeGenFunction::CoroutineSuspendParameterBypassMapping Mapping(CGF, E);
   Visit(E->getMoveExpr());
 }
 
diff --git a/clang/lib/CodeGen/CGExprComplex.cpp b/clang/lib/CodeGen/CGExprComplex.cpp
index 9ad4737c36024..53ce4d61d485e 100644
--- a/clang/lib/CodeGen/CGExprComplex.cpp
+++ b/clang/lib/CodeGen/CGExprComplex.cpp
@@ -171,7 +171,6 @@ class ComplexExprEmitter
 
   ComplexPairTy VisitCoroutineSuspendParameterBypassExpr(
       CoroutineSuspendParameterBypassExpr *E) {
-    CodeGenFunction::CoroutineSuspendParameterBypassMapping Mapping(CGF, E);
     return Visit(E->getMoveExpr());
   }
   ComplexPairTy VisitPseudoObjectExpr(PseudoObjectExpr *E) {
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index cfd23aee9bf42..d3064d2498a03 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -602,7 +602,6 @@ class ScalarExprEmitter
 
   Value *VisitCoroutineSuspendParameterBypassExpr(
       CoroutineSuspendParameterBypassExpr *E) {
-    CodeGenFunction::CoroutineSuspendParameterBypassMapping Mapping(CGF, E);
     return Visit(E->getMoveExpr());
   }
 
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 4b3d9bb934225..4c9157840d479 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -1774,8 +1774,6 @@ class CodeGenFunction : public CodeGenTypeCache {
   /// expressions.
   llvm::DenseMap<const OpaqueValueExpr *, LValue> OpaqueLValues;
   llvm::DenseMap<const OpaqueValueExpr *, RValue> OpaqueRValues;
-  llvm::DenseMap<const MaterializeTemporaryExpr *, LValue>
-      PreEvaluatedMaterializedTemporaries;
 
   // VLASizeMap - This keeps track of the associated size for each VLA type.
   // We track this by the size expression rather than the type itself because
@@ -3108,26 +3106,8 @@ class CodeGenFunction : public CodeGenTypeCache {
   /// already been emitted.
   bool isOpaqueValueEmitted(const OpaqueValueExpr *E);
 
-  bool hasPreEvaluatedTemporary(const MaterializeTemporaryExpr *M) const {
-    return PreEvaluatedMaterializedTemporaries.count(M);
-  }
-  LValue getPreEvaluatedTemporary(const MaterializeTemporaryExpr *M) {
-    auto It = PreEvaluatedMaterializedTemporaries.find(M);
-    assert(It != PreEvaluatedMaterializedTemporaries.end() &&
-           "MTE is not pre-evaluated");
-    return It->second;
-  }
 
-  class CoroutineSuspendParameterBypassMapping {
-    OpaqueValueMapping OVM;
 
-  public:
-    CoroutineSuspendParameterBypassMapping(
-        CodeGenFunction &CGF, const CoroutineSuspendParameterBypassExpr *E)
-        : OVM(CGF, OpaqueValueExpr::findInCopyConstruct(E->getMoveExpr()),
-              CGF.getPreEvaluatedTemporary(
-                  cast<MaterializeTemporaryExpr>(E->getSubExpr()))) {}
-  };
 
   /// Get the index of the current ArrayInitLoopExpr, if any.
   llvm::Value *getArrayInitIndex() { return ArrayInitIndex; }
diff --git a/clang/test/Modules/coroutine-bypass-inalloca.cpp b/clang/test/Modules/coroutine-bypass-inalloca.cpp
new file mode 100644
index 0000000000000..ca8d4eca43dd0
--- /dev/null
+++ b/clang/test/Modules/coroutine-bypass-inalloca.cpp
@@ -0,0 +1,71 @@
+// RUN: rm -rf %t
+// RUN: split-file %s %t
+//
+// RUN: %clang_cc1 -std=c++20 -triple i686-pc-windows-msvc -Wno-coroutines-unsupported-target \
+// RUN:   -emit-reduced-module-interface -o %t/m.pcm %t/m.cppm
+// RUN: %clang_cc1 -std=c++20 -triple i686-pc-windows-msvc -Wno-coroutines-unsupported-target \
+// RUN:   -fmodule-file=m=%t/m.pcm -fsyntax-only -verify %t/use.cpp
+
+//--- m.cppm
+export module m;
+
+namespace std {
+template <typename R, typename... Args>
+struct coroutine_traits {
+  using promise_type = typename R::promise_type;
+};
+
+template <class Promise = void> struct coroutine_handle {
+  coroutine_handle() = default;
+  static coroutine_handle from_address(void *) noexcept;
+};
+template <> struct coroutine_handle<void> {
+  static coroutine_handle from_address(void *) noexcept;
+  coroutine_handle() = default;
+  template <class PromiseType>
+  coroutine_handle(coroutine_handle<PromiseType>) noexcept;
+};
+} // namespace std
+
+struct suspend_never {
+  bool await_ready() noexcept { return true; }
+  void await_suspend(std::coroutine_handle<>) noexcept {}
+  void await_resume() noexcept {}
+};
+
+export struct task {
+  struct promise_type {
+    task get_return_object() { return {}; }
+    suspend_never initial_suspend() { return {}; }
+    suspend_never final_suspend() noexcept { return {}; }
+    void return_void() {}
+    void unhandled_exception() {}
+  };
+};
+
+export struct Noisy {
+  int val;
+  Noisy(int v);
+  Noisy(const Noisy&) = delete;
+  Noisy(Noisy&& o) noexcept;
+  ~Noisy();
+};
+
+export struct Awaiter {
+  bool await_ready() noexcept { return false; }
+  void await_suspend(std::coroutine_handle<>) noexcept {}
+  Noisy await_resume() noexcept;
+};
+
+export void consume_two(Noisy x, Noisy y);
+
+export inline task my_task() {
+  consume_two(co_await Awaiter{}, Noisy(42));
+}
+
+//--- use.cpp
+// expected-no-diagnostics
+import m;
+void use() {
+  my_task();
+}

>From d64c81e4add1bda5f823d091dd20e312b4202063 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Sat, 1 Aug 2026 10:48:21 +0000
Subject: [PATCH 25/26] Nit

---
 clang/lib/CodeGen/CGExpr.cpp        | 1 -
 clang/lib/CodeGen/CodeGenFunction.h | 3 ---
 2 files changed, 4 deletions(-)

diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 044e67db91681..9201e40bc13a1 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -521,7 +521,6 @@ static bool isAAPCS(const TargetInfo &TargetInfo) {
 
 LValue CodeGenFunction::
 EmitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *M) {
-
   const Expr *E = M->getSubExpr();
 
   assert((!M->getExtendingDecl() || !isa<VarDecl>(M->getExtendingDecl()) ||
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 4c9157840d479..3fc9052adb87b 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -3106,9 +3106,6 @@ class CodeGenFunction : public CodeGenTypeCache {
   /// already been emitted.
   bool isOpaqueValueEmitted(const OpaqueValueExpr *E);
 
-
-
-
   /// Get the index of the current ArrayInitLoopExpr, if any.
   llvm::Value *getArrayInitIndex() { return ArrayInitIndex; }
 

>From f730e2d512ac12a3695bccfb0b5bb837f2c82af5 Mon Sep 17 00:00:00 2001
From: Etienne Pierre-doray <etiennep at chromium.org>
Date: Sat, 1 Aug 2026 11:30:35 +0000
Subject: [PATCH 26/26] Fix

---
 clang/lib/CodeGen/CGCall.cpp | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index aefcb309cfdc9..5a3dde5bdde4d 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -5146,8 +5146,11 @@ void CodeGenFunction::EmitCallArgs(
            BypassesToPreEvaluate) {
         const auto *MTE = cast<MaterializeTemporaryExpr>(Bypass->getSubExpr());
         LValue LV = EmitMaterializeTemporaryExpr(MTE);
-        Mappings.push_back(std::make_unique<OpaqueValueMapping>(
-            *this, Bypass->getOpaqueValue(), LV));
+        const OpaqueValueExpr *OVE =
+            OpaqueValueExpr::findInCopyConstruct(Bypass->getMoveExpr());
+        assert(OVE && "OVE not found in MoveExpr");
+        Mappings.push_back(
+            std::make_unique<OpaqueValueMapping>(*this, OVE, LV));
       }
     } else {
       for (int i = BypassesToPreEvaluate.size() - 1; i >= 0; --i) {
@@ -5155,8 +5158,11 @@ void CodeGenFunction::EmitCallArgs(
             BypassesToPreEvaluate[i];
         const auto *MTE = cast<MaterializeTemporaryExpr>(Bypass->getSubExpr());
         LValue LV = EmitMaterializeTemporaryExpr(MTE);
-        Mappings.push_back(std::make_unique<OpaqueValueMapping>(
-            *this, Bypass->getOpaqueValue(), LV));
+        const OpaqueValueExpr *OVE =
+            OpaqueValueExpr::findInCopyConstruct(Bypass->getMoveExpr());
+        assert(OVE && "OVE not found in MoveExpr");
+        Mappings.push_back(
+            std::make_unique<OpaqueValueMapping>(*this, OVE, LV));
       }
     }
   }
@@ -5308,8 +5314,9 @@ void CodeGenFunction::EmitCallArg(CallArgList &args, const Expr *E,
   bool HasAggregateEvalKind = hasAggregateEvaluationKind(type);
 
   if (IsBypassed && HasAggregateEvalKind) {
-    LValue LV =
-        getPreEvaluatedTemporary(cast<MaterializeTemporaryExpr>(SubExpr));
+    const OpaqueValueExpr *OVE = OpaqueValueExpr::findInCopyConstruct(MoveExpr);
+    assert(OVE && "OVE not found in MoveExpr");
+    LValue LV = getOrCreateOpaqueLValueMapping(OVE);
     args.addUncopiedAggregate(LV, type);
     args.back().setMoveExpr(MoveExpr);
     return;



More information about the cfe-commits mailing list