diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp index c6d2398f371..cd60eac9d7f 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp @@ -1615,28 +1615,18 @@ struct CopiedLoadBorrowEliminationVisitor // MARK: Partial Consume/Reinit Checking //===----------------------------------------------------------------------===// -namespace { - -/// When partial consumption is enabled, we only allow for destructure through -/// deinits. When partial consumption is disabled, we error on /all/ partial -/// consumption. -enum class IsPartialConsumeOrReinit_t { - IsPartialConsume, - IsPartialReinit, -}; - -} // namespace - -static std::pair -shouldEmitPartialError(UseState &useState, SILInstruction *user, - SILType useType, TypeTreeLeafTypeRange usedBits) { +/// Whether an error should be emitted in response to a partial consumption. +static llvm::Optional +shouldEmitPartialMutationError(UseState &useState, SILInstruction *user, + SILType useType, + TypeTreeLeafTypeRange usedBits) { SILFunction *fn = useState.getFunction(); // We walk down from our ancestor to our projection, emitting an error if // any of our types have a deinit. auto iterType = useState.address->getType(); if (iterType.isMoveOnlyWrapped()) - return {SILType(), nullptr}; + return {}; TypeOffsetSizePair pair(usedBits); auto targetType = useType; @@ -1652,13 +1642,15 @@ shouldEmitPartialError(UseState &useState, SILInstruction *user, if (iterType == targetType) { LLVM_DEBUG(llvm::dbgs() << " IterType is TargetType! Exiting early " "without emitting error!\n"); - return {SILType(), nullptr}; + return {}; } // Emit the error. - return {iterType, nullptr}; + return {PartialMutationError::featureDisabled(iterType)}; } + LLVM_DEBUG(llvm::dbgs() << " MoveOnlyPartialConsumption enabled!\n"); + // Otherwise, walk the type looking for the deinit. while (iterType != targetType) { // If we have a nominal type as our parent type, see if it has a @@ -1671,7 +1663,7 @@ shouldEmitPartialError(UseState &useState, SILInstruction *user, // through the deinit. Emit a nice error saying what it is. Since we // are emitting an error, we do a bit more work and construct the // actual projection string. - return {iterType, nom}; + return {PartialMutationError::hasDeinit(iterType, *nom)}; } } @@ -1681,103 +1673,24 @@ shouldEmitPartialError(UseState &useState, SILInstruction *user, *pair.walkOneLevelTowardsChild(iterPair, iterType, fn); } - return {SILType(), nullptr}; + return {}; } -static void -checkForPartialConsume(UseState &useState, DiagnosticEmitter &diagnosticEmitter, - SILInstruction *user, SILType useType, - TypeTreeLeafTypeRange usedBits, - IsPartialConsumeOrReinit_t isPartialConsumeOrReinit) { - SILFunction *fn = useState.getFunction(); - +static bool checkForPartialMutation(UseState &useState, + DiagnosticEmitter &diagnosticEmitter, + SILInstruction *user, SILType useType, + TypeTreeLeafTypeRange usedBits, + PartialMutation partialMutateKind) { // We walk down from our ancestor to our projection, emitting an error if // any of our types have a deinit. - TypeOffsetSizePair pair(usedBits); - SILType errorIterType; - NominalTypeDecl *nom; - std::tie(errorIterType, nom) = - shouldEmitPartialError(useState, user, useType, usedBits); - if (!errorIterType) - return; + auto error = + shouldEmitPartialMutationError(useState, user, useType, usedBits); + if (!error) + return false; - if (!fn->getModule().getASTContext().LangOpts.hasFeature( - Feature::MoveOnlyPartialConsumption)) { - // Otherwise, build up the path string and emit the error. - SmallString<128> pathString; - auto rootType = useState.address->getType(); - if (errorIterType != rootType) { - llvm::raw_svector_ostream os(pathString); - pair.constructPathString(errorIterType, {rootType, fn}, rootType, fn, os); - } - - diagnosticEmitter.emitCannotPartiallyConsumeError( - useState.address, pathString, nullptr /*nominal*/, user, - false /*deinit only*/); - return; - } - - LLVM_DEBUG(llvm::dbgs() << " MoveOnlyPartialConsumption enabled!\n"); - - SmallString<128> pathString; - auto rootType = useState.address->getType(); - if (errorIterType != rootType) { - llvm::raw_svector_ostream os(pathString); - pair.constructPathString(errorIterType, {rootType, fn}, rootType, fn, os); - } - - diagnosticEmitter.emitCannotPartiallyConsumeError( - useState.address, pathString, nom, user, true /*deinit only*/); -} - -static void -checkForPartialConsume(UseState &useState, DiagnosticEmitter &diagnosticEmitter, - Operand *op, TypeTreeLeafTypeRange usedBits, - IsPartialConsumeOrReinit_t isPartialConsumeOrReinit) { - return checkForPartialConsume(useState, diagnosticEmitter, op->getUser(), - op->get()->getType(), usedBits, - isPartialConsumeOrReinit); -} - -static void diagnosePartialReinitError(UseState &useState, - DiagnosticEmitter &diagnosticEmitter, - SILInstruction *user, SILType errorType, - NominalTypeDecl *nom, - SILInstruction *earlierConsumingUse, - TypeTreeLeafTypeRange usedBits) { - SILFunction *fn = useState.getFunction(); - - // We walk down from our ancestor to our projection, emitting an error if - // any of our types have a deinit. - TypeOffsetSizePair pair(usedBits); - if (!fn->getModule().getASTContext().LangOpts.hasFeature( - Feature::MoveOnlyPartialConsumption)) { - // Otherwise, build up the path string and emit the error. - SmallString<128> pathString; - auto rootType = useState.address->getType(); - if (errorType != rootType) { - llvm::raw_svector_ostream os(pathString); - pair.constructPathString(errorType, {rootType, fn}, rootType, fn, os); - } - - diagnosticEmitter.emitCannotPartiallyReinitError( - useState.address, pathString, nullptr /*nominal*/, user, - earlierConsumingUse, false /*deinit only*/); - return; - } - - LLVM_DEBUG(llvm::dbgs() << " MoveOnlyPartialConsumption enabled!\n"); - - SmallString<128> pathString; - auto rootType = useState.address->getType(); - if (errorType != rootType) { - llvm::raw_svector_ostream os(pathString); - pair.constructPathString(errorType, {rootType, fn}, rootType, fn, os); - } - - diagnosticEmitter.emitCannotPartiallyReinitError( - useState.address, pathString, nom, user, earlierConsumingUse, - true /*deinit only*/); + diagnosticEmitter.emitCannotPartiallyMutateError( + useState.address, error.value(), user, usedBits, partialMutateKind); + return true; } namespace { @@ -1791,14 +1704,6 @@ struct PartialReinitChecker { void performPartialReinitChecking(FieldSensitiveMultiDefPrunedLiveRange &liveness); - -private: - void checkForPartialConsumeOrInitError( - SILInstruction *user, SILType useType, TypeTreeLeafTypeRange usedBits, - IsPartialConsumeOrReinit_t isPartialConsumeOrReinit) { - ::checkForPartialConsume(useState, diagnosticEmitter, user, useType, - usedBits, isPartialConsumeOrReinit); - } }; } // namespace @@ -1819,18 +1724,10 @@ void PartialReinitChecker::performPartialReinitChecking( emittedError = !liveness.findEarlierConsumingUse( initToValues.first, index, [&](SILInstruction *consumingInst) -> bool { - SILType errorType; - NominalTypeDecl *nom; - std::tie(errorType, nom) = shouldEmitPartialError( - useState, initToValues.first, value->getType(), - TypeTreeLeafTypeRange(index, index + 1)); - if (!errorType) - return true; - - diagnosePartialReinitError( - useState, diagnosticEmitter, initToValues.first, errorType, - nom, consumingInst, TypeTreeLeafTypeRange(index, index + 1)); - return false; + return !checkForPartialMutation( + useState, diagnosticEmitter, initToValues.first, + value->getType(), TypeTreeLeafTypeRange(index, index + 1), + PartialMutation::reinit(*consumingInst)); }); // If we emitted an error for this index break. We only want to emit one @@ -1862,18 +1759,10 @@ void PartialReinitChecker::performPartialReinitChecking( emittedError = !liveness.findEarlierConsumingUse( reinitToValues.first, index, [&](SILInstruction *consumingInst) -> bool { - SILType errorType; - NominalTypeDecl *nom; - std::tie(errorType, nom) = shouldEmitPartialError( - useState, reinitToValues.first, value->getType(), - TypeTreeLeafTypeRange(index, index + 1)); - if (!errorType) - return true; - - diagnosePartialReinitError( - useState, diagnosticEmitter, reinitToValues.first, errorType, - nom, consumingInst, TypeTreeLeafTypeRange(index, index + 1)); - return false; + return !checkForPartialMutation( + useState, diagnosticEmitter, reinitToValues.first, + value->getType(), TypeTreeLeafTypeRange(index, index + 1), + PartialMutation::reinit(*consumingInst)); }); if (emittedError) break; @@ -2102,8 +1991,9 @@ bool GatherUsesVisitor::visitUse(Operand *op) { // If we have a copy_addr, we are either going to have a take or a // copy... in either case, this copy_addr /is/ going to be a consuming // operation. Make sure to check if we semantically destructure. - checkForPartialConsume(useState, diagnosticEmitter, op, *leafRange, - IsPartialConsumeOrReinit_t::IsPartialConsume); + checkForPartialMutation(useState, diagnosticEmitter, op->getUser(), + op->get()->getType(), *leafRange, + PartialMutation::consume()); if (copyAddr->isTakeOfSrc()) { LLVM_DEBUG(llvm::dbgs() << "Found take: " << *user); @@ -2296,8 +2186,9 @@ bool GatherUsesVisitor::visitUse(Operand *op) { } else { // Now that we know that we are going to perform a take, perform a // checkForDestructure. - checkForPartialConsume(useState, diagnosticEmitter, op, *leafRange, - IsPartialConsumeOrReinit_t::IsPartialConsume); + checkForPartialMutation(useState, diagnosticEmitter, op->getUser(), + op->get()->getType(), *leafRange, + PartialMutation::consume()); // If we emitted an error diagnostic, do not transform further and instead // mark that we emitted an early diagnostic and return true. @@ -2352,8 +2243,9 @@ bool GatherUsesVisitor::visitUse(Operand *op) { // error. unsigned numDiagnostics = moveChecker.diagnosticEmitter.getDiagnosticCount(); - checkForPartialConsume(useState, diagnosticEmitter, op, *leafRange, - IsPartialConsumeOrReinit_t::IsPartialConsume); + checkForPartialMutation(useState, diagnosticEmitter, op->getUser(), + op->get()->getType(), *leafRange, + PartialMutation::consume()); if (numDiagnostics != moveChecker.diagnosticEmitter.getDiagnosticCount()) { LLVM_DEBUG(llvm::dbgs() << "Emitting destructure through deinit error!\n"); diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp index 20c97be8974..69664b926ce 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp @@ -25,6 +25,7 @@ #include "swift/SIL/SILArgument.h" #include "llvm/Support/Debug.h" +#include "MoveOnlyTypeUtils.h" using namespace swift; using namespace swift::siloptimizer; @@ -931,13 +932,25 @@ void DiagnosticEmitter::emitPromotedBoxArgumentError( } } -void DiagnosticEmitter::emitCannotPartiallyConsumeError( - MarkUnresolvedNonCopyableValueInst *markedValue, StringRef pathString, - NominalTypeDecl *nominal, SILInstruction *consumingUser, bool isForDeinit) { +void DiagnosticEmitter::emitCannotPartiallyMutateError( + MarkUnresolvedNonCopyableValueInst *address, PartialMutationError error, + SILInstruction *user, TypeTreeLeafTypeRange usedBits, + PartialMutation kind) { + + TypeOffsetSizePair pair(usedBits); + + SmallString<128> pathString; + auto rootType = address->getType(); + if (error.type != rootType) { + llvm::raw_svector_ostream os(pathString); + auto *fn = address->getFunction(); + pair.constructPathString(error.type, {rootType, fn}, rootType, fn, os); + } + auto &astContext = fn->getASTContext(); SmallString<64> varName; - getVariableNameForValue(markedValue, varName); + getVariableNameForValue(address, varName); if (!pathString.empty()) varName.append(pathString); @@ -946,68 +959,43 @@ void DiagnosticEmitter::emitCannotPartiallyConsumeError( astContext.LangOpts.hasFeature(Feature::MoveOnlyPartialConsumption); (void)hasPartialConsumption; - if (isForDeinit) { - assert(hasPartialConsumption); - diagnose(astContext, consumingUser, - diag::sil_movechecking_cannot_destructure_has_deinit, varName); - - } else { + switch (error) { + case PartialMutationError::Kind::FeatureDisabled: assert(!hasPartialConsumption); - diagnose(astContext, consumingUser, - diag::sil_movechecking_cannot_destructure, varName); - } - - registerDiagnosticEmitted(markedValue); - - if (!isForDeinit) + switch (kind) { + case PartialMutation::Kind::Consume: + diagnose(astContext, user, diag::sil_movechecking_cannot_destructure, + varName); + break; + case PartialMutation::Kind::Reinit: + diagnose(astContext, user, diag::sil_movechecking_cannot_partially_reinit, + varName); + diagnose(astContext, &kind.getEarlierConsumingUse(), + diag::sil_movechecking_consuming_use_here); + break; + } + registerDiagnosticEmitted(address); return; - - // Point to the deinit if we know where it is. - assert(nominal); - if (auto deinitLoc = - nominal->getValueTypeDestructor()->getLoc(/*SerializedOK=*/false)) - astContext.Diags.diagnose(deinitLoc, diag::sil_movechecking_deinit_here); -} - -void DiagnosticEmitter::emitCannotPartiallyReinitError( - MarkUnresolvedNonCopyableValueInst *markedValue, StringRef pathString, - NominalTypeDecl *nominal, SILInstruction *initingUser, - SILInstruction *consumingUser, bool isForDeinit) { - auto &astContext = fn->getASTContext(); - - SmallString<64> varName; - getVariableNameForValue(markedValue, varName); - - if (!pathString.empty()) - varName.append(pathString); - - bool hasPartialConsumption = - astContext.LangOpts.hasFeature(Feature::MoveOnlyPartialConsumption); - (void)hasPartialConsumption; - - if (isForDeinit) { + case PartialMutationError::Kind::HasDeinit: { assert(hasPartialConsumption); - diagnose(astContext, initingUser, - diag::sil_movechecking_cannot_partially_reinit_has_deinit, - varName); - - } else { - assert(!hasPartialConsumption); - diagnose(astContext, initingUser, - diag::sil_movechecking_cannot_partially_reinit, varName); - } - - diagnose(astContext, consumingUser, - diag::sil_movechecking_consuming_use_here); - - registerDiagnosticEmitted(markedValue); - - if (!isForDeinit) - return; - - // Point to the deinit if we know where it is. - assert(nominal); - if (auto deinitLoc = - nominal->getValueTypeDestructor()->getLoc(/*SerializedOK=*/false)) + switch (kind) { + case PartialMutation::Kind::Consume: + diagnose(astContext, user, + diag::sil_movechecking_cannot_destructure_has_deinit, varName); + break; + case PartialMutation::Kind::Reinit: + diagnose(astContext, user, + diag::sil_movechecking_cannot_partially_reinit_has_deinit, + varName); + break; + } + registerDiagnosticEmitted(address); + auto deinitLoc = error.getNominal().getValueTypeDestructor()->getLoc( + /*SerializedOK=*/false); + if (!deinitLoc) + return; astContext.Diags.diagnose(deinitLoc, diag::sil_movechecking_deinit_here); + return; + } + } } diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.h b/lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.h index 7c4a65041d4..4fa8db39b2a 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.h +++ b/lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.h @@ -20,6 +20,7 @@ #define SWIFT_SILOPTIMIZER_MANDATORY_MOVEONLYDIAGNOSTICS_H #include "MoveOnlyObjectCheckerUtils.h" +#include "MoveOnlyTypeUtils.h" #include "swift/Basic/NullablePtr.h" #include "swift/SIL/FieldSensitivePrunedLiveness.h" #include "swift/SIL/SILInstruction.h" @@ -187,15 +188,10 @@ public: emitPromotedBoxArgumentError(MarkUnresolvedNonCopyableValueInst *markedValue, SILFunctionArgument *arg); - void emitCannotPartiallyConsumeError( - MarkUnresolvedNonCopyableValueInst *markedValue, StringRef pathString, - NominalTypeDecl *nominal, SILInstruction *consumingUser, - bool dueToDeinit); - - void emitCannotPartiallyReinitError( - MarkUnresolvedNonCopyableValueInst *markedValue, StringRef pathString, - NominalTypeDecl *nominal, SILInstruction *initUser, - SILInstruction *consumingUser, bool dueToDeinit); + void emitCannotPartiallyMutateError( + MarkUnresolvedNonCopyableValueInst *markedValue, + PartialMutationError error, SILInstruction *user, + TypeTreeLeafTypeRange usedBits, PartialMutation kind); private: /// Emit diagnostics for the final consuming uses and consuming uses needing diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyTypeUtils.h b/lib/SILOptimizer/Mandatory/MoveOnlyTypeUtils.h index d224cf638ac..2a4efeb140e 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyTypeUtils.h +++ b/lib/SILOptimizer/Mandatory/MoveOnlyTypeUtils.h @@ -20,6 +20,7 @@ #ifndef SWIFT_SILOPTIMIZER_MANDATORY_MOVEONLYTYPEUTILS_H #define SWIFT_SILOPTIMIZER_MANDATORY_MOVEONLYTYPEUTILS_H +#include "swift/Basic/TaggedUnion.h" #include "swift/SIL/FieldSensitivePrunedLiveness.h" #include "swift/SIL/SILBuilder.h" @@ -87,6 +88,105 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &os, << ")"; } +/// How a portion of a move-only value/address was "mutated". +/// +/// Emulates this enum with associated value: +/// +/// enum PartialMutation { +/// case consumed +/// case reinitialized(Instruction) +/// } +class PartialMutation { + struct Consume {}; + struct Reinit { + SILInstruction &earlierConsumingUse; + }; + TaggedUnion storage; + + PartialMutation(Consume c) : storage({c}) {} + PartialMutation(Reinit r) : storage({r}) {} + +public: + enum class Kind { + Consume, + Reinit, + }; + + operator Kind() { + if (storage.isa()) + return Kind::Consume; + return Kind::Reinit; + } + + static PartialMutation consume() { return PartialMutation(Consume{}); } + static PartialMutation reinit(SILInstruction &earlierConsumingUse) { + return PartialMutation(Reinit{earlierConsumingUse}); + } + SILInstruction &getEarlierConsumingUse() { + return storage.get().earlierConsumingUse; + } +}; + +/// How a partial mutation (consume/reinit) is illegal for diagnostic emission. +/// +/// Emulates the following enum with associated value: +/// +/// enum class PartialMutationError { +/// case featureDisabled(SILType) +/// case hasDeinit(SILType, NominalTypeDecl) +/// } +class PartialMutationError { + struct FeatureDisabled {}; + struct HasDeinit { + NominalTypeDecl &nominal; + }; + TaggedUnion kind; + + PartialMutationError(SILType type, FeatureDisabled fd) + : kind({fd}), type(type) {} + PartialMutationError(SILType type, HasDeinit r) : kind({r}), type(type) {} + +public: + /// The type within the aggregate responsible for the error. + /// + /// case featureDisabled: + /// The type of the portion of the aggregate that would be mutated. + /// case hasDeinit: + /// The type that has the deinit. + SILType type; + /// The reason it's illegal. + /// + /// See shouldEmitPartialMutationError, emitPartialMutationError. + enum class Kind : uint8_t { + /// The partial consumption feature is disabled. + /// + /// See -enable-experimental-feature MoveOnlyPartialConsumption. + FeatureDisabled, + /// A partially consumed/reinitialized aggregate has a deinit. + /// + /// The aggregate is somewhere in the type layout between the consumed + /// range and the full value. It is the \p nominal field of + /// PartialMutationError. + HasDeinit, + }; + + operator Kind() { + if (kind.isa()) + return Kind::FeatureDisabled; + return Kind::HasDeinit; + } + + static PartialMutationError featureDisabled(SILType type) { + return PartialMutationError(type, FeatureDisabled{}); + } + + static PartialMutationError hasDeinit(SILType type, + NominalTypeDecl &nominal) { + return PartialMutationError(type, HasDeinit{nominal}); + } + + NominalTypeDecl &getNominal() { return kind.get().nominal; }; +}; } // namespace siloptimizer } // namespace swift