Skip to content

Commit e6dc231

Browse files
committed
[move-only-address] Now that we borrow move only types when we call functions, enable the main move only address transform.
The main constraint that we needed to fight here is that we are not allowed in the move only address checker to expand exclusivity scopes since we run after the main exclusivity passes. This means that to enable this transformation I needed to add a new error diagnostic that is emitted if we are unable to convert a loadCopyOrTake operation to a borrow like operation due to an exclusivity scope that is too tight. Example: ``` %0 = begin_access [read] %addr %1 = load [copy] %0 end_access %0 ... destroy_value %1 ``` Luckily this does not actually happen for move only structs/move only enums after 525ba8c which makes it so that we perform a true borrow of those types when passing them to functions. If we pass a move only class directly, we get the same behavior. Sadly if we pass a field from a move only class, we still get a tight exclusivity scope and thus will emit an error today. This is an issue that we can fight at a later time once we decide to productize move only classes further. But at least structs/enums work well. One can see this behavior by looking at all of the places that needed to be updated as a result of this change. rdar://102491093
1 parent 3101484 commit e6dc231

File tree

4 files changed

+142
-63
lines changed

4 files changed

+142
-63
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -735,6 +735,8 @@ ERROR(sil_moveonlychecker_inout_not_reinitialized_before_end_of_function, none,
735735
"'%0' consumed but not reinitialized before end of function", (StringRef))
736736
ERROR(sil_moveonlychecker_value_consumed_in_a_loop, none,
737737
"'%0' consumed by a use in a loop", (StringRef))
738+
ERROR(sil_moveonlychecker_exclusivity_violation, none,
739+
"'%0' has consuming use that cannot be eliminated due to a tight exclusivity scope", (StringRef))
738740

739741
NOTE(sil_moveonlychecker_consuming_use_here, none,
740742
"consuming use", ())

lib/SILOptimizer/Mandatory/MoveOnlyAddressChecker.cpp

Lines changed: 106 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@
146146
#include "llvm/ADT/PointerUnion.h"
147147
#include "llvm/ADT/SmallBitVector.h"
148148
#include "llvm/ADT/SmallPtrSet.h"
149+
#include "llvm/ADT/SmallVector.h"
149150
#include "llvm/Support/Debug.h"
150151
#include "llvm/Support/ErrorHandling.h"
151152

@@ -204,10 +205,31 @@ struct DiagnosticEmitter {
204205
SILInstruction *violatingUse);
205206
void emitAddressDiagnosticNoCopy(MarkMustCheckInst *markedValue,
206207
SILInstruction *consumingUse);
208+
void emitExclusivityHazardDiagnostic(MarkMustCheckInst *markedValue,
209+
SILInstruction *consumingUse);
207210
};
208211

209212
} // namespace
210213

