CoroutinesCodeGenerator.cpp
Go to the documentation of this file.
1/******************************************************************************
2 *
3 * C++ Insights, copyright (C) by Andreas Fertig
4 * Distributed under an MIT license. See LICENSE for details
5 *
6 ****************************************************************************/
7
8#include <iterator>
9#include <optional>
10#include <vector>
11#include "ASTHelpers.h"
12#include "CodeGenerator.h"
13#include "DPrint.h"
14#include "Insights.h"
15#include "InsightsHelpers.h"
16#include "NumberIterator.h"
17
18#include <algorithm>
19//-----------------------------------------------------------------------------
20
21namespace ranges = std::ranges;
22//-----------------------------------------------------------------------------
23
24namespace clang::insights {
25
26constexpr std::string_view CORO_FRAME_NAME{"__f"sv};
27const std::string CORO_FRAME_ACCESS{StrCat(CORO_FRAME_NAME, "->"sv)};
29const std::string SUSPEND_INDEX_NAME{BuildInternalVarName("suspend_index"sv)};
30const std::string INITIAL_AWAIT_SUSPEND_CALLED_NAME{BuildInternalVarName("initial_await_suspend_called"sv)};
31const std::string RESUME_LABEL_PREFIX{BuildInternalVarName("resume"sv)};
32const std::string FINAL_SUSPEND_NAME{BuildInternalVarName("final_suspend"sv)};
33//-----------------------------------------------------------------------------
34
35using namespace asthelpers;
36//-----------------------------------------------------------------------------
37
38QualType CoroutinesCodeGenerator::GetFramePointerType() const
39{
40 return Ptr(GetFrameType());
41}
42//-----------------------------------------------------------------------------
43
45{
46 RETURN_IF(not(mASTData.mFrameType and mASTData.mDoInsertInDtor));
47
48 mASTData.mFrameType->completeDefinition();
49
51
52 // Using the "normal" CodeGenerator here as this is only about inserting the made up coroutine-frame.
53 CodeGeneratorVariant codeGenerator{ofm};
54 codeGenerator->InsertArg(mASTData.mFrameType);
55
56 // Insert the made-up struct before the function declaration
57 mOutputFormatHelper.InsertAt(mPosBeforeFunc, ofm);
58}
59//-----------------------------------------------------------------------------
60
61static FieldDecl* AddField(CoroutineASTData& astData, std::string_view name, QualType type)
62{
63 if(nullptr == astData.mFrameType) {
64 return nullptr;
65 }
66
67 auto* fieldDecl = mkFieldDecl(astData.mFrameType, name, type);
68
69 astData.mFrameType->addDecl(fieldDecl);
70
71 return fieldDecl;
72}
73//-----------------------------------------------------------------------------
74
75FieldDecl* CoroutinesCodeGenerator::AddField(std::string_view name, QualType type)
76{
77 return ::clang::insights::AddField(mASTData, name, type);
78}
79//-----------------------------------------------------------------------------
80
81static auto* CreateCoroFunctionDecl(std::string funcName, QualType type)
82{
83 params_vector params{{CORO_FRAME_NAME, type}};
84 const std::string coroFsmName{BuildInternalVarName(funcName)};
85
86 return Function(coroFsmName, VoidTy(), params);
87}
88//-----------------------------------------------------------------------------
89
90static void SetFunctionBody(FunctionDecl* fd, StmtsContainer& bodyStmts)
91{
92 fd->setBody(mkCompoundStmt(bodyStmts));
93}
94//-----------------------------------------------------------------------------
95
96static std::string BuildSuspendVarName(const OpaqueValueExpr* stmt)
97{
99 MakeLineColumnName(GetGlobalAST().getSourceManager(), stmt->getSourceExpr()->getBeginLoc(), "suspend_"sv));
100}
101//-----------------------------------------------------------------------------
102
103/// \brief Find a \c SuspendsExpr's in a coroutine body statement for early transformation.
104///
105/// Traverse the whole CoroutineBodyStmt to find all appearing \c VarDecl. These need to be rerouted to the
106/// coroutine frame and hence prefixed by something like __f->. For that reason we only look for \c VarDecls
107/// directly appearing in the body, \c CallExpr will be skipped.
108class CoroutineASTTransformer : public StmtVisitor<CoroutineASTTransformer>
109{
110 StmtsContainer mBodyStmts{};
111 Stmt* mPrevStmt{}; // used to insert the suspendexpr
112 CoroutineASTData& mASTData;
113 Stmt* mStaged{};
114 bool mSkip{};
115 size_t& mSuspendsCount;
116 llvm::DenseMap<VarDecl*, MemberExpr*> mVarNamePrefix{};
117
118public:
120 size_t& suspendsCounter,
121 Stmt* stmt,
122 llvm::DenseMap<VarDecl*, MemberExpr*> varNamePrefix,
123 Stmt* prev = nullptr)
124 : mPrevStmt{prev}
125 , mASTData{coroutineASTData}
126 , mSuspendsCount{suspendsCounter}
127 , mVarNamePrefix{varNamePrefix}
128 {
129 if(nullptr == mPrevStmt) {
130 mPrevStmt = stmt;
131 }
132
133 Visit(stmt);
134 }
135
136 void Visit(Stmt* stmt)
137 {
138 if(stmt) {
140 }
141 }
142
143 void VisitCompoundStmt(CompoundStmt* stmt)
144 {
145 for(auto* child : stmt->body()) {
146 mStaged = child;
147 Visit(child);
148
149 if(not mSkip) {
150 mBodyStmts.Add(child);
151
152 if(const auto* coret = dyn_cast_or_null<CoreturnStmt>(child);
153 coret and (coret->getOperand() == nullptr)) {
154 mBodyStmts.Add(Goto(FINAL_SUSPEND_NAME));
155 }
156 }
157
158 mSkip = false;
159 }
160
161 auto* comp = mkCompoundStmt(mBodyStmts);
162
163 ReplaceNode(mPrevStmt, stmt, comp);
164
165 mBodyStmts.clear();
166 }
167
168 void VisitSwitchStmt(SwitchStmt* stmt)
169 {
170 Visit(stmt->getCond());
171
172 CoroutineASTTransformer{mASTData, mSuspendsCount, stmt->getBody(), mVarNamePrefix, stmt};
173 }
174
175 void VisitDoStmt(DoStmt* stmt)
176 {
177 Visit(stmt->getCond());
178
179 CoroutineASTTransformer{mASTData, mSuspendsCount, stmt->getBody(), mVarNamePrefix, stmt};
180 }
181
182 void VisitWhileStmt(WhileStmt* stmt)
183 {
184 Visit(stmt->getCond());
185
186 CoroutineASTTransformer{mASTData, mSuspendsCount, stmt->getBody(), mVarNamePrefix, stmt};
187 }
188
189 void VisitIfStmt(IfStmt* stmt)
190 {
191 Visit(stmt->getCond());
192
193 CoroutineASTTransformer{mASTData, mSuspendsCount, stmt->getThen(), mVarNamePrefix, stmt};
194
195 CoroutineASTTransformer{mASTData, mSuspendsCount, stmt->getElse(), mVarNamePrefix, stmt};
196 }
197
198 void VisitForStmt(ForStmt* stmt)
199 {
200 // technically because of the init the entire for loop should be put into a dedicated scope
201 Visit(stmt->getInit());
202
203 // Special case. A VarDecl in init will be added to the body of the function and the actual init is left
204 // untouched. Work some magic to put it in the right place.
205 if(mSkip) {
206 auto* oldInit = stmt->getInit();
207 auto* newInit = mBodyStmts.mStmts.back();
208 mBodyStmts.mStmts.pop_back();
209
210 ReplaceNode(stmt, oldInit, newInit);
211
212 mSkip = false;
213 }
214
215 Visit(stmt->getCond());
216
217 Visit(stmt->getInc());
218
219 CoroutineASTTransformer{mASTData, mSuspendsCount, stmt->getBody(), mVarNamePrefix, stmt};
220
221 mSkip = false;
222 }
223
224 void VisitCXXForRangeStmt(CXXForRangeStmt* stmt)
225 {
226 Visit(stmt->getRangeStmt());
227
228 // ignoring the loop variable should be fine.
229
230 CoroutineASTTransformer{mASTData, mSuspendsCount, stmt->getBody(), mVarNamePrefix, stmt};
231 }
232
233 void VisitDeclRefExpr(DeclRefExpr* stmt)
234 {
235 if(auto* vd = dyn_cast_or_null<VarDecl>(stmt->getDecl())) {
236 RETURN_IF(not vd->isLocalVarDeclOrParm() or vd->isStaticLocal() or not Contains(mVarNamePrefix, vd));
237
238 auto* memberExpr = mVarNamePrefix[vd];
239
240 ReplaceNode(mPrevStmt, stmt, memberExpr);
241 }
242 }
243
244 void VisitDeclStmt(DeclStmt* stmt)
245 {
246 for(auto* decl : stmt->decls()) {
247 if(auto* varDecl = dyn_cast_or_null<VarDecl>(decl)) {
248 if(varDecl->isStaticLocal()) {
249 continue;
250 }
251
252 // add this point a placement-new would be appropriate for at least some cases.
253
254 auto* field = AddField(mASTData, GetName(*varDecl), varDecl->getType());
255 auto* me = AccessMember(mASTData.mFrameAccessDeclRef, field);
256 auto* assign = Assign(me, field, varDecl->getInit());
257
258 mVarNamePrefix.insert(std::make_pair(varDecl, me));
259
260 Visit(varDecl->getInit());
261
262 mSkip = true;
263 mBodyStmts.Add(assign);
264
265 } else if(const auto* recordDecl = dyn_cast_or_null<CXXRecordDecl>(decl)) {
266 mASTData.mFrameType->addDecl(const_cast<CXXRecordDecl*>(recordDecl));
267 }
268 }
269 }
270
271 void VisitCXXThisExpr(CXXThisExpr* stmt)
272 {
273 auto* fieldDecl = mkFieldDecl(mASTData.mFrameType, kwInternalThis, stmt->getType());
274 auto* indirectThisMemberExpr = AccessMember(mASTData.mFrameAccessDeclRef, fieldDecl);
275
276 ReplaceNode(mPrevStmt, stmt, indirectThisMemberExpr);
277
278 if(0 == mASTData.mThisExprs.size()) {
279 mASTData.mThisExprs.push_back(stmt);
280 }
281 }
282
283 void VisitCallExpr(CallExpr* stmt)
284 {
285 auto* tmp = mPrevStmt;
286 mPrevStmt = stmt;
287
288 for(auto* arg : stmt->arguments()) {
289 Visit(arg);
290 }
291
292 mPrevStmt = tmp;
293 }
294
295 void VisitCXXMemberCallExpr(CXXMemberCallExpr* stmt)
296 {
297 auto* tmp = mPrevStmt;
298 mPrevStmt = stmt->getCallee();
299
300 Visit(stmt->getCallee());
301
302 mPrevStmt = tmp;
303
305 }
306
307 void VisitCoreturnStmt(CoreturnStmt* stmt)
308 {
309 Visit(stmt->getOperand());
310 Visit(stmt->getPromiseCall());
311 }
312
313 void VisitCoyieldExpr(CoyieldExpr* stmt)
314 {
315 ++mSuspendsCount;
316
317 if(isa<ExprWithCleanups>(mStaged)) {
318 mBodyStmts.Add(stmt);
319 mSkip = true;
320 }
321
322 Visit(stmt->getOperand());
323 }
324
325 void VisitCoawaitExpr(CoawaitExpr* stmt)
326 {
327 ++mSuspendsCount;
328
329 if(const bool returnsVoid{stmt->getResumeExpr()->getType()->isVoidType()}; returnsVoid) {
330 Visit(stmt->getOperand());
331
332 // in the void return case there is nothing to do, because this expression (potentially) is not nested.
333 return;
334 }
335
336 mBodyStmts.Add(stmt);
337
338 // Note: Add the this pointer to the name isn't the best but s quick approach
339 const std::string name{StrCat(CORO_FRAME_ACCESS, BuildSuspendVarName(stmt->getOpaqueValue()), "_res"sv)};
340
341 auto* resultVar = Variable(name, stmt->getType());
342 auto* resultVarDeclRef = mkDeclRefExpr(resultVar);
343
344 ReplaceNode(mPrevStmt, stmt, resultVarDeclRef);
345
346 Visit(stmt->getCommonExpr());
347 Visit(stmt->getOperand());
348 Visit(stmt->getSuspendExpr());
349 Visit(stmt->getReadyExpr());
350 Visit(stmt->getResumeExpr());
351 }
352
353 void VisitCoroutineBodyStmt(CoroutineBodyStmt* stmt)
354 {
355 auto* varDecl = stmt->getPromiseDecl();
356
357 mASTData.mPromiseField = AddField(mASTData, GetName(*varDecl), varDecl->getType());
358 auto* me = AccessMember(mASTData.mFrameAccessDeclRef, mASTData.mPromiseField);
359
360 mVarNamePrefix.insert(std::make_pair(varDecl, me));
361
362 auto& ctx = GetGlobalAST();
363
364 // add the suspend index variable
365 mASTData.mSuspendIndexField = AddField(mASTData, SUSPEND_INDEX_NAME, ctx.IntTy);
367
368 // https://timsong-cpp.github.io/cppwp/n4861/dcl.fct.def.coroutine#5.3
372
373 for(auto* param : stmt->getParamMoves()) {
374 if(auto* declStmt = dyn_cast_or_null<DeclStmt>(param)) {
375 if(auto* varDecl2 = dyn_cast_or_null<VarDecl>(declStmt->getSingleDecl())) {
376 // For the captured parameters we need to find the ParmVarDecl instead of the newly created VarDecl
377 if(auto* declRef = FindDeclRef(varDecl2->getAnyInitializer())) {
378 auto* varDecl = dyn_cast<ParmVarDecl>(declRef->getDecl());
379
380 auto* field = AddField(mASTData, GetName(*varDecl), varDecl->getType());
381 auto* me = AccessMember(mASTData.mFrameAccessDeclRef, field);
382
383 mVarNamePrefix.insert(std::make_pair(const_cast<ParmVarDecl*>(varDecl), me));
384 }
385 }
386 }
387 }
388
389 Visit(stmt->getBody());
390
391 Visit(stmt->getReturnStmt());
392 Visit(stmt->getReturnValue());
393 Visit(stmt->getReturnValueInit());
394 Visit(stmt->getExceptionHandler());
395 Visit(stmt->getReturnStmtOnAllocFailure());
396 Visit(stmt->getFallthroughHandler());
397 Visit(stmt->getInitSuspendStmt());
398 Visit(stmt->getFinalSuspendStmt());
399 }
400
401 void VisitStmt(Stmt* stmt)
402 {
403 auto* tmp = mPrevStmt;
404 mPrevStmt = stmt;
405
406 for(auto* child : stmt->children()) {
407 Visit(child);
408 }
409
410 mPrevStmt = tmp;
411 }
412};
413//-----------------------------------------------------------------------------
414
415void CoroutinesCodeGenerator::InsertCoroutine(const FunctionDecl& fd, const CoroutineBodyStmt* stmt)
416{
418
419 auto& ctx = GetGlobalAST();
420
421 mFSMName = [&] {
422 OutputFormatHelper ofm{};
423 CodeGeneratorVariant codeGenerator{ofm};
424
425 // Coroutines can be templates and then we end up with the same FSM name but different template parameters.
426 // XXX: This will fail with NTTP's like 3.14
427 if(const auto* args = fd.getTemplateSpecializationArgs()) {
428 ofm.Append('_');
429
430 for(OnceFalse needsUnderscore{}; const auto& arg : args->asArray()) {
431 if(needsUnderscore) {
432 ofm.Append('_');
433 }
434
435 codeGenerator->InsertTemplateArg(arg);
436 }
437 }
438
439 auto str = std::move(ofm.GetString());
440 ReplaceAll(str, "<"sv, ""sv);
441 ReplaceAll(str, ":"sv, ""sv);
442 ReplaceAll(str, ">"sv, ""sv);
443
445
446 if(fd.isOverloadedOperator()) {
447 return StrCat(MakeLineColumnName(ctx.getSourceManager(), stmt->getBeginLoc(), "operator_"sv), str);
448 } else {
449 return StrCat(GetName(fd), str);
450 }
451 }();
452
453 mFrameName = BuildInternalVarName(StrCat(mFSMName, "Frame"sv));
454
455 // Insert a made up struct which holds the "captured" parameters stored in the coroutine frame
456 mASTData.mFrameType = Struct(mFrameName);
457 mASTData.mFrameAccessDeclRef = mkVarDeclRefExpr(CORO_FRAME_NAME, GetFrameType());
458
459 // The coroutine frame starts with two function pointers to the resume and destroy function. See:
460 // https://gcc.gnu.org/legacy-ml/gcc-patches/2020-01/msg01096.html:
461 // "The ABI mandates that pointers into the coroutine frame point to an area
462 // begining with two function pointers (to the resume and destroy functions
463 // described below); these are immediately followed by the "promise object"
464 // described in the standard."
465 //
466 // and
467 // https://llvm.org/docs/Coroutines.html#id72 "Coroutine Representation"
468 auto* resumeFnFd = Function(hlpResumeFn, VoidTy(), {{CORO_FRAME_NAME, GetFramePointerType()}});
469 auto resumeFnType = Ptr(resumeFnFd->getType());
470 mASTData.mResumeFnField = AddField(hlpResumeFn, resumeFnType);
471
472 auto* destroyFnFd = Function(hlpDestroyFn, VoidTy(), {{CORO_FRAME_NAME, GetFramePointerType()}});
473 auto destroyFnType = Ptr(destroyFnFd->getType());
474 mASTData.mDestroyFnField = AddField(hlpDestroyFn, destroyFnType);
475
476 // Allocated the made up frame
477 mOutputFormatHelper.AppendCommentNewLine("Allocate the frame including the promise"sv);
478 mOutputFormatHelper.AppendCommentNewLine("Note: The actual parameter new is __builtin_coro_size"sv);
479
480 auto* coroFrameVar = Variable(CORO_FRAME_NAME, GetFramePointerType());
481 auto* reicast = ReinterpretCast(GetFramePointerType(), stmt->getAllocate());
482
483 coroFrameVar->setInit(reicast);
484
485 InsertArg(coroFrameVar);
486
487 // P0057R8: [dcl.fct.def.coroutine] p8: get_return_object_on_allocation_failure indicates that new may return a
488 // nullptr. In this case return get_return_object_on_allocation_failure.
489 if(stmt->getReturnStmtOnAllocFailure()) {
490 auto* nptr = new(ctx) CXXNullPtrLiteralExpr({});
491
492 // Create an IfStmt.
493 StmtsContainer bodyStmts{stmt->getReturnStmtOnAllocFailure()};
494 auto* ifStmt = If(Equal(nptr, mASTData.mFrameAccessDeclRef), bodyStmts);
495
497 InsertArg(ifStmt);
498 }
499
501 mASTData, mSuspendsCounter, const_cast<CoroutineBodyStmt*>(stmt), llvm::DenseMap<VarDecl*, MemberExpr*>{}};
502
503 // set initial suspend count to zero.
504 auto* setSuspendIndexToZero = Assign(mASTData.mFrameAccessDeclRef, mASTData.mSuspendIndexField, Int32(0));
505 InsertArgWithNull(setSuspendIndexToZero);
506
507 // https://timsong-cpp.github.io/cppwp/n4861/dcl.fct.def.coroutine#5.3
508 auto* initializeInitialAwaitResume =
510 InsertArgWithNull(initializeInitialAwaitResume);
511
512 // Move the parameters first
513 for(auto* param : stmt->getParamMoves()) {
514 if(const auto* declStmt = dyn_cast_or_null<DeclStmt>(param)) {
515 if(const auto* varDecl = dyn_cast_or_null<VarDecl>(declStmt->getSingleDecl())) {
516 const auto varName = GetName(*varDecl);
517
519 varName,
520 " = "sv,
521 "std::forward<"sv,
522 GetName(varDecl->getType()),
523 ">("sv,
524 varName,
525 ");"sv);
526 }
527 }
528 }
529
530 // According to https://eel.is/c++draft/dcl.fct.def.coroutine#5.7 the promise_type constructor can have
531 // parameters. If so, they must be equal to the coroutines function parameters.
532 // The code here performs a _simple_ lookup for a matching ctor without using Clang's overload resolution.
533 ArrayRef<ParmVarDecl*> funParams = fd.parameters();
534 SmallVector<ParmVarDecl*, 16> funParamStorage{};
535 QualType cxxMethodType{};
536
537 if(const auto* cxxMethodDecl = dyn_cast_or_null<CXXMethodDecl>(&fd)) {
538 funParamStorage.reserve(funParams.size() + 1);
539
540 cxxMethodType = cxxMethodDecl->getFunctionObjectParameterType();
541
542 // In case we have a member function the first parameter is a reference to this. The following code injects
543 // this parameter.
544 funParamStorage.push_back(Parameter(&fd, CORO_FRAME_ACCESS_THIS, cxxMethodType));
545
546 ranges::copy(funParams, std::back_inserter(funParamStorage));
547
548 funParams = funParamStorage;
549 }
550
551 auto getNonRefType = [&](auto* var) -> QualType {
552#if IS_CLANG_NEWER_THAN(22)
553 return QualType(var->getType().getNonReferenceType().getCanonicalType().getTypePtrOrNull(), 0);
554#else
555 if(const auto* et = var->getType().getNonReferenceType()->template getAs<ElaboratedType>()) {
556 return et->getNamedType();
557 } else {
558 return QualType(var->getType().getNonReferenceType().getTypePtrOrNull(), 0);
559 }
560#endif
561 };
562
563 SmallVector<Expr*, 16> exprs{};
564
565 for(auto* promiseTypeRecordDecl = mASTData.mPromiseField->getType()->getAsCXXRecordDecl();
566 auto* ctor : promiseTypeRecordDecl->ctors()) {
567
568 if(not ranges::equal(
569 ctor->parameters(), funParams, [&](auto& a, auto& b) { return getNonRefType(a) == getNonRefType(b); })) {
570 continue;
571 }
572
573 // In case of a promise ctor which takes this as the first argument, that parameter needs to be deferences,
574 // as it can only be taken as a reference.
575 OnceTrue derefFirstParam{};
576
577 if(not ctor->param_empty() and (not cxxMethodType.isNull()) and
578 (getNonRefType(ctor->getParamDecl(0)) == QualType(cxxMethodType.getCanonicalType().getTypePtrOrNull(), 0))) {
579 if(0 == mASTData.mThisExprs.size()) {
580 mASTData.mThisExprs.push_back(CXXThisExpr::Create(ctx, {}, Ptr(cxxMethodType), false));
581 }
582 } else {
583 (void)static_cast<bool>(derefFirstParam); // set it to false
584 }
585
586 for(const auto& fparam : funParams) {
587 if(derefFirstParam) {
588 exprs.push_back(Dref(mkDeclRefExpr(fparam)));
589
590 } else {
591 exprs.push_back(AccessMember(mASTData.mFrameAccessDeclRef, fparam));
592 }
593 }
594
595 if(funParams.size()) {
596 // The <new> header needs to be included.
598 }
599
600 break; // We've found what we were looking for
601 }
602
603 if(mASTData.mThisExprs.size()) {
605 }
606
607 // Now call the promise ctor, as it may access some of the parameters it comes at this point.
609 mOutputFormatHelper.AppendCommentNewLine("Construct the promise."sv);
610 auto* me = AccessMember(mASTData.mFrameAccessDeclRef, mASTData.mPromiseField);
611
612 auto* ctorArgs = new(ctx) InitListExpr{ctx,
613 {},
614 exprs,
615 {}
616#if IS_CLANG_NEWER_THAN(22)
617 ,
618 false
619#endif
620 };
621
622 CXXNewExpr* newFrame = New({AddrOf(me)}, ctorArgs, mASTData.mPromiseField->getType());
623
624 InsertArgWithNull(newFrame);
625
626 // Add parameters from the original function to the list
627
628 // P0057R8: [dcl.fct.def.coroutine] p5: before initial_suspend and at tops 1
629
630 // Make a call to the made up state machine function for the initial suspend
632
633 // [dcl.fct.def.coroutine]
634 mOutputFormatHelper.AppendCommentNewLine("Forward declare the resume and destroy function."sv);
635
636 auto* fsmFuncDecl = CreateCoroFunctionDecl(StrCat(mFSMName, "Resume"sv), GetFramePointerType());
637 InsertArg(fsmFuncDecl);
638 auto* deallocFuncDecl = CreateCoroFunctionDecl(StrCat(mFSMName, "Destroy"sv), GetFramePointerType());
639 InsertArg(deallocFuncDecl);
640
642
643 mOutputFormatHelper.AppendCommentNewLine("Assign the resume and destroy function pointers."sv);
644
645 auto* assignResumeFn = Assign(mASTData.mFrameAccessDeclRef, mASTData.mResumeFnField, Ref(fsmFuncDecl));
646 InsertArgWithNull(assignResumeFn);
647
648 auto* assignDestroyFn = Assign(mASTData.mFrameAccessDeclRef, mASTData.mDestroyFnField, Ref(deallocFuncDecl));
649 InsertArgWithNull(assignDestroyFn);
651
653 R"A(Call the made up function with the coroutine body for initial suspend.
654 This function will be called subsequently by coroutine_handle<>::resume()
655 which calls __builtin_coro_resume(__handle_))A"sv);
656
657 auto* callCoroFSM = Call(fsmFuncDecl, {mASTData.mFrameAccessDeclRef});
658 InsertArgWithNull(callCoroFSM);
659
662
663 InsertArg(stmt->getResultDecl());
664 InsertArg(stmt->getReturnStmt());
665
667
668 mOutputFormatHelper.CloseScope(OutputFormatHelper::NoNewLineBefore::Yes);
671
672 // add contents of the original function to the body of our made up function
673 StmtsContainer fsmFuncBodyStmts{stmt};
674
675 mOutputFormatHelper.AppendCommentNewLine("This function invoked by coroutine_handle<>::resume()"sv);
676 SetFunctionBody(fsmFuncDecl, fsmFuncBodyStmts);
677 InsertArg(fsmFuncDecl);
678
679 mASTData.mDoInsertInDtor = true; // As we have a coroutine insert the frame when this object goes out of scope.
680
681#if 0 // Preserve for later. Technically the destructor for the entire frame that's made up below takes care of
682 // everything.
683
684 // A destructor is only present, if they promise_type or one of its members is non-trivially destructible.
685 if(auto* dtor = mASTData.mPromiseField->getType()->getAsCXXRecordDecl()->getDestructor()) {
686 deallocFuncBodyStmts.Add(Comment("Deallocating the coroutine promise type"sv));
687
688 auto* promiseAccess = AccessMember(mASTData.mFrameAccessDeclRef, mASTData.mPromiseField);
689 auto* deallocPromise = AccessMember(promiseAccess, dtor, false);
690 auto* dtorCall = CallMemberFun(deallocPromise, dtor->getType());
691 deallocFuncBodyStmts.Add(dtorCall);
692
693 } else {
694 deallocFuncBodyStmts.Add(
695 Comment("promise_type is trivially destructible, no dtor required."sv));
696 }
697#endif
698
699 // This code isn't really there but it is the easiest and cleanest way to visualize the destruction of all
700 // member in the frame. The deallocation function:
701 // https://devblogs.microsoft.com/oldnewthing/20210331-00/?p=105028
703 mOutputFormatHelper.AppendCommentNewLine("This function invoked by coroutine_handle<>::destroy()"sv);
704
705 StmtsContainer deallocFuncBodyStmts{Comment("destroy all variables with dtors"sv)};
706
707 auto* dtorFuncDecl =
708 Function(StrCat("~"sv, GetName(*mASTData.mFrameType)), VoidTy(), {{CORO_FRAME_NAME, GetFramePointerType()}});
709 auto* deallocPromise = AccessMember(mASTData.mFrameAccessDeclRef, dtorFuncDecl);
710 auto* dtorCall = CallMemberFun(deallocPromise, GetFrameType());
711 deallocFuncBodyStmts.Add(dtorCall);
712
713 deallocFuncBodyStmts.Add(Comment("Deallocating the coroutine frame"sv));
714 deallocFuncBodyStmts.Add(
715 Comment("Note: The actual argument to delete is __builtin_coro_frame with the promise as parameter"sv));
716
717 deallocFuncBodyStmts.Add(stmt->getDeallocate());
718
719 SetFunctionBody(deallocFuncDecl, deallocFuncBodyStmts);
720 InsertArg(deallocFuncDecl);
721}
722//-----------------------------------------------------------------------------
723
724void CoroutinesCodeGenerator::InsertArg(const CoroutineBodyStmt* stmt)
725{
726 // insert a made up switch for continuing a resume
727 SwitchStmt* sstmt = Switch(mASTData.mSuspendIndexAccess);
728
729 // insert 0 with break for consistency
730 auto* initialSuspendCase = Case(0, Break());
731 StmtsContainer switchBodyStmts{initialSuspendCase};
732
733 for(const auto& i : NumberIterator{mSuspendsCounter}) {
734 switchBodyStmts.Add(Case(i + 1, Goto(BuildResumeLabelName(i + 1))));
735 }
736
737 auto* switchBody = mkCompoundStmt(switchBodyStmts);
738 sstmt->setBody(switchBody);
739
740 StmtsContainer funcBodyStmts{
741 Comment("Create a switch to get to the correct resume point"sv), sstmt, stmt->getInitSuspendStmt()};
742
743 // insert the init suspend expr
744 mState = eState::InitialSuspend;
745
746 if(mASTData.mThisExprs.size()) {
747 AddField(kwInternalThis, mASTData.mThisExprs.at(0)->getType());
748 }
749
750 mInsertVarDecl = false;
751 mSupressRecordDecls = true;
752
753 for(const auto* c : stmt->getBody()->children()) {
754 funcBodyStmts.Add(c);
755 }
756
757 if(const auto* coReturnVoid = dyn_cast_or_null<CoreturnStmt>(stmt->getFallthroughHandler())) {
758 funcBodyStmts.Add(coReturnVoid);
759 }
760
761 auto* gotoFinalSuspend = Goto(FINAL_SUSPEND_NAME);
762 funcBodyStmts.Add(gotoFinalSuspend);
763
764 auto* body = [&]() -> Stmt* {
765 auto* tryBody = mkCompoundStmt(funcBodyStmts);
766
767 // First open the try-catch block, as we get an error when jumping across such blocks with goto
768 if(const auto* exceptionHandler = stmt->getExceptionHandler()) {
769 // If we encounter an exceptionbefore inital_suspend's await_suspend was called we re-throw the
770 // exception.
771 auto* ifStmt = If(Not(mASTData.mInitialAwaitResumeCalledAccess), Throw());
772
773 StmtsContainer catchBodyStmts{ifStmt, exceptionHandler};
774
775 return Try(tryBody, Catch(catchBodyStmts));
776 }
777
778 return tryBody;
779 }();
780
781 InsertArg(body);
782
784
785 auto* finalSuspendLabel = Label(FINAL_SUSPEND_NAME);
786 InsertArg(finalSuspendLabel);
787 mState = eState::FinalSuspend;
788 InsertArg(stmt->getFinalSuspendStmt());
789
790 // disable prefixing names and types
791 mInsertVarDecl = true;
792}
793//-----------------------------------------------------------------------------
794
795void CoroutinesCodeGenerator::InsertArg(const CXXRecordDecl* stmt)
796{
797 if(not mSupressRecordDecls) {
799 }
800}
801//-----------------------------------------------------------------------------
803// We seem to need this, to peal of some static_casts in a CoroutineSuspendExpr.
804void CoroutinesCodeGenerator::InsertArg(const ImplicitCastExpr* stmt)
805{
806 if(mSupressCasts) {
807 InsertArg(stmt->getSubExpr());
808 } else {
810 }
811}
812//-----------------------------------------------------------------------------
813
814// A special hack to avoid having calls to __builtin_coro_xxx as some of them result in a crash
815// of the compiler and have assumption on the call order and function location.
816void CoroutinesCodeGenerator::InsertArg(const CallExpr* stmt)
817{
818 if(const auto* callee = dyn_cast_or_null<DeclRefExpr>(stmt->getCallee()->IgnoreCasts())) {
819 if(GetPlainName(*callee) == "__builtin_coro_frame"sv) {
821 return;
822
823 } else if(GetPlainName(*callee) == "__builtin_coro_free"sv) {
824 CodeGenerator::InsertArg(stmt->getArg(0));
825 return;
826
827 } else if(GetPlainName(*callee) == "__builtin_coro_size"sv) {
828 CodeGenerator::InsertArg(Sizeof(GetFrameType()));
829 return;
830 }
831 }
832
834}
835//-----------------------------------------------------------------------------
837static std::optional<std::string>
838FindValue(llvm::DenseMap<const Expr*, std::pair<const DeclRefExpr*, std::string>>& map, const Expr* key)
839{
840 if(const auto& s = map.find(key); s != map.end()) {
841 return s->second.second;
842 }
843
844 return {};
845}
846//-----------------------------------------------------------------------------
847
848void CoroutinesCodeGenerator::InsertArg(const OpaqueValueExpr* stmt)
849{
850 const auto* sourceExpr = stmt->getSourceExpr();
851
852 if(const auto& s = FindValue(mOpaqueValues, sourceExpr)) {
853 mOutputFormatHelper.Append(s.value());
854
855 } else {
856 // Needs to be internal because a user can create the same type and it gets put into the stack frame
857 std::string name{BuildSuspendVarName(stmt)};
858
859 // In case of a coroutine-template the same suspension point can occur multiple times. But to know when to add
860 // the _1 we must match the one from each instantiation. The DeclRefExpr is what distinguishes the same
861 // OpaqueValueExpr between multiple instantiations.
862 const auto* dref = FindDeclRef(sourceExpr);
863
864 // The initial_suspend and final_suspend expressions carry the same location info. If we hit such a case,
865 // make up another name.
866 // Below is a std::find_if. However, the same code looks unreadable with std::find_if
867 for(const auto lookupName{StrCat(CORO_FRAME_ACCESS, name)}; const auto& [k, value] : mOpaqueValues) {
868 if(auto [thisDeref, v] = value; (thisDeref == dref) and (v == lookupName)) {
869 name += "_1"sv;
870 break;
871 }
872 }
873
874 const auto accessName{StrCat(CORO_FRAME_ACCESS, name)};
875 mOpaqueValues.insert(std::make_pair(sourceExpr, std::make_pair(dref, accessName)));
876
877 OutputFormatHelper ofm{};
878 CoroutinesCodeGenerator codeGenerator{ofm, mPosBeforeFunc, mFSMName, mSuspendsCount, mASTData};
879
880 auto* promiseField = AddField(name, stmt->getType());
881 BinaryOperator* assignPromiseSuspend =
882 Assign(mASTData.mFrameAccessDeclRef, promiseField, stmt->getSourceExpr());
883
884 codeGenerator.InsertArg(assignPromiseSuspend);
885 ofm.AppendSemiNewLine();
886
887 ofm.SetIndent(mOutputFormatHelper);
888
889 mOutputFormatHelper.InsertAt(mPosBeforeSuspendExpr, ofm);
890 mOutputFormatHelper.Append(accessName);
891 }
892}
893//-----------------------------------------------------------------------------
894
895std::string CoroutinesCodeGenerator::BuildResumeLabelName(int index) const
896{
897 return StrCat(RESUME_LABEL_PREFIX, "_"sv, mFSMName, "_"sv, index);
898}
899//-----------------------------------------------------------------------------
900
901void CoroutinesCodeGenerator::InsertArg(const CoroutineSuspendExpr* stmt)
902{
904 InsertInstantiationPoint(GetGlobalAST().getSourceManager(), stmt->getKeywordLoc(), [&] {
905 if(isa<CoawaitExpr>(stmt)) {
906 return kwCoAwaitSpace;
907 } else {
908 return kwCoYieldSpace;
909 }
910 }());
911
912 mPosBeforeSuspendExpr = mOutputFormatHelper.CurrentPos();
913
914 /// Represents an expression that might suspend coroutine execution;
915 /// either a co_await or co_yield expression.
916 ///
917 /// Evaluation of this expression first evaluates its 'ready' expression. If
918 /// that returns 'false':
919 /// -- execution of the coroutine is suspended
920 /// -- the 'suspend' expression is evaluated
921 /// -- if the 'suspend' expression returns 'false', the coroutine is
922 /// resumed
923 /// -- otherwise, control passes back to the resumer.
924 /// If the coroutine is not suspended, or when it is resumed, the 'resume'
925 /// expression is evaluated, and its result is the result of the overall
926 /// expression.
927
928 // mOutputFormatHelper.AppendNewLine("// __builtin_coro_save() // frame->suspend_index = n");
929
930 // For why, see the implementation of CoroutinesCodeGenerator::InsertArg(const ImplicitCastExpr* stmt)
931 mSupressCasts = true;
932
933 auto* il = Int32(++mSuspendsCount);
934 auto* bop = Assign(mASTData.mSuspendIndexAccess, mASTData.mSuspendIndexField, il);
935
936 // Find out whether the return type is void or bool. In case of bool, we need to insert an if-statement, to
937 // suspend only, if the return value was true.
938 // Technically only void, bool, or std::coroutine_handle<Z> is allowed. [expr.await] p3.7
939 const bool returnsVoid{stmt->getSuspendExpr()->getType()->isVoidType()};
940
941 // XXX: check if getResumeExpr is marked noexcept. Otherwise we need additional expcetion handling?
942 // CGCoroutine.cpp:229
943
944 StmtsContainer bodyStmts{};
945 Expr* initializeInitialAwaitResume = nullptr;
946
947 const bool canThrow{[&] {
948 if(const auto* e = dyn_cast_or_null<ExprWithCleanups>(stmt->getSuspendExpr())) {
949 if(const auto* ce = dyn_cast_or_null<CallExpr>(e->getSubExpr())) {
950 if(const FunctionDecl* fd = ce->getDirectCallee()) {
951 if(const FunctionProtoType* fpt = fd->getType()->getAs<FunctionProtoType>()) {
952 return not fpt->isNothrow(/*ResultIfDependent=*/false);
953 }
954 }
955 }
956 }
957
958 return true;
959 }()};
960
961 auto addInitialAwaitSuspendCalled = [&] {
962 if(eState::InitialSuspend == mState) {
963 mState = eState::Body;
964 // https://timsong-cpp.github.io/cppwp/n4861/dcl.fct.def.coroutine#5.3
965 initializeInitialAwaitResume =
966 Assign(mASTData.mFrameAccessDeclRef, mASTData.mInitialAwaitResumeCalledField, Bool(true));
967 bodyStmts.Add(initializeInitialAwaitResume);
968 }
969 };
970
971 auto insertTryCatchIfNecessary = [&](StmtsContainer& cont) {
972 if(canThrow) {
973 auto* tryBody = mkCompoundStmt(cont);
974
975 StmtsContainer catchBodyStmts{
976 Assign(mASTData.mSuspendIndexAccess, mASTData.mSuspendIndexField, Int32(mSuspendsCount - 1)), Throw()};
977
978 cont.clear();
979 cont.Add(Try(tryBody, Catch(catchBodyStmts)));
980 }
981 };
982
983 if(returnsVoid) {
984 bodyStmts.Add(bop);
985 bodyStmts.Add(stmt->getSuspendExpr());
986
987 insertTryCatchIfNecessary(bodyStmts);
988
989 addInitialAwaitSuspendCalled();
990 bodyStmts.Add(Return());
991
992 InsertArg(If(Not(stmt->getReadyExpr()), bodyStmts));
993
994 } else {
995 addInitialAwaitSuspendCalled();
996 bodyStmts.Add(Return());
997
998 auto* ifSuspend = If(stmt->getSuspendExpr(), bodyStmts);
999
1000 StmtsContainer innerBodyStmts{bop, ifSuspend};
1001 insertTryCatchIfNecessary(innerBodyStmts);
1002
1003 InsertArg(If(Not(stmt->getReadyExpr()), innerBodyStmts));
1004 }
1005
1006 if(not returnsVoid and initializeInitialAwaitResume) {
1007 // At this point we technically haven't called initial suspend
1008 InsertArgWithNull(initializeInitialAwaitResume);
1009 mOutputFormatHelper.AppendNewLine();
1010 }
1011
1012 auto* suspendLabel = Label(BuildResumeLabelName(mSuspendsCount));
1013 InsertArg(suspendLabel);
1014
1015 if(eState::FinalSuspend == mState) {
1016 auto* memExpr = AccessMember(mASTData.mFrameAccessDeclRef, mASTData.mDestroyFnField, true);
1017 auto* callCoroFSM = Call(memExpr, {mASTData.mFrameAccessDeclRef});
1018 InsertArg(callCoroFSM);
1019 return;
1020 }
1021
1022 const auto* resumeExpr = stmt->getResumeExpr();
1023
1024 if(not resumeExpr->getType()->isVoidType()) {
1025 const auto* sourceExpr = stmt->getOpaqueValue()->getSourceExpr();
1026
1027 if(const auto& s = FindValue(mOpaqueValues, sourceExpr)) {
1028 const auto fieldName{StrCat(std::string_view{s.value()}.substr(CORO_FRAME_ACCESS.size()), "_res"sv)};
1029 mOutputFormatHelper.Append(CORO_FRAME_ACCESS, fieldName, hlpAssing);
1030
1031 AddField(fieldName, resumeExpr->getType());
1032 }
1033 }
1034
1035 InsertArg(resumeExpr);
1036}
1037//-----------------------------------------------------------------------------
1038
1039void CoroutinesCodeGenerator::InsertArg(const CoreturnStmt* stmt)
1040{
1041 InsertInstantiationPoint(GetGlobalAST().getSourceManager(), stmt->getKeywordLoc(), kwCoReturnSpace);
1042
1043 if(stmt->getPromiseCall()) {
1044 InsertArg(stmt->getPromiseCall());
1045
1046 if(stmt->isImplicit()) {
1047 mOutputFormatHelper.AppendComment("implicit"sv);
1048 }
1049 }
1050}
1051//-----------------------------------------------------------------------------
1052
1053void CoroutinesCodeGenerator::InsertArgWithNull(const Stmt* stmt)
1054{
1055 InsertArg(stmt);
1056 InsertArg(mkNullStmt());
1057}
1058//-----------------------------------------------------------------------------
1059
1060} // namespace clang::insights
const ASTContext & GetGlobalAST()
Get access to the ASTContext.
Definition Insights.cpp:71
constexpr std::string_view hlpDestroyFn
constexpr std::string_view kwCoReturnSpace
constexpr std::string_view kwInternalThis
constexpr std::string_view hlpResumeFn
constexpr std::string_view hlpAssing
#define RETURN_IF(cond)
! A helper inspired by https://github.com/Microsoft/wil/wiki/Error-handling-helpers
virtual void InsertArg(const Decl *stmt)
void InsertInstantiationPoint(const SourceManager &sm, const SourceLocation &instLoc, std::string_view text={})
Inserts the instantiation point of a template.
void InsertTemplateArg(const TemplateArgument &arg)
OutputFormatHelper & mOutputFormatHelper
A special container which creates either a CodeGenerator or a CfrontCodeGenerator depending on the co...
Find a SuspendsExpr's in a coroutine body statement for early transformation.
CoroutineASTTransformer(CoroutineASTData &coroutineASTData, size_t &suspendsCounter, Stmt *stmt, llvm::DenseMap< VarDecl *, MemberExpr * > varNamePrefix, Stmt *prev=nullptr)
A special generator for coroutines. It is only activated, if -show-coroutines-transformation is given...
void InsertArg(const ImplicitCastExpr *stmt) override
void InsertCoroutine(const FunctionDecl &fd, const CoroutineBodyStmt *body)
void OpenScope()
Open a scope by inserting a '{' followed by an indented newline.
void AppendNewLine(const char c)
Same as Append but adds a newline after the last argument.
void Append(const char c)
Append a single character.
void CloseScope(const NoNewLineBefore newLineBefore=NoNewLineBefore::No)
Close a scope by inserting a '}'.
void AppendCommentNewLine(const std::string_view &arg)
void AppendSemiNewLine()
Append a semicolon and a newline.
void InsertAt(const size_t atPos, std::string_view data)
Insert a string before the position atPos.
BinaryOperator * Equal(Expr *var, Expr *assignExpr)
UnaryOperator * AddrOf(const Expr *stmt)
CXXReinterpretCastExpr * ReinterpretCast(QualType toType, const Expr *toExpr, bool makePointer)
CXXRecordDecl * Struct(std::string_view name)
FieldDecl * mkFieldDecl(DeclContext *dc, std::string_view name, QualType type)
DeclRefExpr * mkDeclRefExpr(const ValueDecl *vd)
CXXNewExpr * New(ArrayRef< Expr * > placementArgs, const Expr *expr, QualType t)
std::vector< std::pair< std::string_view, QualType > > params_vector
Definition ASTHelpers.h:27
CallExpr * Call(const FunctionDecl *fd, ArrayRef< Expr * > params)
UnaryOperator * Ref(const Expr *e)
VarDecl * Variable(std::string_view name, QualType type, DeclContext *dc)
MemberExpr * AccessMember(const Expr *expr, const ValueDecl *vd, bool isArrow)
DeclRefExpr * mkVarDeclRefExpr(std::string_view name, QualType type)
ReturnStmt * Return(Expr *stmt)
CaseStmt * Case(int value, Stmt *stmt)
Stmt * Comment(std::string_view comment)
IfStmt * If(const Expr *condition, ArrayRef< Stmt * > bodyStmts)
GotoStmt * Goto(std::string_view labelName)
CXXCatchStmt * Catch(ArrayRef< Stmt * > body)
CXXThrowExpr * Throw(const Expr *expr)
void ReplaceNode(Stmt *parent, Stmt *oldNode, Stmt *newNode)
UnaryExprOrTypeTraitExpr * Sizeof(QualType toType)
LabelStmt * Label(std::string_view name)
UnaryOperator * Not(const Expr *stmt)
CXXBoolLiteralExpr * Bool(bool b)
FunctionDecl * Function(std::string_view name, QualType returnType, const params_vector &parameters)
CompoundStmt * mkCompoundStmt(ArrayRef< Stmt * > bodyStmts, SourceLocation beginLoc, SourceLocation endLoc)
SwitchStmt * Switch(Expr *stmt)
IntegerLiteral * Int32(uint64_t value)
ParmVarDecl * Parameter(const FunctionDecl *fd, std::string_view name, QualType type)
UnaryOperator * Dref(const Expr *stmt)
QualType Ptr(QualType srcType)
CXXStaticCastExpr * StaticCast(QualType toType, const Expr *toExpr, bool makePointer)
CXXMemberCallExpr * CallMemberFun(Expr *memExpr, QualType retType)
BinaryOperator * Assign(const VarDecl *var, Expr *assignExpr)
CXXTryStmt * Try(const Stmt *tryBody, CXXCatchStmt *catchAllBody)
const std::string SUSPEND_INDEX_NAME
const std::string RESUME_LABEL_PREFIX
bool Contains(const std::string_view source, const std::string_view search)
const DeclRefExpr * FindDeclRef(const Stmt *stmt)
Go deep in a Stmt if necessary and look to all childs for a DeclRefExpr.
std::string GetPlainName(const DeclRefExpr &DRE)
static auto * CreateCoroFunctionDecl(std::string funcName, QualType type)
static void SetFunctionBody(FunctionDecl *fd, StmtsContainer &bodyStmts)
constexpr std::string_view CORO_FRAME_NAME
std::string MakeLineColumnName(const SourceManager &sm, const SourceLocation &loc, const std::string_view &prefix)
std::string GetName(const NamedDecl &nd, const QualifiedName qualifiedName)
void ReplaceAll(std::string &str, std::string_view from, std::string_view to)
std::string BuildInternalVarName(const std::string_view &varName)
const std::string CORO_FRAME_ACCESS_THIS
std::string BuildTemplateParamObjectName(std::string name)
const std::string FINAL_SUSPEND_NAME
const std::string INITIAL_AWAIT_SUSPEND_CALLED_NAME
static std::string BuildSuspendVarName(const OpaqueValueExpr *stmt)
static std::optional< std::string > FindValue(llvm::DenseMap< const Expr *, std::pair< const DeclRefExpr *, std::string > > &map, const Expr *key)
void EnableGlobalInsert(GlobalInserts idx)
Definition Insights.cpp:96
const std::string CORO_FRAME_ACCESS
static FieldDecl * AddField(CoroutineASTData &astData, std::string_view name, QualType type)
std::string StrCat(const auto &... args)
std::vector< const CXXThisExpr * > mThisExprs
! A helper type to have a container for ArrayRef
Definition ASTHelpers.h:64