[move-only] Refactor CanonicalizeOSSALifetime::canonicalizeValueLifetime into an API that computes liveness and a second API that rewrites copies/destroys and fix up MoveOnly checkers to use it.

For those who are unaware, CanonicalizeOSSALifetime::canonicalizeValueLifetime()
is really a high level driver routine for the functionality of
CanonicalizeOSSALifetime that computes liveness and then rewrites copies using
boundary information. This change introduces splits the implementation of
canonicalizeValueLifetime into two parts: a first part called computeLiveness
and a second part called rewriteLifetimes. Internally canonicalizeValueLifetime
still just calls these two methods.

The reason why I am doing this is that it lets the move only object checker use
the raw liveness information computed before the rewriting mucks with the
analysis information. This information is used by the checker to compute the raw
liveness boundary of a value and use that information to determine the list of
consuming uses not on the boundary, consuming uses on the boundary, and
non-consuming uses on the boundary. This is then used by later parts of the
checker to emit our errors.

Some additional benefits of doing this are:

1. I was able to eliminate callbacks in the rewriting stage of
CanonicalOSSALifetimes which previously gave the checker this information.

2. Previously the move checker did not have access to the non-consuming boundary
uses causing us to always fail appropriately, but sadly not emit a note showing
the non-consuming use. I am going to wire this up in a subsequent commit.

The other change to the implementation of the move checker that this caused is
that I needed to add an extra diagnostic check for instructions that consume the
value twice or consume the value and use the value. The reason why this must be
done is that liveness does not distinguish in between different operands on the
same instruction meaning such an error would be lost.
This commit is contained in:
Michael Gottesman
2023-02-01 16:20:28 -08:00
parent c6942eed7d
commit 20479c96fb
11 changed files with 403 additions and 106 deletions

View File

