Skip to content

Commit 392014b

Browse files
committed
Add a CanonicalOSSALifetime utility.
Canonicalizing OSSA provably minimizes the number of retains and releases within the boundaries of that lifetime. This eliminates the need for ad-hoc optimization of OSSA copies. This initial implementation only canonicalizes owned values, but canonicalizing guaranteed values is a simple extension. This was originally part of the CopyPropagation prototype years ago. Now OSSA is specified completely enough that it can be turned into a simple utility instead. CanonicalOSSALifetime uses PrunedLiveness to find the extended live range and identify the consumes on the boundary. All other consumes need their own copy. No other copies are needed. By running this after other transformations that affect OSSA lifetimes, we can avoid the need to run pattern-matching optimization to SemanticARC to recover from suboptimal patterns, which is not robust, maintainable, or efficient.
1 parent 50d7172 commit 392014b

File tree

4 files changed

+700
-667
lines changed

4 files changed

+700
-667
lines changed
Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
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 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`.
22+
///
23+
/// FIXME: Canonicalization currently bails out if any uses of the def has
24+
/// OperandOwnership::PointerEscape. Once project_box is protected by a borrow
25+
/// scope and mark_dependence is associated with an end_dependence,
26+
/// canonicalization will work everywhere as intended.
27+
///
28+
//===----------------------------------------------------------------------===//
29+
30+
#ifndef SWIFT_SILOPTIMIZER_UTILS_CANONICALOSSALIFETIME_H
31+
#define SWIFT_SILOPTIMIZER_UTILS_CANONICALOSSALIFETIME_H
32+
33+
#include "llvm/ADT/DenseMap.h"
34+
#include "llvm/ADT/SetVector.h"
35+
#include "swift/SIL/SILInstruction.h"
36+
#include "swift/SILOptimizer/Utils/PrunedLiveness.h"
37+
38+
namespace swift {
39+
40+
struct CanonicalOSSALifetime;
41+
42+
/// Top-Level API: rewrites copies and destroys within \p def's extended
43+
/// lifetime. \p lifetime caches transient analysis state across multiple calls
44+
/// and indicates whether any SILAnalyses must be invalidated.
45+
///
46+
/// Return false if the OSSA structure cannot be recognized (with a proper OSSA
47+
/// representation this will always return true).
48+
bool canonicalizeValueLifetime(SILValue def, CanonicalOSSALifetime &lifetime);
49+
50+
/// Find the original definition of a potentially copied value.
51+
///
52+
/// This use-def walk must be consistent with the def-use walks performed within
53+
/// the CanonicalOSSALifetime implementation.
54+
inline SILValue getCanonicalCopiedDef(SILValue v) {
55+
while (true) {
56+
if (auto *copy = dyn_cast<CopyValueInst>(v)) {
57+
v = copy->getOperand();
58+
continue;
59+
}
60+
return v;
61+
}
62+
}
63+
64+
/// Information about consumes on the extended-lifetime boundary. Consuming uses
65+
/// within the lifetime are not included--they will consume a copy after
66+
/// rewriting.
67+
///
68+
/// This result remains valid during copy rewriting. The only instructions
69+
/// referenced it contains are consumes that cannot be deleted.
70+
class CanonicalOSSAConsumeInfo {
71+
// Map blocks on the lifetime boundary to the last consuming instruction.
72+
llvm::SmallDenseMap<SILBasicBlock *, SILInstruction *, 4> finalBlockConsumes;
73+
74+
public:
75+
bool empty() const { return finalBlockConsumes.empty(); }
76+
77+
void clear() { finalBlockConsumes.clear(); }
78+
79+
void recordFinalConsume(SILInstruction *inst) {
80+
assert(!finalBlockConsumes.count(inst->getParent()));
81+
finalBlockConsumes[inst->getParent()] = inst;
82+
}
83+
84+
// Return true if this instruction is marked as a final consume point of the
85+
// current def's live range. A consuming instruction can only be claimed once
86+
// because instructions like `tuple` can consume the same value via multiple
87+
// operands.
88+
bool claimConsume(SILInstruction *inst) {
89+
auto destroyPos = finalBlockConsumes.find(inst->getParent());
90+
if (destroyPos != finalBlockConsumes.end() && destroyPos->second == inst) {
91+
finalBlockConsumes.erase(destroyPos);
92+
return true;
93+
}
94+
return false;
95+
}
96+
97+
SWIFT_ASSERT_ONLY_DECL(void dump() const LLVM_ATTRIBUTE_USED);
98+
};
99+
100+
/// Canonicalize OSSA lifetimes.
101+
///
102+
/// Allows the allocation of analysis state to be reused across calls to
103+
/// canonicalizeValueLifetime().
104+
struct CanonicalOSSALifetime {
105+
// Current copied def for which this state describes the liveness.
106+
SILValue currDef;
107+
108+
// Cumulatively, have any instructions been modified by canonicalization?
109+
bool changed = false;
110+
111+
// Pruned liveness for the extended live range including copies. For this
112+
// purpose, only consuming instructions are considered "lifetime
113+
// ending". end_borrows do not end a liverange that may include owned copies.
114+
PrunedLiveness liveness;
115+
116+
// Original points in the CFG where the current value's lifetime ends. This
117+
// includes any point in which the value is consumed or destroyed. For
118+
// guaranteed values, it also includes points where the borrow scope
119+
// ends. A backward walk from these blocks must discover all uses on paths
120+
// that lead to a return or throw.
121+
//
122+
// These blocks are not necessarily in the pruned live blocks since
123+
// pruned liveness does not consider destroy_values.
124+
llvm::SmallSetVector<SILBasicBlock *, 8> lifetimeEndBlocks;
125+
126+
public:
127+
CanonicalOSSAConsumeInfo consumes;
128+
129+
SILValue def() const { return currDef; }
130+
131+
void initDef(SILValue def) {
132+
assert(lifetimeEndBlocks.empty() && liveness.empty());
133+
consumes.clear();
134+
135+
currDef = def;
136+
liveness.initializeDefBlock(def->getParentBlock());
137+
}
138+
139+
void clearLiveness() {
140+
lifetimeEndBlocks.clear();
141+
liveness.clear();
142+
}
143+
144+
bool isChanged() const { return changed; }
145+
146+
void setChanged() { changed = true; }
147+
148+
void updateLivenessForUse(Operand *use) {
149+
// Because this liverange may include owned copies, only record consuming
150+
// instructions as "lifetime ending".
151+
bool consuming =
152+
use->isLifetimeEnding()
153+
&& (use->get().getOwnershipKind() == OwnershipKind::Owned);
154+
liveness.updateForUse(use, consuming);
155+
}
156+
157+
PrunedLiveBlocks::IsLive getBlockLiveness(SILBasicBlock *bb) const {
158+
return liveness.getBlockLiveness(bb);
159+
}
160+
161+
enum IsInterestingUser { NonUser, NonConsumingUse, ConsumingUse };
162+
IsInterestingUser isInterestingUser(SILInstruction *user) const {
163+
// Translate PrunedLiveness "lifetime-ending" uses to consuming uses. For
164+
// the purpose of an extended liverange that includes owned copies, an
165+
// end_borrow is not "lifetime-ending".
166+
switch (liveness.isInterestingUser(user)) {
167+
case PrunedLiveness::NonUser:
168+
return NonUser;
169+
case PrunedLiveness::NonLifetimeEndingUse:
170+
return NonConsumingUse;
171+
case PrunedLiveness::LifetimeEndingUse:
172+
return ConsumingUse;
173+
}
174+
}
175+
176+
void recordLifetimeEnd(Operand *use) {
177+
lifetimeEndBlocks.insert(use->getUser()->getParent());
178+
}
179+
180+
ArrayRef<SILBasicBlock *> getLifetimeEndBlocks() const {
181+
return lifetimeEndBlocks.getArrayRef();
182+
}
183+
};
184+
185+
} // end namespace swift
186+
187+
#endif

0 commit comments

Comments
 (0)