Skip to content

Commit 15e17b2

Browse files
authored
Merge pull request #60867 from gottesmm/pr-e5ec659a62bef9590245df9b47f851d787746754
[pruned-liveness] Implement FieldSensitiveAddressPrunedLiveness.
2 parents f54e990 + 7c452ca commit 15e17b2

File tree

2 files changed

+529
-1
lines changed

2 files changed

+529
-1
lines changed

include/swift/SIL/PrunedLiveness.h

Lines changed: 287 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,9 @@
9494
#ifndef SWIFT_SILOPTIMIZER_UTILS_PRUNEDLIVENESS_H
9595
#define SWIFT_SILOPTIMIZER_UTILS_PRUNEDLIVENESS_H
9696

97+
#include "swift/AST/TypeExpansionContext.h"
9798
#include "swift/SIL/SILBasicBlock.h"
99+
#include "swift/SIL/SILFunction.h"
98100
#include "llvm/ADT/MapVector.h"
99101
#include "llvm/ADT/PointerIntPair.h"
100102
#include "llvm/ADT/SmallVector.h"
@@ -236,6 +238,8 @@ class PrunedLiveBlocks {
236238
assert(!discoveredBlocks || discoveredBlocks->empty());
237239
}
238240

241+
unsigned getNumBitsToTrack() const { return numBitsToTrack; }
242+
239243
bool empty() const { return liveBlocks.empty(); }
240244

241245
void clear() {
@@ -290,7 +294,6 @@ class PrunedLiveBlocks {
290294
return;
291295
}
292296

293-
assert(liveBlockIter->second.size() == 1);
294297
liveBlockIter->second.getLiveness(startBitNo, endBitNo, foundLivenessInfo);
295298
}
296299

@@ -553,6 +556,289 @@ struct PrunedLivenessBoundary {
553556
ArrayRef<SILBasicBlock *> postDomBlocks);
554557
};
555558