214+
void DiagnosticEmitter::emitExclusivityHazardDiagnostic(
215+
MarkMustCheckInst *markedValue, SILInstruction *consumingUse) {
216+
if (!useWithDiagnostic.insert(consumingUse).second)
217+
return;
218+
219+
auto &astContext = markedValue->getFunction()->getASTContext();
220+
StringRef varName = getVariableNameForValue(markedValue);
221+
222+
LLVM_DEBUG(llvm::dbgs() << "Emitting error for exclusivity!\n");
223+
LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue);
224+
LLVM_DEBUG(llvm::dbgs() << " Consuming use: " << *consumingUse);
225+
226+
diagnose(astContext,
227+
markedValue->getDefiningInstruction()->getLoc().getSourceLoc(),
228+
diag::sil_moveonlychecker_exclusivity_violation, varName);
229+
diagnose(astContext, consumingUse->getLoc().getSourceLoc(),
230+
diag::sil_moveonlychecker_consuming_use_here);
231+
}
232+
211233
void DiagnosticEmitter::emitAddressDiagnostic(
212234
MarkMustCheckInst *markedValue, SILInstruction *lastLiveUse,
213235
SILInstruction *violatingUse, bool isUseConsuming,
@@ -775,13 +797,18 @@ struct GatherUsesVisitor : public AccessUseVisitor {
775797
bool emittedEarlyDiagnostic = false;
776798
DiagnosticEmitter &diagnosticEmitter;
777799

800+
// Pruned liveness used to validate that load [take]/load [copy] can be
801+
// converted to load_borrow without violating exclusivity.
802+
SSAPrunedLiveness &liveness;
803+
778804
GatherUsesVisitor(MoveOnlyChecker &moveChecker, UseState &useState,
779805
MarkMustCheckInst *markedValue,
780-
DiagnosticEmitter &diagnosticEmitter)
806+
DiagnosticEmitter &diagnosticEmitter,
807+
SSAPrunedLiveness &gatherUsesLiveness)
781808
: AccessUseVisitor(AccessUseType::Overlapping,
782809
NestedAccessType::IgnoreAccessBegin),
783810
moveChecker(moveChecker), useState(useState), markedValue(markedValue),
784-
diagnosticEmitter(diagnosticEmitter) {}
811+
diagnosticEmitter(diagnosticEmitter), liveness(gatherUsesLiveness) {}
785812

786813
bool visitUse(Operand *op, AccessUseType useTy) override;
787814
void reset(SILValue address) { useState.address = address; }
@@ -795,6 +822,41 @@ struct GatherUsesVisitor : public AccessUseVisitor {
795822
/// base address that we are checking which should be the operand of the mark
796823
/// must check value.
797824
SILValue getRootAddress() const { return markedValue; }
825+
826+
/// Returns true if we emitted an error.
827+
bool checkForExclusivityHazards(LoadInst *li) {
828+
SWIFT_DEFER { liveness.clear(); };
829+
830+
LLVM_DEBUG(llvm::dbgs() << "Checking for exclusivity hazards for: " << *li);
831+
832+
// Grab our access path with in scope. We want to find the inner most access
833+
// scope.
834+
auto accessPathWithBase =
835+
AccessPathWithBase::computeInScope(li->getOperand());
836+
auto accessPath = accessPathWithBase.accessPath;
837+
// TODO: Make this a we don't understand error.
838+
assert(accessPath.isValid() && "Invalid access path?!");
839+
840+
auto *bai = dyn_cast<BeginAccessInst>(accessPathWithBase.base);
841+
842+
if (!bai) {
843+
LLVM_DEBUG(llvm::dbgs()
844+
<< " No begin access... so no exclusivity violation!\n");
845+
return false;
846+
}
847+
848+
bool emittedError = false;
849+
liveness.initializeDef(bai);
850+
liveness.computeSimple();
851+
for (auto *consumingUse : li->getConsumingUses()) {
852+
if (!liveness.isWithinBoundary(consumingUse->getUser())) {
853+
diagnosticEmitter.emitExclusivityHazardDiagnostic(
854+
markedValue, consumingUse->getUser());
855+
emittedError = true;
856+
}
857+
}
858+
return emittedError;
859+
}
798860
};
799861

800862
} // end anonymous namespace
@@ -934,6 +996,14 @@ bool GatherUsesVisitor::visitUse(Operand *op, AccessUseType useTy) {
934996
return false;
935997

936998
LLVM_DEBUG(llvm::dbgs() << "Found potential borrow: " << *user);
999+
1000+
if (checkForExclusivityHazards(li)) {
1001+
LLVM_DEBUG(llvm::dbgs() << "Found exclusivity violation?!\n");
1002+
emittedEarlyDiagnostic = true;
1003+
moveChecker.valuesWithDiagnostics.insert(markedValue);
1004+
return true;
1005+
}
1006+
9371007
useState.borrows.insert({user, *leafRange});
9381008
return true;
9391009
}
@@ -965,7 +1035,14 @@ bool GatherUsesVisitor::visitUse(Operand *op, AccessUseType useTy) {
9651035
return false;
9661036

9671037
if (moveChecker.finalConsumingUses.empty()) {
968-
LLVM_DEBUG(llvm::dbgs() << "Found borrow inst: " << *user);
1038+
LLVM_DEBUG(llvm::dbgs() << "Found potential borrow inst: " << *user);
1039+
if (checkForExclusivityHazards(li)) {
1040+
LLVM_DEBUG(llvm::dbgs() << "Found exclusivity violation?!\n");
1041+
emittedEarlyDiagnostic = true;
1042+
moveChecker.valuesWithDiagnostics.insert(markedValue);
1043+
return true;
1044+
}
1045+
9691046
useState.borrows.insert({user, *leafRange});
9701047
} else {
9711048
LLVM_DEBUG(llvm::dbgs() << "Found take inst: " << *user);
@@ -1155,8 +1232,10 @@ bool MoveOnlyChecker::performSingleCheck(MarkMustCheckInst *markedAddress) {
11551232
// Then gather all uses of our address by walking from def->uses. We use this
11561233
// to categorize the uses of this address into their ownership behavior (e.x.:
11571234
// init, reinit, take, destroy, etc.).
1235+
SmallVector<SILBasicBlock *, 32> gatherUsesDiscoveredBlocks;
1236+
SSAPrunedLiveness gatherUsesLiveness(&gatherUsesDiscoveredBlocks);
11581237
GatherUsesVisitor visitor(*this, addressUseState, markedAddress,
1159-
diagnosticEmitter);
1238+
diagnosticEmitter, gatherUsesLiveness);
11601239
SWIFT_DEFER { visitor.clear(); };
11611240
visitor.reset(markedAddress);
11621241
if (!visitAccessPathUses(visitor, accessPath, fn)) {
@@ -1467,13 +1546,6 @@ bool MoveOnlyChecker::performSingleCheck(MarkMustCheckInst *markedAddress) {
14671546
}
14681547
}
14691548

1470-
// Now before we do anything more, just exit and say we errored. This will not
1471-
// actually make us fail and will cause code later in the checker to rewrite
1472-
// all non-explicit copy instructions to their explicit variant. I did this
1473-
// for testing purposes only.
1474-
valuesWithDiagnostics.insert(markedAddress);
1475-
return true;
1476-
14771549
// Ok, we not have emitted our main errors. Now we begin the
14781550
// transformation. We begin by processing borrows. We can also emit errors
14791551
// here if we find that we can not expand the borrow scope of the load [copy]
@@ -1487,50 +1559,16 @@ bool MoveOnlyChecker::performSingleCheck(MarkMustCheckInst *markedAddress) {
14871559
// must have been destroy_value. So we can just gather up those
14881560
// destroy_value and use then to create a new load_borrow scope.
14891561
SILBuilderWithScope builder(li);
1490-
SILValue base = stripAccessMarkers(li->getOperand());
1491-
bool foundAccess = base != li->getOperand();
1492-
1493-
// Insert a new access scope.
1494-
SILValue newBase = base;
1495-
if (foundAccess) {
1496-
newBase = builder.createBeginAccess(
1497-
li->getLoc(), base, SILAccessKind::Read,
1498-
SILAccessEnforcement::Static, false /*no nested conflict*/,
1499-
false /*is from builtin*/);
1500-
}
1501-
auto *lbi = builder.createLoadBorrow(li->getLoc(), newBase);
1562+
auto *lbi = builder.createLoadBorrow(li->getLoc(), li->getOperand());
15021563

15031564
for (auto *consumeUse : li->getConsumingUses()) {
15041565
auto *dvi = cast<DestroyValueInst>(consumeUse->getUser());
15051566
SILBuilderWithScope destroyBuilder(dvi);
15061567
destroyBuilder.createEndBorrow(dvi->getLoc(), lbi);
1507-
if (foundAccess)
1508-
destroyBuilder.createEndAccess(dvi->getLoc(), newBase,
1509-
false /*aborted*/);
15101568
destroys.push_back(dvi);
15111569
changed = true;
15121570
}
15131571

1514-
// TODO: We need to check if this is the only use.
1515-
if (auto *access = dyn_cast<BeginAccessInst>(li->getOperand())) {
1516-
bool foundUnknownUse = false;
1517-
for (auto *accUse : access->getUses()) {
1518-
if (accUse->getUser() == li)
1519-
continue;
1520-
if (auto *ea = dyn_cast<EndAccessInst>(accUse->getUser())) {
1521-
endAccesses.push_back(ea);
1522-
continue;
1523-
}
1524-
foundUnknownUse = true;
1525-
}
1526-
if (!foundUnknownUse) {
1527-
for (auto *end : endAccesses)
1528-
end->eraseFromParent();
1529-
access->replaceAllUsesWithUndef();
1530-
access->eraseFromParent();
1531-
}
1532-
}
1533-
15341572
for (auto *d : destroys)
15351573
d->eraseFromParent();
15361574
li->replaceAllUsesWith(lbi);
@@ -1539,6 +1577,7 @@ bool MoveOnlyChecker::performSingleCheck(MarkMustCheckInst *markedAddress) {
15391577
}
15401578
}
15411579

1580+
llvm::dbgs() << "Borrow: " << *pair.first;
15421581
llvm_unreachable("Unhandled case?!");
15431582
}
15441583

@@ -1570,6 +1609,16 @@ bool MoveOnlyChecker::performSingleCheck(MarkMustCheckInst *markedAddress) {
15701609
}
15711610
llvm_unreachable("Error?! This is a double destroy?!");
15721611
}
1612+
1613+
if (auto *copyAddr = dyn_cast<CopyAddrInst>(&*ii)) {
1614+
if (!copyAddr->isTakeOfSrc()) {
1615+
destroy->eraseFromParent();
1616+
changed = true;
1617+
foundSingleBlockCase = true;
1618+
break;
1619+
}
1620+
llvm_unreachable("Error?! This is a double destroy?!");
1621+
}
15731622
}
15741623

