Skip to content

Commit 6f940b4

Browse files
committed
[move-only] Implement the borrow-gep to destructure transform.
This enables us to emit the appropriate error for consuming uses of fields and also causes us to eliminate copies exposed by using fields of a move only type in a non-consuming way. rdar://103271138
1 parent e15eab5 commit 6f940b4

12 files changed

+2391
-116
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -743,7 +743,13 @@ ERROR(sil_moveonlychecker_value_consumed_in_a_loop, none,
743743
"'%0' consumed by a use in a loop", (StringRef))
744744
ERROR(sil_moveonlychecker_exclusivity_violation, none,
745745
"'%0' has consuming use that cannot be eliminated due to a tight exclusivity scope", (StringRef))
746+
ERROR(sil_moveonlychecker_moveonly_field_consumed, none,
747+
"'%0' has a move only field that was consumed before later uses", (StringRef))
746748

749+
NOTE(sil_moveonlychecker_moveonly_field_consumed_here, none,
750+
"move only field consumed here", ())
751+
NOTE(sil_moveonlychecker_boundary_use, none,
752+
"boundary use here", ())
747753
NOTE(sil_moveonlychecker_consuming_use_here, none,
748754
"consuming use", ())
749755
NOTE(sil_moveonlychecker_consuming_closure_use_here, none,

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,9 @@ PASS(MovedAsyncVarDebugInfoPropagator, "sil-moved-async-var-dbginfo-propagator",
460460
"Propagate debug info from moved async vars after coroutine funclet boundaries")
461461
PASS(MoveOnlyDeinitInsertion, "sil-move-only-deinit-insertion",
462462
"After running move only checking, convert last destroy_values to deinit calls")
463+
PASS(MoveOnlyBorrowToDestructureTransform, "sil-move-only-borrow-to-destructure",
464+
"Utility pass that is used to test the borrow to destructure transform "
465+
"independently of the move only object/address checkers")
463466
PASS(PruneVTables, "prune-vtables",
464467
"Mark class methods that do not require vtable dispatch")
465468
PASS_RANGE(AllPasses, AADumper, PruneVTables)

lib/SILOptimizer/Mandatory/CMakeLists.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ target_sources(swiftSILOptimizer PRIVATE
2424
MovedAsyncVarDebugInfoPropagator.cpp
2525
MoveKillsCopyableAddressesChecker.cpp
2626
MoveKillsCopyableValuesChecker.cpp
27-
MoveOnlyDiagnostics.cpp
28-
MoveOnlyObjectChecker.cpp
2927
MoveOnlyAddressChecker.cpp
28+
MoveOnlyBorrowToDestructureTransform.cpp
3029
MoveOnlyDeinitInsertion.cpp
30+
MoveOnlyDiagnostics.cpp
31+
MoveOnlyObjectChecker.cpp
32+
MoveOnlyUtils.cpp
3133
NestedSemanticFunctionCheck.cpp
3234
OptimizeHopToExecutor.cpp
3335
PerformanceDiagnostics.cpp

lib/SILOptimizer/Mandatory/MoveOnlyAddressChecker.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -869,8 +869,7 @@ struct MoveOnlyChecker {
869869
instToDelete->eraseFromParent();
870870
})));
871871
canonicalizer.init(fn, accessBlockAnalysis, domTree, deleter);
872-
diagnosticEmitter.fn = fn;
873-
diagnosticEmitter.canonicalizer = &canonicalizer;
872+
diagnosticEmitter.init(fn, &canonicalizer);
874873
}
875874

