Skip to content

Commit 4e86268

Browse files
committed
[move-only] Teach the borrow to destructure transform how to handle consuming/non-consuming uses on the same instruction.
NOTE: The additional errors that are occuring in the move only object checker is b/c I tweaked checkDestructureUsesOnBoundary so that when it detects an error it continues instead of returns. This ensures that we get more that we emit errors for multiple violations instead of just the first one. rdar://104900171
1 parent 9cf008c commit 4e86268

9 files changed

+357
-41
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 6 additions & 0 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,
@@ -752,6 +754,10 @@ NOTE(sil_moveonlychecker_boundary_use, none,
752754
"boundary use here", ())
753755
NOTE(sil_moveonlychecker_consuming_use_here, none,
754756
"consuming use", ())
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,
756762
"closure capture", ())
757763
NOTE(sil_moveonlychecker_nonconsuming_use_here, none,

lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureTransform.cpp

Lines changed: 135 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,34 +202,144 @@ 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

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

lib/SILOptimizer/Mandatory/MoveOnlyObjectChecker.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#ifndef SWIFT_SILOPTIMIZER_MANDATORY_MOVEONLYOBJECTCHECKER_H
2121
#define SWIFT_SILOPTIMIZER_MANDATORY_MOVEONLYOBJECTCHECKER_H
2222

23+
#include "swift/Basic/FrozenMultiMap.h"
2324
#include "swift/SIL/FieldSensitivePrunedLiveness.h"
2425
#include "swift/SILOptimizer/Utils/CanonicalizeOSSALifetime.h"
2526
#include "llvm/ADT/IntervalMap.h"
@@ -188,6 +189,11 @@ struct BorrowToDestructureTransform {
188189
SmallFrozenMultiMap<SILBasicBlock *, std::pair<Operand *, InterestingUser>, 8>
189190
blocksToUses;
190191

192+
/// A frozen multi-map we use to diagnose consuming uses that are used by the
193+
/// same instruction as another consuming use or non-consuming use.
194+
SmallFrozenMultiMap<SILInstruction *, Operand *, 8>
195+
instToInterestingOperandIndexMap;
196+
191197
BorrowToDestructureTransform(
192198
IntervalMapAllocator &allocator, MarkMustCheckInst *mmci,
193199
DiagnosticEmitter &diagnosticEmitter, PostOrderAnalysis *poa,
@@ -221,6 +227,10 @@ struct BorrowToDestructureTransform {
221227
/// transform.
222228
void checkDestructureUsesOnBoundary() const;
223229

230+
/// Check for cases where we have two consuming uses on the same instruction
231+
/// or a consuming/non-consuming use on the same instruction.
232+
void checkForErrorsOnSameInstruction();
233+
224234
/// Rewrite all of the uses of our borrow on our borrow operand, performing
225235
/// destructures as appropriate.
226236
void rewriteUses();

0 commit comments

Comments
 (0)