Skip to content

Commit aad6680

Browse files
authored
Merge pull request swiftlang#63342 from gottesmm/pr-be1c0e09106b26c3a14caa3e66d52a889dc36491
[move-only] Teach the borrow to destructure transform how to handle consuming/non-consuming uses on the same instruction.
2 parents 133c936 + 43b90e8 commit aad6680

22 files changed

+2266
-2108
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,8 @@ ERROR(noimplicitcopy_used_on_generic_or_existential, none,
727727
// move only checker diagnostics
728728
ERROR(sil_moveonlychecker_owned_value_consumed_more_than_once, none,
729729
"'%0' consumed more than once", (StringRef))
730+
ERROR(sil_moveonlychecker_owned_value_consumed_and_used_at_same_time, none,
731+
"'%0' consumed and used at the same time", (StringRef))
730732
ERROR(sil_moveonlychecker_value_used_after_consume, none,
731733
"'%0' used after consume. Lifetime extension of variable requires a copy", (StringRef))
732734
ERROR(sil_moveonlychecker_guaranteed_value_consumed, none,
@@ -751,11 +753,15 @@ NOTE(sil_moveonlychecker_moveonly_field_consumed_here, none,
751753
NOTE(sil_moveonlychecker_boundary_use, none,
752754
"boundary use here", ())
753755
NOTE(sil_moveonlychecker_consuming_use_here, none,
754-
"consuming use", ())
756+
"consuming use here", ())
757+
NOTE(sil_moveonlychecker_two_consuming_uses_here, none,
758+
"two consuming uses here", ())
759+
NOTE(sil_moveonlychecker_consuming_and_non_consuming_uses_here, none,
760+
"consuming and non-consuming uses here", ())
755761
NOTE(sil_moveonlychecker_consuming_closure_use_here, none,
756-
"closure capture", ())
762+
"closure capture here", ())
757763
NOTE(sil_moveonlychecker_nonconsuming_use_here, none,
758-
"non-consuming use", ())
764+
"non-consuming use here", ())
759765
ERROR(sil_moveonlychecker_not_understand_no_implicit_copy, none,
760766
"Usage of @noImplicitCopy that the move checker does not know how to "
761767
"check!", ())

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6653,6 +6653,8 @@ ERROR(noimplicitcopy_attr_valid_only_on_local_let_params,
66536653
none, "'@_noImplicitCopy' attribute can only be applied to local lets and params", ())
66546654
ERROR(noimplicitcopy_attr_invalid_in_generic_context,
66556655
none, "'@_noImplicitCopy' attribute cannot be applied to entities in generic contexts", ())
6656+
ERROR(noimplicitcopy_attr_not_allowed_on_moveonlytype,none,
6657+
"'@_noImplicitCopy' has no effect when applied to a move only type", ())
66566658

66576659
//------------------------------------------------------------------------------
66586660
// MARK: Type inference from default expressions

lib/SILGen/SILGenLValue.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "ExecutorBreadcrumb.h"
2222
#include "Initialization.h"
2323
#include "LValue.h"
24+
#include "ManagedValue.h"
2425
#include "RValue.h"
2526
#include "SILGen.h"
2627
#include "SILGenFunction.h"
@@ -4606,6 +4607,19 @@ RValue SILGenFunction::emitLoadOfLValue(SILLocation loc, LValue &&src,
46064607
substFormalType, rvalueTL, C, IsNotTake, isBaseGuaranteed);
46074608
} else if (isReadAccessResultOwned(src.getAccessKind()) &&
46084609
!projection.isPlusOne(*this)) {
4610+
4611+
// Before we copy, if we have a move only wrapped value, unwrap the
4612+
// value using a guaranteed moveonlywrapper_to_copyable.
4613+
if (projection.getType().isMoveOnlyWrapped()) {
4614+
// We are assuming we always get a guaranteed value here.
4615+
assert(projection.getValue()->getOwnershipKind() ==
4616+
OwnershipKind::Guaranteed);
4617+
// We use SILValues here to ensure we get a tight scope around our
4618+
// copy.
4619+
projection =
4620+
B.createGuaranteedMoveOnlyWrapperToCopyableValue(loc, projection);
4621+
}
4622+
46094623
projection = projection.copy(*this, loc);
46104624
}
46114625

lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureTransform.cpp

Lines changed: 149 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,20 @@
2828
#include "MoveOnlyDiagnostics.h"
2929
#include "MoveOnlyObjectChecker.h"
3030

31+
#include "swift/Basic/BlotSetVector.h"
3132
#include "swift/Basic/Defer.h"
33+
#include "swift/Basic/FrozenMultiMap.h"
34+
#include "swift/SIL/FieldSensitivePrunedLiveness.h"
3235
#include "swift/SIL/SILBuilder.h"
3336
#include "swift/SIL/SILInstruction.h"
3437
#include "swift/SILOptimizer/Analysis/Analysis.h"
3538
#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h"
36-
#include "swift/Basic/BlotSetVector.h"
37-
#include "swift/Basic/FrozenMultiMap.h"
3839
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
3940
#include "swift/SILOptimizer/PassManager/Passes.h"
4041
#include "swift/SILOptimizer/PassManager/Transforms.h"
4142
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
4243
#include "llvm/ADT/ArrayRef.h"
44+
#include "llvm/ADT/SmallBitVector.h"
4345

4446
using namespace swift;
4547
using namespace swift::siloptimizer;
@@ -143,6 +145,7 @@ bool BorrowToDestructureTransform::gatherUses(
143145
{nextUse, {*leafRange, false /*is lifetime ending*/}});
144146
liveness.updateForUse(nextUse->getUser(), *leafRange,
145147
false /*is lifetime ending*/);
148+
instToInterestingOperandIndexMap.insert(nextUse->getUser(), nextUse);
146149
continue;
147150
}
148151

@@ -166,6 +169,7 @@ bool BorrowToDestructureTransform::gatherUses(
166169
{nextUse, {*leafRange, true /*is lifetime ending*/}});
167170
liveness.updateForUse(nextUse->getUser(), *leafRange,
168171
true /*is lifetime ending*/);
172+
instToInterestingOperandIndexMap.insert(nextUse->getUser(), nextUse);
169173
continue;
170174
}
171175

@@ -198,39 +202,163 @@ bool BorrowToDestructureTransform::gatherUses(
198202
return true;
199203
}
200204

