Skip to content

Commit be91ecd

Browse files
[Clang] Introduce [[clang::coro_await_elidable]]
1 parent 92966fb commit be91ecd

File tree

10 files changed

+187
-35
lines changed

10 files changed

+187
-35
lines changed

llvm/docs/Coroutines.rst

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,6 +2022,12 @@ The pass CoroSplit builds coroutine frame and outlines resume and destroy parts
20222022
into separate functions. This pass also lowers `coro.await.suspend.void`_,
20232023
`coro.await.suspend.bool`_ and `coro.await.suspend.handle`_ intrinsics.
20242024

2025+
CoroAnnotationElide
2026+
-------------------
2027+
This pass finds all usages of coroutines that are "must elide" and replaces
2028+
`coro.begin` intrinsic with an address of a coroutine frame placed on its caller
2029+
and replaces `coro.alloc` and `coro.free` intrinsics with `false` and `null`
2030+
respectively to remove the deallocation code.
20252031

20262032
CoroElide
20272033
---------
@@ -2049,6 +2055,22 @@ the coroutine must reach the final suspend point when it get destroyed.
20492055

20502056
This attribute only works for switched-resume coroutines now.
20512057

2058+
coro_must_elide
2059+
---------------
2060+
2061+
When a Call or Invoke instruction is marked with `coro_must_elide`,
2062+
CoroAnnotationElidePass performs heap elision when possible. Note that for
2063+
recursive or mutually recursive functions this elision is usually not possible.
2064+
2065+
2066+
coro_gen_noalloc_ramp
2067+
---------------------
2068+
2069+
This attribute hints CoroSplitPass to generate a `f.noalloc` ramp function for
2070+
a given coroutine `f`. For any call or invoke instruction that calls `f` and
2071+
attributed as `coro_must_elide`, CoroAnnotationElidePass is able to redirect
2072+
the call to use the `.noalloc` variant.
2073+
20522074
Metadata
20532075
========
20542076

llvm/lib/Transforms/Coroutines/CoroInternal.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ bool declaresIntrinsics(const Module &M,
2626
const std::initializer_list<StringRef>);
2727
void replaceCoroFree(CoroIdInst *CoroId, bool Elide);
2828

29+
void suppressCoroAllocs(CoroIdInst *CoroId);
30+
void suppressCoroAllocs(LLVMContext &Context,
31+
ArrayRef<CoroAllocInst *> CoroAllocs);
32+
2933
/// Attempts to rewrite the location operand of debug intrinsics in terms of
3034
/// the coroutine frame pointer, folding pointer offsets into the DIExpression
3135
/// of the intrinsic.

llvm/lib/Transforms/Coroutines/CoroSplit.cpp

Lines changed: 116 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "llvm/ADT/PriorityWorklist.h"
2626
#include "llvm/ADT/SmallPtrSet.h"
2727
#include "llvm/ADT/SmallVector.h"
28+
#include "llvm/ADT/StringExtras.h"
2829
#include "llvm/ADT/StringRef.h"
2930
#include "llvm/ADT/Twine.h"
3031
#include "llvm/Analysis/CFG.h"
@@ -1177,6 +1178,14 @@ static void updateAsyncFuncPointerContextSize(coro::Shape &Shape) {
11771178
Shape.AsyncLowering.AsyncFuncPointer->setInitializer(NewFuncPtrStruct);
11781179
}
11791180

