Skip to content

[DCE] Don't delete instructions which consume escaping values. #80766

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

Merged
merged 4 commits into from
Apr 12, 2025
Merged
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
44 changes: 26 additions & 18 deletions include/swift/SIL/OSSALifetimeCompletion.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,34 +43,42 @@ class OSSALifetimeCompletion {
enum HandleTrivialVariable_t { IgnoreTrivialVariable, ExtendTrivialVariable };

private:
// If domInfo is nullptr, then InteriorLiveness never assumes dominance. As a
// result it may report extra unenclosedPhis. In that case, any attempt to
// create a new phi would result in an immediately redundant phi.
/// If domInfo is nullptr, then InteriorLiveness never assumes dominance. As a
/// result it may report extra unenclosedPhis. In that case, any attempt to
/// create a new phi would result in an immediately redundant phi.
const DominanceInfo *domInfo = nullptr;

DeadEndBlocks &deadEndBlocks;

// Cache intructions already handled by the recursive algorithm to avoid
// recomputing their lifetimes.
/// Cache intructions already handled by the recursive algorithm to avoid
/// recomputing their lifetimes.
ValueSet completedValues;

// Extend trivial variables for lifetime diagnostics (only in SILGenCleanup).
/// Extend trivial variables for lifetime diagnostics (only in SILGenCleanup).
HandleTrivialVariable_t handleTrivialVariable;

/// Whether verification of the computed liveness should be run even when the
/// global setting is off.
/// TODO: Remove this option.
bool ForceLivenessVerification;

public:
OSSALifetimeCompletion(SILFunction *function, const DominanceInfo *domInfo,
DeadEndBlocks &deadEndBlocks,
HandleTrivialVariable_t handleTrivialVariable = IgnoreTrivialVariable)
OSSALifetimeCompletion(
SILFunction *function, const DominanceInfo *domInfo,
DeadEndBlocks &deadEndBlocks,
HandleTrivialVariable_t handleTrivialVariable = IgnoreTrivialVariable,
bool forceLivenessVerification = false)
: domInfo(domInfo), deadEndBlocks(deadEndBlocks),
completedValues(function), handleTrivialVariable(handleTrivialVariable) {}

// The kind of boundary at which to complete the lifetime.
//
// Liveness: "As early as possible." Consume the value after the last
// non-consuming uses.
// Availability: "As late as possible." Consume the value in the last blocks
// beyond the non-consuming uses in which the value has been
// consumed on no incoming paths.
completedValues(function), handleTrivialVariable(handleTrivialVariable),
ForceLivenessVerification(forceLivenessVerification) {}

/// The kind of boundary at which to complete the lifetime.
///
/// Liveness: "As early as possible." Consume the value after the last
/// non-consuming uses.
/// Availability: "As late as possible." Consume the value in the last blocks
/// beyond the non-consuming uses in which the value has been
/// consumed on no incoming paths.
struct Boundary {
enum Value : uint8_t {
Liveness,
Expand Down
5 changes: 3 additions & 2 deletions lib/SIL/Utils/OSSALifetimeCompletion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -501,8 +501,9 @@ bool OSSALifetimeCompletion::analyzeAndUpdateLifetime(
};
Walker walker(*this, scopedAddress, boundary, liveness);
AddressUseKind result = walker.walk(scopedAddress.value);
if (VerifyLifetimeCompletion && boundary != Boundary::Availability
&& result != AddressUseKind::NonEscaping) {
if ((VerifyLifetimeCompletion || ForceLivenessVerification) &&
boundary != Boundary::Availability &&
result != AddressUseKind::NonEscaping) {
llvm::errs() << "Incomplete liveness for:\n" << scopedAddress.value;
if (auto *escapingUse = walker.getEscapingUse()) {
llvm::errs() << " escapes at:\n";
Expand Down
26 changes: 25 additions & 1 deletion lib/SILOptimizer/Transforms/DeadCodeElimination.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,28 @@ static bool seemsUseful(SILInstruction *I) {
return true;
}

// Instructions which end the lifetimes of values which escape can only be
// deleted if compensating lifetime ends are added. Compensating lifetime
// ends are added by OSSACompleteLifetime when the def block of the value
// is different from the parent block of the instruction. But
// OSSACompleteLifetime requires that liveness be complete--that there are no
// pointer escapes. So we can't delete instructions which end the lifetime
// of values which escape to a pointer and whose parent blocks are different.
if (llvm::any_of(I->getAllOperands(), [I](Operand &operand) {
if (!operand.isLifetimeEnding())
return false;
auto value = operand.get();
if (isa<SILUndef>(value))
return false;
auto *insertionPoint = value->getDefiningInsertionPoint();
ASSERT(insertionPoint);
if (insertionPoint->getParent() == I->getParent())
return false;
return findPointerEscape(value);
})) {
return true;
}

if (auto *BI = dyn_cast<BuiltinInst>(I)) {
// Although the onFastPath builtin has no side-effects we don't want to
// remove it.
Expand Down Expand Up @@ -813,7 +835,9 @@ bool DCE::removeDead() {
}
}

OSSALifetimeCompletion completion(F, DT, *deadEndBlocks);
OSSALifetimeCompletion completion(
F, DT, *deadEndBlocks, OSSALifetimeCompletion::IgnoreTrivialVariable,
/*forceLivenessVerification=*/true);
for (auto value : valuesToComplete) {
if (!value.has_value())
continue;
Expand Down
31 changes: 31 additions & 0 deletions test/SILOptimizer/dead_code_elimination_ossa.sil
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@ class C {}

sil @getC : $@convention(thin) () -> @owned C
sil @barrier : $@convention(thin) () -> ()
sil @initC : $@convention(thin) (Builtin.Word) -> (@owned C, Builtin.RawPointer)
sil [readnone] @finalC : $@convention(thin) (@owned C) -> @owned C

struct CAndBit {
var c: C
var bit: Int1
}

struct Bool {
var _value: Builtin.Int1
}

struct MO: ~Copyable {
var x: Int
deinit
Expand Down Expand Up @@ -507,3 +513,28 @@ bb0(%0 : @owned $Outer):
return %6 : $()
}

// CHECK-LABEL: sil [ossa] @dont_remove_lifetime_end_of_escaping_value
// CHECK: [[FINALIZE:%[^,]+]] = function_ref @finalC
// CHECK: apply [[FINALIZE]]
// CHECK-LABEL: } // end sil function 'dont_remove_lifetime_end_of_escaping_value'
sil [ossa] @dont_remove_lifetime_end_of_escaping_value : $@convention(thin) (Bool) -> () {
bb0(%0 : $Bool):
%1 = integer_literal $Builtin.Word, 1
%2 = function_ref @initC : $@convention(thin) (Builtin.Word) -> (@owned C, Builtin.RawPointer)
%3 = apply %2(%1) : $@convention(thin) (Builtin.Word) -> (@owned C, Builtin.RawPointer)
(%4, %5) = destructure_tuple %3
%6 = mark_dependence %5 on %4
%7 = pointer_to_address %6 to [strict] $*Bool
store %0 to [trivial] %7
br bb1

bb1:
%13 = function_ref @finalC : $@convention(thin) (@owned C) -> @owned C
%14 = apply %13(%4) : $@convention(thin) (@owned C) -> @owned C
destroy_value %14
br bb2

bb2:
%17 = tuple ()
return %17
}