Skip to content

Commit bb4df03

Browse files
committed
[ownership] Add a new class BorrowScopeIntroducingValue that enables code to work abstractly with values that introduce a new borrow scope.
The idea is that this can be used to work with things like load_borrow, begin_borrow, SILFunctionArgument, results of begin_apply(in the future) and the like in a generic way using exhaustive switches to make sure this code stays up to date. I refactored code in SemanticARCOpts and some utilities in OwnershipUtils.cpp to use these new APIs. The code looks a lot nicer and should be quite easy to expand to handle new borrow introducers (e.x.: end_apply).
1 parent 94c7d04 commit bb4df03

File tree

3 files changed

+283
-76
lines changed

3 files changed

+283
-76
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 146 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#define SWIFT_SIL_OWNERSHIPUTILS_H
1515

1616
#include "swift/Basic/LLVM.h"
17+
#include "swift/SIL/BranchPropagatedUser.h"
1718
#include "swift/SIL/SILArgument.h"
1819
#include "swift/SIL/SILInstruction.h"
1920
#include "swift/SIL/SILValue.h"
@@ -164,10 +165,152 @@ bool isOwnershipForwardingInst(SILInstruction *i);
164165

165166
bool isGuaranteedForwardingInst(SILInstruction *i);
166167

168+
struct BorrowScopeIntroducerKind {
169+
using UnderlyingKindTy = std::underlying_type<ValueKind>::type;
170+
171+
/// Enum we use for exhaustive pattern matching over borrow scope introducers.
172+
enum Kind : UnderlyingKindTy {
173+
LoadBorrow = UnderlyingKindTy(ValueKind::LoadBorrowInst),
174+
BeginBorrow = UnderlyingKindTy(ValueKind::BeginBorrowInst),
175+
SILFunctionArgument = UnderlyingKindTy(ValueKind::SILFunctionArgument),
176+
};
177+
178+
static Optional<BorrowScopeIntroducerKind> get(ValueKind kind) {
179+
switch (kind) {
180+
default:
181+
return None;
182+
case ValueKind::LoadBorrowInst:
183+
return BorrowScopeIntroducerKind(LoadBorrow);
184+
case ValueKind::BeginBorrowInst:
185+
return BorrowScopeIntroducerKind(BeginBorrow);
186+
case ValueKind::SILFunctionArgument:
187+
return BorrowScopeIntroducerKind(SILFunctionArgument);
188+
}
189+
}
190+
191+
Kind value;
192+
193+
BorrowScopeIntroducerKind(Kind newValue) : value(newValue) {}
194+
BorrowScopeIntroducerKind(const BorrowScopeIntroducerKind &other)
195+
: value(other.value) {}
196+
operator Kind() const { return value; }
197+
198+
/// Is this a borrow scope that begins and ends within the same function and
199+
/// thus is guaranteed to have an "end_scope" instruction.
200+
///
201+
/// In contrast, borrow scopes that are non-local (e.x. from
202+
/// SILFunctionArguments) rely a construct like a SILFunction as the begin/end
203+
/// of the scope.
204+
bool isLocalScope() const {
205+
switch (value) {
206+
case BorrowScopeIntroducerKind::BeginBorrow:
207+
case BorrowScopeIntroducerKind::LoadBorrow:
208+
return true;
209+
case BorrowScopeIntroducerKind::SILFunctionArgument:
210+
return false;
211+
}
212+
llvm_unreachable("Covered switch isnt covered?!");
213+
}
214+
215+
void print(llvm::raw_ostream &os) const;
216+
LLVM_ATTRIBUTE_DEPRECATED(void dump() const, "only for use in the debugger");
217+
};
218+
219+
llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
220+
BorrowScopeIntroducerKind kind);
221+
222+
/// A higher level construct for working with values that represent the
223+
/// introduction of a new borrow scope.
224+
///
225+
/// DISCUSSION: A "borrow introducer" is a SILValue that represents the
226+
/// beginning of a borrow scope that the ownership verifier validates. The idea
227+
/// is this API allows one to work in a generic way with all of the various
228+
/// introducers.
229+
///
230+
/// Some examples of borrow introducers: guaranteed SILFunctionArgument,
231+
/// LoadBorrow, BeginBorrow, guaranteed BeginApply results.
232+
///
233+
/// NOTE: It is assumed that if a borrow introducer is a value of a
234+
/// SILInstruction with multiple results, that the all of the SILInstruction's
235+
/// guaranteed results are borrow introducers. In practice this means that
236+
/// borrow introducers can not have guaranteed results that are not creating a
237+
/// new borrow scope. No such instructions exist today.
238+
struct BorrowScopeIntroducingValue {
239+
BorrowScopeIntroducerKind kind;
240+
SILValue value;
241+
242+
BorrowScopeIntroducingValue(LoadBorrowInst *lbi)
243+
: kind(BorrowScopeIntroducerKind::LoadBorrow), value(lbi) {}
244+
BorrowScopeIntroducingValue(BeginBorrowInst *bbi)
245+
: kind(BorrowScopeIntroducerKind::BeginBorrow), value(bbi) {}
246+
BorrowScopeIntroducingValue(SILFunctionArgument *arg)
247+
: kind(BorrowScopeIntroducerKind::SILFunctionArgument), value(arg) {
248+
assert(arg->getOwnershipKind() == ValueOwnershipKind::Guaranteed);
249+
}
250+
251+
BorrowScopeIntroducingValue(SILValue v)
252+
: kind(*BorrowScopeIntroducerKind::get(v->getKind())), value(v) {
253+
assert(v.getOwnershipKind() == ValueOwnershipKind::Guaranteed);
254+
}
255+
256+
/// If value is a borrow introducer return it after doing some checks.
257+
static Optional<BorrowScopeIntroducingValue> get(SILValue value) {
258+
auto kind = BorrowScopeIntroducerKind::get(value->getKind());
259+
if (!kind || value.getOwnershipKind() != ValueOwnershipKind::Guaranteed)
260+
return None;
261+
return BorrowScopeIntroducingValue(*kind, value);
262+
}
263+
264+
/// If this value is introducing a local scope, gather all local end scope
265+
/// instructions and append them to \p scopeEndingInsts. Asserts if this is
266+
/// called with a scope that is not local.
267+
///
268+
/// NOTE: To determine if a scope is a local scope, call
269+
/// BorrowScopeIntoducingValue::isLocalScope().
270+
void getLocalScopeEndingInstructions(
271+
SmallVectorImpl<SILInstruction *> &scopeEndingInsts) const;
272+
273+
/// If this value is introducing a local scope, gather all local end scope
274+
/// instructions and pass them individually to visitor. Asserts if this is
275+
/// called with a scope that is not local.
276+
///
277+
/// The intention is that this method can be used instead of
278+
/// BorrowScopeIntroducingValue::getLocalScopeEndingInstructions() to avoid
279+
/// introducing an intermediate array when one needs to transform the
280+
/// instructions before storing them.
281+
///
282+
/// NOTE: To determine if a scope is a local scope, call
283+
/// BorrowScopeIntoducingValue::isLocalScope().
284+
void visitLocalScopeEndingInstructions(
285+
function_ref<void(SILInstruction *)> visitor) const;
286+
287+
bool isLocalScope() const { return kind.isLocalScope(); }
288+
289+
/// Returns true if the passed in set of instructions is completely within the
290+
/// lifetime of this borrow introducer.
291+
///
292+
/// NOTE: Scratch space is used internally to this method to store the end
293+
/// borrow scopes if needed.
294+
bool areInstructionsWithinScope(
295+
ArrayRef<BranchPropagatedUser> instructions,
296+
SmallVectorImpl<BranchPropagatedUser> &scratchSpace,
297+
SmallPtrSetImpl<SILBasicBlock *> &visitedBlocks,
298+
DeadEndBlocks &deadEndBlocks) const;
299+
300+
private:
301+
/// Internal constructor for failable static constructor. Please do not expand
302+
/// its usage since it assumes the code passed in is well formed.
303+
BorrowScopeIntroducingValue(BorrowScopeIntroducerKind kind, SILValue value)
304+
: kind(kind), value(value) {}
305+
};
306+
167307
/// Look up through the def-use chain of \p inputValue, recording any "borrow"
168-
/// introducers that we find into \p out.
169-
bool getUnderlyingBorrowIntroducers(SILValue inputValue,
170-
SmallVectorImpl<SILValue> &out);
308+
/// introducing values that we find into \p out. If at any point, we find a
309+
/// point in the chain we do not understand, we bail and return false. If we are
310+
/// able to understand all of the def-use graph, we know that we have found all
311+
/// of the borrow introducing values, we return true.
312+
bool getUnderlyingBorrowIntroducingValues(
313+
SILValue inputValue, SmallVectorImpl<BorrowScopeIntroducingValue> &out);
171314