15751624
if (addressUseState.livenessUses.count(&*ii)) {
@@ -1606,6 +1655,17 @@ bool MoveOnlyChecker::performSingleCheck(MarkMustCheckInst *markedAddress) {
16061655
}
16071656
}
16081657

1658+
if (auto *copy = dyn_cast<CopyAddrInst>(take.first)) {
1659+
if (copy->isTakeOfSrc())
1660+
continue;
1661+
// Convert this to its take form.
1662+
if (auto *access = dyn_cast<BeginAccessInst>(copy->getSrc()))
1663+
access->setAccessKind(SILAccessKind::Modify);
1664+
copy->setIsTakeOfSrc(IsTake);
1665+
continue;
1666+
}
1667+
1668+
llvm::dbgs() << "Take User: " << *take.first;
16091669
llvm_unreachable("Unhandled case?!");
16101670
}
16111671

test/SILOptimizer/moveonly_addresschecker_diagnostics.swift

Lines changed: 32 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -339,35 +339,43 @@ public func classAssignToVar5Arg2(_ x: Klass, _ x2: inout Klass) { // expected-e
339339

340340
public func classAccessAccessField(_ x: Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}}
341341
var x2 = x // expected-note {{consuming use}}
342+
// expected-error @-1 {{'x2' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
343+
// expected-error @-2 {{'x2' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
342344
x2 = Klass()
343-
classUseMoveOnlyWithoutEscaping(x2.k!)
345+
classUseMoveOnlyWithoutEscaping(x2.k!) // expected-note {{consuming use}}
344346
for _ in 0..<1024 {
345-
classUseMoveOnlyWithoutEscaping(x2.k!)
347+
classUseMoveOnlyWithoutEscaping(x2.k!) // expected-note {{consuming use}}
346348
}
347349
}
348350

