[clang] [Win32][Coroutine] Fix inalloca arguments lifetime in coroutines (PR #212331)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 29 14:47:04 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/16] [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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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/16] 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;
}
More information about the cfe-commits
mailing list