@@ -153,32 +153,30 @@ void DiagnosticEmitter::emitObjectDiagnosticsForFoundUses(
bool ignorePartialApplyUses) const {
auto &astContext = fn->getASTContext();
for (auto *consumingUse : getCanonicalizer().consumingUsesNeedingCopy) {
for (auto *consumingUser : getCanonicalizer().consumingUsesNeedingCopy) {
// See if the consuming use is an owned moveonly_to_copyable whose only
// user is a return. In that case, use the return loc instead. We do this
// b/c it is illegal to put a return value location on a non-return value
// instruction... so we have to hack around this slightly.
auto *user = consumingUse->getUser();
auto loc = user->getLoc();
if (auto *mtc = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(user)) {
auto loc = consumingUser->getLoc();
if (auto *mtc =
dyn_cast<MoveOnlyWrapperToCopyableValueInst>(consumingUser)) {
if (auto *ri = mtc->getSingleUserOfType<ReturnInst>()) {
loc = ri->getLoc();
}
}
if (ignorePartialApplyUses &&
isa<PartialApplyInst>(consumingUse->getUser()))
if (ignorePartialApplyUses && isa<PartialApplyInst>(consumingUser))
continue;
diagnose(astContext, loc.getSourceLoc(),
diag::sil_moveonlychecker_consuming_use_here);
}
for (auto *consumingUse : getCanonicalizer().finalConsumingUses) {
for (auto *user : getCanonicalizer().consumingBoundaryUsers) {
// See if the consuming use is an owned moveonly_to_copyable whose only
// user is a return. In that case, use the return loc instead. We do this
// b/c it is illegal to put a return value location on a non-return value
// instruction... so we have to hack around this slightly.
auto *user = consumingUse->getUser();
auto loc = user->getLoc();
if (auto *mtc = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(user)) {
if (auto *ri = mtc->getSingleUserOfType<ReturnInst>()) {
@@ -186,8 +184,7 @@ void DiagnosticEmitter::emitObjectDiagnosticsForFoundUses(
}
}
if (ignorePartialApplyUses &&
isa<PartialApplyInst>(consumingUse->getUser()))
if (ignorePartialApplyUses && isa<PartialApplyInst>(user))
continue;
diagnose(astContext, loc.getSourceLoc(),
@@ -198,12 +195,11 @@ void DiagnosticEmitter::emitObjectDiagnosticsForFoundUses(
void DiagnosticEmitter::emitObjectDiagnosticsForPartialApplyUses() const {
auto &astContext = fn->getASTContext();
for (auto *consumingUse : getCanonicalizer().consumingUsesNeedingCopy) {
for (auto *user : getCanonicalizer().consumingUsesNeedingCopy) {
// See if the consuming use is an owned moveonly_to_copyable whose only
// user is a return. In that case, use the return loc instead. We do this
// b/c it is illegal to put a return value location on a non-return value
// instruction... so we have to hack around this slightly.
auto *user = consumingUse->getUser();
auto loc = user->getLoc();
if (auto *mtc = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(user)) {
if (auto *ri = mtc->getSingleUserOfType<ReturnInst>()) {
@@ -211,18 +207,17 @@ void DiagnosticEmitter::emitObjectDiagnosticsForPartialApplyUses() const {
}
}
if (!isa<PartialApplyInst>(consumingUse->getUser()))
if (!isa<PartialApplyInst>(user))
continue;
diagnose(astContext, loc.getSourceLoc(),
diag::sil_moveonlychecker_consuming_closure_use_here);
}
for (auto *consumingUse : getCanonicalizer().finalConsumingUses) {
for (auto *user : getCanonicalizer().consumingBoundaryUsers) {
// See if the consuming use is an owned moveonly_to_copyable whose only
// user is a return. In that case, use the return loc instead. We do this
// b/c it is illegal to put a return value location on a non-return value
// instruction... so we have to hack around this slightly.
auto *user = consumingUse->getUser();
auto loc = user->getLoc();
if (auto *mtc = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(user)) {
if (auto *ri = mtc->getSingleUserOfType<ReturnInst>()) {
@@ -230,7 +225,7 @@ void DiagnosticEmitter::emitObjectDiagnosticsForPartialApplyUses() const {
}
}
if (!isa<PartialApplyInst>(consumingUse->getUser()))
if (!isa<PartialApplyInst>(user))
continue;
diagnose(astContext, loc.getSourceLoc(),
@@ -454,14 +449,13 @@ void DiagnosticEmitter::emitObjectDestructureNeededWithinBorrowBoundary(
registerDiagnosticEmitted(markedValue);
}
void DiagnosticEmitter::emitObjectConsumesDestructuredValueTwice(
void DiagnosticEmitter::emitObjectInstConsumesValueTwice(
MarkMustCheckInst *markedValue, Operand *firstUse, Operand *secondUse) {
assert(firstUse->getUser() == secondUse->getUser());
assert(firstUse->isConsuming());
assert(secondUse->isConsuming());
LLVM_DEBUG(
llvm::dbgs() << "Emitting object consumes destructure twice error!\n");
LLVM_DEBUG(llvm::dbgs() << "Emitting object consumes value twice error!\n");
LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue);
LLVM_DEBUG(llvm::dbgs() << " User: " << *firstUse->getUser());
LLVM_DEBUG(llvm::dbgs() << " First Conflicting Operand: "
@@ -480,15 +474,14 @@ void DiagnosticEmitter::emitObjectConsumesDestructuredValueTwice(
registerDiagnosticEmitted(markedValue);
}
void DiagnosticEmitter::emitObjectConsumesAndUsesDestructuredValue(
void DiagnosticEmitter::emitObjectInstConsumesAndUsesValue(
MarkMustCheckInst *markedValue, Operand *consumingUse,
Operand *nonConsumingUse) {
assert(consumingUse->getUser() == nonConsumingUse->getUser());
assert(consumingUse->isConsuming());
assert(!nonConsumingUse->isConsuming());
LLVM_DEBUG(
llvm::dbgs() << "Emitting object consumes destructure twice error!\n");
LLVM_DEBUG(llvm::dbgs() << "Emitting object consumeed and used error!\n");
LLVM_DEBUG(llvm::dbgs() << " Mark: " << *markedValue);
LLVM_DEBUG(llvm::dbgs() << " User: " << *consumingUse->getUser());
LLVM_DEBUG(llvm::dbgs() << " Consuming Operand: "