Skip to content

Commit 2f7c7e9

Browse files
committed
[move-only] Ensure that we treat captured escaping closure arguments as such even if the closure doesn't actually escape
Specifically, we already have the appropriate semantics for arguments captured by escaping closures but in certain cases allocbox to stack is able to prove that the closure doesn’t actually escape. This results in the capture being converted into a non-escaping SIL form. This then causes the move checker to emit the wrong kind of error. The solution is to create an early allocbox to stack that doesn’t promote move only types in boxes from heap -> stack if it is captured by an escaping closure but does everything else normally. Then once the move checking is completed, we run alloc box to stack an additional time to ensure that we keep the guarantee that heap -> stack is performed in those cases. rdar://108905586 (cherry picked from commit 224674c)
1 parent 65e6402 commit 2f7c7e9

11 files changed

+260
-301
lines changed

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ PASS(AccessMarkerElimination, "access-marker-elim",
112112
"Access Marker Elimination.")
113113
PASS(AddressLowering, "address-lowering",
114114
"SIL Address Lowering")
115+
PASS(EarlyAllocBoxToStack, "early-allocbox-to-stack",
116+
"Stack Promotion of Box Objects. Doesn't attempt to promote noncopyable "
117+
"types captured by escaping closures")
115118
PASS(AllocBoxToStack, "allocbox-to-stack",
116119
"Stack Promotion of Box Objects")
117120
IRGEN_PASS(AllocStackHoisting, "alloc-stack-hoisting",

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) {
125125
// This guarantees that stack-promotable boxes have [static] enforcement.
126126
P.addAccessEnforcementSelection();
127127

128-
P.addAllocBoxToStack();
128+
P.addEarlyAllocBoxToStack();
129129
P.addNoReturnFolding();
130130
addDefiniteInitialization(P);
131131

@@ -169,6 +169,11 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) {
169169
// End Ownership Optimizations
170170
//===---
171171

172+
// Now that we have finished checking noncopyable types, run later alloc box
173+
// to stack, so that we promote to the stack any heap boxes that are captured
174+
// by escaping closures where the closure does not actually escape.
175+
P.addAllocBoxToStack();
176+
172177
#ifndef NDEBUG
173178
// Add a verification pass to check our work when skipping
174179
// function bodies.

lib/SILOptimizer/Transforms/AllocBoxToStack.cpp

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "swift/AST/SemanticAttrs.h"
1717
#include "swift/Basic/BlotMapVector.h"
1818
#include "swift/Basic/GraphNodeWorklist.h"
19+
#include "swift/Basic/OptionSet.h"
1920
#include "swift/SIL/ApplySite.h"
2021
#include "swift/SIL/BasicBlockDatastructures.h"
2122
#include "swift/SIL/Dominance.h"
@@ -87,6 +88,18 @@ static bool useCaptured(Operand *UI) {
8788
return true;
8889
}
8990

91+
namespace {
92+
93+
enum class AllocBoxToStackFlags {
94+
InAppliedFunction = 0x1,
95+
CanPromoteNonCopyableCapturedByEscapingClosure = 0x2,
96+
HasMoveOnlyBox = 0x4,
97+
};
98+
99+
using AllocBoxToStackOptions = OptionSet<AllocBoxToStackFlags>;
100+
101+
} // namespace
102+
90103
//===----------------------------------------------------------------------===//
91104
// Liveness for alloc_box Promotion
92105
//===----------------------------------------------------------------------===//
@@ -301,7 +314,7 @@ static bool partialApplyEscapes(SILValue V, bool examineApply) {
301314
}
302315

303316
static SILInstruction *recursivelyFindBoxOperandsPromotableToAddress(
304-
SILValue Box, bool inAppliedFunction, SmallVectorImpl<Operand *> &,
317+
SILValue Box, AllocBoxToStackOptions Options, SmallVectorImpl<Operand *> &,
305318
SmallPtrSetImpl<SILFunction *> &, unsigned CurrentRecurDepth);
306319

307320
/// checkLocalApplyBody - Check the body of an apply's callee to see
@@ -311,7 +324,8 @@ static SILInstruction *recursivelyFindBoxOperandsPromotableToAddress(
311324
static bool checkLocalApplyBody(Operand *O,
312325
SmallVectorImpl<Operand *> &PromotedOperands,
313326
SmallPtrSetImpl<SILFunction *> &VisitedCallees,
314-
unsigned CurrentRecurDepth) {
327+
unsigned CurrentRecurDepth,
328+
AllocBoxToStackOptions Options) {
315329
SILFunction *F = ApplySite(O->getUser()).getReferencedFunctionOrNull();
316330
// If we cannot examine the function body, assume the worst.
317331
if (!F || F->empty())
@@ -323,10 +337,11 @@ static bool checkLocalApplyBody(Operand *O,
323337
if (!iter.second)
324338
return false;
325339

340+
Options |= {AllocBoxToStackFlags::InAppliedFunction};
341+
326342
auto calleeArg = F->getArgument(ApplySite(O->getUser()).getCalleeArgIndex(*O));
327343
auto res = !recursivelyFindBoxOperandsPromotableToAddress(
328-
calleeArg,
329-
/* inAppliedFunction = */ true, PromotedOperands, VisitedCallees,
344+
calleeArg, Options, PromotedOperands, VisitedCallees,
330345
CurrentRecurDepth + 1);
331346
return res;
332347
}
@@ -370,7 +385,7 @@ static bool isOptimizableApplySite(ApplySite Apply) {
370385
/// box is passed don't have any unexpected uses, `PromotedOperands` will be
371386
/// populated with the box arguments in DFS order.
372387
static SILInstruction *recursivelyFindBoxOperandsPromotableToAddress(
373-
SILValue Box, bool inAppliedFunction,
388+
SILValue Box, AllocBoxToStackOptions Options,
374389
SmallVectorImpl<Operand *> &PromotedOperands,
375390
SmallPtrSetImpl<SILFunction *> &VisitedCallees,
376391
unsigned CurrentRecurDepth = 0) {
@@ -394,7 +409,8 @@ static SILInstruction *recursivelyFindBoxOperandsPromotableToAddress(
394409
// Projections are fine as well.
395410
if (isa<StrongRetainInst>(User) || isa<StrongReleaseInst>(User) ||
396411
isa<ProjectBoxInst>(User) || isa<DestroyValueInst>(User) ||
397-
(!inAppliedFunction && isa<DeallocBoxInst>(User)) ||
412+
(!Options.contains(AllocBoxToStackFlags::InAppliedFunction) &&
413+
isa<DeallocBoxInst>(User)) ||
398414
isa<EndBorrowInst>(User))
399415
continue;
400416

@@ -413,8 +429,17 @@ static SILInstruction *recursivelyFindBoxOperandsPromotableToAddress(
413429
}
414430
switch (Apply.getKind()) {
415431
case ApplySiteKind::PartialApplyInst: {
432+
// If we have a noncopyable type in a box and we were passed in the
433+
// option that tells us that we should not promote any boxes that are
434+
// captured by an escaping closure (even if we can prove the closure
435+
// does not escape), treat the partial apply use as an escape.
436+
if (Box->getType().isBoxedNonCopyableType(Box->getFunction()) &&
437+
!Options.contains(
438+
AllocBoxToStackFlags::
439+
CanPromoteNonCopyableCapturedByEscapingClosure))
440+
break;
416441
if (checkLocalApplyBody(Op, LocalPromotedOperands, VisitedCallees,
417-
CurrentRecurDepth) &&
442+
CurrentRecurDepth, Options) &&
418443
!partialApplyEscapes(cast<PartialApplyInst>(User),
419444
/* examineApply = */ true)) {
420445
LocalPromotedOperands.push_back(Op);
@@ -427,7 +452,7 @@ static SILInstruction *recursivelyFindBoxOperandsPromotableToAddress(
427452
case ApplySiteKind::TryApplyInst:
428453
if (isOptimizableApplySite(Apply) &&
429454
checkLocalApplyBody(Op, LocalPromotedOperands, VisitedCallees,
430-
CurrentRecurDepth)) {
455+
CurrentRecurDepth, Options)) {
431456
LocalPromotedOperands.push_back(Op);
432457
continue;
433458
}
@@ -450,13 +475,13 @@ static InFlightDiagnostic diagnose(ASTContext &Context, SourceLoc loc,
450475

451476
/// canPromoteAllocBox - Can we promote this alloc_box to an alloc_stack?
452477
static bool canPromoteAllocBox(AllocBoxInst *ABI,
453-
SmallVectorImpl<Operand *> &PromotedOperands) {
478+
SmallVectorImpl<Operand *> &PromotedOperands,
479+
AllocBoxToStackOptions Options) {
454480
SmallPtrSet<SILFunction *, 8> VisitedCallees;
455481
// Scan all of the uses of the address of the box to see if any
456482
// disqualifies the box from being promoted to the stack.
457483
if (auto *User = recursivelyFindBoxOperandsPromotableToAddress(
458-
ABI,
459-
/* inAppliedFunction = */ false, PromotedOperands, VisitedCallees,
484+
ABI, Options, PromotedOperands, VisitedCallees,
460485
/* CurrentRecurDepth = */ 0)) {
461486
(void)User;
462487
// Otherwise, we have an unexpected use.
@@ -1221,6 +1246,8 @@ static unsigned rewritePromotedBoxes(AllocBoxToStackState &pass) {
12211246
}
12221247

12231248
namespace {
1249+
1250+
template <bool CanPromoteMoveOnlyCapturedByEscapingClosure>
12241251
class AllocBoxToStack : public SILFunctionTransform {
12251252
/// The entry point to the transformation.
12261253
void run() override {
@@ -1229,10 +1256,16 @@ class AllocBoxToStack : public SILFunctionTransform {
12291256
return;
12301257

12311258
AllocBoxToStackState pass(this);
1259+
1260+
AllocBoxToStackOptions Options;
1261+
if (CanPromoteMoveOnlyCapturedByEscapingClosure)
1262+
Options |=
1263+
AllocBoxToStackFlags::CanPromoteNonCopyableCapturedByEscapingClosure;
1264+
12321265
for (auto &BB : *getFunction()) {
12331266
for (auto &I : BB)
12341267
if (auto *ABI = dyn_cast<AllocBoxInst>(&I))
1235-
if (canPromoteAllocBox(ABI, pass.PromotedOperands))
1268+
if (canPromoteAllocBox(ABI, pass.PromotedOperands, Options))
12361269
pass.Promotable.push_back(ABI);
12371270
}
12381271

@@ -1251,8 +1284,13 @@ class AllocBoxToStack : public SILFunctionTransform {
12511284
}
12521285
}
12531286
};
1287+
12541288
} // end anonymous namespace
12551289

12561290
SILTransform *swift::createAllocBoxToStack() {
1257-
return new AllocBoxToStack();
1291+
return new AllocBoxToStack<true>();
1292+
}
1293+
1294+
SILTransform *swift::createEarlyAllocBoxToStack() {
1295+
return new AllocBoxToStack<false>();
12581296
}

test/SILGen/moveonly_escaping_closure.swift

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -125,15 +125,13 @@ func testGlobalClosureCaptureVar() {
125125
// CHECK: end_access [[READ_ACCESS]]
126126
// CHECK: } // end sil function '$s16moveonly_closure29testLocalLetClosureCaptureVaryyFyycfU_'
127127
func testLocalLetClosureCaptureVar() {
128-
var x = SingleElt() // expected-error {{'x' was consumed but it is illegal to consume a noncopyable mutable capture of an escaping closure. One can only read from it or assign over it}}
129-
// expected-error @-1 {{'x' was consumed but it is illegal to consume a noncopyable mutable capture of an escaping closure. One can only read from it or assign over it}}
130-
// expected-error @-2 {{'x' was consumed but it is illegal to consume a noncopyable mutable capture of an escaping closure. One can only read from it or assign over it}}
128+
var x = SingleElt()
131129
x = SingleElt()
132130
let f = {
133131
borrowVal(x)
134-
consumeVal(x) // expected-note {{consuming use here}}
135-
consumeVal(x) // expected-note {{consuming use here}}
136-
borrowConsumeVal(x, x) // expected-note {{consuming use here}}
132+
consumeVal(x) // expected-error {{'x' was consumed but it is illegal to consume a noncopyable mutable capture of an escaping closure. One can only read from it or assign over it}}
133+
consumeVal(x) // expected-error {{'x' was consumed but it is illegal to consume a noncopyable mutable capture of an escaping closure. One can only read from it or assign over it}}
134+
borrowConsumeVal(x, x) // expected-error {{'x' was consumed but it is illegal to consume a noncopyable mutable capture of an escaping closure. One can only read from it or assign over it}}
137135
// expected-error @-1 {{overlapping accesses, but deinitialization requires exclusive access}}
138136
// expected-note @-2 {{conflicting access is here}}
139137
}
@@ -476,14 +474,11 @@ func testGlobalClosureCaptureLet() {
476474
// CHECK: } // end sil function '$s16moveonly_closure026testLocalLetClosureCaptureE0yyFyycfU_'
477475
func testLocalLetClosureCaptureLet() {
478476
let x = SingleElt()
479-
// expected-error @-1 {{'x' was consumed but it is illegal to consume a noncopyable immutable capture of an escaping closure. One can only read from it}}
480-
// expected-error @-2 {{'x' was consumed but it is illegal to consume a noncopyable immutable capture of an escaping closure. One can only read from it}}
481-
// expected-error @-3 {{'x' was consumed but it is illegal to consume a noncopyable immutable capture of an escaping closure. One can only read from it}}
482477
let f = {
483478
borrowVal(x)
484-
consumeVal(x) // expected-note {{consuming use here}}
485-
consumeVal(x) // expected-note {{consuming use here}}
486-
borrowConsumeVal(x, x) // expected-note {{consuming use here}}
479+
consumeVal(x) // expected-error {{'x' was consumed but it is illegal to consume a noncopyable immutable capture of an escaping closure. One can only read from it}}
480+
consumeVal(x) // expected-error {{'x' was consumed but it is illegal to consume a noncopyable immutable capture of an escaping closure. One can only read from it}}
481+
borrowConsumeVal(x, x) // expected-error {{'x' was consumed but it is illegal to consume a noncopyable immutable capture of an escaping closure. One can only read from it}}
487482
}
488483
f()
489484
}
@@ -1090,14 +1085,11 @@ func testGlobalClosureCaptureConsuming(_ x: consuming SingleElt) {
10901085
// CHECK: end_access [[READ_ACCESS]]
10911086
// CHECK: } // end sil function '$s16moveonly_closure35testLocalLetClosureCaptureConsumingyyAA9SingleEltVnFyycfU_'
10921087
func testLocalLetClosureCaptureConsuming(_ x: consuming SingleElt) {
1093-
// expected-error @-1 {{'x' was consumed but it is illegal to consume a noncopyable mutable capture of an escaping closure. One can only read from it or assign over it}}
1094-
// expected-error @-2 {{'x' was consumed but it is illegal to consume a noncopyable mutable capture of an escaping closure. One can only read from it or assign over it}}
1095-
// expected-error @-3 {{'x' was consumed but it is illegal to consume a noncopyable mutable capture of an escaping closure. One can only read from it or assign over it}}
10961088
let f = {
10971089
borrowVal(x)
1098-
consumeVal(x) // expected-note {{consuming use here}}
1099-
consumeVal(x) // expected-note {{consuming use here}}
1100-
borrowConsumeVal(x, x) // expected-note {{consuming use here}}
1090+
consumeVal(x) // expected-error {{'x' was consumed but it is illegal to consume a noncopyable mutable capture of an escaping closure. One can only read from it or assign over it}}
1091+
consumeVal(x) // expected-error {{'x' was consumed but it is illegal to consume a noncopyable mutable capture of an escaping closure. One can only read from it or assign over it}}
1092+
borrowConsumeVal(x, x) // expected-error {{'x' was consumed but it is illegal to consume a noncopyable mutable capture of an escaping closure. One can only read from it or assign over it}}
11011093
// expected-error @-1 {{overlapping accesses, but deinitialization requires exclusive access}}
11021094
// expected-note @-2 {{conflicting access is here}}
11031095
}
@@ -1373,14 +1365,11 @@ func testGlobalClosureCaptureOwned(_ x: __owned SingleElt) {
13731365
// CHECK: apply {{%.*}}([[LOADED_READ]], [[LOADED_TAKE]])
13741366
// CHECK: } // end sil function '$s16moveonly_closure31testLocalLetClosureCaptureOwnedyyAA9SingleEltVnFyycfU_'
13751367
func testLocalLetClosureCaptureOwned(_ x: __owned SingleElt) {
1376-
// expected-error @-1 {{'x' was consumed but it is illegal to consume a noncopyable immutable capture of an escaping closure. One can only read from it}}
1377-
// expected-error @-2 {{'x' was consumed but it is illegal to consume a noncopyable immutable capture of an escaping closure. One can only read from it}}
1378-
// expected-error @-3 {{'x' was consumed but it is illegal to consume a noncopyable immutable capture of an escaping closure. One can only read from it}}
13791368
let f = {
13801369
borrowVal(x)
1381-
consumeVal(x) // expected-note {{consuming use here}}
1382-
consumeVal(x) // expected-note {{consuming use here}}
1383-
borrowConsumeVal(x, x) // expected-note {{consuming use here}}
1370+
consumeVal(x) // expected-error {{'x' was consumed but it is illegal to consume a noncopyable immutable capture of an escaping closure. One can only read from it}}
1371+
consumeVal(x) // expected-error {{'x' was consumed but it is illegal to consume a noncopyable immutable capture of an escaping closure. One can only read from it}}
1372+
borrowConsumeVal(x, x) // expected-error {{'x' was consumed but it is illegal to consume a noncopyable immutable capture of an escaping closure. One can only read from it}}
13841373
}
13851374
f()
13861375
}

test/SILOptimizer/allocbox_to_stack_noncopyable.sil

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// RUN: %target-sil-opt -enable-sil-verify-all %s -allocbox-to-stack -enable-copy-propagation=requested-passes-only | %FileCheck %s
2+
// RUN: %target-sil-opt -enable-sil-verify-all %s -early-allocbox-to-stack -enable-copy-propagation=requested-passes-only | %FileCheck -check-prefix=EARLY %s
23

34
sil_stage raw
45

@@ -160,3 +161,43 @@ bb3:
160161
%30 = tuple ()
161162
return %30 : $()
162163
}
164+
165+
// Make sure that if our box is captured by a partial_apply, we do not process
166+
// it with the early allocbox to stack, but we do with the normal allocbox to
167+
// stack.
168+
// EARLY: sil hidden [ossa] @earlyallocbox_to_stack_partial_apply_test_caller : $@convention(thin) (@owned NonTrivialStruct) -> () {
169+
// EARLY: alloc_box ${ var NonTrivialStruct }, var, name "x"
170+
// EARLY: } // end sil function 'earlyallocbox_to_stack_partial_apply_test_caller'
171+
//
172+
// CHECK: sil hidden [ossa] @earlyallocbox_to_stack_partial_apply_test_caller : $@convention(thin) (@owned NonTrivialStruct) -> () {
173+
// CHECK: alloc_stack [lexical] $NonTrivialStruct, var, name "x"
174+
// CHECK: } // end sil function 'earlyallocbox_to_stack_partial_apply_test_caller'
175+
sil hidden [ossa] @earlyallocbox_to_stack_partial_apply_test_caller : $@convention(thin) (@owned NonTrivialStruct) -> () {
176+
bb0(%arg : @owned $NonTrivialStruct):
177+
%0 = alloc_box ${ var NonTrivialStruct }, var, name "x"
178+
%1 = begin_borrow [lexical] %0 : ${ var NonTrivialStruct }
179+
%2 = project_box %1 : ${ var NonTrivialStruct }, 0
180+
store %arg to [init] %2 : $*NonTrivialStruct
181+
%15 = function_ref @earlyallocbox_to_stack_partial_apply_test_callee : $@convention(thin) (@guaranteed { var NonTrivialStruct }) -> ()
182+
%16 = copy_value %1 : ${ var NonTrivialStruct }
183+
%18 = partial_apply [callee_guaranteed] %15(%16) : $@convention(thin) (@guaranteed { var NonTrivialStruct }) -> ()
184+
destroy_value %18 : $@callee_guaranteed () -> ()
185+
end_borrow %1 : ${ var NonTrivialStruct }
186+
destroy_value %0 : ${ var NonTrivialStruct }
187+
%24 = tuple ()
188+
return %24 : $()
189+
}
190+
191+
sil private [ossa] @earlyallocbox_to_stack_partial_apply_test_callee : $@convention(thin) (@guaranteed { var NonTrivialStruct }) -> () {
192+
bb0(%0 : @closureCapture @guaranteed ${ var NonTrivialStruct }):
193+
%1 = project_box %0 : ${ var NonTrivialStruct }, 0
194+
debug_value %1 : $*NonTrivialStruct, var, name "x", argno 1, expr op_deref
195+
%3 = begin_access [read] [unknown] %1 : $*NonTrivialStruct
196+
%4 = mark_must_check [no_consume_or_assign] %3 : $*NonTrivialStruct
197+
%5 = load [copy] %4 : $*NonTrivialStruct
198+
end_access %3 : $*NonTrivialStruct
199+
%7 = move_value %5 : $NonTrivialStruct
200+
destroy_value %7 : $NonTrivialStruct
201+
%9 = tuple ()
202+
return %9 : $()
203+
}

test/SILOptimizer/allocbox_to_stack_ownership.sil

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// RUN: %target-sil-opt -enable-sil-verify-all %s -allocbox-to-stack -enable-copy-propagation=requested-passes-only -enable-lexical-borrow-scopes=false | %FileCheck %s
2+
// RUN: %target-sil-opt -enable-sil-verify-all %s -early-allocbox-to-stack -enable-copy-propagation=requested-passes-only -enable-lexical-borrow-scopes=false | %FileCheck %s
23

34
sil_stage raw
45

test/SILOptimizer/allocboxtostack_escape.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ class Klass {}
99
func myPrint(_ x: inout Box<Klass>) -> () { print(x) }
1010

1111
func testError() -> (() -> ()) {
12+
// We emit this error twice since we run allocbox to stack twice in the pipeline for now. This is not an official feature, so it is ok for now.
1213
@_semantics("boxtostack.mustbeonstack")
1314
var x = Box<Klass>(value: Klass()) // expected-error {{Can not promote value from heap to stack due to value escaping}}
15+
// expected-error @-1 {{Can not promote value from heap to stack due to value escaping}}
1416
let result = { // expected-note {{value escapes here}}
17+
// expected-note @-1 {{value escapes here}}
1518
myPrint(&x)
1619
}
1720
return result

0 commit comments

Comments
 (0)