876875
/// Search through the current function for candidate mark_must_check
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
//===--- MoveOnlyBorrowToDestructureTransform.cpp -------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
///
13+
/// \file This is a utility pass that simulates how the move only object/address
14+
/// checker run the borrow to destructure transform. It is intended to be used
15+
/// only for testing purposes.
16+
///
17+
/// TODO: Once the BorrowToDestructureTransform moves to ./SILOptimizer/Utils,
18+
/// this should move to ./SILOptimizer/UtilityPasses
19+
///
20+
//===----------------------------------------------------------------------===//
21+
22+
#define DEBUG_TYPE "sil-move-only-checker"
23+
24+
#include "MoveOnlyDiagnostics.h"
25+
#include "MoveOnlyObjectChecker.h"
26+
27+
#include "swift/Basic/Defer.h"
28+
#include "swift/SIL/SILInstruction.h"
29+
#include "swift/SILOptimizer/Analysis/Analysis.h"
30+
#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h"
31+
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
32+
#include "swift/SILOptimizer/PassManager/Passes.h"
33+
#include "swift/SILOptimizer/PassManager/Transforms.h"
34+
#include "llvm/ADT/ArrayRef.h"
35+
36+
using namespace swift;
37+
using namespace swift::siloptimizer;
38+
39+
static bool runTester(SILFunction *fn,
40+
ArrayRef<MarkMustCheckInst *> moveIntroducersToProcess,
41+
PostOrderAnalysis *poa,
42+
DiagnosticEmitter &diagnosticEmitter) {
43+
BorrowToDestructureTransform::IntervalMapAllocator allocator;
44+
bool madeChange = false;
45+
while (!moveIntroducersToProcess.empty()) {
46+
auto *mmci = moveIntroducersToProcess.back();
47+
moveIntroducersToProcess = moveIntroducersToProcess.drop_back();
48+
49+
StackList<BeginBorrowInst *> borrowWorklist(mmci->getFunction());
50+
51+
// If we failed to gather borrows due to the transform not understanding
52+
// part of the SIL, fail and return false.
53+
if (!BorrowToDestructureTransform::gatherBorrows(mmci, borrowWorklist))
54+
return madeChange;
55+
56+
// If we do not have any borrows to process, continue and process the next
57+
// instruction.
58+
if (borrowWorklist.empty())
59+
continue;
60+
61+
SmallVector<SILBasicBlock *, 8> discoveredBlocks;
62+
63+
// Now that we have found all of our borrows, we want to find struct_extract
64+
// uses of our borrow as well as any operands that cannot use an owned
65+
// value.
66+
SWIFT_DEFER { discoveredBlocks.clear(); };
67+
BorrowToDestructureTransform transform(allocator, mmci, diagnosticEmitter,
68+
poa, discoveredBlocks);
69+
70+
// Attempt to gather uses. Return false if we saw something that we did not
71+
// understand.
72+
if (!transform.gatherUses(borrowWorklist))
73+
return madeChange;
74+
75+
// Next make sure that any destructure needing instructions are on the
76+
// boundary in a per bit field sensitive manner.
77+
transform.checkDestructureUsesOnBoundary();
78+
79+
// If we emitted any diagnostic, break out. We return true since we actually
80+
// succeeded in our processing by finding the error. We only return false if
81+
// we want to tell the rest of the checker that there was an internal
82+
// compiler error that we need to emit a "compiler doesn't understand
83+
// error".
84+
if (diagnosticEmitter.emittedAnyDiagnostics())
85+
return madeChange;
86+
87+
// At this point, we know that all of our destructure requiring uses are on
88+
// the boundary of our live range. Now we need to do the rewriting.
89+
transform.blockToAvailableValues.emplace(transform.liveness);
90+
transform.rewriteUses();
91+
92+
// Now that we have done our rewritting, we need to do a few cleanups.
93+
transform.cleanup(borrowWorklist);
94+
}
95+
96+
return madeChange;
97+
}
98+
99+
//===----------------------------------------------------------------------===//
100+
// Top Level Entrypoint
101+
//===----------------------------------------------------------------------===//
102+
103+
namespace {
104+
105+
class MoveOnlyBorrowToDestructureTransformPass : public SILFunctionTransform {
106+
void run() override {
107+
auto *fn = getFunction();
108+
109+
// Only run this pass if the move only language feature is enabled.
110+
if (!fn->getASTContext().LangOpts.Features.contains(Feature::MoveOnly))
111+
return;
112+
113+
// Don't rerun diagnostics on deserialized functions.
114+
if (getFunction()->wasDeserializedCanonical())
115+
return;
116+
117+
assert(fn->getModule().getStage() == SILStage::Raw &&
118+
"Should only run on Raw SIL");
119+
120+
LLVM_DEBUG(llvm::dbgs() << "===> MoveOnly Object Checker. Visiting: "
121+
<< fn->getName() << '\n');
122+
123+
auto *postOrderAnalysis = getAnalysis<PostOrderAnalysis>();
124+
125+
SmallSetVector<MarkMustCheckInst *, 32> moveIntroducersToProcess;
126+
DiagnosticEmitter emitter;
127+
128+
bool madeChange = searchForCandidateObjectMarkMustChecks(
129+
getFunction(), moveIntroducersToProcess, emitter);
130+
if (madeChange) {
131+
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
132+
}
133+
134+
if (emitter.emittedAnyDiagnostics())
135+
return;
136+
137+
auto introducers = llvm::makeArrayRef(moveIntroducersToProcess.begin(),
138+
moveIntroducersToProcess.end());
139+
if (runTester(fn, introducers, postOrderAnalysis, emitter)) {
140+
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
141+
}
142+
}
143+
};
144+
145+
} // namespace
146+
147+
SILTransform *swift::createMoveOnlyBorrowToDestructureTransform() {
148+
return new MoveOnlyBorrowToDestructureTransformPass();
149+
}

lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include "swift/AST/DiagnosticsSIL.h"
1818
#include "swift/SIL/DebugUtils.h"
19+
#include "swift/SIL/FieldSensitivePrunedLiveness.h"
1920
#include "swift/SIL/SILArgument.h"
2021
#include "llvm/Support/Debug.h"
2122

