diff --git a/include/swift/Basic/FrozenMultiMap.h b/include/swift/Basic/FrozenMultiMap.h index 617fd64407266..a36f3e4704b1d 100644 --- a/include/swift/Basic/FrozenMultiMap.h +++ b/include/swift/Basic/FrozenMultiMap.h @@ -71,7 +71,7 @@ class FrozenMultiMap { // Since our array is sorted, we need to first find the first pair with our // inst as the first element. auto start = std::lower_bound( - storage.begin(), storage.end(), std::make_pair(key, Value()), + storage.begin(), storage.end(), std::make_pair(key, llvm::None), [&](const std::pair> &p1, const std::pair> &p2) { return p1.first < p2.first; diff --git a/include/swift/SIL/FieldSensitivePrunedLiveness.h b/include/swift/SIL/FieldSensitivePrunedLiveness.h index 1728e0955bd3d..456ba6c387bee 100644 --- a/include/swift/SIL/FieldSensitivePrunedLiveness.h +++ b/include/swift/SIL/FieldSensitivePrunedLiveness.h @@ -358,6 +358,13 @@ struct TypeTreeLeafTypeRange { endEltOffset >= range.endEltOffset; } + /// Sets each bit in \p bits corresponding to an element of this range. + void setBits(SmallBitVector &bits) { + for (auto element : getRange()) { + bits.set(element); + } + } + IntRange getRange() const { return range(startEltOffset, endEltOffset); } @@ -666,17 +673,60 @@ class FieldSensitivePrunedLiveness { FieldSensitivePrunedLiveBlocks liveBlocks; public: + enum IsInterestingUser { NonUser, NonLifetimeEndingUse, LifetimeEndingUse }; + struct InterestingUser { - TypeTreeLeafTypeRange subEltSpan; - bool isConsuming; + SmallBitVector liveBits; + SmallBitVector consumingBits; - InterestingUser() : subEltSpan(), isConsuming(false) {} - InterestingUser(TypeTreeLeafTypeRange subEltSpan, bool isConsuming) - : subEltSpan(subEltSpan), isConsuming(isConsuming) {} + InterestingUser(unsigned bitCount) + : liveBits(bitCount), consumingBits(bitCount) {} - InterestingUser &operator&=(bool otherValue) { - isConsuming &= otherValue; - return *this; + InterestingUser(unsigned bitCount, TypeTreeLeafTypeRange range, + bool lifetimeEnding) + : liveBits(bitCount), consumingBits(bitCount) { + addUses(range, lifetimeEnding); + } + + /// Record that the instruction uses the bits of the value in \p range. + void addUses(TypeTreeLeafTypeRange range, bool lifetimeEnding) { + range.setBits(liveBits); + if (lifetimeEnding) { + range.setBits(consumingBits); + } + } + + /// Populates the provided vector with contiguous ranges of bits which are + /// users of the same sort. + void getContiguousRanges( + SmallVectorImpl> + &ranges) const { + if (liveBits.size() == 0) + return; + + assert(ranges.empty()); + Optional> current = llvm::None; + for (unsigned bit = 0, size = liveBits.size(); bit < size; ++bit) { + auto interesting = isInterestingUser(bit); + if (!current) { + current = {bit, interesting}; + continue; + } + if (current->second != interesting) { + ranges.push_back( + {TypeTreeLeafTypeRange(current->first, bit), current->second}); + current = {bit, interesting}; + } + } + ranges.push_back({TypeTreeLeafTypeRange(current->first, liveBits.size()), + current->second}); + } + + IsInterestingUser isInterestingUser(unsigned element) const { + if (!liveBits.test(element)) + return NonUser; + return consumingBits.test(element) ? LifetimeEndingUse + : NonLifetimeEndingUse; } }; @@ -758,42 +808,6 @@ class FieldSensitivePrunedLiveness { return llvm::make_range(users.begin(), users.end()); } - using LifetimeEndingUserRange = OptionalTransformRange< - UserRange, - function_ref>( - const std::pair &)>>; - LifetimeEndingUserRange getAllLifetimeEndingUses() const { - assert(isInitialized()); - function_ref>( - const std::pair &)> - op; - op = [](const std::pair &pair) - -> Optional> { - if (pair.second.isConsuming) - return {{pair.first, pair.second.subEltSpan}}; - return None; - }; - return LifetimeEndingUserRange(getAllUsers(), op); - } - - using NonLifetimeEndingUserRange = OptionalTransformRange< - UserRange, - function_ref>( - const std::pair &)>>; - NonLifetimeEndingUserRange getAllNonLifetimeEndingUses() const { - assert(isInitialized()); - function_ref>( - const std::pair &)> - op; - op = [](const std::pair &pair) - -> Optional> { - if (!pair.second.isConsuming) - return {{pair.first, pair.second.subEltSpan}}; - return None; - }; - return NonLifetimeEndingUserRange(getAllUsers(), op); - } - using UserBlockRange = TransformRange< UserRange, function_ref &)>>; @@ -848,19 +862,37 @@ class FieldSensitivePrunedLiveness { SmallBitVector &liveOutBits, SmallBitVector &deadBits) const; - enum IsInterestingUser { NonUser, NonLifetimeEndingUse, LifetimeEndingUse }; + /// If \p user has had uses recored, return a pointer to the InterestingUser + /// where they've been recorded. + InterestingUser const *getInterestingUser(SILInstruction *user) const { + auto iter = users.find(user); + if (iter == users.end()) + return nullptr; + return &iter->second; + } - /// Return a result indicating whether the given user was identified as an - /// interesting use of the current def and whether it ends the lifetime. - std::pair> - isInterestingUser(SILInstruction *user) const { + /// How \p user uses the field at \p element. + IsInterestingUser isInterestingUser(SILInstruction *user, + unsigned element) const { assert(isInitialized()); - auto useIter = users.find(user); - if (useIter == users.end()) - return {NonUser, None}; - auto isInteresting = - useIter->second.isConsuming ? LifetimeEndingUse : NonLifetimeEndingUse; - return {isInteresting, useIter->second.subEltSpan}; + auto *record = getInterestingUser(user); + if (!record) + return NonUser; + return record->isInterestingUser(element); + } + + /// Whether \p user uses the fields in \p range as indicated by \p kind. + bool isInterestingUserOfKind(SILInstruction *user, IsInterestingUser kind, + TypeTreeLeafTypeRange range) const { + auto *record = getInterestingUser(user); + if (!record) + return kind == IsInterestingUser::NonUser; + + for (auto element : range.getRange()) { + if (isInterestingUser(user, element) != kind) + return false; + } + return true; } unsigned getNumSubElements() const { return liveBlocks.getNumBitsToTrack(); } @@ -886,10 +918,11 @@ class FieldSensitivePrunedLiveness { /// argument must be copied. void addInterestingUser(SILInstruction *user, TypeTreeLeafTypeRange range, bool lifetimeEnding) { - auto iterAndSuccess = - users.insert({user, InterestingUser(range, lifetimeEnding)}); - if (!iterAndSuccess.second) - iterAndSuccess.first->second &= lifetimeEnding; + auto iter = users.find(user); + if (iter == users.end()) { + iter = users.insert({user, InterestingUser(getNumSubElements())}).first; + } + iter->second.addUses(range, lifetimeEnding); } }; diff --git a/lib/SIL/IR/TypeLowering.cpp b/lib/SIL/IR/TypeLowering.cpp index 8ea1c2000ef79..2697129bbfa57 100644 --- a/lib/SIL/IR/TypeLowering.cpp +++ b/lib/SIL/IR/TypeLowering.cpp @@ -2394,6 +2394,7 @@ namespace { if (D->isMoveOnly()) { properties.setNonTrivial(); + properties.setLexical(IsLexical); if (properties.isAddressOnly()) return handleMoveOnlyAddressOnly(structType, properties); return new (TC) MoveOnlyLoadableStructTypeLowering( @@ -2475,6 +2476,7 @@ namespace { if (D->isMoveOnly()) { properties.setNonTrivial(); + properties.setLexical(IsLexical); if (properties.isAddressOnly()) return handleMoveOnlyAddressOnly(enumType, properties); return new (TC) diff --git a/lib/SIL/Utils/FieldSensitivePrunedLiveness.cpp b/lib/SIL/Utils/FieldSensitivePrunedLiveness.cpp index 27cbaebc45098..e80dbac5946d0 100644 --- a/lib/SIL/Utils/FieldSensitivePrunedLiveness.cpp +++ b/lib/SIL/Utils/FieldSensitivePrunedLiveness.cpp @@ -563,6 +563,38 @@ void FieldSensitivePrunedLiveBlocks::print(llvm::raw_ostream &OS) const { void FieldSensitivePrunedLiveBlocks::dump() const { print(llvm::dbgs()); } +//===----------------------------------------------------------------------===// +// FieldSensitivePrunedLivenessBoundary +//===----------------------------------------------------------------------===// + +void FieldSensitivePrunedLivenessBoundary::print(llvm::raw_ostream &OS) const { + for (auto pair : lastUsers) { + auto *user = pair.first; + auto bits = pair.second; + OS << "last user: " << *user + << "\tat " << bits << "\n"; + } + for (auto pair : boundaryEdges) { + auto *block = pair.first; + auto bits = pair.second; + OS << "boundary edge: "; + block->printAsOperand(OS); + OS << "\n" << "\tat " << bits << "\n"; + } + if (!deadDefs.empty()) { + for (auto pair : deadDefs) { + auto *deadDef = pair.first; + auto bits = pair.second; + OS << "dead def: " << *deadDef + << "\tat " << bits << "\n"; + } + } +} + +void FieldSensitivePrunedLivenessBoundary::dump() const { + print(llvm::dbgs()); +} + //===----------------------------------------------------------------------===// // MARK: FieldSensitiveLiveness //===----------------------------------------------------------------------===// @@ -673,9 +705,7 @@ bool FieldSensitivePrunedLiveRange::isWithinBoundary( // If we are not live and have an interesting user that maps to our bit, // mark this bit as being live again. if (!isLive) { - auto interestingUser = isInterestingUser(&blockInst); - bool isInteresting = - interestingUser.first && interestingUser.second->contains(bit); + bool isInteresting = isInterestingUser(&blockInst, bit); PRUNED_LIVENESS_LOG(llvm::dbgs() << " Inst was dead... Is InterestingUser: " << (isInteresting ? "true" : "false") << '\n'); @@ -806,8 +836,7 @@ void findBoundaryInNonDefBlock(SILBasicBlock *block, unsigned bitNo, PRUNED_LIVENESS_LOG(llvm::dbgs() << "Looking for boundary in non-def block\n"); for (SILInstruction &inst : llvm::reverse(*block)) { PRUNED_LIVENESS_LOG(llvm::dbgs() << "Visiting: " << inst); - auto interestingUser = liveness.isInterestingUser(&inst); - if (interestingUser.first && interestingUser.second->contains(bitNo)) { + if (liveness.isInterestingUser(&inst, bitNo)) { PRUNED_LIVENESS_LOG(llvm::dbgs() << " Is interesting user for this bit!\n"); boundary.getLastUserBits(&inst).set(bitNo); return; @@ -837,8 +866,7 @@ void findBoundaryInSSADefBlock(SILNode *ssaDef, unsigned bitNo, boundary.getDeadDefsBits(cast(&inst)).set(bitNo); return; } - auto interestingUser = liveness.isInterestingUser(&inst); - if (interestingUser.first && interestingUser.second->contains(bitNo)) { + if (liveness.isInterestingUser(&inst, bitNo)) { PRUNED_LIVENESS_LOG(llvm::dbgs() << " Found interesting user: " << inst); boundary.getLastUserBits(&inst).set(bitNo); return; @@ -973,8 +1001,7 @@ void FieldSensitiveMultiDefPrunedLiveRange::findBoundariesInBlock( PRUNED_LIVENESS_LOG(llvm::dbgs() << " Checking if this inst is also a last user...\n"); if (!isLive) { - auto interestingUser = isInterestingUser(&inst); - if (interestingUser.first && interestingUser.second->contains(bitNo)) { + if (isInterestingUser(&inst, bitNo)) { PRUNED_LIVENESS_LOG( llvm::dbgs() << " Was interesting user! Moving from dead -> live!\n"); diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp index 330931afa0507..bb9ab67d4bcb2 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp @@ -1150,9 +1150,8 @@ namespace { struct ConsumeInfo { /// Map blocks on the lifetime boundary to the last consuming instruction. - llvm::MapVector< - SILBasicBlock *, - SmallVector, 1>> + llvm::MapVector, 1>> finalBlockConsumes; bool isFrozen = false; @@ -1167,8 +1166,7 @@ struct ConsumeInfo { if (!inst) continue; os << "Inst: " << *inst; - os << "Range: "; - instRangePairVector.second.dump(); + os << "Range: " << instRangePairVector.second; os << '\n'; } } @@ -1191,13 +1189,15 @@ struct ConsumeInfo { return foundAny; } - void recordFinalConsume(SILInstruction *inst, TypeTreeLeafTypeRange span) { + void recordFinalConsume(SILInstruction *inst, SmallBitVector const &bits) { assert(!isFrozen); - auto iter = finalBlockConsumes.insert({inst->getParent(), {{inst, span}}}); - if (iter.second) - return; + auto *block = inst->getParent(); + auto iter = finalBlockConsumes.find(block); + if (iter == finalBlockConsumes.end()) { + iter = finalBlockConsumes.insert({block, {}}).first; + } LLVM_DEBUG(llvm::dbgs() << "Recorded Final Consume: " << *inst); - iter.first->second.emplace_back(inst, span); + iter->second.emplace_back(inst, bits); } void finishRecordingFinalConsumes() { @@ -1205,8 +1205,8 @@ struct ConsumeInfo { for (auto &pair : finalBlockConsumes) { llvm::stable_sort( pair.second, - [](const std::pair &lhs, - const std::pair &rhs) { + [](const std::pair &lhs, + const std::pair &rhs) { return lhs.first < rhs.first; }); } @@ -1222,7 +1222,7 @@ struct ConsumeInfo { // operands. // // Can only be used once frozen. - bool claimConsume(SILInstruction *inst, TypeTreeLeafTypeRange range) { + bool claimConsume(SILInstruction *inst, SmallBitVector const &bits) { assert(isFrozen); bool claimedConsume = false; @@ -1230,7 +1230,7 @@ struct ConsumeInfo { auto &iter = finalBlockConsumes[inst->getParent()]; for (unsigned i : indices(iter)) { auto &instRangePair = iter[i]; - if (instRangePair.first == inst && instRangePair.second == range) { + if (instRangePair.first == inst && instRangePair.second == bits) { instRangePair.first = nullptr; claimedConsume = true; LLVM_DEBUG(llvm::dbgs() << "Claimed consume: " << *inst); @@ -2183,9 +2183,9 @@ bool GlobalLivenessChecker::testInstVectorLiveness( // array and emit an error on those instead since it would be a better // error than using end_borrow here. { - auto pair = liveness.isInterestingUser(&*ii); - if (pair.first == FieldSensitivePrunedLiveness::NonLifetimeEndingUse && - pair.second->contains(errorSpan)) { + if (liveness.isInterestingUserOfKind( + &*ii, FieldSensitivePrunedLiveness::NonLifetimeEndingUse, + errorSpan)) { diagnosticEmitter.emitAddressDiagnostic( addressUseState.address, &*ii, errorUser, false /*is consuming*/, addressUseState.isInOutTermUser(&*ii)); @@ -2361,7 +2361,7 @@ static void insertDestroyBeforeInstruction(UseState &addressUseState, // claim that destroy instead of inserting another destroy_addr. if (auto *dai = dyn_cast(nextInstruction)) { if (dai->getOperand() == baseAddress) { - consumes.recordFinalConsume(dai, TypeTreeLeafTypeRange(0, bv.size())); + consumes.recordFinalConsume(dai, bv); return; } } @@ -2371,7 +2371,7 @@ static void insertDestroyBeforeInstruction(UseState &addressUseState, auto loc = RegularLocation::getAutoGeneratedLocation(nextInstruction->getLoc()); auto *dai = builder.createDestroyAddr(loc, baseAddress); - consumes.recordFinalConsume(dai, TypeTreeLeafTypeRange(0, bv.size())); + consumes.recordFinalConsume(dai, bv); addressUseState.destroys.insert({dai, TypeTreeLeafTypeRange(0, bv.size())}); return; } @@ -2389,7 +2389,9 @@ static void insertDestroyBeforeInstruction(UseState &addressUseState, if (pair.first->getType().isTrivial(*nextInstruction->getFunction())) continue; auto *dai = builder.createDestroyAddr(loc, pair.first); - consumes.recordFinalConsume(dai, pair.second); + SmallBitVector consumedBits(bv.size()); + pair.second.setBits(consumedBits); + consumes.recordFinalConsume(dai, consumedBits); addressUseState.destroys.insert({dai, pair.second}); } } @@ -2437,52 +2439,67 @@ void MoveOnlyAddressCheckerPImpl::insertDestroysOnBoundary( LLVM_DEBUG(llvm::dbgs() << " User: " << *inst); - auto interestingUse = liveness.isInterestingUser(inst); - switch (interestingUse.first) { - case IsInterestingUser::LifetimeEndingUse: { - LLVM_DEBUG(llvm::dbgs() - << " Lifetime ending use! Recording final consume!\n"); - // If we have a consuming use, when we stop at the consuming use we want - // the value to still be around. We only want the value to be invalidated - // once the consume operation has occured. Thus we always place the - // debug_value undef strictly after the consuming operation. - if (auto *ti = dyn_cast(inst)) { - for (auto *succBlock : ti->getSuccessorBlocks()) { - insertUndefDebugValue(&succBlock->front()); - } - } else { - insertUndefDebugValue(inst->getNextInstruction()); - } - consumes.recordFinalConsume(inst, *interestingUse.second); - continue; + auto interestingUser = liveness.getInterestingUser(inst); + SmallVector, 4> ranges; + if (interestingUser) { + interestingUser->getContiguousRanges(ranges); } - case IsInterestingUser::NonLifetimeEndingUse: - case IsInterestingUser::NonUser: - LLVM_DEBUG(llvm::dbgs() << " NoneUser or NonLifetimeEndingUse! " - "inserting destroy before instruction!\n"); - // If we are dealing with an inout parameter, we will have modeled our - // last use by treating a return inst as a last use. Since it doesn't have - // any successors, this results in us not inserting any destroy_addr. - if (isa(inst)) { - auto *block = inst->getParent(); - for (auto *succBlock : block->getSuccessorBlocks()) { - auto *insertPt = &*succBlock->begin(); - insertDestroyBeforeInstruction(addressUseState, insertPt, - liveness.getRootValue(), bv, consumes); - // We insert the debug_value undef /after/ the last use since we want - // the value to be around when we stop at the last use instruction. - insertUndefDebugValue(insertPt); + + for (auto rangePair : ranges) { + SmallBitVector bits(bv.size()); + rangePair.first.setBits(bits); + switch (rangePair.second) { + case IsInterestingUser::LifetimeEndingUse: { + LLVM_DEBUG( + llvm::dbgs() + << " Lifetime ending use! Recording final consume!\n"); + // If we have a consuming use, when we stop at the consuming use we want + // the value to still be around. We only want the value to be + // invalidated once the consume operation has occured. Thus we always + // place the debug_value undef strictly after the consuming operation. + if (auto *ti = dyn_cast(inst)) { + for (auto *succBlock : ti->getSuccessorBlocks()) { + insertUndefDebugValue(&succBlock->front()); + } + } else { + insertUndefDebugValue(inst->getNextInstruction()); } + consumes.recordFinalConsume(inst, bits); continue; } + case IsInterestingUser::NonUser: + break; + case IsInterestingUser::NonLifetimeEndingUse: + LLVM_DEBUG(llvm::dbgs() << " NonLifetimeEndingUse! " + "inserting destroy before instruction!\n"); + // If we are dealing with an inout parameter, we will have modeled our + // last use by treating a return inst as a last use. Since it doesn't + // have any successors, this results in us not inserting any + // destroy_addr. + if (isa(inst)) { + auto *block = inst->getParent(); + for (auto *succBlock : block->getSuccessorBlocks()) { + + auto *insertPt = &*succBlock->begin(); + insertDestroyBeforeInstruction(addressUseState, insertPt, + liveness.getRootValue(), bits, + consumes); + // We insert the debug_value undef /after/ the last use since we + // want the value to be around when we stop at the last use + // instruction. + insertUndefDebugValue(insertPt); + } + continue; + } - auto *insertPt = inst->getNextInstruction(); - insertDestroyBeforeInstruction(addressUseState, insertPt, - liveness.getRootValue(), bv, consumes); - // We insert the debug_value undef /after/ the last use since we want - // the value to be around when we stop at the last use instruction. - insertUndefDebugValue(insertPt); - continue; + auto *insertPt = inst->getNextInstruction(); + insertDestroyBeforeInstruction(addressUseState, insertPt, + liveness.getRootValue(), bits, consumes); + // We insert the debug_value undef /after/ the last use since we want + // the value to be around when we stop at the last use instruction. + insertUndefDebugValue(insertPt); + continue; + } } } @@ -2545,8 +2562,9 @@ void MoveOnlyAddressCheckerPImpl::rewriteUses( // Process destroys for (auto destroyPair : addressUseState.destroys) { /// Is this destroy instruction a final consuming use? - bool isFinalConsume = - consumes.claimConsume(destroyPair.first, destroyPair.second); + SmallBitVector bits(liveness.getNumSubElements()); + destroyPair.second.setBits(bits); + bool isFinalConsume = consumes.claimConsume(destroyPair.first, bits); // Remove destroys that are not the final consuming use. if (!isFinalConsume) { @@ -2583,22 +2601,26 @@ void MoveOnlyAddressCheckerPImpl::rewriteUses( for (auto reinitPair : addressUseState.reinitInsts) { if (!isReinitToInitConvertibleInst(reinitPair.first)) continue; - if (!consumes.claimConsume(reinitPair.first, reinitPair.second)) + SmallBitVector bits(liveness.getNumSubElements()); + reinitPair.second.setBits(bits); + if (!consumes.claimConsume(reinitPair.first, bits)) convertMemoryReinitToInitForm(reinitPair.first, debugVar); } // Check all takes. for (auto takeInst : addressUseState.takeInsts) { - bool claimedConsume = - consumes.claimConsume(takeInst.first, takeInst.second); + SmallBitVector bits(liveness.getNumSubElements()); + takeInst.second.setBits(bits); + bool claimedConsume = consumes.claimConsume(takeInst.first, bits); (void)claimedConsume; assert(claimedConsume && "Should claim all copies?!"); } // Then rewrite all copy insts to be takes and claim them. for (auto copyInst : addressUseState.copyInsts) { - bool claimedConsume = - consumes.claimConsume(copyInst.first, copyInst.second); + SmallBitVector bits(liveness.getNumSubElements()); + copyInst.second.setBits(bits); + bool claimedConsume = consumes.claimConsume(copyInst.first, bits); if (!claimedConsume) { llvm::errs() << "Found consume that was not recorded as a 'claimed consume'!\n"; diff --git a/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureUtils.cpp b/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureUtils.cpp index 9f01ba50013d9..b328701d6fcbb 100644 --- a/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureUtils.cpp +++ b/lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureUtils.cpp @@ -317,9 +317,10 @@ bool Implementation::gatherUses(SILValue value) { } LLVM_DEBUG(llvm::dbgs() << " Found non lifetime ending use!\n"); - blocksToUses.insert( - nextUse->getParentBlock(), - {nextUse, {*leafRange, false /*is lifetime ending*/}}); + blocksToUses.insert(nextUse->getParentBlock(), + {nextUse, + {liveness.getNumSubElements(), *leafRange, + false /*is lifetime ending*/}}); liveness.updateForUse(nextUse->getUser(), *leafRange, false /*is lifetime ending*/); instToInterestingOperandIndexMap.insert(nextUse->getUser(), nextUse); @@ -344,7 +345,9 @@ 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*/}}); + {nextUse, + {liveness.getNumSubElements(), *leafRange, + true /*is lifetime ending*/}}); liveness.updateForUse(nextUse->getUser(), *leafRange, true /*is lifetime ending*/); instToInterestingOperandIndexMap.insert(nextUse->getUser(), nextUse); @@ -381,9 +384,10 @@ bool Implementation::gatherUses(SILValue value) { // Otherwise, treat it as a normal use. LLVM_DEBUG(llvm::dbgs() << " Treating non-begin_borrow borrow as " "a non lifetime ending use!\n"); - blocksToUses.insert( - nextUse->getParentBlock(), - {nextUse, {*leafRange, false /*is lifetime ending*/}}); + blocksToUses.insert(nextUse->getParentBlock(), + {nextUse, + {liveness.getNumSubElements(), *leafRange, + false /*is lifetime ending*/}}); liveness.updateForUse(nextUse->getUser(), *leafRange, false /*is lifetime ending*/); instToInterestingOperandIndexMap.insert(nextUse->getUser(), nextUse); @@ -989,13 +993,13 @@ void Implementation::rewriteUses(InstructionDeleter *deleter) { if (auto operandList = blocksToUses.find(block)) { // If we do, gather up the bits that we need. for (auto operand : *operandList) { - auto &subEltSpan = operand.second.subEltSpan; + auto &liveBits = operand.second.liveBits; LLVM_DEBUG(llvm::dbgs() << " Found need operand " << operand.first->getOperandNumber() - << " of inst: " << *operand.first->getUser() - << " Needs bits: " << subEltSpan << '\n'); - bitsNeededInBlock.set(subEltSpan.startEltOffset, - subEltSpan.endEltOffset); + << " of inst: " << *operand.first->getUser()); + for (auto bit : liveBits.set_bits()) { + bitsNeededInBlock.set(bit); + } seenOperands.insert(operand.first); } } diff --git a/lib/SILOptimizer/Transforms/DestroyAddrHoisting.cpp b/lib/SILOptimizer/Transforms/DestroyAddrHoisting.cpp index 4feb70403f429..94598b56c84aa 100644 --- a/lib/SILOptimizer/Transforms/DestroyAddrHoisting.cpp +++ b/lib/SILOptimizer/Transforms/DestroyAddrHoisting.cpp @@ -870,6 +870,11 @@ bool hoistDestroys(SILValue root, bool ignoreDeinitBarriers, BasicCalleeAnalysis *calleeAnalysis) { LLVM_DEBUG(llvm::dbgs() << "Performing destroy hoisting on " << root); + // Don't canonicalize the lifetimes of addresses of move-only type. + // According to language rules, they are fixed. + if (root->getType().isMoveOnly()) + return false; + SILFunction *function = root->getFunction(); if (!function) return false; diff --git a/lib/SILOptimizer/UtilityPasses/UnitTestRunner.cpp b/lib/SILOptimizer/UtilityPasses/UnitTestRunner.cpp index d3a2808d11379..ebf9a35bdd8e6 100644 --- a/lib/SILOptimizer/UtilityPasses/UnitTestRunner.cpp +++ b/lib/SILOptimizer/UtilityPasses/UnitTestRunner.cpp @@ -89,6 +89,7 @@ #include "swift/SILOptimizer/Utils/InstructionDeleter.h" #include "swift/SILOptimizer/Utils/ParseTestSpecification.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/Debug.h" #include "llvm/Support/raw_ostream.h" #include #include @@ -287,6 +288,19 @@ struct OwnershipUtilsHasPointerEscape : UnitTest { } }; +// Arguments: +// - value: whose type will be printed +// Dumps: +// - the type lowering of the type +struct PrintTypeLowering : UnitTest { + PrintTypeLowering(UnitTestRunner *pass) : UnitTest(pass) {} + void invoke(Arguments &arguments) override { + auto value = arguments.takeValue(); + auto ty = value->getType(); + getFunction()->getTypeLowering(ty).print(llvm::dbgs()); + } +}; + //===----------------------------------------------------------------------===// // MARK: OSSA Lifetime Unit Tests //===----------------------------------------------------------------------===// @@ -899,6 +913,7 @@ void UnitTestRunner::withTest(StringRef name, Doit doit) { ADD_UNIT_TEST_SUBCLASS("find-enclosing-defs", FindEnclosingDefsTest) ADD_UNIT_TEST_SUBCLASS("function-get-self-argument-index", FunctionGetSelfArgumentIndex) ADD_UNIT_TEST_SUBCLASS("has-pointer-escape", OwnershipUtilsHasPointerEscape) + ADD_UNIT_TEST_SUBCLASS("print-type-lowering", PrintTypeLowering) ADD_UNIT_TEST_SUBCLASS("interior-liveness", InteriorLivenessTest) ADD_UNIT_TEST_SUBCLASS("is-deinit-barrier", IsDeinitBarrierTest) ADD_UNIT_TEST_SUBCLASS("is-lexical", IsLexicalTest) diff --git a/lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp b/lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp index f9fda092cbea2..85d0a7e33e8e2 100644 --- a/lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp +++ b/lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp @@ -1156,6 +1156,12 @@ void CanonicalizeOSSALifetime::rewriteLifetimes() { bool CanonicalizeOSSALifetime::canonicalizeValueLifetime(SILValue def) { LivenessState livenessState(*this, def); + // Don't canonicalize the lifetimes of values of move-only type. According to + // language rules, they are fixed. + if (def->getType().isMoveOnly()) { + return false; + } + // Step 1: Compute liveness. if (!computeLiveness()) { LLVM_DEBUG(llvm::dbgs() << "Failed to compute liveness boundary!\n"); diff --git a/test/SIL/type_lowering_unit.sil b/test/SIL/type_lowering_unit.sil new file mode 100644 index 0000000000000..23e1ba35bbf64 --- /dev/null +++ b/test/SIL/type_lowering_unit.sil @@ -0,0 +1,18 @@ +// RUN: %target-sil-opt -unit-test-runner %s -o /dev/null 2>&1 | %FileCheck %s + +sil_stage raw + +import Builtin + +struct S : ~Copyable {} + +// CHECK-LABEL: begin {{.*}} print-type-lowering with: @argument[0] +// CHECK: isLexical: true +// CHECK-LABEL: end {{.*}} print-type-lowering with: @argument[0] +sil [ossa] @move_only_argument : $@convention(thin) (@owned S) -> () { +bb0(%0 : @owned $S): + test_specification "print-type-lowering @argument[0]" + destroy_value %0 : $S + %retval = tuple () + return %retval : $() +} diff --git a/test/SILGen/discard.swift b/test/SILGen/discard.swift index abb4ba5d8a5ad..17a2e5a17360a 100644 --- a/test/SILGen/discard.swift +++ b/test/SILGen/discard.swift @@ -89,7 +89,8 @@ func invokedDeinit() {} // CHECK-LABEL: sil hidden [ossa] @$s4test11PointerTreeV10tryDestroy9doDiscardySb_tKF : $@convention(method) (Bool, @owned PointerTree) -> @error any Error { // CHECK: bb0{{.*}}: // CHECK: [[SELF_BOX:%.*]] = alloc_box ${ var PointerTree }, var, name "self" -// CHECK: [[SELF_PTR:%.*]] = project_box [[SELF_BOX]] : ${ var PointerTree }, 0 +// CHECK: [[SELF_BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[SELF_BOX]] +// CHECK: [[SELF_PTR:%.*]] = project_box [[SELF_BOX_LIFETIME]] : ${ var PointerTree }, 0 // .. skip to the conditional test .. // CHECK: [[SHOULD_FORGET:%.*]] = struct_extract {{.*}} : $Bool, #Bool._value // CHECK: cond_br [[SHOULD_FORGET]], bb1, bb2 @@ -107,6 +108,7 @@ func invokedDeinit() {} // CHECK: br bb3 // // CHECK: bb3: +// CHECK: end_borrow [[SELF_BOX_LIFETIME]] // CHECK: destroy_value [[SELF_BOX]] : ${ var PointerTree } // CHECK: throw // CHECK: } // end sil function diff --git a/test/SILGen/moveonly.swift b/test/SILGen/moveonly.swift index e25375482eb4e..a68c83106e85a 100644 --- a/test/SILGen/moveonly.swift +++ b/test/SILGen/moveonly.swift @@ -933,7 +933,8 @@ struct EmptyStruct { // CHECK-LABEL: sil hidden [ossa] @$s8moveonly11EmptyStructVACycfC : $@convention(method) (@thin EmptyStruct.Type) -> @owned EmptyStruct { // CHECK: [[BOX:%.*]] = alloc_box ${ var EmptyStruct }, var, name "self" // CHECK: [[MARKED_UNINIT:%.*]] = mark_uninitialized [rootself] [[BOX]] - // CHECK: [[PROJECT:%.*]] = project_box [[MARKED_UNINIT]] + // CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[MARKED_UNINIT]] + // CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // CHECK: [[STRUCT:%.*]] = struct $EmptyStruct () // CHECK: store [[STRUCT]] to [init] [[PROJECT]] // CHECK: [[MV_CHECK:%.*]] = mark_must_check [assignable_but_not_consumable] [[PROJECT]] @@ -1039,7 +1040,8 @@ public struct LoadableSubscriptGetOnlyTester : ~Copyable { // CHECK-LABEL: sil [ossa] @$s8moveonly047testSubscriptGetOnly_BaseLoadable_ResultAddressE4_VaryyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box $ -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // The get call // CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] @@ -1077,7 +1079,8 @@ public func testSubscriptGetOnly_BaseLoadable_ResultAddressOnly_Var() { // CHECK-LABEL: sil [ossa] @$s8moveonly047testSubscriptGetOnly_BaseLoadable_ResultAddressE4_LetyyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box $ -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // The get call // CHECK: [[MARK:%.*]] = mark_must_check [no_consume_or_assign] [[PROJECT]] @@ -1140,7 +1143,8 @@ public struct LoadableSubscriptGetOnlyTesterNonCopyableStructParent : ~Copyable // CHECK-LABEL: sil [ossa] @$s8moveonly077testSubscriptGetOnlyThroughNonCopyableParentStruct_BaseLoadable_ResultAddressE4_VaryyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box $ -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // The first get call // CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] @@ -1181,7 +1185,8 @@ public func testSubscriptGetOnlyThroughNonCopyableParentStruct_BaseLoadable_Resu // CHECK-LABEL: sil [ossa] @$s8moveonly077testSubscriptGetOnlyThroughNonCopyableParentStruct_BaseLoadable_ResultAddressE4_LetyyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box ${ let L -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // CHECK: [[MARK:%.*]] = mark_must_check [no_consume_or_assign] [[PROJECT]] // CHECK: [[LOAD:%.*]] = load_borrow [[MARK]] @@ -1350,7 +1355,8 @@ public struct LoadableSubscriptGetSetTester : ~Copyable { // CHECK-LABEL: sil [ossa] @$s8moveonly54testSubscriptGetSet_BaseLoadable_ResultAddressOnly_VaryyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box $ -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // The get call // CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] @@ -1396,7 +1402,8 @@ public func testSubscriptGetSet_BaseLoadable_ResultAddressOnly_Var() { // CHECK-LABEL: sil [ossa] @$s8moveonly54testSubscriptGetSet_BaseLoadable_ResultAddressOnly_LetyyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box $ -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // The get call // CHECK: [[MARK:%.*]] = mark_must_check [no_consume_or_assign] [[PROJECT]] @@ -1507,7 +1514,8 @@ public struct LoadableSubscriptGetSetTesterNonCopyableStructParent : ~Copyable { // CHECK-LABEL: sil [ossa] @$s8moveonly84testSubscriptGetSetThroughNonCopyableParentStruct_BaseLoadable_ResultAddressOnly_VaryyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box $ -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // The first get call // CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] @@ -1561,7 +1569,8 @@ public func testSubscriptGetSetThroughNonCopyableParentStruct_BaseLoadable_Resul // CHECK-LABEL: sil [ossa] @$s8moveonly84testSubscriptGetSetThroughNonCopyableParentStruct_BaseLoadable_ResultAddressOnly_LetyyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box ${ let L -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // CHECK: [[MARK:%.*]] = mark_must_check [no_consume_or_assign] [[PROJECT]] // CHECK: [[LOAD:%.*]] = load_borrow [[MARK]] @@ -1819,7 +1828,8 @@ public struct LoadableSubscriptReadSetTester : ~Copyable { // CHECK-LABEL: sil [ossa] @$s8moveonly55testSubscriptReadSet_BaseLoadable_ResultAddressOnly_VaryyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box $ -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // The read call // CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] @@ -1865,7 +1875,8 @@ public func testSubscriptReadSet_BaseLoadable_ResultAddressOnly_Var() { // CHECK-LABEL: sil [ossa] @$s8moveonly55testSubscriptReadSet_BaseLoadable_ResultAddressOnly_LetyyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box $ -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // The get call // CHECK: [[MARK:%.*]] = mark_must_check [no_consume_or_assign] [[PROJECT]] @@ -1979,7 +1990,8 @@ public struct LoadableSubscriptReadSetTesterNonCopyableStructParent : ~Copyable // CHECK-LABEL: sil [ossa] @$s8moveonly85testSubscriptReadSetThroughNonCopyableParentStruct_BaseLoadable_ResultAddressOnly_VaryyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box $ -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // The first get call // CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] @@ -2031,7 +2043,8 @@ public func testSubscriptReadSetThroughNonCopyableParentStruct_BaseLoadable_Resu // CHECK-LABEL: sil [ossa] @$s8moveonly85testSubscriptReadSetThroughNonCopyableParentStruct_BaseLoadable_ResultAddressOnly_LetyyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box ${ let L -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // CHECK: [[MARK:%.*]] = mark_must_check [no_consume_or_assign] [[PROJECT]] // CHECK: [[LOAD:%.*]] = load_borrow [[MARK]] @@ -2285,7 +2298,8 @@ public struct LoadableSubscriptReadModifyTester : ~Copyable { // CHECK-LABEL: sil [ossa] @$s8moveonly58testSubscriptReadModify_BaseLoadable_ResultAddressOnly_VaryyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box $ -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // The read call // CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] @@ -2328,7 +2342,8 @@ public func testSubscriptReadModify_BaseLoadable_ResultAddressOnly_Var() { // CHECK-LABEL: sil [ossa] @$s8moveonly58testSubscriptReadModify_BaseLoadable_ResultAddressOnly_LetyyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box $ -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // The get call // CHECK: [[MARK:%.*]] = mark_must_check [no_consume_or_assign] [[PROJECT]] @@ -2431,7 +2446,8 @@ public struct LoadableSubscriptReadModifyTesterNonCopyableStructParent : ~Copyab // CHECK-LABEL: sil [ossa] @$s8moveonly88testSubscriptReadModifyThroughNonCopyableParentStruct_BaseLoadable_ResultAddressOnly_VaryyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box $ -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // The first get call // CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] @@ -2477,7 +2493,8 @@ public func testSubscriptReadModifyThroughNonCopyableParentStruct_BaseLoadable_R // CHECK-LABEL: sil [ossa] @$s8moveonly88testSubscriptReadModifyThroughNonCopyableParentStruct_BaseLoadable_ResultAddressOnly_LetyyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box ${ let L -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // CHECK: [[MARK:%.*]] = mark_must_check [no_consume_or_assign] [[PROJECT]] // CHECK: [[LOAD:%.*]] = load_borrow [[MARK]] @@ -2699,7 +2716,8 @@ public struct LoadableSubscriptGetModifyTester : ~Copyable { // CHECK-LABEL: sil [ossa] @$s8moveonly57testSubscriptGetModify_BaseLoadable_ResultAddressOnly_VaryyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box $ -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // The get call // CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] @@ -2743,7 +2761,8 @@ public func testSubscriptGetModify_BaseLoadable_ResultAddressOnly_Var() { // CHECK-LABEL: sil [ossa] @$s8moveonly57testSubscriptGetModify_BaseLoadable_ResultAddressOnly_LetyyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box $ -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // The get call // CHECK: [[MARK:%.*]] = mark_must_check [no_consume_or_assign] [[PROJECT]] @@ -2850,7 +2869,8 @@ public struct LoadableSubscriptGetModifyTesterNonCopyableStructParent : ~Copyabl // CHECK-LABEL: sil [ossa] @$s8moveonly87testSubscriptGetModifyThroughNonCopyableParentStruct_BaseLoadable_ResultAddressOnly_VaryyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box $ -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // The first get call // CHECK: [[ACCESS:%.*]] = begin_access [read] [unknown] [[PROJECT]] @@ -2900,7 +2920,8 @@ public func testSubscriptGetModifyThroughNonCopyableParentStruct_BaseLoadable_Re // CHECK-LABEL: sil [ossa] @$s8moveonly87testSubscriptGetModifyThroughNonCopyableParentStruct_BaseLoadable_ResultAddressOnly_LetyyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box ${ let L -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // // CHECK: [[MARK:%.*]] = mark_must_check [no_consume_or_assign] [[PROJECT]] // CHECK: [[LOAD:%.*]] = load_borrow [[MARK]] diff --git a/test/SILGen/moveonly_deinits.swift b/test/SILGen/moveonly_deinits.swift index 73a7353966af0..88a6fd778ef20 100644 --- a/test/SILGen/moveonly_deinits.swift +++ b/test/SILGen/moveonly_deinits.swift @@ -83,7 +83,8 @@ var value: Bool { false } // SILGEN-LABEL: sil [ossa] @$s16moveonly_deinits24testIntPairWithoutDeinityyF : $@convention(thin) () -> () { // SILGEN: [[BOX:%.*]] = alloc_box -// SILGEN: [[PROJECT:%.*]] = project_box [[BOX]] +// SILGEN: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// SILGEN: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // SILGEN: cond_br {{%.*}}, bb1, bb2 // // SILGEN: bb1: @@ -132,7 +133,8 @@ public func testIntPairWithoutDeinit() { // SILGEN-LABEL: sil [ossa] @$s16moveonly_deinits21testIntPairWithDeinityyF : $@convention(thin) () -> () { // SILGEN: [[BOX:%.*]] = alloc_box -// SILGEN: [[PROJECT:%.*]] = project_box [[BOX]] +// SILGEN: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// SILGEN: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // SILGEN: cond_br {{%.*}}, bb1, bb2 // // SILGEN: bb1: @@ -343,7 +345,8 @@ func consumeKlassEnumPairWithDeinit(_ x: __owned KlassEnumPairWithDeinit) { } // SILGEN-LABEL: sil [ossa] @$s16moveonly_deinits28testIntEnumPairWithoutDeinityyF : $@convention(thin) () -> () { // SILGEN: [[BOX:%.*]] = alloc_box -// SILGEN: [[PROJECT:%.*]] = project_box [[BOX]] +// SILGEN: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// SILGEN: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // SILGEN: cond_br {{%.*}}, bb1, bb2 // // SILGEN: bb1: @@ -391,7 +394,8 @@ public func testIntEnumPairWithoutDeinit() { // SILGEN-LABEL: sil [ossa] @$s16moveonly_deinits25testIntEnumPairWithDeinityyF : $@convention(thin) () -> () { // SILGEN: [[BOX:%.*]] = alloc_box -// SILGEN: [[PROJECT:%.*]] = project_box [[BOX]] +// SILGEN: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// SILGEN: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // SILGEN: cond_br {{%.*}}, bb1, bb2 // // SILGEN: bb1: diff --git a/test/SILGen/moveonly_enum_literal.swift b/test/SILGen/moveonly_enum_literal.swift index 4d566835f4b83..13555bce43a40 100644 --- a/test/SILGen/moveonly_enum_literal.swift +++ b/test/SILGen/moveonly_enum_literal.swift @@ -16,7 +16,8 @@ var value: Bool { false } // CHECK-LABEL: sil hidden [ossa] @$s21moveonly_enum_literal4testyyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // CHECK: [[VALUE:%.*]] = enum $MoveOnlyIntPair, #MoveOnlyIntPair.lhs!enumelt, // CHECK: store [[VALUE]] to [init] [[PROJECT]] // diff --git a/test/SILGen/moveonly_escaping_closure.swift b/test/SILGen/moveonly_escaping_closure.swift index 1b20481e0147e..3977658feebb9 100644 --- a/test/SILGen/moveonly_escaping_closure.swift +++ b/test/SILGen/moveonly_escaping_closure.swift @@ -24,9 +24,10 @@ func borrowConsumeVal(_ x: borrowing SingleElt, _ y: consuming SingleElt) {} // CHECK-LABEL: sil hidden [ossa] @$s16moveonly_closure27testGlobalClosureCaptureVaryyF : $@convention(thin) () -> () { // CHECK: [[GLOBAL:%.*]] = global_addr @$s16moveonly_closure23globalClosureCaptureVaryycvp // CHECK: [[BOX:%.*]] = alloc_box ${ var SingleElt } -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // CHECK: [[CLOSURE:%.*]] = function_ref @$s16moveonly_closure27testGlobalClosureCaptureVaryyFyycfU_ : $@convention(thin) (@guaranteed { var SingleElt }) -> () -// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX]] +// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX_LIFETIME]] // CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] [[CLOSURE]]([[BOX_COPY]]) // CHECK: [[ACCESS:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] // CHECK: assign [[PAI]] to [[ACCESS]] @@ -84,8 +85,9 @@ func testGlobalClosureCaptureVar() { // CHECK-LABEL: sil hidden [ossa] @$s16moveonly_closure29testLocalLetClosureCaptureVaryyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] -// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] +// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX_LIFETIME]] // CHECK: mark_function_escape [[PROJECT]] // CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] {{%.*}}([[BOX_COPY]]) // CHECK: [[BORROW_PAI:%.*]] = begin_borrow [lexical] [[PAI]] @@ -151,8 +153,9 @@ func testLocalLetClosureCaptureVar() { // CHECK-LABEL: sil hidden [ossa] @$s16moveonly_closure026testLocalVarClosureCaptureE0yyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] -// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] +// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX_LIFETIME]] // CHECK: mark_function_escape [[PROJECT]] // CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] {{%.*}}([[BOX_COPY]]) // CHECK: } // end sil function '$s16moveonly_closure026testLocalVarClosureCaptureE0yyF' @@ -209,9 +212,10 @@ func testLocalVarClosureCaptureVar() { // CHECK-LABEL: sil hidden [ossa] @$s16moveonly_closure026testInOutVarClosureCaptureF0yyyyczF : $@convention(thin) (@inout @callee_guaranteed () -> ()) -> () { // CHECK: bb0([[F:%.*]] : $*@callee_guaranteed () -> ()): // CHECK: [[BOX:%.*]] = alloc_box ${ var SingleElt } -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // CHECK: [[CLOSURE:%.*]] = function_ref @$s16moveonly_closure026testInOutVarClosureCaptureF0yyyyczFyycfU_ : $@convention(thin) (@guaranteed { var SingleElt }) -> () -// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX]] +// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX_LIFETIME]] // CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] [[CLOSURE]]([[BOX_COPY]]) // CHECK: [[ACCESS:%.*]] = begin_access [modify] [unknown] [[F]] // CHECK: assign [[PAI]] to [[ACCESS]] @@ -275,9 +279,10 @@ func testInOutVarClosureCaptureVar(_ f: inout () -> ()) { // CHECK: store [[ARG]] to [init] [[UNWRAP]] // // CHECK: [[BOX:%.*]] = alloc_box ${ var SingleElt } -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // CHECK: [[CLOSURE:%.*]] = function_ref @$s16moveonly_closure36testConsumingEscapeClosureCaptureVaryyyycnFyycfU_ : $@convention(thin) (@guaranteed { var SingleElt }) -// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX]] +// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX_LIFETIME]] // CHECK: mark_function_escape [[PROJECT]] // CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] [[CLOSURE]]([[BOX_COPY]]) // CHECK: [[ACCESS:%.*]] = begin_access [modify] [unknown] [[FUNC_PROJECT]] @@ -342,9 +347,10 @@ func testConsumingEscapeClosureCaptureVar(_ f: consuming @escaping () -> ()) { // CHECK-LABEL: sil hidden [ossa] @$s16moveonly_closure27testGlobalClosureCaptureLetyyF : $@convention(thin) () -> () { // CHECK: [[GLOBAL:%.*]] = global_addr @$s16moveonly_closure23globalClosureCaptureLetyycvp // CHECK: [[BOX:%.*]] = alloc_box ${ let SingleElt } -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // CHECK: [[CLOSURE:%.*]] = function_ref @$s16moveonly_closure27testGlobalClosureCaptureLetyyFyycfU_ : $@convention(thin) (@guaranteed { let SingleElt }) -> () -// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX]] +// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX_LIFETIME]] // CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] [[CLOSURE]]([[BOX_COPY]]) // CHECK: [[ACCESS:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] // CHECK: assign [[PAI]] to [[ACCESS]] @@ -388,8 +394,9 @@ func testGlobalClosureCaptureLet() { // CHECK-LABEL: sil hidden [ossa] @$s16moveonly_closure026testLocalLetClosureCaptureE0yyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] -// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] +// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX_LIFETIME]] // CHECK: mark_function_escape [[PROJECT]] // CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] {{%.*}}([[BOX_COPY]]) // CHECK: [[BORROW_PAI:%.*]] = begin_borrow [lexical] [[PAI]] @@ -437,8 +444,9 @@ func testLocalLetClosureCaptureLet() { // CHECK-LABEL: sil hidden [ossa] @$s16moveonly_closure29testLocalVarClosureCaptureLetyyF : $@convention(thin) () -> () { // CHECK: [[BOX:%.*]] = alloc_box -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] -// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] +// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX_LIFETIME]] // CHECK: mark_function_escape [[PROJECT]] // CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] {{%.*}}([[BOX_COPY]]) // CHECK: } // end sil function '$s16moveonly_closure29testLocalVarClosureCaptureLetyyF' @@ -482,9 +490,10 @@ func testLocalVarClosureCaptureLet() { // CHECK-LABEL: sil hidden [ossa] @$s16moveonly_closure29testInOutVarClosureCaptureLetyyyyczF : $@convention(thin) (@inout @callee_guaranteed () -> ()) -> () { // CHECK: bb0([[F:%.*]] : $*@callee_guaranteed () -> ()): // CHECK: [[BOX:%.*]] = alloc_box ${ let SingleElt } -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // CHECK: [[CLOSURE:%.*]] = function_ref @$s16moveonly_closure29testInOutVarClosureCaptureLetyyyyczFyycfU_ : $@convention(thin) (@guaranteed { let SingleElt }) -> () -// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX]] +// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX_LIFETIME]] // CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] [[CLOSURE]]([[BOX_COPY]]) // CHECK: [[ACCESS:%.*]] = begin_access [modify] [unknown] [[F]] // CHECK: assign [[PAI]] to [[ACCESS]] @@ -530,14 +539,15 @@ func testInOutVarClosureCaptureLet(_ f: inout () -> ()) { // CHECK-LABEL: sil hidden [ossa] @$s16moveonly_closure36testConsumingEscapeClosureCaptureLetyyyycnF : $@convention(thin) (@owned @callee_guaranteed () -> ()) -> () { // CHECK: bb0([[ARG:%.*]] : @noImplicitCopy @_eagerMove @owned // CHECK: [[FUNC_BOX:%.*]] = alloc_box ${ var @moveOnly @callee_guaranteed () -> () } -// CHECK: [[FUNC_PROJECT:%.*]] = project_box [[FUNC_BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[FUNC_BOX]] // CHECK: [[UNWRAP:%.*]] = moveonlywrapper_to_copyable_addr [[FUNC_PROJECT]] // CHECK: store [[ARG]] to [init] [[UNWRAP]] // // CHECK: [[BOX:%.*]] = alloc_box ${ let SingleElt } -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // CHECK: [[CLOSURE:%.*]] = function_ref @$s16moveonly_closure36testConsumingEscapeClosureCaptureLetyyyycnFyycfU_ : $@convention(thin) (@guaranteed { let SingleElt }) -// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX]] +// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX_LIFETIME]] // CHECK: mark_function_escape [[PROJECT]] // CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] [[CLOSURE]]([[BOX_COPY]]) // CHECK: [[ACCESS:%.*]] = begin_access [modify] [unknown] [[FUNC_PROJECT]] @@ -840,9 +850,10 @@ func testConsumingEscapeClosureCaptureInOut(_ f: consuming @escaping () -> (), _ // CHECK-LABEL: sil hidden [ossa] @$s16moveonly_closure33testGlobalClosureCaptureConsumingyyAA9SingleEltVnF : $@convention(thin) (@owned SingleElt) -> () { // CHECK: [[GLOBAL:%.*]] = global_addr @$s16moveonly_closure29globalClosureCaptureConsumingyycvp // CHECK: [[BOX:%.*]] = alloc_box ${ var SingleElt } -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // CHECK: [[CLOSURE:%.*]] = function_ref @$s16moveonly_closure33testGlobalClosureCaptureConsumingyyAA9SingleEltVnFyycfU_ : $@convention(thin) (@guaranteed { var SingleElt }) -> () -// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX]] +// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX_LIFETIME]] // CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] [[CLOSURE]]([[BOX_COPY]]) // CHECK: [[ACCESS:%.*]] = begin_access [modify] [dynamic] [[GLOBAL]] // CHECK: assign [[PAI]] to [[ACCESS]] @@ -898,8 +909,9 @@ func testGlobalClosureCaptureConsuming(_ x: consuming SingleElt) { // CHECK-LABEL: sil hidden [ossa] @$s16moveonly_closure35testLocalLetClosureCaptureConsumingyyAA9SingleEltVnF : $@convention(thin) (@owned SingleElt) -> () { // CHECK: [[BOX:%.*]] = alloc_box -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] -// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] +// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX_LIFETIME]] // CHECK: mark_function_escape [[PROJECT]] // CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] {{%.*}}([[BOX_COPY]]) // CHECK: [[BORROW_PAI:%.*]] = begin_borrow [lexical] [[PAI]] @@ -976,8 +988,9 @@ func testLocalLetClosureCaptureConsuming2(_ x: consuming SingleElt) -> (() -> () // CHECK-LABEL: sil hidden [ossa] @$s16moveonly_closure35testLocalVarClosureCaptureConsumingyyAA9SingleEltVnF : $@convention(thin) (@owned SingleElt) -> () { // CHECK: [[BOX:%.*]] = alloc_box -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] -// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] +// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX_LIFETIME]] // CHECK: mark_function_escape [[PROJECT]] // CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] {{%.*}}([[BOX_COPY]]) // CHECK: } // end sil function '$s16moveonly_closure35testLocalVarClosureCaptureConsumingyyAA9SingleEltVnF' @@ -1038,9 +1051,10 @@ func testLocalVarClosureCaptureConsuming(_ x: consuming SingleElt) { // CHECK: store [[ARG]] to [init] [[UNWRAP]] // // CHECK: [[BOX:%.*]] = alloc_box ${ var SingleElt } -// CHECK: [[PROJECT:%.*]] = project_box [[BOX]] +// CHECK: [[BOX_LIFETIME:%.*]] = begin_borrow [lexical] [[BOX]] +// CHECK: [[PROJECT:%.*]] = project_box [[BOX_LIFETIME]] // CHECK: [[CLOSURE:%.*]] = function_ref @$s16moveonly_closure033testConsumingEscapeClosureCaptureD0yyyycn_AA9SingleEltVntFyycfU_ : $@convention(thin) (@guaranteed { var SingleElt }) -> () -// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX]] +// CHECK: [[BOX_COPY:%.*]] = copy_value [[BOX_LIFETIME]] // CHECK: mark_function_escape [[PROJECT]] // CHECK: [[PAI:%.*]] = partial_apply [callee_guaranteed] [[CLOSURE]]([[BOX_COPY]]) // CHECK: [[ACCESS:%.*]] = begin_access [modify] [unknown] [[FUNC_PROJECT]] diff --git a/test/SILOptimizer/canonicalize_ossa_lifetime_unit.sil b/test/SILOptimizer/canonicalize_ossa_lifetime_unit.sil index 2f36344f108d6..7d2f7425b1f25 100644 --- a/test/SILOptimizer/canonicalize_ossa_lifetime_unit.sil +++ b/test/SILOptimizer/canonicalize_ossa_lifetime_unit.sil @@ -5,6 +5,9 @@ sil @getOwned : $@convention(thin) () -> @owned C sil @barrier : $@convention(thin) () -> () struct S {} +@_moveOnly struct MoS {} +@_moveOnly struct MoE {} + // When access scopes are respected, the lifetime which previously extended // beyond the access scope still extends beyond it. // CHECK-LABEL: begin running test 1 of 2 on retract_value_lifetime_into_access_scope_when_access_scopes_not_respected: canonicalize-ossa-lifetime with: true, false, true, @trace @@ -134,3 +137,46 @@ exit(%phi : @owned $C, %typhi : $S): %retval = tuple () return %retval : $() } + +sil @empty : $@convention(thin) () -> () { +[global: ] +bb0: + %0 = tuple () + return %0 : $() +} + +// Even though the apply of %empty is not a deinit barrier, verify that the +// destroy is not hoisted, because MoS is move-only. +// CHECK-LABEL: begin running test {{.*}} on dont_move_destroy_value_of_moveonly_struct: canonicalize-ossa-lifetime with: true, false, true, @argument +// CHECK-LABEL: sil [ossa] @dont_move_destroy_value_of_moveonly_struct : {{.*}} { +// CHECK: {{bb[0-9]+}}([[INSTANCE:%[^,]+]] : +// CHECK: apply +// CHECK: destroy_value [[INSTANCE]] +// CHECK-LABEL: } // end sil function 'dont_move_destroy_value_of_moveonly_struct' +// CHECK-LABEL: end running test {{.*}} on dont_move_destroy_value_of_moveonly_struct: canonicalize-ossa-lifetime with: true, false, true, @argument +sil [ossa] @dont_move_destroy_value_of_moveonly_struct : $@convention(thin) (@owned MoS) -> () { +entry(%instance : @owned $MoS): + test_specification "canonicalize-ossa-lifetime true false true @argument" + %empty = function_ref @empty : $@convention(thin) () -> () + apply %empty() : $@convention(thin) () -> () + destroy_value %instance : $MoS + %retval = tuple () + return %retval : $() +} + +// CHECK-LABEL: begin running test {{.*}} on dont_move_destroy_value_of_moveonly_enum: canonicalize-ossa-lifetime with: true, false, true, @argument +// CHECK-LABEL: sil [ossa] @dont_move_destroy_value_of_moveonly_enum : {{.*}} { +// CHECK: {{bb[0-9]+}}([[INSTANCE:%[^,]+]] : +// CHECK: apply +// CHECK: destroy_value [[INSTANCE]] +// CHECK-LABEL: } // end sil function 'dont_move_destroy_value_of_moveonly_enum' +// CHECK-LABEL: end running test {{.*}} on dont_move_destroy_value_of_moveonly_enum: canonicalize-ossa-lifetime with: true, false, true, @argument +sil [ossa] @dont_move_destroy_value_of_moveonly_enum : $@convention(thin) (@owned MoE) -> () { +entry(%instance : @owned $MoE): + test_specification "canonicalize-ossa-lifetime true false true @argument" + %empty = function_ref @empty : $@convention(thin) () -> () + apply %empty() : $@convention(thin) () -> () + destroy_value %instance : $MoE + %retval = tuple () + return %retval : $() +} diff --git a/test/SILOptimizer/hoist_destroy_addr.sil b/test/SILOptimizer/hoist_destroy_addr.sil index 71e7099d13d33..837203379b38a 100644 --- a/test/SILOptimizer/hoist_destroy_addr.sil +++ b/test/SILOptimizer/hoist_destroy_addr.sil @@ -79,6 +79,9 @@ struct STXXITXXII { var i: I } +@_moveOnly struct MoS {} +@_moveOnly struct MoE {} + sil @unknown : $@convention(thin) () -> () sil @use_S : $@convention(thin) (@in_guaranteed S) -> () @@ -1145,3 +1148,34 @@ entry(%addr : $*X): %retval = tuple () return %retval : $() } + +// Even though the apply of %empty is not a deinit barrier (c.f. +// hoist_over_apply_of_non_barrier_fn), verify that the destroy_addr is not +// hoisted, because MoS is move-only. +// CHECK-LABEL: sil [ossa] @dont_move_destroy_addr_of_moveonly_struct : {{.*}} { +// CHECK: {{bb[0-9]+}}([[ADDR:%[^,]+]] : +// CHECK: apply +// CHECK: destroy_addr [[ADDR]] +// CHECK-LABEL: } // end sil function 'dont_move_destroy_addr_of_moveonly_struct' +sil [ossa] @dont_move_destroy_addr_of_moveonly_struct : $@convention(thin) (@in MoS) -> () { +entry(%addr : $*MoS): + %empty = function_ref @empty : $@convention(thin) () -> () + apply %empty() : $@convention(thin) () -> () + destroy_addr %addr : $*MoS + %retval = tuple () + return %retval : $() +} + +// CHECK-LABEL: sil [ossa] @dont_move_destroy_addr_of_moveonly_enum : {{.*}} { +// CHECK: {{bb[0-9]+}}([[ADDR:%[^,]+]] : +// CHECK: apply +// CHECK: destroy_addr [[ADDR]] +// CHECK-LABEL: } // end sil function 'dont_move_destroy_addr_of_moveonly_enum' +sil [ossa] @dont_move_destroy_addr_of_moveonly_enum : $@convention(thin) (@in MoE) -> () { +entry(%addr : $*MoE): + %empty = function_ref @empty : $@convention(thin) () -> () + apply %empty() : $@convention(thin) () -> () + destroy_addr %addr : $*MoE + %retval = tuple () + return %retval : $() +} diff --git a/test/SILOptimizer/moveonly_addresschecker.sil b/test/SILOptimizer/moveonly_addresschecker.sil index 65b098e7c3418..da86665e997b7 100644 --- a/test/SILOptimizer/moveonly_addresschecker.sil +++ b/test/SILOptimizer/moveonly_addresschecker.sil @@ -575,3 +575,120 @@ bb0(%0 : @owned $NonTrivialStruct): %9999 = tuple() return %9999 : $() } + +@_moveOnly +struct M { + deinit {} +} +@_moveOnly +struct M2 { + let s1: M + let s2: M +} + +sil @get_M2 : $@convention(thin) () -> @owned M2 +sil @end_addr_see_addr : $@convention(thin) (@in M, @in_guaranteed M) -> () + +/// A single instruction, apply @end_addr_see_addr, consumes one field and +/// borrows another. + +/// Varify that the consumed value isn't destroyed twice and that the borrowed +/// value isn't destroyed before it's used. +/// +/// Note: This test case doesn't have valid SIL (#M2.s1 is consumed twice), but +/// the invalidity wasn't the cause of the miscompile. With the fix, this +/// is transformed into valid SIL. +/// +/// Once verification is enabled, feel free to modify this test case to +/// have a destroy_addr of %second_addr instead, though not that this will +/// no longer verify the fix. +// CHECK-LABEL: sil [ossa] @rdar110909290 : {{.*}} { +// CHECK: [[STACK:%[^,]+]] = alloc_stack $M2 +// CHECK: [[GET_M2:%[^,]+]] = function_ref @get_M2 +// CHECK: [[M2:%[^,]+]] = apply [[GET_M2]]() +// CHECK: store [[M2]] to [init] [[STACK]] : $*M2 +// CHECK-NOT: destroy_addr +// CHECK: [[S1_ADDR:%[^,]+]] = struct_element_addr [[STACK]] : $*M2, #M2.s1 +// CHECK: [[S2_ADDR:%[^,]+]] = struct_element_addr [[STACK]] : $*M2, #M2.s2 +// CHECK: [[END_ADDR_SEE_ADDR:%[^,]+]] = function_ref @end_addr_see_addr +// CHECK: apply [[END_ADDR_SEE_ADDR]]([[S1_ADDR]], [[S2_ADDR]]) +// CHECK-NOT: struct_element_addr [[STACK]] : $*M2, #M2.s1 +// CHECK: [[S2_ADDR_2:%[^,]+]] = struct_element_addr [[STACK]] : $*M2, #M2.s2 +// CHECK: destroy_addr [[S2_ADDR_2]] : $*M +// CHECK-LABEL: } // end sil function 'rdar110909290' +sil [ossa] @rdar110909290 : $@convention(thin) () -> () { +bb0: + %0 = alloc_stack $M2 + %1 = mark_must_check [consumable_and_assignable] %0 : $*M2 + %3 = function_ref @get_M2 : $@convention(thin) () -> @owned M2 + %4 = apply %3() : $@convention(thin) () -> @owned M2 + store %4 to [init] %1 : $*M2 + %first_addr = struct_element_addr %1 : $*M2, #M2.s1 + %second_addr = struct_element_addr %1 : $*M2, #M2.s2 + %end_addr_see_addr = function_ref @end_addr_see_addr : $@convention(thin) (@in M, @in_guaranteed M) -> () + apply %end_addr_see_addr(%first_addr, %second_addr) : $@convention(thin) (@in M, @in_guaranteed M) -> () + destroy_addr %1 : $*M2 + dealloc_stack %0 : $*M2 + %22 = tuple () + return %22 : $() +} + +@_moveOnly +struct M4 { + let s1: M + let s2: M + let s3: M + let s4: M +} + +sil @get_M4 : $@convention(thin) () -> @owned M4 +sil @end_2 : $@convention(thin) (@owned M, @owned M) -> () +sil @see_addr_2 : $@convention(thin) (@in_guaranteed M, @in_guaranteed M) -> () + + +/// Two non-contiguous fields (#M4.s2, #M4.s4) are borrowed by @see_addr_2. +/// Two non-contiguous fields (#M4.s1, #M$.s3) are consumed by @end_2. +/// +/// Verify that #M4.s2 and #M4.s4 both survive past the apply of @see_addr_2. +// CHECK-LABEL: sil [ossa] @rdar110676577 : {{.*}} { +// CHECK: [[STACK:%[^,]+]] = alloc_stack $M4 +// CHECK: [[GET_M4:%[^,]+]] = function_ref @get_M4 +// CHECK: [[M4:%[^,]+]] = apply [[GET_M4]]() : $@convention(thin) () -> @owned M4 +// CHECK: store [[M4]] to [init] [[STACK]] : $*M4 +// CHECK: [[M4_S2_ADDR:%[^,]+]] = struct_element_addr [[STACK]] : $*M4, #M4.s2 +// CHECK: [[M4_S4_ADDR:%[^,]+]] = struct_element_addr [[STACK]] : $*M4, #M4.s4 +// CHECK: [[SEE_ADDR_2:%[^,]+]] = function_ref @see_addr_2 +// CHECK: apply [[SEE_ADDR_2]]([[M4_S2_ADDR]], [[M4_S4_ADDR]]) +// HECK: [[M4_S4_ADDR_2:%[^,]+]] = struct_element_addr [[STACK]] : $*M4, #M4.s4 +// HECK: destroy_addr [[M4_S4_ADDR_2]] +// CHECK: [[M4_S2_ADDR_2:%[^,]+]] = struct_element_addr [[STACK]] : $*M4, #M4.s2 +// CHECK: destroy_addr [[M4_S2_ADDR_2]] +// CHECK: [[M4_S1_ADDR:%[^,]+]] = struct_element_addr [[STACK]] : $*M4, #M4.s1 +// CHECK: [[M4_S1:%[^,]+]] = load [take] [[M4_S1_ADDR]] : $*M +// CHECK: [[M4_S3_ADDR:%[^,]+]] = struct_element_addr [[STACK]] : $*M4, #M4.s3 +// CHECK: [[M4_S3:%[^,]+]] = load [take] [[M4_S3_ADDR]] : $*M +// CHECK: [[END_2:%[^,]+]] = function_ref @end_2 +// CHECK: apply [[END_2]]([[M4_S1]], [[M4_S3]]) +// CHECK-LABEL: } // end sil function 'rdar110676577' +sil [ossa] @rdar110676577 : $@convention(thin) () -> () { +bb0: + %0 = alloc_stack $M4 + %1 = mark_must_check [consumable_and_assignable] %0 : $*M4 + %3 = function_ref @get_M4 : $@convention(thin) () -> @owned M4 + %4 = apply %3() : $@convention(thin) () -> @owned M4 + store %4 to [init] %1 : $*M4 + %6 = struct_element_addr %1 : $*M4, #M4.s2 + %6a = struct_element_addr %1 : $*M4, #M4.s4 + %see_addr_2 = function_ref @see_addr_2 : $@convention(thin) (@in_guaranteed M, @in_guaranteed M) -> () + apply %see_addr_2(%6, %6a) : $@convention(thin) (@in_guaranteed M, @in_guaranteed M) -> () + %12 = struct_element_addr %1 : $*M4, #M4.s1 + %13 = load [copy] %12 : $*M + %14 = struct_element_addr %1 : $*M4, #M4.s3 + %15 = load [copy] %14 : $*M + %16 = function_ref @end_2 : $@convention(thin) (@owned M, @owned M) -> () + %17 = apply %16(%13, %15) : $@convention(thin) (@owned M, @owned M) -> () + destroy_addr %1 : $*M4 + dealloc_stack %0 : $*M4 + %22 = tuple () + return %22 : $() +}