Skip to content

Commit 8c9d402

Browse files
authored
Merge pull request #36913 from gottesmm/pr-45e4affc7e9132e241abc77bc7eef73664feef8a
[sil-mem2reg] Add a simple scope data structure and use it to simplify some code.
2 parents 8ed2a95 + c0e31d8 commit 8c9d402

File tree

2 files changed

+306
-19
lines changed

2 files changed

+306
-19
lines changed
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
//===--- ScopeOptUtils.h --------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2021 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+
/// A simple scope like construct for use in the SILOptimizer.
14+
///
15+
//===----------------------------------------------------------------------===//
16+
17+
#ifndef SWIFT_SILOPTIMIZER_UTILS_SCOPEOPTUTILS_H
18+
#define SWIFT_SILOPTIMIZER_UTILS_SCOPEOPTUTILS_H
19+
20+
#include "swift/Basic/LLVM.h"
21+
#include "swift/SIL/SILBuilder.h"
22+
#include "swift/SIL/SILValue.h"
23+
#include "llvm/ADT/Optional.h"
24+
#include "llvm/ADT/SmallVector.h"
25+
#include <functional>
26+
27+
namespace swift {
28+
namespace siloptimizer {
29+
30+
template <typename Result, typename... Args>
31+
struct Cleanup {
32+
std::function<Result(Args...)> func;
33+
};
34+
35+
/// A simple stack data structure that manages a stack of std::function cleanups
36+
/// that can be invalidated by index.
37+
///
38+
/// It is called asserting, since we force our user to explicitly pop the scope
39+
/// and abort otherwise.
40+
template <typename Result, typename... Args>
41+
class AssertingScope {
42+
using ScopeCleanup = Cleanup<Result, Args...>;
43+
SmallVector<Optional<ScopeCleanup>, 64> cleanups;
44+
bool didPop = false;
45+
46+
protected:
47+
~AssertingScope() { assert(didPop && "Did not pop scope?!"); }
48+
49+
AssertingScope() {}
50+
51+
/// Push a new SIL function based cleanup. Meant to be very
52+
/// lightweight. Returns the index of the cleanup.
53+
unsigned pushScope(std::function<Result(Args...)> func) {
54+
assert(!didPop && "Can not reuse scope once used?!");
55+
unsigned index = cleanups.size();
56+
cleanups.emplace_back(ScopeCleanup{func});
57+
return index;
58+
}
59+
60+
/// Invalidate the cleanup at the specific index. Asserts on index too big and
61+
/// on Optional has value.
62+
void invalidateCleanup(unsigned cleanupIndex) {
63+
assert(!didPop && "Should not invalidate once popped?!");
64+
assert(cleanupIndex < cleanups.size());
65+
assert(cleanups[cleanupIndex].hasValue());
66+
cleanups[cleanupIndex] = None;
67+
}
68+
69+
/// Pop the scope, running all non-Optional cleanups, and setting didPop. Once
70+
/// this is done the scope has been consumed and no longer has any state.
71+
///
72+
/// Can only be done once the scope has been moved.
73+
void pop(Args... args) && {
74+
while (cleanups.size()) {
75+
auto val = cleanups.pop_back_val();
76+
if (val) {
77+
val->func(std::forward<Args>(args)...);
78+
}
79+
}
80+
didPop = true;
81+
}
82+
83+
/// Can not be called once the scope is moved.
84+
void nonDestructivePop(Args... args) const {
85+
for (auto val : llvm::reverse(cleanups)) {
86+
if (val) {
87+
val->func(std::forward<Args>(args)...);
88+
}
89+
}
90+
}
91+
};
92+
93+
class ScopedValue;
94+
class SILOptScope;
95+
96+
/// Part of the SILRAIIScope interface that is known to ScopedValue.
97+
class SILOptScopeBase : public AssertingScope<void, SILInstruction *> {
98+
protected:
99+
SILOptScopeBase() {}
100+
101+
public:
102+
using Index = unsigned;
103+
104+
/// Destructive pop at end of scope.
105+
void popAtEndOfScope(SILInstruction *insertPt) && {
106+
std::move(*this).pop(insertPt);
107+
}
108+
109+
/// Non-destructive pop along a control flow path that is serving as an early
110+
/// exit.
111+
void popAtEarlyExit(SILInstruction *insertPt) const {
112+
nonDestructivePop(insertPt);
113+
}
114+
115+
private:
116+
friend class ScopedValue;
117+
118+
/// Invalidate the passed in scoped value destructively.
119+
void invalidateValue(ScopedValue &&value);
120+
};
121+
122+
/// A light weight wrapper around a SILValue that also contains information
123+
/// about a Cleanup closure that ends the lifetime of the given value.
124+
///
125+
/// In order to make it really easy to access the underlying value, we define
126+
/// operator * and operator-> as returning the underlying SILValue. It also
127+
/// defines explicit operator bool() so one should be able to use if statements
128+
/// to perform conditional initialization by returning an empty ScopedValue().
129+
///
130+
/// If one realizes that one does not actually need to cleanup a value, one can
131+
/// destructively forward the ScopedValue by writing:
132+
///
133+
/// std::move(scopedValue).forward();
134+
///
135+
/// This will cause the cleanup in the SILOptScopeBase to be invalidated and
136+
/// thus will not be emitted when the scope is popped.
137+
class ScopedValue {
138+
friend class SILOptScopeBase;
139+
friend class SILOptScope;
140+
141+
/// A back pointer to the scope we are managed by so that we can invalidate
142+
/// the cleanup associated with us. Also used to invalidate the underlying
143+
/// ScopedValue by setting this to nullptr.
144+
SILOptScopeBase *scope;
145+
146+
/// The index in our parent scope object of our cleanup. This is used to
147+
/// enable invalidation.
148+
Optional<unsigned> scopeIndex;
149+
150+
/// The underlying SILValue that we are working with.
151+
SILValue value;
152+
153+
/// A scoped value that has a cleanup associated with it at the index \p
154+
/// scopeIndex within \p scope.
155+
ScopedValue(SILOptScopeBase &scope, SILValue value, unsigned scopeIndex)
156+
: scope(&scope), scopeIndex(scopeIndex), value(value) {}
157+
158+
/// A scoped value that does not have an associated cleanup in the scope.
159+
ScopedValue(SILOptScopeBase &scope, SILValue value)
160+
: scope(&scope), scopeIndex(None), value(value) {}
161+
162+
public:
163+
/// Default empty constructor. Should be used in combination with operator
164+
/// bool() to perform conditional initialization.
165+
ScopedValue() : scope(nullptr), scopeIndex(), value() {}
166+
167+
/// Explicit operator that says if this is a valid scoped Value.
168+
///
169+
/// Allows for conditional initialization via return value:
170+
///
171+
/// if (auto value = getScopedValue(...)) {
172+
/// ...
173+
/// }
174+
explicit operator bool() const { return isValid(); }
175+
176+
/// Check just for the validity of the value, this doesn't
177+
bool isValid() const { return scope && value; }
178+
179+
/// Returns true if this scoped value has a cleanup associated with it that
180+
/// can be invalidated.
181+
///
182+
/// Example: Result of SILOptScope::copyValue would return true, but result of
183+
/// a SILOptScope::borrowValue would return false since we do not allow for
184+
/// borrows to be cancelled.
185+
bool hasInvalidatableCleanup() const {
186+
assert(isValid());
187+
return bool(scopeIndex);
188+
}
189+
190+
/// Operator to access internal SILValue.
191+
SILValue operator*() const {
192+
assert(isValid());
193+
return value;
194+
}
195+
196+
/// Operator to call methods on internal SILValue.
197+
SILValue operator->() const {
198+
assert(isValid());
199+
return value;
200+
}
201+
202+
/// If this scope has a scoped index, invalidate the scoped value so that its
203+
/// cleanup is not emitted at end of scope.
204+
void invalidate() && {
205+
assert(isValid());
206+
// If we have an invalidated cleanup, do the invalidation.
207+
if (hasInvalidatableCleanup()) {
208+
scope->invalidateValue(std::move(*this));
209+
}
210+
// No matter what we do, make it so that if this object is used again, we
211+
// get an assert.
212+
scope = nullptr;
213+
scopeIndex.reset();
214+
}
215+
};
216+
217+
inline void SILOptScopeBase::invalidateValue(ScopedValue &&value) {
218+
if (value.scopeIndex)
219+
invalidateCleanup(*value.scopeIndex);
220+
}
221+
222+
/// A scope like stack data structure that one can use to manage cleanups for
223+
/// SILValues using light weight ScopedValue wrappers. This is done by using the
224+
/// copyValue/borrowValue methods. These copy/borrow the value and then push a
225+
/// destroy_value/end_borrow cleanup and return a ScopedValue containing that
226+
/// the user can then manipulate the value without needing to worry about the
227+
/// cleanup. If the user then decides a specific ScopedValue should be able to
228+
/// be cancelled, we allow for the ScopedValue to be invalidated
229+
class SILOptScope : public SILOptScopeBase {
230+
public:
231+
SILOptScope() {}
232+
233+
/// Create a destroy_value cleanup for \p value. Returns a scoped value for \p
234+
/// value with a live cleanup.
235+
ScopedValue pushDestroyValue(SILValue value) {
236+
// We need to capture value by value, not by address.
237+
auto func = std::function<void(SILInstruction *)>(
238+
[innerValue = value](SILInstruction *insertPt) {
239+
SILBuilderWithScope builder(insertPt);
240+
builder.emitDestroyValue(RegularLocation::getAutoGeneratedLocation(),
241+
innerValue);
242+
});
243+
return {*this, value, pushScope(func)};
244+
}
245+
246+
/// Copy \p value at \p insertPt and push a destroy_value cleanup. Return the
247+
/// new copied value as a ScopedValue with cleanup.
248+
ScopedValue copyValue(SILInstruction *insertPt, SILValue value) {
249+
SILValue copy = SILBuilderWithScope(insertPt).emitCopyValueOperation(
250+
insertPt->getLoc(), value);
251+
return pushDestroyValue(copy);
252+
}
253+
254+
ScopedValue pushEndBorrow(SILValue value) {
255+
// We need to capture value by value, not by address.
256+
auto func = std::function<void(SILInstruction *)>(
257+
[innerValue = value](SILInstruction *insertPt) {
258+
SILBuilderWithScope builder(insertPt);
259+
builder.emitEndBorrowOperation(
260+
RegularLocation::getAutoGeneratedLocation(), innerValue);
261+
});
262+
// We do not return the index of our end_borrow since an end_borrow should
263+
// never have its cleanup cancelled.
264+
pushScope(func);
265+
return {*this, value};
266+
}
267+
268+
/// Borrow \p value at \p insertPt and push an end_borrow cleanup. Return the
269+
/// new borrowed value as a ScopedValue without cleanup. The cleanup is not
270+
/// returned since end_borrows should not be cancellable.
271+
ScopedValue borrowValue(SILInstruction *insertPt, SILValue value) {
272+
if (!insertPt->getFunction()->hasOwnership() ||
273+
value.getOwnershipKind().isCompatibleWith(OwnershipKind::Guaranteed))
274+
return {};
275+
SILValue borrow = SILBuilderWithScope(insertPt).emitBeginBorrowOperation(
276+
insertPt->getLoc(), value);
277+
return pushEndBorrow(borrow);
278+
}
279+
};
280+
281+
} // namespace siloptimizer
282+
} // namespace swift
283+
284+
#endif

