Skip to content

Commit d825f2c

Browse files
committed
[move-function] Implement Closure Argument Dataflow.
This dataflow is used to determine if a closure/defer use of a value as an inout_aliasable value is consume inclusive-or liveness requiring use in a caller. This information is used to determine if a closure invocation or defer at the source level acts as a use after move in the source level caller of a moved var or if the moved var is reinited in the function. This is done by: 1. Classifying all uses of the inout_aliasable argument as either init, consume, use. 2. We then perform two separate dataflows that summarize the effect of the closure upon the specific address argument: a. First for each individual use, we walk upwards along predecessors to the beginning of the function stopping at consumes, inits, and other uses. If our dataflow eventually reaches the entrance block, then we know that we have a use that should be an error. If all uses are blocked by a consume, init, or other use then we know that if our operand was moved in a caller, the address is not used after the move at least in this callee. b. With that in hand, we then perform a similar dataflow for each individual consume operation except that we only stop at init blocks and other consume blocks. This leaves us with a set of consuming operations of the address that can be considered to be consume up out of the callee. We then make sure that these consumes form a post dominating set of upon the address or bail. If we bail, we do not handle the move in the caller causing us to emit an unhandled move error later after all diagnostics have run. After performing both of these dataflows, our caller function can use our outputs to emit an error if we had a use and a move is live in the caller or create a new callee where our address argument is converted from an inout_aliasable parameter to an out parameter.
1 parent fe6e3f9 commit d825f2c

File tree

1 file changed

+330
-12
lines changed

1 file changed

+330
-12
lines changed

lib/SILOptimizer/Mandatory/MoveKillsCopyableAddressesChecker.cpp

Lines changed: 330 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,34 @@ static SourceLoc getSourceLocFromValue(SILValue value) {
188188
llvm_unreachable("Do not know how to get source loc for value?!");
189189
}
190190

191+
//===----------------------------------------------------------------------===//
192+
// Forward Declarations
193+
//===----------------------------------------------------------------------===//
194+
195+
namespace {
196+
197+
enum class DownwardScanResult {
198+
Invalid,
199+
Destroy,
200+
Reinit,
201+
// NOTE: We use UseForDiagnostic both for defer uses and normal uses.
202+
UseForDiagnostic,
203+
MoveOut,
204+
ClosureConsume,
205+
ClosureUse,
206+
};
207+
208+
struct ClosureOperandState {
209+
DownwardScanResult result = DownwardScanResult::Invalid;
210+
TinyPtrVector<SILInstruction *> pairedConsumingInsts;
211+
TinyPtrVector<SILInstruction *> pairedUseInsts;
212+
bool isUpwardsUse = false;
213+
bool isUpwardsConsume = false;
214+
bool isUpwardsInit = false;
215+
};
216+
217+
} // namespace
218+
191219
//===----------------------------------------------------------------------===//
192220
// Use Gathering
193221
//===----------------------------------------------------------------------===//
@@ -233,6 +261,8 @@ struct UseState {
233261
reinits.clear();
234262
reinitToIndexMap.clear();
235263
}
264+
265+
SILFunction *getFunction() const { return address->getFunction(); }
236266
};
237267

