Skip to content

5.9: [MoveOnlyAddressChecker] Fix representation for used fields. #66738

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2 changes: 1 addition & 1 deletion include/swift/Basic/FrozenMultiMap.h
Original file line number Diff line number Diff line change
@@ -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<Key, Optional<Value>> &p1,
const std::pair<Key, Optional<Value>> &p2) {
return p1.first < p2.first;
219 changes: 160 additions & 59 deletions include/swift/SIL/FieldSensitivePrunedLiveness.h
Original file line number Diff line number Diff line change
@@ -345,6 +345,16 @@ struct TypeTreeLeafTypeRange {
return TypeTreeLeafTypeRange(start, end);
}

/// Whether \p bits contains any of the in-range bits.
bool intersects(SmallBitVector const &bits) const {
for (auto element : getRange()) {
if (bits.test(element)) {
return true;
}
}
return false;
}

/// Is the given leaf type specified by \p singleLeafElementNumber apart of
/// our \p range of leaf type values in the our larger type.
bool contains(SubElementOffset singleLeafElementNumber) const {
@@ -358,6 +368,13 @@ struct TypeTreeLeafTypeRange {
endEltOffset >= range.endEltOffset;
}

/// Sets each bit in \p bits corresponding to an element of this range.
void setBits(SmallBitVector &bits) const {
for (auto element : getRange()) {
bits.set(element);
}
}

IntRange<unsigned> getRange() const {
return range(startEltOffset, endEltOffset);
}
@@ -666,17 +683,68 @@ class FieldSensitivePrunedLiveness {
FieldSensitivePrunedLiveBlocks liveBlocks;

public:
enum IsInterestingUser { NonUser, NonLifetimeEndingUse, LifetimeEndingUse };

struct InterestingUser {
TypeTreeLeafTypeRange subEltSpan;
bool isConsuming;
SmallBitVector liveBits;
SmallBitVector consumingBits;

InterestingUser(unsigned bitCount)
: liveBits(bitCount), consumingBits(bitCount) {}

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);
}
}

/// Record that the instruction uses the bits in \p bits.
void addUses(SmallBitVector const &bits, bool lifetimeEnding) {
liveBits |= bits;
if (lifetimeEnding) {
consumingBits |= bits;
}
}

InterestingUser() : subEltSpan(), isConsuming(false) {}
InterestingUser(TypeTreeLeafTypeRange subEltSpan, bool isConsuming)
: subEltSpan(subEltSpan), isConsuming(isConsuming) {}
/// Populates the provided vector with contiguous ranges of bits which are
/// users of the same sort.
void getContiguousRanges(
SmallVectorImpl<std::pair<TypeTreeLeafTypeRange, IsInterestingUser>>
&ranges) const {
if (liveBits.size() == 0)
return;

assert(ranges.empty());
Optional<std::pair<unsigned, IsInterestingUser>> 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});
}

InterestingUser &operator&=(bool otherValue) {
isConsuming &= otherValue;
return *this;
IsInterestingUser isInterestingUser(unsigned element) const {
if (!liveBits.test(element))
return NonUser;
return consumingBits.test(element) ? LifetimeEndingUse
: NonLifetimeEndingUse;
}
};

@@ -758,42 +826,6 @@ class FieldSensitivePrunedLiveness {
return llvm::make_range(users.begin(), users.end());
}

using LifetimeEndingUserRange = OptionalTransformRange<
UserRange,
function_ref<Optional<std::pair<SILInstruction *, TypeTreeLeafTypeRange>>(
const std::pair<SILInstruction *, InterestingUser> &)>>;
LifetimeEndingUserRange getAllLifetimeEndingUses() const {
assert(isInitialized());
function_ref<Optional<std::pair<SILInstruction *, TypeTreeLeafTypeRange>>(
const std::pair<SILInstruction *, InterestingUser> &)>
op;
op = [](const std::pair<SILInstruction *, InterestingUser> &pair)
-> Optional<std::pair<SILInstruction *, TypeTreeLeafTypeRange>> {
if (pair.second.isConsuming)
return {{pair.first, pair.second.subEltSpan}};
return None;
};
return LifetimeEndingUserRange(getAllUsers(), op);
}

using NonLifetimeEndingUserRange = OptionalTransformRange<
UserRange,
function_ref<Optional<std::pair<SILInstruction *, TypeTreeLeafTypeRange>>(
const std::pair<SILInstruction *, InterestingUser> &)>>;
NonLifetimeEndingUserRange getAllNonLifetimeEndingUses() const {
assert(isInitialized());
function_ref<Optional<std::pair<SILInstruction *, TypeTreeLeafTypeRange>>(
const std::pair<SILInstruction *, InterestingUser> &)>
op;
op = [](const std::pair<SILInstruction *, InterestingUser> &pair)
-> Optional<std::pair<SILInstruction *, TypeTreeLeafTypeRange>> {
if (!pair.second.isConsuming)
return {{pair.first, pair.second.subEltSpan}};
return None;
};
return NonLifetimeEndingUserRange(getAllUsers(), op);
}

using UserBlockRange = TransformRange<
UserRange, function_ref<SILBasicBlock *(
const std::pair<SILInstruction *, InterestingUser> &)>>;
@@ -824,6 +856,9 @@ class FieldSensitivePrunedLiveness {
void updateForUse(SILInstruction *user, TypeTreeLeafTypeRange span,
bool lifetimeEnding);

void updateForUse(SILInstruction *user, SmallBitVector const &bits,
bool lifetimeEnding);

void getBlockLiveness(SILBasicBlock *bb, TypeTreeLeafTypeRange span,
SmallVectorImpl<FieldSensitivePrunedLiveBlocks::IsLive>
&resultingFoundLiveness) const {
@@ -848,19 +883,46 @@ class FieldSensitivePrunedLiveness {
SmallBitVector &liveOutBits,
SmallBitVector &deadBits) const;

enum IsInterestingUser { NonUser, NonLifetimeEndingUse, LifetimeEndingUse };
InterestingUser &getOrCreateInterestingUser(SILInstruction *user) {
auto iter = users.find(user);
if (iter == users.end()) {
iter = users.insert({user, InterestingUser(getNumSubElements())}).first;
}
return *&iter->second;
}

/// 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, Optional<TypeTreeLeafTypeRange>>
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 (record->isInterestingUser(element) != kind)
return false;
}
return true;
}

