Skip to content

Commit 39d3264

Browse files
committed
[move-only] Make sure that when the borrow to destructure transform emits an error, we clean up the IR appropriately.
This ensures we appropriately eliminate mark_must_check if we error and change copy_value -> explicit_copy_value.
1 parent 7016679 commit 39d3264

File tree

5 files changed

+168
-36
lines changed

5 files changed

+168
-36
lines changed

lib/SILOptimizer/Mandatory/MoveOnlyBorrowToDestructureTransform.cpp

Lines changed: 52 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
#include "swift/SIL/SILInstruction.h"
2929
#include "swift/SILOptimizer/Analysis/Analysis.h"
3030
#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h"
31+
#include "swift/Basic/BlotSetVector.h"
32+
#include "swift/Basic/FrozenMultiMap.h"
3133
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
3234
#include "swift/SILOptimizer/PassManager/Passes.h"
3335
#include "swift/SILOptimizer/PassManager/Transforms.h"
@@ -37,9 +39,6 @@
3739
using namespace swift;
3840
using namespace swift::siloptimizer;
3941

40-
using namespace swift;
41-
using namespace swift::siloptimizer;
42-
4342
namespace {
4443
using AvailableValues = BorrowToDestructureTransform::AvailableValues;
4544
}
@@ -202,8 +201,8 @@ void BorrowToDestructureTransform::checkDestructureUsesOnBoundary() const {
202201
for (auto *use : destructureNeedingUses) {
203202
LLVM_DEBUG(llvm::dbgs()
204203
<< " DestructureNeedingUse: " << *use->getUser());
205-
auto destructureUse = *TypeTreeLeafTypeRange::get(use->get(), mmci);
206-
if (liveness.isWithinBoundary(use->getUser(), destructureUse)) {
204+
auto destructureUseSpan = *TypeTreeLeafTypeRange::get(use->get(), mmci);
205+
if (liveness.isWithinBoundary(use->getUser(), destructureUseSpan)) {
207206
LLVM_DEBUG(llvm::dbgs() << " Within boundary! Emitting error!\n");
208207
// Emit an error. We have a use after free.
209208
//
@@ -217,7 +216,7 @@ void BorrowToDestructureTransform::checkDestructureUsesOnBoundary() const {
217216
liveness.getNumSubElements());
218217
liveness.computeBoundary(boundary);
219218
diagnosticEmitter.emitObjectDestructureNeededWithinBorrowBoundary(
220-
mmci, use->getUser(), destructureUse, boundary);
219+
mmci, use->getUser(), destructureUseSpan, boundary);
221220
return;
222221
} else {
223222
LLVM_DEBUG(llvm::dbgs() << " On boundary! No error!\n");
@@ -1338,12 +1337,20 @@ static bool runTransform(SILFunction *fn,
13381337
auto *mmci = moveIntroducersToProcess.back();
13391338
moveIntroducersToProcess = moveIntroducersToProcess.drop_back();
13401339

1340+
unsigned currentDiagnosticCount = diagnosticEmitter.getDiagnosticCount();
1341+
13411342
StackList<BeginBorrowInst *> borrowWorklist(mmci->getFunction());
13421343

13431344
// If we failed to gather borrows due to the transform not understanding
1344-
// part of the SIL, fail and return false.
1345-
if (!BorrowToDestructureTransform::gatherBorrows(mmci, borrowWorklist))
1346-
return madeChange;
1345+
// part of the SIL, emit a diagnostic, RAUW the mark must check, and
1346+
// continue.
1347+
if (!BorrowToDestructureTransform::gatherBorrows(mmci, borrowWorklist)) {
1348+
diagnosticEmitter.emitCheckerDoesntUnderstandDiagnostic(mmci);
1349+
mmci->replaceAllUsesWith(mmci->getOperand());
1350+
mmci->eraseFromParent();
1351+
madeChange = true;
1352+
continue;
1353+
}
13471354

13481355
// If we do not have any borrows to process, continue and process the next
13491356
// instruction.
@@ -1360,29 +1367,43 @@ static bool runTransform(SILFunction *fn,
13601367
poa, discoveredBlocks);
13611368

13621369
// Attempt to gather uses. Return if we saw something that we did not
1363-
// understand. Return made change so we invalidate as appropriate.
1364-
if (!transform.gatherUses(borrowWorklist))
1365-
return madeChange;
1370+
// understand. Emit a compiler did not understand diagnostic, RAUW the mmci
1371+
// so later passes do not see it, and set madeChange to true.
1372+
if (!transform.gatherUses(borrowWorklist)) {
1373+
diagnosticEmitter.emitCheckerDoesntUnderstandDiagnostic(mmci);
1374+
mmci->replaceAllUsesWith(mmci->getOperand());
1375+
mmci->eraseFromParent();
1376+
madeChange = true;
1377+
continue;
1378+
}
13661379

13671380
// Next make sure that any destructure needing instructions are on the
13681381
// boundary in a per bit field sensitive manner.
13691382
transform.checkDestructureUsesOnBoundary();
13701383

1371-
// If we emitted any diagnostic, break out. We return true since we actually
1372-
// succeeded in our processing by finding the error. We only return false if
1373-
// we want to tell the rest of the checker that there was an internal
1374-
// compiler error that we need to emit a "compiler doesn't understand
1375-
// error".
1376-
if (diagnosticEmitter.emittedAnyDiagnostics())
1377-
return madeChange;
1384+
// If we emitted any diagnostic, set madeChange to true, eliminate our mmci,
1385+
// and continue.
1386+
if (currentDiagnosticCount != diagnosticEmitter.getDiagnosticCount()) {
1387+
mmci->replaceAllUsesWith(mmci->getOperand());
1388+
mmci->eraseFromParent();
1389+
madeChange = true;
1390+
continue;
1391+
}
13781392

13791393
// At this point, we know that all of our destructure requiring uses are on
13801394
// the boundary of our live range. Now we need to do the rewriting.
13811395
transform.blockToAvailableValues.emplace(transform.liveness);
13821396
transform.rewriteUses();
13831397

13841398
// Now that we have done our rewritting, we need to do a few cleanups.
1399+
//
1400+
// NOTE: We do not eliminate our mark_must_check since we want later passes
1401+
// to do additional checking upon the mark_must_check including making sure
1402+
// that our destructures do not need cause the need for additional copies to
1403+
// be inserted. We only eliminate the mark_must_check if we emitted a
1404+
// diagnostic of some sort.
13851405
transform.cleanup(borrowWorklist);
1406+
madeChange = true;
13861407
}
13871408

13881409
return madeChange;
@@ -1405,8 +1426,9 @@ class MoveOnlyBorrowToDestructureTransformPass : public SILFunctionTransform {
14051426
assert(fn->getModule().getStage() == SILStage::Raw &&
14061427
"Should only run on Raw SIL");
14071428

1408-
LLVM_DEBUG(llvm::dbgs() << "===> MoveOnly Object Checker. Visiting: "
1409-
<< fn->getName() << '\n');
1429+
LLVM_DEBUG(llvm::dbgs()
1430+
<< "===> MoveOnlyBorrowToDestructureTransform. Visiting: "
1431+
<< fn->getName() << '\n');
14101432

14111433
auto *postOrderAnalysis = getAnalysis<PostOrderAnalysis>();
14121434

@@ -1419,14 +1441,22 @@ class MoveOnlyBorrowToDestructureTransformPass : public SILFunctionTransform {
14191441
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
14201442
}
14211443

1422-
if (emitter.emittedAnyDiagnostics())
1444+
if (emitter.emittedAnyDiagnostics()) {
1445+
if (cleanupSILAfterEmittingObjectMoveOnlyDiagnostics(fn))
1446+
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
14231447
return;
1448+
}
14241449

14251450
auto introducers = llvm::makeArrayRef(moveIntroducersToProcess.begin(),
14261451
moveIntroducersToProcess.end());
14271452
if (runTransform(fn, introducers, postOrderAnalysis, emitter)) {
14281453
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
14291454
}
1455+
1456+
if (emitter.emittedAnyDiagnostics()) {
1457+
if (cleanupSILAfterEmittingObjectMoveOnlyDiagnostics(fn))
1458+
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
1459+
}
14301460
}
14311461
};
14321462

lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,13 @@ class DiagnosticEmitter {
4141
/// here.
4242
SmallPtrSet<MarkMustCheckInst *, 4> valuesWithDiagnostics;
4343

44-
// Track any violating uses we have emitted a diagnostic for so we don't emit
45-
// multiple diagnostics for the same use.
44+
/// Track any violating uses we have emitted a diagnostic for so we don't emit
45+
/// multiple diagnostics for the same use.
4646
SmallPtrSet<SILInstruction *, 8> useWithDiagnostic;
4747

48+
/// A count of the total diagnostics emitted so that callers of routines that
49+
/// take a diagnostic emitter can know if the emitter emitted additional
50+
/// diagnosics while running a callee.
4851
unsigned diagnosticCount = 0;
4952

5053
public:

lib/SILOptimizer/Mandatory/MoveOnlyObjectChecker.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,22 @@ bool swift::siloptimizer::cleanupSILAfterEmittingObjectMoveOnlyDiagnostics(
290290
cvi->replaceAllUsesWith(expCopy);
291291
cvi->eraseFromParent();
292292
localChanged = true;
293+
continue;
294+
}
295+
296+
// Also eliminate any mark_must_check on objects, just to be safe. We
297+
// emitted an object level diagnostic and if the user wants to get more
298+
// diagnostics, they should fix these diagnostics and recompile.
299+
if (auto *mmci = dyn_cast<MarkMustCheckInst>(&*ii)) {
300+
++ii;
301+
302+
if (mmci->getType().isAddress())
303+
continue;
304+
305+
mmci->replaceAllUsesWith(mmci->getOperand());
306+
mmci->eraseFromParent();
307+
localChanged = true;
308+
continue;
293309
}
294310

295311
++ii;
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// RUN: %target-sil-opt -move-only-diagnostics-silently-emit-diagnostics -enable-experimental-move-only -enable-experimental-feature MoveOnlyClasses -enable-sil-verify-all -sil-move-only-borrow-to-destructure %s | %FileCheck %s
2+
// RUN: %target-sil-opt -verify -enable-experimental-move-only -enable-experimental-feature MoveOnlyClasses -enable-sil-verify-all -sil-move-only-borrow-to-destructure %s
3+
4+
sil_stage raw
5+
6+
import Builtin
7+
8+
//////////////////
9+
// Declarations //
10+
//////////////////
11+
12+
class Klass {}
13+
@_moveOnly
14+
class MoveOnlyKlass {
15+
var value: Builtin.Int32
16+
}
17+
18+
@_moveOnly
19+
struct KlassPair {
20+
var lhs: Klass
21+
var rhs: MoveOnlyKlass
22+
}
23+
24+
@_moveOnly
25+
struct AggStruct {
26+
var pair: KlassPair
27+
}
28+
29+
@_moveOnly
30+
struct KlassPair2 {
31+
var lhs: MoveOnlyKlass
32+
var rhs: MoveOnlyKlass
33+
}
34+
35+
@_moveOnly
36+
struct AggStruct2 {
37+
var lhs: MoveOnlyKlass
38+
var pair: KlassPair2
39+
var rhs: MoveOnlyKlass
40+
}
41+
42+
@_moveOnly
43+
struct SingleIntContainingStruct {
44+
var value: Builtin.Int32
45+
}
46+
47+
sil @misc_use : $@convention(thin) () -> ()
48+
sil @aggstruct_consume : $@convention(thin) (@owned AggStruct) -> ()
49+
sil @moveonlyklass_consume : $@convention(thin) (@owned MoveOnlyKlass) -> ()
50+
sil @moveonlyklass_use : $@convention(thin) (@guaranteed MoveOnlyKlass) -> ()
51+
sil @klass_use : $@convention(thin) (@guaranteed Klass) -> ()
52+
53+
///////////
54+
// Tests //
55+
///////////
56+
57+
// CHECK-LABEL: sil [ossa] @test_access_single_child_field_consume : $@convention(method) (@guaranteed AggStruct2) -> () {
58+
// CHECK: bb0([[ARG:%.*]] : @guaranteed $AggStruct2):
59+
// CHECK: explicit_copy_value [[ARG]]
60+
// CHECK-NOT: mark_must_check
61+
// CHECK: } // end sil function 'test_access_single_child_field_consume'
62+
sil [ossa] @test_access_single_child_field_consume : $@convention(method) (@guaranteed AggStruct2) -> () {
63+
bb0(%0 : @guaranteed $AggStruct2):
64+
%1 = copy_value %0 : $AggStruct2
65+
%2 = mark_must_check [no_copy] %1 : $AggStruct2 // expected-error {{'self' has a move only field that was consumed before later uses}}
66+
debug_value %2 : $AggStruct2, let, name "self", argno 1, implicit
67+
%4 = begin_borrow %2 : $AggStruct2
68+
%5 = struct_extract %4 : $AggStruct2, #AggStruct2.pair
69+
%6 = struct_extract %5 : $KlassPair2, #KlassPair2.lhs
70+
%7 = copy_value %6 : $MoveOnlyKlass
71+
end_borrow %4 : $AggStruct2
72+
%4a = begin_borrow %2 : $AggStruct2
73+
%5a = struct_extract %4a : $AggStruct2, #AggStruct2.pair
74+
%6a = struct_extract %5a : $KlassPair2, #KlassPair2.lhs
75+
%7a = copy_value %6a : $MoveOnlyKlass
76+
end_borrow %4a : $AggStruct2
77+
destroy_value %2 : $AggStruct2
78+
%9 = function_ref @moveonlyklass_consume : $@convention(thin) (@owned MoveOnlyKlass) -> ()
79+
apply %9(%7) : $@convention(thin) (@owned MoveOnlyKlass) -> () // expected-note {{consuming use}}
80+
apply %9(%7a) : $@convention(thin) (@owned MoveOnlyKlass) -> () // expected-note {{boundary use here}}
81+
%9999 = tuple()
82+
return %9999 : $()
83+
}

test/SILOptimizer/moveonly_objectchecker_diagnostics.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -949,8 +949,8 @@ public func aggStructAccessFieldOwnedArg(_ x2: __owned AggStruct) {
949949
}
950950
}
951951

952-
public func aggStructConsumeField(_ x: AggStruct) { // expected-error {{'x' has guaranteed ownership but was consumed}}
953-
let x2 = x // expected-note {{consuming use}}
952+
public func aggStructConsumeField(_ x: AggStruct) {
953+
let x2 = x
954954
// expected-error @-1 {{'x2' has a move only field that was consumed before later uses}}
955955
classConsume(x2.lhs)
956956
for _ in 0..<1024 {
@@ -996,8 +996,8 @@ public func aggStructAccessGrandFieldOwnedArg(_ x2: __owned AggStruct) {
996996
}
997997
}
998998

999-
public func aggStructConsumeGrandField(_ x: AggStruct) { // expected-error {{'x' has guaranteed ownership but was consumed}}
1000-
let x2 = x // expected-note {{consuming use}}
999+
public func aggStructConsumeGrandField(_ x: AggStruct) {
1000+
let x2 = x
10011001
// expected-error @-1 {{'x2' has a move only field that was consumed before later uses}}
10021002
classConsume(x2.pair.lhs)
10031003
for _ in 0..<1024 {
@@ -1254,8 +1254,8 @@ public func aggGenericStructAccessFieldOwnedArg(_ x2: __owned AggGenericStruct<K
12541254
}
12551255
}
12561256

1257-
public func aggGenericStructConsumeField(_ x: AggGenericStruct<Klass>) { // expected-error {{'x' has guaranteed ownership but was consumed}}
1258-
let x2 = x // expected-note {{consuming use}}
1257+
public func aggGenericStructConsumeField(_ x: AggGenericStruct<Klass>) {
1258+
let x2 = x
12591259
// expected-error @-1 {{'x2' has a move only field that was consumed before later uses}}
12601260
classConsume(x2.lhs)
12611261
for _ in 0..<1024 {
@@ -1301,8 +1301,8 @@ public func aggGenericStructAccessGrandFieldOwnedArg(_ x2: __owned AggGenericStr
13011301
}
13021302
}
13031303

1304-
public func aggGenericStructConsumeGrandField(_ x: AggGenericStruct<Klass>) { // expected-error {{'x' has guaranteed ownership but was consumed}}
1305-
let x2 = x // expected-note {{consuming use}}
1304+
public func aggGenericStructConsumeGrandField(_ x: AggGenericStruct<Klass>) {
1305+
let x2 = x
13061306
// expected-error @-1 {{'x2' has a move only field that was consumed before later uses}}
13071307
classConsume(x2.pair.lhs)
13081308
for _ in 0..<1024 {
@@ -1522,8 +1522,8 @@ public func aggGenericStructAccessFieldOwnedArg<T>(_ x2: __owned AggGenericStruc
15221522
}
15231523
}
15241524

1525-
public func aggGenericStructConsumeField<T>(_ x: AggGenericStruct<T>) { // expected-error {{'x' has guaranteed ownership but was consumed}}
1526-
let x2 = x // expected-note {{consuming use}}
1525+
public func aggGenericStructConsumeField<T>(_ x: AggGenericStruct<T>) {
1526+
let x2 = x
15271527
// expected-error @-1 {{'x2' has a move only field that was consumed before later uses}}
15281528
classConsume(x2.lhs)
15291529
for _ in 0..<1024 {
@@ -1569,8 +1569,8 @@ public func aggGenericStructAccessGrandFieldOwnedArg<T>(_ x2: __owned AggGeneric
15691569
}
15701570
}
15711571

1572-
public func aggGenericStructConsumeGrandField<T>(_ x: AggGenericStruct<T>) { // expected-error {{'x' has guaranteed ownership but was consumed}}
1573-
let x2 = x // expected-note {{consuming use}}
1572+
public func aggGenericStructConsumeGrandField<T>(_ x: AggGenericStruct<T>) {
1573+
let x2 = x
15741574
// expected-error @-1 {{'x2' has a move only field that was consumed before later uses}}
15751575
classConsume(x2.pair.lhs)
15761576
for _ in 0..<1024 {

0 commit comments

Comments
 (0)