@@ -85,7 +86,7 @@ void DiagnosticEmitter::emitCheckerDoesntUnderstandDiagnostic(
8586
diagnose(fn->getASTContext(), markedValue->getLoc().getSourceLoc(),
8687
diag::sil_moveonlychecker_not_understand_moveonly);
8788
}
88-
valuesWithDiagnostics.insert(markedValue);
89+
registerDiagnosticEmitted(markedValue);
8990
}
9091

9192
//===----------------------------------------------------------------------===//
@@ -104,7 +105,7 @@ void DiagnosticEmitter::emitObjectGuaranteedDiagnostic(
104105
diag::sil_moveonlychecker_guaranteed_value_captured_by_closure,
105106
varName);
106107
emitObjectDiagnosticsForPartialApplyUses();
107-
valuesWithDiagnostics.insert(markedValue);
108+
registerDiagnosticEmitted(markedValue);
108109
}
109110

110111
// If we do not have any non-partial apply consuming uses... just exit early.
@@ -121,7 +122,7 @@ void DiagnosticEmitter::emitObjectGuaranteedDiagnostic(
121122
diag::sil_moveonlychecker_let_value_consumed_in_closure,
122123
varName);
123124
emitObjectDiagnosticsForFoundUses(true /*ignore partial apply uses*/);
124-
valuesWithDiagnostics.insert(markedValue);
125+
registerDiagnosticEmitted(markedValue);
125126
return;
126127
}
127128
}
@@ -131,7 +132,7 @@ void DiagnosticEmitter::emitObjectGuaranteedDiagnostic(
131132
diag::sil_moveonlychecker_guaranteed_value_consumed, varName);
132133

133134
emitObjectDiagnosticsForFoundUses(true /*ignore partial apply uses*/);
134-
valuesWithDiagnostics.insert(markedValue);
135+
registerDiagnosticEmitted(markedValue);
135136
}
136137

137138
void DiagnosticEmitter::emitObjectOwnedDiagnostic(
@@ -145,7 +146,7 @@ void DiagnosticEmitter::emitObjectOwnedDiagnostic(
145146
varName);
146147

147148
emitObjectDiagnosticsForFoundUses();
148-
valuesWithDiagnostics.insert(markedValue);
149+
registerDiagnosticEmitted(markedValue);
149150
}
150151