unsigned getNumSubElements() const { return liveBlocks.getNumBitsToTrack(); }
@@ -886,10 +948,12 @@ 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;
getOrCreateInterestingUser(user).addUses(range, lifetimeEnding);
}

void addInterestingUser(SILInstruction *user, SmallBitVector const &bits,
bool lifetimeEnding) {
getOrCreateInterestingUser(user).addUses(bits, lifetimeEnding);
}
};

@@ -1003,6 +1067,11 @@ class FieldSensitivePrunedLiveRange : public FieldSensitivePrunedLiveness {
void updateForUse(SILInstruction *user, TypeTreeLeafTypeRange span,
bool lifetimeEnding);

/// Customize updateForUse for FieldSensitivePrunedLiveness such that we check
/// that we consider defs as stopping liveness from being propagated up.
void updateForUse(SILInstruction *user, SmallBitVector const &bits,
bool lifetimeEnding);

/// Compute the boundary from the blocks discovered during liveness analysis.
///
/// Precondition: \p liveness.getDiscoveredBlocks() is a valid list of all
@@ -1074,6 +1143,14 @@ class FieldSensitiveSSAPrunedLiveRange
return inst == defInst.first && defInst.second->contains(bit);
}

bool isDef(SILInstruction *inst, SmallBitVector const &bits) const {
if (inst != defInst.first)
return false;
SmallBitVector defBits(bits.size());
defInst.second->setBits(defBits);
return (defBits & bits) == bits;
}

bool isDef(SILInstruction *inst, TypeTreeLeafTypeRange span) const {
return inst == defInst.first &&
defInst.second->setIntersection(span).has_value();
@@ -1184,6 +1261,30 @@ class FieldSensitiveMultiDefPrunedLiveRange
*iter, [&](TypeTreeLeafTypeRange span) { return span.contains(bit); });
}

bool isDef(SILValue value, SmallBitVector const &bits) const {
assert(isInitialized());
auto iter = defs.find(cast<SILNode>(value));
if (!iter)
return false;
SmallBitVector allBits(bits.size());
for (auto range : *iter) {
range.setBits(allBits);
}
return (bits & allBits) == bits;
}

bool isDef(SILInstruction *inst, SmallBitVector const &bits) const {
assert(isInitialized());
auto iter = defs.find(cast<SILNode>(inst));
if (!iter)
return false;
SmallBitVector allBits(bits.size());
for (auto range : *iter) {
range.setBits(allBits);
}
return (bits & allBits) == bits;
}

