Skip to content

Commit 7311ca3

Browse files
committed
[SUA][Coroutines] Change LLVM's Coroutine lowering to allow for and use extra hash parameter to llvm.coro.id.retcon.once and llvm.coro.id.retcon (#10015)
[SUA][Coroutines] Change LLVM's Coroutine lowering to allow for and use extra hash parameter to llvm.coro.id.retcon.once and llvm.coro.id.retcon Change the signatures of llvm.coro.id.retcon.once and llvm.coro.id.retcon to pass a hash parameter as a variable argument. This change will allow IRGen to pass the new runtime function swift_coroFrameAlloc (which calls the typed allocator if TMO is enabled) along with a type hash to these two intrinsics. rdar://141236876 (cherry picked from commit fe25ae6)
1 parent b35b745 commit 7311ca3

22 files changed

+467
-51
lines changed

llvm/include/llvm/IR/Intrinsics.td

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1674,11 +1674,11 @@ def int_coro_id : DefaultAttrsIntrinsic<[llvm_token_ty],
16741674
NoCapture<ArgIndex<2>>]>;
16751675
def int_coro_id_retcon : Intrinsic<[llvm_token_ty],
16761676
[llvm_i32_ty, llvm_i32_ty, llvm_ptr_ty,
1677-
llvm_ptr_ty, llvm_ptr_ty, llvm_ptr_ty],
1677+
llvm_ptr_ty, llvm_ptr_ty, llvm_ptr_ty, llvm_vararg_ty],
16781678
[]>;
16791679
def int_coro_id_retcon_once : Intrinsic<[llvm_token_ty],
16801680
[llvm_i32_ty, llvm_i32_ty, llvm_ptr_ty,
1681-
llvm_ptr_ty, llvm_ptr_ty, llvm_ptr_ty],
1681+
llvm_ptr_ty, llvm_ptr_ty, llvm_ptr_ty, llvm_vararg_ty],
16821682
[]>;
16831683
def int_coro_alloc : Intrinsic<[llvm_i1_ty], [llvm_token_ty], []>;
16841684
def int_coro_id_async : Intrinsic<[llvm_token_ty],
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
//===- CoroShape.h - Coroutine info for lowering --------------*- C++ -*---===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
// This file declares the shape info struct that is required by many coroutine
9+
// utility methods.
10+
//===----------------------------------------------------------------------===//
11+
12+
#ifndef LLVM_TRANSFORMS_COROUTINES_COROSHAPE_H
13+
#define LLVM_TRANSFORMS_COROUTINES_COROSHAPE_H
14+
15+
#include "llvm/IR/IRBuilder.h"
16+
#include "llvm/IR/PassManager.h"
17+
#include "llvm/Transforms/Coroutines/CoroInstr.h"
18+
19+
namespace llvm {
20+
21+
class CallGraph;
22+
23+
namespace coro {
24+
25+
enum class ABI {
26+
/// The "resume-switch" lowering, where there are separate resume and
27+
/// destroy functions that are shared between all suspend points. The
28+
/// coroutine frame implicitly stores the resume and destroy functions,
29+
/// the current index, and any promise value.
30+
Switch,
31+
32+
/// The "returned-continuation" lowering, where each suspend point creates a
33+
/// single continuation function that is used for both resuming and
34+
/// destroying. Does not support promises.
35+
Retcon,
36+
37+
/// The "unique returned-continuation" lowering, where each suspend point
38+
/// creates a single continuation function that is used for both resuming
39+
/// and destroying. Does not support promises. The function is known to
40+
/// suspend at most once during its execution, and the return value of
41+
/// the continuation is void.
42+
RetconOnce,
43+
44+
/// The "async continuation" lowering, where each suspend point creates a
45+
/// single continuation function. The continuation function is available as an
46+
/// intrinsic.
47+
Async,
48+
};
49+
50+
// Holds structural Coroutine Intrinsics for a particular function and other
51+
// values used during CoroSplit pass.
52+
struct Shape {
53+
CoroBeginInst *CoroBegin = nullptr;
54+
SmallVector<AnyCoroEndInst *, 4> CoroEnds;
55+
SmallVector<CoroSizeInst *, 2> CoroSizes;
56+
SmallVector<CoroAlignInst *, 2> CoroAligns;
57+
SmallVector<AnyCoroSuspendInst *, 4> CoroSuspends;
58+
SmallVector<CoroAwaitSuspendInst *, 4> CoroAwaitSuspends;
59+
SmallVector<CallInst *, 2> SymmetricTransfers;
60+
61+
// Values invalidated by replaceSwiftErrorOps()
62+
SmallVector<CallInst *, 2> SwiftErrorOps;
63+
64+
void clear() {
65+
CoroBegin = nullptr;
66+
CoroEnds.clear();
67+
CoroSizes.clear();
68+
CoroAligns.clear();
69+
CoroSuspends.clear();
70+
CoroAwaitSuspends.clear();
71+
SymmetricTransfers.clear();
72+
73+
SwiftErrorOps.clear();
74+
75+
FrameTy = nullptr;
76+
FramePtr = nullptr;
77+
AllocaSpillBlock = nullptr;
78+
}
79+
80+
// Scan the function and collect the above intrinsics for later processing
81+
void analyze(Function &F, SmallVectorImpl<CoroFrameInst *> &CoroFrames,
82+
SmallVectorImpl<CoroSaveInst *> &UnusedCoroSaves);
83+
// If for some reason, we were not able to find coro.begin, bailout.
84+
void invalidateCoroutine(Function &F,
85+
SmallVectorImpl<CoroFrameInst *> &CoroFrames);
86+
// Perform ABI related initial transformation
87+
void initABI();
88+
// Remove orphaned and unnecessary intrinsics
89+
void cleanCoroutine(SmallVectorImpl<CoroFrameInst *> &CoroFrames,
90+
SmallVectorImpl<CoroSaveInst *> &UnusedCoroSaves);
91+
92+
// Field indexes for special fields in the switch lowering.
93+
struct SwitchFieldIndex {
94+
enum {
95+
Resume,
96+
Destroy
97+
98+
// The promise field is always at a fixed offset from the start of
99+
// frame given its type, but the index isn't a constant for all
100+
// possible frames.
101+
102+
// The switch-index field isn't at a fixed offset or index, either;
103+
// we just work it in where it fits best.
104+
};
105+
};
106+
107+
coro::ABI ABI;
108+
109+
StructType *FrameTy = nullptr;
110+
Align FrameAlign;
111+
uint64_t FrameSize = 0;
112+
Value *FramePtr = nullptr;
113+
BasicBlock *AllocaSpillBlock = nullptr;
114+
115+
struct SwitchLoweringStorage {
116+
SwitchInst *ResumeSwitch;
117+
AllocaInst *PromiseAlloca;
118+
BasicBlock *ResumeEntryBlock;
119+
unsigned IndexField;
120+
unsigned IndexAlign;
121+
unsigned IndexOffset;
122+
bool HasFinalSuspend;
123+
bool HasUnwindCoroEnd;
124+
};
125+
126+
struct RetconLoweringStorage {
127+
Function *ResumePrototype;
128+
Function *Alloc;
129+
Function *Dealloc;
130+
BasicBlock *ReturnBlock;
131+
bool IsFrameInlineInStorage;
132+
};
133+
134+
struct AsyncLoweringStorage {
135+
Value *Context;
136+
CallingConv::ID AsyncCC;
137+
unsigned ContextArgNo;
138+
uint64_t ContextHeaderSize;
139+
uint64_t ContextAlignment;
140+
uint64_t FrameOffset; // Start of the frame.
141+
uint64_t ContextSize; // Includes frame size.
142+
GlobalVariable *AsyncFuncPointer;
143+
144+
Align getContextAlignment() const { return Align(ContextAlignment); }
145+
};
146+
147+
union {
148+
SwitchLoweringStorage SwitchLowering;
149+
RetconLoweringStorage RetconLowering;
150+
AsyncLoweringStorage AsyncLowering;
151+
};
152+
153+
CoroIdInst *getSwitchCoroId() const {
154+
assert(ABI == coro::ABI::Switch);
155+
return cast<CoroIdInst>(CoroBegin->getId());
156+
}
157+
158+
AnyCoroIdRetconInst *getRetconCoroId() const {
159+
assert(ABI == coro::ABI::Retcon || ABI == coro::ABI::RetconOnce);
160+
return cast<AnyCoroIdRetconInst>(CoroBegin->getId());
161+
}
162+
163+
CoroIdAsyncInst *getAsyncCoroId() const {
164+
assert(ABI == coro::ABI::Async);
165+
return cast<CoroIdAsyncInst>(CoroBegin->getId());
166+
}
167+
168+
unsigned getSwitchIndexField() const {
169+
assert(ABI == coro::ABI::Switch);
170+
assert(FrameTy && "frame type not assigned");
171+
return SwitchLowering.IndexField;
172+
}
173+
IntegerType *getIndexType() const {
174+
assert(ABI == coro::ABI::Switch);
175+
assert(FrameTy && "frame type not assigned");
176+
return cast<IntegerType>(FrameTy->getElementType(getSwitchIndexField()));
177+
}
178+
ConstantInt *getIndex(uint64_t Value) const {
179+
return ConstantInt::get(getIndexType(), Value);
180+
}
181+
182+
PointerType *getSwitchResumePointerType() const {
183+
assert(ABI == coro::ABI::Switch);
184+
assert(FrameTy && "frame type not assigned");
185+
return cast<PointerType>(FrameTy->getElementType(SwitchFieldIndex::Resume));
186+
}
187+
188+
FunctionType *getResumeFunctionType() const {
189+
switch (ABI) {
190+
case coro::ABI::Switch:
191+
return FunctionType::get(Type::getVoidTy(FrameTy->getContext()),
192+
PointerType::getUnqual(FrameTy->getContext()),
193+
/*IsVarArg=*/false);
194+
case coro::ABI::Retcon:
195+
case coro::ABI::RetconOnce:
196+
return RetconLowering.ResumePrototype->getFunctionType();
197+
case coro::ABI::Async:
198+
// Not used. The function type depends on the active suspend.
199+
return nullptr;
200+
}
201+
202+
llvm_unreachable("Unknown coro::ABI enum");
203+
}
204+
205+
ArrayRef<Type *> getRetconResultTypes() const {
206+
assert(ABI == coro::ABI::Retcon || ABI == coro::ABI::RetconOnce);
207+
auto FTy = CoroBegin->getFunction()->getFunctionType();
208+
209+
// The safety of all this is checked by checkWFRetconPrototype.
210+
if (auto STy = dyn_cast<StructType>(FTy->getReturnType())) {
211+
return STy->elements().slice(1);
212+
} else {
213+
return ArrayRef<Type *>();
214+
}
215+
}
216+
217+
ArrayRef<Type *> getRetconResumeTypes() const {
218+
assert(ABI == coro::ABI::Retcon || ABI == coro::ABI::RetconOnce);
219+
220+
// The safety of all this is checked by checkWFRetconPrototype.
221+
auto FTy = RetconLowering.ResumePrototype->getFunctionType();
222+
return FTy->params().slice(1);
223+
}
224+
225+
CallingConv::ID getResumeFunctionCC() const {
226+
switch (ABI) {
227+
case coro::ABI::Switch:
228+
return CallingConv::Fast;
229+
230+
case coro::ABI::Retcon:
231+
case coro::ABI::RetconOnce:
232+
return RetconLowering.ResumePrototype->getCallingConv();
233+
case coro::ABI::Async:
234+
return AsyncLowering.AsyncCC;
235+
}
236+
llvm_unreachable("Unknown coro::ABI enum");
237+
}
238+
239+
AllocaInst *getPromiseAlloca() const {
240+
if (ABI == coro::ABI::Switch)
241+
return SwitchLowering.PromiseAlloca;
242+
return nullptr;
243+
}
244+
245+
BasicBlock::iterator getInsertPtAfterFramePtr() const {
246+
if (auto *I = dyn_cast<Instruction>(FramePtr)) {
247+
BasicBlock::iterator It = std::next(I->getIterator());
248+
It.setHeadBit(true); // Copy pre-RemoveDIs behaviour.
249+
return It;
250+
}
251+
return cast<Argument>(FramePtr)->getParent()->getEntryBlock().begin();
252+
}
253+
254+
/// Allocate memory according to the rules of the active lowering.
255+
///
256+
/// \param CG - if non-null, will be updated for the new call
257+
Value *emitAlloc(IRBuilder<> &Builder, Value *Size, CallGraph *CG) const;
258+
259+
/// Deallocate memory according to the rules of the active lowering.
260+
///
261+
/// \param CG - if non-null, will be updated for the new call
262+
void emitDealloc(IRBuilder<> &Builder, Value *Ptr, CallGraph *CG) const;
263+
264+
Shape() = default;
265+
explicit Shape(Function &F) {
266+
SmallVector<CoroFrameInst *, 8> CoroFrames;
267+
SmallVector<CoroSaveInst *, 2> UnusedCoroSaves;
268+
269+
analyze(F, CoroFrames, UnusedCoroSaves);
270+
if (!CoroBegin) {
271+
invalidateCoroutine(F, CoroFrames);
272+
return;
273+
}
274+
cleanCoroutine(CoroFrames, UnusedCoroSaves);
275+
}
276+
};
277+
278+
} // end namespace coro
279+
280+
} // end namespace llvm
281+
282+
#endif // LLVM_TRANSFORMS_COROUTINES_COROSHAPE_H