238268
/// Visit all of the uses of a lexical lifetime, initializing useState as we go.
@@ -352,18 +382,6 @@ bool GatherLexicalLifetimeUseVisitor::visitUse(Operand *op,
352382
// Dataflow
353383
//===----------------------------------------------------------------------===//
354384

355-
namespace {
356-
357-
enum class DownwardScanResult {
358-
Invalid,
359-
Destroy,
360-
Reinit,
361-
UseForDiagnostic,
362-
MoveOut
363-
};
364-
365-
}
366-
367385
/// Returns true if we are move out, false otherwise. If we find an interesting
368386
/// inst, we return it in foundInst. If no inst is returned, one must continue.
369387
static DownwardScanResult
@@ -484,6 +502,303 @@ static bool upwardScanForInit(SILInstruction *inst, UseState &useState) {
484502
return true;
485503
}
486504

505+
//===----------------------------------------------------------------------===//
506+
// Closure Argument Global Dataflow
507+
//===----------------------------------------------------------------------===//
508+
509+
namespace {
510+
511+
/// A utility class that analyzes a closure that captures a moved value. It is
512+
/// used to perform move checking within the closure as well as to determine a
513+
/// set of reinit/destroys that we will need to convert to init and or eliminate
514+
/// while cloning the closure.
515+
///
516+
/// NOTE: We do not need to consider if the closure reinitializes the memory
517+
/// since there must be some sort of use for the closure to even reference it
518+
/// and the compiler emits assigns when it reinitializes vars this early in the
519+
/// pipeline.
520+
struct ConsumingClosureArgDataflowState {
521+
SmallVector<SILInstruction *, 32> livenessWorklist;
522+
SmallVector<SILInstruction *, 32> consumingWorklist;
523+
SmallBlotSetVector<SILInstruction *, 8> postDominatingConsumingUsers;
524+
PrunedLiveness livenessForConsumes;
525+
UseState &useState;
526+
527+
public:
528+
ConsumingClosureArgDataflowState(UseState &useState) : useState(useState) {}
529+
530+
bool isPostDominatingConsumingUser(SILInstruction *inst) const {
531+
return postDominatingConsumingUsers.count(inst);
532+
}
533+
534+
bool process(SILArgument *arg, ClosureOperandState &state);
535+
536+
void clear() {
537+
postDominatingConsumingUsers.clear();
538+
livenessForConsumes.clear();
539+
}
540+
541+
private:
542+
/// Perform our liveness dataflow. Returns true if we found any liveness uses
543+
/// at all. These we will need to error upon.
544+
bool performLivenessDataflow(const BasicBlockSet &initBlocks,
545+
const BasicBlockSet &livenessBlocks,
546+
const BasicBlockSet &consumingBlocks);
547+
548+
/// Perform our consuming dataflow. Returns true if we found an earliest set
549+
/// of consuming uses that we can handle that post-dominate the argument.
550+
/// Returns false otherwise.
551+
bool performConsumingDataflow(const BasicBlockSet &initBlocks,
552+
const BasicBlockSet &consumingBlocks);
553+
554+
void classifyUses(BasicBlockSet &initBlocks, BasicBlockSet &livenessBlocks,
555+
BasicBlockSet &consumingBlocks);
556+
557+
bool handleSingleBlockCase(SILArgument *address, ClosureOperandState &state);
558+
};
559+
560+
} // namespace
561+
562+
bool ConsumingClosureArgDataflowState::handleSingleBlockCase(
563+
SILArgument *address, ClosureOperandState &state) {
564+
// Walk the instructions from the beginning of the block to the end.
565+
for (auto &inst : *address->getParent()) {
566+
assert(!useState.inits.count(&inst) &&
567+
"Shouldn't see an init before a destroy or reinit");
568+
569+
// If we see a destroy, then we know we are upwards consume... stash it so
570+
// that we can destroy it
571+
if (auto *dvi = dyn_cast<DestroyValueInst>(&inst)) {
572+
if (useState.destroyToIndexMap.count(dvi)) {
573+
state.pairedConsumingInsts.push_back(dvi);
574+
state.isUpwardsConsume = true;
575+
state.result = DownwardScanResult::ClosureConsume;
576+
return true;
577+
}
578+
}
579+
580+
// Same for reinits.
581+
if (useState.reinits.count(&inst)) {
582+
state.pairedConsumingInsts.push_back(&inst);
583+
state.isUpwardsConsume = true;
584+
state.result = DownwardScanResult::ClosureConsume;
585+
return true;
586+
}
587+
588+
// Finally, if we have a liveness use, report it for a diagnostic.
589+
if (useState.livenessUses.count(&inst)) {
590+
state.pairedUseInsts.push_back(&inst);
591+
state.isUpwardsUse = true;
592+
state.result = DownwardScanResult::ClosureUse;
593+
return true;
594+
}
595+
}
596+
597+
return false;
598+
}
599+
600+
bool ConsumingClosureArgDataflowState::performLivenessDataflow(
601+
const BasicBlockSet &initBlocks, const BasicBlockSet &livenessBlocks,
602+
const BasicBlockSet &consumingBlocks) {
603+
bool foundSingleLivenessUse = false;
604+
auto *fn = useState.getFunction();
605+
auto *frontBlock = &*fn->begin();
606+
BasicBlockWorklist worklist(fn);
607+
for (unsigned i : indices(livenessWorklist)) {
608+
auto *&user = livenessWorklist[i];
609+
610+
if (frontBlock == user->getParent())
611+
continue;
612+
613+
bool success = false;
614+
for (auto *predBlock : user->getParent()->getPredecessorBlocks()) {
615+
worklist.pushIfNotVisited(predBlock);
616+
}
617+
while (auto *next = worklist.pop()) {
618+
if (livenessBlocks.contains(next) || initBlocks.contains(next) ||
619+
consumingBlocks.contains(next)) {
620+
continue;
621+
}
622+
623+
if (frontBlock == next) {
624+
success = true;
625+
foundSingleLivenessUse = true;
626+
break;
627+
}
628+
629+
for (auto *predBlock : next->getPredecessorBlocks()) {
630+
worklist.pushIfNotVisited(predBlock);
631+
}
632+
}
633+
if (!success) {
634+
user = nullptr;
635+
}
636+
}
637+
return foundSingleLivenessUse;
638+
}
639+
640+
bool ConsumingClosureArgDataflowState::performConsumingDataflow(
641+
const BasicBlockSet &initBlocks, const BasicBlockSet &consumingBlocks) {
642+
auto *fn = useState.getFunction();
643+
auto *frontBlock = &*fn->begin();
644+
645+
bool foundSingleConsumingUse = false;
646+
BasicBlockWorklist worklist(fn);
647+
for (unsigned i : indices(consumingWorklist)) {
648+
auto *&user = consumingWorklist[i];
649+
650+
if (frontBlock == user->getParent())
651+
continue;
652+
653+
bool success = false;
654+
for (auto *predBlock : user->getParent()->getPredecessorBlocks()) {
655+
worklist.pushIfNotVisited(predBlock);
656+
}
657+
while (auto *next = worklist.pop()) {
658+
if (initBlocks.contains(next) || consumingBlocks.contains(next)) {
659+
continue;
660+
}
661+
662+
if (frontBlock == next) {
663+
success = true;
664+
foundSingleConsumingUse = true;
665+
break;
666+
}
667+
668+
for (auto *predBlock : next->getPredecessorBlocks()) {
669+
worklist.pushIfNotVisited(predBlock);
670+
}
671+
}
672+
if (!success) {
673+
user = nullptr;
674+
}
675+
}
676+
return foundSingleConsumingUse;
677+
}
678+
679+
void ConsumingClosureArgDataflowState::classifyUses(
680+
BasicBlockSet &initBlocks, BasicBlockSet &livenessBlocks,
681+
BasicBlockSet &consumingBlocks) {
682+
683+
for (auto *user : useState.inits) {
684+
if (upwardScanForInit(user, useState)) {
685+
initBlocks.insert(user->getParent());
686+
}
687+
}
688+
689+
for (auto *user : useState.livenessUses) {
690+
if (upwardScanForUseOut(user, useState)) {
691+
livenessBlocks.insert(user->getParent());
692+
livenessWorklist.push_back(user);
693+
}
694+
}
695+
696+
for (auto destroyOpt : useState.destroys) {
697+
assert(destroyOpt);
698+
699+
auto *destroy = *destroyOpt;
700+
701+
auto iter = useState.destroyToIndexMap.find(destroy);
702+
assert(iter != useState.destroyToIndexMap.end());
703+
704+
if (upwardScanForDestroys(destroy, useState)) {
705+
LLVM_DEBUG(llvm::dbgs() << " Found destroy block at: " << *destroy);
706+
consumingBlocks.insert(destroy->getParent());
707+
consumingWorklist.push_back(destroy);
708+
}
709+
}
710+
711+
for (auto reinitOpt : useState.reinits) {
712+
assert(reinitOpt);
713+
714+
auto *reinit = *reinitOpt;
715+
auto iter = useState.reinitToIndexMap.find(reinit);
716+
assert(iter != useState.reinitToIndexMap.end());
717+
718+
if (upwardScanForDestroys(reinit, useState)) {
719+
LLVM_DEBUG(llvm::dbgs() << " Found reinit block at: " << *reinit);
720+
consumingBlocks.insert(reinit->getParent());
721+
consumingWorklist.push_back(reinit);
722+
}
723+
}
724+
}
725+
726+
bool ConsumingClosureArgDataflowState::process(SILArgument *address,
727+
ClosureOperandState &state) {
728+
clear();
729+
730+
SILFunction *fn = address->getFunction();
731+
assert(fn);
732+
733+
// First see if our function only has a single block. In such a case,
734+
// summarize using the single processing routine.
735+
if (address->getParent()->getTerminator()->isFunctionExiting())
736+
return handleSingleBlockCase(address, state);
737+
738+
// At this point, we begin by classifying the uses of our address into init
739+
// blocks, liveness blocks, consuming blocks. We also seed the worklist for
740+
// our two dataflows.
741+
BasicBlockSet initBlocks(fn);
742+
BasicBlockSet livenessBlocks(fn);
743+
BasicBlockSet consumingBlocks(fn);
744+
classifyUses(initBlocks, livenessBlocks, consumingBlocks);
745+
746+
// Liveness Dataflow:
747+
//
748+
// The way that we do this is that for each such instruction:
749+
//
750+
// 1. If the instruction is in the entrance block, then it is our only answer.
751+
//
752+
// 2. If the user is not in the entrance block, visit recursively its
753+
// predecessor blocks until one either hits the entrance block (in which
754+
// case this is the result) /or/ one hits a block in one of our basic block
755+
// sets which means there is an earlier use. Consuming blocks only stop for
756+
// consuming blocks and init blocks. Liveness blocks stop for all other
757+
// blocks.
758+
//
759+
// The result is what remains in our set. Thus we start by processing
760+
// liveness.
761+
if (performLivenessDataflow(initBlocks, livenessBlocks, consumingBlocks)) {
762+
for (unsigned i : indices(livenessWorklist)) {
763+
if (auto *ptr = livenessWorklist[i]) {
764+
state.pairedUseInsts.push_back(ptr);
765+
}
766+
}
767+
state.isUpwardsUse = true;
768+
state.result = DownwardScanResult::ClosureUse;
769+
return true;
770+
}
771+
772+
// Then perform the consuming use dataflow. In this case, we think we may have
773+
// found a set of post-dominating consuming uses for our inout_aliasable
774+
// parameter. We are going to change it to be an out parameter and eliminate
775+
// these when we clone the closure.
776+
if (performConsumingDataflow(initBlocks, consumingBlocks)) {
777+
SWIFT_DEFER { livenessForConsumes.clear(); };
778+
auto *frontBlock = &*fn->begin();
779+
livenessForConsumes.initializeDefBlock(frontBlock);
780+
781+
for (unsigned i : indices(livenessWorklist)) {
782+
if (auto *ptr = livenessWorklist[i]) {
783+
state.pairedConsumingInsts.push_back(ptr);
784+
livenessForConsumes.updateForUse(ptr, true /*is lifetime ending*/);
785+
}
786+
}
787+
788+
// If our consumes do not have a linear lifetime, bail. We will error on the
789+
// move being unknown.
790+
for (auto *ptr : state.pairedConsumingInsts) {
791+
if (livenessForConsumes.isWithinBoundary(ptr))
792+
return false;
793+
postDominatingConsumingUsers.insert(ptr);
794+
}
795+
state.isUpwardsConsume = true;
796+
state.result = DownwardScanResult::ClosureConsume;
797+
return true;
798+
}
799+
800+
return true;
801+
}
487802
//===----------------------------------------------------------------------===//
488803
// Global Dataflow
489804
//===----------------------------------------------------------------------===//
@@ -815,6 +1130,9 @@ static bool performSingleBasicBlockAnalysis(DataflowState &dataflowState,
8151130
auto &useState = dataflowState.useState;
8161131
SILInstruction *interestingUser = nullptr;
8171132
switch (downwardScanForMoveOut(mvi, useState, &interestingUser)) {
1133+
case DownwardScanResult::ClosureConsume:
1134+
case DownwardScanResult::ClosureUse:
1135+
llvm_unreachable("unhandled");
8181136
case DownwardScanResult::Invalid:
8191137
llvm_unreachable("invalid");
8201138
case DownwardScanResult::Destroy: {

0 commit comments

Comments
 (0)