559+
/// Given a type T and a descendent field F in T's type tree, then the
560+
/// sub-element number of F is the first leaf element of the type tree in its
561+
/// linearized representation.
562+
struct SubElementNumber {
563+
unsigned number;
564+
565+
SubElementNumber(unsigned number) : number(number) {}
566+
567+
/// Given an arbitrary projection \p projectionFromRoot from the \p
568+
/// rootAddress, compute the sub element number for that \p SILValue. The sub
569+
/// element number of a type T is always the index of its first leaf node
570+
/// descendent in the type tree.
571+
///
572+
/// DISCUSSION: This works for non-leaf types in the type tree as well as
573+
/// normal leaf elements. It is the index of the first leaf element that is a
574+
/// sub element of the root SILType that this projection will effect. The rest
575+
/// of the elements effected can be found by computing the number of leaf sub
576+
/// elements of \p projectionFromRoot's type and adding this to the result of
577+
/// this function.
578+
///
579+
/// \returns None if we didn't know how to compute sub-element for this
580+
/// projection.
581+
static Optional<SubElementNumber> compute(SILValue projectionFromRoot,
582+
SILValue root);
583+
584+
operator unsigned() const { return number; }
585+
};
586+
587+
/// Given a type T, this is the number of leaf field types in T's type tree. A
588+
/// leaf field type is a descendent field of T that does not have any
589+
/// descendent's itself.
590+
struct TypeSubElementCount {
591+
unsigned number;
592+
593+
TypeSubElementCount(unsigned number) : number(number) {}
594+
595+
/// Given a type \p type, compute the total number of leaf sub-elements of \p
596+
/// type in the type tree.
597+
///
598+
/// Some interesting properties of this computation:
599+
///
600+
/// 1. When applied to the root type, this equals the total number of bits of
601+
/// liveness that we track.
602+
///
603+
/// 2. When applied to a field type F of the type tree for a type T,
604+
/// computeNumLeafSubElements(F) when added to F's start sub element number
605+
/// will go to the next sibling node in the type tree, walking up the tree and
606+
/// attempting to find siblings if no further siblings exist.
607+
TypeSubElementCount(SILType type, SILModule &mod,
608+
TypeExpansionContext context);
609+
610+
TypeSubElementCount(SILValue value)
611+
: TypeSubElementCount(value->getType(), *value->getModule(),
612+
TypeExpansionContext(*value->getFunction())) {}
613+
614+
operator unsigned() const { return number; }
615+
};
616+
617+
/// A span of leaf elements in the sub-element break down of the linearization
618+
/// of the type tree of a type T.
619+
struct TypeTreeLeafTypeRange {
620+
SubElementNumber startEltOffset;
621+
SubElementNumber endEltOffset;
622+
623+
/// The leaf type range for the entire type tree.
624+
TypeTreeLeafTypeRange(SILValue rootAddress)
625+
: startEltOffset(0), endEltOffset(TypeSubElementCount(rootAddress)) {}
626+
627+
/// The leaf type sub-range of the type tree of \p rootAddress, consisting of
628+
/// \p projectedAddress and all of \p projectedAddress's descendent fields in
629+
/// the type tree.
630+
TypeTreeLeafTypeRange(SILValue projectedAddress, SILValue rootAddress)
631+
: startEltOffset(
632+
*SubElementNumber::compute(projectedAddress, rootAddress)),
633+
endEltOffset(startEltOffset + TypeSubElementCount(projectedAddress)) {}
634+
635+
/// Is the given leaf type specified by \p singleLeafElementNumber apart of
636+
/// our \p range of leaf type values in the our larger type.
637+
bool contains(SubElementNumber singleLeafElementNumber) const {
638+
return startEltOffset <= singleLeafElementNumber &&
639+
singleLeafElementNumber < endEltOffset;
640+
}
641+
642+
/// Returns true if either of this overlaps at all with the given range.
643+
bool contains(TypeTreeLeafTypeRange range) const {
644+
if (startEltOffset <= range.startEltOffset &&
645+
range.startEltOffset < endEltOffset)
646+
return true;
647+
648+
// If our start and end offsets, our extent is only 1 and we know that our
649+
// value
650+
unsigned rangeLastElt = range.endEltOffset - 1;
651+
if (range.startEltOffset == rangeLastElt)
652+
return false;
653+
654+
// Othrwise, see if endEltOffset - 1 is within the range.
655+
return startEltOffset <= rangeLastElt && rangeLastElt < endEltOffset;
656+
}
657+
};
658+
659+
/// This is exactly like pruned liveness except that instead of tracking a
660+
/// single bit of liveness, it tracks multiple bits of liveness for leaf type
661+
/// tree nodes of an allocation one is calculating pruned liveness for.
662+
///
663+
/// DISCUSSION: One can view a type T as a tree with recursively each field F of
664+
/// the type T being a child of T in the tree. We say recursively since the tree
665+
/// unfolds for F and its children as well.
666+
class FieldSensitiveAddressPrunedLiveness {
667+
PrunedLiveBlocks liveBlocks;
668+
669+
struct InterestingUser {
670+
TypeTreeLeafTypeRange subEltSpan;
671+
bool isConsuming;
672+
673+
InterestingUser(TypeTreeLeafTypeRange subEltSpan, bool isConsuming)
674+
: subEltSpan(subEltSpan), isConsuming(isConsuming) {}
675+
676+
InterestingUser &operator&=(bool otherValue) {
677+
isConsuming &= otherValue;
678+
return *this;
679+
}
680+
};
681+
682+
/// Map all "interesting" user instructions in this def's live range to a pair
683+
/// consisting of the SILValue that it uses and a flag indicating whether they
684+
/// must end the lifetime.
685+
///
686+
/// Lifetime-ending users are always on the boundary so are always
687+
/// interesting.
688+
///
689+
/// Non-lifetime-ending uses within a LiveWithin block are interesting because
690+
/// they may be the last use in the block.
691+
///
692+
/// Non-lifetime-ending within a LiveOut block are uninteresting.
693+
llvm::SmallMapVector<SILInstruction *, InterestingUser, 8> users;
694+
695+
/// The root address of our type tree.
696+
SILValue rootAddress;
697+
698+
public:
699+
FieldSensitiveAddressPrunedLiveness(
700+
SILFunction *fn, SILValue rootValue,
701+
SmallVectorImpl<SILBasicBlock *> *discoveredBlocks = nullptr)
702+
: liveBlocks(TypeSubElementCount(rootValue), discoveredBlocks),
703+
rootAddress(rootValue) {}
704+
705+
bool empty() const {
706+
assert(!liveBlocks.empty() || users.empty());
707+
return liveBlocks.empty();
708+
}
709+
710+
void clear() {
711+
liveBlocks.clear();
712+
users.clear();
713+
}
714+
715+
SILValue getRootAddress() const { return rootAddress; }
716+
717+
unsigned numLiveBlocks() const { return liveBlocks.numLiveBlocks(); }
718+
719+
/// If the constructor was provided with a vector to populate, then this
720+
/// returns the list of all live blocks with no duplicates.
721+
ArrayRef<SILBasicBlock *> getDiscoveredBlocks() const {
722+
return liveBlocks.getDiscoveredBlocks();
723+
}
724+
725+
using UserRange =
726+
iterator_range<const std::pair<SILInstruction *, InterestingUser> *>;
727+
UserRange getAllUsers() const {
728+
return llvm::make_range(users.begin(), users.end());
729+
}
730+
731+
using UserBlockRange = TransformRange<
732+
UserRange, function_ref<SILBasicBlock *(
733+
const std::pair<SILInstruction *, InterestingUser> &)>>;
734+
UserBlockRange getAllUserBlocks() const {
735+
function_ref<SILBasicBlock *(
736+
const std::pair<SILInstruction *, InterestingUser> &)>
737+
op;
738+
op = [](const std::pair<SILInstruction *, InterestingUser> &pair)
739+
-> SILBasicBlock * { return pair.first->getParent(); };
740+
return UserBlockRange(getAllUsers(), op);
741+
}
742+
743+
void initializeDefBlock(SILBasicBlock *defBB, TypeTreeLeafTypeRange span) {
744+
liveBlocks.initializeDefBlock(defBB, span.startEltOffset,
745+
span.endEltOffset);
746+
}
747+
748+
/// For flexibility, \p lifetimeEnding is provided by the
749+
/// caller. PrunedLiveness makes no assumptions about the def-use
750+
/// relationships that generate liveness. For example, use->isLifetimeEnding()
751+
/// cannot distinguish the end of the borrow scope that defines this extended
752+
/// live range vs. a nested borrow scope within the extended live range.
753+
///
754+
/// Also for flexibility, \p affectedAddress must be a derived projection from
755+
/// the base that \p user is affecting.
756+
void updateForUse(SILInstruction *user, TypeTreeLeafTypeRange span,
757+
bool lifetimeEnding);
758+
759+
void getBlockLiveness(
760+
SILBasicBlock *bb, TypeTreeLeafTypeRange span,
761+
SmallVectorImpl<PrunedLiveBlocks::IsLive> &resultingFoundLiveness) const {
762+
liveBlocks.getBlockLiveness(bb, span.startEltOffset, span.endEltOffset,
763+
resultingFoundLiveness);
764+
}
765+
766+
/// Return the liveness for this specific sub-element of our root value.
767+
PrunedLiveBlocks::IsLive getBlockLiveness(SILBasicBlock *bb,
768+
unsigned subElementNumber) const {
769+
SmallVector<PrunedLiveBlocks::IsLive, 1> isLive;
770+
liveBlocks.getBlockLiveness(bb, subElementNumber, subElementNumber + 1,
771+
isLive);
772+
return isLive[0];
773+
}
774+
775+
void getBlockLiveness(
776+
SILBasicBlock *bb,
777+
SmallVectorImpl<PrunedLiveBlocks::IsLive> &foundLiveness) const {
778+
liveBlocks.getBlockLiveness(bb, 0, liveBlocks.getNumBitsToTrack(),
779+
foundLiveness);
780+
}
781+
782+
enum IsInterestingUser { NonUser, NonLifetimeEndingUse, LifetimeEndingUse };
783+
784+
/// Return a result indicating whether the given user was identified as an
785+
/// interesting use of the current def and whether it ends the lifetime.
786+
std::pair<IsInterestingUser, Optional<TypeTreeLeafTypeRange>>
787+
isInterestingUser(SILInstruction *user) const {
788+
auto useIter = users.find(user);
789+
if (useIter == users.end())
790+
return {NonUser, None};
791+
auto isInteresting =
792+
useIter->second.isConsuming ? LifetimeEndingUse : NonLifetimeEndingUse;
793+
return {isInteresting, useIter->second.subEltSpan};
794+
}
795+
796+
unsigned getNumSubElements() const { return liveBlocks.getNumBitsToTrack(); }
797+
798+
/// Return true if \p inst occurs before the liveness boundary. Used when the
799+
/// client already knows that inst occurs after the start of liveness.
800+
void isWithinBoundary(SILInstruction *inst, SmallBitVector &outVector) const;
801+
};
802+
803+
/// Record the last use points and CFG edges that form the boundary of
804+
/// FieldSensitiveAddressPrunedLiveness. It does this on a per type tree leaf
805+
/// node basis.
806+
struct FieldSensitiveAddressPrunedLivenessBoundary {
807+
/// The list of last users and an associated SILValue that is the address that
808+
/// is being used. The address can be used to determine the start sub element
809+
/// number of the user in the type tree and the end sub element number.
810+
///
811+
/// TODO (MG): If we don't eventually need to store the SILValue here (I am
812+
/// not sure yet...), just store a tuple with the start/end sub element
813+
/// number.
814+
SmallVector<std::tuple<SILInstruction *, TypeTreeLeafTypeRange>, 8> lastUsers;
815+
816+
/// Blocks where the value was live out but had a successor that was dead.
817+
SmallVector<SILBasicBlock *, 8> boundaryEdges;
818+
819+
void clear() {
820+
lastUsers.clear();
821+
boundaryEdges.clear();
822+
}
823+
824+
/// Compute the boundary from the blocks discovered during liveness analysis.
825+
///
826+
/// Precondition: \p liveness.getDiscoveredBlocks() is a valid list of all
827+
/// live blocks with no duplicates.
828+
///
829+
/// The computed boundary will completely post-dominate, including dead end
830+
/// paths. The client should query DeadEndBlocks to ignore those dead end
831+
/// paths.
832+
void compute(const FieldSensitiveAddressPrunedLiveness &liveness);
833+
834+
private:
835+
void
836+
findLastUserInBlock(SILBasicBlock *bb,
837+
FieldSensitiveAddressPrunedLivenessBoundary &boundary,
838+
const FieldSensitiveAddressPrunedLiveness &liveness,
839+
unsigned subElementNumber);
840+
};
841+
556842
} // namespace swift
557843

558844
#endif

0 commit comments

Comments
 (0)