lib/SILOptimizer/Transforms/SILMem2Reg.cpp

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,31 @@
2121
#define DEBUG_TYPE "sil-mem2reg"
2222

2323
#include "swift/AST/DiagnosticsSIL.h"
24+
#include "swift/SIL/BasicBlockDatastructures.h"
2425
#include "swift/SIL/Dominance.h"
2526
#include "swift/SIL/Projection.h"
2627
#include "swift/SIL/SILBuilder.h"
2728
#include "swift/SIL/SILFunction.h"
2829
#include "swift/SIL/SILInstruction.h"
2930
#include "swift/SIL/SILModule.h"
3031
#include "swift/SIL/TypeLowering.h"
31-
#include "swift/SIL/BasicBlockDatastructures.h"
3232
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
3333
#include "swift/SILOptimizer/PassManager/Passes.h"
3434
#include "swift/SILOptimizer/PassManager/Transforms.h"
3535
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
3636
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
37+
#include "swift/SILOptimizer/Utils/OwnershipOptUtils.h"
38+
#include "swift/SILOptimizer/Utils/ScopeOptUtils.h"
3739
#include "llvm/ADT/DenseMap.h"
3840
#include "llvm/ADT/DenseSet.h"
3941
#include "llvm/ADT/STLExtras.h"
4042
#include "llvm/ADT/Statistic.h"
4143
#include "llvm/Support/Debug.h"
4244
#include <algorithm>
4345
#include <queue>
46+
4447
using namespace swift;
48+
using namespace swift::siloptimizer;
4549

