Skip to content

Commit ba4f424

Browse files
committed
SILGen: Move error values into indirect error returns in proper order with cleanups.
There's an unfortunate layering difference in the cleanup order between address-only and loadable error values during `catch` pattern matching: for address-only values, the value is copied into a temporary stack slot, and the stack slot is cleaned up on exit from the pattern match, meaning the value must be moved into the error return slot on the "no catch" case before cleanups run. But if it's a loadable value, then we borrow it for the duration of the switch, and the borrow is released during cleanup on exit from the pattern match, so the value must be forwarded after running cleanups. The way the code is structured, it handles these cases properly when the convention of the function being emitted is in sync with the fundamental properties of the error type (when the error type is loadable and the error return is by value, or when the error type is address-only and the error return is indirect, in other words). But when a closure literal with a loadable error type is emitted in an argument context that expects a function with an indirect error return, we would try to forward the loadable error value into the error return slot while a borrow is still active on it, leading to verifier errors. Defer forwarding the value into memory until after cleanups are popped, fixing rdar://126576356. A tidier solution might be to always emit the function body to use a bbarg on the throw block to pass the error value from the body emission to the epilog when the type is loadable, deferring the move into memory to the epilog block. This would make the right behavior fall out of the existing implementation, but would require a bit more invasive changes (pretty much everywhere that checks IndirectErrorReturn would need to check a different-tracked AddressOnlyErrorType bit instead or in addition). This change is more localized.
1 parent 4077c75 commit ba4f424

File tree

4 files changed

+81
-7
lines changed

4 files changed

+81
-7
lines changed

