Skip to content

Commit bffe0e7

Browse files
authored
Merge pull request #64022 from atrick/ossa-complete-util
[NFC] Add SILGenCleanup::completeOSSLifetimes
2 parents 40573ad + 0018a0a commit bffe0e7

32 files changed

+674
-69
lines changed

include/swift/AST/SILOptions.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ class SILOptions {
173173
/// If set to true, compile with the SIL Opaque Values enabled.
174174
bool EnableSILOpaqueValues = false;
175175

176+
/// Require linear OSSA lifetimes after SILGen
177+
bool OSSACompleteLifetimes = false;
178+
176179
// The kind of function bodies to skip emitting.
177180
FunctionBodySkipping SkipFunctionBodies = FunctionBodySkipping::None;
178181

include/swift/Option/FrontendOptions.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1165,4 +1165,8 @@ def enable_emit_generic_class_ro_t_list :
11651165
def experimental_spi_only_imports :
11661166
Flag<["-"], "experimental-spi-only-imports">,
11671167
HelpText<"Enable use of @_spiOnly imports">;
1168+
1169+
def enable_ossa_complete_lifetimes :
1170+
Flag<["-"], "enable-ossa-complete-lifetimes">,
1171+
HelpText<"Require linear OSSA lifetimes after SILGen">;
11681172
} // end let Flags = [FrontendOption, NoDriverOption, HelpHidden]