172315
} // namespace swift
173316

lib/SIL/OwnershipUtils.cpp

Lines changed: 104 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "swift/SIL/OwnershipUtils.h"
14+
#include "swift/Basic/Defer.h"
1415
#include "swift/SIL/SILArgument.h"
1516
#include "swift/SIL/SILInstruction.h"
1617

@@ -76,8 +77,72 @@ bool swift::isOwnershipForwardingInst(SILInstruction *i) {
7677
return isOwnershipForwardingValueKind(SILNodeKind(i->getKind()));
7778
}
7879

79-
bool swift::getUnderlyingBorrowIntroducers(SILValue inputValue,
80-
SmallVectorImpl<SILValue> &out) {
80+
//===----------------------------------------------------------------------===//
81+
// Borrow Introducers
82+
//===----------------------------------------------------------------------===//
83+
84+
void BorrowScopeIntroducerKind::print(llvm::raw_ostream &os) const {
85+
switch (value) {
86+
case BorrowScopeIntroducerKind::SILFunctionArgument:
87+
os << "SILFunctionArgument";
88+
return;
89+
case BorrowScopeIntroducerKind::BeginBorrow:
90+
os << "BeginBorrowInst";
91+
return;
92+
case BorrowScopeIntroducerKind::LoadBorrow:
93+
os << "LoadBorrowInst";
94+
return;
95+
}
96+
llvm_unreachable("Covered switch isn't covered?!");
97+
}
98+
99+
void BorrowScopeIntroducerKind::dump() const {
100+
#ifndef NDEBUG
101+
print(llvm::dbgs());
102+
#endif
103+
}
104+
105+
void BorrowScopeIntroducingValue::getLocalScopeEndingInstructions(
106+
SmallVectorImpl<SILInstruction *> &scopeEndingInsts) const {
107+
assert(isLocalScope() && "Should only call this given a local scope");
108+
109+
switch (kind) {
110+
case BorrowScopeIntroducerKind::SILFunctionArgument:
111+
llvm_unreachable("Should only call this with a local scope");
112+
case BorrowScopeIntroducerKind::BeginBorrow:
113+
llvm::copy(cast<BeginBorrowInst>(value)->getEndBorrows(),
114+
std::back_inserter(scopeEndingInsts));
115+
return;
116+
case BorrowScopeIntroducerKind::LoadBorrow:
117+
llvm::copy(cast<LoadBorrowInst>(value)->getEndBorrows(),
118+
std::back_inserter(scopeEndingInsts));
119+
return;
120+
}
121+
llvm_unreachable("Covered switch isn't covered?!");
122+
}
123+
124+
void BorrowScopeIntroducingValue::visitLocalScopeEndingInstructions(
125+
function_ref<void(SILInstruction *)> visitor) const {
126+
assert(isLocalScope() && "Should only call this given a local scope");
127+
switch (kind) {
128+
case BorrowScopeIntroducerKind::SILFunctionArgument:
129+
llvm_unreachable("Should only call this with a local scope");
130+
case BorrowScopeIntroducerKind::BeginBorrow:
131+
for (auto *inst : cast<BeginBorrowInst>(value)->getEndBorrows()) {
132+
visitor(inst);
133+
}
134+
return;
135+
case BorrowScopeIntroducerKind::LoadBorrow:
136+
for (auto *inst : cast<LoadBorrowInst>(value)->getEndBorrows()) {
137+
visitor(inst);
138+
}
139+
return;
140+
}
141+
llvm_unreachable("Covered switch isn't covered?!");
142+
}
143+
144+
bool swift::getUnderlyingBorrowIntroducingValues(
145+
SILValue inputValue, SmallVectorImpl<BorrowScopeIntroducingValue> &out) {
81146
if (inputValue.getOwnershipKind() != ValueOwnershipKind::Guaranteed)
82147
return false;
83148

@@ -88,25 +153,11 @@ bool swift::getUnderlyingBorrowIntroducers(SILValue inputValue,
88153
SILValue v = worklist.pop_back_val();
89154

90155
// First check if v is an introducer. If so, stash it and continue.
91-
if (isa<LoadBorrowInst>(v) ||
92-
isa<BeginBorrowInst>(v)) {
93-
out.push_back(v);
156+
if (auto scopeIntroducer = BorrowScopeIntroducingValue::get(v)) {
157+
out.push_back(*scopeIntroducer);
94158
continue;
95159
}
96160

97-
// If we have a function argument with guaranteed convention, it is also an
98-
// introducer.
99-
if (auto *arg = dyn_cast<SILFunctionArgument>(v)) {
100-
if (arg->getOwnershipKind() == ValueOwnershipKind::Guaranteed) {
101-
out.push_back(v);
102-
continue;
103-
}
104-
105-
// Otherwise, we do not know how to handle this function argument, so
106-
// bail.
107-
return false;
108-
}
109-
110161
// Otherwise if v is an ownership forwarding value, add its defining
111162
// instruction
112163
if (isGuaranteedForwardingValue(v)) {
@@ -125,3 +176,38 @@ bool swift::getUnderlyingBorrowIntroducers(SILValue inputValue,
125176

126177
return true;
127178
}
179+
180+
llvm::raw_ostream &swift::operator<<(llvm::raw_ostream &os,
181+
BorrowScopeIntroducerKind kind) {
182+
kind.print(os);
183+
return os;
184+
}
185+
186+
bool BorrowScopeIntroducingValue::areInstructionsWithinScope(
187+
ArrayRef<BranchPropagatedUser> instructions,
188+
SmallVectorImpl<BranchPropagatedUser> &scratchSpace,
189+
SmallPtrSetImpl<SILBasicBlock *> &visitedBlocks,
190+
DeadEndBlocks &deadEndBlocks) const {
191+
// Make sure that we clear our scratch space/utilities before we exit.
192+
SWIFT_DEFER {
193+
scratchSpace.clear();
194+
visitedBlocks.clear();
195+
};
196+
197+
// First make sure that we actually have a local scope. If we have a non-local
198+
// scope, then we have something (like a SILFunctionArgument) where a larger
199+
// semantic construct (in the case of SILFunctionArgument, the function
200+
// itself) acts as the scope. So we already know that our passed in
201+
// instructions must be in the same scope.
202+
if (!isLocalScope())
203+
return true;
204+
205+
// Otherwise, gather up our local scope ending instructions.
206+
visitLocalScopeEndingInstructions(
207+
[&scratchSpace](SILInstruction *i) { scratchSpace.emplace_back(i); });
208+
209+
auto result = valueHasLinearLifetime(
210+
value, scratchSpace, instructions, visitedBlocks, deadEndBlocks,
211+
ownership::ErrorBehaviorKind::ReturnFalse);
212+
return !result.getFoundError();
213+
}

0 commit comments

Comments
 (0)