151152
void DiagnosticEmitter::emitObjectDiagnosticsForFoundUses(
@@ -245,8 +246,7 @@ void DiagnosticEmitter::emitAddressExclusivityHazardDiagnostic(
245246
MarkMustCheckInst *markedValue, SILInstruction *consumingUse) {
246247
if (!useWithDiagnostic.insert(consumingUse).second)
247248
return;
248-
249-
valuesWithDiagnostics.insert(markedValue);
249+
registerDiagnosticEmitted(markedValue);
250250

251251
auto &astContext = markedValue->getFunction()->getASTContext();
252252
StringRef varName = getVariableNameForValue(markedValue);
@@ -269,8 +269,7 @@ void DiagnosticEmitter::emitAddressDiagnostic(MarkMustCheckInst *markedValue,
269269
bool isInOutEndOfFunction) {
270270
if (!useWithDiagnostic.insert(violatingUse).second)
271271
return;
272-
273-
valuesWithDiagnostics.insert(markedValue);
272+
registerDiagnosticEmitted(markedValue);
274273

275274
auto &astContext = markedValue->getFunction()->getASTContext();
276275
StringRef varName = getVariableNameForValue(markedValue);
@@ -360,8 +359,7 @@ void DiagnosticEmitter::emitInOutEndOfFunctionDiagnostic(
360359
MarkMustCheckInst *markedValue, SILInstruction *violatingUse) {
361360
if (!useWithDiagnostic.insert(violatingUse).second)
362361
return;
363-
364-
valuesWithDiagnostics.insert(markedValue);
362+
registerDiagnosticEmitted(markedValue);
365363

366364
assert(cast<SILFunctionArgument>(markedValue->getOperand())
367365
->getArgumentConvention()
@@ -418,5 +416,39 @@ void DiagnosticEmitter::emitAddressDiagnosticNoCopy(
418416
diag::sil_moveonlychecker_guaranteed_value_consumed, varName);
419417
diagnose(astContext, consumingUse->getLoc().getSourceLoc(),
420418
diag::sil_moveonlychecker_consuming_use_here);
421-
valuesWithDiagnostics.insert(markedValue);
419+
registerDiagnosticEmitted(markedValue);
420+
}
421+
422+
void DiagnosticEmitter::emitObjectDestructureNeededWithinBorrowBoundary(
423+
MarkMustCheckInst *markedValue, SILInstruction *destructureNeedingUse,
424+
TypeTreeLeafTypeRange destructureSpan,
425+
FieldSensitivePrunedLivenessBoundary &boundary) {
426+
if (!useWithDiagnostic.insert(destructureNeedingUse).second)
427+
return;
428+
429+
auto &astContext = markedValue->getFunction()->getASTContext();
430+
StringRef varName = getVariableNameForValue(markedValue);
431+
432+
LLVM_DEBUG(llvm::dbgs() << "Emitting destructure can't be created error!\n");
433+
LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue);
434+
LLVM_DEBUG(llvm::dbgs() << " Destructure Needing Use: "
435+
<< *destructureNeedingUse);
436+
437+
diagnose(astContext,
438+
markedValue->getDefiningInstruction()->getLoc().getSourceLoc(),
439+
diag::sil_moveonlychecker_moveonly_field_consumed, varName);
440+
diagnose(astContext, destructureNeedingUse->getLoc().getSourceLoc(),
441+
diag::sil_moveonlychecker_moveonly_field_consumed_here);
442+
// Only emit errors for last users that overlap with our needed destructure
443+
// bits.
444+
for (auto pair : boundary.getLastUsers()) {
445+
if (llvm::any_of(destructureSpan.getRange(),
446+
[&](unsigned index) { return pair.second.test(index); })) {
447+
LLVM_DEBUG(llvm::dbgs()
448+
<< " Destructure Boundary Use: " << *pair.first);
449+
diagnose(astContext, pair.first->getLoc().getSourceLoc(),
450+
diag::sil_moveonlychecker_boundary_use);
451+
}
452+
}
453+
registerDiagnosticEmitted(markedValue);
422454
}

lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,16 @@
2121

2222
#include "MoveOnlyObjectChecker.h"
2323
#include "swift/Basic/NullablePtr.h"
24+
#include "swift/SIL/FieldSensitivePrunedLiveness.h"
25+
#include "swift/SIL/SILInstruction.h"
2426

2527
namespace swift {
28+
29+
class FieldSensitivePrunedLivenessBoundary;
30+
2631
namespace siloptimizer {
2732

28-
struct DiagnosticEmitter {
33+
class DiagnosticEmitter {
2934
SILFunction *fn;
3035

3136
/// The canonicalizer that contains the final consuming uses and consuming
@@ -40,6 +45,14 @@ struct DiagnosticEmitter {
4045
// multiple diagnostics for the same use.
4146
SmallPtrSet<SILInstruction *, 8> useWithDiagnostic;
4247

48+
unsigned diagnosticCount = 0;
49+
50+
public:
51+
void init(SILFunction *inputFn, OSSACanonicalizer *inputCanonicalizer) {
52+
fn = inputFn;
53+
canonicalizer = inputCanonicalizer;
54+
}
55+
4356
/// Clear our cache of uses that we have diagnosed for a specific
4457
/// mark_must_check.
4558
void clearUsesWithDiagnostic() { useWithDiagnostic.clear(); }
@@ -48,6 +61,8 @@ struct DiagnosticEmitter {
4861
return *canonicalizer.get();
4962
}
5063

64+
unsigned getDiagnosticCount() const { return diagnosticCount; }
65+
5166
void emitCheckerDoesntUnderstandDiagnostic(MarkMustCheckInst *markedValue);
5267
void emitObjectGuaranteedDiagnostic(MarkMustCheckInst *markedValue);
5368
void emitObjectOwnedDiagnostic(MarkMustCheckInst *markedValue);
@@ -68,6 +83,10 @@ struct DiagnosticEmitter {
6883
SILInstruction *consumingUse);
6984
void emitAddressExclusivityHazardDiagnostic(MarkMustCheckInst *markedValue,
7085
SILInstruction *consumingUse);
86+
void emitObjectDestructureNeededWithinBorrowBoundary(
87+
MarkMustCheckInst *markedValue, SILInstruction *destructureNeedingUse,
88+
TypeTreeLeafTypeRange destructureNeededBits,
89+
FieldSensitivePrunedLivenessBoundary &boundary);
7190

7291
private:
7392
/// Emit diagnostics for the final consuming uses and consuming uses needing
@@ -76,6 +95,11 @@ struct DiagnosticEmitter {
7695
/// the caller processed it correctly. false, then we continue to process it.
7796
void emitObjectDiagnosticsForFoundUses(bool ignorePartialApply = false) const;
7897
void emitObjectDiagnosticsForPartialApplyUses() const;
98+
99+
void registerDiagnosticEmitted(MarkMustCheckInst *value) {
100+
++diagnosticCount;
101+
valuesWithDiagnostics.insert(value);
102+
}
79103
};
80104

81105
} // namespace siloptimizer

0 commit comments

Comments
 (0)