Skip to content

Commit 4aa4ac3

Browse files
committed
[moveOnly] Add checker that validates that if a let/param copyable value has _move applied to it, the let doesn't have any later uses.
This is just an initial prototype for people to play with. It is as always behind the -enable-experimental-move-only flag. NOTE: In this PR I implemented this only for 'local let' like things (local lets/params). I did not implement in this PR support for local var and haven't done anything with class ivars or globals. rdar://83957028
1 parent 99d87c3 commit 4aa4ac3

File tree

8 files changed

+748
-6
lines changed

8 files changed

+748
-6
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,5 +733,15 @@ ERROR(sil_moveonlychecker_value_consumed_more_than_once, none,
733733
NOTE(sil_moveonlychecker_consuming_use_here, none,
734734
"consuming use", ())
735735

736+
// move kills copyable values checker diagnostics
737+
ERROR(sil_movekillscopyablevalue_value_consumed_more_than_once, none,
738+
"'%0' used after being moved", (StringRef))
739+
NOTE(sil_movekillscopyablevalue_move_here, none,
740+
"move here", ())
741+
NOTE(sil_movekillscopyablevalue_use_here, none,
742+
"use here", ())
743+
NOTE(sil_movekillscopyablevalue_value_consumed_in_loop, none,
744+
"cyclic move here. move will occur multiple times in the loop", ())
745+
736746
#define UNDEFINE_DIAGNOSTIC_MACROS
737747
#include "DefineDiagnosticMacros.h"

include/swift/SIL/PrunedLiveness.h

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
#define SWIFT_SILOPTIMIZER_UTILS_PRUNEDLIVENESS_H
9292

9393
#include "swift/SIL/SILBasicBlock.h"
94+
#include "llvm/ADT/MapVector.h"
9495

9596
namespace swift {
9697

@@ -221,11 +222,19 @@ class PrunedLiveness {
221222
// they may be the last use in the block.
222223
//
223224
// Non-lifetime-ending within a LiveOut block are uninteresting.
224-
llvm::SmallDenseMap<SILInstruction *, bool, 8> users;
225+
llvm::SmallMapVector<SILInstruction *, bool, 8> users;
226+
227+
/// A side array that stores any non lifetime ending uses we find in live out
228+
/// blocks. This is used to enable our callers to emit errors on non-lifetime
229+
/// ending uses that extend liveness into a loop body.
230+
SmallSetVector<SILInstruction *, 8> *nonLifetimeEndingUsesInLiveOut;
225231

226232
public:
227-
PrunedLiveness(SmallVectorImpl<SILBasicBlock *> *discoveredBlocks = nullptr)
228-
: liveBlocks(discoveredBlocks) {}
233+
PrunedLiveness(SmallVectorImpl<SILBasicBlock *> *discoveredBlocks = nullptr,
234+
SmallSetVector<SILInstruction *, 8>
235+
*nonLifetimeEndingUsesInLiveOut = nullptr)
236+
: liveBlocks(discoveredBlocks),
237+
nonLifetimeEndingUsesInLiveOut(nonLifetimeEndingUsesInLiveOut) {}
229238

230239
bool empty() const {
231240
assert(!liveBlocks.empty() || users.empty());
@@ -235,6 +244,8 @@ class PrunedLiveness {
235244
void clear() {
236245
liveBlocks.clear();
237246
users.clear();
247+
if (nonLifetimeEndingUsesInLiveOut)
248+
nonLifetimeEndingUsesInLiveOut->clear();
238249
}
239250

240251
unsigned numLiveBlocks() const { return liveBlocks.numLiveBlocks(); }
@@ -245,6 +256,47 @@ class PrunedLiveness {
245256
return liveBlocks.getDiscoveredBlocks();
246257
}
247258

259+
using NonLifetimeEndingUsesInLiveOutRange =
260+
iterator_range<SILInstruction *const *>;
261+
262+
NonLifetimeEndingUsesInLiveOutRange
263+
getNonLifetimeEndingUsesInLiveOut() const {
264+
assert(nonLifetimeEndingUsesInLiveOut &&
265+
"Called without passing in nonLifetimeEndingUsesInLiveOut to "
266+
"constructor?!");
267+
return llvm::make_range(nonLifetimeEndingUsesInLiveOut->begin(),
268+
nonLifetimeEndingUsesInLiveOut->end());
269+
}
270+
271+
using NonLifetimeEndingUsesInLiveOutBlocksRange =
272+
TransformRange<NonLifetimeEndingUsesInLiveOutRange,
273+
function_ref<SILBasicBlock *(const SILInstruction *&)>>;
274+
NonLifetimeEndingUsesInLiveOutBlocksRange
275+
getNonLifetimeEndingUsesInLiveOutBlocks() const {
276+
function_ref<SILBasicBlock *(const SILInstruction *&)> op;
277+
op = [](const SILInstruction *&ptr) -> SILBasicBlock * {
278+
return ptr->getParent();
279+
};
280+
return NonLifetimeEndingUsesInLiveOutBlocksRange(
281+
getNonLifetimeEndingUsesInLiveOut(), op);
282+
}
283+
284+
using UserRange = iterator_range<const std::pair<SILInstruction *, bool> *>;
285+
UserRange getAllUsers() const {
286+
return llvm::make_range(users.begin(), users.end());
287+
}
288+
289+
using UserBlockRange = TransformRange<
290+
UserRange,
291+
function_ref<SILBasicBlock *(const std::pair<SILInstruction *, bool> &)>>;
292+
UserBlockRange getAllUserBlocks() const {
293+
function_ref<SILBasicBlock *(const std::pair<SILInstruction *, bool> &)> op;
294+
op = [](const std::pair<SILInstruction *, bool> &pair) -> SILBasicBlock * {
295+
return pair.first->getParent();
296+
};
297+
return UserBlockRange(getAllUsers(), op);
298+
}
299+
248300
void initializeDefBlock(SILBasicBlock *defBB) {
249301
liveBlocks.initializeDefBlock(defBB);
250302
}

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,9 @@ PASS(AssemblyVisionRemarkGenerator, "assembly-vision-remark-generator",
422422
"Emit assembly vision remarks that provide source level guidance of where runtime calls ended up")
423423
PASS(MoveOnlyChecker, "sil-move-only-checker",
424424
"Pass that enforces move only invariants on raw SIL")
425+
PASS(MoveKillsCopyableValuesChecker, "sil-move-kills-copyable-values-checker",
426+
"Pass that checks that any copyable (non-move only) value that is passed "
427+
"to _move do not have any uses later than the _move")
425428
PASS(PruneVTables, "prune-vtables",
426429
"Mark class methods that do not require vtable dispatch")
427430
PASS_RANGE(AllPasses, AADumper, PruneVTables)

lib/SIL/Utils/PrunedLiveness.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ void PrunedLiveness::updateForUse(SILInstruction *user, bool lifetimeEnding) {
8080
// Record all uses of blocks on the liveness boundary. For blocks marked
8181
// LiveWithin, the boundary is considered to be the last use in the block.
8282
if (!lifetimeEnding && useBlockLive == PrunedLiveBlocks::LiveOut) {
83+
if (nonLifetimeEndingUsesInLiveOut)
84+
nonLifetimeEndingUsesInLiveOut->insert(user);
8385
return;
8486
}
8587
// Note that a user may use the current value from multiple operands. If any
@@ -93,7 +95,7 @@ void PrunedLiveness::updateForUse(SILInstruction *user, bool lifetimeEnding) {
9395
//
9496
// This call is not considered the end of %val's lifetime. The @owned
9597
// argument must be copied.
96-
auto iterAndSuccess = users.try_emplace(user, lifetimeEnding);
98+
auto iterAndSuccess = users.insert({user, lifetimeEnding});
9799
if (!iterAndSuccess.second)
98100
iterAndSuccess.first->second &= lifetimeEnding;
99101
}

lib/SILOptimizer/Mandatory/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ target_sources(swiftSILOptimizer PRIVATE
1818
LowerHopToActor.cpp
1919
MandatoryInlining.cpp
2020
MoveOnlyChecker.cpp
21+
MoveKillsCopyableValuesChecker.cpp
2122
NestedSemanticFunctionCheck.cpp
2223
OptimizeHopToExecutor.cpp
2324
PerformanceDiagnostics.cpp

0 commit comments

Comments
 (0)