Skip to content

Commit 95662b0

Browse files
authored
Merge pull request #35721 from gottesmm/pr-4d794a36822362fa51f2d2bad655b867394042e4
[sil-combine] Fix optimization of COWBufferRead for ownership
2 parents 0b63100 + ed368ab commit 95662b0

File tree

5 files changed

+293
-32
lines changed

5 files changed

+293
-32
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -527,13 +527,34 @@ struct BorrowedValue {
527527
SWIFT_DEBUG_DUMP { print(llvm::dbgs()); }
528528

529529
/// Visit each of the interior pointer uses of this underlying borrow
530-
/// introduced value. These object -> address projections and any transitive
531-
/// address uses must be treated as liveness requiring uses of the guaranteed
532-
/// value and we can not shrink the scope beyond that point. Returns true if
533-
/// we were able to understand all uses and thus guarantee we found all
534-
/// interior pointer uses. Returns false otherwise.
530+
/// introduced value without looking through nested borrows or reborrows.
531+
///
532+
/// These object -> address projections and any transitive address uses must
533+
/// be treated as liveness requiring uses of the guaranteed value and we can
534+
/// not shrink the scope beyond that point. Returns true if we were able to
535+
/// understand all uses and thus guarantee we found all interior pointer
536+
/// uses. Returns false otherwise.
535537
bool visitInteriorPointerOperands(
536-
function_ref<void(const InteriorPointerOperand &)> func) const;
538+
function_ref<void(InteriorPointerOperand)> func) const {
539+
return visitInteriorPointerOperandHelper(
540+
func, InteriorPointerOperandVisitorKind::NoNestedNoReborrows);
541+
}
542+
543+
/// Visit each of the interior pointer uses of this underlying borrow
544+
/// introduced value looking through nested borrow scopes but not reborrows.
545+
bool visitNestedInteriorPointerOperands(
546+
function_ref<void(InteriorPointerOperand)> func) const {
547+
return visitInteriorPointerOperandHelper(
548+
func, InteriorPointerOperandVisitorKind::YesNestedNoReborrows);
549+
}
550+
551+
/// Visit each of the interior pointer uses of this underlying borrow
552+
/// introduced value looking through nested borrow scopes and reborrows.
553+
bool visitExtendedInteriorPointerOperands(
554+
function_ref<void(InteriorPointerOperand)> func) const {
555+
return visitInteriorPointerOperandHelper(
556+
func, InteriorPointerOperandVisitorKind::YesNestedYesReborrows);
557+
}
537558

538559
/// Visit all immediate uses of this borrowed value and if any of them are
539560
/// reborrows, place them in BorrowingOperand form into \p
@@ -560,6 +581,16 @@ struct BorrowedValue {
560581
SILValue operator->() const { return value; }
561582
SILValue operator*() { return value; }
562583
SILValue operator*() const { return value; }
584+
585+
private:
586+
enum class InteriorPointerOperandVisitorKind {
587+
NoNestedNoReborrows,
588+
YesNestedNoReborrows,
589+
YesNestedYesReborrows,
590+
};
591+
bool visitInteriorPointerOperandHelper(
592+
function_ref<void(InteriorPointerOperand)> func,
593+
InteriorPointerOperandVisitorKind kind) const;
563594
};
564595

565596
llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
@@ -603,6 +634,8 @@ class InteriorPointerOperandKind {
603634
return value;
604635
}
605636

637+
explicit operator bool() const { return isValid(); }
638+
606639
bool isValid() const { return value != Kind::Invalid; }
607640

