Skip to content

Commit d7ae2e9

Browse files
authored
Merge pull request #65712 from gottesmm/release/5.9/more_closure_stuff
[5.9][move-only] More batched closure changes
2 parents e4f2412 + 018f56f commit d7ae2e9

13 files changed

+325
-329
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/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1526,6 +1526,13 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
15261526

15271527
if (markedValue->getCheckKind() ==
15281528
MarkMustCheckInst::CheckKind::NoConsumeOrAssign) {
1529+
if (isa<ProjectBoxInst>(stripAccessMarkers(markedValue->getOperand()))) {
1530+
LLVM_DEBUG(llvm::dbgs()
1531+
<< "Found mark must check [nocopy] use of escaping box: " << *user);
1532+
diagnosticEmitter.emitAddressEscapingClosureCaptureLoadedAndConsumed(markedValue);
1533+
emittedEarlyDiagnostic = true;
1534+
return true;
1535+
}
15291536
LLVM_DEBUG(llvm::dbgs()
15301537
<< "Found mark must check [nocopy] error: " << *user);
15311538
diagnosticEmitter.emitAddressDiagnosticNoCopy(markedValue, copyAddr);

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
}
@@ -411,14 +409,11 @@ func testGlobalClosureCaptureLet() {
411409
// CHECK: } // end sil function '$s16moveonly_closure026testLocalLetClosureCaptureE0yyFyycfU_'
412410
func testLocalLetClosureCaptureLet() {
413411
let x = SingleElt()
414-
// 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}}
415-
// 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}}
416-
// 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}}
417412
let f = {
418413
borrowVal(x)
419-
consumeVal(x) // expected-note {{consuming use here}}
420-
consumeVal(x) // expected-note {{consuming use here}}
421-
borrowConsumeVal(x, x) // expected-note {{consuming use here}}
414+
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}}
415+
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}}
416+
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}}
422417
}
423418
f()
424419
}
@@ -935,14 +930,11 @@ func testGlobalClosureCaptureConsuming(_ x: consuming SingleElt) {
935930
// CHECK: end_access [[READ_ACCESS]]
936931
// CHECK: } // end sil function '$s16moveonly_closure35testLocalLetClosureCaptureConsumingyyAA9SingleEltVnFyycfU_'
937932
func testLocalLetClosureCaptureConsuming(_ x: consuming SingleElt) {
938-
// 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}}
939-
// 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}}
940-
// 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}}
941933
let f = {
942934
borrowVal(x)
943-
consumeVal(x) // expected-note {{consuming use here}}
944-
consumeVal(x) // expected-note {{consuming use here}}
945-
borrowConsumeVal(x, x) // expected-note {{consuming use here}}
935+
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}}
936+
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}}
937+
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}}
946938
// expected-error @-1 {{overlapping accesses, but deinitialization requires exclusive access}}
947939
// expected-note @-2 {{conflicting access is here}}
948940
}
@@ -1154,14 +1146,11 @@ func testGlobalClosureCaptureOwned(_ x: __owned SingleElt) {
11541146
// CHECK: apply {{%.*}}([[LOADED_READ]], [[LOADED_TAKE]])
11551147
// CHECK: } // end sil function '$s16moveonly_closure31testLocalLetClosureCaptureOwnedyyAA9SingleEltVnFyycfU_'
11561148
func testLocalLetClosureCaptureOwned(_ x: __owned SingleElt) {
1157-
// 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}}
1158-
// 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}}
1159-
// 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}}
11601149
let f = {
11611150
borrowVal(x)
1162-
consumeVal(x) // expected-note {{consuming use here}}
1163-
consumeVal(x) // expected-note {{consuming use here}}
1164-
borrowConsumeVal(x, x) // expected-note {{consuming use here}}
1151+
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}}
1152+
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}}
1153+
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}}
11651154
}
11661155
f()
11671156
}

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)