205+
void BorrowToDestructureTransform::checkForErrorsOnSameInstruction() {
206+
// At this point, we have emitted all boundary checks. We also now need to
207+
// check if any of our consuming uses that are on the boundary are used by the
208+
// same instruction as a different consuming or non-consuming use.
209+
instToInterestingOperandIndexMap.setFrozen();
210+
SmallBitVector usedBits(liveness.getNumSubElements());
211+
212+
for (auto instRangePair : instToInterestingOperandIndexMap.getRange()) {
213+
SWIFT_DEFER { usedBits.reset(); };
214+
215+
// First loop through our uses and handle any consuming twice errors. We
216+
// also setup usedBits to check for non-consuming uses that may overlap.
217+
Operand *badOperand = nullptr;
218+
Optional<TypeTreeLeafTypeRange> badRange;
219+
for (auto *use : instRangePair.second) {
220+
if (!use->isConsuming())
221+
continue;
222+
223+
auto destructureUseSpan = *TypeTreeLeafTypeRange::get(use->get(), mmci);
224+
for (unsigned index : destructureUseSpan.getRange()) {
225+
if (usedBits[index]) {
226+
// If we get that we used the same bit twice, we have an error. We set
227+
// the badIndex error and break early.
228+
badOperand = use;
229+
badRange = destructureUseSpan;
230+
break;
231+
}
232+
233+
usedBits[index] = true;
234+
}
235+
236+
// If we set badOperand, break so we can emit an error for this
237+
// instruction.
238+
if (badOperand)
239+
break;
240+
}
241+
242+
// If we did not set badIndex for consuming uses, we did not have any
243+
// conflicts among consuming uses. see if we have any conflicts with
244+
// non-consuming uses. Otherwise, we continue.
245+
if (!badOperand) {
246+
for (auto *use : instRangePair.second) {
247+
if (use->isConsuming())
248+
continue;
249+
250+
auto destructureUseSpan = *TypeTreeLeafTypeRange::get(use->get(), mmci);
251+
for (unsigned index : destructureUseSpan.getRange()) {
252+
if (!usedBits[index])
253+
continue;
254+
255+
// If we get that we used the same bit twice, we have an error. We set
256+
// the badIndex error and break early.
257+
badOperand = use;
258+
badRange = destructureUseSpan;
259+
break;
260+
}
261+
262+
// If we set badOperand, break so we can emit an error for this
263+
// instruction.
264+
if (badOperand)
265+
break;
266+
}
267+
268+
// If we even did not find a non-consuming use that conflicts, then
269+
// continue.
270+
if (!badOperand)
271+
continue;
272+
}
273+
274+
// If badIndex is set, we broke out of the inner loop and need to emit an
275+
// error. Use a little more compile time to identify the other operand that
276+
// caused the failure. NOTE: badOperand /could/ be a non-consuming use, but
277+
// the use we are identifying here will always be consuming.
278+
usedBits.reset();
279+
280+
// Reinitialize use bits with the bad bits.
281+
for (unsigned index : badRange->getRange())
282+
usedBits[index] = true;
283+
284+
// Now loop back through looking for the original operand that set the used
285+
// bits. This will always be a consuming use.
286+
for (auto *use : instRangePair.second) {
287+
if (!use->isConsuming())
288+
continue;
289+
290+
auto destructureUseSpan = *TypeTreeLeafTypeRange::get(use->get(), mmci);
291+
bool emittedError = false;
292+
for (unsigned index : destructureUseSpan.getRange()) {
293+
if (!usedBits[index])
294+
continue;
295+
296+
if (badOperand->isConsuming())
297+
diagnosticEmitter.emitObjectConsumesDestructuredValueTwice(
298+
mmci, use, badOperand);
299+
else
300+
diagnosticEmitter.emitObjectConsumesAndUsesDestructuredValue(
301+
mmci, use, badOperand);
302+
emittedError = true;
303+
}
304+
305+
// Once we have emitted the error, just break out of the loop.
306+
if (emittedError)
307+
break;
308+
}
309+
}
310+
}
311+
201312
void BorrowToDestructureTransform::checkDestructureUsesOnBoundary() const {
202313
LLVM_DEBUG(llvm::dbgs() << "Checking destructure uses on boundary!\n");
314+
203315
// Now that we have found all of our destructure needing uses and liveness
204316
// needing uses, make sure that none of our destructure needing uses are
205-
// within our boundary. If so, we have an automatic error.
317+
// within our boundary. If so, we have an automatic error since we have a
318+
// use-after-free.
206319
for (auto *use : destructureNeedingUses) {
207320
LLVM_DEBUG(llvm::dbgs()
208321
<< " DestructureNeedingUse: " << *use->getUser());
322+
209323
auto destructureUseSpan = *TypeTreeLeafTypeRange::get(use->get(), mmci);
210-
if (liveness.isWithinBoundary(use->getUser(), destructureUseSpan)) {
211-
LLVM_DEBUG(llvm::dbgs() << " Within boundary! Emitting error!\n");
212-
// Emit an error. We have a use after free.
213-
//
214-
// NOTE: Since we are going to emit an error here, we do the boundary
215-
// computation to ensure that we only do the boundary computation once:
216-
// when we emit an error or once we know we need to do rewriting.
217-
//
218-
// TODO: Fix diagnostic to use destructure needing use and boundary
219-
// uses.
220-
FieldSensitivePrunedLivenessBoundary boundary(
221-
liveness.getNumSubElements());
222-
liveness.computeBoundary(boundary);
223-
diagnosticEmitter.emitObjectDestructureNeededWithinBorrowBoundary(
224-
mmci, use->getUser(), destructureUseSpan, boundary);
225-
return;
226-
} else {
227-
LLVM_DEBUG(llvm::dbgs() << " On boundary! No error!\n");
324+
if (!liveness.isWithinBoundary(use->getUser(), destructureUseSpan)) {
325+
LLVM_DEBUG(llvm::dbgs()
326+
<< " On boundary or within boundary! No error!\n");
327+
continue;
228328
}
329+
330+
// Emit an error. We have a use after free.
331+
//
332+
// NOTE: Since we are going to emit an error here, we do the boundary
333+
// computation to ensure that we only do the boundary computation once:
334+
// when we emit an error or once we know we need to do rewriting.
335+
//
336+
// TODO: Fix diagnostic to use destructure needing use and boundary
337+
// uses.
338+
LLVM_DEBUG(llvm::dbgs() << " Within boundary! Emitting error!\n");
339+
FieldSensitivePrunedLivenessBoundary boundary(liveness.getNumSubElements());
340+
liveness.computeBoundary(boundary);
341+
diagnosticEmitter.emitObjectDestructureNeededWithinBorrowBoundary(
342+
mmci, use->getUser(), destructureUseSpan, boundary);
229343
}
230344
}
231345

232346
bool BorrowToDestructureTransform::gatherBorrows(
233347
MarkMustCheckInst *mmci, StackList<BeginBorrowInst *> &borrowWorklist) {
348+
// If we have a no implicit copy mark_must_check, we do not run the borrow to
349+
// destructure transform since:
350+
//
351+
// 1. If we have a move only type, we should have emitted an earlier error
352+
// saying that move only types should not be marked as no implicit copy.
353+
//
354+
// 2. If we do not have a move only type, then we know that all fields that we
355+
// access directly and would cause a need to destructure must be copyable,
356+
// so no transformation/error is needed.
357+
if (mmci->getType().isMoveOnlyWrapped()) {
358+
LLVM_DEBUG(llvm::dbgs() << "Skipping move only wrapped inst: " << *mmci);
359+
return true;
360+
}
361+
234362
LLVM_DEBUG(llvm::dbgs() << "Searching for borrows for inst: " << *mmci);
235363

236364
StackList<Operand *> worklist(mmci->getFunction());

lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureTransformTester.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,19 @@ static bool runTransform(SILFunction *fn,
106106
continue;
107107
}
108108

109+
// Then check if we had two consuming uses on the same instruction or a
110+
// consuming/non-consuming use on the same isntruction.
111+
transform.checkForErrorsOnSameInstruction();
112+
113+
// If we emitted any diagnostic, set madeChange to true, eliminate our mmci,
114+
// and continue.
115+
if (currentDiagnosticCount != diagnosticEmitter.getDiagnosticCount()) {
116+
mmci->replaceAllUsesWith(mmci->getOperand());
117+
mmci->eraseFromParent();
118+
madeChange = true;
119+
continue;
120+
}
121+
109122
// At this point, we know that all of our destructure requiring uses are on
110123
// the boundary of our live range. Now we need to do the rewriting.
111124
transform.blockToAvailableValues.emplace(transform.liveness);

lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -453,3 +453,56 @@ void DiagnosticEmitter::emitObjectDestructureNeededWithinBorrowBoundary(
453453
}
454454
registerDiagnosticEmitted(markedValue);
455455
}
456+
457+
void DiagnosticEmitter::emitObjectConsumesDestructuredValueTwice(
458+
MarkMustCheckInst *markedValue, Operand *firstUse, Operand *secondUse) {
459+
assert(firstUse->getUser() == secondUse->getUser());
460+
assert(firstUse->isConsuming());
461+
assert(secondUse->isConsuming());
462+
463+
LLVM_DEBUG(
464+
llvm::dbgs() << "Emitting object consumes destructure twice error!\n");
465+
LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue);
466+
LLVM_DEBUG(llvm::dbgs() << " User: " << *firstUse->getUser());
467+
LLVM_DEBUG(llvm::dbgs() << " First Conflicting Operand: "
468+
<< firstUse->getOperandNumber() << '\n');
469+
LLVM_DEBUG(llvm::dbgs() << " Second Conflicting Operand: "
470+
<< secondUse->getOperandNumber() << '\n');
471+
472+
auto &astContext = markedValue->getModule().getASTContext();
473+
StringRef varName = getVariableNameForValue(markedValue);
474+
diagnose(astContext,
475+
markedValue->getDefiningInstruction()->getLoc().getSourceLoc(),
476+
diag::sil_moveonlychecker_owned_value_consumed_more_than_once,
477+
varName);
478+
diagnose(astContext, firstUse->getUser()->getLoc().getSourceLoc(),
479+
diag::sil_moveonlychecker_two_consuming_uses_here);
480+
registerDiagnosticEmitted(markedValue);
481+
}
482+
483+
void DiagnosticEmitter::emitObjectConsumesAndUsesDestructuredValue(
484+
MarkMustCheckInst *markedValue, Operand *consumingUse,
485+
Operand *nonConsumingUse) {
486+
assert(consumingUse->getUser() == nonConsumingUse->getUser());
487+
assert(consumingUse->isConsuming());
488+
assert(!nonConsumingUse->isConsuming());
489+
490+
LLVM_DEBUG(
491+
llvm::dbgs() << "Emitting object consumes destructure twice error!\n");
492+
LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue);
493+
LLVM_DEBUG(llvm::dbgs() << " User: " << *consumingUse->getUser());
494+
LLVM_DEBUG(llvm::dbgs() << " Consuming Operand: "
495+
<< consumingUse->getOperandNumber() << '\n');
496+
LLVM_DEBUG(llvm::dbgs() << " Non Consuming Operand: "
497+
<< nonConsumingUse->getOperandNumber() << '\n');
498+
499+
auto &astContext = markedValue->getModule().getASTContext();
500+
StringRef varName = getVariableNameForValue(markedValue);
501+
diagnose(astContext,
502+
markedValue->getDefiningInstruction()->getLoc().getSourceLoc(),
503+
diag::sil_moveonlychecker_owned_value_consumed_and_used_at_same_time,
504+
varName);
505+
diagnose(astContext, consumingUse->getUser()->getLoc().getSourceLoc(),
506+
diag::sil_moveonlychecker_consuming_and_non_consuming_uses_here);
507+
registerDiagnosticEmitted(markedValue);
508+
}

lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,14 @@ class DiagnosticEmitter {
9191
TypeTreeLeafTypeRange destructureNeededBits,
9292
FieldSensitivePrunedLivenessBoundary &boundary);
9393

94+
void emitObjectConsumesDestructuredValueTwice(MarkMustCheckInst *markedValue,
95+
Operand *firstConsumingUse,
96+
Operand *secondConsumingUse);
97+
void
98+
emitObjectConsumesAndUsesDestructuredValue(MarkMustCheckInst *markedValue,
99+
Operand *consumingUse,
100+
Operand *nonConsumingUse);
101+
94102
private:
95103
/// Emit diagnostics for the final consuming uses and consuming uses needing
96104
/// copy. If filter is non-null, allow for the caller to pre-process operands

lib/SILOptimizer/Mandatory/MoveOnlyObjectChecker.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,18 @@ bool MoveOnlyChecker::convertBorrowExtractsToOwnedDestructures(
387387
unsigned diagnosticCount = diagnosticEmitter.getDiagnosticCount();
388388
transform.checkDestructureUsesOnBoundary();
389389

390+
// If we emitted any diagnostic, break out. We return true since we actually
391+
// succeeded in our processing by finding the error. We only return false if
392+
// we want to tell the rest of the checker that there was an internal
393+
// compiler error that we need to emit a "compiler doesn't understand
394+
// error".
395+
if (diagnosticCount != diagnosticEmitter.getDiagnosticCount())
396+
return true;
397+
398+
// Then check if we had two consuming uses on the same instruction or a
399+
// consuming/non-consuming use on the same isntruction.
400+
transform.checkForErrorsOnSameInstruction();
401+
390402
// If we emitted any diagnostic, break out. We return true since we actually
391403
// succeeded in our processing by finding the error. We only return false if
392404
// we want to tell the rest of the checker that there was an internal

0 commit comments

Comments
 (0)