Skip to content

Commit 71239d6

Browse files
committed
[CoroutineAccessors] SIL represents callee alloc.
When its operand has coroutine kind `yield_once_2`, a `begin_apply` instruction produces an additional value representing the storage allocated by the callee. This storage must be deallocated by a `dealloc_stack` on every path out of the function. Like any other stack allocation, it must obey stack discipline.
1 parent 49a2831 commit 71239d6

File tree

18 files changed

+219
-28
lines changed

18 files changed

+219
-28
lines changed

docs/SIL.rst

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6132,23 +6132,36 @@ begin_apply
61326132
// %float : $Float
61336133
// %token is a token
61346134

6135+
(%anyAddr, %float, %token, %allocation) = begin_apply %0() : $@yield_once_2 () -> (@yields @inout %Any, @yields Float)
6136+
// %anyAddr : $*Any
6137+
// %float : $Float
6138+
// %token is a token
6139+
// %allocation is a pointer to a token
6140+
61356141
Transfers control to coroutine ``%0``, passing it the given arguments.
61366142
The rules for the application generally follow the rules for ``apply``,
61376143
except:
61386144

6139-
- the callee value must have a ``yield_once`` coroutine type,
6145+
- the callee value must have be of single-yield coroutine type (``yield_once``
6146+
or ``yield_once_2``)
61406147

61416148
- control returns to this function not when the coroutine performs a
61426149
``return``, but when it performs a ``yield``, and
61436150

61446151
- the instruction results are derived from the yields of the coroutine
61456152
instead of its normal results.
61466153

6147-
The final result of a ``begin_apply`` is a "token", a special value which
6148-
can only be used as the operand of an ``end_apply`` or ``abort_apply``
6149-
instruction. Before this second instruction is executed, the coroutine
6150-
is said to be "suspended", and the token represents a reference to its
6151-
suspended activation record.
6154+
The final (in the case of ``@yield_once``) or penultimate (in the case of
6155+
``@yield_once_2``) result of a ``begin_apply`` is a "token", a special value
6156+
which can only be used as the operand of an ``end_apply`` or ``abort_apply``
6157+
instruction. Before this second instruction is executed, the coroutine is said
6158+
to be "suspended", and the token represents a reference to its suspended
6159+
activation record.
6160+
6161+
If the coroutine's kind ``yield_once_2``, its final result is an address of a
6162+
"token", representing the allocation done by the callee coroutine. It can only
6163+
be used as the operand of a ``dealloc_stack`` which must appear after the
6164+
coroutine is resumed.
61526165

61536166
The other results of the instruction correspond to the yields in the
61546167
coroutine type. In general, the rules of a yield are similar to the rules

include/swift/SIL/SILInstruction.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -847,6 +847,9 @@ class SILInstruction : public llvm::ilist_node<SILInstruction> {
847847
/// instruction.
848848
bool isAllocatingStack() const;
849849

850+
/// The stack allocation produced by the instruction, if any.
851+
SILValue getStackAllocation() const;
852+
850853
/// Returns true if this is the deallocation of a stack allocating instruction.
851854
/// The first operand must be the allocating instruction.
852855
bool isDeallocatingStack() const;
@@ -3256,12 +3259,20 @@ class BeginApplyInst final
32563259
}
32573260

32583261
MultipleValueInstructionResult *getTokenResult() const {
3262+
return const_cast<MultipleValueInstructionResult *>(
3263+
&getAllResultsBuffer().drop_back(isCalleeAllocated() ? 1 : 0).back());
3264+
}
3265+
3266+
MultipleValueInstructionResult *getCalleeAllocationResult() const {
3267+
if (!isCalleeAllocated()) {
3268+
return nullptr;
3269+
}
32593270
return const_cast<MultipleValueInstructionResult *>(
32603271
&getAllResultsBuffer().back());
32613272
}
32623273

32633274
SILInstructionResultArray getYieldedValues() const {
3264-
return getAllResultsBuffer().drop_back();
3275+
return getAllResultsBuffer().drop_back(isCalleeAllocated() ? 2 : 1);
32653276
}
32663277