bool isDef(SILInstruction *inst, TypeTreeLeafTypeRange span) const {
assert(isInitialized());
auto iter = defs.find(cast<SILNode>(inst));
91 changes: 82 additions & 9 deletions lib/SIL/Utils/FieldSensitivePrunedLiveness.cpp
Original file line number Diff line number Diff line change
@@ -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
//===----------------------------------------------------------------------===//
@@ -577,6 +609,16 @@ void FieldSensitivePrunedLiveness::updateForUse(SILInstruction *user,
addInterestingUser(user, range, lifetimeEnding);
}

void FieldSensitivePrunedLiveness::updateForUse(SILInstruction *user,
SmallBitVector const &bits,
bool lifetimeEnding) {
for (auto bit : bits.set_bits()) {
liveBlocks.updateForUse(user, bit);
}

addInterestingUser(user, bits, lifetimeEnding);
}

//===----------------------------------------------------------------------===//
// MARK: FieldSensitivePrunedLiveRange
//===----------------------------------------------------------------------===//
@@ -673,9 +715,7 @@ bool FieldSensitivePrunedLiveRange<LivenessWithDefs>::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');
@@ -792,6 +832,42 @@ void FieldSensitivePrunedLiveRange<LivenessWithDefs>::updateForUse(
FieldSensitivePrunedLiveness::updateForUse(user, range, lifetimeEnding);
}

template <typename LivenessWithDefs>
void FieldSensitivePrunedLiveRange<LivenessWithDefs>::updateForUse(
SILInstruction *user, SmallBitVector const &bits, bool lifetimeEnding) {
PRUNED_LIVENESS_LOG(
llvm::dbgs()
<< "Begin FieldSensitivePrunedLiveRange<LivenessWithDefs>::updateForUse "
"for: "
<< *user);
PRUNED_LIVENESS_LOG(llvm::dbgs()
<< "Looking for def instruction earlier in the block!\n");

auto *parentBlock = user->getParent();
for (auto ii = std::next(user->getReverseIterator()),
ie = parentBlock->rend();
ii != ie; ++ii) {
// If we find the def, just mark this instruction as being an interesting
// instruction.
if (asImpl().isDef(&*ii, bits)) {
PRUNED_LIVENESS_LOG(llvm::dbgs() << " Found def: " << *ii);
PRUNED_LIVENESS_LOG(
llvm::dbgs()
<< " Marking inst as interesting user and returning!\n");
addInterestingUser(user, bits, lifetimeEnding);
return;
}
}

// Otherwise, just delegate to our parent class's update for use. This will
// update liveness for our predecessor blocks and add this instruction as an
// interesting user.
PRUNED_LIVENESS_LOG(llvm::dbgs()
<< "No defs found! Delegating to "
"FieldSensitivePrunedLiveness::updateForUse.\n");
FieldSensitivePrunedLiveness::updateForUse(user, bits, lifetimeEnding);
}

//===----------------------------------------------------------------------===//
// MARK: Boundary Computation Utilities
//===----------------------------------------------------------------------===//
@@ -806,8 +882,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 +912,7 @@ void findBoundaryInSSADefBlock(SILNode *ssaDef, unsigned bitNo,
boundary.getDeadDefsBits(cast<SILNode>(&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 +1047,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");
716 changes: 549 additions & 167 deletions lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Large diffs are not rendered by default.

28 changes: 16 additions & 12 deletions lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureUtils.cpp
Original file line number Diff line number Diff line change
@@ -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);
}
}
7 changes: 7 additions & 0 deletions lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp
Original file line number Diff line number Diff line change
@@ -14,6 +14,7 @@

#include "MoveOnlyDiagnostics.h"

#include "swift/AST/Decl.h"
#include "swift/AST/DiagnosticsSIL.h"
#include "swift/AST/Stmt.h"
#include "swift/Basic/Defer.h"
@@ -226,6 +227,12 @@ void DiagnosticEmitter::emitMissingConsumeInDiscardingContext(
return true;

case SILLocation::RegularKind: {
Decl *decl = loc.getAsASTNode<Decl>();
if (decl && isa<AbstractFunctionDecl>(decl)) {
// Having the function itself as a location results in a location at the
// first line of the function. Find another location.
return false;
}
Stmt *stmt = loc.getAsASTNode<Stmt>();
if (!stmt)
return true; // For non-statements, assume it is exiting the func.
5 changes: 5 additions & 0 deletions lib/SILOptimizer/Transforms/SSADestroyHoisting.cpp
Original file line number Diff line number Diff line change
@@ -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;
6 changes: 6 additions & 0 deletions lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp
Original file line number Diff line number Diff line change
@@ -1084,6 +1084,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");
307 changes: 307 additions & 0 deletions test/Interpreter/moveonly_address_maximize.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
// RUN: %target-run-simple-swift(-Xfrontend -sil-verify-all) | %FileCheck %s
// RUN: %target-run-simple-swift(-O -Xfrontend -sil-verify-all) | %FileCheck %s

struct S : ~Copyable {
let s: String
init(_ s: String) { self.s = s }
deinit {
print("destroying \(s)")
}
}
struct S2 : ~Copyable {
var s1: S
var s2: S
init(_ s: String) {
self.s1 = S("\(s).s1")
self.s2 = S("\(s).s2")
}
}
struct S3 : ~Copyable {
var s1: S
var s2: S
var s3: S
init(_ s: String) {
self.s1 = S("\(s).s1")
self.s2 = S("\(s).s2")
self.s3 = S("\(s).s3")
}
}

func consumeVal(_ s: consuming S) {}
func consumeVal(_ s: consuming S2) {}
func borrowVal(_ s: borrowing S) {}
func borrowVal(_ s: borrowing S2) {}

func marker(_ s: String) {
print("\(#function): \(s)")
}

// Simple test that makes sure that we still after we consume have the lifetime
// of s be completely consumed by consumeVal.
// CHECK: destroying simpleTestVar().first.s1
// CHECK: destroying simpleTestVar().first.s2
// CHECK: destroying simpleTestVar().second.s1
// CHECK: destroying simpleTestVar().second.s2
// CHECK: marker(_:): simpleTestVar().1
@_silgen_name("simpleTestVar")
func simpleTestVar() {
var s = S2("\(#function).first")
s = S2("\(#function).second")
consumeVal(s) // Lifetime of s should end here before end of scope.
marker("\(#function).1")
}

// Simple test that proves that we can maximize lifetimes in a field sensitive
// manner. Since we only consume s.s1, s.s2's lifetime should still be maximized
// and be at end of scope.
// CHECK: destroying simpleTestVar2().first.s1
// CHECK: destroying simpleTestVar2().first.s2
// CHECK: destroying simpleTestVar2().second.s1
// CHECK: marker(_:): simpleTestVar2().1
// CHECK: destroying simpleTestVar2().second.s2
func simpleTestVar2() {
var s = S2("\(#function).first")
s = S2("\(#function).second")
consumeVal(s.s1) // Lifetime of s1 should end here.
marker("\(#function).1")
// Lifetime of s2 should end at end of scope after marker.
}

// In this case, we consume all of s by consuming s.s1 and s.s2 separately, so
// all lifetimes should be done before marker.
// CHECK: destroying simpleTestVar3().first.s1
// CHECK: destroying simpleTestVar3().first.s2
// CHECK: destroying simpleTestVar3().second.s1
// CHECK: destroying simpleTestVar3().second.s2
// CHECK: marker(_:): simpleTestVar3().1
func simpleTestVar3() {
var s = S2("\(#function).first")
s = S2("\(#function).second")
consumeVal(s.s1)
consumeVal(s.s2)
marker("\(#function).1") // Lifetimes should end before marker.
}

// In this case, we completely consume s and then reinitialize s implying we
// need to deal with two disjoint lifetimes. The second lifetime of s should end
// after marker.
// CHECK: destroying simpleTestVar3a().first.s1
// CHECK: destroying simpleTestVar3a().first.s2
// CHECK: destroying simpleTestVar3a().second.s1
// CHECK: destroying simpleTestVar3a().second.s2
// CHECK: marker(_:): simpleTestVar3a().1
// CHECK: marker(_:): simpleTestVar3a().2
// CHECK: destroying simpleTestVar3a().third.s1
// CHECK: destroying simpleTestVar3a().third.s2
func simpleTestVar3a() {
var s = S2("\(#function).first")
s = S2("\(#function).second")
consumeVal(s.s1)
consumeVal(s.s2)

marker("\(#function).1")

s = S2("\(#function).third")
marker("\(#function).2")
}

// In this case, we have another borrowVal of s.s2. That should still let s.s2's
// lifetime end after marker.
// CHECK: destroying simpleTestVar3b().first.s1
// CHECK: destroying simpleTestVar3b().first.s2
// CHECK: destroying simpleTestVar3b().second.s1
// CHECK: marker(_:): simpleTestVar3b().1
// CHECK: destroying simpleTestVar3b().second.s2
func simpleTestVar3b() {
var s = S2("\(#function).first")
s = S2("\(#function).second")
consumeVal(s.s1)
borrowVal(s.s2)
marker("\(#function).1") // s2 should end its lifetime after marker.
}

// In this case, we are testing reinitialization and making sure that we can
// handle two initializations properly. We also are testing conditional merge
// logic. Since in both cases below s is completely consumed in b, s's lifetime
// would end at marker.

// CHECK: destroying simpleTestVar4(_:_:)[false, false)].first.s1
// CHECK: destroying simpleTestVar4(_:_:)[false, false)].first.s2
// CHECK: marker(_:): simpleTestVar4(_:_:)[false, false)].1
// CHECK: destroying simpleTestVar4(_:_:)[false, false)].second.s1
// CHECK: destroying simpleTestVar4(_:_:)[false, false)].second.s2
// CHECK: destroying simpleTestVar4(_:_:)[false, false)].third.s1
// CHECK: destroying simpleTestVar4(_:_:)[false, false)].third.s2
// CHECK: marker(_:): simpleTestVar4(_:_:)[false, false)].2

// CHECK: destroying simpleTestVar4(_:_:)[false, true)].first.s1
// CHECK: destroying simpleTestVar4(_:_:)[false, true)].first.s2
// CHECK: marker(_:): simpleTestVar4(_:_:)[false, true)].1
// CHECK: destroying simpleTestVar4(_:_:)[false, true)].second.s1
// CHECK: destroying simpleTestVar4(_:_:)[false, true)].second.s2
// CHECK: destroying simpleTestVar4(_:_:)[false, true)].third.s1
// CHECK: destroying simpleTestVar4(_:_:)[false, true)].third.s2

// CHECK: destroying simpleTestVar4(_:_:)[true, false)].first.s1
// CHECK: destroying simpleTestVar4(_:_:)[true, false)].first.s2
// CHECK: destroying simpleTestVar4(_:_:)[true, false)].second.s1
// CHECK: destroying simpleTestVar4(_:_:)[true, false)].second.s2
// CHECK: destroying simpleTestVar4(_:_:)[true, false)].third.s1
// CHECK: destroying simpleTestVar4(_:_:)[true, false)].third.s2
// CHECK: marker(_:): simpleTestVar4(_:_:)[true, false)].2

// CHECK: destroying simpleTestVar4(_:_:)[true, true)].first.s1
// CHECK: destroying simpleTestVar4(_:_:)[true, true)].first.s2
// CHECK: destroying simpleTestVar4(_:_:)[true, true)].second.s1
// CHECK: destroying simpleTestVar4(_:_:)[true, true)].second.s2
// CHECK: destroying simpleTestVar4(_:_:)[true, true)].third.s1
// CHECK: destroying simpleTestVar4(_:_:)[true, true)].third.s2
func simpleTestVar4(_ b1: Bool, _ b2: Bool) {
var s = S2("\(#function)[\(b1), \(b2))].first")
s = S2("\(#function)[\(b1), \(b2))].second")

if b1 {
consumeVal(s)
} else {
marker("\(#function)[\(b1), \(b2))].1")
// S's lifetime should end after marker in this block.
}

s = S2("\(#function)[\(b1), \(b2))].third")

if b2 {
consumeVal(s)
} else {
marker("\(#function)[\(b1), \(b2))].2")
// S's 2nd lifetime should end after marker in this block.
}
}

// This test is similar to the previous, except we are consuming different
// values along the if/else branch that completely covers the value. As a result
// of this, we need to end the lifetime of s in the branches.
// CHECK: destroying simpleTestVar6(_:)[false].first.s1
// CHECK: destroying simpleTestVar6(_:)[false].first.s2
// CHECK: destroying simpleTestVar6(_:)[false].second.s2
// CHECK: marker(_:): simpleTestVar6(_:)[false].2
// CHECK: destroying simpleTestVar6(_:)[false].second.s1
// CHECK: destroying simpleTestVar6(_:)[false].third.s1
// CHECK: destroying simpleTestVar6(_:)[false].third.s2

// CHECK: destroying simpleTestVar6(_:)[true].first.s1
// CHECK: destroying simpleTestVar6(_:)[true].first.s2
// CHECK: destroying simpleTestVar6(_:)[true].second.s1
// CHECK: marker(_:): simpleTestVar6(_:)[true].1
// CHECK: destroying simpleTestVar6(_:)[true].second.s2
// CHECK: destroying simpleTestVar6(_:)[true].third.s1
// CHECK: destroying simpleTestVar6(_:)[true].third.s2
func simpleTestVar6(_ b: Bool) {
var s = S2("\(#function)[\(b)].first")
s = S2("\(#function)[\(b)].second")

if b {
consumeVal(s.s1) // end of s.s1's lifetime.
marker("\(#function)[\(b)].1")
// s.s2 should end here.
} else {
consumeVal(s.s2) // end of s.s2's lifetime
marker("\(#function)[\(b)].2")
// end of s.s1's lifetime should end after marker.
}

s = S2("\(#function)[\(b)].third")
}

// In this case, we are using S3 implying we have three fields. So despite the
// fact that we are deleting these two values in the if-else branches, s3's
// lifetime needs to end at end of scope.
// CHECK: destroying simpleTestVar6a(_:)[false].first.s1
// CHECK: destroying simpleTestVar6a(_:)[false].first.s2
// CHECK: destroying simpleTestVar6a(_:)[false].first.s3
// CHECK: destroying simpleTestVar6a(_:)[false].second.s2
// CHECK: marker(_:): simpleTestVar6a(_:)[false].2
// CHECK: destroying simpleTestVar6a(_:)[false].second.s1
// CHECK: marker(_:): simpleTestVar6a(_:)[false].3
// CHECK: destroying simpleTestVar6a(_:)[false].second.s3

// CHECK: destroying simpleTestVar6a(_:)[true].first.s1
// CHECK: destroying simpleTestVar6a(_:)[true].first.s2
// CHECK: destroying simpleTestVar6a(_:)[true].first.s3
// CHECK: destroying simpleTestVar6a(_:)[true].second.s1
// CHECK: marker(_:): simpleTestVar6a(_:)[true].1
// CHECK: destroying simpleTestVar6a(_:)[true].second.s2
// CHECK: marker(_:): simpleTestVar6a(_:)[true].3
// CHECK: destroying simpleTestVar6a(_:)[true].second.s3
func simpleTestVar6a(_ b: Bool) {
var s = S3("\(#function)[\(b)].first")
s = S3("\(#function)[\(b)].second")

if b {
consumeVal(s.s1) // end of s.s1's lifetime.
marker("\(#function)[\(b)].1")
// s.s2 should end here.
} else {
consumeVal(s.s2) // end of s.s2's lifetime
marker("\(#function)[\(b)].2")
// end of s.s1's lifetime should end after marker.
}

marker("\(#function)[\(b)].3")
// s.s3's lifetime should end here.
}

// In this case, we are using S3, but we are consuming two disjoint parts of S
// in the if statement so we cover again completely.
// CHECK: destroying simpleTestVar6b(_:)[false].first.s1
// CHECK: destroying simpleTestVar6b(_:)[false].first.s2
// CHECK: destroying simpleTestVar6b(_:)[false].first.s3
// CHECK: destroying simpleTestVar6b(_:)[false].second.s2
// CHECK: marker(_:): simpleTestVar6b(_:)[false].2
// CHECK: destroying simpleTestVar6b(_:)[false].second.s3
// CHECK: destroying simpleTestVar6b(_:)[false].second.s1
// CHECK: marker(_:): simpleTestVar6b(_:)[false].3

// CHECK: destroying simpleTestVar6b(_:)[true].first.s1
// CHECK: destroying simpleTestVar6b(_:)[true].first.s2
// CHECK: destroying simpleTestVar6b(_:)[true].first.s3
// CHECK: destroying simpleTestVar6b(_:)[true].second.s1
// CHECK: destroying simpleTestVar6b(_:)[true].second.s3
// CHECK: marker(_:): simpleTestVar6b(_:)[true].1
// CHECK: destroying simpleTestVar6b(_:)[true].second.s2
// CHECK: marker(_:): simpleTestVar6b(_:)[true].3
func simpleTestVar6b(_ b: Bool) {
var s = S3("\(#function)[\(b)].first")
s = S3("\(#function)[\(b)].second")

if b {
consumeVal(s.s1) // end of s.s1's lifetime.
consumeVal(s.s3) // end of s.s3's lifetime
marker("\(#function)[\(b)].1")
// s.s2 should end here.
} else {
consumeVal(s.s2) // end of s.s2's lifetime
marker("\(#function)[\(b)].2")
// end of s.s1's lifetime should end after marker.
// end of s.s3's lifetime should end after marker.
}

marker("\(#function)[\(b)].3")
}


simpleTestVar()
simpleTestVar2()
simpleTestVar3()
simpleTestVar3a()
simpleTestVar3b()
simpleTestVar4(false, false)
simpleTestVar4(false, true)
simpleTestVar4(true, false)
simpleTestVar4(true, true)
simpleTestVar6(false)
simpleTestVar6(true)
simpleTestVar6a(false)
simpleTestVar6a(true)
simpleTestVar6b(false)
simpleTestVar6b(true)

33 changes: 33 additions & 0 deletions test/Interpreter/moveonly_maximize.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// RUN: %target-run-simple-swift(-Xfrontend -sil-verify-all) | %FileCheck %s
// RUN: %target-run-simple-swift(-O -Xfrontend -sil-verify-all) | %FileCheck %s

// REQUIRES: executable_test
struct Alice: ~Copyable {
var age: Int

init(age: Int) {
print("INIT");
self.age = age
}

deinit { print("DEINIT") }
}

func eatMe(_ alice: consuming Alice) {
print(" start")
print(" age:", alice.age)
print(" end")
}

func doit() {
let alice = Alice(age: 10)
eatMe(alice)
}

doit()

// CHECK: INIT
// CHECK: start
// CHECK: age: 10
// CHECK: end
// CHECK: DEINIT
46 changes: 46 additions & 0 deletions test/SILOptimizer/canonicalize_ossa_lifetime_unit.sil
Original file line number Diff line number Diff line change
@@ -2,6 +2,9 @@

class C {}

@_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
@@ -45,3 +48,46 @@ bb0(%addr : $*C):
%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 : $()
}
41 changes: 20 additions & 21 deletions test/SILOptimizer/discard_checking.swift
Original file line number Diff line number Diff line change
@@ -125,8 +125,8 @@ struct Basics: ~Copyable {
if case .red = c {
discard self // expected-note {{discarded self here}}
} else {
mutator() // expected-error {{must consume 'self' before exiting method that discards self}}
throw E.someError // <- better spot
mutator()
throw E.someError // expected-error {{must consume 'self' before exiting method that discards self}}
}
}

@@ -212,10 +212,10 @@ struct Basics: ~Copyable {
return
} catch {
print("hi")
return // <- better spot!!
return // expected-error {{must consume 'self' before exiting method that discards self}}
}
_ = consume self // expected-warning {{will never be executed}}
} // expected-error {{must consume 'self' before exiting method that discards self}}
}