349351
public func classAccessAccessFieldArg(_ x2: inout Klass) {
350-
classUseMoveOnlyWithoutEscaping(x2.k!)
352+
// expected-error @-1 {{'x2' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
353+
// expected-error @-2 {{'x2' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
354+
classUseMoveOnlyWithoutEscaping(x2.k!) // expected-note {{consuming use}}
351355
for _ in 0..<1024 {
352-
classUseMoveOnlyWithoutEscaping(x2.k!)
356+
classUseMoveOnlyWithoutEscaping(x2.k!) // expected-note {{consuming use}}
353357
}
354358
}
355359

356360
public func classAccessConsumeField(_ x: Klass) { // expected-error {{'x' has guaranteed ownership but was consumed}}
357361
var x2 = x // expected-note {{consuming use}}
362+
// expected-error @-1 {{'x2' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
363+
// expected-error @-2 {{'x2' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
358364
x2 = Klass()
359365
// Since a class is a reference type, we do not emit an error here.
360-
classConsume(x2.k!)
366+
classConsume(x2.k!) // expected-note {{consuming use}}
361367
for _ in 0..<1024 {
362-
classConsume(x2.k!)
368+
classConsume(x2.k!) // expected-note {{consuming use}}
363369
}
364370
}
365371

366372
public func classAccessConsumeFieldArg(_ x2: inout Klass) {
373+
// expected-error @-1 {{'x2' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
374+
// expected-error @-2 {{'x2' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
367375
// Since a class is a reference type, we do not emit an error here.
368-
classConsume(x2.k!)
376+
classConsume(x2.k!) // expected-note {{consuming use}}
369377
for _ in 0..<1024 {
370-
classConsume(x2.k!)
378+
classConsume(x2.k!) // expected-note {{consuming use}}
371379
}
372380
}
373381

