Skip to content

Commit 4325c0a

Browse files
authored
Merge pull request #66314 from atrick/fix-dropdeinit
[move-only] Fix SIL representation and SILOptimizer to preserve value deinits
2 parents 2ab4d7b + 46dbf8e commit 4325c0a

32 files changed

+615
-325
lines changed

include/swift/SIL/InstructionUtils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ SILValue stripCastsWithoutMarkDependence(SILValue V);
4040
/// begin_borrow instructions.
4141
SILValue lookThroughOwnershipInsts(SILValue v);
4242

43+
/// Reverse of lookThroughOwnershipInsts.
44+
///
45+
/// Return true if \p visitor returned true for all uses.
46+
bool visitNonOwnershipUses(SILValue value,
47+
function_ref<bool(Operand *)> visitor);
48+
4349
/// Return the underlying SILValue after looking through all copy_value
4450
/// instructions.
4551
SILValue lookThroughCopyValueInsts(SILValue v);

include/swift/SIL/SILMoveOnlyDeinit.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,10 @@ class SILMoveOnlyDeinit final : public SILAllocated<SILMoveOnlyDeinit> {
6161

6262
NominalTypeDecl *getNominalDecl() const { return nominalDecl; }
6363

64-
SILFunction *getImplementation() const { return funcImpl; }
64+
SILFunction *getImplementation() const {
65+
assert(funcImpl);
66+
return funcImpl;
67+
}
6568

6669
IsSerialized_t isSerialized() const {
6770
return serialized ? IsSerialized : IsNotSerialized;

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,7 @@ PASS(PartialApplySimplification, "partial-apply-simplification",
464464
"Transform partial_apply instructions into explicit closure box constructions")
465465
PASS(MovedAsyncVarDebugInfoPropagator, "sil-moved-async-var-dbginfo-propagator",
466466
"Propagate debug info from moved async vars after coroutine funclet boundaries")
467-
PASS(MoveOnlyDeinitInsertion, "sil-move-only-deinit-insertion",
467+
PASS(MoveOnlyDeinitDevirtualization, "sil-move-only-deinit-devirtualization",
468468
"After running move only checking, convert last destroy_values to deinit calls")
469469
PASS(MoveOnlyBorrowToDestructureTransform,
470470
"sil-move-only-borrow-to-destructure",

include/swift/SILOptimizer/Utils/InstOptUtils.h

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,16 @@ void collectUsesOfValue(SILValue V,
141141
/// value itself)
142142
void eraseUsesOfValue(SILValue value);
143143

144+
/// Return true if \p type is a value type (struct/enum) that requires
145+
/// deinitialization beyond destruction of its members.
146+
bool hasValueDeinit(SILType type);
147+
148+
/// Return true if \p value has a value type (struct/enum) that requires
149+
/// deinitialization beyond destruction of its members.
150+
inline bool hasValueDeinit(SILValue value) {
151+
return hasValueDeinit(value->getType());
152+
}
153+
144154
/// Gets the concrete value which is stored in an existential box.
145155
/// Returns %value in following pattern:
146156
///
@@ -381,8 +391,10 @@ ignore_expect_uses(ValueBase *value) {
381391
/// operations from it. These can be simplified and removed.
382392
bool simplifyUsers(SingleValueInstruction *inst);
383393

384-
/// True if a type can be expanded
385-
/// without a significant increase to code size.
394+
/// True if a type can be expanded without a significant increase to code size.
395+
///
396+
/// False if expanding a type is invalid. For example, expanding a
397+
/// struct-with-deinit drops the deinit.
386398
bool shouldExpand(SILModule &module, SILType ty);
387399

388400
/// Check if the value of value is computed by means of a simple initialization.

lib/IRGen/IRGenSIL.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1250,8 +1250,7 @@ class IRGenSILFunction :
12501250
setLoweredExplosion(i, e);
12511251
}
12521252
void visitDropDeinitInst(DropDeinitInst *i) {
1253-
auto e = getLoweredExplosion(i->getOperand());
1254-
setLoweredExplosion(i, e);
1253+
llvm_unreachable("only valid in ownership SIL");
12551254
}
12561255
void visitMarkMustCheckInst(MarkMustCheckInst *i) {
12571256
llvm_unreachable("Invalid in Lowered SIL");

lib/SIL/IR/TypeLowering.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1581,6 +1581,19 @@ namespace {
15811581
return B.createStruct(loc, getLoweredType(), values);
15821582
}
15831583

1584+
void
1585+
emitLoweredDestroyValue(SILBuilder &B, SILLocation loc, SILValue aggValue,
1586+
TypeExpansionKind loweringStyle) const override {
1587+
// A value type with a deinit cannot be memberwise destroyed.
1588+
if (auto *nominal = getLoweredType().getNominalOrBoundGenericNominal()) {
1589+
if (nominal->getValueTypeDestructor()) {
1590+
emitDestroyValue(B, loc, aggValue);
1591+
return;
1592+
}
1593+
}
1594+
Super::emitLoweredDestroyValue(B, loc, aggValue, loweringStyle);
1595+
}
1596+
15841597
private:
15851598
void lowerChildren(TypeConverter &TC,
15861599
SmallVectorImpl<Child> &children) const override {

lib/SIL/Utils/InstructionUtils.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,30 @@ SILValue swift::lookThroughOwnershipInsts(SILValue v) {
3939
}
4040
}
4141

42+
bool swift::visitNonOwnershipUses(SILValue value,
43+
function_ref<bool(Operand *)> visitor) {
44+
// All ownership insts have a single operand, so a recursive walk is
45+
// sufficient and cannot revisit operands.
46+
for (Operand *use : value->getUses()) {
47+
auto *user = use->getUser();
48+
switch (user->getKind()) {
49+
default:
50+
if (!visitor(use))
51+
return false;
52+
53+
break;
54+
case SILInstructionKind::MoveValueInst:
55+
case SILInstructionKind::CopyValueInst:
56+
case SILInstructionKind::BeginBorrowInst:
57+
if (!visitNonOwnershipUses(cast<SingleValueInstruction>(user), visitor))
58+
return false;
59+
60+
break;
61+
}
62+
}
63+
return true;
64+
}
65+
4266
SILValue swift::lookThroughCopyValueInsts(SILValue val) {
4367
while (auto *cvi =
4468
dyn_cast_or_null<CopyValueInst>(val->getDefiningInstruction())) {

lib/SIL/Verifier/SILVerifier.cpp

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3556,6 +3556,13 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
35563556
"destructure with none ownership kind operand and non-none "
35573557
"ownership kind result?!");
35583558
}
3559+
if (operandTy.getNominalOrBoundGenericNominal()
3560+
->getValueTypeDestructor()) {
3561+
require(
3562+
isa<DropDeinitInst>(lookThroughOwnershipInsts(DSI->getOperand())),
3563+
"a destructure of a move-only-type-with-deinit requires a "
3564+
"drop_deinit");
3565+
}
35593566
}
35603567
}
35613568

@@ -5978,12 +5985,40 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
59785985
"Result and operand must have the same type, today.");
59795986
}
59805987

5988+
// check that a drop_deinit can only ever be destroyed or destructured
5989+
void checkDropDeinitUses(DropDeinitInst *ddi) {
5990+
// Address-type drop_deinit has no special structural requirements. It just
5991+
// sits there and blocks optimization on the allocation and downstream uses
5992+
// of the address. If we want to optimize around address-type drop_deinit,
5993+
// then we need a seperate verifier for its requirements.
5994+
if (ddi->getType().isAddress())
5995+
return;
5996+
5997+
visitNonOwnershipUses(ddi, [&](Operand *use) {
5998+
auto *user = use->getUser();
5999+
require(isa<DestroyValueInst>(user)
6000+
|| isa<EndLifetimeInst>(user)
6001+
|| isa<DestructureStructInst>(user)
6002+
|| isa<SwitchEnumInst>(user),
6003+
"A drop_deinit can only be destroyed or destructured");
6004+
return true;
6005+
});
6006+
}
6007+
59816008
void checkDropDeinitInst(DropDeinitInst *ddi) {
5982-
require(ddi->getType() == ddi->getOperand()->getType(),
6009+
require(F.hasOwnership(), "drop_deinit only allowed in OSSA");
6010+
6011+
auto type = ddi->getType();
6012+
require(type == ddi->getOperand()->getType(),
59836013
"Result and operand must have the same type.");
5984-
require(ddi->getType().isMoveOnlyNominalType(),
6014+
require(type.isMoveOnlyNominalType(),
59856015
"drop_deinit only allowed for move-only types");
5986-
require(F.hasOwnership(), "drop_deinit only allowed in OSSA");
6016+
require(type.getNominalOrBoundGenericNominal()
6017+
->getValueTypeDestructor(), "drop_deinit only allowed for "
6018+
"struct/enum types that define a deinit");
6019+
assert(!type.isTrivial(F) && "a type with a deinit is nontrivial");
6020+
6021+
checkDropDeinitUses(ddi);
59876022
}
59886023

59896024
void checkMarkMustCheckInst(MarkMustCheckInst *i) {

lib/SILGen/SILGenDestructor.cpp

Lines changed: 45 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ void SILGenFunction::emitDeallocatingMoveOnlyDestructor(DestructorDecl *dd) {
262262
// Clean up our members, consuming our +1 self value as we do it.
263263
emitMoveOnlyMemberDestruction(selfValue,
264264
dd->getDeclContext()->getSelfNominalTypeDecl(),
265-
cleanupLoc, nullptr);
265+
cleanupLoc);
266266

267267
// Return.
268268
B.createReturn(loc, emitEmptyTuple(loc));
@@ -497,111 +497,59 @@ void SILGenFunction::emitClassMemberDestruction(ManagedValue selfValue,
497497

498498
void SILGenFunction::emitMoveOnlyMemberDestruction(SILValue selfValue,
499499
NominalTypeDecl *nom,
500-
CleanupLocation cleanupLoc,
501-
SILBasicBlock *finishBB) {
500+
CleanupLocation cleanupLoc) {
501+
// drop_deinit invalidates any user-defined struct/enum deinit
502+
// before the individual members are destroyed.
502503
selfValue = B.createDropDeinit(cleanupLoc, selfValue);
503-
if (selfValue->getType().isAddress()) {
504-
if (auto *structDecl = dyn_cast<StructDecl>(nom)) {
505-
for (VarDecl *vd : nom->getStoredProperties()) {
506-
const TypeLowering &ti = getTypeLowering(vd->getType());
507-
if (ti.isTrivial())
508-
continue;
509-
510-
SILValue addr = B.createStructElementAddr(
511-
cleanupLoc, selfValue, vd, ti.getLoweredType().getAddressType());
512-
addr = B.createBeginAccess(cleanupLoc, addr, SILAccessKind::Deinit,
513-
SILAccessEnforcement::Static,
514-
false /*noNestedConflict*/,
515-
false /*fromBuiltin*/);
516-
B.createDestroyAddr(cleanupLoc, addr);
517-
B.createEndAccess(cleanupLoc, addr, false /*is aborting*/);
518-
}
519-
} else {
520-
auto *origBlock = B.getInsertionBB();
521-
auto *enumDecl = cast<EnumDecl>(nom);
522-
SmallVector<std::pair<EnumElementDecl *, SILBasicBlock *>, 8>
523-
caseCleanups;
524-
auto *contBlock = createBasicBlock();
525-
526-
for (auto *enumElt : enumDecl->getAllElements()) {
527-
auto *enumBlock = createBasicBlock();
528-
SILBuilder builder(enumBlock, enumBlock->begin());
529-
530-
if (enumElt->hasAssociatedValues()) {
531-
auto *take = builder.createUncheckedTakeEnumDataAddr(
532-
cleanupLoc, selfValue, enumElt);
533-
builder.createDestroyAddr(cleanupLoc, take);
534-
}
535-
536-
// Branch to the continue trampoline block.
537-
builder.createBranch(cleanupLoc, contBlock);
538-
caseCleanups.emplace_back(enumElt, enumBlock);
539-
540-
// Set the insertion point to after this enum block so we insert the
541-
// next new block after this block.
542-
B.setInsertionPoint(enumBlock);
543-
}
504+
if (selfValue->getType().isObject()) {
505+
// A destroy value that uses the result of a drop_deinit implicitly performs
506+
// memberwise destruction.
507+
B.emitDestroyValueOperation(cleanupLoc, selfValue);
508+
return;
509+
}
510+
if (auto *structDecl = dyn_cast<StructDecl>(nom)) {
511+
for (VarDecl *vd : nom->getStoredProperties()) {
512+
const TypeLowering &ti = getTypeLowering(vd->getType());
513+
if (ti.isTrivial())
514+
continue;
544515

545-
B.setInsertionPoint(origBlock);
546-
B.createSwitchEnumAddr(cleanupLoc, selfValue, nullptr, caseCleanups);
547-
B.setInsertionPoint(contBlock);
516+
SILValue addr = B.createStructElementAddr(
517+
cleanupLoc, selfValue, vd, ti.getLoweredType().getAddressType());
518+
addr = B.createBeginAccess(
519+
cleanupLoc, addr, SILAccessKind::Deinit, SILAccessEnforcement::Static,
520+
false /*noNestedConflict*/, false /*fromBuiltin*/);
521+
B.createDestroyAddr(cleanupLoc, addr);
522+
B.createEndAccess(cleanupLoc, addr, false /*is aborting*/);
548523
}
549524
} else {
550-
if (auto *sd = dyn_cast<StructDecl>(nom)) {
551-
if (llvm::any_of(sd->getStoredProperties(),
552-
[&](VarDecl *vd) {
553-
auto &lowering = getTypeLowering(vd->getType());
554-
return !lowering.isTrivial();
555-
})) {
556-
auto *d = B.createDestructureStruct(cleanupLoc, selfValue,
557-
OwnershipKind::Owned);
558-
for (auto result : d->getResults()) {
559-
B.emitDestroyValueOperation(cleanupLoc, result);
560-
}
561-
} else {
562-
// If we only contain trivial uses, we don't have anything to cleanup,
563-
// so just insert an end_lifetime.
564-
B.createEndLifetime(cleanupLoc, selfValue);
525+
auto *origBlock = B.getInsertionBB();
526+
auto *enumDecl = cast<EnumDecl>(nom);
527+
SmallVector<std::pair<EnumElementDecl *, SILBasicBlock *>, 8> caseCleanups;
528+
auto *contBlock = createBasicBlock();
529+
530+
for (auto *enumElt : enumDecl->getAllElements()) {
531+
auto *enumBlock = createBasicBlock();
532+
SILBuilder builder(enumBlock, enumBlock->begin());
533+
534+
if (enumElt->hasAssociatedValues()) {
535+
auto *take = builder.createUncheckedTakeEnumDataAddr(
536+
cleanupLoc, selfValue, enumElt);
537+
builder.createDestroyAddr(cleanupLoc, take);
565538
}
566-
} else {
567-
auto *origBlock = B.getInsertionBB();
568-
auto *enumDecl = dyn_cast<EnumDecl>(nom);
569-
SmallVector<std::pair<EnumElementDecl *, SILBasicBlock *>, 8>
570-
caseCleanups;
571-
auto *contBlock = createBasicBlock();
572-
573-
for (auto *enumElt : enumDecl->getAllElements()) {
574-
auto *enumBlock = createBasicBlock();
575-
SILBuilder builder(enumBlock, enumBlock->begin());
576-
577-
if (enumElt->hasAssociatedValues()) {
578-
auto caseType = selfValue->getType().getEnumElementType(
579-
enumElt, enumBlock->getParent());
580-
auto *phiArg =
581-
enumBlock->createPhiArgument(caseType, OwnershipKind::Owned);
582-
builder.emitDestroyValueOperation(cleanupLoc, phiArg);
583-
}
584539

585-
// Branch to the continue trampoline block.
586-
builder.createBranch(cleanupLoc, contBlock);
587-
caseCleanups.emplace_back(enumElt, enumBlock);
540+
// Branch to the continue trampoline block.
541+
builder.createBranch(cleanupLoc, contBlock);
542+
caseCleanups.emplace_back(enumElt, enumBlock);
588543

589-
// Set the insertion point to after this enum block so we insert the
590-
// next new block after this block.
591-
B.setInsertionPoint(enumBlock);
592-
}
593-
594-
B.setInsertionPoint(origBlock);
595-
B.createSwitchEnum(cleanupLoc, selfValue, nullptr, caseCleanups);
596-
B.setInsertionPoint(contBlock);
544+
// Set the insertion point to after this enum block so we insert the
545+
// next new block after this block.
546+
B.setInsertionPoint(enumBlock);
597547
}
598-
}
599548

600-
if (finishBB)
601-
B.createBranch(cleanupLoc, finishBB);
602-
603-
if (finishBB)
604-
B.emitBlock(finishBB);
549+
B.setInsertionPoint(origBlock);
550+
B.createSwitchEnumAddr(cleanupLoc, selfValue, nullptr, caseCleanups);
551+
B.setInsertionPoint(contBlock);
552+
}
605553
}
606554

607555
void SILGenFunction::emitObjCDestructor(SILDeclRef dtor) {

lib/SILGen/SILGenFunction.h

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -815,12 +815,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
815815
///
816816
/// \param selfValue The 'self' value.
817817
/// \param nd The nominal declaration whose members are being destroyed.
818-
/// \param finishBB If set, used as the basic block after members have been
819-
/// destroyed, and we're ready to perform final cleanups
820-
/// before returning.
821818
void emitMoveOnlyMemberDestruction(SILValue selfValue, NominalTypeDecl *nd,
822-
CleanupLocation cleanupLoc,
823-
SILBasicBlock *finishBB);
819+
CleanupLocation cleanupLoc);
824820

825821
/// Generates code to destroy linearly recursive data structures, without
826822
/// building up the call stack.

lib/SILGen/SILGenStmt.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -795,8 +795,7 @@ void StmtEmitter::visitDiscardStmt(DiscardStmt *S) {
795795
}
796796
}
797797

798-
SGF.emitMoveOnlyMemberDestruction(selfValue.forward(SGF), nominal, loc,
799-
nullptr);
798+
SGF.emitMoveOnlyMemberDestruction(selfValue.forward(SGF), nominal, loc);
800799
}
801800

802801
void StmtEmitter::visitYieldStmt(YieldStmt *S) {

0 commit comments

Comments
 (0)