Skip to content

[SIL Optimization] Factor out a utility function: isConsumingUse from CopyPropagation.cpp into InstOptUtils.cpp. #27794

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions include/swift/SILOptimizer/Utils/InstOptUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,16 @@ struct LLVM_LIBRARY_VISIBILITY FindLocalApplySitesResult {
Optional<FindLocalApplySitesResult>
findLocalApplySites(FunctionRefBaseInst *fri);

/// Return true iff the given operand, that is a non-trivial and non-address
/// value, is consumed by its user. That means, in ownership SIL this use must
/// consume the value, and in non-ownership SIL, this operand must be released
/// by this use. The given operand is required to be non-trivial and
/// non-address.
///
/// TODO: Review the semantics of operations that extend the lifetime *without*
/// propagating the value. Ideally, that never happens without borrowing first.
bool isConsumingUse(Operand *use);

} // end namespace swift

#endif
142 changes: 3 additions & 139 deletions lib/SILOptimizer/Transforms/CopyPropagation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -198,142 +198,6 @@ static bool isUnknownUse(Operand *use) {
}
}

/// Return true if the given owned operand is consumed by the given call.
static bool isAppliedArgConsumed(ApplySite apply, Operand *oper) {
ParameterConvention paramConv;
if (oper->get() == apply.getCallee()) {
assert(oper->getOperandNumber() == 0
&& "function can't be passed to itself");
paramConv = apply.getSubstCalleeType()->getCalleeConvention();
} else {
unsigned argIndex = apply.getCalleeArgIndex(*oper);
paramConv = apply.getSubstCalleeConv()
.getParamInfoForSILArg(argIndex)
.getConvention();
}
return isConsumedParameter(paramConv);
}

/// Return true if the given builtin consumes its operand.
static bool isBuiltinArgConsumed(BuiltinInst *BI) {
const BuiltinInfo &Builtin = BI->getBuiltinInfo();
switch (Builtin.ID) {
default:
llvm_unreachable("Unexpected Builtin with owned value operand.");
// Extend lifetime without consuming.
case BuiltinValueKind::ErrorInMain:
case BuiltinValueKind::UnexpectedError:
case BuiltinValueKind::WillThrow:
return false;
// UnsafeGuaranteed moves the value, which will later be destroyed.
case BuiltinValueKind::UnsafeGuaranteed:
return true;
}
}

/// Return true if the given operand is consumed by its user.
///
/// TODO: Review the semantics of operations that extend the lifetime *without*
/// propagating the value. Ideally, that never happens without borrowing first.
static bool isConsuming(Operand *use) {
auto *user = use->getUser();
if (isa<ApplySite>(user))
return isAppliedArgConsumed(ApplySite(user), use);

if (auto *BI = dyn_cast<BuiltinInst>(user))
return isBuiltinArgConsumed(BI);

switch (user->getKind()) {
default:
llvm::dbgs() << *user;
llvm_unreachable("Unexpected use of a loadable owned value.");

// Consume the value.
case SILInstructionKind::AutoreleaseValueInst:
case SILInstructionKind::DeallocBoxInst:
case SILInstructionKind::DeallocExistentialBoxInst:
case SILInstructionKind::DeallocRefInst:
case SILInstructionKind::DeinitExistentialValueInst:
case SILInstructionKind::DestroyValueInst:
case SILInstructionKind::EndLifetimeInst:
case SILInstructionKind::InitExistentialRefInst:
case SILInstructionKind::InitExistentialValueInst:
case SILInstructionKind::KeyPathInst:
case SILInstructionKind::ReleaseValueInst:
case SILInstructionKind::ReleaseValueAddrInst:
case SILInstructionKind::StoreInst:
case SILInstructionKind::StrongReleaseInst:
case SILInstructionKind::UnownedReleaseInst:
case SILInstructionKind::UnconditionalCheckedCastValueInst:
return true;

// Terminators must consume their owned values.
case SILInstructionKind::BranchInst:
case SILInstructionKind::CheckedCastBranchInst:
case SILInstructionKind::CheckedCastValueBranchInst:
case SILInstructionKind::CondBranchInst:
case SILInstructionKind::ReturnInst:
case SILInstructionKind::ThrowInst:
return true;

case SILInstructionKind::DeallocPartialRefInst:
return cast<DeallocPartialRefInst>(user)->getInstance() == use->get();

// Move the value.
case SILInstructionKind::TupleInst:
case SILInstructionKind::StructInst:
case SILInstructionKind::ObjectInst:
case SILInstructionKind::EnumInst:
case SILInstructionKind::OpenExistentialRefInst:
case SILInstructionKind::UpcastInst:
case SILInstructionKind::UncheckedRefCastInst:
case SILInstructionKind::ConvertFunctionInst:
case SILInstructionKind::RefToBridgeObjectInst:
case SILInstructionKind::BridgeObjectToRefInst:
case SILInstructionKind::UnconditionalCheckedCastInst:
case SILInstructionKind::MarkUninitializedInst:
case SILInstructionKind::UncheckedEnumDataInst:
case SILInstructionKind::DestructureStructInst:
case SILInstructionKind::DestructureTupleInst:
return true;

// BeginBorrow should already be skipped.
// EndBorrow extends the lifetime like a normal use.
case SILInstructionKind::EndBorrowInst:
return false;

// Extend the lifetime without borrowing, propagating, or destroying it.
case SILInstructionKind::BridgeObjectToWordInst:
case SILInstructionKind::ClassMethodInst:
case SILInstructionKind::CopyBlockInst:
case SILInstructionKind::CopyValueInst:
case SILInstructionKind::DebugValueInst:
case SILInstructionKind::ExistentialMetatypeInst:
case SILInstructionKind::FixLifetimeInst:
case SILInstructionKind::SelectEnumInst:
case SILInstructionKind::SetDeallocatingInst:
case SILInstructionKind::StoreWeakInst:
case SILInstructionKind::ValueMetatypeInst:
return false;

// Escape the value. The lifetime must already be enforced via something like
// fix_lifetime.
case SILInstructionKind::RefToRawPointerInst:
case SILInstructionKind::RefToUnmanagedInst:
case SILInstructionKind::RefToUnownedInst:
case SILInstructionKind::UncheckedBitwiseCastInst:
case SILInstructionKind::UncheckedTrivialBitCastInst:
return false;

// Dynamic dispatch without capturing self.
case SILInstructionKind::ObjCMethodInst:
case SILInstructionKind::ObjCSuperMethodInst:
case SILInstructionKind::SuperMethodInst:
case SILInstructionKind::WitnessMethodInst:
return false;
}
}