include/swift/SIL/LinearLifetimeChecker.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,23 @@ class LinearLifetimeChecker {
5757
friend class SILOwnershipVerifier;
5858
friend class SILValueOwnershipChecker;
5959

60-
DeadEndBlocks &deadEndBlocks;
60+
// TODO: migrate away from using dead end blocks for OSSA values. end_borrow
61+
// or destroy_value should ideally exist on all paths. However, deadEndBlocks
62+
// may still be useful for checking memory lifetime for address uses.
63+
DeadEndBlocks *deadEndBlocks;
6164

6265
public:
63-
LinearLifetimeChecker(DeadEndBlocks &deadEndBlocks)
66+
/// \p deadEndBlocks should be provided for lifetimes that do not require
67+
/// consuming uses on dead-end paths, which end in an unreachable terminator.
68+
/// OSSA values require consumes on all paths, so \p deadEndBlocks are *not*
69+
/// required for OSSA lifetimes. Memory lifetimes and access scopes only
70+
/// require destroys on non-dead-end paths.
71+
///
72+
/// TODO: The verifier currently requires OSSA borrow scopes to end on all
73+
/// paths. Owned OSSA lifetimes may still be missing destroys on dead-end
74+
/// paths. Once owned values are fully enforced, the same invariant will hold
75+
/// for all OSSA values.
76+
LinearLifetimeChecker(DeadEndBlocks *deadEndBlocks = nullptr)
6477
: deadEndBlocks(deadEndBlocks) {}
6578

6679
/// Returns true that \p value forms a linear lifetime with consuming uses \p
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
//===--- OwnershipLifetimeCompletion.h ------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
///
13+
/// OSSA lifetime completion adds lifetime ending instructions to make
14+
/// linear lifetimes complete.
15+
///
16+
/// Completion is bottom-up recursive over nested borrow scopes. Additionally,
17+
/// this may be extended to support dependent owned lifetimes in the future to
18+
/// handle owned non-escaping values.
19+
///
20+
/// Lexical lifetimes can only be incomplete as a result of dead-end blocks. In
21+
/// this case, their lifetime ends immediately before the dead-end block.
22+
///
23+
/// Nonlexical lifetimes can be incomplete for any reason. Their lifetime ends
24+
/// at the liveness boundary.
25+
///
26+
//===----------------------------------------------------------------------===//
27+
28+
#ifndef SWIFT_SILOPTIMIZER_UTILS_OSSSALIFETIMECOMPLETION_H
29+
#define SWIFT_SILOPTIMIZER_UTILS_OSSSALIFETIMECOMPLETION_H
30+
31+
#include "swift/SIL/NodeDatastructures.h"
32+
#include "swift/SIL/OwnershipLiveness.h"
33+
#include "swift/SIL/SILFunction.h"
34+
35+
namespace swift {
36+
37+
enum class LifetimeCompletion { NoLifetime, AlreadyComplete, WasCompleted };
38+
39+
class OSSALifetimeCompletion {
40+
// If domInfo is nullptr, then InteriorLiveness never assumes dominance. As a
41+
// result it may report extra unenclosedPhis. In that case, any attempt to
42+
// create a new phi would result in an immediately redundant phi.
43+
const DominanceInfo *domInfo = nullptr;
44+
45+
// Cache intructions already handled by the recursive algorithm to avoid
46+
// recomputing their lifetimes.
47+
ValueSet completedValues;
48+
49+
public:
50+
OSSALifetimeCompletion(SILFunction *function, const DominanceInfo *domInfo)
51+
: domInfo(domInfo), completedValues(function) {}
52+
53+
/// Insert a lifetime-ending instruction on every path to complete the OSSA
54+
/// lifetime of \p value. Lifetime completion is only relevant for owned
55+
/// values or borrow introducers.
56+
///
57+
/// Returns true if any new instructions were created to complete the
58+
/// lifetime.
59+
///
60+
/// TODO: We also need to complete scoped addresses (e.g. store_borrow)!
61+
LifetimeCompletion completeOSSALifetime(SILValue value) {
62+
if (value->getOwnershipKind() == OwnershipKind::None)
63+
return LifetimeCompletion::NoLifetime;
64+
65+
if (value->getOwnershipKind() != OwnershipKind::Owned) {
66+
BorrowedValue borrowedValue(value);
67+
if (!borrowedValue)
68+
return LifetimeCompletion::NoLifetime;
69+
70+
if (!borrowedValue.isLocalScope())
71+
return LifetimeCompletion::AlreadyComplete;
72+
}
73+
if (!completedValues.insert(value))
74+
return LifetimeCompletion::AlreadyComplete;
75+
76+
return analyzeAndUpdateLifetime(value)
77+
? LifetimeCompletion::WasCompleted
78+
: LifetimeCompletion::AlreadyComplete;
79+
}
80+
81+
protected:
82+
bool analyzeAndUpdateLifetime(SILValue value);
83+
};
84+
85+
//===----------------------------------------------------------------------===//
86+
// UnreachableLifetimeCompletion
87+
//===----------------------------------------------------------------------===//
88+
89+
/// Fixup OSSA before deleting an unreachable code path.
90+
///
91+
/// Only needed when a code path reaches a no-return function, making the
92+
/// path now partially unreachable. Conditional branch folding requires no fixup
93+
/// because it causes the entire path to become unreachable.
94+
class UnreachableLifetimeCompletion {
95+
SILFunction *function;
96+
97+
// If domInfo is nullptr, lifetime completion may attempt to recreate
98+
// redundant phis, which should be immediately discarded.
99+
const DominanceInfo *domInfo = nullptr;
100+
101+
BasicBlockSetVector unreachableBlocks;
102+
InstructionSet unreachableInsts; // not including those in unreachableBlocks
103+
ValueSetVector incompleteValues;
104+
bool updatingLifetimes = false;
105+
106+
public:
107+
UnreachableLifetimeCompletion(SILFunction *function, DominanceInfo *domInfo)
108+
: function(function), unreachableBlocks(function),
109+
unreachableInsts(function), incompleteValues(function) {}
110+
111+
/// Record information about this unreachable instruction and return true if
112+
/// ends any simple OSSA lifetimes.
113+
///
114+
/// Note: this must be called in forward order so that lifetime completion
115+
/// runs from the inside out.
116+
void visitUnreachableInst(SILInstruction *instruction);
117+
118+
void visitUnreachableBlock(SILBasicBlock *block) {
119+
unreachableBlocks.insert(block);
120+
}
121+
122+
/// Complete the lifetime of any value defined outside of the unreachable
123+
/// region that was previously destroyed in the unreachable region.
124+
bool completeLifetimes();
125+
};
126+
127+
} // namespace swift
128+
129+
#endif

include/swift/SIL/OwnershipLiveness.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,11 @@ class InteriorLiveness : public OSSALiveness {
241241
void compute(const DominanceInfo *domInfo,
242242
InnerScopeHandlerRef handleInnerScope = InnerScopeHandlerRef());
243243

244+
/// Compute the boundary from the blocks discovered during liveness analysis.
245+
void computeBoundary(PrunedLivenessBoundary &boundary) const {
246+
liveness.computeBoundary(boundary);
247+
}
248+
244249
AddressUseKind getAddressUseKind() const { return addressUseKind; }
245250

246251
ArrayRef<SILValue> getUnenclosedPhis() const { return unenclosedPhis; }

include/swift/SIL/SILFunction.h

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,18 +1403,30 @@ class SILFunction
14031403
decl->getLifetimeAnnotation());
14041404
}
14051405

