Skip to content

Commit c1716c9

Browse files
committed
[PrunedLiveness] Add extendToNonUse.
And use it in lifetime extension/maximization. The new member function differs from updateForUse in that it doesn't overwrite the old value for lifetime ending associated with the instruction (calling updateForUse with lifetimeEnding=false overwrites the flag set by a previous call with lifetimeEnding=true because if an instruction both consumes and doesn't consume a copy-extended value, the value must be live after the instruction).
1 parent 61984e0 commit c1716c9

File tree

4 files changed

+321
-45
lines changed

4 files changed

+321
-45
lines changed

include/swift/SIL/PrunedLiveness.h

Lines changed: 84 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,61 @@ struct LiveRangeSummary {
353353
/// necessarily include liveness up to destroy_value or end_borrow
354354
/// instructions.
355355
class PrunedLiveness {
356+
public:
357+
/// A tristate describing how an instruction uses the value:
358+
/// - non-ending: the value's lifetime does not end at the instruction;
359+
/// results from updateForUse(, lifetimeEnding: false)
360+
/// - ending: the value's lifetime ends at the instruction; results from
361+
/// updateForUse(, lifetimeEnding: true)
362+
/// - non-use: the instruction doesn't use the value but liveness was extended
363+
/// to it; results from extendToNonUse
364+
///
365+
/// If an instruction is added to liveness with multiple LifetimeEnding
366+
/// instances, the stored instance needs to be updated appropriately, taking
367+
/// into account the instances that were already seen.
368+
///
369+
/// For example, if Nonuse is seen after Ending, it is ignored: that the
370+
/// instruction ends the lifetime takes priority: the instruction doesn't end
371+
/// the lifetime of the value any less because liveness was extended to it.
372+
///
373+
/// Similarly, if Ending is seen after NonEnding, it is ignored: that the
374+
/// instruction ends the lifetime of a copy of the value can't change the fact
375+
/// that liveness already extends _beyond_ the instruction because it was
376+
/// already recognized as a non-ending use.
377+
///
378+
/// This relationship of "overriding" is captured by the order of the cases in
379+
/// LifetimeEnding::Value and which case overrides another is computed by
380+
/// taking the meet--i.e. the lesser of the two cases overrides.
381+
///
382+
/// Note: Taking the meet may not be appropriate for branch instructions
383+
/// which may need to be recognized as lifetime-ending when they have
384+
/// as uses both a reborrow and a guaranteed phi.
385+
struct LifetimeEnding {
386+
enum class Value {
387+
// The instruction doesn't consume the value.
388+
NonEnding,
389+
// The instruction consumes the value.
390+
Ending,
391+
// The instruction doesn't use the value.
392+
NonUse,
393+
};
394+
Value value;
395+
396+
LifetimeEnding(Value value) : value(value) {}
397+
explicit LifetimeEnding(bool lifetimeEnding)
398+
: value(lifetimeEnding ? Value::Ending : Value::NonEnding) {}
399+
operator Value() const { return value; }
400+
LifetimeEnding meet(LifetimeEnding const other) const {
401+
return value < other.value ? *this : other;
402+
}
403+
void meetInPlace(LifetimeEnding const other) { *this = meet(other); }
404+
bool isEnding() const { return value == Value::Ending; }
405+
406+
static LifetimeEnding NonUse() { return {Value::NonUse}; };
407+
static LifetimeEnding Ending() { return {Value::Ending}; };
408+
static LifetimeEnding NonEnding() { return {Value::NonEnding}; };
409+
};
410+
356411
protected:
357412
PrunedLiveBlocks liveBlocks;
358413

@@ -365,7 +420,7 @@ class PrunedLiveness {
365420
// they may be the last use in the block.
366421
//
367422
// Non-lifetime-ending within a LiveOut block are uninteresting.
368-
llvm::SmallMapVector<SILInstruction *, bool, 8> users;
423+
llvm::SmallMapVector<SILInstruction *, LifetimeEnding, 8> users;
369424

370425
public:
371426
PrunedLiveness(SILFunction *function,
@@ -411,11 +466,12 @@ class PrunedLiveness {
411466
auto useIter = users.find(user);
412467
if (useIter == users.end())
413468
return NonUser;
414-
return useIter->second ? LifetimeEndingUse : NonLifetimeEndingUse;
469+
return useIter->second == LifetimeEnding::Ending() ? LifetimeEndingUse
470+
: NonLifetimeEndingUse;
415471
}
416472

417473
using ConstUserRange =
418-
iterator_range<const std::pair<SILInstruction *, bool> *>;
474+
iterator_range<const std::pair<SILInstruction *, LifetimeEnding> *>;
419475
ConstUserRange getAllUsers() const {
420476
return llvm::make_range(users.begin(), users.end());
421477
}
@@ -425,49 +481,53 @@ class PrunedLiveness {
425481
/// IDE.
426482
struct RangeIterationHelpers {
427483
struct MapFunctor {
428-
SILInstruction *
429-
operator()(const std::pair<SILInstruction *, bool> &pair) const {
484+
SILInstruction *operator()(
485+
const std::pair<SILInstruction *, LifetimeEnding> &pair) const {
430486
// Strip off the const to ease use with other APIs.
431487
return const_cast<SILInstruction *>(pair.first);
432488
}
433489
};
434490

435-
struct LifetimeEnding {
491+
struct IsLifetimeEnding {
436492
struct FilterFunctor {
437-
bool operator()(const std::pair<SILInstruction *, bool> &pair) const {
438-
return pair.second;
493+
bool operator()(
494+
const std::pair<SILInstruction *, LifetimeEnding> &pair) const {
495+
return pair.second.isEnding();
439496
}
440497
};
441498

442499
using MapFilterIter = llvm::mapped_iterator<
443-
llvm::filter_iterator<const std::pair<SILInstruction *, bool> *,
444-
FilterFunctor>,
500+
llvm::filter_iterator<
501+
const std::pair<SILInstruction *, LifetimeEnding> *,
502+
FilterFunctor>,
445503
MapFunctor>;
446504
};
447505

448506
struct NonLifetimeEnding {
449507
struct FilterFunctor {
450-
bool operator()(const std::pair<SILInstruction *, bool> &pair) const {
451-
return !pair.second;
508+
bool operator()(
509+
const std::pair<SILInstruction *, LifetimeEnding> &pair) const {
510+
return !pair.second.isEnding();
452511
}
453512
};
454513

455514
using MapFilterIter = llvm::mapped_iterator<
456-
llvm::filter_iterator<const std::pair<SILInstruction *, bool> *,
457-
FilterFunctor>,
515+
llvm::filter_iterator<
516+
const std::pair<SILInstruction *, LifetimeEnding> *,
517+
FilterFunctor>,
458518
MapFunctor>;
459519
};
460520
};
461521
using LifetimeEndingUserRange = llvm::iterator_range<
462-
RangeIterationHelpers::LifetimeEnding::MapFilterIter>;
522+
RangeIterationHelpers::IsLifetimeEnding::MapFilterIter>;
463523

464524
/// Return a range consisting of the current set of consuming users fed into
465525
/// this PrunedLiveness instance.
466526
LifetimeEndingUserRange getLifetimeEndingUsers() const {
467527
return map_range(
468528
llvm::make_filter_range(
469529
getAllUsers(),
470-
RangeIterationHelpers::LifetimeEnding::FilterFunctor()),
530+
RangeIterationHelpers::IsLifetimeEnding::FilterFunctor()),
471531
RangeIterationHelpers::MapFunctor());
472532
}
473533

@@ -552,6 +612,8 @@ class PrunedLiveRange : public PrunedLiveness {
552612
ValueSet &visited,
553613
SILValue value);
554614

615+
void updateForUse(SILInstruction *user, LifetimeEnding lifetimeEnding);
616+
555617
public:
556618
/// For flexibility, \p lifetimeEnding is provided by the
557619
/// caller. PrunedLiveness makes no assumptions about the def-use
@@ -560,6 +622,12 @@ class PrunedLiveRange : public PrunedLiveness {
560622
/// live range vs. a nested borrow scope within the extended live range.
561623
void updateForUse(SILInstruction *user, bool lifetimeEnding);
562624

625+
/// Adds \p inst which doesn't use the def to liveness.
626+
///
627+
/// Different from calling updateForUse because it never overrides the value
628+
/// \p lifetimeEnding stored for \p inst.
629+
void extendToNonUse(SILInstruction *inst);
630+
563631
/// Updates the liveness for a whole borrow scope, beginning at \p op.
564632
/// Returns false if this cannot be done. This assumes that nested OSSA
565633
/// lifetimes are complete.

lib/SIL/Utils/PrunedLiveness.cpp

Lines changed: 82 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,17 @@ void PrunedLiveBlocks::dump() const {
8585
void PrunedLiveness::print(llvm::raw_ostream &OS) const {
8686
liveBlocks.print(OS);
8787
for (auto &userAndIsLifetimeEnding : users) {
88-
if (userAndIsLifetimeEnding.second)
88+
switch (userAndIsLifetimeEnding.second) {
89+
case LifetimeEnding::Value::NonUse:
90+
OS << "non-user: ";
91+
break;
92+
case LifetimeEnding::Value::Ending:
8993
OS << "lifetime-ending user: ";
90-
else
94+
break;
95+
case LifetimeEnding::Value::NonEnding:
9196
OS << "regular user: ";
97+
break;
98+
}
9299
userAndIsLifetimeEnding.first->print(OS);
93100
}
94101
}
@@ -185,8 +192,9 @@ static FunctionTest
185192
//===----------------------------------------------------------------------===//
186193

187194
template <typename LivenessWithDefs>
188-
void PrunedLiveRange<LivenessWithDefs>::updateForUse(SILInstruction *user,
189-
bool lifetimeEnding) {
195+
void PrunedLiveRange<LivenessWithDefs>::updateForUse(
196+
SILInstruction *user,
197+
PrunedLiveRange<LivenessWithDefs>::LifetimeEnding lifetimeEnding) {
190198
liveBlocks.updateForUse(user, asImpl().isUserBeforeDef(user));
191199

192200
// Note that a user may use the current value from multiple operands. If any
@@ -202,7 +210,17 @@ void PrunedLiveRange<LivenessWithDefs>::updateForUse(SILInstruction *user,
202210
// argument must be copied.
203211
auto iterAndSuccess = users.insert({user, lifetimeEnding});
204212
if (!iterAndSuccess.second)
205-
iterAndSuccess.first->second &= lifetimeEnding;
213+
iterAndSuccess.first->second.meetInPlace(lifetimeEnding);
214+
}
215+
template <typename LivenessWithDefs>
216+
void PrunedLiveRange<LivenessWithDefs>::updateForUse(SILInstruction *user,
217+
bool lifetimeEnding) {
218+
updateForUse(user, LifetimeEnding(lifetimeEnding));
219+
}
220+
221+
template <typename LivenessWithDefs>
222+
void PrunedLiveRange<LivenessWithDefs>::extendToNonUse(SILInstruction *inst) {
223+
updateForUse(inst, LifetimeEnding::NonUse());
206224
}
207225

208226
template <typename LivenessWithDefs>
@@ -257,7 +275,7 @@ template <typename LivenessWithDefs>
257275
void PrunedLiveRange<LivenessWithDefs>::extendAcrossLiveness(
258276
PrunedLiveness &otherLiveness) {
259277
// update this liveness for all the interesting users in otherLiveness.
260-
for (std::pair<SILInstruction *, bool> userAndEnd :
278+
for (std::pair<SILInstruction *, LifetimeEnding> userAndEnd :
261279
otherLiveness.getAllUsers()) {
262280
updateForUse(userAndEnd.first, userAndEnd.second);
263281
}
@@ -371,6 +389,64 @@ static FunctionTest SSALivenessTest("ssa_liveness", [](auto &function,
371389
liveness.computeBoundary(boundary);
372390
boundary.print(llvm::outs());
373391
});
392+
393+
// Arguments:
394+
// - SILValue: def whose pruned liveness will be calculated
395+
// - the string "uses:"
396+
// - variadic list of live-range user instructions
397+
// Dumps:
398+
// -
399+
static FunctionTest SSAUseLivenessTest("ssa_use_liveness", [](auto &function,
400+
auto &arguments,
401+
auto &test) {
402+
auto value = arguments.takeValue();
403+
SmallVector<SILBasicBlock *, 8> discoveredBlocks;
404+
SSAPrunedLiveness liveness(&function, &discoveredBlocks);
405+
liveness.initializeDef(value);
406+
407+
auto argument = arguments.takeArgument();
408+
if (cast<StringArgument>(argument).getValue() != "uses:") {
409+
llvm::report_fatal_error("test specification expects the 'uses:' label\n");
410+
}
411+
412+
while (arguments.hasUntaken()) {
413+
auto *inst = arguments.takeInstruction();
414+
auto kindString = arguments.takeString();
415+
enum Kind {
416+
NonUse,
417+
Ending,
418+
NonEnding,
419+
};
420+
auto kind = llvm::StringSwitch<llvm::Optional<Kind>>(kindString)
421+
.Case("non-use", Kind::NonUse)
422+
.Case("ending", Kind::Ending)
423+
.Case("non-ending", Kind::NonEnding)
424+
.Default(llvm::None);
425+
if (!kind.has_value()) {
426+
llvm::errs() << "Unknown kind: " << kindString << "\n";
427+
llvm::report_fatal_error("Bad user kind. Value must be one of "
428+
"'non-use', 'ending', 'non-ending'");
429+
}
430+
switch (kind.value()) {
431+
case Kind::NonUse:
432+
liveness.extendToNonUse(inst);
433+
break;
434+
case Kind::Ending:
435+
liveness.updateForUse(inst, /*lifetimeEnding*/ true);
436+
break;
437+
case Kind::NonEnding:
438+
liveness.updateForUse(inst, /*lifetimeEnding*/ false);
439+
break;
440+
}
441+
}
442+
443+
liveness.print(llvm::outs());
444+
445+
PrunedLivenessBoundary boundary;
446+
liveness.computeBoundary(boundary);
447+
boundary.print(llvm::outs());
448+
});
449+
374450
} // end namespace swift::test
375451

376452
template <typename LivenessWithDefs>

lib/SILOptimizer/Utils/CanonicalizeOSSALifetime.cpp

Lines changed: 8 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -262,21 +262,10 @@ void CanonicalizeOSSALifetime::extendLivenessToDeinitBarriers() {
262262
OSSALifetimeCompletion::visitUnreachableLifetimeEnds(
263263
getCurrentDef(), completeLiveness, [&](auto *unreachable) {
264264
recordConsumingUser(unreachable);
265-
if (auto *previous = unreachable->getPreviousInstruction()) {
266-
if (liveness->isInterestingUser(previous) ==
267-
PrunedLiveness::IsInterestingUser::NonUser) {
268-
liveness->updateForUse(previous, /*lifetimeEnding=*/false);
269-
}
270-
return;
271-
}
272-
for (auto *predecessor :
273-
unreachable->getParent()->getPredecessorBlocks()) {
274-
auto *previous = &predecessor->back();
275-
if (liveness->isInterestingUser(previous) ==
276-
PrunedLiveness::IsInterestingUser::NonUser) {
277-
liveness->updateForUse(previous, /*lifetimeEnding=*/false);
278-
}
279-
}
265+
unreachable->visitPriorInstructions([&](auto *inst) {
266+
liveness->extendToNonUse(inst);
267+
return true;
268+
});
280269
});
281270

282271
auto *def = getCurrentDef()->getDefiningInstruction();
@@ -648,14 +637,10 @@ void CanonicalizeOSSALifetime::extendUnconsumedLiveness(
648637
continue;
649638
// Add "the instruction(s) before the terminator" of the predecessor to
650639
// liveness.
651-
if (auto *inst = predecessor->getTerminator()->getPreviousInstruction()) {
652-
liveness->updateForUse(inst, /*lifetimeEnding*/ false);
653-
} else {
654-
for (auto *grandPredecessor : predecessor->getPredecessorBlocks()) {
655-
liveness->updateForUse(grandPredecessor->getTerminator(),
656-
/*lifetimeEnding*/ false);
657-
}
658-
}
640+
predecessor->getTerminator()->visitPriorInstructions([&](auto *inst) {
641+
liveness->extendToNonUse(inst);
642+
return true;
643+
});
659644
}
660645
}
661646

0 commit comments

Comments
 (0)