//===----------------------------------------------------------------------===//
// CopyPropagationState: shared state for the pass's analysis and transforms.
//===----------------------------------------------------------------------===//
Expand Down Expand Up @@ -409,7 +273,7 @@ class LivenessInfo {
//
// This call cannot be allowed to destroy %val.
void recordUser(Operand *use) {
bool consume = isConsuming(use);
bool consume = isConsumingUse(use);
auto iterAndSuccess = users.try_emplace(use->getUser(), consume);
if (!iterAndSuccess.second)
iterAndSuccess.first->second &= consume;
Expand Down Expand Up @@ -620,7 +484,7 @@ static bool computeLiveness(CopyPropagationState &pass) {
}
continue;
}
if (isConsuming(use)) {
if (isConsumingUse(use)) {
pass.liveness.recordOriginalDestroy(use);
// Destroying a values does not force liveness.
if (isa<DestroyValueInst>(user))
Expand Down Expand Up @@ -810,7 +674,7 @@ static void rewriteCopies(CopyPropagationState &pass) {
return;
}
// Nonconsuming uses do not need copies and cannot be marked as destroys.
if (!isConsuming(use))
if (!isConsumingUse(use))
return;

// If this use was marked as a final destroy *and* this is the first
Expand Down
140 changes: 140 additions & 0 deletions lib/SILOptimizer/Utils/InstOptUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1575,3 +1575,143 @@ void swift::insertDestroyOfCapturedArguments(
releasePartialApplyCapturedArg(builder, loc, arg.get(), paramInfo);
}
}

/// Return true if the given operand is consumed by the given call.
static bool isAppliedArgConsumed(ApplySite apply, Operand *oper) {
ParameterConvention paramConv;
if (oper->get() == apply.getCallee()) {
assert(oper->getOperandNumber() == 0 &&
"function can't be passed to itself");
paramConv = apply.getSubstCalleeType()->getCalleeConvention();
} else {
unsigned argIndex = apply.getCalleeArgIndex(*oper);
paramConv = apply.getSubstCalleeConv()
.getParamInfoForSILArg(argIndex)
.getConvention();
}
return isConsumedParameter(paramConv);
}

/// Return true if the given builtin consumes its operand.
static bool isBuiltinArgConsumed(BuiltinInst *BI) {
const BuiltinInfo &Builtin = BI->getBuiltinInfo();
switch (Builtin.ID) {
default:
llvm_unreachable("Unexpected Builtin with non-trivial value operand.");
// Extend lifetime without consuming.
case BuiltinValueKind::ErrorInMain:
case BuiltinValueKind::UnexpectedError:
case BuiltinValueKind::WillThrow:
case BuiltinValueKind::GlobalStringTablePointer:
return false;
// UnsafeGuaranteed moves the value, which will later be destroyed.
case BuiltinValueKind::UnsafeGuaranteed:
return true;
}
}

bool swift::isConsumingUse(Operand *use) {
assert(!use->get()->getType().isAddress());

auto *user = use->getUser();
if (isa<ApplySite>(user))
return isAppliedArgConsumed(ApplySite(user), use);

if (auto *BI = dyn_cast<BuiltinInst>(user))
return isBuiltinArgConsumed(BI);

switch (user->getKind()) {
default:
llvm::dbgs() << *user;
llvm_unreachable("Unexpected use of a non-trivial, non-address value.");

// Consume the value.
case SILInstructionKind::AutoreleaseValueInst:
case SILInstructionKind::DeallocBoxInst:
case SILInstructionKind::DeallocExistentialBoxInst:
case SILInstructionKind::DeallocRefInst:
case SILInstructionKind::DeinitExistentialValueInst:
case SILInstructionKind::DestroyValueInst:
case SILInstructionKind::EndLifetimeInst:
case SILInstructionKind::InitExistentialRefInst:
case SILInstructionKind::InitExistentialValueInst:
case SILInstructionKind::KeyPathInst:
case SILInstructionKind::ReleaseValueInst:
case SILInstructionKind::ReleaseValueAddrInst:
case SILInstructionKind::StoreInst:
case SILInstructionKind::StrongReleaseInst:
case SILInstructionKind::UnownedReleaseInst:
case SILInstructionKind::UnconditionalCheckedCastValueInst:
return true;

// Terminators must consume their owned values.
case SILInstructionKind::BranchInst:
case SILInstructionKind::CheckedCastBranchInst:
case SILInstructionKind::CheckedCastValueBranchInst:
case SILInstructionKind::CondBranchInst:
case SILInstructionKind::ReturnInst:
case SILInstructionKind::ThrowInst:
return true;

case SILInstructionKind::DeallocPartialRefInst:
return cast<DeallocPartialRefInst>(user)->getInstance() == use->get();

// Move the value.
case SILInstructionKind::TupleInst:
case SILInstructionKind::StructInst:
case SILInstructionKind::ObjectInst:
case SILInstructionKind::EnumInst:
case SILInstructionKind::OpenExistentialRefInst:
case SILInstructionKind::UpcastInst:
case SILInstructionKind::UncheckedRefCastInst:
case SILInstructionKind::ConvertFunctionInst:
case SILInstructionKind::RefToBridgeObjectInst:
case SILInstructionKind::BridgeObjectToRefInst:
case SILInstructionKind::UnconditionalCheckedCastInst:
case SILInstructionKind::MarkUninitializedInst:
case SILInstructionKind::UncheckedEnumDataInst:
case SILInstructionKind::DestructureStructInst:
case SILInstructionKind::DestructureTupleInst:
return true;

// BeginBorrow extends the lifetime like a normal use. Note that end_borrow
// is a consuming use of begin_borrow.
case SILInstructionKind::BeginBorrowInst:
case SILInstructionKind::LoadBorrowInst:
case SILInstructionKind::StructExtractInst:
case SILInstructionKind::TupleExtractInst:
return false;
case SILInstructionKind::EndBorrowInst:
return true;

// Extend the lifetime without borrowing, propagating, or destroying it.
case SILInstructionKind::BridgeObjectToWordInst:
case SILInstructionKind::ClassMethodInst:
case SILInstructionKind::CopyBlockInst:
case SILInstructionKind::CopyValueInst:
case SILInstructionKind::DebugValueInst:
case SILInstructionKind::ExistentialMetatypeInst:
case SILInstructionKind::FixLifetimeInst:
case SILInstructionKind::SelectEnumInst:
case SILInstructionKind::SetDeallocatingInst:
case SILInstructionKind::StoreWeakInst:
case SILInstructionKind::ValueMetatypeInst:
return false;

// Escape the value. The lifetime must already be enforced via something like
// fix_lifetime.
case SILInstructionKind::RefToRawPointerInst:
case SILInstructionKind::RefToUnmanagedInst:
case SILInstructionKind::RefToUnownedInst:
case SILInstructionKind::UncheckedBitwiseCastInst:
case SILInstructionKind::UncheckedTrivialBitCastInst:
return false;

// Dynamic dispatch without capturing self.
case SILInstructionKind::ObjCMethodInst:
case SILInstructionKind::ObjCSuperMethodInst:
case SILInstructionKind::SuperMethodInst:
case SILInstructionKind::WitnessMethodInst:
return false;
}
}