Skip to content

Commit feecc51

Browse files
Merge pull request #60478 from nate-chandler/lexical_lifetimes/optimize_more_alloc_stack_destroys
[SILOpt] Hoist destroys for non-lexical alloc_stacks more aggressively.
2 parents a222ca8 + 3df609f commit feecc51

12 files changed

+391
-54
lines changed

lib/SIL/Verifier/SILVerifier.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2227,6 +2227,31 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
22272227
"Found load borrow that is invalidated by a local write?!");
22282228
}
22292229

2230+
void checkBeginBorrowInst(BeginBorrowInst *bbi) {
2231+
if (!bbi->isLexical())
2232+
return;
2233+
// Lexical begin_borrows of instances of some SILBoxType must derive from
2234+
// alloc_boxes or captures.
2235+
auto value = bbi->getOperand();
2236+
if (!value->getType().is<SILBoxType>())
2237+
return;
2238+
while (true) {
2239+
// Inlining may introduce additional begin_borrow instructions.
2240+
if (auto bbi = dyn_cast<BeginBorrowInst>(value))
2241+
value = bbi->getOperand();
2242+
// SILGen introduces copy_value instructions.
2243+
else if (auto cvi = dyn_cast<CopyValueInst>(value))
2244+
value = cvi->getOperand();
2245+
// SILGen inserts mark_uninitialized instructions of alloc_boxes.
2246+
else if (auto *mui = dyn_cast<MarkUninitializedInst>(value))
2247+
value = mui->getOperand();
2248+
else
2249+
break;
2250+
}
2251+
require(isa<AllocBoxInst>(value) || isa<SILFunctionArgument>(value),
2252+
"Lexical borrows of SILBoxTypes must be of vars or captures.");
2253+
}
2254+
22302255
void checkEndBorrowInst(EndBorrowInst *EBI) {
22312256
require(
22322257
F.hasOwnership(),

lib/SILOptimizer/Transforms/AllocBoxToStack.cpp

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,13 @@
1313
#define DEBUG_TYPE "allocbox-to-stack"
1414
#include "swift/AST/DiagnosticsSIL.h"
1515
#include "swift/Basic/BlotMapVector.h"
16+
#include "swift/Basic/GraphNodeWorklist.h"
1617
#include "swift/SIL/ApplySite.h"
18+
#include "swift/SIL/BasicBlockDatastructures.h"
1719
#include "swift/SIL/Dominance.h"
1820
#include "swift/SIL/SILArgument.h"
1921
#include "swift/SIL/SILBuilder.h"
2022
#include "swift/SIL/SILCloner.h"
21-
#include "swift/SIL/BasicBlockDatastructures.h"
2223
#include "swift/SILOptimizer/PassManager/Passes.h"
2324
#include "swift/SILOptimizer/PassManager/Transforms.h"
2425
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
@@ -27,6 +28,7 @@
2728
#include "swift/SILOptimizer/Utils/StackNesting.h"
2829
#include "swift/SILOptimizer/Utils/ValueLifetime.h"
2930
#include "llvm/ADT/DenseMap.h"
31+
#include "llvm/ADT/STLExtras.h"
3032
#include "llvm/ADT/SmallPtrSet.h"
3133
#include "llvm/ADT/SmallSet.h"
3234
#include "llvm/ADT/SmallVector.h"
@@ -538,13 +540,35 @@ static bool rewriteAllocBoxAsAllocStack(AllocBoxInst *ABI) {
538540
SILBuilderWithScope Builder(ABI);
539541
assert(ABI->getBoxType()->getLayout()->getFields().size() == 1
540542
&& "rewriting multi-field box not implemented");
541-
auto &mod = ABI->getFunction()->getModule();
542-
bool isLexical = mod.getASTContext().SILOpts.supportsLexicalLifetimes(mod);
543-
auto *ASI = Builder.createAllocStack(
544-
ABI->getLoc(),
545-
getSILBoxFieldType(TypeExpansionContext(*ABI->getFunction()),
546-
ABI->getBoxType(), ABI->getModule().Types, 0),
547-
ABI->getVarInfo(), ABI->hasDynamicLifetime(), isLexical);
543+
auto ty = getSILBoxFieldType(TypeExpansionContext(*ABI->getFunction()),
544+
ABI->getBoxType(), ABI->getModule().Types, 0);
545+
auto isLexical = [&]() -> bool {
546+
auto &mod = ABI->getFunction()->getModule();
547+
bool lexicalLifetimesEnabled =
548+
mod.getASTContext().SILOpts.supportsLexicalLifetimes(mod);
549+
if (!lexicalLifetimesEnabled)
550+
return false;
551+
// Look for lexical borrows of the alloc_box.
552+
GraphNodeWorklist<Operand *, 4> worklist;
553+
worklist.initializeRange(ABI->getUses());
554+
while (auto *use = worklist.pop()) {
555+
// See through mark_uninitialized and non-lexical begin_borrow
556+
// instructions. It's verified that lexical begin_borrows of SILBoxType
557+
// values originate either from AllocBoxInsts or SILFunctionArguments.
558+
if (auto *mui = dyn_cast<MarkUninitializedInst>(use->getUser())) {
559+
for (auto *use : mui->getUses())
560+
worklist.insert(use);
561+
} else if (auto *bbi = dyn_cast<BeginBorrowInst>(use->getUser())) {
562+
if (bbi->isLexical())
563+
return true;
564+
for (auto *use : bbi->getUses())
565+
worklist.insert(use);
566+
}
567+
}
568+
return false;
569+
};
570+
auto *ASI = Builder.createAllocStack(ABI->getLoc(), ty, ABI->getVarInfo(),
571+
ABI->hasDynamicLifetime(), isLexical());
548572

549573
// Transfer a mark_uninitialized if we have one.
550574
SILValue StackBox = ASI;

lib/SILOptimizer/Transforms/SSADestroyHoisting.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -962,7 +962,7 @@ void SSADestroyHoisting::run() {
962962
}
963963
// Alloc stacks always enclose their accesses.
964964
for (auto *asi : asis) {
965-
changed |= hoistDestroys(asi, /*ignoreDeinitBarriers=*/false,
965+
changed |= hoistDestroys(asi, /*ignoreDeinitBarriers=*/!asi->isLexical(),
966966
remainingDestroyAddrs, deleter);
967967
}
968968
// Arguments enclose everything.

lib/SILOptimizer/Transforms/TempRValueElimination.cpp

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -501,11 +501,29 @@ void TempRValueOptPass::tryOptimizeCopyIntoTemp(CopyAddrInst *copyInst) {
501501
if (!tempObj)
502502
return;
503503

504-
// If the storage corresponds to a source-level var, do not optimize here.
505-
// Mem2Reg will transform the lexical alloc_stack into a lexical begin_borrow
506-
// which will ensure that the value's lifetime isn't observably shortened.
507-
if (tempObj->isLexical())
508-
return;
504+
// If the temporary storage is lexical, it either came from a source-level var
505+
// or was marked lexical because it was passed to a function that has been
506+
// inlined.
507+
// TODO: [begin_borrow_addr] Once we can mark addresses as being borrowed, we
508+
// won't need to mark alloc_stacks lexical during inlining. At that
509+
// point, the above comment should change, but the implementation
510+
// remains the same.
511+
//
512+
// In either case, we can eliminate the temporary if the source of the copy is
513+
// lexical and it is live for longer than the temporary.
514+
if (tempObj->isLexical()) {
515+
// TODO: Determine whether the base of the copy_addr's source is lexical and
516+
// its live range contains the range in which the alloc_stack
517+
// contains the value copied into it via the copy_addr.
518+
//
519+
// For now, only look for guaranteed arguments.
520+
auto storage = AccessStorageWithBase::compute(copyInst->getSrc());
521+
if (!storage.base)
522+
return;
523+
if (auto *arg = dyn_cast<SILFunctionArgument>(storage.base))
524+
if (arg->getOwnershipKind() != OwnershipKind::Guaranteed)
525+
return;
526+
}
509527

510528
bool isOSSA = copyInst->getFunction()->hasOwnership();
511529

@@ -645,10 +663,20 @@ TempRValueOptPass::tryOptimizeStoreIntoTemp(StoreInst *si) {
645663
return std::next(si->getIterator());
646664
}
647665

648-
// If the storage corresponds to a source-level var, do not optimize here.
649-
// Mem2Reg will transform the lexical alloc_stack into a lexical begin_borrow
650-
// which will ensure that the value's lifetime isn't observably shortened.
666+
// If the temporary storage is lexical, it either came from a source-level var
667+
// or was marked lexical because it was passed to a function that has been
668+
// inlined.
669+
// TODO: [begin_borrow_addr] Once we can mark addresses as being borrowed, we
670+
// won't need to mark alloc_stacks lexical during inlining. At that
671+
// point, the above comment should change, but the implementation
672+
// remains the same.
673+
//
674+
// In either case, we can eliminate the temporary if the source of the store
675+
// is lexical and it is live for longer than the temporary.
651676
if (tempObj->isLexical()) {
677+
// TODO: Find the lexical root of the source, if any, and allow optimization
678+
// if its live range contains the range in which the alloc_stack
679+
// contains the value stored into it.
652680
return std::next(si->getIterator());
653681
}
654682

lib/SILOptimizer/Utils/SILInliner.cpp

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include "swift/SILOptimizer/Utils/SILInliner.h"
1616
#include "swift/AST/Builtins.h"
1717
#include "swift/AST/DiagnosticsSIL.h"
18+
#include "swift/Basic/Defer.h"
19+
#include "swift/SIL/MemAccessUtils.h"
1820
#include "swift/SIL/PrettyStackTrace.h"
1921
#include "swift/SIL/SILDebugScope.h"
2022
#include "swift/SIL/SILInstruction.h"
@@ -433,17 +435,44 @@ void SILInlineCloner::cloneInline(ArrayRef<SILValue> AppliedArgs) {
433435
auto calleeConv = getCalleeFunction()->getConventions();
434436
for (auto p : llvm::enumerate(AppliedArgs)) {
435437
SILValue callArg = p.value();
438+
SWIFT_DEFER { entryArgs.push_back(callArg); };
436439
unsigned idx = p.index();
437440
if (idx >= calleeConv.getSILArgIndexOfFirstParam()) {
438-
// Insert begin/end borrow for guaranteed arguments.
439-
if (calleeConv.getParamInfoForSILArg(idx).isGuaranteed()) {
440-
if (SILValue newValue = borrowFunctionArgument(callArg, Apply)) {
441-
callArg = newValue;
442-
borrowedArgs[idx] = true;
441+
auto paramInfo = calleeConv.getParamInfoForSILArg(idx);
442+
if (callArg->getType().isAddress()) {
443+
// If lexical lifetimes are enabled, any alloc_stacks in the caller that
444+
// are passed to the callee being inlined (except mutating exclusive
445+
// accesses) need to be promoted to be lexical. Otherwise,
446+
// destroy_addrs could be hoisted through the body of the newly inlined
447+
// function without regard to the deinit barriers it contains.
448+
//
449+
// TODO: [begin_borrow_addr] Instead of marking the alloc_stack as a
450+
// whole lexical, just mark the inlined range lexical via
451+
// begin_borrow_addr [lexical]/end_borrow_addr just as is done
452+
// with values.
453+
auto &module = Apply.getFunction()->getModule();
454+
auto enableLexicalLifetimes =
455+
module.getASTContext().SILOpts.supportsLexicalLifetimes(module);
456+
if (!enableLexicalLifetimes)
457+
continue;
458+
459+
// Exclusive mutating accesses don't entail a lexical scope.
460+
if (paramInfo.getConvention() == ParameterConvention::Indirect_Inout)
461+
continue;
462+
463+
auto storage = AccessStorageWithBase::compute(callArg);
464+
if (auto *asi = dyn_cast<AllocStackInst>(storage.base))
465+
asi->setIsLexical();
466+
} else {
467+
// Insert begin/end borrow for guaranteed arguments.
468+
if (paramInfo.isGuaranteed()) {
469+
if (SILValue newValue = borrowFunctionArgument(callArg, Apply)) {
470+
callArg = newValue;
471+
borrowedArgs[idx] = true;
472+
}
443473
}
444474
}
445475
}
446-
entryArgs.push_back(callArg);
447476
}
448477

449478
// Create the return block and set ReturnToBB for use in visitTerminator

test/SILGen/copy_operator.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class Klass {}
3737
// CHECK-SIL-LABEL: sil @$s8moveonly7useCopyyAA5KlassCADF : $@convention(thin) (@guaranteed Klass) -> @owned Klass {
3838
// CHECK-SIL: bb0([[ARG:%.*]] : $Klass):
3939
// CHECK-SIL-NEXT: debug_value
40-
// CHECK-SIL-NEXT: [[INPUT:%.*]] = alloc_stack $Klass
40+
// CHECK-SIL-NEXT: [[INPUT:%.*]] = alloc_stack [lexical] $Klass
4141
// CHECK-SIL-NEXT: store [[ARG]] to [[INPUT]]
4242
// CHECK-SIL-NEXT: [[VALUE:%[0-9][0-9]*]] = load [[INPUT]]{{.*}}
4343
// CHECK-SIL-NEXT: strong_retain [[VALUE]]
@@ -78,7 +78,7 @@ public func useCopy(_ k: Klass) -> Klass {
7878
// CHECK-SIL-LABEL: sil @$s8moveonly7useCopyyxxRlzClF : $@convention(thin) <T where T : AnyObject> (@guaranteed T) -> @owned T {
7979
// CHECK-SIL: bb0([[ARG:%.*]] :
8080
// CHECK-SIL-NEXT: debug_value
81-
// CHECK-SIL-NEXT: [[INPUT_TO_BE_ELIMINATED:%.*]] = alloc_stack $T
81+
// CHECK-SIL-NEXT: [[INPUT_TO_BE_ELIMINATED:%.*]] = alloc_stack [lexical] $T
8282
// CHECK-SIL-NEXT: store [[ARG]] to [[INPUT]] : $*T
8383
// CHECK-SIL-NEXT: [[VALUE:%.*]] = load [[INPUT]] : $*T
8484
// CHECK-SIL-NEXT: strong_retain [[VALUE]]

test/SILOptimizer/allocbox_to_stack_lifetime.sil

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@ struct Bool {
1414

1515
protocol Error {}
1616

17-
// CHECK-LABEL: sil [ossa] @simple_promotion
18-
sil [ossa] @simple_promotion : $@convention(thin) (Int) -> Int {
17+
// CHECK-LABEL: sil [ossa] @promote_nonlexical
18+
// CHECK: alloc_stack $
19+
// CHECK-NOT: alloc_box
20+
// CHECK-NOT: destroy_value
21+
// CHECK: return
22+
// CHECK-LABEL: } // end sil function 'promote_nonlexical'
23+
sil [ossa] @promote_nonlexical : $@convention(thin) (Int) -> Int {
1924
bb0(%0 : $Int):
2025
%1 = alloc_box ${ var Int }
2126
%1a = project_box %1 : ${ var Int }, 0
@@ -24,9 +29,24 @@ bb0(%0 : $Int):
2429
%3 = load [trivial] %1a : $*Int
2530
destroy_value %1 : ${ var Int }
2631
return %3 : $Int
27-
// CHECK: alloc_stack [lexical]
28-
// CHECK-NOT: alloc_box
29-
// CHECK-NOT: destroy_value
30-
// CHECK: return
32+
}
33+
34+
// CHECK-LABEL: sil [ossa] @promote_lexical
35+
// CHECK: alloc_stack [lexical]
36+
// CHECK-NOT: alloc_box
37+
// CHECK-NOT: destroy_value
38+
// CHECK: return
39+
// CHECK-LABEL: } // end sil function 'promote_lexical'
40+
sil [ossa] @promote_lexical : $@convention(thin) (Int) -> Int {
41+
bb0(%0 : $Int):
42+
%1 = alloc_box ${ var Int }
43+
%b = begin_borrow [lexical] %1 : ${ var Int }
44+
%1a = project_box %b : ${ var Int }, 0
45+
store %0 to [trivial] %1a : $*Int
46+
47+
%3 = load [trivial] %1a : $*Int
48+
end_borrow %b : ${ var Int }
49+
destroy_value %1 : ${ var Int }
50+
return %3 : $Int
3151
}
3252

0 commit comments

Comments
 (0)