32673278
void getCoroutineEndPoints(

lib/IRGen/GenCall.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4635,6 +4635,16 @@ void irgen::emitDeallocYieldManyCoroutineBuffer(IRGenFunction &IGF,
46354635
IGF.Builder.CreateLifetimeEnd(buffer, bufferSize);
46364636
}
46374637

4638+
void irgen::emitDeallocYieldOnce2CoroutineFrame(IRGenFunction &IGF,
4639+
llvm::Value *allocation) {
4640+
if (IGF.IGM.IRGen.Opts.EmitYieldOnce2AsYieldOnce) {
4641+
assert(!allocation);
4642+
return;
4643+
}
4644+
assert(allocation);
4645+
llvm::report_fatal_error("unimplemented");
4646+
}
4647+
46384648
Address irgen::emitAllocAsyncContext(IRGenFunction &IGF,
46394649
llvm::Value *sizeValue) {
46404650
auto alignment = IGF.IGM.getAsyncContextAlignment();

lib/IRGen/GenCall.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ namespace irgen {
203203

204204
Address emitAllocYieldOnceCoroutineBuffer(IRGenFunction &IGF);
205205
void emitDeallocYieldOnceCoroutineBuffer(IRGenFunction &IGF, Address buffer);
206+
void emitDeallocYieldOnce2CoroutineFrame(IRGenFunction &IGF,
207+
llvm::Value *allocation);
206208
void
207209
emitYieldOnceCoroutineEntry(IRGenFunction &IGF,
208210
CanSILFunctionType coroutineType,

lib/IRGen/IRGenSIL.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ class IRGenSILFunction :
366366
llvm::DenseMap<SILValue, LoweredValue> LoweredValues;
367367
llvm::DenseMap<SILValue, StackAddress> LoweredPartialApplyAllocations;
368368
llvm::DenseMap<SILType, LoweredValue> LoweredUndefs;
369+
llvm::DenseMap<SILValue, llvm::Value *> LoweredSingleYieldFrames;
369370

370371
/// All alloc_ref instructions which allocate the object on the stack.
371372
llvm::SmallPtrSet<SILInstruction *, 8> StackAllocs;
@@ -6264,6 +6265,13 @@ void IRGenSILFunction::visitDeallocStackInst(swift::DeallocStackInst *i) {
62646265
emitDeallocateDynamicAlloca(stackAddr);
62656266
return;
62666267
}
6268+
if (isaResultOf<BeginApplyInst>(i->getOperand())) {
6269+
auto *mvi = getAsResultOf<BeginApplyInst>(i->getOperand());
6270+
auto *bai = cast<BeginApplyInst>(mvi->getParent());
6271+
emitDeallocYieldOnce2CoroutineFrame(
6272+
*this, LoweredSingleYieldFrames[bai->getCalleeAllocationResult()]);
6273+
return;
6274+
}
62676275

62686276
auto allocatedType = i->getOperand()->getType();
62696277
const TypeInfo &allocatedTI = getTypeInfo(allocatedType);

lib/SIL/IR/SILInstruction.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1283,6 +1283,10 @@ bool SILInstruction::isAllocatingStack() const {
12831283
&& !PA->getFunction()->hasOwnership();
12841284
}
12851285

1286+
if (auto *BAI = dyn_cast<BeginApplyInst>(this)) {
1287+
return BAI->isCalleeAllocated();
1288+
}
1289+
12861290
if (auto *BI = dyn_cast<BuiltinInst>(this)) {
12871291
if (BI->getBuiltinKind() == BuiltinValueKind::StackAlloc ||
12881292
BI->getBuiltinKind() == BuiltinValueKind::UnprotectedStackAlloc) {
@@ -1293,6 +1297,17 @@ bool SILInstruction::isAllocatingStack() const {
12931297
return false;
12941298
}
12951299

1300+
SILValue SILInstruction::getStackAllocation() const {
1301+
if (!isAllocatingStack()) {
1302+
return {};
1303+
}
1304+
1305+
if (auto *bai = dyn_cast<BeginApplyInst>(this)) {
1306+
return bai->getCalleeAllocationResult();
1307+
}
1308+
return cast<SingleValueInstruction>(this);
1309+
}
1310+
12961311
bool SILInstruction::isDeallocatingStack() const {
12971312
if (isa<DeallocStackInst>(this) ||
12981313
isa<DeallocStackRefInst>(this) ||

lib/SIL/IR/SILInstructions.cpp

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -665,14 +665,19 @@ BeginApplyInst *BeginApplyInst::create(
665665
: SILModuleConventions(parentFunction.getModule())));
666666
}
667667

668-
resultTypes.push_back(
669-
SILType::getSILTokenType(parentFunction.getASTContext()));
668+
auto tokenTy = SILType::getSILTokenType(parentFunction.getASTContext());
669+
resultTypes.push_back(tokenTy);
670670
// The begin_apply token represents the borrow scope of all owned and
671671
// guaranteed call arguments. Although SILToken is (currently) trivially
672672
// typed, it must have guaranteed ownership so end_apply and abort_apply will
673673
// be recognized as lifetime-ending uses.
674674
resultOwnerships.push_back(OwnershipKind::Guaranteed);
675675

676+
if (substCalleeType->isCalleeAllocatedCoroutine()) {
677+
resultTypes.push_back(tokenTy.getAddressType());
678+
resultOwnerships.push_back(OwnershipKind::None);
679+
}
680+
676681
SmallVector<SILValue, 32> typeDependentOperands;
677682
collectTypeDependentOperands(typeDependentOperands, parentFunction,
678683
substCalleeType, subs);

lib/SIL/Verifier/SILVerifier.cpp

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1688,8 +1688,11 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
16881688
}
16891689

16901690
static bool isLegalSILTokenProducer(SILValue value) {
1691-
if (auto *baResult = isaResultOf<BeginApplyInst>(value))
1692-
return baResult->isBeginApplyToken();
1691+
if (auto *baResult = isaResultOf<BeginApplyInst>(value)) {
1692+
auto *bai = cast<BeginApplyInst>(baResult->getParent());
1693+
return value == bai->getTokenResult() ||
1694+
value == bai->getCalleeAllocationResult();
1695+
}
16931696

16941697
if (isa<OpenPackElementInst>(value))
16951698
return true;
@@ -3772,12 +3775,23 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
37723775
}
37733776

37743777
void checkDeallocStackInst(DeallocStackInst *DI) {
3778+
auto isTokenFromCalleeAllocatedBeginApply = [](SILValue value) -> bool {
3779+
auto *inst = value->getDefiningInstruction();
3780+
if (!inst)
3781+
return false;
3782+
auto *bai = dyn_cast<BeginApplyInst>(inst);
3783+
if (!bai)
3784+
return false;
3785+
return value == bai->getCalleeAllocationResult();
3786+
};
37753787
require(isa<SILUndef>(DI->getOperand()) ||
37763788
isa<AllocStackInst>(DI->getOperand()) ||
37773789
isa<AllocVectorInst>(DI->getOperand()) ||
37783790
(isa<PartialApplyInst>(DI->getOperand()) &&
3779-
cast<PartialApplyInst>(DI->getOperand())->isOnStack()),
3780-
"Operand of dealloc_stack must be an alloc_stack, alloc_vector or partial_apply "
3791+
cast<PartialApplyInst>(DI->getOperand())->isOnStack()) ||
3792+
(isTokenFromCalleeAllocatedBeginApply(DI->getOperand())),
3793+
"Operand of dealloc_stack must be an alloc_stack, alloc_vector or "
3794+
"partial_apply "
37813795
"[stack]");
37823796
}
37833797
void checkDeallocPackInst(DeallocPackInst *DI) {
@@ -6768,7 +6782,7 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
67686782
};
67696783

67706784
struct BBState {
6771-
std::vector<SingleValueInstruction*> Stack;
6785+
std::vector<SILValue> Stack;
67726786

67736787
/// Contents: BeginAccessInst*, BeginApplyInst*.
67746788
std::set<SILInstruction*> ActiveOps;
@@ -6818,9 +6832,15 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
68186832
}
68196833

68206834
if (i.isAllocatingStack()) {
6821-
state.Stack.push_back(cast<SingleValueInstruction>(&i));
6822-
6823-
} else if (i.isDeallocatingStack()) {
6835+
if (auto *BAI = dyn_cast<BeginApplyInst>(&i)) {
6836+
state.Stack.push_back(BAI->getCalleeAllocationResult());
6837+
} else {
6838+
state.Stack.push_back(cast<SingleValueInstruction>(&i));
6839+
}
6840+
// Not "else if": begin_apply both allocates stack and begins an
6841+
// operation.
6842+
}
6843+
if (i.isDeallocatingStack()) {
68246844
SILValue op = i.getOperand(0);
68256845
while (auto *mvi = dyn_cast<MoveValueInst>(op)) {
68266846
op = mvi->getOperand();

lib/SILGen/SILGenApply.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5056,6 +5056,10 @@ SILGenFunction::emitBeginApply(SILLocation loc, ManagedValue fn,
50565056
/*indirect results*/ {}, /*indirect errors*/ {},
50575057
rawResults, ExecutorBreadcrumb());
50585058

5059+
if (substFnType->isCalleeAllocatedCoroutine()) {
5060+
auto allocation = rawResults.pop_back_val();
5061+
enterDeallocStackCleanup(allocation);
5062+
}
50595063
auto token = rawResults.pop_back_val();
50605064
auto yieldValues = llvm::ArrayRef(rawResults);
50615065

lib/SILOptimizer/Utils/LoopUtils.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,8 @@ bool swift::canDuplicateLoopInstruction(SILLoop *L, SILInstruction *I) {
231231

232232
// The deallocation of a stack allocation must be in the loop, otherwise the
233233
// deallocation will be fed by a phi node of two allocations.
234-
if (I->isAllocatingStack()) {
235-
for (auto *UI : cast<SingleValueInstruction>(I)->getUses()) {
234+
if (auto allocation = I->getStackAllocation()) {
235+
for (auto *UI : allocation->getUses()) {
236236
if (UI->getUser()->isDeallocatingStack()) {
237237
if (!L->contains(UI->getUser()->getParent()))
238238
return false;
@@ -246,6 +246,8 @@ bool swift::canDuplicateLoopInstruction(SILLoop *L, SILInstruction *I) {
246246
SILValue address = dealloc->getOperand();
247247
if (isa<AllocStackInst>(address) || isa<PartialApplyInst>(address))
248248
alloc = cast<SingleValueInstruction>(address);
249+
else if (isaResultOf<BeginApplyInst>(address))
250+
alloc = cast<MultipleValueInstructionResult>(address)->getParent();
249251
}
250252
if (auto *dealloc = dyn_cast<DeallocStackRefInst>(I))
251253
alloc = dealloc->getAllocRef();

0 commit comments

Comments
 (0)