4650
STATISTIC(NumAllocStackFound, "Number of AllocStack found");
4751
STATISTIC(NumAllocStackCaptured, "Number of AllocStack captured");
@@ -141,6 +145,7 @@ static void replaceLoad(LoadInst *li, SILValue newValue, AllocStackInst *asi,
141145
ProjectionPath projections(newValue->getType());
142146
SILValue op = li->getOperand();
143147
SILBuilderWithScope builder(li, ctx);
148+
SILOptScope scope;
144149

145150
while (op != asi) {
146151
assert(isa<UncheckedAddrCastInst>(op) || isa<StructElementAddrInst>(op) ||
@@ -150,29 +155,28 @@ static void replaceLoad(LoadInst *li, SILValue newValue, AllocStackInst *asi,
150155
op = inst->getOperand(0);
151156
}
152157

153-
SmallVector<SILValue, 4> borrowedVals;
154-
for (auto iter = projections.rbegin(); iter != projections.rend(); ++iter) {
155-
const Projection &projection = *iter;
156-
assert(projection.getKind() == ProjectionKind::BitwiseCast ||
157-
projection.getKind() == ProjectionKind::Struct ||
158-
projection.getKind() == ProjectionKind::Tuple);
158+
for (const auto &proj : llvm::reverse(projections)) {
159+
assert(proj.getKind() == ProjectionKind::BitwiseCast ||
160+
proj.getKind() == ProjectionKind::Struct ||
161+
proj.getKind() == ProjectionKind::Tuple);
159162

160163
// struct_extract and tuple_extract expect guaranteed operand ownership
161-
// non-trivial RunningVal is owned. Insert borrow operation to convert
162-
if (projection.getKind() == ProjectionKind::Struct ||
163-
projection.getKind() == ProjectionKind::Tuple) {
164-
SILValue opVal = builder.emitBeginBorrowOperation(li->getLoc(), newValue);
165-
if (opVal != newValue) {
166-
borrowedVals.push_back(opVal);
167-
newValue = opVal;
164+
// non-trivial RunningVal is owned. Insert borrow operation to convert them
165+
// to guaranteed!
166+
if (proj.getKind() == ProjectionKind::Struct ||
167+
proj.getKind() == ProjectionKind::Tuple) {
168+
if (auto opVal = scope.borrowValue(li, newValue)) {
169+
assert(*opVal != newValue &&
170+
"Valid value should be different from input value");
171+
newValue = *opVal;
168172
}
169173
}
170174
newValue =
171-
projection.createObjectProjection(builder, li->getLoc(), newValue)
172-
.get();
175+
proj.createObjectProjection(builder, li->getLoc(), newValue).get();
173176
}
174177

175178
op = li->getOperand();
179+
176180
// Replace users of the loaded value with `val`
177181
// If we have a load [copy], replace the users with copy_value of `val`
178182
if (li->getOwnershipQualifier() == LoadOwnershipQualifier::Copy) {
@@ -183,9 +187,8 @@ static void replaceLoad(LoadInst *li, SILValue newValue, AllocStackInst *asi,
183187
li->replaceAllUsesWith(newValue);
184188
}
185189

186-
for (auto borrowedVal : borrowedVals) {
187-
builder.emitEndBorrowOperation(li->getLoc(), borrowedVal);
188-
}
190+
// Pop the scope so that we emit cleanups.
191+
std::move(scope).popAtEndOfScope(&*builder.getInsertionPoint());
189192

190193
// Delete the load
191194
li->eraseFromParent();

0 commit comments

Comments
 (0)