Index: llvm/lib/Transforms/Coroutines/CMakeLists.txt =================================================================== --- llvm/lib/Transforms/Coroutines/CMakeLists.txt +++ llvm/lib/Transforms/Coroutines/CMakeLists.txt @@ -8,4 +8,5 @@ DEPENDS intrinsics_gen + LLVMAnalysis ) Index: llvm/lib/Transforms/Coroutines/CoroFrame.cpp =================================================================== --- llvm/lib/Transforms/Coroutines/CoroFrame.cpp +++ llvm/lib/Transforms/Coroutines/CoroFrame.cpp @@ -20,6 +20,7 @@ #include "llvm/ADT/BitVector.h" #include "llvm/ADT/SmallString.h" #include "llvm/Analysis/PtrUseVisitor.h" +#include "llvm/Analysis/StackLifetime.h" #include "llvm/Config/llvm-config.h" #include "llvm/IR/CFG.h" #include "llvm/IR/DIBuilder.h" @@ -28,8 +29,8 @@ #include "llvm/IR/InstIterator.h" #include "llvm/Support/Debug.h" #include "llvm/Support/MathExtras.h" -#include "llvm/Support/circular_raw_ostream.h" #include "llvm/Support/OptimizedStructLayout.h" +#include "llvm/Support/circular_raw_ostream.h" #include "llvm/Transforms/Utils/BasicBlockUtils.h" #include "llvm/Transforms/Utils/Local.h" #include "llvm/Transforms/Utils/PromoteMemToReg.h" @@ -361,6 +362,12 @@ SmallVector Fields; DenseMap FieldIndexByKey; + DenseMap AllocaIndex; + DenseMap SpillOfAllocas; + using AllocaSetType = SmallVector; + DenseMap NonOverlapedAllocas; + DenseSet AllocasWithField; + public: FrameTypeBuilder(LLVMContext &Context, DataLayout const &DL) : DL(DL), Context(Context) {} @@ -437,6 +444,146 @@ assert(IsFinished && "not yet finished!"); return Fields[Id.Value].FieldIndex; } + + /// For each NonOverlapped Alloca Set, find the largest type allocated + /// by these allocas. Use the largest type as the field type for all of + /// the allocas in the NonOverlapped Alloca Set. + void addFieldForAllocas() { + for (auto &IndexAndSet : NonOverlapedAllocas) { + AllocaSetType &AllocaSet = IndexAndSet.second; + assert(!AllocaSet.empty() && "AllocaSet should not be empty.\n"); + auto *LargestAI = *AllocaSet.begin(); + addFieldForAlloca(LargestAI, SpillOfAllocas[LargestAI]); + } + } + /// We want to put the allocas whose lifetime-ranges are not overlapped + /// into one slot of coroutine frame. + /// Consider the example at:https://bugs.llvm.org/show_bug.cgi?id=45566 + /// + /// cppcoro::task alternative_paths(bool cond) { + /// if (cond) { + /// big_structure a; + /// process(a); + /// co_await something(); + /// } else { + /// big_structure b; + /// process2(b); + /// co_await something(); + /// } + /// } + /// + /// We want to put variable a and variable b in the same slot to + /// reduce the size of coroutine frame. + /// + /// This function use StackLifetime algorithm to partition the AllocaInsts in + /// Spills to non-overlapped sets in order to put Alloca in the same + /// non-overlapped set into the same slot in the Coroutine Frame. Then + /// `addFieldForAllocas` function would add field for the allocas in the same + /// non-overlapped set. + /// + /// Side Effects: Because We use `DenseMap` to record the allocas, the order + /// of allocas in the frame may be different with the order in the source + /// code. + void BuildNonOverlapedAllocas(const Function &F, SpillInfo &Spills) { + SmallVector Allocas; + const DataLayout &DL = F.getParent()->getDataLayout(); + for (auto &Spill : Spills) + if (AllocaInst *AI = dyn_cast(Spill.def())) + if (find(Allocas, AI) == Allocas.end()) { + SpillOfAllocas[AI] = &Spill; + Allocas.emplace_back(AI); + } + // Because there are pathes from the lifetime.start to coro.end + // for each alloca, the liferanges for every alloca is overlaped + // in the blocks who contain coro.end and the successor blocks. + // So we choose to skip there blocks when we calculates the liferange + // for each alloca. It should be reasonable since there shouldn't be uses + // in these blocks and the coroutine frame shouldn't be used outside the + // coroutine body. + // + // Note that the user of coro.suspend may not be SwitchInst. However, this + // case seems too complex to handle. And it is harmless to skip these + // patterns since it just prevend putting the allocas to live in the same + // slot. + DenseMap DefaultSuspendDest; + for (auto &I : instructions(&F)) { + auto *Suspend = dyn_cast(&I); + if (!Suspend || Suspend->getIntrinsicID() != Intrinsic::coro_suspend) + continue; + for (auto U : Suspend->users()) { + if (auto *ConstSWI = dyn_cast(U)) { + auto *SWI = const_cast(ConstSWI); + DefaultSuspendDest[SWI] = SWI->getDefaultDest(); + SWI->setDefaultDest(SWI->getSuccessor(1)); + } + } + } + StackLifetime StackLifetimeAnalyzer(F, Allocas, + StackLifetime::LivenessType::May); + StackLifetimeAnalyzer.run(); + auto IsAllocaInferenre = [&](const AllocaInst *AI1, const AllocaInst *AI2) { + return StackLifetimeAnalyzer.getLiveRange(AI1).overlaps( + StackLifetimeAnalyzer.getLiveRange(AI2)); + }; + auto GetAllocaSize = [&](const AllocaInst *AI) { + Optional RetSize = AI->getAllocationSizeInBits(DL); + assert(RetSize && "We can't handle scalable type now.\n"); + return RetSize.getValue(); + }; + // Put larger allocas in the front. So the larger allocas have higher + // priority to merge, which can save more space potentially. Also each + // AllocaSet would be ordered. So we can get the largest Alloca in one + // AllocaSet easily. + sort(Allocas, [&](auto Iter1, auto Iter2) { + return GetAllocaSize(Iter1) > GetAllocaSize(Iter2); + }); + for (auto Alloca : Allocas) { + bool Merged = false; + // Try to find if the Alloca is not inferenced with any existing + // NonOverlappedAllocaSet. If it is true, insert the alloca to that + // NonOverlappedAllocaSet. + for (auto &IndexAndSet : NonOverlapedAllocas) { + auto Index = IndexAndSet.first; + auto &AllocaSet = IndexAndSet.second; + assert(!AllocaSet.empty() && "Processing Alloca Set is not empty.\n"); + bool CouldMerge = none_of(AllocaSet, [&](auto Iter) { + return IsAllocaInferenre(Alloca, Iter); + }); + if (!CouldMerge) + continue; + AllocaIndex[Alloca] = Index; + AllocaSet.push_back(Alloca); + Merged = true; + break; + } + if (!Merged) { + auto Index = NonOverlapedAllocas.size(); + AllocaIndex[Alloca] = Index; + NonOverlapedAllocas[Index] = AllocaSetType(); + NonOverlapedAllocas[Index].push_back(Alloca); + } + } + // Recover the default target destination for each Switch statement + // reserved. + for (auto SwitchAndDefaultDest : DefaultSuspendDest) { + SwitchInst *SWI = SwitchAndDefaultDest.first; + BasicBlock *DestBB = SwitchAndDefaultDest.second; + SWI->setDefaultDest(DestBB); + } + // This Debug Info could tell us which allocas are merged into one slot. + LLVM_DEBUG(for (auto &IndexAndSet + : NonOverlapedAllocas) { + auto Index = IndexAndSet.first; + AllocaSetType &AllocaSet = IndexAndSet.second; + if (AllocaSet.size() > 1) { + dbgs() << "In Function:" << F.getName() << "\n"; + dbgs() << "Find Union Set " << Index << "\n"; + dbgs() << "\tAllocas are \n"; + for (auto Alloca : AllocaSet) + dbgs() << "\t\t" << *Alloca << "\n"; + } + }); + } }; } // namespace @@ -496,7 +643,14 @@ F.Offset = Offset; F.FieldIndex = FieldTypes.size(); if (F.ForSpill) { - F.ForSpill->setFieldIndex(F.FieldIndex); + if (AllocaInst *AI = dyn_cast(F.ForSpill->def())) { + assert(AllocaIndex.find(AI) != AllocaIndex.end()); + int AIIndex = AllocaIndex[AI]; + auto &AllocaSet = NonOverlapedAllocas[AIIndex]; + for (auto Alloca : AllocaSet) + SpillOfAllocas[Alloca]->setFieldIndex(F.FieldIndex); + } else + F.ForSpill->setFieldIndex(F.FieldIndex); } FieldTypes.push_back(F.Ty); @@ -536,6 +690,7 @@ }(); FrameTypeBuilder B(C, DL); + B.BuildNonOverlapedAllocas(F, Spills); AllocaInst *PromiseAlloca = Shape.getPromiseAlloca(); Optional PromiseFieldId; @@ -568,9 +723,11 @@ assert(PromiseAlloca == nullptr && "lowering doesn't support promises"); } + // Because multiple allocas may own the same field slot, + // we add allocas to field here. + B.addFieldForAllocas(); Value *CurrentDef = nullptr; - - // Create an entry for every spilled value. + // Create an entry for every spilled value which is not an AllocaInst. for (auto &S : Spills) { // We can have multiple entries in Spills for a single value, but // they should form a contiguous run. Ignore all but the first. @@ -582,9 +739,7 @@ assert(CurrentDef != PromiseAlloca && "recorded spill use of promise alloca?"); - if (auto *AI = dyn_cast(CurrentDef)) { - B.addFieldForAlloca(AI, &S); - } else { + if (!isa(CurrentDef)) { Type *Ty = CurrentDef->getType(); B.addField(Ty, None, &S); } @@ -820,7 +975,17 @@ } } - return Builder.CreateInBoundsGEP(FrameTy, FramePtr, Indices); + auto GEP = cast( + Builder.CreateInBoundsGEP(FrameTy, FramePtr, Indices)); + if (isa(Orig)) { + // If the type of GEP is not equal to the type of AllocaInst, it implies + // that the AllocaInst may be reused in the Frame slot of other + // AllocaInst. So we cast the GEP to the type of AllocaInst. + if (GEP->getResultElementType() != Orig->getType()) + return Builder.CreateBitCast(GEP, Orig->getType(), + Orig->getName() + Twine(".cast")); + } + return GEP; }; // Create a load instruction to reload the spilled value from the coroutine Index: llvm/test/Transforms/Coroutines/coro-frame-arrayalloca.ll =================================================================== --- llvm/test/Transforms/Coroutines/coro-frame-arrayalloca.ll +++ llvm/test/Transforms/Coroutines/coro-frame-arrayalloca.ll @@ -40,9 +40,9 @@ ; See if we used correct index to access prefix, data, suffix (@f) ; CHECK-LABEL: @f( -; CHECK: %prefix = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 2 +; CHECK: %prefix = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 3 ; CHECK-NEXT: %data = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 4 -; CHECK-NEXT: %suffix = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 3 +; CHECK-NEXT: %suffix = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 2 ; CHECK-NEXT: call void @consume.double.ptr(double* %prefix) ; CHECK-NEXT: call void @consume.i32.ptr(i32* %data) ; CHECK-NEXT: call void @consume.double.ptr(double* %suffix) @@ -50,9 +50,9 @@ ; See if we used correct index to access prefix, data, suffix (@f.resume) ; CHECK-LABEL: @f.resume( -; CHECK: %[[SUFFIX:.+]] = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 3 +; CHECK: %[[SUFFIX:.+]] = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 2 ; CHECK: %[[DATA:.+]] = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 4 -; CHECK: %[[PREFIX:.+]] = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 2 +; CHECK: %[[PREFIX:.+]] = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 3 ; CHECK: call void @consume.double.ptr(double* %[[PREFIX]]) ; CHECK-NEXT: call void @consume.i32.ptr(i32* %[[DATA]]) ; CHECK-NEXT: call void @consume.double.ptr(double* %[[SUFFIX]]) Index: llvm/test/Transforms/Coroutines/coro-frame-reuse-alloca-00.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/Coroutines/coro-frame-reuse-alloca-00.ll @@ -0,0 +1,78 @@ +; Check that we can handle spills of array allocas +; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s + +%struct.big_structure = type { [500 x i8] } +declare void @consume(%struct.big_structure*) + +define i8* @f(i1 %cond) "coroutine.presplit"="1" { +entry: + %data = alloca %struct.big_structure, align 1 + %data2 = alloca %struct.big_structure, align 1 + %id = call token @llvm.coro.id(i32 0, i8* null, i8* null, i8* null) + %size = call i32 @llvm.coro.size.i32() + %alloc = call i8* @malloc(i32 %size) + %hdl = call i8* @llvm.coro.begin(token %id, i8* %alloc) + br i1 %cond, label %then, label %else + +then: + %0 = bitcast %struct.big_structure* %data to i8* + call void @llvm.lifetime.start.p0i8(i64 500, i8* nonnull %0) + call void @consume(%struct.big_structure* %data) + %suspend.value = call i8 @llvm.coro.suspend(token none, i1 false) + switch i8 %suspend.value, label %coro.ret [i8 0, label %resume + i8 1, label %cleanup1] + +resume: + call void @llvm.lifetime.end.p0i8(i64 500, i8* nonnull %0) + br label %cleanup1 + +cleanup1: + call void @llvm.lifetime.end.p0i8(i64 500, i8* nonnull %0) + br label %cleanup + +else: + %1 = bitcast %struct.big_structure* %data2 to i8* + call void @llvm.lifetime.start.p0i8(i64 500, i8* nonnull %1) + call void @consume(%struct.big_structure* %data2) + %suspend.value2 = call i8 @llvm.coro.suspend(token none, i1 false) + switch i8 %suspend.value2, label %coro.ret [i8 0, label %resume2 + i8 1, label %cleanup2] + +resume2: + call void @llvm.lifetime.end.p0i8(i64 500, i8* nonnull %1) + br label %cleanup2 + +cleanup2: + call void @llvm.lifetime.end.p0i8(i64 500, i8* nonnull %1) + br label %cleanup + +cleanup: + %mem = call i8* @llvm.coro.free(token %id, i8* %hdl) + call void @free(i8* %mem) + br label %coro.ret +coro.ret: + call i1 @llvm.coro.end(i8* %hdl, i1 0) + ret i8* %hdl +} + +; CHECK-LABEL: @f( +; CHECK: call i8* @malloc(i32 520) + +declare i8* @llvm.coro.free(token, i8*) +declare i32 @llvm.coro.size.i32() +declare i8 @llvm.coro.suspend(token, i1) +declare void @llvm.coro.resume(i8*) +declare void @llvm.coro.destroy(i8*) + +declare token @llvm.coro.id(i32, i8*, i8*, i8*) +declare i1 @llvm.coro.alloc(token) +declare i8* @llvm.coro.begin(token, i8*) +declare i1 @llvm.coro.end(i8*, i1) + +declare noalias i8* @malloc(i32) +declare double @print(double) +declare void @free(i8*) + +declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) +declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) \ No newline at end of file Index: llvm/test/Transforms/Coroutines/coro-frame-reuse-alloca-01.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/Coroutines/coro-frame-reuse-alloca-01.ll @@ -0,0 +1,77 @@ +; Tests that variables in a Corotuine whose lifetime range is not overlapping each other +; re-use the same slot in Coroutine frame. +; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s +%"struct.task::promise_type" = type { i8 } +%struct.awaitable = type { i8 } +%struct.big_structure = type { [500 x i8] } +declare i8* @malloc(i64) +declare void @consume(%struct.big_structure*) +define void @a(i1 zeroext %cond) "coroutine.presplit"="1" { +entry: + %__promise = alloca %"struct.task::promise_type", align 1 + %a = alloca %struct.big_structure, align 1 + %ref.tmp7 = alloca %struct.awaitable, align 1 + %b = alloca %struct.big_structure, align 1 + %ref.tmp18 = alloca %struct.awaitable, align 1 + %0 = getelementptr inbounds %"struct.task::promise_type", %"struct.task::promise_type"* %__promise, i64 0, i32 0 + %1 = call token @llvm.coro.id(i32 16, i8* nonnull %0, i8* bitcast (void (i1)* @a to i8*), i8* null) + br label %init.ready +init.ready: + %2 = call noalias nonnull i8* @llvm.coro.begin(token %1, i8* null) + call void @llvm.lifetime.start.p0i8(i64 1, i8* nonnull %0) + br i1 %cond, label %if.then, label %if.else +if.then: + %3 = getelementptr inbounds %struct.big_structure, %struct.big_structure* %a, i64 0, i32 0, i64 0 + call void @llvm.lifetime.start.p0i8(i64 500, i8* nonnull %3) + call void @consume(%struct.big_structure* nonnull %a) + %save = call token @llvm.coro.save(i8* null) + %suspend = call i8 @llvm.coro.suspend(token %save, i1 false) + switch i8 %suspend, label %coro.ret [ + i8 0, label %await.ready + i8 1, label %cleanup1 + ] +await.ready: + call void @llvm.lifetime.end.p0i8(i64 500, i8* nonnull %3) + br label %cleanup1 +if.else: + %4 = getelementptr inbounds %struct.big_structure, %struct.big_structure* %b, i64 0, i32 0, i64 0 + call void @llvm.lifetime.start.p0i8(i64 500, i8* nonnull %4) + call void @consume(%struct.big_structure* nonnull %b) + %save2 = call token @llvm.coro.save(i8* null) + %suspend2 = call i8 @llvm.coro.suspend(token %save2, i1 false) + switch i8 %suspend2, label %coro.ret [ + i8 0, label %await2.ready + i8 1, label %cleanup2 + ] +await2.ready: + call void @llvm.lifetime.end.p0i8(i64 500, i8* nonnull %4) + br label %cleanup2 +cleanup1: + call void @llvm.lifetime.end.p0i8(i64 500, i8* nonnull %3) + br label %cleanup +cleanup2: + call void @llvm.lifetime.end.p0i8(i64 500, i8* nonnull %4) + br label %cleanup +cleanup: + call i8* @llvm.coro.free(token %1, i8* %2) + br label %coro.ret +coro.ret: + call i1 @llvm.coro.end(i8* null, i1 false) + ret void +} +; CHECK-LABEL: @a.resume( +; CHECK: %a.reload.addr{{[0-9]+}} = getelementptr inbounds %a.Frame, %a.Frame* %FramePtr[[APositon:.*]] +; CHECK: %b.reload.addr{{[0-9]+}} = getelementptr inbounds %a.Frame, %a.Frame* %FramePtr[[APositon]] + +declare token @llvm.coro.id(i32, i8* readnone, i8* nocapture readonly, i8*) +declare i1 @llvm.coro.alloc(token) #3 +declare i64 @llvm.coro.size.i64() #5 +declare i8* @llvm.coro.begin(token, i8* writeonly) #3 +declare token @llvm.coro.save(i8*) #3 +declare i8* @llvm.coro.frame() #5 +declare i8 @llvm.coro.suspend(token, i1) #3 +declare i8* @llvm.coro.free(token, i8* nocapture readonly) #2 +declare i1 @llvm.coro.end(i8*, i1) #3 +declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #4 +declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #4 \ No newline at end of file Index: llvm/test/Transforms/Coroutines/coro-frame-reuse-alloca-02.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/Coroutines/coro-frame-reuse-alloca-02.ll @@ -0,0 +1,78 @@ +; Tests that variables of different type in a Corotuine whose lifetime range is not overlapping each other +; re-use the same slot in Coroutine frame. +; RUN: opt < %s -coro-split -S | FileCheck %s +; RUN: opt < %s -passes=coro-split -S | FileCheck %s +%"struct.task::promise_type" = type { i8 } +%struct.awaitable = type { i8 } +%struct.big_structure = type { [500 x i8] } +%struct.big_structure.2 = type { [300 x i8] } +declare i8* @malloc(i64) +declare void @consume(%struct.big_structure*) +declare void @consume.2(%struct.big_structure.2*) +define void @a(i1 zeroext %cond) "coroutine.presplit"="1" { +entry: + %__promise = alloca %"struct.task::promise_type", align 1 + %a = alloca %struct.big_structure, align 1 + %ref.tmp7 = alloca %struct.awaitable, align 1 + %b = alloca %struct.big_structure.2, align 1 + %ref.tmp18 = alloca %struct.awaitable, align 1 + %0 = getelementptr inbounds %"struct.task::promise_type", %"struct.task::promise_type"* %__promise, i64 0, i32 0 + %1 = call token @llvm.coro.id(i32 16, i8* nonnull %0, i8* bitcast (void (i1)* @a to i8*), i8* null) + br label %init.ready +init.ready: + %2 = call noalias nonnull i8* @llvm.coro.begin(token %1, i8* null) + call void @llvm.lifetime.start.p0i8(i64 1, i8* nonnull %0) + br i1 %cond, label %if.then, label %if.else +if.then: + %3 = getelementptr inbounds %struct.big_structure, %struct.big_structure* %a, i64 0, i32 0, i64 0 + call void @llvm.lifetime.start.p0i8(i64 500, i8* nonnull %3) + call void @consume(%struct.big_structure* nonnull %a) + %save = call token @llvm.coro.save(i8* null) + %suspend = call i8 @llvm.coro.suspend(token %save, i1 false) + switch i8 %suspend, label %coro.ret [ + i8 0, label %await.ready + i8 1, label %cleanup1 + ] +await.ready: + call void @llvm.lifetime.end.p0i8(i64 500, i8* nonnull %3) + br label %cleanup1 +if.else: + %4 = getelementptr inbounds %struct.big_structure.2, %struct.big_structure.2* %b, i64 0, i32 0, i64 0 + call void @llvm.lifetime.start.p0i8(i64 300, i8* nonnull %4) + call void @consume.2(%struct.big_structure.2* nonnull %b) + %save2 = call token @llvm.coro.save(i8* null) + %suspend2 = call i8 @llvm.coro.suspend(token %save2, i1 false) + switch i8 %suspend2, label %coro.ret [ + i8 0, label %await2.ready + i8 1, label %cleanup2 + ] +await2.ready: + call void @llvm.lifetime.end.p0i8(i64 300, i8* nonnull %4) + br label %cleanup2 +cleanup1: + call void @llvm.lifetime.end.p0i8(i64 500, i8* nonnull %3) + br label %cleanup +cleanup2: + call void @llvm.lifetime.end.p0i8(i64 300, i8* nonnull %4) + br label %cleanup +cleanup: + call i8* @llvm.coro.free(token %1, i8* %2) + br label %coro.ret +coro.ret: + call i1 @llvm.coro.end(i8* null, i1 false) + ret void +} +; CHECK-LABEL: @a.resume( +; CHECK: %b.reload.addr = bitcast %struct.big_structure* %0 to %struct.big_structure.2* + +declare token @llvm.coro.id(i32, i8* readnone, i8* nocapture readonly, i8*) +declare i1 @llvm.coro.alloc(token) #3 +declare i64 @llvm.coro.size.i64() #5 +declare i8* @llvm.coro.begin(token, i8* writeonly) #3 +declare token @llvm.coro.save(i8*) #3 +declare i8* @llvm.coro.frame() #5 +declare i8 @llvm.coro.suspend(token, i1) #3 +declare i8* @llvm.coro.free(token, i8* nocapture readonly) #2 +declare i1 @llvm.coro.end(i8*, i1) #3 +declare void @llvm.lifetime.start.p0i8(i64, i8* nocapture) #4 +declare void @llvm.lifetime.end.p0i8(i64, i8* nocapture) #4 \ No newline at end of file Index: llvm/test/Transforms/Coroutines/coro-param-copy.ll =================================================================== --- llvm/test/Transforms/Coroutines/coro-param-copy.ll +++ llvm/test/Transforms/Coroutines/coro-param-copy.ll @@ -62,11 +62,11 @@ ; See that we only copy the x as y was not modified prior to coro.begin. ; CHECK: store void (%f.Frame*)* @f.destroy, void (%f.Frame*)** %destroy.addr ; The next 3 instructions are to copy data in %x.addr from stack to frame. -; CHECK-NEXT: %0 = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 3 +; CHECK-NEXT: %0 = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 4 ; CHECK-NEXT: %1 = load i64, i64* %x.addr, align 4 ; CHECK-NEXT: store i64 %1, i64* %0, align 4 ; The next 2 instructions are to recreate %y.cast in the original IR. -; CHECK-NEXT: %2 = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 4 +; CHECK-NEXT: %2 = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 3 ; CHECK-NEXT: %3 = bitcast i64* %2 to i8* ; The next 3 instructions are to copy data in %z.addr from stack to frame. ; CHECK-NEXT: %4 = getelementptr inbounds %f.Frame, %f.Frame* %FramePtr, i32 0, i32 5