Skip to content

Commit 335c551

Browse files
Merge pull request #83624 from nate-chandler/rdar157291161
[SemanticARCOpts] Fix miscompile at dead-ends.
2 parents eca5c36 + c893c74 commit 335c551

File tree

11 files changed

+242
-83
lines changed

11 files changed

+242
-83
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,21 @@ struct BorrowedValue {
630630
/// necessarilly dominated by the borrow scope.
631631
void computeTransitiveLiveness(MultiDefPrunedLiveness &liveness) const;
632632

633+
/// Whether \p insts are completely within this borrow introducer's local
634+
/// scope.
635+
///
636+
/// Precondition: \p insts are dominated by the local borrow introducer.
637+
///
638+
/// This ignores reborrows. The assumption is that, since \p insts are
639+
/// dominated by this local scope, checking the extended borrow scope should
640+
/// not be necessary to determine they are within the scope.
641+
///
642+
/// \p deadEndBlocks is optional during transition. It will be completely
643+
/// removed in an upcoming commit.
644+
template <typename Instructions>
645+
bool areWithinExtendedScope(Instructions insts,
646+
DeadEndBlocks *deadEndBlocks) const;
647+
633648
/// Returns true if \p uses are completely within this borrow introducer's
634649
/// local scope.
635650
///
@@ -1426,6 +1441,43 @@ bool isRedundantMoveValue(MoveValueInst *mvi);
14261441
/// `forEndBorrowValue`, which is the operand value of an `end_borrow`.
14271442
void updateReborrowFlags(SILValue forEndBorrowValue);
14281443

1444+
/// A location at which a value is used. Abstracts over explicit uses
1445+
/// (operands) and implicit uses (instructions).
1446+
struct UsePoint {
1447+
using Value = llvm::PointerUnion<SILInstruction *, Operand *>;
1448+
Value value;
1449+
1450+
UsePoint(Operand *op) : value(op) {}
1451+
UsePoint(SILInstruction *inst) : value(inst) {}
1452+
UsePoint(Value value) : value(value) {}
1453+
1454+
SILInstruction *getInstruction() const {
1455+
if (auto *op = dyn_cast<Operand *>(value)) {
1456+
return op->getUser();
1457+
}
1458+
return cast<SILInstruction *>(value);
1459+
}
1460+
1461+
Operand *getOperandOrNull() const { return dyn_cast<Operand *>(value); }
1462+
1463+
Operand *getOperand() const { return cast<Operand *>(value); }
1464+
};
1465+
1466+
struct UsePointToInstruction {
1467+
SILInstruction *operator()(const UsePoint point) const {
1468+
return point.getInstruction();
1469+
}
1470+
};
1471+
1472+
using UsePointInstructionRange =
1473+
TransformRange<ArrayRef<UsePoint>, UsePointToInstruction>;
1474+
1475+
struct PointToOperand {
1476+
Operand *operator()(const UsePoint point) const { return point.getOperand(); }
1477+
};
1478+
1479+
using PointOperandRange = TransformRange<ArrayRef<UsePoint>, PointToOperand>;
1480+
14291481
} // namespace swift
14301482

14311483
#endif

