Skip to content

Commit daa82bc

Browse files
committed
Fix ExistentialSpecializer to correctly cleanup storage.
Handle calling conventions and cleanups in all the places (hopefully). - when ExistentialSpecializer copies the specialized concrete arg into the original existential value - when ExistentialSpecializer generates a think - when SILCombine substitutes concrete values in place of the opened existential. One particularly nasty problem is the existential boxes need to be destroyed. It is not ok to simply destroy their value. The "leaks" tool does not catch this problem. Ownership SIL will make this all much more robust. Fixes <rdar://problem/50595630> Multiple leaks detected - Swift Perf
1 parent c7acbdb commit daa82bc

File tree

8 files changed

+460
-148
lines changed

8 files changed

+460
-148
lines changed

lib/SILOptimizer/FunctionSignatureTransforms/ExistentialSpecializer.cpp

Lines changed: 11 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -115,19 +115,6 @@ bool ExistentialSpecializer::findConcreteTypeFromSoleConformingType(
115115
return true;
116116
}
117117

118-
/// Check if the argument Arg is used in a destroy_use instruction.
119-
static void
120-
findIfCalleeUsesArgInDestroyUse(SILValue Arg,
121-
ExistentialTransformArgumentDescriptor &ETAD) {
122-
for (Operand *ArgUse : Arg->getUses()) {
123-
auto *ArgUser = ArgUse->getUser();
124-
if (isa<DestroyAddrInst>(ArgUser)) {
125-
ETAD.DestroyAddrUse = true;
126-
break;
127-
}
128-
}
129-
}
130-
131118
/// Helper function to ensure that the argument is not InOut or InOut_Aliasable
132119
static bool isNonInoutIndirectArgument(SILValue Arg,
133120
SILArgumentConvention ArgConvention) {
@@ -193,18 +180,21 @@ bool ExistentialSpecializer::canSpecializeExistentialArgsInFunction(
193180
continue;
194181
}
195182

196-
/// Determine attributes of the existential addr arguments such as
197-
/// destroy_use, immutable_access.
183+
/// Determine attributes of the existential addr argument.
198184
ExistentialTransformArgumentDescriptor ETAD;
199185
auto paramInfo = origCalleeConv.getParamInfoForSILArg(Idx);
200-
ETAD.AccessType = (paramInfo.isIndirectMutating() || paramInfo.isConsumed())
186+
// The ExistentialSpecializerCloner copies the incoming generic argument
187+
// into an existential. This won't work if the original argument is
188+
// mutated. Furthermore, SILCombine would not be able to replace a mutated
189+
// existential with a concrete value, so the specialization thunk could not
190+
// be optimized away.
191+
if (paramInfo.isIndirectMutating())
192+
continue;
193+
194+
ETAD.AccessType = paramInfo.isConsumed()
201195
? OpenedExistentialAccess::Mutable
202196
: OpenedExistentialAccess::Immutable;
203-
ETAD.DestroyAddrUse = false;
204-
if ((CalleeArgs[Idx]->getType().getPreferredExistentialRepresentation(
205-
F->getModule()))
206-
!= ExistentialRepresentation::Class)
207-
findIfCalleeUsesArgInDestroyUse(CalleeArg, ETAD);
197+
ETAD.isConsumed = paramInfo.isConsumed();
208198

209199
/// Save the attributes
210200
ExistentialArgDescriptor[Idx] = ETAD;

lib/SILOptimizer/FunctionSignatureTransforms/ExistentialTransform.cpp

Lines changed: 75 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ class ExistentialSpecializerCloner
5757
// Use one OpenedArchetypesTracker while cloning.
5858
SILOpenedArchetypesTracker OpenedArchetypesTracker;
5959

60-
// Holds an entry for any indirect argument requiring a new alloc_stack
61-
// created during argument cloning.
62-
SmallDenseMap<int, AllocStackInst *> ArgToAllocStackMap;
60+
// AllocStack instructions introduced in the new prolog that require cleanup.
61+
SmallVector<AllocStackInst *, 4> AllocStackInsts;
62+
// Temporary values introduced in the new prolog that require cleanup.
63+
SmallVector<SILValue, 4> CleanupValues;
6364

6465
protected:
6566
void postProcess(SILInstruction *Orig, SILInstruction *Cloned) {
@@ -99,36 +100,24 @@ void ExistentialSpecializerCloner::cloneAndPopulateFunction() {
99100
auto *NewEntryBB = getBuilder().getFunction().getEntryBlock();
100101
cloneFunctionBody(&Original, NewEntryBB, entryArgs);
101102

102-
/// If there is an argument with no DestroyUse, insert DeallocStack
103-
/// before return Instruction.
104-
SmallPtrSet<ReturnInst *, 4> ReturnInsts;
105-
/// Find the set of return instructions in a function.
106-
for (auto &BB : getBuilder().getFunction()) {
107-
TermInst *TI = BB.getTerminator();
108-
if (auto *RI = dyn_cast<ReturnInst>(TI)) {
109-
ReturnInsts.insert(RI);
110-
}
111-
}
112-
113-
/// Walk over destroy_addr instructions and perform fixups.
114-
for (auto &ArgDesc : reversed(ArgumentDescList)) {
115-
int ArgIndex = ArgDesc.Index;
116-
auto iter = ArgToAllocStackMap.find(ArgIndex);
117-
if (iter != ArgToAllocStackMap.end()) {
118-
// Need to insert DeallocStack before return.
119-
for (auto *I : ReturnInsts) {
120-
SILBuilderWithScope Builder(I);
121-
// A return location can't be used for a non-return instruction.
122-
auto loc = RegularLocation::getAutoGeneratedLocation();
123-
Builder.createDeallocStack(loc, iter->second);
124-
}
125-
}
103+
// Cleanup allocations created in the new prolog.
104+
SmallVector<SILBasicBlock *, 4> exitingBlocks;
105+
getBuilder().getFunction().findExitingBlocks(exitingBlocks);
106+
for (auto *exitBB : exitingBlocks) {
107+
SILBuilderWithScope Builder(exitBB->getTerminator());
108+
// A return location can't be used for a non-return instruction.
109+
auto loc = RegularLocation::getAutoGeneratedLocation();
110+
for (SILValue cleanupVal : CleanupValues)
111+
Builder.createDestroyAddr(loc, cleanupVal);
112+
113+
for (auto *ASI : reversed(AllocStackInsts))
114+
Builder.createDeallocStack(loc, ASI);
126115
}
127116
}
128117

129118
// Create the entry basic block with the function arguments.
130-
void ExistentialSpecializerCloner::
131-
cloneArguments(SmallVectorImpl<SILValue> &entryArgs) {
119+
void ExistentialSpecializerCloner::cloneArguments(
120+
SmallVectorImpl<SILValue> &entryArgs) {
132121
auto &M = OrigF->getModule();
133122
auto &Ctx = M.getASTContext();
134123

@@ -190,38 +179,42 @@ cloneArguments(SmallVectorImpl<SILValue> &entryArgs) {
190179
/// bb0(%0 : $*T):
191180
/// %3 = alloc_stack $P
192181
/// %4 = init_existential_addr %3 : $*P, $T
193-
/// copy_addr %0 to [initialization] %4 : $*T
194-
/// destroy_addr %0 : $*T
182+
/// copy_addr [take] %0 to [initialization] %4 : $*T
195183
/// %7 = open_existential_addr immutable_access %3 : $*P to
196184
/// $*@opened P
197185
auto *ASI =
198186
NewFBuilder.createAllocStack(InsertLoc, ArgDesc.Arg->getType());
199-
ArgToAllocStackMap.insert(
200-
std::pair<int, AllocStackInst *>(ArgDesc.Index, ASI));
187+
AllocStackInsts.push_back(ASI);
201188

202189
auto *EAI = NewFBuilder.createInitExistentialAddr(
203190
InsertLoc, ASI, NewArg->getType().getASTType(), NewArg->getType(),
204191
Conformances);
205-
/// If DestroyAddr is already there, then do not use [take].
206-
NewFBuilder.createCopyAddr(
207-
InsertLoc, NewArg, EAI,
208-
ExistentialArgDescriptor[ArgDesc.Index].AccessType ==
209-
OpenedExistentialAccess::Immutable
210-
? IsTake_t::IsNotTake
211-
: IsTake_t::IsTake,
212-
IsInitialization_t::IsInitialization);
213-
if (ExistentialArgDescriptor[ArgDesc.Index].DestroyAddrUse) {
214-
NewFBuilder.createDestroyAddr(InsertLoc, NewArg);
215-
}
192+
193+
bool origConsumed = ExistentialArgDescriptor[ArgDesc.Index].isConsumed;
194+
// If the existential is not consumed in the function body, then the one
195+
// we introduce here needs cleanup.
196+
if (!origConsumed)
197+
CleanupValues.push_back(ASI);
198+
199+
NewFBuilder.createCopyAddr(InsertLoc, NewArg, EAI,
200+
origConsumed ? IsTake_t::IsTake
201+
: IsTake_t::IsNotTake,
202+
IsInitialization_t::IsInitialization);
216203
entryArgs.push_back(ASI);
217204
break;
218205
}
219206
case ExistentialRepresentation::Class: {
207+
// FIXME_ownership: init_existential_ref always takes ownership of the
208+
// incoming reference. If the argument convention is borrowed
209+
// (!isConsumed), then we should create a copy_value here and add this new
210+
// existential to the CleanupValues vector.
211+
220212
/// Simple case: Create an init_existential.
221213
/// %5 = init_existential_ref %0 : $T : $T, $P
222214
auto *InitRef = NewFBuilder.createInitExistentialRef(
223215
InsertLoc, ArgDesc.Arg->getType(), NewArg->getType().getASTType(),
224216
NewArg, Conformances);
217+
225218
entryArgs.push_back(InitRef);
226219
break;
227220
}
@@ -399,12 +392,16 @@ void ExistentialTransform::populateThunkBody() {
399392
/// Determine arguments to Apply.
400393
/// Generate opened existentials for generics.
401394
SmallVector<SILValue, 8> ApplyArgs;
395+
// Maintain a list of arg values to be destroyed. These are consumed by the
396+
// convention and require a copy.
397+
SmallVector<CopyAddrInst *, 8> TempCopyAddrInsts;
402398
SmallDenseMap<GenericTypeParamType *, Type> GenericToOpenedTypeMap;
403399
for (auto &ArgDesc : ArgumentDescList) {
404400
auto iter = ArgToGenericTypeMap.find(ArgDesc.Index);
405401
auto it = ExistentialArgDescriptor.find(ArgDesc.Index);
406402
if (iter != ArgToGenericTypeMap.end() &&
407403
it != ExistentialArgDescriptor.end()) {
404+
ExistentialTransformArgumentDescriptor &ETAD = it->second;
408405
OpenedArchetypeType *Opened;
409406
auto OrigOperand = ThunkBody->getArgument(ArgDesc.Index);
410407
auto SwiftType = ArgDesc.Arg->getType().getASTType();
@@ -418,11 +415,26 @@ void ExistentialTransform::populateThunkBody() {
418415
case ExistentialRepresentation::Opaque: {
419416
archetypeValue = Builder.createOpenExistentialAddr(
420417
Loc, OrigOperand, OpenedSILType, it->second.AccessType);
421-
ApplyArgs.push_back(archetypeValue);
418+
SILValue calleeArg = archetypeValue;
419+
if (ETAD.isConsumed) {
420+
// open_existential_addr projects a borrowed address into the
421+
// existential box. Since the callee consumes the generic value, we
422+
// must pass in a copy.
423+
auto *ASI =
424+
Builder.createAllocStack(Loc, OpenedSILType);
425+
auto *CAI =
426+
Builder.createCopyAddr(Loc, archetypeValue, ASI, IsNotTake,
427+
IsInitialization_t::IsInitialization);
428+
TempCopyAddrInsts.push_back(CAI);
429+
calleeArg = ASI;
430+
}
431+
ApplyArgs.push_back(calleeArg);
422432
break;
423433
}
424434
case ExistentialRepresentation::Class: {
425435
/// If the operand is not object type, we would need an explicit load.
436+
// OpenExistentialRef forwards ownership, so it does the right thing
437+
// regardless of whether the argument is borrowed or consumed.
426438
assert(OrigOperand->getType().isObject());
427439
archetypeValue =
428440
Builder.createOpenExistentialRef(Loc, OrigOperand, OpenedSILType);
@@ -498,7 +510,24 @@ void ExistentialTransform::populateThunkBody() {
498510
/// Create the Apply with substitutions
499511
ReturnValue = Builder.createApply(Loc, FRI, SubMap, ApplyArgs);
500512
}
501-
513+
auto cleanupLoc = RegularLocation::getAutoGeneratedLocation();
514+
for (CopyAddrInst *CAI : reversed(TempCopyAddrInsts)) {
515+
// The original argument was copied into a temporary and consumed by the
516+
// callee as such:
517+
// bb (%consumedExistential : $*Protocol)
518+
// %valAdr = open_existential_addr %consumedExistential
519+
// %temp = alloc_stack $T
520+
// copy_addr %valAdr to %temp // <== Temp CopyAddr
521+
// apply(%temp) // <== Temp is consumed by the apply
522+
//
523+
// Destroy the original arument and deallocation the temporary:
524+
// destroy_addr %consumedExistential : $*Protocol
525+
// dealloc_stack %temp : $*T
526+
auto *consumedExistential = cast<SILFunctionArgument>(
527+
cast<OpenExistentialAddrInst>(CAI->getSrc())->getOperand());
528+
Builder.createDestroyAddr(cleanupLoc, consumedExistential);
529+
Builder.createDeallocStack(cleanupLoc, CAI->getDest());
530+
}
502531
/// Set up the return results.
503532
if (NewF->isNoReturnFunction()) {
504533
Builder.createUnreachable(Loc);

lib/SILOptimizer/FunctionSignatureTransforms/ExistentialTransform.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ namespace swift {
3333
/// to transformation.
3434
struct ExistentialTransformArgumentDescriptor {
3535
OpenedExistentialAccess AccessType;
36-
bool DestroyAddrUse;
36+
bool isConsumed;
3737
};
3838

3939
/// ExistentialTransform creates a protocol constrained generic and a thunk.

0 commit comments

Comments
 (0)