consuming func test9_fixed(_ c: Color) throws {
if case .red = c {
@@ -238,16 +238,16 @@ struct Basics: ~Copyable {

consuming func test10(_ c: Color) throws {
if case .red = c {
discard self // expected-note {{discarded self here}}
discard self // expected-note 2{{discarded self here}}
return
}

do {
throw E.someError // expected-error {{must consume 'self' before exiting method that discards self}}
throw E.someError
} catch E.someError {
return // <- better spot
return // expected-error {{must consume 'self' before exiting method that discards self}}
} catch {
return // <- ok spot
return // expected-error {{must consume 'self' before exiting method that discards self}}
}
}

@@ -263,8 +263,8 @@ struct Basics: ~Copyable {
borrower()
let x = self
self = x
mutator() // expected-error {{must consume 'self' before exiting method that discards self}}
}
mutator()
} // expected-error {{must consume 'self' before exiting method that discards self}}

consuming func test11_fixed(_ c: Color) {
guard case .red = c else {
@@ -328,13 +328,13 @@ struct Basics: ~Copyable {
_ = consume self
}

consuming func test13(_ c: Color) async { // expected-error {{must consume 'self' before exiting method that discards self}}
consuming func test13(_ c: Color) async {
guard case .red = c else {
discard self // expected-note {{discarded self here}}
return
}
await asyncer()
} // <- better spot
} // expected-error {{must consume 'self' before exiting method that discards self}}

consuming func test13_fixed(_ c: Color) async {
guard case .red = c else {
@@ -350,11 +350,11 @@ struct Basics: ~Copyable {
discard self // expected-note {{discarded self here}}
return
}
await withCheckedContinuation { cont in // expected-error {{must consume 'self' before exiting method that discards self}}
await withCheckedContinuation { cont in
cont.resume()
}
print("back!")
} // <- better spot
} // expected-error {{must consume 'self' before exiting method that discards self}}

consuming func test14_fixed(_ c: Color) async {
guard case .red = c else {
@@ -402,30 +402,29 @@ struct Basics: ~Copyable {
case 0:
fallthrough
case 1:
throw E.someError // expected-error 2{{must consume 'self' before exiting method that discards self}}
throw E.someError // expected-error {{must consume 'self' before exiting method that discards self}}
case 2:
return // expected-error {{must consume 'self' before exiting method that discards self}}
case 3:
fatalError("no") // expected-error {{must consume 'self' before exiting method that discards self}}
case 4:
globalConsumingFn(self)
default:
discard self // expected-note 4{{discarded self here}}
discard self // expected-note 3{{discarded self here}}
}
}

consuming func loopyExit_bad(_ i: Int) {
if i < 0 {
discard self // expected-note 2{{discarded self here}}
discard self // expected-note {{discarded self here}}
return
}

// TODO: rdar://110239087 (avoid duplicate consume-before-exit diagnostics for loop in discarding method)
for _ in 0..<i { // expected-error {{must consume 'self' before exiting method that discards self}}
self = Basics() // expected-error {{must consume 'self' before exiting method that discards self}}
for _ in 0..<i {
self = Basics()
}

return
return // expected-error {{must consume 'self' before exiting method that discards self}}
}

consuming func loopyExit_good(_ i: Int) {
34 changes: 34 additions & 0 deletions test/SILOptimizer/hoist_destroy_addr.sil
Original file line number Diff line number Diff line change
@@ -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 : $()
}
95 changes: 76 additions & 19 deletions test/SILOptimizer/moveonly_addresschecker.sil
Original file line number Diff line number Diff line change
@@ -100,12 +100,12 @@ bb0(%arg1 : @owned $NonTrivialStruct, %arg2 : @owned $NonTrivialStruct):
// CHECK: bb0([[ARG:%.*]] : @owned $NonTrivialStruct):
// CHECK-NEXT: [[ALLOC_STACK:%.*]] = alloc_stack [lexical]
// CHECK-NEXT: store [[ARG]] to [init] [[ALLOC_STACK]]
// CHECK-NEXT: [[GEP_3:%.*]] = struct_element_addr [[ALLOC_STACK]] : $*NonTrivialStruct, #NonTrivialStruct.k
// CHECK-NEXT: [[RESULT:%.*]] = load [take] [[GEP_3]]
// CHECK-NEXT: [[GEP_1:%.*]] = struct_element_addr [[ALLOC_STACK]] : $*NonTrivialStruct, #NonTrivialStruct.copyableK
// CHECK-NEXT: [[GEP_2:%.*]] = struct_element_addr [[ALLOC_STACK]] : $*NonTrivialStruct, #NonTrivialStruct.nonTrivialStruct2
// CHECK-NEXT: destroy_addr [[GEP_1]]
// CHECK-NEXT: destroy_addr [[GEP_2]]
// CHECK-NEXT: [[GEP_3:%.*]] = struct_element_addr [[ALLOC_STACK]] : $*NonTrivialStruct, #NonTrivialStruct.k
// CHECK-NEXT: [[RESULT:%.*]] = load [take] [[GEP_3]]
// CHECK-NEXT: dealloc_stack [[ALLOC_STACK]]
// CHECK-NEXT: return [[RESULT]]
// CHECK: } // end sil function 'simpleInitReturnMoveOnlyField'
@@ -125,10 +125,6 @@ bb0(%0 : @owned $NonTrivialStruct):
// CHECK: bb0([[ARG:%.*]] : @owned $NonTrivialStruct):
// CHECK-NEXT: [[ALLOC_STACK:%.*]] = alloc_stack [lexical]
// CHECK-NEXT: store [[ARG]] to [init] [[ALLOC_STACK]]
// CHECK-NEXT: [[GEP_1:%.*]] = struct_element_addr [[ALLOC_STACK]] : $*NonTrivialStruct, #NonTrivialStruct.copyableK
// CHECK-NEXT: [[GEP_2:%.*]] = struct_element_addr [[ALLOC_STACK]] : $*NonTrivialStruct, #NonTrivialStruct.nonTrivialStruct2
// CHECK-NEXT: destroy_addr [[GEP_1]]
// CHECK-NEXT: destroy_addr [[GEP_2]]
// CHECK-NEXT: cond_br undef, bb1, bb2
//
// CHECK: bb1:
@@ -142,6 +138,10 @@ bb0(%0 : @owned $NonTrivialStruct):
// CHECK-NEXT: br bb3([[RESULT]] :

// CHECK: bb3([[RESULT:%.*]] :
// CHECK-NEXT: [[GEP_1:%.*]] = struct_element_addr [[ALLOC_STACK]] : $*NonTrivialStruct, #NonTrivialStruct.copyableK
// CHECK-NEXT: [[GEP_2:%.*]] = struct_element_addr [[ALLOC_STACK]] : $*NonTrivialStruct, #NonTrivialStruct.nonTrivialStruct2
// CHECK-NEXT: destroy_addr [[GEP_1]]
// CHECK-NEXT: destroy_addr [[GEP_2]]
// CHECK-NEXT: dealloc_stack [[ALLOC_STACK]]
// CHECK-NEXT: return [[RESULT]]
// CHECK: } // end sil function 'simpleInitReturnMoveOnlyFieldMultiBlock'
@@ -172,18 +172,14 @@ bb3(%5 : @owned $Klass):
// CHECK: bb0([[ARG:%.*]] : @owned $NonTrivialStruct):
// CHECK-NEXT: [[ALLOC_STACK:%.*]] = alloc_stack [lexical]
// CHECK-NEXT: store [[ARG]] to [init] [[ALLOC_STACK]]
// CHECK-NEXT: [[GEP_1:%.*]] = struct_element_addr [[ALLOC_STACK]] : $*NonTrivialStruct, #NonTrivialStruct.copyableK
// CHECK-NEXT: [[GEP_2:%.*]] = struct_element_addr [[ALLOC_STACK]] : $*NonTrivialStruct, #NonTrivialStruct.nonTrivialStruct2
// CHECK-NEXT: destroy_addr [[GEP_1]]
// CHECK-NEXT: destroy_addr [[GEP_2]]
// CHECK-NEXT: cond_br undef, bb1, bb2
//
// CHECK: bb1:
// CHECK-NEXT: [[GEP:%.*]] = struct_element_addr [[ALLOC_STACK]]
// CHECK-NEXT: destroy_addr [[GEP]]
// CHECK-NEXT: function_ref get_klass
// CHECK-NEXT: [[FUNC:%.*]] = function_ref @get_klass :
// CHECK-NEXT: [[RESULT:%.*]] = apply [[FUNC]]()
// CHECK-NEXT: [[GEP:%.*]] = struct_element_addr [[ALLOC_STACK]]
// CHECK-NEXT: destroy_addr [[GEP]]
// CHECK-NEXT: br bb3([[RESULT]] :
//
// CHECK: bb2:
@@ -192,6 +188,10 @@ bb3(%5 : @owned $Klass):
// CHECK-NEXT: br bb3([[RESULT]] :

// CHECK: bb3([[RESULT:%.*]] :
// CHECK-NEXT: [[GEP_1:%.*]] = struct_element_addr [[ALLOC_STACK]] : $*NonTrivialStruct, #NonTrivialStruct.copyableK
// CHECK-NEXT: [[GEP_2:%.*]] = struct_element_addr [[ALLOC_STACK]] : $*NonTrivialStruct, #NonTrivialStruct.nonTrivialStruct2
// CHECK-NEXT: destroy_addr [[GEP_1]]
// CHECK-NEXT: destroy_addr [[GEP_2]]
// CHECK-NEXT: dealloc_stack [[ALLOC_STACK]]
// CHECK-NEXT: return [[RESULT]]
// CHECK: } // end sil function 'simpleInitReturnMoveOnlyFieldMultiBlock2'
@@ -222,19 +222,15 @@ bb3(%5 : @owned $Klass):
// CHECK: bb0([[ARG:%.*]] : @owned $NonTrivialStruct):
// CHECK-NEXT: [[ALLOC_STACK:%.*]] = alloc_stack [lexical]
// CHECK-NEXT: store [[ARG]] to [init] [[ALLOC_STACK]]
// CHECK-NEXT: [[GEP_1:%.*]] = struct_element_addr [[ALLOC_STACK]] : $*NonTrivialStruct, #NonTrivialStruct.copyableK
// CHECK-NEXT: [[GEP_2:%.*]] = struct_element_addr [[ALLOC_STACK]] : $*NonTrivialStruct, #NonTrivialStruct.nonTrivialStruct2
// CHECK-NEXT: destroy_addr [[GEP_1]]
// CHECK-NEXT: destroy_addr [[GEP_2]]
// CHECK-NEXT: cond_br undef, bb1, bb2
//
// CHECK: bb1:
// CHECK-NEXT: [[GEP:%.*]] = struct_element_addr [[ALLOC_STACK]]
// CHECK-NEXT: destroy_addr [[GEP]]
// CHECK-NEXT: function_ref get_klass
// CHECK-NEXT: [[FUNC:%.*]] = function_ref @get_klass :
// CHECK-NEXT: [[RESULT:%.*]] = apply [[FUNC]]()
// CHECK-NEXT: [[GEP:%.*]] = struct_element_addr [[ALLOC_STACK]]
// CHECK-NEXT: [[GEPP:%.*]] = struct_element_addr [[ALLOC_STACK]]
// CHECK-NEXT: destroy_addr [[GEPP]]
// CHECK-NEXT: store [[RESULT]] to [init] [[GEP]]
// CHECK-NEXT: [[RESULT:%.*]] = load [take] [[GEP]]
// CHECK-NEXT: br bb3([[RESULT]] :
@@ -245,6 +241,10 @@ bb3(%5 : @owned $Klass):
// CHECK-NEXT: br bb3([[RESULT]] :
//
// CHECK: bb3([[RESULT:%.*]] :
// CHECK-NEXT: [[GEP_1:%.*]] = struct_element_addr [[ALLOC_STACK]] : $*NonTrivialStruct, #NonTrivialStruct.copyableK
// CHECK-NEXT: [[GEP_2:%.*]] = struct_element_addr [[ALLOC_STACK]] : $*NonTrivialStruct, #NonTrivialStruct.nonTrivialStruct2
// CHECK-NEXT: destroy_addr [[GEP_1]]
// CHECK-NEXT: destroy_addr [[GEP_2]]
// CHECK-NEXT: dealloc_stack [[ALLOC_STACK]]
// CHECK-NEXT: return [[RESULT]]
// CHECK: } // end sil function 'simpleInitReturnMoveOnlyFieldMultiBlock3'
@@ -310,8 +310,8 @@ bb0(%arg : @owned $Klass):
// CHECK-LABEL: sil [ossa] @classSimpleNonConsumingUseTest : $@convention(thin) (@owned Klass, @owned Klass) -> () {
// CHECK: [[STACK:%.*]] = alloc_stack [lexical] $Klass, var, name "x2"
// CHECK: store {{%.*}} to [init] [[STACK]]
// CHECK: destroy_addr [[STACK]]
// CHECK: [[ACCESS:%.*]] = begin_access [modify] [static] [[STACK]]
// CHECK: destroy_addr [[STACK]]
// CHECK: store {{%.*}} to [init] [[ACCESS]]
// CHECK: end_access [[ACCESS]]
// CHECK: [[ACCESS:%.*]] = begin_access [read] [static] [[STACK]]
@@ -575,3 +575,60 @@ 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 : $()
}
6 changes: 3 additions & 3 deletions test/SILOptimizer/moveonly_addresschecker_debuginfo.sil
Original file line number Diff line number Diff line change
@@ -50,13 +50,13 @@ bb1:
// CHECK: [[BORROW:%.*]] = load_borrow [[STACK]]
// CHECK: apply {{%.*}}([[BORROW]])
// CHECK-NEXT: end_borrow [[BORROW]]
// CHECK-NEXT: destroy_addr [[STACK]]
// CHECK-NEXT: debug_value undef : $*NonTrivialStruct, let, name "v" // {{.*}}; line:[[DEBUG_LOC]]
// CHECK: br bb3
//
// CHECK: bb2:
// CHECK-NEXT: destroy_addr [[STACK]]
// CHECK-NEXT: br bb3
// CHECK: bb3:
// CHECK-NEXT: debug_value undef : $*NonTrivialStruct, let, name "v" // {{.*}}; line:[[DEBUG_LOC]]
// CHECK-NEXT: destroy_addr [[STACK]]
// CHECK: } // end sil function 'non_lifetime_ending_use_test_boundary_edge'
sil [ossa] @non_lifetime_ending_use_test_boundary_edge : $@convention(thin) () -> () {
%f = function_ref @get_nontrivial_struct : $@convention(thin) () -> @owned NonTrivialStruct
1,130 changes: 1,130 additions & 0 deletions test/SILOptimizer/moveonly_addresschecker_maximize.sil

Large diffs are not rendered by default.

67 changes: 67 additions & 0 deletions test/SILOptimizer/moveonly_addresschecker_unmaximized.sil
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// RUN: %target-sil-opt -module-name moveonly_addresschecker -sil-move-only-address-checker -enable-experimental-feature MoveOnlyClasses -enable-sil-verify-all %s -move-only-address-checker-disable-lifetime-extension=true | %FileCheck %s

@_moveOnly
struct M {
deinit {}
}

@_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]])
// CHECK: [[M4_S4_ADDR_2:%[^,]+]] = struct_element_addr [[STACK]] : $*M4, #M4.s4
// CHECK: 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 : $()
}

9 changes: 4 additions & 5 deletions test/SILOptimizer/moveonly_lifetime.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-swift-emit-sil -sil-verify-all -module-name moveonly_lifetime -o /dev/null -Xllvm -sil-print-canonical-module -Onone -verify -enable-experimental-feature MoveOnlyClasses %s 2>&1 | %FileCheck %s
// RUN: %target-swift-emit-sil -sil-verify-all -module-name moveonly_lifetime -o /dev/null -Xllvm -sil-print-canonical-module -Onone -verify -enable-experimental-feature MoveOnlyClasses %s | %FileCheck %s

struct C : ~Copyable {
deinit {}
@@ -31,17 +31,16 @@ func something()
// CHECK: [[INSTANCE:%.*]] = load [take] [[STACK]]
// CHECK: [[TAKE_C:%[^,]+]] = function_ref @takeC
// CHECK: apply [[TAKE_C]]([[INSTANCE]])
// CHECK: br [[BOTTOM:bb[0-9]+]]
//
// CHECK: [[LEFT]]:
// CHECK: [[INSTANCE:%.*]] = load_borrow [[STACK]]
// CHECK: [[BORROW_C:%[^,]+]] = function_ref @borrowC
// CHECK: apply [[BORROW_C]]([[INSTANCE]])
//
// TODO: Once we maximize lifetimes this should be below something.
// CHECK: destroy_addr [[STACK]]
//
// CHECK: [[SOMETHING:%[^,]+]] = function_ref @something
// CHECK: apply [[SOMETHING]]
// CHECK: destroy_addr [[STACK]]
// CHECK: br [[BOTTOM]]
// CHECK-LABEL: } // end sil function 'test_diamond__consume_r__use_l'
@_silgen_name("test_diamond__consume_r__use_l")
func test_diamond(_ condition: Bool) {