1181+
static TypeSize getFrameSizeForShape(coro::Shape &Shape) {
1182+
// In the same function all coro.sizes should have the same result type.
1183+
auto *SizeIntrin = Shape.CoroSizes.back();
1184+
Module *M = SizeIntrin->getModule();
1185+
const DataLayout &DL = M->getDataLayout();
1186+
return DL.getTypeAllocSize(Shape.FrameTy);
1187+
}
1188+
11801189
static void replaceFrameSizeAndAlignment(coro::Shape &Shape) {
11811190
if (Shape.ABI == coro::ABI::Async)
11821191
updateAsyncFuncPointerContextSize(Shape);
@@ -1192,10 +1201,8 @@ static void replaceFrameSizeAndAlignment(coro::Shape &Shape) {
11921201

11931202
// In the same function all coro.sizes should have the same result type.
11941203
auto *SizeIntrin = Shape.CoroSizes.back();
1195-
Module *M = SizeIntrin->getModule();
1196-
const DataLayout &DL = M->getDataLayout();
1197-
auto Size = DL.getTypeAllocSize(Shape.FrameTy);
1198-
auto *SizeConstant = ConstantInt::get(SizeIntrin->getType(), Size);
1204+
auto *SizeConstant =
1205+
ConstantInt::get(SizeIntrin->getType(), getFrameSizeForShape(Shape));
11991206

12001207
for (CoroSizeInst *CS : Shape.CoroSizes) {
12011208
CS->replaceAllUsesWith(SizeConstant);
@@ -1453,6 +1460,74 @@ struct SwitchCoroutineSplitter {
14531460
setCoroInfo(F, Shape, Clones);
14541461
}
14551462

1463+
// Create a variant of ramp function that does not perform heap allocation
1464+
// for a switch ABI coroutine.
1465+
//
1466+
// The newly split `.noalloc` ramp function has the following differences:
1467+
// - Has one additional frame pointer parameter in lieu of dynamic
1468+
// allocation.
1469+
// - Suppressed allocations by replacing coro.alloc and coro.free.
1470+
static Function *createNoAllocVariant(Function &F, coro::Shape &Shape,
1471+
SmallVectorImpl<Function *> &Clones) {
1472+
auto *OrigFnTy = F.getFunctionType();
1473+
auto OldParams = OrigFnTy->params();
1474+
1475+
SmallVector<Type *> NewParams;
1476+
NewParams.reserve(OldParams.size() + 1);
1477+
NewParams.append(OldParams.begin(), OldParams.end());
1478+
NewParams.push_back(PointerType::getUnqual(Shape.FrameTy));
1479+
1480+
auto *NewFnTy = FunctionType::get(OrigFnTy->getReturnType(), NewParams,
1481+
OrigFnTy->isVarArg());
1482+
Function *NoAllocF =
1483+
Function::Create(NewFnTy, F.getLinkage(), F.getName() + ".noalloc");
1484+
1485+
ValueToValueMapTy VMap;
1486+
unsigned int Idx = 0;
1487+
for (const auto &I : F.args()) {
1488+
VMap[&I] = NoAllocF->getArg(Idx++);
1489+
}
1490+
SmallVector<ReturnInst *, 4> Returns;
1491+
CloneFunctionInto(NoAllocF, &F, VMap,
1492+
CloneFunctionChangeType::LocalChangesOnly, Returns);
1493+
1494+
if (Shape.CoroBegin) {
1495+
auto *NewCoroBegin =
1496+
cast_if_present<CoroBeginInst>(VMap[Shape.CoroBegin]);
1497+
auto *NewCoroId = cast<CoroIdInst>(NewCoroBegin->getId());
1498+
coro::replaceCoroFree(NewCoroId, /*Elide=*/true);
1499+
coro::suppressCoroAllocs(NewCoroId);
1500+
NewCoroBegin->replaceAllUsesWith(NoAllocF->getArg(Idx));
1501+
NewCoroBegin->eraseFromParent();
1502+
}
1503+
1504+
Module *M = F.getParent();
1505+
M->getFunctionList().insert(M->end(), NoAllocF);
1506+
1507+
removeUnreachableBlocks(*NoAllocF);
1508+
auto NewAttrs = NoAllocF->getAttributes();
1509+
// We just appended the frame pointer as the last argument of the new
1510+
// function.
1511+
auto FrameIdx = NoAllocF->arg_size() - 1;
1512+
// When we elide allocation, we read these attributes to determine the
1513+
// frame size and alignment.
1514+
addFramePointerAttrs(NewAttrs, NoAllocF->getContext(), FrameIdx,
1515+
Shape.FrameSize, Shape.FrameAlign,
1516+
/*NoAlias=*/false);
1517+
1518+
NoAllocF->setAttributes(NewAttrs);
1519+
1520+
Clones.push_back(NoAllocF);
1521+
// Reset the original function's coro info, make the new noalloc variant
1522+
// connected to the original ramp function.
1523+
setCoroInfo(F, Shape, Clones);
1524+
// After copying, set the linkage to internal linkage. Original function
1525+
// may have different linkage, but optimization dependent on this function
1526+
// generally relies on LTO.
1527+
NoAllocF->setLinkage(llvm::GlobalValue::InternalLinkage);
1528+
return NoAllocF;
1529+
}
1530+
14561531
private:
14571532
// Create a resume clone by cloning the body of the original function, setting
14581533
// new entry block and replacing coro.suspend an appropriate value to force
@@ -1911,6 +1986,21 @@ class PrettyStackTraceFunction : public PrettyStackTraceEntry {
19111986
};
19121987
} // namespace
19131988

1989+
/// Remove calls to llvm.coro.end in the original function.
1990+
static void removeCoroEndsFromRampFunction(const coro::Shape &Shape) {
1991+
if (Shape.ABI != coro::ABI::Switch) {
1992+
for (auto *End : Shape.CoroEnds) {
1993+
replaceCoroEnd(End, Shape, Shape.FramePtr, /*in resume*/ false, nullptr);
1994+
}
1995+
} else {
1996+
for (llvm::AnyCoroEndInst *End : Shape.CoroEnds) {
1997+
auto &Context = End->getContext();
1998+
End->replaceAllUsesWith(ConstantInt::getFalse(Context));
1999+
End->eraseFromParent();
2000+
}
2001+
}
2002+
}
2003+
19142004
static coro::Shape
19152005
splitCoroutine(Function &F, SmallVectorImpl<Function *> &Clones,
19162006
TargetTransformInfo &TTI, bool OptimizeFrame,
@@ -1930,10 +2020,19 @@ splitCoroutine(Function &F, SmallVectorImpl<Function *> &Clones,
19302020
simplifySuspendPoints(Shape);
19312021
buildCoroutineFrame(F, Shape, TTI, MaterializableCallback);
19322022
replaceFrameSizeAndAlignment(Shape);
2023+
bool isNoSuspendCoroutine = Shape.CoroSuspends.empty();
19332024

2025+
bool shouldCreateNoAllocVariant =
2026+
!isNoSuspendCoroutine && Shape.ABI == coro::ABI::Switch &&
2027+
F.hasFnAttribute(llvm::Attribute::CoroGenNoallocRamp);
2028+
2029+
// We don't need this attribute any more. Delete it.
2030+
if (F.hasFnAttribute(llvm::Attribute::CoroGenNoallocRamp)) {
2031+
F.removeFnAttr(llvm::Attribute::CoroGenNoallocRamp);
2032+
}
19342033
// If there are no suspend points, no split required, just remove
19352034
// the allocation and deallocation blocks, they are not needed.
1936-
if (Shape.CoroSuspends.empty()) {
2035+
if (isNoSuspendCoroutine) {
19372036
handleNoSuspendCoroutine(Shape);
19382037
} else {
19392038
switch (Shape.ABI) {
@@ -1963,22 +2062,13 @@ splitCoroutine(Function &F, SmallVectorImpl<Function *> &Clones,
19632062
coro::salvageDebugInfo(ArgToAllocaMap, *DDI, false /*UseEntryValue*/);
19642063
for (DbgVariableRecord *DVR : DbgVariableRecords)
19652064
coro::salvageDebugInfo(ArgToAllocaMap, *DVR, false /*UseEntryValue*/);
1966-
return Shape;
1967-
}
19682065

1969-
/// Remove calls to llvm.coro.end in the original function.
1970-
static void removeCoroEndsFromRampFunction(const coro::Shape &Shape) {
1971-
if (Shape.ABI != coro::ABI::Switch) {
1972-
for (auto *End : Shape.CoroEnds) {
1973-
replaceCoroEnd(End, Shape, Shape.FramePtr, /*in resume*/ false, nullptr);
1974-
}
1975-
} else {
1976-
for (llvm::AnyCoroEndInst *End : Shape.CoroEnds) {
1977-
auto &Context = End->getContext();
1978-
End->replaceAllUsesWith(ConstantInt::getFalse(Context));
1979-
End->eraseFromParent();
1980-
}
1981-
}
2066+
removeCoroEndsFromRampFunction(Shape);
2067+
2068+
if (shouldCreateNoAllocVariant)
2069+
SwitchCoroutineSplitter::createNoAllocVariant(F, Shape, Clones);
2070+
2071+
return Shape;
19822072
}
19832073

19842074
static void updateCallGraphAfterCoroutineSplit(
@@ -2104,18 +2194,18 @@ PreservedAnalyses CoroSplitPass::run(LazyCallGraph::SCC &C,
21042194
// Split all the coroutines.
21052195
for (LazyCallGraph::Node *N : Coroutines) {
21062196
Function &F = N->getFunction();
2197+
21072198
LLVM_DEBUG(dbgs() << "CoroSplit: Processing coroutine '" << F.getName()
21082199
<< "\n");
21092200
F.setSplittedCoroutine();
21102201

21112202
SmallVector<Function *, 4> Clones;
2112-
auto &ORE = FAM.getResult<OptimizationRemarkEmitterAnalysis>(F);
2113-
const coro::Shape Shape =
2203+
coro::Shape Shape =
21142204
splitCoroutine(F, Clones, FAM.getResult<TargetIRAnalysis>(F),
21152205
OptimizeFrame, MaterializableCallback);
2116-
removeCoroEndsFromRampFunction(Shape);
21172206
updateCallGraphAfterCoroutineSplit(*N, Shape, Clones, C, CG, AM, UR, FAM);
21182207

2208+
auto &ORE = FAM.getResult<OptimizationRemarkEmitterAnalysis>(F);
21192209
ORE.emit([&]() {
21202210
return OptimizationRemark(DEBUG_TYPE, "CoroSplit", &F)
21212211
<< "Split '" << ore::NV("function", F.getName())
@@ -2131,9 +2221,9 @@ PreservedAnalyses CoroSplitPass::run(LazyCallGraph::SCC &C,
21312221
}
21322222
}
21332223

2134-
for (auto *PrepareFn : PrepareFns) {
2135-
replaceAllPrepares(PrepareFn, CG, C);
2136-
}
2224+
for (auto *PrepareFn : PrepareFns) {
2225+
replaceAllPrepares(PrepareFn, CG, C);
2226+
}
21372227

21382228
return PreservedAnalyses::none();
21392229
}

llvm/lib/Transforms/Coroutines/Coroutines.cpp

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,33 @@ void coro::replaceCoroFree(CoroIdInst *CoroId, bool Elide) {
145145
}
146146
}
147147

148+
void coro::suppressCoroAllocs(CoroIdInst *CoroId) {
149+
SmallVector<CoroAllocInst *, 4> CoroAllocs;
150+
for (User *U : CoroId->users())
151+
if (auto *CA = dyn_cast<CoroAllocInst>(U))
152+
CoroAllocs.push_back(CA);
153+
154+
if (CoroAllocs.empty())
155+
return;
156+
157+
coro::suppressCoroAllocs(CoroId->getContext(), CoroAllocs);
158+
}
159+
160+
// Replacing llvm.coro.alloc with false will suppress dynamic
161+
// allocation as it is expected for the frontend to generate the code that
162+
// looks like:
163+
// id = coro.id(...)
164+
// mem = coro.alloc(id) ? malloc(coro.size()) : 0;
165+
// coro.begin(id, mem)
166+
void coro::suppressCoroAllocs(LLVMContext &Context,
167+
ArrayRef<CoroAllocInst *> CoroAllocs) {
168+
auto *False = ConstantInt::getFalse(Context);
169+
for (auto *CA : CoroAllocs) {
170+
CA->replaceAllUsesWith(False);
171+
CA->eraseFromParent();
172+
}
173+
}
174+
148175
static void clear(coro::Shape &Shape) {
149176
Shape.CoroBegin = nullptr;
150177
Shape.CoroEnds.clear();

llvm/test/Transforms/Coroutines/ArgAddr.ll

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
; coro.begin.
33
; RUN: opt < %s -passes='cgscc(coro-split),simplifycfg,early-cse' -S | FileCheck %s
44

5-
define nonnull ptr @f(i32 %n) presplitcoroutine {
5+
; CHECK: @[[RESUMER:.+]] = private constant [4 x ptr] [ptr @f.resume, ptr @f.destroy, ptr @f.cleanup, ptr @f.noalloc]
6+
7+
define nonnull ptr @f(i32 %n) presplitcoroutine coro_gen_noalloc_ramp {
68
; CHECK-LABEL: @f(
79
; CHECK-NEXT: entry:
8-
; CHECK-NEXT: [[ID:%.*]] = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr @f.resumers)
10+
; CHECK-NEXT: [[ID:%.*]] = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr @[[RESUMER]])
911
; CHECK-NEXT: [[N_ADDR:%.*]] = alloca i32, align 4
1012
; CHECK-NEXT: store i32 [[N:%.*]], ptr [[N_ADDR]], align 4
1113
; CHECK-NEXT: [[CALL:%.*]] = tail call ptr @malloc(i32 24)

llvm/test/Transforms/Coroutines/coro-alloca-07.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ declare void @free(ptr)
6262

6363
; CHECK-LABEL: @f(
6464
; CHECK-NEXT: entry:
65-
; CHECK-NEXT: [[ID:%.*]] = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr @f.resumers)
65+
; CHECK-NEXT: [[ID:%.*]] = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr @{{.*}})
6666
; CHECK-NEXT: [[ALLOC:%.*]] = call ptr @malloc(i32 48)
6767
; CHECK-NEXT: [[HDL:%.*]] = call noalias nonnull ptr @llvm.coro.begin(token [[ID]], ptr [[ALLOC]])
6868
; CHECK-NEXT: store ptr @f.resume, ptr [[HDL]], align 8

llvm/test/Transforms/Coroutines/coro-alloca-loop-carried-address.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
define void @foo() presplitcoroutine {
88
; CHECK-LABEL: @foo(
99
; CHECK-NEXT: entry:
10-
; CHECK-NEXT: [[ID:%.*]] = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr @foo.resumers)
10+
; CHECK-NEXT: [[ID:%.*]] = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr @{{.*}})
1111
; CHECK-NEXT: [[ALLOC:%.*]] = call ptr @malloc(i64 40)
1212
; CHECK-NEXT: [[VFRAME:%.*]] = call noalias nonnull ptr @llvm.coro.begin(token [[ID]], ptr [[ALLOC]])
1313
; CHECK-NEXT: store ptr @foo.resume, ptr [[VFRAME]], align 8

llvm/test/Transforms/Coroutines/coro-lifetime-end.ll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ declare void @consume.i8.array(ptr)
1313
define void @HasNoLifetimeEnd() presplitcoroutine {
1414
; CHECK-LABEL: define void @HasNoLifetimeEnd() {
1515
; CHECK-NEXT: entry:
16-
; CHECK-NEXT: [[ID:%.*]] = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr @HasNoLifetimeEnd.resumers)
16+
; CHECK-NEXT: [[ID:%.*]] = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr @{{.*}})
1717
; CHECK-NEXT: [[ALLOC:%.*]] = call ptr @malloc(i64 16)
1818
; CHECK-NEXT: [[VFRAME:%.*]] = call noalias nonnull ptr @llvm.coro.begin(token [[ID]], ptr [[ALLOC]])
1919
; CHECK-NEXT: store ptr @HasNoLifetimeEnd.resume, ptr [[VFRAME]], align 8
@@ -50,7 +50,7 @@ exit:
5050
define void @LifetimeEndAfterCoroEnd() presplitcoroutine {
5151
; CHECK-LABEL: define void @LifetimeEndAfterCoroEnd() {
5252
; CHECK-NEXT: entry:
53-
; CHECK-NEXT: [[ID:%.*]] = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr @LifetimeEndAfterCoroEnd.resumers)
53+
; CHECK-NEXT: [[ID:%.*]] = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr @{{.*}})
5454
; CHECK-NEXT: [[ALLOC:%.*]] = call ptr @malloc(i64 16)
5555
; CHECK-NEXT: [[VFRAME:%.*]] = call noalias nonnull ptr @llvm.coro.begin(token [[ID]], ptr [[ALLOC]])
5656
; CHECK-NEXT: store ptr @LifetimeEndAfterCoroEnd.resume, ptr [[VFRAME]], align 8
@@ -88,7 +88,7 @@ exit:
8888
define void @BranchWithoutLifetimeEnd() presplitcoroutine {
8989
; CHECK-LABEL: define void @BranchWithoutLifetimeEnd() {
9090
; CHECK-NEXT: entry:
91-
; CHECK-NEXT: [[ID:%.*]] = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr @BranchWithoutLifetimeEnd.resumers)
91+
; CHECK-NEXT: [[ID:%.*]] = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr @{{.*}})
9292
; CHECK-NEXT: [[ALLOC:%.*]] = call ptr @malloc(i64 16)
9393
; CHECK-NEXT: [[VFRAME:%.*]] = call noalias nonnull ptr @llvm.coro.begin(token [[ID]], ptr [[ALLOC]])
9494
; CHECK-NEXT: store ptr @BranchWithoutLifetimeEnd.resume, ptr [[VFRAME]], align 8

llvm/test/Transforms/Coroutines/coro-spill-after-phi.ll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
define ptr @f(i1 %n) presplitcoroutine {
99
; CHECK-LABEL: @f(
1010
; CHECK-NEXT: entry:
11-
; CHECK-NEXT: [[ID:%.*]] = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr @f.resumers)
11+
; CHECK-NEXT: [[ID:%.*]] = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr @{{.*}})
1212
; CHECK-NEXT: [[ALLOC:%.*]] = call ptr @malloc(i32 32)
1313
; CHECK-NEXT: [[HDL:%.*]] = call noalias nonnull ptr @llvm.coro.begin(token [[ID]], ptr [[ALLOC]])
1414
; CHECK-NEXT: store ptr @f.resume, ptr [[HDL]], align 8

llvm/test/Transforms/Coroutines/coro-split-00.ll

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
; Tests that coro-split pass splits the coroutine into f, f.resume and f.destroy
22
; RUN: opt < %s -passes='cgscc(coro-split),simplifycfg,early-cse' -S | FileCheck %s
33

4-
define ptr @f() presplitcoroutine !func_sanitize !0 {
4+
define ptr @f() presplitcoroutine coro_gen_noalloc_ramp !func_sanitize !0 {
55
entry:
66
%id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)
77
%need.alloc = call i1 @llvm.coro.alloc(token %id)
@@ -63,6 +63,13 @@ suspend:
6363
; CHECK-NOT: call void @free(
6464
; CHECK: ret void
6565

66+
; CHECK-LABEL: @f.noalloc(ptr noundef nonnull align 8 dereferenceable(24) %{{.*}})
67+
; CHECK-NOT: call ptr @malloc
68+
; CHECK: call void @print(i32 0)
69+
; CHECK-NOT: call void @print(i32 1)
70+
; CHECK-NOT: call void @free(
71+
; CHECK: ret ptr %{{.*}}
72+
6673
declare ptr @llvm.coro.free(token, ptr)
6774
declare i32 @llvm.coro.size.i32()
6875
declare i8 @llvm.coro.suspend(token, i1)

0 commit comments

Comments
 (0)