Skip to content

Commit 693d601

Browse files
committed
[AddressLowering] Shorten stack lifetimes.
During storage allocation, create all alloc_stacks at the front of the function entry block, except those for opened existential types. Do not create any dealloc_stacks, even for opened existential types. Then, after function rewriting, position the alloc_stacks according to uses, except those for opened existential types. This means allocating in the block that's the least common ancestor of all uses. At this point, create dealloc_stacks on the dominance frontier of the alloc_stacks, even for allocations for opened existential types. The behavior for opened existential types diverges from all others in order to maintain SIL validity (as much as possible) while the pass runs. It would be invalid to create the alloc_stacks for opened existential types at the front of the entry block because the type which is opened is not yet available, but that type's opening must dominate the corresponding alloc_stack.
1 parent 4d42269 commit 693d601

File tree

2 files changed

+62
-22
lines changed

2 files changed

+62
-22
lines changed

lib/SILOptimizer/Mandatory/AddressLowering.cpp

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,16 +1002,28 @@ namespace {
10021002
/// function in postorder. If the definition is an argument of this function,
10031003
/// simply replace the function argument with an address representing the
10041004
/// caller's storage.
1005-
///
1006-
/// TODO: shrink lifetimes by inserting alloc_stack at the dominance LCA and
1007-
/// finding the lifetime boundary with a simple backward walk from uses.
10081005
class OpaqueStorageAllocation {
10091006
AddressLoweringState &pass;
1007+
/// The alloc_stacks that have been created which eventually need to have
1008+
/// corresponding dealloc_stacks created.
1009+
///
1010+
/// Supports erasure because created alloc_stacks may be erased when block
1011+
/// arguments are coalesced.
1012+
SmallBlotSetVector<AllocStackInst *, 16> allocs;
1013+
/// The alloc_stacks that have been created which eventually need to be
1014+
/// positioned appropriately.
1015+
///
1016+
/// The subset of allocs which aren't for opened existentials.
1017+
InstructionSet allocsToReposition;
10101018

10111019
public:
1012-
explicit OpaqueStorageAllocation(AddressLoweringState &pass) : pass(pass) {}
1020+
explicit OpaqueStorageAllocation(AddressLoweringState &pass)
1021+
: pass(pass), allocsToReposition(pass.function) {}
10131022

10141023
void allocateOpaqueStorage();
1024+
/// Position alloc_stacks according to uses and create dealloc_stacks that
1025+
/// jointly postdominate.
1026+
void finalizeOpaqueStorage();
10151027

10161028
protected:
10171029
void allocateValue(SILValue value);
@@ -1037,6 +1049,8 @@ class OpaqueStorageAllocation {
10371049

10381050
AllocStackInst *createStackAllocation(SILValue value);
10391051

1052+
SILBasicBlock *getLeastCommonAncestorOfUses(SILValue value);
1053+
10401054
void createStackAllocationStorage(SILValue value) {
10411055
pass.valueStorageMap.getStorage(value).storageAddress =
10421056
createStackAllocation(value);
@@ -1226,9 +1240,20 @@ void OpaqueStorageAllocation::removeAllocation(SILValue value) {
12261240
for (Operand *use : uses) {
12271241
pass.deleter.forceDelete(cast<DeallocStackInst>(use->getUser()));
12281242
}
1243+
allocs.erase(allocInst);
12291244
pass.deleter.forceDelete(allocInst);
12301245
}
12311246

1247+
SILBasicBlock *
1248+
OpaqueStorageAllocation::getLeastCommonAncestorOfUses(SILValue value) {
1249+
SILBasicBlock *lca = nullptr;
1250+
for (auto *use : value->getUses()) {
1251+
auto *block = use->getParentBlock();
1252+
lca = lca ? pass.domInfo->findNearestCommonDominator(lca, block) : block;
1253+
}
1254+
return lca;
1255+
}
1256+
12321257
// Create alloc_stack that dominates an owned value \p value. Create
12331258
// jointly-postdominating dealloc_stack instructions. Nesting will be fixed
12341259
// later.
@@ -1250,8 +1275,8 @@ AllocStackInst *OpaqueStorageAllocation::createStackAllocation(SILValue value) {
12501275

12511276
// For opened existential types, allocate stack space at the type
12521277
// definition. Allocating as early as possible provides more opportunity for
1253-
// creating use projections into value. But allocation must be no earlier then
1254-
// the latest type definition.
1278+
// creating use projections into value. But allocation must be no earlier
1279+
// then the latest type definition.
12551280
SILInstruction *latestOpeningInst = nullptr;
12561281
allocTy.getASTType().visit([&](CanType type) {
12571282
auto archetype = dyn_cast<ArchetypeType>(type);
@@ -1274,31 +1299,44 @@ AllocStackInst *OpaqueStorageAllocation::createStackAllocation(SILValue value) {
12741299
latestOpeningInst = openingInst;
12751300
}
12761301
});
1277-
auto allocPt = latestOpeningInst ? std::next(latestOpeningInst->getIterator())
1278-
: pass.function->begin()->begin();
1302+
1303+
auto allocPt = latestOpeningInst
1304+
? latestOpeningInst->getNextInstruction()->getIterator()
1305+
: pass.function->getEntryBlock()->front().getIterator();
12791306
auto allocBuilder = pass.getBuilder(allocPt);
12801307
AllocStackInst *alloc = allocBuilder.createAllocStack(pass.genLoc(), allocTy);
1308+
allocs.insert(alloc);
1309+
if (latestOpeningInst == nullptr) {
1310+
allocsToReposition.insert(alloc);
1311+
}
1312+
return alloc;
1313+
}
1314+
1315+
void OpaqueStorageAllocation::finalizeOpaqueStorage() {
1316+
SmallVector<SILBasicBlock *, 4> boundary;
1317+
for (auto maybeAlloc : allocs) {
1318+
// An allocation may be erased when coalescing block arguments.
1319+
if (!maybeAlloc.hasValue())
1320+
continue;
1321+
1322+
auto *alloc = maybeAlloc.value();
1323+
1324+
if (allocsToReposition.contains(alloc)) {
1325+
auto allocPt = &*getLeastCommonAncestorOfUses(alloc)->begin();
1326+
alloc->moveBefore(allocPt);
1327+
}
12811328

1282-
auto dealloc = [&](SILBasicBlock::iterator insertPt) {
1283-
auto deallocBuilder = pass.getBuilder(insertPt);
1284-
deallocBuilder.createDeallocStack(pass.genLoc(), alloc);
1285-
};
1286-
if (latestOpeningInst) {
12871329
// Deallocate at the predecessors of dominance frontier blocks that are
12881330
// dominated by the alloc to ensure that allocation encloses not only the
12891331
// uses of the current value, but also of any values reusing this storage as
12901332
// a use projection.
1291-
SmallVector<SILBasicBlock *, 4> boundary;
12921333
computeDominatedBoundaryBlocks(alloc->getParent(), pass.domInfo, boundary);
12931334
for (SILBasicBlock *deallocBlock : boundary) {
1294-
dealloc(deallocBlock->getTerminator()->getIterator());
1295-
}
1296-
} else {
1297-
for (SILInstruction *deallocPoint : pass.exitingInsts) {
1298-
dealloc(deallocPoint->getIterator());
1335+
auto deallocBuilder = pass.getBuilder(deallocBlock->back().getIterator());
1336+
deallocBuilder.createDeallocStack(pass.genLoc(), alloc);
12991337
}
1338+
boundary.clear();
13001339
}
1301-
return alloc;
13021340
}
13031341

13041342
//===----------------------------------------------------------------------===//
@@ -3850,6 +3888,8 @@ void AddressLowering::runOnFunction(SILFunction *function) {
38503888
// forward order, setting 'storageAddress' for each projection as it goes.
38513889
rewriteFunction(pass);
38523890

3891+
allocator.finalizeOpaqueStorage();
3892+
38533893
deleteRewrittenInstructions(pass);
38543894

38553895
StackNesting::fixNesting(function);

test/SILOptimizer/opaque_values_Onone.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ enum Maybe1<T : Equatable> {
1515
case yep(T)
1616
// CHECK-LABEL: sil hidden @maybe1_compare {{.*}} {
1717
// CHECK: [[LHS_ADDR:%[^,]+]] = alloc_stack [lexical] $Maybe1
18-
// CHECK: [[RHS_ADDR:%[^,]+]] = alloc_stack [lexical] $Maybe1
1918
// CHECK: switch_enum_addr [[LHS_ADDR]] : $*Maybe1<T>, case #Maybe1.yep!enumelt: [[L_YEP:bb[0-9]+]], case #Maybe1.nope!enumelt: {{bb[0-9]+}}
2019
// CHECK: [[L_YEP]]:
20+
// CHECK: [[RHS_ADDR:%[^,]+]] = alloc_stack [lexical] $Maybe1
2121
// CHECK: unchecked_take_enum_data_addr [[LHS_ADDR]] : $*Maybe1<T>, #Maybe1.yep!enumelt
2222
// CHECK: switch_enum_addr [[RHS_ADDR]] : $*Maybe1<T>, case #Maybe1.yep!enumelt: [[L_AND_R_YEP:bb[0-9]+]], default {{bb[0-9]+}}
2323
// CHECK: [[L_AND_R_YEP]]:
@@ -42,9 +42,9 @@ enum Maybe2<T : Equatable> {
4242

4343
// CHECK-LABEL: sil hidden @maybe2_compare {{.*}} {
4444
// CHECK: [[LHS_ADDR:%[^,]+]] = alloc_stack [lexical] $Maybe2<T>
45-
// CHECK: [[RHS_ADDR:%[^,]+]] = alloc_stack [lexical] $Maybe2<T>
4645
// CHECK: switch_enum_addr [[LHS_ADDR]] : $*Maybe2<T>, case #Maybe2.yep!enumelt: [[L_YEP:bb[0-9]+]], case #Maybe2.nope!enumelt: {{bb[0-9]+}}
4746
// CHECK: [[L_YEP]]:
47+
// CHECK: [[RHS_ADDR:%[^,]+]] = alloc_stack [lexical] $Maybe2<T>
4848
// CHECK: unchecked_take_enum_data_addr [[LHS_ADDR]] : $*Maybe2<T>, #Maybe2.yep!enumelt
4949
// CHECK: switch_enum_addr [[RHS_ADDR]] : $*Maybe2<T>, case #Maybe2.yep!enumelt: [[R_YEP:bb[0-9]+]], default {{bb[0-9]+}}
5050
// CHECK: [[L_AND_R_YEP]]:

0 commit comments

Comments
 (0)