1406-
/// verify - Run the IR verifier to make sure that the SILFunction follows
1406+
/// verify - Run the SIL verifier to make sure that the SILFunction follows
14071407
/// invariants.
1408-
void verify(bool SingleFunction = true) const;
1408+
void verify(bool SingleFunction = true,
1409+
bool isCompleteOSSA = true,
1410+
bool checkLinearLifetime = true) const;
1411+
1412+
/// Run the SIL verifier without assuming OSSA lifetimes end at dead end
1413+
/// blocks.
1414+
void verifyIncompleteOSSA() const {
1415+
verify(/*SingleFunction=*/true, /*completeOSSALifetimes=*/false);
1416+
}
14091417

14101418
/// Verifies the lifetime of memory locations in the function.
14111419
void verifyMemoryLifetime();
14121420

1413-
/// Run the SIL ownership verifier to check for ownership invariant failures.
1421+
/// Run the SIL ownership verifier to check that all values with ownership
1422+
/// have a linear lifetime. Regular OSSA invariants are checked separately in
1423+
/// normal SIL verification.
1424+
///
1425+
/// \p deadEndBlocks is nullptr when OSSA lifetimes are complete.
14141426
///
1415-
/// NOTE: The ownership verifier is always run when performing normal IR
1427+
/// NOTE: The ownership verifier is run when performing normal IR
14161428
/// verification, so this verification can be viewed as a subset of
1417-
/// SILFunction::verify.
1429+
/// SILFunction::verify(checkLinearLifetimes=true).
14181430
void verifyOwnership(DeadEndBlocks *deadEndBlocks) const;
14191431

14201432
/// Verify that all non-cond-br critical edges have been split.

include/swift/SIL/SILModule.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -904,7 +904,17 @@ class SILModule {
904904

905905
/// Run the SIL verifier to make sure that all Functions follow
906906
/// invariants.
907-
void verify() const;
907+
void verify(bool isCompleteOSSA = true,
908+
bool checkLinearLifetime = true) const;
909+
910+
/// Run the SIL verifier without assuming OSSA lifetimes end at dead end
911+
/// blocks.
912+
void verifyIncompleteOSSA() const {
913+
verify(/*isCompleteOSSA=*/false);
914+
}
915+
916+
/// Check linear OSSA lifetimes, assuming complete OSSA.
917+
void verifyOwnership() const;
908918

909919
/// Check if there are any leaking instructions.
910920
///

include/swift/SIL/SILValue.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,8 @@ class SILValue {
675675
}
676676

677677
/// Verify that this SILValue and its uses respects ownership invariants.
678+
///
679+
/// \p DEBlocks is nullptr when OSSA lifetimes are complete.
678680
void verifyOwnership(DeadEndBlocks *DEBlocks) const;
679681

680682
SWIFT_DEBUG_DUMP;

include/swift/SILOptimizer/PassManager/PassPipeline.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#define PASSPIPELINE(NAME, DESCRIPTION)
2323
#endif
2424

25+
PASSPIPELINE(SILGen, "SILGen Passes")
2526
PASSPIPELINE(Diagnostic, "Guaranteed Passes")
2627
PASSPIPELINE(LowerHopToActor, "Lower Hop to Actor")
2728
PASSPIPELINE(OwnershipEliminator, "Utility pass to just run the ownership eliminator pass")

lib/Frontend/CompilerInvocation.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2001,6 +2001,8 @@ static bool ParseSILArgs(SILOptions &Opts, ArgList &Args,
20012001
parseExclusivityEnforcementOptions(A, Opts, Diags);
20022002
}
20032003

2004+
Opts.OSSACompleteLifetimes |= Args.hasArg(OPT_enable_ossa_complete_lifetimes);
2005+
20042006
return false;
20052007
}
20062008

lib/SIL/Utils/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ target_sources(swiftSIL PRIVATE
1111
MemAccessUtils.cpp
1212
MemoryLocations.cpp
1313
OptimizationRemark.cpp
14+
OSSALifetimeCompletion.cpp
1415
OwnershipLiveness.cpp
1516
OwnershipUtils.cpp
1617
PrettyStackTrace.cpp

0 commit comments

Comments
 (0)