Skip to content

Commit bd90e31

Browse files
authored
Merge pull request #66952 from gottesmm/pr-87fcc78991b465d8a15454dc76b66d6f347ddff0
[move-only] Emit an error if we /ever/ partially consume a noncopyable type.
2 parents e1ab0eb + 3dde9df commit bd90e31

26 files changed

+1236
-175
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -785,6 +785,9 @@ ERROR(sil_movechecking_notconsumable_but_assignable_was_consumed, none,
785785
ERROR(sil_movechecking_cannot_destructure_has_deinit, none,
786786
"cannot partially consume '%0' when it has a deinitializer",
787787
(StringRef))
788+
ERROR(sil_movechecking_cannot_destructure, none,
789+
"cannot partially consume '%0'",
790+
(StringRef))
788791
ERROR(sil_movechecking_discard_missing_consume_self, none,
789792
"must consume 'self' before exiting method that discards self", ())
790793
ERROR(sil_movechecking_reinit_after_discard, none,

include/swift/Basic/Features.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ EXPERIMENTAL_FEATURE(OldOwnershipOperatorSpellings, true)
135135
EXPERIMENTAL_FEATURE(MoveOnlyEnumDeinits, true)
136136
EXPERIMENTAL_FEATURE(MoveOnlyTuples, true)
137137
EXPERIMENTAL_FEATURE(MoveOnlyResilientTypes, true)
138+
EXPERIMENTAL_FEATURE(MoveOnlyPartialConsumption, true)
138139

139140
EXPERIMENTAL_FEATURE(OneWayClosureParameters, false)
140141
EXPERIMENTAL_FEATURE(TypeWitnessSystemInference, false)

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,8 @@ PASS(MoveOnlyAddressChecker, "sil-move-only-address-checker",
448448
"Utility pass that enforces move only invariants on raw SIL for addresses for testing purposes")
449449
PASS(MoveOnlyChecker, "sil-move-only-checker",
450450
"Pass that enforces move only invariants on raw SIL for addresses and objects")
451+
PASS(MoveOnlyTempAllocationFromLetTester, "sil-move-only-temp-allocation-from-let-tester",
452+
"Pass that allows us to run separately SIL test cases for the eliminateTemporaryAllocationsFromLet utility")
451453
PASS(ConsumeOperatorCopyableValuesChecker, "sil-consume-operator-copyable-values-checker",
452454
"Pass that performs checking of the consume operator for copyable values")
453455
PASS(TrivialMoveOnlyTypeEliminator, "sil-trivial-move-only-type-eliminator",

lib/AST/ASTPrinter.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3310,6 +3310,11 @@ static bool usesFeatureMoveOnlyResilientTypes(Decl *decl) {
33103310
return false;
33113311
}
33123312

3313+
static bool usesFeatureMoveOnlyPartialConsumption(Decl *decl) {
3314+
// Partial consumption does not affect declarations directly.
3315+
return false;
3316+
}
3317+
33133318
static bool usesFeatureOneWayClosureParameters(Decl *decl) {
33143319
return false;
33153320
}

lib/SILOptimizer/Mandatory/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ target_sources(swiftSILOptimizer PRIVATE
3131
MoveOnlyDiagnostics.cpp
3232
MoveOnlyObjectCheckerTester.cpp
3333
MoveOnlyObjectCheckerUtils.cpp
34+
MoveOnlyTempAllocationFromLetTester.cpp
3435
MoveOnlyTypeUtils.cpp
3536
MoveOnlyUtils.cpp
3637
MovedAsyncVarDebugInfoPropagator.cpp

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 80 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -287,108 +287,9 @@ llvm::cl::opt<bool> DisableMoveOnlyAddressCheckerLifetimeExtension(
287287
"move-only values."));
288288

289289
//===----------------------------------------------------------------------===//
290-
// MARK: Memory Utilities
290+
// MARK: Utilities
291291
//===----------------------------------------------------------------------===//
292292

293-
static bool memInstMustInitialize(Operand *memOper) {
294-
SILValue address = memOper->get();
295-
296-
SILInstruction *memInst = memOper->getUser();
297-
298-
switch (memInst->getKind()) {
299-
default:
300-
return false;
301-
302-
case SILInstructionKind::CopyAddrInst: {
303-
auto *CAI = cast<CopyAddrInst>(memInst);
304-
return CAI->getDest() == address && CAI->isInitializationOfDest();
305-
}
306-
case SILInstructionKind::ExplicitCopyAddrInst: {
307-
auto *CAI = cast<ExplicitCopyAddrInst>(memInst);
308-
return CAI->getDest() == address && CAI->isInitializationOfDest();
309-
}
310-
case SILInstructionKind::MarkUnresolvedMoveAddrInst: {
311-
return cast<MarkUnresolvedMoveAddrInst>(memInst)->getDest() == address;
312-
}
313-
case SILInstructionKind::InitExistentialAddrInst:
314-
case SILInstructionKind::InitEnumDataAddrInst:
315-
case SILInstructionKind::InjectEnumAddrInst:
316-
return true;
317-
318-
case SILInstructionKind::BeginApplyInst:
319-
case SILInstructionKind::TryApplyInst:
320-
case SILInstructionKind::ApplyInst: {
321-
FullApplySite applySite(memInst);
322-
return applySite.isIndirectResultOperand(*memOper);
323-
}
324-
case SILInstructionKind::StoreInst: {
325-
auto qual = cast<StoreInst>(memInst)->getOwnershipQualifier();
326-
return qual == StoreOwnershipQualifier::Init ||
327-
qual == StoreOwnershipQualifier::Trivial;
328-
}
329-
330-
#define NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \
331-
case SILInstructionKind::Store##Name##Inst: \
332-
return cast<Store##Name##Inst>(memInst)->isInitializationOfDest();
333-
#include "swift/AST/ReferenceStorage.def"
334-
}
335-
}
336-
337-
static bool memInstMustReinitialize(Operand *memOper) {
338-
SILValue address = memOper->get();
339-
340-
SILInstruction *memInst = memOper->getUser();
341-
342-
switch (memInst->getKind()) {
343-
default:
344-
return false;
345-
346-
case SILInstructionKind::CopyAddrInst: {
347-
auto *CAI = cast<CopyAddrInst>(memInst);
348-
return CAI->getDest() == address && !CAI->isInitializationOfDest();
349-
}
350-
case SILInstructionKind::ExplicitCopyAddrInst: {
351-
auto *CAI = cast<ExplicitCopyAddrInst>(memInst);
352-
return CAI->getDest() == address && !CAI->isInitializationOfDest();
353-
}
354-
case SILInstructionKind::YieldInst: {
355-
auto *yield = cast<YieldInst>(memInst);
356-
return yield->getYieldInfoForOperand(*memOper).isIndirectInOut();
357-
}
358-
case SILInstructionKind::BeginApplyInst:
359-
case SILInstructionKind::TryApplyInst:
360-
case SILInstructionKind::ApplyInst: {
361-
FullApplySite applySite(memInst);
362-
return applySite.getArgumentOperandConvention(*memOper).isInoutConvention();
363-
}
364-
case SILInstructionKind::StoreInst:
365-
return cast<StoreInst>(memInst)->getOwnershipQualifier() ==
366-
StoreOwnershipQualifier::Assign;
367-
368-
#define NEVER_OR_SOMETIMES_LOADABLE_CHECKED_REF_STORAGE(Name, ...) \
369-
case SILInstructionKind::Store##Name##Inst: \
370-
return !cast<Store##Name##Inst>(memInst)->isInitializationOfDest();
371-
#include "swift/AST/ReferenceStorage.def"
372-
}
373-
}
374-
375-
/// Is this a reinit instruction that we know how to convert into its init form.
376-
static bool isReinitToInitConvertibleInst(SILInstruction *memInst) {
377-
switch (memInst->getKind()) {
378-
default:
379-
return false;
380-
381-
case SILInstructionKind::CopyAddrInst: {
382-
auto *cai = cast<CopyAddrInst>(memInst);
383-
return !cai->isInitializationOfDest();
384-
}
385-
case SILInstructionKind::StoreInst: {
386-
auto *si = cast<StoreInst>(memInst);
387-
return si->getOwnershipQualifier() == StoreOwnershipQualifier::Assign;
388-
}
389-
}
390-
}
391-
392293
static void insertDebugValueBefore(SILInstruction *insertPt,
393294
DebugVarCarryingInst debugVar,
394295
llvm::function_ref<SILValue ()> operand) {
@@ -432,47 +333,20 @@ static void convertMemoryReinitToInitForm(SILInstruction *memInst,
432333
[&]{ return debugVar.getOperandForDebugValueClone(); });
433334
}
434335

435-
static bool memInstMustConsume(Operand *memOper) {
436-
SILValue address = memOper->get();
437-
438-
SILInstruction *memInst = memOper->getUser();
439-
440-
// FIXME: drop_deinit must be handled here!
336+
/// Is this a reinit instruction that we know how to convert into its init form.
337+
static bool isReinitToInitConvertibleInst(SILInstruction *memInst) {
441338
switch (memInst->getKind()) {
442339
default:
443340
return false;
444341

445342
case SILInstructionKind::CopyAddrInst: {
446-
auto *CAI = cast<CopyAddrInst>(memInst);
447-
return (CAI->getSrc() == address && CAI->isTakeOfSrc()) ||
448-
(CAI->getDest() == address && !CAI->isInitializationOfDest());
449-
}
450-
case SILInstructionKind::ExplicitCopyAddrInst: {
451-
auto *CAI = cast<ExplicitCopyAddrInst>(memInst);
452-
return (CAI->getSrc() == address && CAI->isTakeOfSrc()) ||
453-
(CAI->getDest() == address && !CAI->isInitializationOfDest());
454-
}
455-
case SILInstructionKind::BeginApplyInst:
456-
case SILInstructionKind::TryApplyInst:
457-
case SILInstructionKind::ApplyInst: {
458-
FullApplySite applySite(memInst);
459-
return applySite.getArgumentOperandConvention(*memOper).isOwnedConvention();
460-
}
461-
case SILInstructionKind::PartialApplyInst: {
462-
// If we are on the stack or have an inout convention, we do not
463-
// consume. Otherwise, we do.
464-
auto *pai = cast<PartialApplyInst>(memInst);
465-
if (pai->isOnStack())
466-
return false;
467-
ApplySite applySite(pai);
468-
auto convention = applySite.getArgumentConvention(*memOper);
469-
return !convention.isInoutConvention();
343+
auto *cai = cast<CopyAddrInst>(memInst);
344+
return !cai->isInitializationOfDest();
345+
}
346+
case SILInstructionKind::StoreInst: {
347+
auto *si = cast<StoreInst>(memInst);
348+
return si->getOwnershipQualifier() == StoreOwnershipQualifier::Assign;
470349
}
471-
case SILInstructionKind::DestroyAddrInst:
472-
return true;
473-
case SILInstructionKind::LoadInst:
474-
return cast<LoadInst>(memInst)->getOwnershipQualifier() ==
475-
LoadOwnershipQualifier::Take;
476350
}
477351
}
478352

@@ -1606,10 +1480,12 @@ struct CopiedLoadBorrowEliminationVisitor final
16061480
// MARK: DestructureThroughDeinit Checking
16071481
//===----------------------------------------------------------------------===//
16081482

1609-
static void
1610-
checkForDestructureThroughDeinit(MarkMustCheckInst *rootAddress, Operand *use,
1611-
TypeTreeLeafTypeRange usedBits,
1612-
DiagnosticEmitter &diagnosticEmitter) {
1483+
/// When partial consumption is enabled, we only allow for destructure through
1484+
/// deinits. When partial consumption is disabled, we error on /all/ partial
1485+
/// consumption.
1486+
static void checkForDestructure(MarkMustCheckInst *rootAddress, Operand *use,
1487+
TypeTreeLeafTypeRange usedBits,
1488+
DiagnosticEmitter &diagnosticEmitter) {
16131489
LLVM_DEBUG(llvm::dbgs() << " DestructureNeedingUse: " << *use->getUser());
16141490

16151491
SILFunction *fn = rootAddress->getFunction();
@@ -1627,6 +1503,29 @@ checkForDestructureThroughDeinit(MarkMustCheckInst *rootAddress, Operand *use,
16271503
if (iterType.isMoveOnlyWrapped())
16281504
return;
16291505

1506+
// If we are not allowing for any partial consumption, just emit an error
1507+
// immediately.
1508+
if (!rootAddress->getModule().getASTContext().LangOpts.hasFeature(
1509+
Feature::MoveOnlyPartialConsumption)) {
1510+
// If the types equal, just bail early.
1511+
if (iterType == targetType)
1512+
return;
1513+
1514+
// Otherwise, build up the path string and emit the error.
1515+
SmallString<128> pathString;
1516+
auto rootType = rootAddress->getType();
1517+
if (iterType != rootType) {
1518+
llvm::raw_svector_ostream os(pathString);
1519+
pair.constructPathString(iterType, {rootType, fn}, rootType, fn, os);
1520+
}
1521+
1522+
diagnosticEmitter.emitCannotDestructureNominalError(
1523+
rootAddress, pathString, nullptr /*nominal*/, use->getUser(),
1524+
false /*is for deinit error*/);
1525+
return;
1526+
}
1527+
1528+
// Otherwise, walk the type looking for the deinit.
16301529
while (iterType != targetType) {
16311530
// If we have a nominal type as our parent type, see if it has a
16321531
// deinit. We know that it must be non-copyable since copyable types
@@ -1645,8 +1544,9 @@ checkForDestructureThroughDeinit(MarkMustCheckInst *rootAddress, Operand *use,
16451544
pair.constructPathString(iterType, {rootType, fn}, rootType, fn, os);
16461545
}
16471546

1648-
diagnosticEmitter.emitCannotDestructureDeinitNominalError(
1649-
rootAddress, pathString, nom, use->getUser());
1547+
diagnosticEmitter.emitCannotDestructureNominalError(
1548+
rootAddress, pathString, nom, use->getUser(),
1549+
true /*is for deinit error*/);
16501550
break;
16511551
}
16521552
}
@@ -1753,7 +1653,7 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
17531653
LLVM_DEBUG(llvm::dbgs() << "Visiting user: " << *user;);
17541654

17551655
// First check if we have init/reinit. These are quick/simple.
1756-
if (::memInstMustInitialize(op)) {
1656+
if (noncopyable::memInstMustInitialize(op)) {
17571657
LLVM_DEBUG(llvm::dbgs() << "Found init: " << *user);
17581658

17591659
// TODO: What about copy_addr of itself. We really should just pre-process
@@ -1766,7 +1666,7 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
17661666
return true;
17671667
}
17681668

1769-
if (::memInstMustReinitialize(op)) {
1669+
if (noncopyable::memInstMustReinitialize(op)) {
17701670
LLVM_DEBUG(llvm::dbgs() << "Found reinit: " << *user);
17711671
auto leafRange = TypeTreeLeafTypeRange::get(op->get(), getRootAddress());
17721672
if (!leafRange)
@@ -1854,8 +1754,10 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
18541754

18551755
// TODO: Add borrow checking here like below.
18561756

1857-
// TODO: Add destructure deinit checking here once address only checking is
1858-
// completely brought up.
1757+
// If we have a copy_addr, we are either going to have a take or a
1758+
// copy... in either case, this copy_addr /is/ going to be a consuming
1759+
// operation. Make sure to check if we semantically destructure.
1760+
checkForDestructure(markedValue, op, *leafRange, diagnosticEmitter);
18591761

18601762
if (copyAddr->isTakeOfSrc()) {
18611763
LLVM_DEBUG(llvm::dbgs() << "Found take: " << *user);
@@ -1923,17 +1825,6 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
19231825
return false;
19241826
}
19251827

1926-
checkForDestructureThroughDeinit(markedValue, op, *leafRange,
1927-
diagnosticEmitter);
1928-
1929-
// If we emitted an error diagnostic, do not transform further and instead
1930-
// mark that we emitted an early diagnostic and return true.
1931-
if (numDiagnostics != moveChecker.diagnosticEmitter.getDiagnosticCount()) {
1932-
LLVM_DEBUG(llvm::dbgs()
1933-
<< "Emitting destructure through deinit error!\n");
1934-
return true;
1935-
}
1936-
19371828
// Canonicalize the lifetime of the load [take], load [copy].
19381829
LLVM_DEBUG(llvm::dbgs() << "Running copy propagation!\n");
19391830
moveChecker.changed |= moveChecker.canonicalizer.canonicalize();
@@ -2034,6 +1925,19 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
20341925
useState.recordLivenessUse(dvi, *leafRange);
20351926
}
20361927
} else {
1928+
// Now that we know that we are going to perform a take, perform a
1929+
// checkForDestructure.
1930+
checkForDestructure(markedValue, op, *leafRange, diagnosticEmitter);
1931+
1932+
// If we emitted an error diagnostic, do not transform further and instead
1933+
// mark that we emitted an early diagnostic and return true.
1934+
if (numDiagnostics !=
1935+
moveChecker.diagnosticEmitter.getDiagnosticCount()) {
1936+
LLVM_DEBUG(llvm::dbgs()
1937+
<< "Emitting destructure through deinit error!\n");
1938+
return true;
1939+
}
1940+
20371941
// If we had a load [copy], store this into the copy list. These are the
20381942
// things that we must merge into destroy_addr or reinits after we are
20391943
// done checking. The load [take] are already complete and good to go.
@@ -2050,7 +1954,7 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
20501954

20511955
// Now that we have handled or loadTakeOrCopy, we need to now track our
20521956
// additional pure takes.
2053-
if (::memInstMustConsume(op)) {
1957+
if (noncopyable::memInstMustConsume(op)) {
20541958
// If we don't have a consumeable and assignable check kind, then we can't
20551959
// consume. Emit an error.
20561960
//
@@ -2078,8 +1982,7 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
20781982
// error.
20791983
unsigned numDiagnostics =
20801984
moveChecker.diagnosticEmitter.getDiagnosticCount();
2081-
checkForDestructureThroughDeinit(markedValue, op, *leafRange,
2082-
diagnosticEmitter);
1985+
checkForDestructure(markedValue, op, *leafRange, diagnosticEmitter);
20831986
if (numDiagnostics != moveChecker.diagnosticEmitter.getDiagnosticCount()) {
20841987
LLVM_DEBUG(llvm::dbgs()
20851988
<< "Emitting destructure through deinit error!\n");
@@ -3212,6 +3115,24 @@ bool MoveOnlyAddressCheckerPImpl::performSingleCheck(
32123115
state.process();
32133116
}
32143117

3118+
// Then if we have a let allocation, see if we have any copy_addr on our
3119+
// markedAddress that form temporary allocation chains. This occurs when we
3120+
// emit SIL for code like:
3121+
//
3122+
// let x: AddressOnlyType = ...
3123+
// let _ = x.y.z
3124+
//
3125+
// SILGen will treat y as a separate rvalue from x and will create a temporary
3126+
// allocation. In contrast if we have a var, we treat x like an lvalue and
3127+
// just create GEPs appropriately.
3128+
if (eliminateTemporaryAllocationsFromLet(markedAddress)) {
3129+
LLVM_DEBUG(
3130+
llvm::dbgs()
3131+
<< "Succeeded in eliminating temporary allocations! Fn after:\n";
3132+
markedAddress->getFunction()->dump());
3133+
changed = true;
3134+
}
3135+
32153136
// Then gather all uses of our address by walking from def->uses. We use this
32163137
// to categorize the uses of this address into their ownership behavior (e.g.:
32173138
// init, reinit, take, destroy, etc.).

0 commit comments

Comments
 (0)