@@ -628,34 +636,42 @@ public func finalClassAssignToVar5Arg(_ x2: inout FinalKlass) { // expected-erro
628636

629637
public func finalClassAccessField() {
630638
var x2 = FinalKlass()
639+
// expected-error @-1 {{'x2' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
640+
// expected-error @-2 {{'x2' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
631641
x2 = FinalKlass()
632-
classUseMoveOnlyWithoutEscaping(x2.k!)
642+
classUseMoveOnlyWithoutEscaping(x2.k!) // expected-note {{consuming use}}
633643
for _ in 0..<1024 {
634-
classUseMoveOnlyWithoutEscaping(x2.k!)
644+
classUseMoveOnlyWithoutEscaping(x2.k!) // expected-note {{consuming use}}
635645
}
636646
}
637647

638648
public func finalClassAccessFieldArg(_ x2: inout FinalKlass) {
639-
classUseMoveOnlyWithoutEscaping(x2.k!)
649+
// expected-error @-1 {{'x2' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
650+
// expected-error @-2 {{'x2' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
651+
classUseMoveOnlyWithoutEscaping(x2.k!) // expected-note {{consuming use}}
640652
for _ in 0..<1024 {
641-
classUseMoveOnlyWithoutEscaping(x2.k!)
653+
classUseMoveOnlyWithoutEscaping(x2.k!) // expected-note {{consuming use}}
642654
}
643655
}
644656

645657
public func finalClassConsumeField() {
646658
var x2 = FinalKlass()
659+
// expected-error @-1 {{'x2' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
660+
// expected-error @-2 {{'x2' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
647661
x2 = FinalKlass()
648662

649-
classConsume(x2.k!)
663+
classConsume(x2.k!) // expected-note {{consuming use}}
650664
for _ in 0..<1024 {
651-
classConsume(x2.k!)
665+
classConsume(x2.k!) // expected-note {{consuming use}}
652666
}
653667
}
654668

655669
public func finalClassConsumeFieldArg(_ x2: inout FinalKlass) {
656-
classConsume(x2.k!)
670+
// expected-error @-1 {{'x2' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
671+
// expected-error @-2 {{'x2' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
672+
classConsume(x2.k!) // expected-note {{consuming use}}
657673
for _ in 0..<1024 {
658-
classConsume(x2.k!)
674+
classConsume(x2.k!) // expected-note {{consuming use}}
659675
}
660676
}
661677

test/SILOptimizer/moveonly_deinits.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,12 @@ enum MoveOnlyEnum {
3232
deinit { // expected-error {{'self' consumed more than once}}
3333
let x = self // expected-note {{consuming use}}
3434
_ = x
35-
var y = MoveOnlyEnum.lhs(Klass())
35+
var y = MoveOnlyEnum.lhs(Klass()) // expected-error {{'y' has consuming use that cannot be eliminated due to a tight exclusivity scope}}
3636
y = self // expected-note {{consuming use}}
3737
// We get an infinite recursion since we are going to call our own
3838
// deinit here. We are just testing diagnostics here though.
3939
_ = y // expected-warning {{function call causes an infinite recursion}}
40+
// expected-note @-1 {{consuming use}}
4041
globalMoveOnlyEnum = self // expected-note {{consuming use}}
4142
} // expected-note {{consuming use}}
4243
}

0 commit comments

Comments
 (0)