lib/SILGen/Cleanup.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,16 +162,28 @@ bool CleanupManager::hasAnyActiveCleanups(CleanupsDepth from) {
162162
/// emitBranchAndCleanups - Emit a branch to the given jump destination,
163163
/// threading out through any cleanups we might need to run. This does not
164164
/// pop the cleanup stack.
165-
void CleanupManager::emitBranchAndCleanups(JumpDest dest, SILLocation branchLoc,
165+
void CleanupManager::emitBranchAndCleanups(JumpDest dest,
166+
SILLocation branchLoc,
167+
ArrayRef<SILValue> args,
168+
ForUnwind_t forUnwind) {
169+
emitCleanupsForBranch(dest, branchLoc, args, forUnwind);
170+
SGF.getBuilder().createBranch(branchLoc, dest.getBlock(), args);
171+
}
172+
173+
/// emitBranchAndCleanups - Emit the cleanups necessary before branching to
174+
/// the given jump destination. This does not pop the cleanup stack, nor does
175+
/// it emit the actual branch.
176+
void CleanupManager::emitCleanupsForBranch(JumpDest dest,
177+
SILLocation branchLoc,
166178
ArrayRef<SILValue> args,
167179
ForUnwind_t forUnwind) {
168180
SILGenBuilder &builder = SGF.getBuilder();
169181
assert(builder.hasValidInsertionPoint() && "Emitting branch in invalid spot");
170182
emitCleanups(dest.getDepth(), dest.getCleanupLocation(),
171183
forUnwind, /*popCleanups=*/false);
172-
builder.createBranch(branchLoc, dest.getBlock(), args);
173184
}
174185

186+
175187
void CleanupManager::emitCleanupsForReturn(CleanupLocation loc,
176188
ForUnwind_t forUnwind) {
177189
SILGenBuilder &builder = SGF.getBuilder();

lib/SILGen/Cleanup.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,17 @@ class LLVM_LIBRARY_VISIBILITY CleanupManager {
221221
ArrayRef<SILValue> args = {},
222222
ForUnwind_t forUnwind = NotForUnwind);
223223

224+
/// Emit a branch to the given jump destination,
225+
/// threading out through any cleanups we need to run. This does not pop the
226+
/// cleanup stack.
227+
///
228+
/// \param dest The destination scope and block.
229+
/// \param branchLoc The location of the branch instruction.
230+
/// \param args Arguments to pass to the destination block.
231+
void emitCleanupsForBranch(JumpDest dest, SILLocation branchLoc,
232+
ArrayRef<SILValue> args = {},
233+
ForUnwind_t forUnwind = NotForUnwind);
234+
224235
/// emitCleanupsForReturn - Emit the top-level cleanups needed prior to a
225236
/// return from the function.
226237
void emitCleanupsForReturn(CleanupLocation loc, ForUnwind_t forUnwind);

lib/SILGen/SILGenStmt.cpp

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1660,11 +1660,12 @@ void SILGenFunction::emitThrow(SILLocation loc, ManagedValue exnMV,
16601660
if (exn->getType().isAddress()) {
16611661
B.createCopyAddr(loc, exn, indirectErrorAddr,
16621662
IsTake, IsInitialization);
1663-
} else {
1664-
// An indirect error is written into the destination error address.
1665-
emitSemanticStore(loc, exn, indirectErrorAddr,
1666-
getTypeLowering(destErrorType), IsInitialization);
16671663
}
1664+
1665+
// If the error is represented as a value, then we should forward it into
1666+
// the indirect error return slot. We have to wait to do that until after
1667+
// we pop cleanups, though, since the value may have a borrow active in
1668+
// scope that won't be released until the cleanups pop.
16681669
} else if (!throwBB.getArguments().empty()) {
16691670
// Load if we need to.
16701671
if (exn->getType().isAddress()) {
@@ -1680,5 +1681,16 @@ void SILGenFunction::emitThrow(SILLocation loc, ManagedValue exnMV,
16801681
}
16811682

16821683
// Branch to the cleanup destination.
1683-
Cleanups.emitBranchAndCleanups(ThrowDest, loc, args, IsForUnwind);
1684+
Cleanups.emitCleanupsForBranch(ThrowDest, loc, args, IsForUnwind);
1685+
1686+
if (indirectErrorAddr && !exn->getType().isAddress()) {
1687+
// Forward the error value into the return slot now. This has to happen
1688+
// after emitting cleanups because the active scope may be borrowing the
1689+
// error value, and we can't forward ownership until those borrows are
1690+
// released.
1691+
emitSemanticStore(loc, exn, indirectErrorAddr,
1692+
getTypeLowering(destErrorType), IsInitialization);
1693+
}
1694+
1695+
getBuilder().createBranch(loc, ThrowDest.getBlock(), args);
16841696
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
// RUN: %target-swift-emit-silgen %s | %FileCheck %s
2+
func test2() throws { // Not OK
3+
// The literal closure below is in a generic error context, even though
4+
// it statically throws `any Error`, leading it to be emitted with an
5+
// indirect `any Error` out parameter.
6+
try call { () throws in
7+
// CHECK-LABEL: sil{{.*}} @{{.*}}5test2{{.*}}fU_
8+
do {
9+
try gorble()
10+
}
11+
// Make sure we stop borrowing the error value before forwarding it into
12+
// the error return slot.
13+
// CHECK: bb{{.*}}([[ERROR:%.*]] : @owned $any Error):
14+
// CHECK: [[ERROR_BORROW:%.*]] = begin_borrow [[ERROR]]
15+
// CHECK: bb{{[0-9]+}}:
16+
// CHECK: bb{{[0-9]+}}:
17+
// CHECK: end_borrow [[ERROR_BORROW]]
18+
// CHECK-NEXT: store [[ERROR]] to [init]
19+
// CHECK-NEXT: throw_addr
20+
21+
catch is E1 { /* ignore */ }
22+
}
23+
}
24+
25+
func call<E: Error, R>(
26+
_ body: () throws(E) -> R
27+
) throws(E) -> R {
28+
try body()
29+
}
30+
31+
struct E1: Error {}
32+
33+
func gorble() throws {}
34+
35+
func test1() throws { // OK
36+
try call { () throws in
37+
try gorble()
38+
}
39+
}

0 commit comments

Comments
 (0)