llvm/lib/Transforms/Coroutines/CoroInstr.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ class LLVM_LIBRARY_VISIBILITY CoroIdInst : public AnyCoroIdInst {
233233
/// This represents either the llvm.coro.id.retcon or
234234
/// llvm.coro.id.retcon.once instruction.
235235
class LLVM_LIBRARY_VISIBILITY AnyCoroIdRetconInst : public AnyCoroIdInst {
236-
enum { SizeArg, AlignArg, StorageArg, PrototypeArg, AllocArg, DeallocArg };
236+
enum { SizeArg, AlignArg, StorageArg, PrototypeArg, AllocArg, DeallocArg, TypeIdArg };
237237

238238
public:
239239
void checkWellFormed() const;
@@ -267,6 +267,19 @@ class LLVM_LIBRARY_VISIBILITY AnyCoroIdRetconInst : public AnyCoroIdInst {
267267
return cast<Function>(getArgOperand(DeallocArg)->stripPointerCasts());
268268
}
269269

270+
/// Return the TypeId to be used for allocating typed memory
271+
ConstantInt *getTypeId() const {
272+
if (arg_size() <= TypeIdArg)
273+
return nullptr;
274+
assert(hasTypeId() && "Invalid number of arguments");
275+
return cast<ConstantInt>(getArgOperand(TypeIdArg));
276+
}
277+
278+
/// Return true if TypeId is present in the list of arguments, false otherwise
279+
bool hasTypeId() const {
280+
return arg_size() == TypeIdArg + 1;
281+
}
282+
270283
// Methods to support type inquiry through isa, cast, and dyn_cast:
271284
static bool classof(const IntrinsicInst *I) {
272285
auto ID = I->getIntrinsicID();

llvm/lib/Transforms/Coroutines/CoroInternal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ struct LLVM_LIBRARY_VISIBILITY Shape {
130130
Function *Dealloc;
131131
BasicBlock *ReturnBlock;
132132
bool IsFrameInlineInStorage;
133+
ConstantInt* TypeId;
133134
};
134135

135136
struct AsyncLoweringStorage {

llvm/lib/Transforms/Coroutines/Coroutines.cpp

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ void coro::Shape::buildFrom(Function &F) {
343343
this->RetconLowering.Dealloc = ContinuationId->getDeallocFunction();
344344
this->RetconLowering.ReturnBlock = nullptr;
345345
this->RetconLowering.IsFrameInlineInStorage = false;
346+
this->RetconLowering.TypeId = ContinuationId->getTypeId();
346347

347348
// Determine the result value types, and make sure they match up with
348349
// the values passed to the suspends.
@@ -465,7 +466,12 @@ Value *coro::Shape::emitAlloc(IRBuilder<> &Builder, Value *Size,
465466
Size = Builder.CreateIntCast(Size,
466467
Alloc->getFunctionType()->getParamType(0),
467468
/*is signed*/ false);
468-
auto *Call = Builder.CreateCall(Alloc, Size);
469+
ConstantInt* TypeId = RetconLowering.TypeId;
470+
CallInst *Call;
471+
if (TypeId == nullptr)
472+
Call = Builder.CreateCall(Alloc, Size);
473+
else
474+
Call = Builder.CreateCall(Alloc, {Size, TypeId});
469475
propagateCallAttrsFromCallee(Call, Alloc);
470476
addCallToCallGraph(CG, Call, Alloc);
471477
return Call;
@@ -558,9 +564,14 @@ static void checkWFAlloc(const Instruction *I, Value *V) {
558564
if (!FT->getReturnType()->isPointerTy())
559565
fail(I, "llvm.coro.* allocator must return a pointer", F);
560566

561-
if (FT->getNumParams() != 1 ||
562-
!FT->getParamType(0)->isIntegerTy())
563-
fail(I, "llvm.coro.* allocator must take integer as only param", F);
567+
if (FT->getNumParams() > 2 || FT->getNumParams() == 0)
568+
fail(I, "llvm.coro.* allocator must take either one or two params", F);
569+
570+
if (FT->getNumParams() == 1 && !FT->getParamType(0)->isIntegerTy())
571+
fail(I, "llvm.coro.* allocator must take integer as its first param", F);
572+
573+
if (FT->getNumParams() == 2 && !FT->getParamType(1)->isIntegerTy())
574+
fail(I, "llvm.coro.* allocator must take uint64_t as its second param", F);
564575
}
565576

566577
/// Check that the given value is a well-formed deallocator.

0 commit comments

Comments
 (0)