include/swift/SIL/PrunedLiveness.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,20 @@ class PrunedLiveRange : public PrunedLiveness {
729729
bool isWithinBoundary(SILInstruction *inst,
730730
DeadEndBlocks *deadEndBlocks) const;
731731

732+
/// Whether all \p insts are between this def and the liveness boundary;
733+
/// \p deadEndBlocks is optional.
734+
template <typename Instructions>
735+
bool areWithinBoundary(Instructions insts,
736+
DeadEndBlocks *deadEndBlocks) const {
737+
assert(asImpl().isInitialized());
738+
739+
for (auto *inst : insts) {
740+
if (!isWithinBoundary(inst, deadEndBlocks))
741+
return false;
742+
}
743+
return true;
744+
};
745+
732746
/// Returns true when all \p uses are between this def and the liveness
733747
/// boundary \p deadEndBlocks is optional.
734748
bool areUsesWithinBoundary(ArrayRef<Operand *> uses,

include/swift/SIL/SILInstruction.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,6 +670,10 @@ class SILInstruction : public llvm::ilist_node<SILInstruction> {
670670
using TransformedOperandValueRange =
671671
OptionalTransformRange<ArrayRef<Operand>, OperandToTransformedValue>;
672672

673+
/// Functor for Operand::getUser()
674+
struct OperandToUser;
675+
using OperandUserRange = TransformRange<ArrayRef<Operand *>, OperandToUser>;
676+
673677
static OperandValueRange getOperandValues(ArrayRef<Operand*> operands);
674678

675679
OperandRefValueRange getOperandValues() const;
@@ -1021,6 +1025,12 @@ struct SILInstruction::OperandRefToValue {
10211025
}
10221026
};
10231027

1028+
struct SILInstruction::OperandToUser {
1029+
SILInstruction *operator()(const Operand *use) const {
1030+
return const_cast<Operand *>(use)->getUser();
1031+
}
1032+
};
1033+
10241034
struct SILInstruction::FilterOperandToRealOperand {
10251035
const SILInstruction &i;
10261036

lib/SIL/Utils/OwnershipUtils.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -975,8 +975,9 @@ computeTransitiveLiveness(MultiDefPrunedLiveness &liveness) const {
975975
});
976976
}
977977

978-
bool BorrowedValue::areUsesWithinExtendedScope(
979-
ArrayRef<Operand *> uses, DeadEndBlocks *deadEndBlocks) const {
978+
template <typename Instructions>
979+
bool BorrowedValue::areWithinExtendedScope(Instructions insts,
980+
DeadEndBlocks *deadEndBlocks) const {
980981
// First make sure that we actually have a local scope. If we have a non-local
981982
// scope, then we have something (like a SILFunctionArgument) where a larger
982983
// semantic construct (in the case of SILFunctionArgument, the function
@@ -988,7 +989,20 @@ bool BorrowedValue::areUsesWithinExtendedScope(
988989
// Compute the local scope's liveness.
989990
MultiDefPrunedLiveness liveness(value->getFunction());
990991
computeTransitiveLiveness(liveness);
991-
return liveness.areUsesWithinBoundary(uses, deadEndBlocks);
992+
return liveness.areWithinBoundary(insts, deadEndBlocks);
993+
}
994+
995+
template bool BorrowedValue::areWithinExtendedScope<UsePointInstructionRange>(
996+
UsePointInstructionRange insts, DeadEndBlocks *deadEndBlocks) const;
997+
998+
template bool
999+
BorrowedValue::areWithinExtendedScope<SILInstruction::OperandUserRange>(
1000+
SILInstruction::OperandUserRange insts, DeadEndBlocks *deadEndBlocks) const;
1001+
1002+
bool BorrowedValue::areUsesWithinExtendedScope(
1003+
ArrayRef<Operand *> uses, DeadEndBlocks *deadEndBlocks) const {
1004+
SILInstruction::OperandUserRange users(uses, SILInstruction::OperandToUser());
1005+
return areWithinExtendedScope(users, deadEndBlocks);
9921006
}
9931007

9941008
// The visitor \p func is only called on final scope-ending uses, not reborrows.

lib/SIL/Utils/PrunedLiveness.cpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -796,14 +796,8 @@ static FunctionTest SSAPrunedLiveness__areUsesWithinBoundary(
796796
template <typename LivenessWithDefs>
797797
bool PrunedLiveRange<LivenessWithDefs>::areUsesWithinBoundary(
798798
ArrayRef<Operand *> uses, DeadEndBlocks *deadEndBlocks) const {
799-
assert(asImpl().isInitialized());
800-
801-
for (auto *use : uses) {
802-
auto *user = use->getUser();
803-
if (!isWithinBoundary(user, deadEndBlocks))
804-
return false;
805-
}
806-
return true;
799+
SILInstruction::OperandUserRange users(uses, SILInstruction::OperandToUser());
800+
return areWithinBoundary(users, deadEndBlocks);
807801
}
808802

809803
template <typename LivenessWithDefs>

lib/SILOptimizer/SemanticARC/CopyValueOpts.cpp

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "swift/SIL/MemAccessUtils.h"
2828
#include "swift/SIL/OwnershipUtils.h"
2929
#include "swift/SIL/Projection.h"
30+
#include "swift/SIL/Test.h"
3031

3132
using namespace swift;
3233
using namespace swift::semanticarc;
@@ -170,7 +171,7 @@ bool SemanticARCOptVisitor::performGuaranteedCopyValueOptimization(
170171
return borrowScope.isLocalScope();
171172
});
172173

173-
auto destroys = lr.getDestroyingUses();
174+
auto destroys = lr.getDestroyingInsts();
174175
if (destroys.empty() && haveAnyLocalScopes) {
175176
return false;
176177
}
@@ -196,8 +197,8 @@ bool SemanticARCOptVisitor::performGuaranteedCopyValueOptimization(
196197
// block.
197198
{
198199
if (llvm::any_of(borrowScopeIntroducers, [&](BorrowedValue borrowScope) {
199-
return !borrowScope.areUsesWithinExtendedScope(
200-
lr.getAllConsumingUses(), nullptr);
200+
return !borrowScope.areWithinExtendedScope(lr.getAllConsumingInsts(),
201+
nullptr);
201202
})) {
202203
LLVM_DEBUG(llvm::dbgs() << "copy_value is extending borrow introducer "
203204
"lifetime, bailing out\n");
@@ -238,8 +239,8 @@ bool SemanticARCOptVisitor::performGuaranteedCopyValueOptimization(
238239
}
239240

240241
if (llvm::any_of(borrowScopeIntroducers, [&](BorrowedValue borrowScope) {
241-
return !borrowScope.areUsesWithinExtendedScope(
242-
phiArgLR.getAllConsumingUses(), nullptr);
242+
return !borrowScope.areWithinExtendedScope(
243+
phiArgLR.getAllConsumingInsts(), nullptr);
243244
})) {
244245
return false;
245246
}
@@ -828,6 +829,20 @@ bool SemanticARCOptVisitor::tryPerformOwnedCopyValueOptimization(
828829
return true;
829830
}
830831

832+
namespace swift::test {
833+
static FunctionTest SemanticARCOptsCopyValueOptsGuaranteedValueOptTest(
834+
"semantic_arc_opts__copy_value_opts__guaranteed_value_opt",
835+
[](auto &function, auto &arguments, auto &test) {
836+
SemanticARCOptVisitor visitor(function, test.getPassManager(),
837+
*test.getDeadEndBlocks(),
838+
/*onlyMandatoryOpts=*/false);
839+
840+
visitor.performGuaranteedCopyValueOptimization(
841+
cast<CopyValueInst>(arguments.takeInstruction()));
842+
function.print(llvm::errs());
843+
});
844+
} // end namespace swift::test
845+
831846
//===----------------------------------------------------------------------===//
832847
// Top Level Entrypoint
833848
//===----------------------------------------------------------------------===//

lib/SILOptimizer/SemanticARC/OwnedToGuaranteedPhiOpt.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,11 @@ bool swift::semanticarc::tryConvertOwnedPhisToGuaranteedPhis(Context &ctx) {
212212
SmallVector<std::pair<SILValue, unsigned>, 8> incomingValueUpdates;
213213
for (auto introducer : ownedValueIntroducers) {
214214
SILValue v = introducer.value;
215-
OwnershipLiveRange lr(v);
215+
SmallVector<std::pair<Operand *, SILValue>, 4> extraConsumes;
216+
for (auto op : incomingValueOperandList) {
217+
extraConsumes.push_back({op.getOperand(), op.getOriginal()});
218+
}
219+
OwnershipLiveRange lr(v, extraConsumes);
216220

217221
// For now, we only handle copy_value for simplicity.
218222
//

lib/SILOptimizer/SemanticARC/OwnershipLiveRange.cpp

Lines changed: 46 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,25 @@
1515

1616
#include "swift/Basic/Assertions.h"
1717
#include "swift/SIL/BasicBlockUtils.h"
18+
#include "swift/SIL/OSSALifetimeCompletion.h"
1819
#include "swift/SIL/OwnershipUtils.h"
1920

2021
using namespace swift;
2122
using namespace swift::semanticarc;
2223

23-
OwnershipLiveRange::OwnershipLiveRange(SILValue value)
24+
OwnershipLiveRange::OwnershipLiveRange(
25+
SILValue value, ArrayRef<std::pair<Operand *, SILValue>> extraUses)
2426
: introducer(OwnedValueIntroducer::get(value)), destroyingUses(),
2527
ownershipForwardingUses(), unknownConsumingUses() {
2628
assert(introducer);
2729
assert(introducer.value->getOwnershipKind() == OwnershipKind::Owned);
2830

29-
SmallVector<Operand *, 32> tmpDestroyingUses;
30-
SmallVector<Operand *, 32> tmpForwardingConsumingUses;
31-
SmallVector<Operand *, 32> tmpUnknownConsumingUses;
31+
SmallVector<UsePoint, 32> tmpDestroyingUses;
32+
SmallVector<UsePoint, 32> tmpForwardingConsumingUses;
33+
SmallVector<UsePoint, 32> tmpUnknownConsumingUses;
34+
35+
SmallVector<SILValue, 4> defs;
36+
defs.push_back(value);
3237

3338
// We know that our silvalue produces an @owned value. Look through all of our
3439
// uses and classify them as either consuming or not.
@@ -111,6 +116,7 @@ OwnershipLiveRange::OwnershipLiveRange(SILValue value)
111116
continue;
112117
}
113118
llvm::copy(v->getUses(), std::back_inserter(worklist));
119+
defs.push_back(v);
114120
}
115121
continue;
116122
}
@@ -138,8 +144,32 @@ OwnershipLiveRange::OwnershipLiveRange(SILValue value)
138144
// Otherwise add all users of this BBArg to the worklist to visit
139145
// recursively.
140146
llvm::copy(succArg->getUses(), std::back_inserter(worklist));
147+
defs.push_back(succArg);
148+
}
149+
}
150+
}
151+
152+
for (auto def : defs) {
153+
if (def->use_begin() == def->use_end()) {
154+
continue;
155+
}
156+
SmallVector<SILBasicBlock *, 32> blocks;
157+
SSAPrunedLiveness liveness(def->getFunction(), &blocks);
158+
liveness.initializeDef(def);
159+
liveness.computeSimple();
160+
for (auto use : extraUses) {
161+
if (use.second != def) {
162+
continue;
141163
}
164+
liveness.updateForUse(use.first->getUser(), /*lifetimeEnding=*/true);
142165
}
166+
OSSALifetimeCompletion::visitAvailabilityBoundary(
167+
def, liveness, [&tmpDestroyingUses](auto *inst, auto end) {
168+
if (end != OSSALifetimeCompletion::LifetimeEnd::Boundary) {
169+
return;
170+
}
171+
tmpDestroyingUses.push_back(inst);
172+
});
143173
}
144174

145175
// The order in which we append these to consumingUses matters since we assume
@@ -248,7 +278,8 @@ void OwnershipLiveRange::insertEndBorrowsAtDestroys(
248278

249279
void OwnershipLiveRange::convertOwnedGeneralForwardingUsesToGuaranteed() && {
250280
while (!ownershipForwardingUses.empty()) {
251-
auto *use = ownershipForwardingUses.back();
281+
auto point = ownershipForwardingUses.back();
282+
auto *use = point.getOperand();
252283
ownershipForwardingUses = ownershipForwardingUses.drop_back();
253284
ForwardingOperand operand(use);
254285
operand.replaceOwnershipKind(OwnershipKind::Owned,
@@ -260,9 +291,12 @@ void OwnershipLiveRange::convertToGuaranteedAndRAUW(
260291
SILValue newGuaranteedValue, InstModCallbacks callbacks) && {
261292
auto *value = cast<SingleValueInstruction>(introducer.value);
262293
while (!destroyingUses.empty()) {
263-
auto *d = destroyingUses.back();
294+
auto point = destroyingUses.back();
295+
auto *destroy = point.getInstruction();
264296
destroyingUses = destroyingUses.drop_back();
265-
callbacks.deleteInst(d->getUser());
297+
if (isa<TermInst>(destroy))
298+
continue;
299+
callbacks.deleteInst(destroy);
266300
}
267301

268302
callbacks.eraseAndRAUWSingleValueInst(value, newGuaranteedValue);
@@ -318,9 +352,12 @@ void OwnershipLiveRange::convertJoinedLiveRangePhiToGuaranteed(
318352

319353
// Then eliminate all of the destroys...
320354
while (!destroyingUses.empty()) {
321-
auto *d = destroyingUses.back();
355+
auto point = destroyingUses.back();
356+
auto *destroy = point.getInstruction();
322357
destroyingUses = destroyingUses.drop_back();
323-
callbacks.deleteInst(d->getUser());
358+
if (isa<TermInst>(destroy))
359+
continue;
360+
callbacks.deleteInst(destroy);
324361
}
325362

326363
// and change all of our guaranteed forwarding insts to have guaranteed
@@ -362,13 +399,3 @@ OwnershipLiveRange::hasUnknownConsumingUse(bool assumingAtFixPoint) const {
362399
// to our introducer.
363400
return HasConsumingUse_t::YesButAllPhiArgs;
364401
}
365-
366-
OwnershipLiveRange::DestroyingInstsRange
367-
OwnershipLiveRange::getDestroyingInsts() const {
368-
return DestroyingInstsRange(getDestroyingUses(), OperandToUser());
369-
}
370-
371-
OwnershipLiveRange::ConsumingInstsRange
372-
OwnershipLiveRange::getAllConsumingInsts() const {
373-
return ConsumingInstsRange(consumingUses, OperandToUser());
374-
}

0 commit comments

Comments
 (0)