Skip to content

Commit d6eb325

Browse files
authored
Merge pull request #35241 from atrick/canonical-ossa
Add a CanonicalOSSALifetime utility
2 parents cfbed60 + 16b90b7 commit d6eb325

File tree

9 files changed

+1542
-735
lines changed

9 files changed

+1542
-735
lines changed

include/swift/Basic/Compiler.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,29 @@
103103
#define SWIFT_CRASH_BUG_REPORT_MESSAGE \
104104
"Please " SWIFT_BUG_REPORT_MESSAGE_BASE " and the crash backtrace."
105105

106+
// Conditionally exclude declarations or statements that are only needed for
107+
// assertions from release builds (NDEBUG) without cluttering the surrounding
108+
// code by #ifdefs.
109+
//
110+
// struct DoThings {
111+
// SWIFT_ASSERT_ONLY_DECL(unsigned verifyCount = 0);
112+
// DoThings() {
113+
// SWIFT_ASSERT_ONLY(verifyCount = getNumberOfThingsToDo());
114+
// }
115+
// void doThings() {
116+
// do {
117+
// // ... do each thing
118+
// SWIFT_ASSERT_ONLY(--verifyCount);
119+
// } while (!done());
120+
// assert(verifyCount == 0 && "did not do everything");
121+
// }
122+
// };
123+
#ifdef NDEBUG
124+
#define SWIFT_ASSERT_ONLY_DECL(X)
125+
#define SWIFT_ASSERT_ONLY(X) do { } while (false)
126+
#else
127+
#define SWIFT_ASSERT_ONLY_DECL(X) X
128+
#define SWIFT_ASSERT_ONLY(X) do { X; } while (false)
129+
#endif
130+
106131
#endif // SWIFT_BASIC_COMPILER_H

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ PASS(ConstantEvaluableSubsetChecker, "test-constant-evaluable-subset",
123123
PASS(CopyForwarding, "copy-forwarding",
124124
"Copy Forwarding to Remove Redundant Copies")
125125
PASS(CopyPropagation, "copy-propagation",
126-
"Copy propagation to Remove Redundant SSA Copies")
126+
"Copy propagation to Remove Redundant SSA Copies, pruning debug info")
127+
PASS(MandatoryCopyPropagation, "mandatory-copy-propagation",
128+
"Copy propagation to Remove Redundant SSA Copies, preserving debug info")
127129
PASS(COWOpts, "cow-opts",
128130
"Optimize COW operations")
129131
PASS(Differentiation, "differentiation",
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
//===--- CanonicalOSSALifetime.h - Canonicalize OSSA lifetimes --*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2020 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+
/// Canonicalize the copies and destroys of a single owned or guaranteed OSSA
14+
/// value.
15+
///
16+
/// This top-level API rewrites the extended OSSA lifetime of a SILValue:
17+
///
18+
/// void canonicalizeValueLifetime(SILValue def, CanonicalOSSALifetime &)
19+
///
20+
/// The extended lifetime transitively includes the uses of `def` itself along
21+
/// with the uses of any copies of `def`. Canonicalization provably minimizes
22+
/// the OSSA lifetime and its copies by rewriting all copies and destroys. Only
23+
/// consusming uses that are not on the liveness boundary require a copy.
24+
///
25+
/// Example #1: Handle consuming and nonconsuming uses.
26+
///
27+
/// bb0(%arg : @owned $T, %addr : @trivial $*T):
28+
/// %copy = copy_value %arg : $T
29+
/// debug_value %copy : $T
30+
/// store %copy to [init] %addr : $*T
31+
/// debug_value %arg : $T
32+
/// debug_value_addr %addr : $*T
33+
/// destroy_value %arg : $T
34+
///
35+
/// Will be transformed to:
36+
///
37+
/// bb0(%arg : @owned $T, %addr : @trivial $*T):
38+
/// // The original copy is deleted.
39+
/// debug_value %arg : $T
40+
/// // A new copy_value is inserted before the consuming store.
41+
/// %copy = copy_value %arg : $T
42+
/// store %copy to [init] %addr : $*T
43+
/// // The non-consuming use now uses the original value.
44+
/// debug_value %arg : $T
45+
/// // A new destroy is inserted after the last use.
46+
/// destroy_value %arg : $T
47+
/// debug_value_addr %addr : $*T
48+
/// // The original destroy is deleted.
49+
///
50+
/// Example #2: Handle control flow.
51+
///
52+
/// bb0(%arg : @owned $T):
53+
/// cond_br %_, bb1, bb2
54+
/// bb1:
55+
/// br bb3
56+
/// bb2:
57+
/// debug_value %arg : $T
58+
/// %copy = copy_value %arg : $T
59+
/// destroy_value %copy : $T
60+
/// br bb3
61+
/// bb3:
62+
/// destroy_value %arg : $T
63+
///
64+
/// Will be transformed to:
65+
///
66+
/// bb0(%arg : @owned $T):
67+
/// cond_br %_, bb1, bb2
68+
/// bb1:
69+
/// destroy_value %arg : $T
70+
/// br bb3
71+
/// bb2:
72+
/// // The original copy is deleted.
73+
/// debug_value %arg : $T
74+
/// destroy_value %arg : $T
75+
/// br bb3
76+
/// bb2:
77+
/// // The original destroy is deleted.
78+
///
79+
/// FIXME: Canonicalization currently bails out if any uses of the def has
80+
/// OperandOwnership::PointerEscape. Once project_box is protected by a borrow
81+
/// scope and mark_dependence is associated with an end_dependence,
82+
/// canonicalization will work everywhere as intended. The intention is to keep
83+
/// the canonicalization algorithm as simple and robust, leaving the remaining
84+
/// performance opportunities contingent on fixing the SIL representation.
85+
///
86+
/// FIXME: Canonicalization currently fails to eliminate copies downstream of a
87+
/// ForwardingBorrow. Aggregates should be fixed to be Reborrow instead of
88+
/// ForwardingBorrow, then computeCanonicalLiveness() can be fixed to extend
89+
/// liveness through ForwardingBorrows.
90+
///
91+
//===----------------------------------------------------------------------===//
92+
93+
#ifndef SWIFT_SILOPTIMIZER_UTILS_CANONICALOSSALIFETIME_H
94+
#define SWIFT_SILOPTIMIZER_UTILS_CANONICALOSSALIFETIME_H
95+
96+
#include "llvm/ADT/DenseMap.h"
97+
#include "llvm/ADT/SetVector.h"
98+
#include "swift/SIL/SILInstruction.h"
99+
#include "swift/SILOptimizer/Utils/PrunedLiveness.h"
100+
101+
namespace swift {
102+
103+
/// Information about consumes on the extended-lifetime boundary. Consuming uses
104+
/// within the lifetime are not included--they will consume a copy after
105+
/// rewriting. For borrowed def values, the consumes do not include the end of
106+
/// the borrow scope, rather the consumes transitively include consumes of any
107+
/// owned copies of the borrowed value.
108+
///
109+
/// This result remains valid during copy rewriting. The only instructions
110+
/// referenced it contains are consumes that cannot be deleted.
111+
class CanonicalOSSAConsumeInfo {
112+
/// Map blocks on the lifetime boundary to the last consuming instruction.
113+
llvm::SmallDenseMap<SILBasicBlock *, SILInstruction *, 4> finalBlockConsumes;
114+
115+
/// Record any debug_value instructions found after a final consume.
116+
SmallVector<DebugValueInst *, 8> debugAfterConsume;
117+
118+
/// For borrowed defs, track per-block copies of the borrowed value that only
119+
/// have uses outside the borrow scope and will not be removed by
120+
/// canonicalization. These copies are effectively distinct OSSA lifetimes
121+
/// that should be canonicalized separately.
122+
llvm::SmallDenseMap<SILBasicBlock *, CopyValueInst *, 4> persistentCopies;
123+
124+
public:
125+
bool hasUnclaimedConsumes() const { return !finalBlockConsumes.empty(); }
126+
127+
void clear() {
128+
finalBlockConsumes.clear();
129+
debugAfterConsume.clear();
130+
persistentCopies.clear();
131+
}
132+
133+
bool hasFinalConsumes() const { return !finalBlockConsumes.empty(); }
134+
135+
void recordFinalConsume(SILInstruction *inst) {
136+
assert(!finalBlockConsumes.count(inst->getParent()));
137+
finalBlockConsumes[inst->getParent()] = inst;
138+
}
139+
140+
// Return true if this instruction is marked as a final consume point of the
141+
// current def's live range. A consuming instruction can only be claimed once
142+
// because instructions like `tuple` can consume the same value via multiple
143+
// operands.
144+
bool claimConsume(SILInstruction *inst) {
145+
auto destroyPos = finalBlockConsumes.find(inst->getParent());
146+
if (destroyPos != finalBlockConsumes.end() && destroyPos->second == inst) {
147+
finalBlockConsumes.erase(destroyPos);
148+
return true;
149+
}
150+
return false;
151+
}
152+
153+
/// Record a debug_value that is known to be outside pruned liveness. Assumes
154+
/// that instructions are only visited once.
155+
void recordDebugAfterConsume(DebugValueInst *dvi) {
156+
debugAfterConsume.push_back(dvi);
157+
}
158+
159+
ArrayRef<DebugValueInst *> getDebugInstsAfterConsume() const {
160+
return debugAfterConsume;
161+
}
162+
163+
bool hasPersistentCopies() const { return !persistentCopies.empty(); }
164+
165+
bool isPersistentCopy(CopyValueInst *copy) const {
166+
auto iter = persistentCopies.find(copy->getParent());
167+
if (iter == persistentCopies.end()) {
168+
return false;
169+
}
170+
return iter->second == copy;
171+
}
172+
173+
SWIFT_ASSERT_ONLY_DECL(void dump() const LLVM_ATTRIBUTE_USED);
174+
};
175+
176+
/// Canonicalize OSSA lifetimes.
177+
///
178+
/// Allows the allocation of analysis state to be reused across calls to
179+
/// canonicalizeValueLifetime().
180+
class CanonicalizeOSSALifetime {
181+
public:
182+
/// Find the original definition of a potentially copied value.
183+
static SILValue getCanonicalCopiedDef(SILValue v);
184+
185+
private:
186+
/// If true, then debug_value instructions outside of non-debug
187+
/// liveness may be pruned during canonicalization.
188+
bool pruneDebug;
189+
190+
/// Current copied def for which this state describes the liveness.
191+
SILValue currentDef;
192+
193+
/// If an outer copy is created for uses outside the borrow scope
194+
CopyValueInst *outerCopy = nullptr;
195+
196+
/// Cumulatively, have any instructions been modified by canonicalization?
197+
bool changed = false;
198+
199+
/// Original points in the CFG where the current value's lifetime is consumed
200+
/// or destroyed. For guaranteed values it remains empty. A backward walk from
201+
/// these blocks must discover all uses on paths that lead to a return or
202+
/// throw.
203+
///
204+
/// These blocks are not necessarily in the pruned live blocks since
205+
/// pruned liveness does not consider destroy_values.
206+
SmallSetVector<SILBasicBlock *, 8> consumingBlocks;
207+
208+
/// Record all interesting debug_value instructions here rather then treating
209+
/// them like a normal use. An interesting debug_value is one that may lie
210+
/// outisde the pruned liveness at the time it is discovered.
211+
llvm::SmallDenseSet<DebugValueInst *, 8> debugValues;
212+
213+
/// Reuse a general worklist for def-use traversal.
214+
SmallSetVector<SILValue, 8> defUseWorklist;
215+
216+
/// Reuse a general worklist for CFG traversal.
217+
SmallSetVector<SILBasicBlock *, 8> blockWorklist;
218+
219+
/// Pruned liveness for the extended live range including copies. For this
220+
/// purpose, only consuming instructions are considered "lifetime
221+
/// ending". end_borrows do not end a liverange that may include owned copies.
222+
PrunedLiveness liveness;
223+
224+
/// Information about consuming instructions discovered in this caonical OSSA
225+
/// lifetime.
226+
CanonicalOSSAConsumeInfo consumes;
227+
228+
public:
229+
CanonicalizeOSSALifetime(bool pruneDebug) : pruneDebug(pruneDebug) {}
230+
231+
SILValue getCurrentDef() const { return currentDef; }
232+
233+
void initDef(SILValue def) {
234+
assert(consumingBlocks.empty() && debugValues.empty() && liveness.empty());
235+
consumes.clear();
236+
237+
currentDef = def;
238+
outerCopy = nullptr;
239+
liveness.initializeDefBlock(def->getParentBlock());
240+
}
241+
242+
void clearLiveness() {
243+
consumingBlocks.clear();
244+
debugValues.clear();
245+
liveness.clear();
246+
}
247+
248+
bool hasChanged() const { return changed; }
249+
250+
void setChanged() { changed = true; }
251+
252+
SILValue createdOuterCopy() const { return outerCopy; }
253+
254+
/// Top-Level API: rewrites copies and destroys within \p def's extended
255+
/// lifetime. \p lifetime caches transient analysis state across multiple
256+
/// calls.
257+
///
258+
/// Return false if the OSSA structure cannot be recognized (with a proper
259+
/// OSSA representation this will always return true).
260+
///
261+
/// Upon returning, isChanged() indicates, cumulatively, whether any SIL
262+
/// changes were made.
263+
///
264+
/// Upon returning, createdOuterCopy() indicates whether a new copy was
265+
/// created for uses outside the borrow scope. To canonicalize the new outer
266+
/// lifetime, call this API again on the value defined by the new copy.
267+
bool canonicalizeValueLifetime(SILValue def);
268+
269+
protected:
270+
void recordDebugValue(DebugValueInst *dvi) {
271+
debugValues.insert(dvi);
272+
}
273+
274+
void recordConsumingUse(Operand *use) {
275+
consumingBlocks.insert(use->getUser()->getParent());
276+
}
277+
278+
bool computeBorrowLiveness();
279+
280+
void consolidateBorrowScope();
281+
282+
bool computeCanonicalLiveness();
283+
284+
void findOrInsertDestroyInBlock(SILBasicBlock *bb);
285+
286+
void findOrInsertDestroys();
287+
288+
void rewriteCopies();
289+
};
290+
291+
} // end namespace swift
292+
293+
#endif

include/swift/SILOptimizer/Utils/PrunedLiveness.h

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,14 +90,6 @@
9090

9191
#include "swift/SIL/SILBasicBlock.h"
9292

93-
#ifdef NDEBUG
94-
#define SWIFT_ASSERT_ONLY_MEMBER(X)
95-
#define SWIFT_ASSERT_ONLY(X) do { } while (false)
96-
#else
97-
#define SWIFT_ASSERT_ONLY_MEMBER(X) X
98-
#define SWIFT_ASSERT_ONLY(X) do { X; } while (false)
99-
#endif
100-
10193
namespace swift {
10294

10395
/// Discover "pruned" liveness for an arbitrary set of uses. The client builds
@@ -140,13 +132,15 @@ class PrunedLiveBlocks {
140132
llvm::SmallDenseMap<SILBasicBlock *, bool, 4> liveBlocks;
141133

142134
// Once the first use has been seen, no definitions can be added.
143-
SWIFT_ASSERT_ONLY_MEMBER(bool seenUse = false);
135+
SWIFT_ASSERT_ONLY_DECL(bool seenUse = false);
144136

145137
public:
146138
bool empty() const { return liveBlocks.empty(); }
147139

148140
void clear() { liveBlocks.clear(); SWIFT_ASSERT_ONLY(seenUse = false); }
149141

142+
unsigned numLiveBlocks() const { return liveBlocks.size(); }
143+
150144
void initializeDefBlock(SILBasicBlock *defBB) {
151145
assert(!seenUse && "cannot initialize more defs with partial liveness");
152146
markBlockLive(defBB, LiveWithin);
@@ -212,10 +206,17 @@ class PrunedLiveness {
212206
users.clear();
213207
}
214208

209+
unsigned numLiveBlocks() const { return liveBlocks.numLiveBlocks(); }
210+
215211
void initializeDefBlock(SILBasicBlock *defBB) {
216212
liveBlocks.initializeDefBlock(defBB);
217213
}
218214

215+
/// For flexibility, \p lifetimeEnding is provided by the
216+
/// caller. PrunedLiveness makes no assumptions about the def-use
217+
/// relationships that generate liveness. For example, use->isLifetimeEnding()
218+
/// cannot distinguish the end of the borrow scope that defines this extended
219+
/// live range vs. a nested borrow scope within the extended live range.
219220
void updateForUse(Operand *use, bool lifetimeEnding);
220221

221222
PrunedLiveBlocks::IsLive getBlockLiveness(SILBasicBlock *bb) const {

0 commit comments

Comments
 (0)