diff --git a/include/swift/SILOptimizer/PassManager/Passes.def b/include/swift/SILOptimizer/PassManager/Passes.def index a8d0747b20ccf..ef49ad08d4749 100644 --- a/include/swift/SILOptimizer/PassManager/Passes.def +++ b/include/swift/SILOptimizer/PassManager/Passes.def @@ -439,9 +439,11 @@ PASS(BugReducerTester, "bug-reducer-tester", PASS(AssemblyVisionRemarkGenerator, "assembly-vision-remark-generator", "Emit assembly vision remarks that provide source level guidance of where runtime calls ended up") PASS(MoveOnlyObjectChecker, "sil-move-only-object-checker", - "Pass that enforces move only invariants on raw SIL for objects") + "Utility pass that enforces move only invariants on raw SIL for objects for testing purposes") PASS(MoveOnlyAddressChecker, "sil-move-only-address-checker", - "Pass that enforces move only invariants on raw SIL for addresses") + "Utility pass that enforces move only invariants on raw SIL for addresses for testing purposes") +PASS(MoveOnlyChecker, "sil-move-only-checker", + "Pass that enforces move only invariants on raw SIL for addresses and objects") PASS(ConsumeOperatorCopyableValuesChecker, "sil-consume-operator-copyable-values-checker", "Pass that performs checking of the consume operator for copyable values") PASS(TrivialMoveOnlyTypeEliminator, "sil-trivial-move-only-type-eliminator", diff --git a/lib/SILOptimizer/Mandatory/CMakeLists.txt b/lib/SILOptimizer/Mandatory/CMakeLists.txt index 6b518a9964576..871d4f10da2c7 100644 --- a/lib/SILOptimizer/Mandatory/CMakeLists.txt +++ b/lib/SILOptimizer/Mandatory/CMakeLists.txt @@ -24,12 +24,16 @@ target_sources(swiftSILOptimizer PRIVATE LowerHopToActor.cpp MandatoryInlining.cpp MovedAsyncVarDebugInfoPropagator.cpp - MoveOnlyAddressChecker.cpp - MoveOnlyBorrowToDestructureTransform.cpp - MoveOnlyBorrowToDestructureTransformTester.cpp + MoveOnlyAddressCheckerUtils.cpp + MoveOnlyAddressCheckerTester.cpp + MoveOnlyBorrowToDestructureUtils.cpp + MoveOnlyBorrowToDestructureTester.cpp MoveOnlyDeinitInsertion.cpp MoveOnlyDiagnostics.cpp - MoveOnlyObjectChecker.cpp + MoveOnlyObjectCheckerUtils.cpp + MoveOnlyObjectCheckerTester.cpp + MoveOnlyChecker.cpp + MoveOnlyUtils.cpp NestedSemanticFunctionCheck.cpp OptimizeHopToExecutor.cpp PerformanceDiagnostics.cpp diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerTester.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerTester.cpp new file mode 100644 index 0000000000000..04af43bc3c4be --- /dev/null +++ b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerTester.cpp @@ -0,0 +1,141 @@ +//===--- MoveOnlyAddressCheckerTester.cpp ---------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#define DEBUG_TYPE "sil-move-only-checker" + +#include "swift/AST/AccessScope.h" +#include "swift/AST/DiagnosticEngine.h" +#include "swift/AST/DiagnosticsSIL.h" +#include "swift/Basic/Debug.h" +#include "swift/Basic/Defer.h" +#include "swift/Basic/FrozenMultiMap.h" +#include "swift/Basic/SmallBitVector.h" +#include "swift/SIL/ApplySite.h" +#include "swift/SIL/BasicBlockBits.h" +#include "swift/SIL/BasicBlockData.h" +#include "swift/SIL/BasicBlockDatastructures.h" +#include "swift/SIL/BasicBlockUtils.h" +#include "swift/SIL/Consumption.h" +#include "swift/SIL/DebugUtils.h" +#include "swift/SIL/FieldSensitivePrunedLiveness.h" +#include "swift/SIL/InstructionUtils.h" +#include "swift/SIL/MemAccessUtils.h" +#include "swift/SIL/OwnershipUtils.h" +#include "swift/SIL/PrunedLiveness.h" +#include "swift/SIL/SILArgument.h" +#include "swift/SIL/SILArgumentConvention.h" +#include "swift/SIL/SILBasicBlock.h" +#include "swift/SIL/SILBuilder.h" +#include "swift/SIL/SILFunction.h" +#include "swift/SIL/SILInstruction.h" +#include "swift/SIL/SILUndef.h" +#include "swift/SIL/SILValue.h" +#include "swift/SILOptimizer/Analysis/ClosureScope.h" +#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h" +#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" +#include "swift/SILOptimizer/Analysis/NonLocalAccessBlockAnalysis.h" +#include "swift/SILOptimizer/PassManager/Transforms.h" +#include "swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h" +#include "swift/SILOptimizer/Utils/InstructionDeleter.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/MapVector.h" +#include "llvm/ADT/PointerIntPair.h" +#include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallBitVector.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/ErrorHandling.h" + +#include "MoveOnlyAddressCheckerUtils.h" +#include "MoveOnlyBorrowToDestructureUtils.h" +#include "MoveOnlyDiagnostics.h" +#include "MoveOnlyObjectCheckerUtils.h" +#include "MoveOnlyUtils.h" + +#include + +using namespace swift; +using namespace swift::siloptimizer; + +//===----------------------------------------------------------------------===// +// MARK: Top Level Entrypoint +//===----------------------------------------------------------------------===// + +namespace { + +class MoveOnlyAddressCheckerTesterPass : public SILFunctionTransform { + void run() override { + auto *fn = getFunction(); + + // Only run this pass if the move only language feature is enabled. + if (!fn->getASTContext().LangOpts.Features.contains(Feature::MoveOnly)) + return; + + // Don't rerun diagnostics on deserialized functions. + if (getFunction()->wasDeserializedCanonical()) + return; + + assert(fn->getModule().getStage() == SILStage::Raw && + "Should only run on Raw SIL"); + LLVM_DEBUG(llvm::dbgs() << "===> MoveOnly Addr Checker. Visiting: " + << fn->getName() << '\n'); + auto *dominanceAnalysis = getAnalysis(); + DominanceInfo *domTree = dominanceAnalysis->get(fn); + auto *poa = getAnalysis(); + + DiagnosticEmitter diagnosticEmitter; + SmallSetVector moveIntroducersToProcess; + searchForCandidateAddressMarkMustChecks(fn, moveIntroducersToProcess, + diagnosticEmitter); + + LLVM_DEBUG(llvm::dbgs() + << "Emitting diagnostic when checking for mark must check inst: " + << (diagnosticEmitter.getDiagnosticCount() ? "yes" : "no") + << '\n'); + + bool madeChange = false; + unsigned diagCount = 0; + if (moveIntroducersToProcess.empty()) { + LLVM_DEBUG(llvm::dbgs() << "No move introducers found?!\n"); + } else { + borrowtodestructure::IntervalMapAllocator allocator; + MoveOnlyAddressChecker checker{getFunction(), diagnosticEmitter, + allocator, domTree, poa}; + madeChange = checker.check(moveIntroducersToProcess); + diagCount = checker.diagnosticEmitter.getDiagnosticCount(); + } + + // If we did not emit any diagnostics, emit a diagnostic if we missed any + // copies. + if (!diagCount) { + emitCheckerMissedCopyOfNonCopyableTypeErrors(getFunction(), + diagnosticEmitter); + } + + // Then cleanup any copies we left behind for either reason and emit an + // error. + madeChange |= + cleanupNonCopyableCopiesAfterEmittingDiagnostic(getFunction()); + + if (madeChange) { + invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); + } + } +}; + +} // anonymous namespace + +SILTransform *swift::createMoveOnlyAddressChecker() { + return new MoveOnlyAddressCheckerTesterPass(); +} diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyAddressChecker.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp similarity index 90% rename from lib/SILOptimizer/Mandatory/MoveOnlyAddressChecker.cpp rename to lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp index e468a5f509573..b0900c5d242a3 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyAddressChecker.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp @@ -1,4 +1,4 @@ -//===--- MoveOnlyAddressChecker.cpp ---------------------------------------===// +//===--- MoveOnlyAddressCheckerUtils.cpp ----------------------------------===// // // This source file is part of the Swift.org open source project // @@ -156,9 +156,11 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/ErrorHandling.h" -#include "MoveOnlyBorrowToDestructure.h" +#include "MoveOnlyAddressCheckerUtils.h" +#include "MoveOnlyBorrowToDestructureUtils.h" #include "MoveOnlyDiagnostics.h" -#include "MoveOnlyObjectChecker.h" +#include "MoveOnlyObjectCheckerUtils.h" +#include "MoveOnlyUtils.h" #include @@ -364,59 +366,46 @@ static bool isInOutDefThatNeedsEndOfFunctionLiveness(SILValue value) { } //===----------------------------------------------------------------------===// -// MARK: Cleanup After Emitting Diagnostic +// MARK: Find Candidate Mark Must Checks //===----------------------------------------------------------------------===// -static bool cleanupAfterEmittingDiagnostic(SILFunction *fn) { - bool changed = false; +void swift::siloptimizer::searchForCandidateAddressMarkMustChecks( + SILFunction *fn, + llvm::SmallSetVector &moveIntroducersToProcess, + DiagnosticEmitter &diagnosticEmitter) { for (auto &block : *fn) { for (auto ii = block.begin(), ie = block.end(); ii != ie;) { - auto *inst = &*ii; + auto *mmci = dyn_cast(&*ii); ++ii; - // Convert load [copy] -> load_borrow + explicit_copy_value. - if (auto *li = dyn_cast(inst)) { - if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Copy) { - SILBuilderWithScope builder(li); - auto *lbi = builder.createLoadBorrow(li->getLoc(), li->getOperand()); - auto *cvi = builder.createExplicitCopyValue(li->getLoc(), lbi); - builder.createEndBorrow(li->getLoc(), lbi); - li->replaceAllUsesWith(cvi); - li->eraseFromParent(); - changed = true; + if (!mmci || !mmci->hasMoveCheckerKind() || !mmci->getType().isAddress()) + continue; + + // Skip any alloc_box due to heap to stack failing on a box capture. This + // will just cause an error. + if (auto *pbi = dyn_cast(mmci->getOperand())) { + if (isa(pbi->getOperand())) { + LLVM_DEBUG( + llvm::dbgs() + << "Early emitting diagnostic for unsupported alloc box!\n"); + diagnosticEmitter.emitCheckerDoesntUnderstandDiagnostic(mmci); + continue; } - } - // Convert copy_addr !take of src to its explicit value form so we don't - // error. - if (auto *copyAddr = dyn_cast(inst)) { - if (!copyAddr->isTakeOfSrc()) { - SILBuilderWithScope builder(copyAddr); - builder.createExplicitCopyAddr( - copyAddr->getLoc(), copyAddr->getSrc(), copyAddr->getDest(), - IsTake_t(copyAddr->isTakeOfSrc()), - IsInitialization_t(copyAddr->isInitializationOfDest())); - copyAddr->eraseFromParent(); - changed = true; + if (auto *bbi = dyn_cast(pbi->getOperand())) { + if (isa(bbi->getOperand())) { + LLVM_DEBUG( + llvm::dbgs() + << "Early emitting diagnostic for unsupported alloc box!\n"); + diagnosticEmitter.emitCheckerDoesntUnderstandDiagnostic(mmci); + continue; + } } } - // Convert any copy_value of move_only type to explicit copy value. - if (auto *cvi = dyn_cast(inst)) { - if (!cvi->getOperand()->getType().isMoveOnly()) - continue; - SILBuilderWithScope b(cvi); - auto *expCopy = - b.createExplicitCopyValue(cvi->getLoc(), cvi->getOperand()); - cvi->replaceAllUsesWith(expCopy); - cvi->eraseFromParent(); - changed = true; - continue; - } + moveIntroducersToProcess.insert(mmci); } } - - return changed; } //===----------------------------------------------------------------------===// @@ -920,7 +909,7 @@ struct ConsumeInfo { ConsumeInfo &operator=(ConsumeInfo const &) = delete; }; -struct MoveOnlyChecker { +struct MoveOnlyAddressCheckerPImpl { bool changed = false; SILFunction *fn; @@ -939,20 +928,23 @@ struct MoveOnlyChecker { /// Diagnostic emission routines wrapped around a consuming use cache. This /// ensures that we only emit a single error per use per marked value. - DiagnosticEmitter diagnosticEmitter; + DiagnosticEmitter &diagnosticEmitter; /// Information about destroys that we use when inserting destroys. ConsumeInfo consumes; - /// Allocator used by the BorrowToDestructureTransform. - borrowtodestructure::IntervalMapAllocator allocator; - /// PostOrderAnalysis used by the BorrowToDestructureTransform. PostOrderAnalysis *poa; - MoveOnlyChecker(SILFunction *fn, DeadEndBlocks *deBlocks, - DominanceInfo *domTree, PostOrderAnalysis *poa) - : fn(fn), deleter(), canonicalizer(), diagnosticEmitter(), poa(poa) { + /// Allocator used by the BorrowToDestructureTransform. + borrowtodestructure::IntervalMapAllocator &allocator; + + MoveOnlyAddressCheckerPImpl( + SILFunction *fn, DiagnosticEmitter &diagnosticEmitter, + DominanceInfo *domTree, PostOrderAnalysis *poa, + borrowtodestructure::IntervalMapAllocator &allocator) + : fn(fn), deleter(), canonicalizer(), + diagnosticEmitter(diagnosticEmitter), poa(poa), allocator(allocator) { deleter.setCallbacks(std::move( InstModCallbacks().onDelete([&](SILInstruction *instToDelete) { if (auto *mvi = dyn_cast(instToDelete)) @@ -971,21 +963,11 @@ struct MoveOnlyChecker { /// Returns true if we emitted a diagnostic. Returns false otherwise. bool searchForCandidateMarkMustChecks(); - /// After we have emitted a diagnostic, we need to clean up the instruction - /// stream by converting /all/ copies of move only typed things to use - /// explicit_copy_value so that we maintain the SIL invariant that in - /// canonical SIL move only types are not copied by normal copies. - /// - /// Returns true if we actually changed any instructions. - void cleanupAfterEmittingDiagnostic(); - /// Emits an error diagnostic for \p markedValue. void performObjectCheck(MarkMustCheckInst *markedValue); bool performSingleCheck(MarkMustCheckInst *markedValue); - bool checkFunction(); - void insertDestroysOnBoundary(FieldSensitiveMultiDefPrunedLiveRange &liveness, FieldSensitivePrunedLivenessBoundary &boundary); @@ -1005,7 +987,7 @@ namespace { /// Visit all of the uses of value in preparation for running our algorithm. struct GatherUsesVisitor : public AccessUseVisitor { - MoveOnlyChecker &moveChecker; + MoveOnlyAddressCheckerPImpl &moveChecker; UseState &useState; MarkMustCheckInst *markedValue; bool emittedEarlyDiagnostic = false; @@ -1015,8 +997,8 @@ struct GatherUsesVisitor : public AccessUseVisitor { // converted to load_borrow without violating exclusivity. SSAPrunedLiveness &liveness; - GatherUsesVisitor(MoveOnlyChecker &moveChecker, UseState &useState, - MarkMustCheckInst *markedValue, + GatherUsesVisitor(MoveOnlyAddressCheckerPImpl &moveChecker, + UseState &useState, MarkMustCheckInst *markedValue, DiagnosticEmitter &diagnosticEmitter, SSAPrunedLiveness &gatherUsesLiveness) : AccessUseVisitor(AccessUseType::Overlapping, @@ -1328,7 +1310,7 @@ bool GatherUsesVisitor::visitUse(Operand *op, AccessUseType useTy) { useState.takeInsts.insert({user, *leafRange}); return true; } - + auto insertLivenessUseForApply = [&](const Operand &op) -> bool { auto leafRange = TypeTreeLeafTypeRange::get(op.get(), getRootAddress()); if (!leafRange) @@ -1358,7 +1340,7 @@ bool GatherUsesVisitor::visitUse(Operand *op, AccessUseType useTy) { break; } } - + if (PartialApplyInst *pas = dyn_cast(user)) { if (pas->isOnStack()) { // On-stack partial applications are always a liveness use of their @@ -1366,7 +1348,7 @@ bool GatherUsesVisitor::visitUse(Operand *op, AccessUseType useTy) { return insertLivenessUseForApply(*op); } } - + // If we don't fit into any of those categories, just track as a liveness // use. We assume all such uses must only be reads to the memory. So we assert // to be careful. @@ -1651,50 +1633,6 @@ bool GlobalLivenessChecker::compute() { // MARK: Main Pass Implementation //===----------------------------------------------------------------------===// -void MoveOnlyChecker::cleanupAfterEmittingDiagnostic() { - changed |= ::cleanupAfterEmittingDiagnostic(fn); -} - -bool MoveOnlyChecker::searchForCandidateMarkMustChecks() { - bool emittedDiagnostic = false; - for (auto &block : *fn) { - for (auto ii = block.begin(), ie = block.end(); ii != ie;) { - auto *mmci = dyn_cast(&*ii); - ++ii; - - if (!mmci || !mmci->hasMoveCheckerKind() || !mmci->getType().isAddress()) - continue; - - // Skip any alloc_box due to heap to stack failing on a box capture. This - // will just cause an error. - if (auto *pbi = dyn_cast(mmci->getOperand())) { - if (isa(pbi->getOperand())) { - LLVM_DEBUG( - llvm::dbgs() - << "Early emitting diagnostic for unsupported alloc box!\n"); - diagnosticEmitter.emitCheckerDoesntUnderstandDiagnostic(mmci); - emittedDiagnostic = true; - continue; - } - - if (auto *bbi = dyn_cast(pbi->getOperand())) { - if (isa(bbi->getOperand())) { - LLVM_DEBUG( - llvm::dbgs() - << "Early emitting diagnostic for unsupported alloc box!\n"); - diagnosticEmitter.emitCheckerDoesntUnderstandDiagnostic(mmci); - emittedDiagnostic = true; - continue; - } - } - } - - moveIntroducersToProcess.insert(mmci); - } - } - return emittedDiagnostic; -} - /// Create a new destroy_value instruction before the specified instruction and /// record it as a final consume. static void insertDestroyBeforeInstruction(UseState &addressUseState, @@ -1741,7 +1679,7 @@ static void insertDestroyBeforeInstruction(UseState &addressUseState, } } -void MoveOnlyChecker::insertDestroysOnBoundary( +void MoveOnlyAddressCheckerPImpl::insertDestroysOnBoundary( FieldSensitiveMultiDefPrunedLiveRange &liveness, FieldSensitivePrunedLivenessBoundary &boundary) { using IsInterestingUser = FieldSensitivePrunedLiveness::IsInterestingUser; @@ -1831,7 +1769,7 @@ void MoveOnlyChecker::insertDestroysOnBoundary( consumes.finishRecordingFinalConsumes(); } -void MoveOnlyChecker::rewriteUses( +void MoveOnlyAddressCheckerPImpl::rewriteUses( FieldSensitiveMultiDefPrunedLiveRange &liveness, const FieldSensitivePrunedLivenessBoundary &boundary) { // First remove all destroy_addr that have not been claimed. @@ -1934,8 +1872,10 @@ void MoveOnlyChecker::rewriteUses( #endif } -bool MoveOnlyChecker::performSingleCheck(MarkMustCheckInst *markedAddress) { +bool MoveOnlyAddressCheckerPImpl::performSingleCheck( + MarkMustCheckInst *markedAddress) { SWIFT_DEFER { diagnosticEmitter.clearUsesWithDiagnostic(); }; + unsigned diagCount = diagnosticEmitter.getDiagnosticCount(); auto accessPathWithBase = AccessPathWithBase::compute(markedAddress); auto accessPath = accessPathWithBase.accessPath; @@ -1969,7 +1909,7 @@ bool MoveOnlyChecker::performSingleCheck(MarkMustCheckInst *markedAddress) { // emitted a copy from the base address + a destroy_addr of the use. By // bailing here, we can make that assumption since we would have errored // earlier otherwise. - if (diagnosticEmitter.emittedAnyDiagnostics()) + if (diagCount != diagnosticEmitter.getDiagnosticCount()) return true; //===--- @@ -2000,144 +1940,28 @@ bool MoveOnlyChecker::performSingleCheck(MarkMustCheckInst *markedAddress) { return true; } -bool MoveOnlyChecker::checkFunction() { - // First search for candidates to process and emit diagnostics on any - // mark_must_check [noimplicitcopy] we didn't recognize. - bool emittedDiagnostic = searchForCandidateMarkMustChecks(); +//===----------------------------------------------------------------------===// +// MARK: Top Level Entrypoint +//===----------------------------------------------------------------------===// - // If we didn't find any introducers to check, just return changed. - // - // NOTE: changed /can/ be true here if we had any mark_must_check - // [noimplicitcopy] that we didn't understand and emitting a diagnostic upon - // and then deleting. - if (moveIntroducersToProcess.empty()) { - if (emittedDiagnostic) - cleanupAfterEmittingDiagnostic(); - return changed; - } +bool MoveOnlyAddressChecker::check( + SmallSetVector &moveIntroducersToProcess) { + assert(moveIntroducersToProcess.size() && + "Must have checks to process to call this function"); + MoveOnlyAddressCheckerPImpl pimpl(fn, diagnosticEmitter, domTree, poa, + allocator); for (auto *markedValue : moveIntroducersToProcess) { LLVM_DEBUG(llvm::dbgs() << "Visiting: " << *markedValue); // Perform our address check. - if (!performSingleCheck(markedValue)) { + if (!pimpl.performSingleCheck(markedValue)) { LLVM_DEBUG(llvm::dbgs() << "Failed to perform single check! Emitting error!\n"); // If we fail the address check in some way, set the diagnose! diagnosticEmitter.emitCheckerDoesntUnderstandDiagnostic(markedValue); } } - emittedDiagnostic = diagnosticEmitter.emittedAnyDiagnostics(); - - // Ok, now that we have performed our checks, we need to eliminate all mark - // must check inst since it is invalid for these to be in canonical SIL and - // our work is done here. - while (!moveIntroducersToProcess.empty()) { - auto *markedInst = moveIntroducersToProcess.pop_back_val(); - markedInst->replaceAllUsesWith(markedInst->getOperand()); - markedInst->eraseFromParent(); - changed = true; - } - - // Once we have finished processing, if we emitted any diagnostics, then we - // may have copy_addr [init], load [copy] of @moveOnly typed values. This is - // not valid in Canonical SIL, so we need to ensure that those copy_value - // become explicit_copy_value. This is ok to do since we are already going to - // fail the compilation and just are trying to maintain SIL invariants. - // - // It is also ok that we use a little more compile time and go over the - // function again, since we are going to fail the compilation and not codegen. - if (emittedDiagnostic) { - cleanupAfterEmittingDiagnostic(); - } - - return changed; -} - -//===----------------------------------------------------------------------===// -// MARK: Missed Copy Diagnostic -//===----------------------------------------------------------------------===// - -/// A small diagnostic helper that causes us to emit a diagnostic error upon any -/// copies we did not eliminate and ask the user for a test case. -static bool checkForMissedCopies(SILFunction *fn) { - bool emittedDiagnostic = false; - DiagnosticEmitter diagnosticEmitter; - for (auto &block : *fn) { - for (auto &inst : block) { - if (auto *cvi = dyn_cast(&inst)) { - if (cvi->getOperand()->getType().isMoveOnly()) { - diagnosticEmitter.emitCheckedMissedCopyError(cvi); - emittedDiagnostic = true; - } - continue; - } - - if (auto *li = dyn_cast(&inst)) { - if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Copy && - li->getType().isMoveOnly()) { - diagnosticEmitter.emitCheckedMissedCopyError(li); - emittedDiagnostic = true; - } - continue; - } - - if (auto *copyAddr = dyn_cast(&inst)) { - if (!copyAddr->isTakeOfSrc() && - copyAddr->getSrc()->getType().isMoveOnly()) { - diagnosticEmitter.emitCheckedMissedCopyError(copyAddr); - emittedDiagnostic = true; - } - continue; - } - } - } - - return emittedDiagnostic; -} - -//===----------------------------------------------------------------------===// -// Top Level Entrypoint -//===----------------------------------------------------------------------===// - -namespace { - -class MoveOnlyCheckerPass : public SILFunctionTransform { - void run() override { - auto *fn = getFunction(); - - // Only run this pass if the move only language feature is enabled. - if (!fn->getASTContext().LangOpts.Features.contains(Feature::MoveOnly)) - return; - - // Don't rerun diagnostics on deserialized functions. - if (getFunction()->wasDeserializedCanonical()) - return; - - assert(fn->getModule().getStage() == SILStage::Raw && - "Should only run on Raw SIL"); - LLVM_DEBUG(llvm::dbgs() << "===> MoveOnly Addr Checker. Visiting: " - << fn->getName() << '\n'); - auto *dominanceAnalysis = getAnalysis(); - DominanceInfo *domTree = dominanceAnalysis->get(fn); - auto *deAnalysis = getAnalysis()->get(fn); - auto *poa = getAnalysis(); - - bool shouldInvalidate = - MoveOnlyChecker(getFunction(), deAnalysis, domTree, poa) - .checkFunction(); - if (checkForMissedCopies(getFunction())) { - cleanupAfterEmittingDiagnostic(getFunction()); - shouldInvalidate = true; - } - if (shouldInvalidate) { - invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); - } - } -}; - -} // anonymous namespace -SILTransform *swift::createMoveOnlyAddressChecker() { - return new MoveOnlyCheckerPass(); + return pimpl.changed; } diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.h b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.h new file mode 100644 index 0000000000000..652b73f634f9e --- /dev/null +++ b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.h @@ -0,0 +1,49 @@ +//===--- MoveOnlyAddressCheckerUtils.h ------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_SILOPTIMIZER_MANDATORY_MOVEONLYADDRESSCHECKERUTILS_H +#define SWIFT_SILOPTIMIZER_MANDATORY_MOVEONLYADDRESSCHECKERUTILS_H + +#include "MoveOnlyBorrowToDestructureUtils.h" + +namespace swift { + +namespace siloptimizer { + +class DiagnosticEmitter; + +/// Searches for candidate mark must checks. +/// +/// NOTE: To see if we emitted a diagnostic, use \p +/// diagnosticEmitter.getDiagnosticCount(). +void searchForCandidateAddressMarkMustChecks( + SILFunction *fn, + SmallSetVector &moveIntroducersToProcess, + DiagnosticEmitter &diagnosticEmitter); + +struct MoveOnlyAddressChecker { + SILFunction *fn; + DiagnosticEmitter &diagnosticEmitter; + borrowtodestructure::IntervalMapAllocator &allocator; + DominanceInfo *domTree; + PostOrderAnalysis *poa; + + /// \returns true if we changed the IR. To see if we emitted a diagnostic, use + /// \p diagnosticEmitter.getDiagnosticCount(). + bool check(SmallSetVector &moveIntroducersToProcess); +}; + +} // namespace siloptimizer + +} // namespace swift + +#endif diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureTransformTester.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureTester.cpp similarity index 84% rename from lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureTransformTester.cpp rename to lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureTester.cpp index ba11718ed7f13..df917e13201dc 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureTransformTester.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureTester.cpp @@ -1,4 +1,4 @@ -//===--- MoveOnlyBorrowToDestructureTransform.cpp -------------------------===// +//===--- MoveOnlyBorrowToDestructureTester.cpp ----------------------------===// // // This source file is part of the Swift.org open source project // @@ -20,17 +20,18 @@ #define DEBUG_TYPE "sil-move-only-checker" -#include "MoveOnlyBorrowToDestructure.h" +#include "MoveOnlyBorrowToDestructureUtils.h" #include "MoveOnlyDiagnostics.h" -#include "MoveOnlyObjectChecker.h" +#include "MoveOnlyObjectCheckerUtils.h" +#include "MoveOnlyUtils.h" +#include "swift/Basic/BlotSetVector.h" #include "swift/Basic/Defer.h" +#include "swift/Basic/FrozenMultiMap.h" #include "swift/SIL/SILBuilder.h" #include "swift/SIL/SILInstruction.h" #include "swift/SILOptimizer/Analysis/Analysis.h" #include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h" -#include "swift/Basic/BlotSetVector.h" -#include "swift/Basic/FrozenMultiMap.h" #include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h" #include "swift/SILOptimizer/PassManager/Passes.h" #include "swift/SILOptimizer/PassManager/Transforms.h" @@ -88,28 +89,30 @@ class MoveOnlyBorrowToDestructureTransformPass : public SILFunctionTransform { auto *postOrderAnalysis = getAnalysis(); SmallSetVector moveIntroducersToProcess; - DiagnosticEmitter emitter; + DiagnosticEmitter diagnosticEmitter; + unsigned diagCount = diagnosticEmitter.getDiagnosticCount(); bool madeChange = searchForCandidateObjectMarkMustChecks( - getFunction(), moveIntroducersToProcess, emitter); + getFunction(), moveIntroducersToProcess, diagnosticEmitter); if (madeChange) { invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); } - if (emitter.emittedAnyDiagnostics()) { - if (cleanupSILAfterEmittingObjectMoveOnlyDiagnostics(fn)) + if (diagCount != diagnosticEmitter.getDiagnosticCount()) { + if (cleanupNonCopyableCopiesAfterEmittingDiagnostic(fn)) invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); return; } + diagCount = diagnosticEmitter.getDiagnosticCount(); auto introducers = llvm::makeArrayRef(moveIntroducersToProcess.begin(), moveIntroducersToProcess.end()); - if (runTransform(fn, introducers, postOrderAnalysis, emitter)) { + if (runTransform(fn, introducers, postOrderAnalysis, diagnosticEmitter)) { invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); } - if (emitter.emittedAnyDiagnostics()) { - if (cleanupSILAfterEmittingObjectMoveOnlyDiagnostics(fn)) + if (diagCount != diagnosticEmitter.getDiagnosticCount()) { + if (cleanupNonCopyableCopiesAfterEmittingDiagnostic(fn)) invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); } } diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureTransform.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureUtils.cpp similarity index 99% rename from lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureTransform.cpp rename to lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureUtils.cpp index 6bfbc1c54e783..ae9a9f7b50561 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureTransform.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureUtils.cpp @@ -25,9 +25,9 @@ #define DEBUG_TYPE "sil-move-only-checker" -#include "MoveOnlyBorrowToDestructure.h" +#include "MoveOnlyBorrowToDestructureUtils.h" #include "MoveOnlyDiagnostics.h" -#include "MoveOnlyObjectChecker.h" +#include "MoveOnlyObjectCheckerUtils.h" #include "swift/Basic/BlotSetVector.h" #include "swift/Basic/Defer.h" @@ -319,8 +319,7 @@ bool Implementation::gatherUses(SILValue value) { {nextUse, {*leafRange, false /*is lifetime ending*/}}); liveness.updateForUse(nextUse->getUser(), *leafRange, false /*is lifetime ending*/); - instToInterestingOperandIndexMap.insert(nextUse->getUser(), - nextUse); + instToInterestingOperandIndexMap.insert(nextUse->getUser(), nextUse); continue; } @@ -341,13 +340,11 @@ bool Implementation::gatherUses(SILValue value) { LLVM_DEBUG(llvm::dbgs() << " Found lifetime ending use!\n"); destructureNeedingUses.push_back(nextUse); - blocksToUses.insert( - nextUse->getParentBlock(), - {nextUse, {*leafRange, true /*is lifetime ending*/}}); + blocksToUses.insert(nextUse->getParentBlock(), + {nextUse, {*leafRange, true /*is lifetime ending*/}}); liveness.updateForUse(nextUse->getUser(), *leafRange, true /*is lifetime ending*/); - instToInterestingOperandIndexMap.insert(nextUse->getUser(), - nextUse); + instToInterestingOperandIndexMap.insert(nextUse->getUser(), nextUse); continue; } @@ -387,8 +384,7 @@ void Implementation::checkForErrorsOnSameInstruction() { instToInterestingOperandIndexMap.setFrozen(); SmallBitVector usedBits(liveness.getNumSubElements()); - for (auto instRangePair : - instToInterestingOperandIndexMap.getRange()) { + for (auto instRangePair : instToInterestingOperandIndexMap.getRange()) { SWIFT_DEFER { usedBits.reset(); }; // First loop through our uses and handle any consuming twice errors. We diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructure.h b/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureUtils.h similarity index 100% rename from lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructure.h rename to lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureUtils.h diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyChecker.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyChecker.cpp new file mode 100644 index 0000000000000..020e939883d0e --- /dev/null +++ b/lib/SILOptimizer/Mandatory/MoveOnlyChecker.cpp @@ -0,0 +1,189 @@ +//===--- MoveOnlyChecker.cpp ----------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#define DEBUG_TYPE "sil-move-only-checker" + +#include "swift/AST/AccessScope.h" +#include "swift/AST/DiagnosticEngine.h" +#include "swift/AST/DiagnosticsSIL.h" +#include "swift/Basic/Debug.h" +#include "swift/Basic/Defer.h" +#include "swift/Basic/FrozenMultiMap.h" +#include "swift/Basic/SmallBitVector.h" +#include "swift/SIL/ApplySite.h" +#include "swift/SIL/BasicBlockBits.h" +#include "swift/SIL/BasicBlockData.h" +#include "swift/SIL/BasicBlockDatastructures.h" +#include "swift/SIL/BasicBlockUtils.h" +#include "swift/SIL/Consumption.h" +#include "swift/SIL/DebugUtils.h" +#include "swift/SIL/FieldSensitivePrunedLiveness.h" +#include "swift/SIL/InstructionUtils.h" +#include "swift/SIL/MemAccessUtils.h" +#include "swift/SIL/OwnershipUtils.h" +#include "swift/SIL/PrunedLiveness.h" +#include "swift/SIL/SILArgument.h" +#include "swift/SIL/SILArgumentConvention.h" +#include "swift/SIL/SILBasicBlock.h" +#include "swift/SIL/SILBuilder.h" +#include "swift/SIL/SILFunction.h" +#include "swift/SIL/SILInstruction.h" +#include "swift/SIL/SILUndef.h" +#include "swift/SIL/SILValue.h" +#include "swift/SILOptimizer/Analysis/ClosureScope.h" +#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h" +#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" +#include "swift/SILOptimizer/Analysis/NonLocalAccessBlockAnalysis.h" +#include "swift/SILOptimizer/PassManager/Transforms.h" +#include "swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h" +#include "swift/SILOptimizer/Utils/InstructionDeleter.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/MapVector.h" +#include "llvm/ADT/PointerIntPair.h" +#include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallBitVector.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/ErrorHandling.h" + +#include "MoveOnlyAddressCheckerUtils.h" +#include "MoveOnlyDiagnostics.h" +#include "MoveOnlyObjectCheckerUtils.h" +#include "MoveOnlyUtils.h" + +using namespace swift; +using namespace swift::siloptimizer; + +//===----------------------------------------------------------------------===// +// MARK: Top Level Object Entrypoint +//===----------------------------------------------------------------------===// + +namespace { + +struct MoveOnlyChecker { + DiagnosticEmitter diagnosticEmitter; + SILFunction *fn; + DominanceInfo *domTree; + PostOrderAnalysis *poa; + bool madeChange = false; + borrowtodestructure::IntervalMapAllocator allocator; + + MoveOnlyChecker(SILFunction *fn, DominanceInfo *domTree, + PostOrderAnalysis *poa) + : fn(fn), domTree(domTree), poa(poa) {} + + void checkObjects(); + void checkAddresses(); +}; + +} // namespace + +void MoveOnlyChecker::checkObjects() { + SmallSetVector moveIntroducersToProcess; + unsigned diagCount = diagnosticEmitter.getDiagnosticCount(); + madeChange |= searchForCandidateObjectMarkMustChecks( + fn, moveIntroducersToProcess, diagnosticEmitter); + + LLVM_DEBUG( + llvm::dbgs() + << "Emitting diagnostic when checking for mark must check inst: " + << (diagCount != diagnosticEmitter.getDiagnosticCount() ? "yes" : "no") + << '\n'); + + if (moveIntroducersToProcess.empty()) { + LLVM_DEBUG(llvm::dbgs() + << "No move introducers found?! Returning early?!\n"); + return; + } + + MoveOnlyObjectChecker checker{diagnosticEmitter, domTree, poa, allocator}; + madeChange |= checker.check(moveIntroducersToProcess); +} + +void MoveOnlyChecker::checkAddresses() { + unsigned diagCount = diagnosticEmitter.getDiagnosticCount(); + SmallSetVector moveIntroducersToProcess; + searchForCandidateAddressMarkMustChecks(fn, moveIntroducersToProcess, + diagnosticEmitter); + + LLVM_DEBUG( + llvm::dbgs() + << "Emitting diagnostic when checking for mark must check inst: " + << (diagCount != diagnosticEmitter.getDiagnosticCount() ? "yes" : "no") + << '\n'); + + if (moveIntroducersToProcess.empty()) { + LLVM_DEBUG(llvm::dbgs() + << "No move introducers found?! Returning early?!\n"); + return; + } + + MoveOnlyAddressChecker checker{fn, diagnosticEmitter, allocator, domTree, + poa}; + madeChange |= checker.check(moveIntroducersToProcess); +} + +//===----------------------------------------------------------------------===// +// MARK: Top Level Entrypoint +//===----------------------------------------------------------------------===// + +namespace { + +class MoveOnlyCheckerPass : public SILFunctionTransform { + void run() override { + auto *fn = getFunction(); + + // Only run this pass if the move only language feature is enabled. + if (!fn->getASTContext().LangOpts.Features.contains(Feature::MoveOnly)) + return; + + // Don't rerun diagnostics on deserialized functions. + if (getFunction()->wasDeserializedCanonical()) + return; + + assert(fn->getModule().getStage() == SILStage::Raw && + "Should only run on Raw SIL"); + + LLVM_DEBUG(llvm::dbgs() + << "===> MoveOnly Checker. Visiting: " << fn->getName() << '\n'); + + MoveOnlyChecker checker( + getFunction(), getAnalysis()->get(getFunction()), + getAnalysis()); + + checker.checkObjects(); + checker.checkAddresses(); + + // If we did not emit any diagnostics, emit an error on any copies that + // remain. If we emitted a diagnostic, we just want to rewrite all of the + // non-copyable copies into explicit variants below and let the user + // recompile. + if (!checker.diagnosticEmitter.getDiagnosticCount()) { + emitCheckerMissedCopyOfNonCopyableTypeErrors(getFunction(), + checker.diagnosticEmitter); + } + + checker.madeChange |= + cleanupNonCopyableCopiesAfterEmittingDiagnostic(getFunction()); + + if (checker.madeChange) + invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); + } +}; + +} // namespace + +SILTransform *swift::createMoveOnlyChecker() { + return new MoveOnlyCheckerPass(); +} diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp index 5ed585d785f98..b7944acf5d565 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp @@ -196,6 +196,8 @@ void DiagnosticEmitter::emitObjectGuaranteedDiagnostic( if (!getCanonicalizer().hasNonPartialApplyConsumingUse()) return; + registerDiagnosticEmitted(markedValue); + // Check if this value is closure captured. In such a case, emit a special // error. if (auto *fArg = dyn_cast( @@ -215,11 +217,12 @@ void DiagnosticEmitter::emitObjectGuaranteedDiagnostic( diag::sil_moveonlychecker_guaranteed_value_consumed, varName); emitObjectDiagnosticsForGuaranteedUses(true /*ignore partial apply uses*/); - registerDiagnosticEmitted(markedValue); } void DiagnosticEmitter::emitObjectOwnedDiagnostic( MarkMustCheckInst *markedValue) { + registerDiagnosticEmitted(markedValue); + auto &astContext = fn->getASTContext(); SmallString<64> varName; getVariableNameForValue(markedValue, varName); @@ -339,8 +342,6 @@ void DiagnosticEmitter::emitObjectOwnedDiagnostic( } } } - - registerDiagnosticEmitted(markedValue); } void DiagnosticEmitter::emitObjectDiagnosticsForGuaranteedUses( diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.h b/lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.h index 75a5c2be3eb6d..50aaae9875c80 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.h +++ b/lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.h @@ -19,7 +19,7 @@ #ifndef SWIFT_SILOPTIMIZER_MANDATORY_MOVEONLYDIAGNOSTICS_H #define SWIFT_SILOPTIMIZER_MANDATORY_MOVEONLYDIAGNOSTICS_H -#include "MoveOnlyObjectChecker.h" +#include "MoveOnlyObjectCheckerUtils.h" #include "swift/Basic/NullablePtr.h" #include "swift/SIL/FieldSensitivePrunedLiveness.h" #include "swift/SIL/SILInstruction.h" @@ -79,8 +79,6 @@ class DiagnosticEmitter { void emitObjectGuaranteedDiagnostic(MarkMustCheckInst *markedValue); void emitObjectOwnedDiagnostic(MarkMustCheckInst *markedValue); - bool emittedAnyDiagnostics() const { return valuesWithDiagnostics.size(); } - bool emittedDiagnosticForValue(MarkMustCheckInst *markedValue) const { return valuesWithDiagnostics.count(markedValue); } diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerTester.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerTester.cpp new file mode 100644 index 0000000000000..41814e996c5b0 --- /dev/null +++ b/lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerTester.cpp @@ -0,0 +1,137 @@ +//===--- MoveOnlyObjectCheckerTester.cpp ----------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#define DEBUG_TYPE "sil-move-only-checker" + +#include "swift/AST/DiagnosticsSIL.h" +#include "swift/AST/TypeCheckRequests.h" +#include "swift/Basic/Defer.h" +#include "swift/Basic/FrozenMultiMap.h" +#include "swift/Basic/STLExtras.h" +#include "swift/SIL/BasicBlockBits.h" +#include "swift/SIL/BasicBlockUtils.h" +#include "swift/SIL/DebugUtils.h" +#include "swift/SIL/FieldSensitivePrunedLiveness.h" +#include "swift/SIL/InstructionUtils.h" +#include "swift/SIL/NodeBits.h" +#include "swift/SIL/OwnershipUtils.h" +#include "swift/SIL/PostOrder.h" +#include "swift/SIL/PrunedLiveness.h" +#include "swift/SIL/SILArgument.h" +#include "swift/SIL/SILBuilder.h" +#include "swift/SIL/SILFunction.h" +#include "swift/SIL/SILInstruction.h" +#include "swift/SIL/SILLocation.h" +#include "swift/SIL/SILUndef.h" +#include "swift/SIL/SILValue.h" +#include "swift/SIL/StackList.h" +#include "swift/SILOptimizer/Analysis/ClosureScope.h" +#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h" +#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" +#include "swift/SILOptimizer/Analysis/NonLocalAccessBlockAnalysis.h" +#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h" +#include "swift/SILOptimizer/PassManager/Transforms.h" +#include "swift/SILOptimizer/Utils/CFGOptUtils.h" +#include "swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h" +#include "swift/SILOptimizer/Utils/InstructionDeleter.h" +#include "swift/SILOptimizer/Utils/SILSSAUpdater.h" +#include "clang/AST/DeclTemplate.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/IntervalMap.h" +#include "llvm/ADT/PointerIntPair.h" +#include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallBitVector.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Allocator.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/RecyclingAllocator.h" + +#include "MoveOnlyBorrowToDestructureUtils.h" +#include "MoveOnlyDiagnostics.h" +#include "MoveOnlyObjectCheckerUtils.h" +#include "MoveOnlyUtils.h" + +using namespace swift; +using namespace swift::siloptimizer; + +//===----------------------------------------------------------------------===// +// MARK: Top Level Entrypoint +//===----------------------------------------------------------------------===// + +namespace { + +class MoveOnlyObjectCheckerTesterPass : public SILFunctionTransform { + void run() override { + auto *fn = getFunction(); + + // Only run this pass if the move only language feature is enabled. + if (!fn->getASTContext().LangOpts.Features.contains(Feature::MoveOnly)) + return; + + // Don't rerun diagnostics on deserialized functions. + if (getFunction()->wasDeserializedCanonical()) + return; + + assert(fn->getModule().getStage() == SILStage::Raw && + "Should only run on Raw SIL"); + + LLVM_DEBUG(llvm::dbgs() << "===> MoveOnly Object Checker. Visiting: " + << fn->getName() << '\n'); + + auto *dominanceAnalysis = getAnalysis(); + DominanceInfo *domTree = dominanceAnalysis->get(fn); + auto *poa = getAnalysis(); + + DiagnosticEmitter diagnosticEmitter; + borrowtodestructure::IntervalMapAllocator allocator; + + unsigned diagCount = diagnosticEmitter.getDiagnosticCount(); + SmallSetVector moveIntroducersToProcess; + bool madeChange = searchForCandidateObjectMarkMustChecks( + fn, moveIntroducersToProcess, diagnosticEmitter); + + LLVM_DEBUG(llvm::dbgs() + << "Emitting diagnostic when checking for mark must check inst: " + << (diagCount != diagnosticEmitter.getDiagnosticCount() ? "yes" + : "no") + << '\n'); + + if (moveIntroducersToProcess.empty()) { + LLVM_DEBUG(llvm::dbgs() + << "No move introducers found?! Returning early?!\n"); + } else { + diagCount = diagnosticEmitter.getDiagnosticCount(); + MoveOnlyObjectChecker checker{diagnosticEmitter, domTree, poa, allocator}; + madeChange |= checker.check(moveIntroducersToProcess); + } + + if (diagCount != diagnosticEmitter.getDiagnosticCount()) { + emitCheckerMissedCopyOfNonCopyableTypeErrors(getFunction(), + diagnosticEmitter); + } + + madeChange |= + cleanupNonCopyableCopiesAfterEmittingDiagnostic(getFunction()); + + if (madeChange) { + invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); + } + } +}; + +} // anonymous namespace + +SILTransform *swift::createMoveOnlyObjectChecker() { + return new MoveOnlyObjectCheckerTesterPass(); +} diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyObjectChecker.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerUtils.cpp similarity index 79% rename from lib/SILOptimizer/Mandatory/MoveOnlyObjectChecker.cpp rename to lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerUtils.cpp index e1f936ff4c5a9..b7b56e72270e2 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyObjectChecker.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerUtils.cpp @@ -57,9 +57,9 @@ #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/RecyclingAllocator.h" -#include "MoveOnlyBorrowToDestructure.h" +#include "MoveOnlyBorrowToDestructureUtils.h" #include "MoveOnlyDiagnostics.h" -#include "MoveOnlyObjectChecker.h" +#include "MoveOnlyObjectCheckerUtils.h" using namespace swift; using namespace swift::siloptimizer; @@ -272,51 +272,6 @@ bool swift::siloptimizer::searchForCandidateObjectMarkMustChecks( return localChanged; } -//===----------------------------------------------------------------------===// -// MARK: Cleanup After Emitting Diagnostic -//===----------------------------------------------------------------------===// - -bool swift::siloptimizer::cleanupSILAfterEmittingObjectMoveOnlyDiagnostics( - SILFunction *fn) { - bool localChanged = false; - for (auto &block : *fn) { - for (auto ii = block.begin(), ie = block.end(); ii != ie;) { - if (auto *cvi = dyn_cast(&*ii)) { - ++ii; - - if (!cvi || !cvi->getOperand()->getType().isMoveOnly()) - continue; - - SILBuilderWithScope b(cvi); - auto *expCopy = - b.createExplicitCopyValue(cvi->getLoc(), cvi->getOperand()); - cvi->replaceAllUsesWith(expCopy); - cvi->eraseFromParent(); - localChanged = true; - continue; - } - - // Also eliminate any mark_must_check on objects, just to be safe. We - // emitted an object level diagnostic and if the user wants to get more - // diagnostics, they should fix these diagnostics and recompile. - if (auto *mmci = dyn_cast(&*ii)) { - ++ii; - - if (mmci->getType().isAddress()) - continue; - - mmci->replaceAllUsesWith(mmci->getOperand()); - mmci->eraseFromParent(); - localChanged = true; - continue; - } - - ++ii; - } - } - return localChanged; -} - //===----------------------------------------------------------------------===// // MARK: OSSACanonicalizer //===----------------------------------------------------------------------===// @@ -383,46 +338,36 @@ bool OSSACanonicalizer::canonicalize(SILValue value) { namespace { -struct MoveOnlyChecker { +struct MoveOnlyObjectCheckerPImpl { SILFunction *fn; - - bool changed = false; + borrowtodestructure::IntervalMapAllocator &allocator; + DiagnosticEmitter &diagnosticEmitter; /// A set of mark_must_check that we are actually going to process. - SmallSetVector moveIntroducersToProcess; + llvm::SmallSetVector &moveIntroducersToProcess; - borrowtodestructure::IntervalMapAllocator allocator; + bool changed = false; - MoveOnlyChecker(SILFunction *fn, DeadEndBlocks *deBlocks) : fn(fn) {} + MoveOnlyObjectCheckerPImpl( + SILFunction *fn, borrowtodestructure::IntervalMapAllocator &allocator, + DiagnosticEmitter &diagnosticEmitter, + llvm::SmallSetVector &moveIntroducersToProcess) + : fn(fn), allocator(allocator), diagnosticEmitter(diagnosticEmitter), + moveIntroducersToProcess(moveIntroducersToProcess) {} void check(DominanceInfo *domTree, PostOrderAnalysis *poa); bool convertBorrowExtractsToOwnedDestructures(MarkMustCheckInst *mmci, - DiagnosticEmitter &emitter, DominanceInfo *domTree, PostOrderAnalysis *poa); - /// After we have emitted a diagnostic, we need to clean up the instruction - /// stream by converting /all/ copies of move only typed things to use - /// explicit_copy_value so that we maintain the SIL invariant that in - /// canonical SIL move only types are not copied by normal copies. - /// - /// Returns true if we actually changed any instructions. - bool cleanupAfterEmittingDiagnostic() { - bool localChange = cleanupSILAfterEmittingObjectMoveOnlyDiagnostics(fn); - changed |= localChange; - return localChange; - } - - bool checkForSameInstMultipleUseErrors(MarkMustCheckInst *base, - DiagnosticEmitter &emitter); + bool checkForSameInstMultipleUseErrors(MarkMustCheckInst *base); }; } // namespace -bool MoveOnlyChecker::convertBorrowExtractsToOwnedDestructures( - MarkMustCheckInst *mmci, DiagnosticEmitter &diagnosticEmitter, - DominanceInfo *domTree, PostOrderAnalysis *poa) { +bool MoveOnlyObjectCheckerPImpl::convertBorrowExtractsToOwnedDestructures( + MarkMustCheckInst *mmci, DominanceInfo *domTree, PostOrderAnalysis *poa) { BorrowToDestructureTransform transform(allocator, mmci, mmci, diagnosticEmitter, poa); if (!transform.transform()) { @@ -434,8 +379,8 @@ bool MoveOnlyChecker::convertBorrowExtractsToOwnedDestructures( return true; } -bool MoveOnlyChecker::checkForSameInstMultipleUseErrors( - MarkMustCheckInst *mmci, DiagnosticEmitter &diagnosticEmitter) { +bool MoveOnlyObjectCheckerPImpl::checkForSameInstMultipleUseErrors( + MarkMustCheckInst *mmci) { LLVM_DEBUG(llvm::dbgs() << "Checking for same inst multiple use error!\n"); SmallFrozenMultiMap instToOperandsMap; @@ -561,10 +506,11 @@ bool MoveOnlyChecker::checkForSameInstMultipleUseErrors( } //===----------------------------------------------------------------------===// -// MARK: Main Routine +// MARK: Main PImpl Routine //===----------------------------------------------------------------------===// -void MoveOnlyChecker::check(DominanceInfo *domTree, PostOrderAnalysis *poa) { +void MoveOnlyObjectCheckerPImpl::check(DominanceInfo *domTree, + PostOrderAnalysis *poa) { auto callbacks = InstModCallbacks().onDelete([&](SILInstruction *instToDelete) { if (auto *mvi = dyn_cast(instToDelete)) @@ -574,40 +520,12 @@ void MoveOnlyChecker::check(DominanceInfo *domTree, PostOrderAnalysis *poa) { InstructionDeleter deleter(std::move(callbacks)); OSSACanonicalizer canonicalizer; canonicalizer.init(fn, domTree, deleter); - DiagnosticEmitter diagnosticEmitter; diagnosticEmitter.init(fn, &canonicalizer); - // First search for candidates to process and emit diagnostics on any - // mark_must_check [noimplicitcopy] we didn't recognize. - bool madeChange = searchForCandidateObjectMarkMustChecks( - fn, moveIntroducersToProcess, diagnosticEmitter); - LLVM_DEBUG(llvm::dbgs() - << "Emitting diagnostic when checking for mark must check inst: " - << (diagnosticEmitter.emittedAnyDiagnostics() ? "yes" : "no") - << '\n'); - - // If we didn't find any introducers to check, just return if we emitted an - // error (which is the only way we emitted a change to the instruction - // stream). - // - // NOTE: changed /can/ be true here if we had any mark_must_check - // [noimplicitcopy] that we didn't understand and emitting a diagnostic upon - // and then deleting. - if (moveIntroducersToProcess.empty()) { - LLVM_DEBUG(llvm::dbgs() - << "No move introducers found?! Returning early?!\n"); - if (madeChange) { - cleanupAfterEmittingDiagnostic(); - } - return; - } + unsigned initialDiagCount = diagnosticEmitter.getDiagnosticCount(); auto moveIntroducers = llvm::makeArrayRef(moveIntroducersToProcess.begin(), moveIntroducersToProcess.end()); - for (auto *introducer : moveIntroducers) { - LLVM_DEBUG(llvm::dbgs() << "Found move introducer: " << *introducer); - } - while (!moveIntroducers.empty()) { SWIFT_DEFER { canonicalizer.clear(); }; @@ -617,9 +535,8 @@ void MoveOnlyChecker::check(DominanceInfo *domTree, PostOrderAnalysis *poa) { // Before we do anything, we need to look for borrowed extracted values and // convert them to destructure operations. - unsigned diagnosticCount = diagnosticEmitter.getDiagnosticCount(); - if (!convertBorrowExtractsToOwnedDestructures( - markedValue, diagnosticEmitter, domTree, poa)) { + unsigned diagCount = diagnosticEmitter.getDiagnosticCount(); + if (!convertBorrowExtractsToOwnedDestructures(markedValue, domTree, poa)) { LLVM_DEBUG(llvm::dbgs() << "Borrow extract to owned destructure transformation didn't " "understand part of the SIL\n"); @@ -632,7 +549,7 @@ void MoveOnlyChecker::check(DominanceInfo *domTree, PostOrderAnalysis *poa) { // instruction. The user can fix and re-compile. We want the OSSA // canonicalizer to be able to assume that all such borrow + struct_extract // uses were already handled. - if (diagnosticCount != diagnosticEmitter.getDiagnosticCount()) { + if (diagCount != diagnosticEmitter.getDiagnosticCount()) { LLVM_DEBUG(llvm::dbgs() << "Emitting diagnostic in BorrowExtractToOwnedDestructure " "transformation!\n"); @@ -642,14 +559,14 @@ void MoveOnlyChecker::check(DominanceInfo *domTree, PostOrderAnalysis *poa) { // First search for transitive consuming uses and prove that we do not have // any errors where a single instruction consumes the same value twice or // consumes and uses a value. - if (!checkForSameInstMultipleUseErrors(markedValue, diagnosticEmitter)) { + if (!checkForSameInstMultipleUseErrors(markedValue)) { LLVM_DEBUG(llvm::dbgs() << "checkForSameInstMultipleUseError didn't " "understand part of the SIL\n"); diagnosticEmitter.emitCheckerDoesntUnderstandDiagnostic(markedValue); continue; } - if (diagnosticCount != diagnosticEmitter.getDiagnosticCount()) { + if (diagCount != diagnosticEmitter.getDiagnosticCount()) { LLVM_DEBUG(llvm::dbgs() << "Found single inst multiple user error!\n"); continue; } @@ -707,7 +624,8 @@ void MoveOnlyChecker::check(DominanceInfo *domTree, PostOrderAnalysis *poa) { canonicalizer.rewriteLifetimes(); } - bool emittedDiagnostic = diagnosticEmitter.emittedAnyDiagnostics(); + bool emittedDiagnostic = + initialDiagCount != diagnosticEmitter.getDiagnosticCount(); LLVM_DEBUG(llvm::dbgs() << "Emitting checker based diagnostic: " << (emittedDiagnostic ? "yes" : "no") << '\n'); @@ -760,62 +678,18 @@ void MoveOnlyChecker::check(DominanceInfo *domTree, PostOrderAnalysis *poa) { markedInst->eraseFromParent(); changed = true; } - - // Once we have finished processing, if we emitted any diagnostics, then we - // may have copy_value of move only and @moveOnly wrapped type values. This is - // not valid in Canonical SIL, so we need to ensure that those copy_value - // become explicit_copy_value. This is ok to do since we are already going to - // fail the compilation and just are trying to maintain SIL invariants. - // - // It is also ok that we use a little more compile time and go over the - // function again, since we are going to fail the compilation and not codegen. - if (emittedDiagnostic) { - changed |= cleanupAfterEmittingDiagnostic(); - } } //===----------------------------------------------------------------------===// -// MARK: Top Level Entrypoint +// MARK: Driver Routine //===----------------------------------------------------------------------===// -namespace { - -class MoveOnlyCheckerPass : public SILFunctionTransform { - void run() override { - auto *fn = getFunction(); - - // Only run this pass if the move only language feature is enabled. - if (!fn->getASTContext().LangOpts.Features.contains(Feature::MoveOnly)) - return; - - // Don't rerun diagnostics on deserialized functions. - if (getFunction()->wasDeserializedCanonical()) - return; - - assert(fn->getModule().getStage() == SILStage::Raw && - "Should only run on Raw SIL"); - - LLVM_DEBUG(llvm::dbgs() << "===> MoveOnly Object Checker. Visiting: " - << fn->getName() << '\n'); - - auto *dominanceAnalysis = getAnalysis(); - DominanceInfo *domTree = dominanceAnalysis->get(fn); - auto *deAnalysis = getAnalysis()->get(fn); - auto *postOrderAnalysis = getAnalysis(); - - MoveOnlyChecker checker(getFunction(), deAnalysis); - checker.check(domTree, postOrderAnalysis); - if (checker.changed) { - invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions); - } - - // NOTE: We validate in the MoveOnlyAddressChecker (which runs after this) - // that we eliminated all copy_value and emit errors otherwise. - } -}; - -} // anonymous namespace - -SILTransform *swift::createMoveOnlyObjectChecker() { - return new MoveOnlyCheckerPass(); +bool MoveOnlyObjectChecker::check( + llvm::SmallSetVector &instsToCheck) { + assert(instsToCheck.size() && + "Should only call this with actual insts to check?!"); + MoveOnlyObjectCheckerPImpl checker(instsToCheck[0]->getFunction(), allocator, + diagnosticEmitter, instsToCheck); + checker.check(domTree, poa); + return checker.changed; } diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyObjectChecker.h b/lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerUtils.h similarity index 86% rename from lib/SILOptimizer/Mandatory/MoveOnlyObjectChecker.h rename to lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerUtils.h index e643d78d834f6..e560b9a440a48 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyObjectChecker.h +++ b/lib/SILOptimizer/Mandatory/MoveOnlyObjectCheckerUtils.h @@ -1,4 +1,4 @@ -//===--- MoveOnlyObjectChecker.h ------------------------------------------===// +//===--- MoveOnlyObjectCheckerUtils.h -------------------------------------===// // // This source file is part of the Swift.org open source project // @@ -17,12 +17,14 @@ /// //===----------------------------------------------------------------------===// -#ifndef SWIFT_SILOPTIMIZER_MANDATORY_MOVEONLYOBJECTCHECKER_H -#define SWIFT_SILOPTIMIZER_MANDATORY_MOVEONLYOBJECTCHECKER_H +#ifndef SWIFT_SILOPTIMIZER_MANDATORY_MOVEONLYOBJECTCHECKERUTILS_H +#define SWIFT_SILOPTIMIZER_MANDATORY_MOVEONLYOBJECTCHECKERUTILS_H #include "swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h" #include "llvm/Support/Compiler.h" +#include "MoveOnlyBorrowToDestructureUtils.h" + namespace swift { namespace siloptimizer { @@ -121,16 +123,26 @@ struct OSSACanonicalizer { /// \returns true if we deleted a mark_must_check inst that we didn't recognize /// after emitting the diagnostic. /// -/// To check if an error was emitted call checker.emittedAnyDiagnostics(). +/// To check if an error was emitted call \p +/// diagnosticEmitter.getDiagnosticCount(). /// /// NOTE: This is the routine used by the move only object checker to find mark /// must checks to process. bool searchForCandidateObjectMarkMustChecks( SILFunction *fn, SmallSetVector &moveIntroducersToProcess, - DiagnosticEmitter &emitter); + DiagnosticEmitter &diagnosticEmitter); + +struct MoveOnlyObjectChecker { + DiagnosticEmitter &diagnosticEmitter; + DominanceInfo *domTree; + PostOrderAnalysis *poa; + borrowtodestructure::IntervalMapAllocator &allocator; -bool cleanupSILAfterEmittingObjectMoveOnlyDiagnostics(SILFunction *fn); + /// Returns true if we changed the IR in any way. Check with \p + /// diagnosticEmitter to see if we emitted any diagnostics. + bool check(llvm::SmallSetVector &instsToCheck); +}; } // namespace siloptimizer } // namespace swift diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyUtils.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyUtils.cpp new file mode 100644 index 0000000000000..ac635e6fc773b --- /dev/null +++ b/lib/SILOptimizer/Mandatory/MoveOnlyUtils.cpp @@ -0,0 +1,169 @@ +//===--- MoveOnlyUtils.cpp ------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#define DEBUG_TYPE "sil-move-only-checker" + +#include "swift/AST/AccessScope.h" +#include "swift/AST/DiagnosticEngine.h" +#include "swift/AST/DiagnosticsSIL.h" +#include "swift/Basic/Debug.h" +#include "swift/Basic/Defer.h" +#include "swift/Basic/FrozenMultiMap.h" +#include "swift/Basic/SmallBitVector.h" +#include "swift/SIL/ApplySite.h" +#include "swift/SIL/BasicBlockBits.h" +#include "swift/SIL/BasicBlockData.h" +#include "swift/SIL/BasicBlockDatastructures.h" +#include "swift/SIL/BasicBlockUtils.h" +#include "swift/SIL/Consumption.h" +#include "swift/SIL/DebugUtils.h" +#include "swift/SIL/FieldSensitivePrunedLiveness.h" +#include "swift/SIL/InstructionUtils.h" +#include "swift/SIL/MemAccessUtils.h" +#include "swift/SIL/OwnershipUtils.h" +#include "swift/SIL/PrunedLiveness.h" +#include "swift/SIL/SILArgument.h" +#include "swift/SIL/SILArgumentConvention.h" +#include "swift/SIL/SILBasicBlock.h" +#include "swift/SIL/SILBuilder.h" +#include "swift/SIL/SILFunction.h" +#include "swift/SIL/SILInstruction.h" +#include "swift/SIL/SILUndef.h" +#include "swift/SIL/SILValue.h" +#include "swift/SILOptimizer/Analysis/ClosureScope.h" +#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h" +#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h" +#include "swift/SILOptimizer/Analysis/NonLocalAccessBlockAnalysis.h" +#include "swift/SILOptimizer/PassManager/Transforms.h" +#include "swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h" +#include "swift/SILOptimizer/Utils/InstructionDeleter.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/MapVector.h" +#include "llvm/ADT/PointerIntPair.h" +#include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallBitVector.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/ErrorHandling.h" + +#include "MoveOnlyDiagnostics.h" +#include "MoveOnlyUtils.h" + +using namespace swift; +using namespace swift::siloptimizer; + +//===----------------------------------------------------------------------===// +// MARK: Missed Copy Diagnostic +//===----------------------------------------------------------------------===// + +/// A small diagnostic helper that causes us to emit a diagnostic error upon any +/// copies we did not eliminate and ask the user for a test case. +void swift::siloptimizer::emitCheckerMissedCopyOfNonCopyableTypeErrors( + SILFunction *fn, DiagnosticEmitter &diagnosticEmitter) { + for (auto &block : *fn) { + for (auto &inst : block) { + if (auto *cvi = dyn_cast(&inst)) { + if (cvi->getOperand()->getType().isMoveOnly()) { + LLVM_DEBUG(llvm::dbgs() + << "Emitting missed copy error for: " << *cvi); + diagnosticEmitter.emitCheckedMissedCopyError(cvi); + } + continue; + } + + if (auto *li = dyn_cast(&inst)) { + if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Copy && + li->getType().isMoveOnly()) { + LLVM_DEBUG(llvm::dbgs() << "Emitting missed copy error for: " << *li); + diagnosticEmitter.emitCheckedMissedCopyError(li); + } + continue; + } + + if (auto *copyAddr = dyn_cast(&inst)) { + if (!copyAddr->isTakeOfSrc() && + copyAddr->getSrc()->getType().isMoveOnly()) { + LLVM_DEBUG(llvm::dbgs() + << "Emitting missed copy error for: " << *copyAddr); + diagnosticEmitter.emitCheckedMissedCopyError(copyAddr); + } + continue; + } + } + } +} + +//===----------------------------------------------------------------------===// +// MARK: Cleanup After Emitting Diagnostic +//===----------------------------------------------------------------------===// + +bool swift::siloptimizer::cleanupNonCopyableCopiesAfterEmittingDiagnostic( + SILFunction *fn) { + bool changed = false; + for (auto &block : *fn) { + for (auto ii = block.begin(), ie = block.end(); ii != ie;) { + auto *inst = &*ii; + ++ii; + + // Convert load [copy] -> load_borrow + explicit_copy_value. + if (auto *li = dyn_cast(inst)) { + if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Copy) { + SILBuilderWithScope builder(li); + auto *lbi = builder.createLoadBorrow(li->getLoc(), li->getOperand()); + auto *cvi = builder.createExplicitCopyValue(li->getLoc(), lbi); + builder.createEndBorrow(li->getLoc(), lbi); + li->replaceAllUsesWith(cvi); + li->eraseFromParent(); + changed = true; + } + } + + // Convert copy_addr !take of src to its explicit value form so we don't + // error. + if (auto *copyAddr = dyn_cast(inst)) { + if (!copyAddr->isTakeOfSrc()) { + SILBuilderWithScope builder(copyAddr); + builder.createExplicitCopyAddr( + copyAddr->getLoc(), copyAddr->getSrc(), copyAddr->getDest(), + IsTake_t(copyAddr->isTakeOfSrc()), + IsInitialization_t(copyAddr->isInitializationOfDest())); + copyAddr->eraseFromParent(); + changed = true; + } + } + + // Convert any copy_value of move_only type to explicit copy value. + if (auto *cvi = dyn_cast(inst)) { + if (!cvi->getOperand()->getType().isMoveOnly()) + continue; + SILBuilderWithScope b(cvi); + auto *expCopy = + b.createExplicitCopyValue(cvi->getLoc(), cvi->getOperand()); + cvi->replaceAllUsesWith(expCopy); + cvi->eraseFromParent(); + changed = true; + continue; + } + + if (auto *mmci = dyn_cast(inst)) { + mmci->replaceAllUsesWith(mmci->getOperand()); + mmci->eraseFromParent(); + changed = true; + continue; + } + } + } + + return changed; +} diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyUtils.h b/lib/SILOptimizer/Mandatory/MoveOnlyUtils.h new file mode 100644 index 0000000000000..25ce3bf7e3881 --- /dev/null +++ b/lib/SILOptimizer/Mandatory/MoveOnlyUtils.h @@ -0,0 +1,35 @@ +//===--- MoveOnlyUtils.h --------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +#ifndef SWIFT_SILOPTIMIZER_MANDATORY_MOVEONLYUTILS_H +#define SWIFT_SILOPTIMIZER_MANDATORY_MOVEONLYUTILS_H + +namespace swift { + +class SILFunction; + +namespace siloptimizer { + +class DiagnosticEmitter; + +bool cleanupNonCopyableCopiesAfterEmittingDiagnostic(SILFunction *fn); + +/// Emit an error if we missed any copies when running markers. To check if a +/// diagnostic was emitted, use \p diagnosticEmitter.getDiagnosticCount(). +void emitCheckerMissedCopyOfNonCopyableTypeErrors( + SILFunction *fn, DiagnosticEmitter &diagnosticEmitter); + +} // namespace siloptimizer + +} // namespace swift + +#endif diff --git a/lib/SILOptimizer/Mandatory/OwnershipModelEliminator.cpp b/lib/SILOptimizer/Mandatory/OwnershipModelEliminator.cpp index 0ff77bf23757b..80b8610e4ea65 100644 --- a/lib/SILOptimizer/Mandatory/OwnershipModelEliminator.cpp +++ b/lib/SILOptimizer/Mandatory/OwnershipModelEliminator.cpp @@ -134,6 +134,7 @@ struct OwnershipModelEliminatorVisitor bool visitStoreBorrowInst(StoreBorrowInst *si); bool visitCopyValueInst(CopyValueInst *cvi); bool visitExplicitCopyValueInst(ExplicitCopyValueInst *cvi); + bool visitExplicitCopyAddrInst(ExplicitCopyAddrInst *cai); bool visitDestroyValueInst(DestroyValueInst *dvi); bool visitLoadBorrowInst(LoadBorrowInst *lbi); bool visitMoveValueInst(MoveValueInst *mvi) { @@ -334,6 +335,18 @@ bool OwnershipModelEliminatorVisitor::visitExplicitCopyValueInst( return true; } +bool OwnershipModelEliminatorVisitor::visitExplicitCopyAddrInst( + ExplicitCopyAddrInst *ecai) { + // Now that we have set the unqualified ownership flag, destroy value + // operation will delegate to the appropriate strong_release, etc. + withBuilder(ecai, [&](SILBuilder &b, SILLocation loc) { + b.createCopyAddr(loc, ecai->getSrc(), ecai->getDest(), ecai->isTakeOfSrc(), + ecai->isInitializationOfDest()); + }); + eraseInstruction(ecai); + return true; +} + bool OwnershipModelEliminatorVisitor::visitUnmanagedRetainValueInst( UnmanagedRetainValueInst *urvi) { // Now that we have set the unqualified ownership flag, destroy value diff --git a/lib/SILOptimizer/PassManager/PassPipeline.cpp b/lib/SILOptimizer/PassManager/PassPipeline.cpp index a72845bcb5124..84c250140066a 100644 --- a/lib/SILOptimizer/PassManager/PassPipeline.cpp +++ b/lib/SILOptimizer/PassManager/PassPipeline.cpp @@ -154,14 +154,8 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) { // resolution of nonescaping closure lifetimes to correctly check the use // of move-only values as captures in nonescaping closures as borrows. - // Check noImplicitCopy and move only types for objects - // - // NOTE: It is important that this is run /before/ move only address checker - // since if the address checker emits an error, it will cleanup copy_value of - // move only objects meaning that we could lose object level diagnostics. - P.addMoveOnlyObjectChecker(); - // Check noImplicitCopy and move only types for addresses. - P.addMoveOnlyAddressChecker(); + // Check noImplicitCopy and move only types for objects and addresses. + P.addMoveOnlyChecker(); // Convert last destroy_value to deinits. P.addMoveOnlyDeinitInsertion(); // Lower move only wrapped trivial types. diff --git a/test/SILOptimizer/ownership_model_eliminator.sil b/test/SILOptimizer/ownership_model_eliminator.sil index ed8150350bf1d..87a82d087a69d 100644 --- a/test/SILOptimizer/ownership_model_eliminator.sil +++ b/test/SILOptimizer/ownership_model_eliminator.sil @@ -381,3 +381,16 @@ bb0(%0 : @guaranteed $Builtin.NativeObject): %9999 = tuple() return %9999 : $() } + +// CHECK-LABEL: sil @lower_explicit_copy_addr : $@convention(thin) (@in_guaranteed C) -> () { +// CHECK: {{[^_]copy_addr}} +// CHECK: } // end sil function 'lower_explicit_copy_addr' +sil [ossa] @lower_explicit_copy_addr : $@convention(thin) (@in_guaranteed C) -> () { +bb0(%0 : $*C): + %1 = alloc_stack $C + explicit_copy_addr %0 to [init] %1 : $*C + destroy_addr %1 : $*C + dealloc_stack %1 : $*C + %9999 = tuple() + return %9999 : $() +} \ No newline at end of file