608641
static InteriorPointerOperandKind get(Operand *use) {
@@ -651,7 +684,6 @@ struct InteriorPointerOperand {
651684

652685
InteriorPointerOperand(Operand *op)
653686
: operand(op), kind(InteriorPointerOperandKind::get(op)) {
654-
assert(kind.isValid());
655687
}
656688

657689
operator bool() const {

lib/SIL/Utils/OwnershipUtils.cpp

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -600,24 +600,56 @@ bool BorrowedValue::visitExtendedLocalScopeEndingUses(
600600
return true;
601601
}
602602

603-
bool BorrowedValue::visitInteriorPointerOperands(
604-
function_ref<void(const InteriorPointerOperand &)> func) const {
603+
bool BorrowedValue::visitInteriorPointerOperandHelper(
604+
function_ref<void(InteriorPointerOperand)> func,
605+
BorrowedValue::InteriorPointerOperandVisitorKind kind) const {
606+
using Kind = BorrowedValue::InteriorPointerOperandVisitorKind;
607+
605608
SmallVector<Operand *, 32> worklist(value->getUses());
606609
while (!worklist.empty()) {
607610
auto *op = worklist.pop_back_val();
608611

609-
if (auto interiorPointer = InteriorPointerOperand::get(op)) {
612+
if (auto interiorPointer = InteriorPointerOperand(op)) {
610613
func(interiorPointer);
611614
continue;
612615
}
613616

617+
if (auto borrowingOperand = BorrowingOperand(op)) {
618+
switch (kind) {
619+
case Kind::NoNestedNoReborrows:
620+
// We do not look through nested things and or reborrows, so just
621+
// continue.
622+
continue;
623+
case Kind::YesNestedNoReborrows:
624+
// We only look through nested borrowing operands, we never look through
625+
// reborrows though.
626+
if (borrowingOperand.isReborrow())
627+
continue;
628+
break;
629+
case Kind::YesNestedYesReborrows:
630+
// Look through everything!
631+
break;
632+
}
633+
634+
borrowingOperand.visitBorrowIntroducingUserResults([&](auto bv) {
635+
for (auto *use : bv->getUses()) {
636+
if (auto intPtrOperand = InteriorPointerOperand(use)) {
637+
func(intPtrOperand);
638+
continue;
639+
}
640+
worklist.push_back(use);
641+
}
642+
return true;
643+
});
644+
continue;
645+
}
646+
614647
auto *user = op->getUser();
615-
if (isa<BeginBorrowInst>(user) || isa<DebugValueInst>(user) ||
616-
isa<SuperMethodInst>(user) || isa<ClassMethodInst>(user) ||
617-
isa<CopyValueInst>(user) || isa<EndBorrowInst>(user) ||
618-
isa<ApplyInst>(user) || isa<StoreBorrowInst>(user) ||
619-
isa<StoreInst>(user) || isa<PartialApplyInst>(user) ||
620-
isa<UnmanagedRetainValueInst>(user) ||
648+
if (isa<DebugValueInst>(user) || isa<SuperMethodInst>(user) ||
649+
isa<ClassMethodInst>(user) || isa<CopyValueInst>(user) ||
650+
isa<EndBorrowInst>(user) || isa<ApplyInst>(user) ||
651+
isa<StoreBorrowInst>(user) || isa<StoreInst>(user) ||
652+
isa<PartialApplyInst>(user) || isa<UnmanagedRetainValueInst>(user) ||
621653
isa<UnmanagedReleaseValueInst>(user) ||
622654
isa<UnmanagedAutoreleaseValueInst>(user)) {
623655
continue;
@@ -693,10 +725,11 @@ bool InteriorPointerOperand::findTransitiveUsesForAddress(
693725
if (Projection::isAddressProjection(user) ||
694726
isa<ProjectBlockStorageInst>(user) ||
695727
isa<OpenExistentialAddrInst>(user) ||
696-
isa<InitExistentialAddrInst>(user) ||
697-
isa<InitEnumDataAddrInst>(user) || isa<BeginAccessInst>(user) ||
698-
isa<TailAddrInst>(user) || isa<IndexAddrInst>(user) ||
699-
isa<UnconditionalCheckedCastAddrInst>(user)) {
728+
isa<InitExistentialAddrInst>(user) || isa<InitEnumDataAddrInst>(user) ||
729+
isa<BeginAccessInst>(user) || isa<TailAddrInst>(user) ||
730+
isa<IndexAddrInst>(user) ||
731+
isa<UnconditionalCheckedCastAddrInst>(user) ||
732+
isa<UncheckedAddrCastInst>(user)) {
700733
for (SILValue r : user->getResults()) {
701734
llvm::copy(r->getUses(), std::back_inserter(worklist));
702735
}

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,8 @@ class SILCombiner :
312312
SILInstruction *optimizeBuiltinIsConcrete(BuiltinInst *I);
313313

314314
SILInstruction *optimizeBuiltinCOWBufferForReading(BuiltinInst *BI);
315+
SILInstruction *optimizeBuiltinCOWBufferForReadingNonOSSA(BuiltinInst *BI);
316+
SILInstruction *optimizeBuiltinCOWBufferForReadingOSSA(BuiltinInst *BI);
315317

316318
// Optimize the "trunc_N1_M2" builtin. if N1 is a result of "zext_M1_*" and
317319
// the following holds true: N1 > M1 and M2>= M1

lib/SILOptimizer/SILCombiner/SILCombinerBuiltinVisitors.cpp

Lines changed: 108 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#define DEBUG_TYPE "sil-combine"
14+
1415
#include "SILCombiner.h"
1516
#include "swift/SIL/DebugUtils.h"
1617
#include "swift/SIL/DynamicCasts.h"
@@ -22,6 +23,7 @@
2223
#include "swift/SILOptimizer/Analysis/ValueTracking.h"
2324
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
2425
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
26+
#include "swift/SILOptimizer/Utils/OwnershipOptUtils.h"
2527
#include "llvm/ADT/DenseMap.h"
2628
#include "llvm/ADT/SmallPtrSet.h"
2729
#include "llvm/ADT/SmallVector.h"
@@ -109,6 +111,92 @@ SILInstruction *SILCombiner::optimizeBuiltinIsConcrete(BuiltinInst *BI) {
109111
return Builder.createIntegerLiteral(BI->getLoc(), BI->getType(), 1);
110112
}
111113

114+
/// Replace
115+
/// \code
116+
/// %b = builtin "COWBufferForReading" %r
117+
/// %bb = begin_borrow %b
118+
/// %a = ref_element_addr %bb
119+
/// ... use %a ...
120+
/// end_borrow %bb
121+
/// \endcode
122+
/// with
123+
/// \code
124+
/// %bb = begin_borrow %r
125+
/// %a = ref_element_addr [immutable] %r
126+
/// ... use %b ...
127+
/// end_borrow %bb
128+
/// \endcode
129+
/// The same for ref_tail_addr.
130+
SILInstruction *
131+
SILCombiner::optimizeBuiltinCOWBufferForReadingOSSA(BuiltinInst *bi) {
132+
SmallVector<BorrowedValue, 32> accumulatedBorrowedValues;
133+
134+
// A helper that performs our main loop to look through uses. It ensures
135+
// that we do not need to fill up the useWorklist on the first iteration.
136+
for (auto *use : bi->getUses()) {
137+
// See if we have a borrowing operand that we can find a local borrowed
138+
// value for. In such a case, we stash that borrowed value so that we can
139+
// use it to find interior pointer operands.
140+
if (auto operand = BorrowingOperand(use)) {
141+
if (operand.isReborrow())
142+
return nullptr;
143+
operand.visitBorrowIntroducingUserResults([&](BorrowedValue bv) {
144+
accumulatedBorrowedValues.push_back(bv);
145+
return true;
146+
});
147+
continue;
148+
}
149+
150+
// Otherwise, look for instructions that we know are uses that we can
151+
// ignore.
152+
auto *user = use->getUser();
153+
154+
// Debug instructions are safe.
155+
if (user->isDebugInstruction())
156+
continue;
157+
158+
// copy_value, destroy_value are safe due to our checking of the
159+
// instruction use list for safety.
160+
if (isa<DestroyValueInst>(user) || isa<CopyValueInst>(user))
161+
continue;
162+
163+
// An instruction we don't understand, bail.
164+
return nullptr;
165+
}
166+
167+
// Now that we know that we have a case we support, use our stashed
168+
// BorrowedValues to find all interior pointer operands into this copy of our
169+
// COWBuffer and mark them as immutable.
170+
//
171+
// NOTE: We currently only use nested int ptr operands instead of extended int
172+
// ptr operands since we do not want to look through reborrows and thus lose
173+
// dominance.
174+
while (!accumulatedBorrowedValues.empty()) {
175+
auto bv = accumulatedBorrowedValues.pop_back_val();
176+
bv.visitNestedInteriorPointerOperands(
177+
[&](InteriorPointerOperand intPtrOperand) {
178+
switch (intPtrOperand.kind) {
179+
case InteriorPointerOperandKind::Invalid:
180+
llvm_unreachable("Invalid int pointer kind?!");
181+
case InteriorPointerOperandKind::RefElementAddr:
182+
cast<RefElementAddrInst>(intPtrOperand->getUser())->setImmutable();
183+
return;
184+
case InteriorPointerOperandKind::RefTailAddr:
185+
cast<RefTailAddrInst>(intPtrOperand->getUser())->setImmutable();
186+
return;
187+
case InteriorPointerOperandKind::OpenExistentialBox:
188+
// Can not mark this immutable.
189+
return;
190+
}
191+
});
192+
}
193+
194+
OwnershipRAUWHelper helper(ownershipFixupContext, bi, bi->getOperand(0));
195+
assert(helper && "COWBufferForReading always has an owned arg/owned result");
196+
helper.perform();
197+
return nullptr;
198+
}
199+
112200
/// Replace
113201
/// \code
114202
/// %b = builtin "COWBufferForReading" %r
@@ -119,23 +207,24 @@ SILInstruction *SILCombiner::optimizeBuiltinIsConcrete(BuiltinInst *BI) {
119207
/// %a = ref_element_addr [immutable] %r
120208
/// \endcode
121209
/// The same for ref_tail_addr.
122-
SILInstruction *SILCombiner::optimizeBuiltinCOWBufferForReading(BuiltinInst *BI) {
123-
auto useIter = BI->use_begin();
124-
while (useIter != BI->use_end()) {
210+
SILInstruction *
211+
SILCombiner::optimizeBuiltinCOWBufferForReadingNonOSSA(BuiltinInst *bi) {
212+
auto useIter = bi->use_begin();
213+
while (useIter != bi->use_end()) {
125214
auto nextIter = std::next(useIter);
126215
SILInstruction *user = useIter->getUser();
127-
SILValue ref = BI->getOperand(0);
216+
SILValue ref = bi->getOperand(0);
128217
switch (user->getKind()) {
129218
case SILInstructionKind::RefElementAddrInst: {
130-
auto *REAI = cast<RefElementAddrInst>(user);
131-
REAI->setOperand(ref);
132-
REAI->setImmutable();
219+
auto *reai = cast<RefElementAddrInst>(user);
220+
reai->setOperand(ref);
221+
reai->setImmutable();
133222
break;
134223
}
135224
case SILInstructionKind::RefTailAddrInst: {
136-
auto *RTAI = cast<RefTailAddrInst>(user);
137-
RTAI->setOperand(ref);
138-
RTAI->setImmutable();
225+
auto *rtai = cast<RefTailAddrInst>(user);
226+
rtai->setOperand(ref);
227+
rtai->setImmutable();
139228
break;
140229
}
141230
case SILInstructionKind::DestroyValueInst:
@@ -151,11 +240,18 @@ SILInstruction *SILCombiner::optimizeBuiltinCOWBufferForReading(BuiltinInst *BI)
151240
}
152241

153242
// If there are unknown users, keep the builtin, and IRGen will handle it.
154-
if (BI->use_empty())
155-
return eraseInstFromFunction(*BI);
243+
if (bi->use_empty())
244+
return eraseInstFromFunction(*bi);
156245
return nullptr;
157246
}
158247

248+
SILInstruction *
249+
SILCombiner::optimizeBuiltinCOWBufferForReading(BuiltinInst *BI) {
250+
if (hasOwnership())
251+
return optimizeBuiltinCOWBufferForReadingOSSA(BI);
252+
return optimizeBuiltinCOWBufferForReadingNonOSSA(BI);
253+
}
254+
159255
static unsigned getTypeWidth(SILType Ty) {
160256
if (auto BuiltinIntTy = Ty.getAs<BuiltinIntegerType>()) {
161257
if (BuiltinIntTy->isFixedWidth()) {

0 commit comments

Comments
 (0)