Skip to content

Commit c0e31d8

Browse files
committed
[sil-mem2reg] Add a simple scope data structure and use it to simplify some code.
We have for a long time talked about creating a scope like data structure for use in the SILOptimizer. The discussion was whether or not to reuse the infrastructure in SILGen that does this already. There were concerns about doing so since the code in the SILOptimizer and SILGen can work differently. With that in mind, I added a small AssertingScope class and built on top of that a composition SIL level class called SILOptScope that one can use to add various cleanups. One is able to both destructively pop at end of scope and pop along early exits. At an implementation level, I kept it simple and: 1. Represented a scope as a stack of Optional<Cleanup> which are just a wrapper around a std::function. The Optional is so that we can invalidate a cleanup. 2. Based all of these scopes around the idea that the user of the scope must invalidate the scope by hand. If not, the scope object will assert at the end of its RAII scope. 3. Rather than creating a whole class hierarchy, I just used std::function closures to keep things simple.
1 parent 90354d9 commit c0e31d8

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)