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 <[email protected]> Date: Mon, 27 Jul 2026 19:14:00 +0000 Subject: [PATCH 1/6] [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 <[email protected]> Date: Mon, 27 Jul 2026 20:32:56 +0000 Subject: [PATCH 2/6] 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 <[email protected]> Date: Wed, 29 Jul 2026 10:49:40 +0000 Subject: [PATCH 3/6] 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 <[email protected]> Date: Wed, 29 Jul 2026 11:05:18 +0000 Subject: [PATCH 4/6] 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 <[email protected]> Date: Wed, 29 Jul 2026 11:15:23 +0000 Subject: [PATCH 5/6] 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@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@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@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 <[email protected]> Date: Wed, 29 Jul 2026 11:22:21 +0000 Subject: [PATCH 6/6] 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; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
