mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
926 lines
32 KiB
C++
926 lines
32 KiB
C++
//===--- PartitionUtils.cpp -----------------------------------------------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2014 - 2023 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "swift/SILOptimizer/Utils/PartitionUtils.h"
|
|
|
|
// We only use this so we can implement print on our type erased errors.
|
|
#include "swift/SILOptimizer/Analysis/RegionAnalysis.h"
|
|
#include "swift/SILOptimizer/Utils/VariableNameUtils.h"
|
|
|
|
using namespace swift;
|
|
using namespace swift::PartitionPrimitives;
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: PartitionOpError
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void PartitionOpError::UnknownCodePatternError::print(
|
|
llvm::raw_ostream &os, RegionAnalysisValueMap &valueMap) const {
|
|
os << " Emitting Error. Kind: Unknown Code Pattern Error\n"
|
|
<< " Inst: " << *op->getSourceInst();
|
|
}
|
|
|
|
void PartitionOpError::LocalUseAfterSendError::print(
|
|
llvm::raw_ostream &os, RegionAnalysisValueMap &valueMap) const {
|
|
os << " Emitting Error. Kind: Use After Send\n"
|
|
<< " Sending Inst: " << *sendingOp->getUser()
|
|
<< " Sending Op Value: " << sendingOp->get()
|
|
<< " Require Inst: " << *op->getSourceInst() << " ID: %%"
|
|
<< sentElement << "\n"
|
|
<< " Rep: " << valueMap.getRepresentativeValue(sentElement)
|
|
<< " Sending Op Num: " << sendingOp->getOperandNumber() << '\n';
|
|
}
|
|
|
|
void PartitionOpError::SentNeverSendableError::print(
|
|
llvm::raw_ostream &os, RegionAnalysisValueMap &info) const {
|
|
os << " Emitting Error. Kind: Sent Non Sendable\n"
|
|
<< " ID: %%" << sentElement << "\n"
|
|
<< " Rep: " << *info.getRepresentative(sentElement)
|
|
<< " Dynamic Isolation Region: ";
|
|
isolationRegionInfo.printForOneLineLogging(os);
|
|
os << '\n';
|
|
if (auto isolatedValue = isolationRegionInfo->maybeGetIsolatedValue()) {
|
|
os << " Isolated Value: " << isolatedValue;
|
|
auto name = VariableNameInferrer::inferName(isolatedValue);
|
|
os << " Isolated Value Name: "
|
|
<< (name.has_value() ? name->get() : "none") << '\n';
|
|
} else {
|
|
os << " Isolated Value: none\n";
|
|
};
|
|
}
|
|
|
|
void PartitionOpError::AssignNeverSendableIntoSendingResultError::print(
|
|
llvm::raw_ostream &os, RegionAnalysisValueMap &valueMap) const {
|
|
os << " Emitting Error. Kind: Assign Isolated Into Sending Result!\n"
|
|
<< " Assign Inst: " << *op->getSourceInst()
|
|
<< " Dest Value: " << *destValue
|
|
<< " Dest Element: " << destElement << '\n'
|
|
<< " Src Value: " << srcValue
|
|
<< " Src Element: " << srcElement << '\n'
|
|
<< " Src Rep: " << valueMap.getRepresentativeValue(srcElement)
|
|
<< " Src Isolation: " << srcIsolationRegionInfo << '\n';
|
|
}
|
|
|
|
void PartitionOpError::InOutSendingNotInitializedAtExitError::print(
|
|
llvm::raw_ostream &os, RegionAnalysisValueMap &valueMap) const {
|
|
os << " Emitting Error. Kind: InOut Not Reinitialized At End Of "
|
|
"Function\n"
|
|
<< " Sending Inst: " << *sendingOp->getUser()
|
|
<< " Sending Op Value: " << sendingOp->get()
|
|
<< " Require Inst: " << *op->getSourceInst() << " ID: %%"
|
|
<< sentElement << "\n"
|
|
<< " Rep: " << valueMap.getRepresentativeValue(sentElement)
|
|
<< " Sending Op Num: " << sendingOp->getOperandNumber() << '\n';
|
|
}
|
|
|
|
void PartitionOpError::InOutSendingNotDisconnectedAtExitError::print(
|
|
llvm::raw_ostream &os, RegionAnalysisValueMap &valueMap) const {
|
|
os << " Emitting Error. Kind: InOut Sending ActorIsolated "
|
|
"at end of "
|
|
"Function Error!\n"
|
|
<< " ID: %%" << inoutSendingElement << "\n"
|
|
<< " Rep: " << valueMap.getRepresentativeValue(inoutSendingElement)
|
|
<< " Dynamic Isolation Region: ";
|
|
isolationInfo.printForOneLineLogging(os);
|
|
os << '\n';
|
|
}
|
|
|
|
void PartitionOpError::NonSendableIsolationCrossingResultError::print(
|
|
llvm::raw_ostream &os, RegionAnalysisValueMap &valueMap) const {
|
|
os << " Emitting Error. Kind: NonSendableIsolationCrossingResultError\n"
|
|
" Inst: "
|
|
<< *op->getSourceInst() << " Result ID: %%" << returnValueElement
|
|
<< '\n';
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: PartitionOp
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void PartitionOp::print(llvm::raw_ostream &os, bool extraSpace) const {
|
|
constexpr static char extraSpaceLiteral[10] = " ";
|
|
switch (opKind) {
|
|
case PartitionOpKind::Assign: {
|
|
os << "assign ";
|
|
if (extraSpace)
|
|
os << extraSpaceLiteral;
|
|
os << "%%" << opArgs[0] << " = %%" << opArgs[1];
|
|
break;
|
|
}
|
|
case PartitionOpKind::AssignFresh:
|
|
os << "assign_fresh %%" << opArgs[0];
|
|
break;
|
|
case PartitionOpKind::Send: {
|
|
os << "send ";
|
|
if (extraSpace)
|
|
os << extraSpaceLiteral;
|
|
os << "%%" << opArgs[0];
|
|
break;
|
|
}
|
|
case PartitionOpKind::UndoSend: {
|
|
os << "undo_send ";
|
|
if (extraSpace)
|
|
os << extraSpaceLiteral;
|
|
os << "%%" << opArgs[0];
|
|
break;
|
|
}
|
|
case PartitionOpKind::Merge: {
|
|
os << "merge ";
|
|
if (extraSpace)
|
|
os << extraSpaceLiteral;
|
|
os << "%%" << opArgs[0] << " with %%" << opArgs[1];
|
|
break;
|
|
}
|
|
case PartitionOpKind::Require: {
|
|
os << "require ";
|
|
if (extraSpace)
|
|
os << extraSpaceLiteral;
|
|
os << "%%" << opArgs[0];
|
|
break;
|
|
}
|
|
case PartitionOpKind::UnknownPatternError:
|
|
os << "unknown pattern error ";
|
|
os << "%%" << opArgs[0];
|
|
break;
|
|
case PartitionOpKind::InOutSendingAtFunctionExit:
|
|
os << "inout_sending_at_function_exit ";
|
|
if (extraSpace)
|
|
os << extraSpaceLiteral;
|
|
os << "%%" << opArgs[0];
|
|
break;
|
|
case PartitionOpKind::NonSendableIsolationCrossingResult:
|
|
os << "nonsendable_isolationcrossing_result ";
|
|
if (extraSpace)
|
|
os << extraSpaceLiteral;
|
|
os << "%%" << opArgs[0];
|
|
break;
|
|
}
|
|
os << ": " << *getSourceInst();
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: Partition
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
Partition Partition::singleRegion(SILLocation loc, ArrayRef<Element> indices,
|
|
IsolationHistory inputHistory) {
|
|
Partition p(inputHistory);
|
|
if (!indices.empty()) {
|
|
// Lowest element is our region representative and the value that our
|
|
// region takes.
|
|
Element repElement = *std::min_element(indices.begin(), indices.end());
|
|
Region repElementRegion = Region(repElement);
|
|
p.freshLabel = Region(repElementRegion + 1);
|
|
|
|
// Place all of the operations until end of scope into one history
|
|
// sequence.
|
|
p.pushHistorySequenceBoundary(loc);
|
|
|
|
// First create a region for repElement. We are going to merge all other
|
|
// regions into its region.
|
|
p.pushNewElementRegion(repElement);
|
|
llvm::SmallVector<Element, 32> nonRepElts;
|
|
for (Element index : indices) {
|
|
p.elementToRegionMap.insert_or_assign(index, repElementRegion);
|
|
if (index != repElement) {
|
|
p.pushNewElementRegion(index);
|
|
nonRepElts.push_back(index);
|
|
}
|
|
p.pushMergeElementRegions(repElement, nonRepElts);
|
|
}
|
|
}
|
|
|
|
assert(p.is_canonical_correct());
|
|
return p;
|
|
}
|
|
|
|
Partition Partition::separateRegions(SILLocation loc, ArrayRef<Element> indices,
|
|
IsolationHistory inputHistory) {
|
|
Partition p(inputHistory);
|
|
if (indices.empty())
|
|
return p;
|
|
|
|
// Place all operations in one history sequence.
|
|
p.pushHistorySequenceBoundary(loc);
|
|
|
|
auto maxIndex = Element(0);
|
|
for (Element index : indices) {
|
|
p.elementToRegionMap.insert_or_assign(index, Region(index));
|
|
p.pushNewElementRegion(index);
|
|
maxIndex = Element(std::max(maxIndex, index));
|
|
}
|
|
p.freshLabel = Region(maxIndex + 1);
|
|
assert(p.is_canonical_correct());
|
|
return p;
|
|
}
|
|
|
|
void Partition::markSent(Element val, SendingOperandSet *sendingOperandSet) {
|
|
// First see if our val is tracked. If it is not tracked, insert it and mark
|
|
// its new region as sent.
|
|
if (!isTrackingElement(val)) {
|
|
elementToRegionMap.insert_or_assign(val, freshLabel);
|
|
pushNewElementRegion(val);
|
|
regionToSendingOpMap.insert({freshLabel, sendingOperandSet});
|
|
freshLabel = Region(freshLabel + 1);
|
|
canonical = false;
|
|
return;
|
|
}
|
|
|
|
// Otherwise, we already have this value in the map. Try to insert it.
|
|
auto iter1 = elementToRegionMap.find(val);
|
|
assert(iter1 != elementToRegionMap.end());
|
|
auto iter2 = regionToSendingOpMap.insert({iter1->second, sendingOperandSet});
|
|
|
|
// If we did insert, just return. We were not tracking any state.
|
|
if (iter2.second)
|
|
return;
|
|
|
|
// Otherwise, we need to merge the sets.
|
|
iter2.first->second = iter2.first->second->merge(sendingOperandSet);
|
|
}
|
|
|
|
bool Partition::undoSend(Element val) {
|
|
// First see if our val is tracked. If it is not tracked, insert it.
|
|
if (!isTrackingElement(val)) {
|
|
elementToRegionMap.insert_or_assign(val, freshLabel);
|
|
pushNewElementRegion(val);
|
|
freshLabel = Region(freshLabel + 1);
|
|
canonical = false;
|
|
return true;
|
|
}
|
|
|
|
// Otherwise, we already have this value in the map. Remove it from the
|
|
// "sending operand" map.
|
|
auto iter1 = elementToRegionMap.find(val);
|
|
assert(iter1 != elementToRegionMap.end());
|
|
return regionToSendingOpMap.erase(iter1->second);
|
|
}
|
|
|
|
void Partition::trackNewElement(Element newElt, bool updateHistory) {
|
|
SWIFT_DEFER { validateRegionToSendingOpMapRegions(); };
|
|
|
|
// First try to emplace newElt with fresh_label.
|
|
auto iter = elementToRegionMap.try_emplace(newElt, freshLabel);
|
|
|
|
// If we did insert, then we know that the value is completely new. We can
|
|
// just update the fresh_label, set canonical to false, and return.
|
|
if (iter.second) {
|
|
// Since the value is completely new, add a completely new history node to
|
|
// the history.
|
|
if (updateHistory)
|
|
pushNewElementRegion(newElt);
|
|
|
|
// Increment the fresh label so it remains fresh.
|
|
freshLabel = Region(freshLabel + 1);
|
|
canonical = false;
|
|
return;
|
|
}
|
|
|
|
// Otherwise, we have a bit more work that we need to perform:
|
|
//
|
|
// 1. We of course need to update iter to point at fresh_label.
|
|
//
|
|
// 2. We need to see if this value was the last element in its current
|
|
// region. If so, then we need to remove the region from the sending op
|
|
// map.
|
|
//
|
|
// This is important to ensure that every region in the sendingOpMap is
|
|
// also in elementToRegionMap.
|
|
auto oldRegion = iter.first->second;
|
|
iter.first->second = freshLabel;
|
|
|
|
auto getValueFromOtherRegion = [&]() -> std::optional<Element> {
|
|
for (auto pair : elementToRegionMap) {
|
|
if (pair.second == oldRegion)
|
|
return pair.first;
|
|
}
|
|
return {};
|
|
};
|
|
|
|
if (auto matchingElt = getValueFromOtherRegion()) {
|
|
if (updateHistory)
|
|
pushRemoveElementFromRegion(*matchingElt, newElt);
|
|
} else {
|
|
regionToSendingOpMap.erase(oldRegion);
|
|
if (updateHistory)
|
|
pushRemoveLastElementFromRegion(newElt);
|
|
}
|
|
|
|
if (updateHistory)
|
|
pushNewElementRegion(newElt);
|
|
|
|
// Increment the fresh label so it remains fresh.
|
|
freshLabel = Region(freshLabel + 1);
|
|
canonical = false;
|
|
}
|
|
|
|
/// Assigns \p oldElt to the region associated with \p newElt.
|
|
void Partition::assignElement(Element oldElt, Element newElt,
|
|
bool updateHistory) {
|
|
// If the old/new elt at the same, just return.
|
|
if (oldElt == newElt)
|
|
return;
|
|
|
|
SWIFT_DEFER { validateRegionToSendingOpMapRegions(); };
|
|
|
|
// First try to emplace oldElt with the newRegion.
|
|
auto newRegion = elementToRegionMap.at(newElt);
|
|
auto iter = elementToRegionMap.try_emplace(oldElt, newRegion);
|
|
|
|
// If we did an insert, then we know that oldElt was new to this
|
|
// partition. This means that we update our history for a completely new
|
|
// value in newElt's region. We also set canonical to false to ensure when
|
|
// ever we do a merge/etc, we renumber indices as appropriate.
|
|
if (iter.second) {
|
|
if (updateHistory) {
|
|
pushNewElementRegion(oldElt);
|
|
pushMergeElementRegions(newElt, oldElt);
|
|
}
|
|
canonical = false;
|
|
return;
|
|
}
|
|
|
|
// Otherwise, we did an assign.
|
|
auto oldRegion = iter.first->second;
|
|
|
|
// First check if oldRegion and newRegion are the same. In such a case, just
|
|
// return.
|
|
if (oldRegion == newRegion)
|
|
return;
|
|
|
|
// Otherwise, we need to actually assign. In such a case, we need to see if
|
|
// oldElt was the last element in oldRegion. If so, we need to erase the
|
|
// oldRegion from regionToSendingOpMap.
|
|
iter.first->second = newRegion;
|
|
|
|
auto getValueFromOtherRegion = [&]() -> std::optional<Element> {
|
|
for (auto pair : elementToRegionMap) {
|
|
if (pair.second == oldRegion)
|
|
return pair.first;
|
|
}
|
|
return {};
|
|
};
|
|
|
|
if (auto otherElt = getValueFromOtherRegion()) {
|
|
if (updateHistory)
|
|
pushRemoveElementFromRegion(*otherElt, oldElt);
|
|
} else {
|
|
regionToSendingOpMap.erase(oldRegion);
|
|
if (updateHistory)
|
|
pushRemoveLastElementFromRegion(oldElt);
|
|
}
|
|
|
|
if (updateHistory) {
|
|
pushNewElementRegion(oldElt);
|
|
pushMergeElementRegions(newElt, oldElt);
|
|
}
|
|
|
|
canonical = false;
|
|
}
|
|
|
|
Partition Partition::join(const Partition &fst, Partition &mutableSnd) {
|
|
// READ THIS! Remember, we cannot touch mutableSnd after this point. We just
|
|
// use it to canonicalize to avoid having to copy snd. After this point,
|
|
// please use the const reference snd to keep each other honest.
|
|
mutableSnd.canonicalize();
|
|
const auto &snd = mutableSnd;
|
|
|
|
// First copy fst into result and canonicalize the result.and canonicalize
|
|
// fst.
|
|
Partition result = fst;
|
|
result.canonicalize();
|
|
|
|
// Push a history join so when processing, we know the next element to
|
|
// process.
|
|
result.pushCFGHistoryJoin(snd.history);
|
|
|
|
// For each (sndEltNumber, sndRegionNumber) in snd_reduced...
|
|
for (auto pair : snd.elementToRegionMap) {
|
|
auto sndEltNumber = pair.first;
|
|
auto sndRegionNumber = pair.second;
|
|
|
|
// Check if result has sndEltNumber already within it...
|
|
{
|
|
auto resultIter = result.elementToRegionMap.find(sndEltNumber);
|
|
if (result.elementToRegionMap.end() != resultIter) {
|
|
auto resultRegion = resultIter->second;
|
|
|
|
// If we do and Element(sndRegionNumber) isn't the same element as
|
|
// sndEltNumber, then we know that sndEltNumber isn't the
|
|
// representative element of its region in sndReduced. We need to
|
|
// ensure that in result, that representative and our current
|
|
// value are in the same region. If they are the same value, we can
|
|
// just reuse sndEltNumber's region in result for the sending
|
|
// check.
|
|
if (sndEltNumber != Element(sndRegionNumber)) {
|
|
// NOTE: History is updated by Partition::merge(...).
|
|
resultRegion = result.merge(sndEltNumber, Element(sndRegionNumber));
|
|
}
|
|
|
|
// Then if sndRegionNumber is sent in sndReduced, make sure mergedRegion
|
|
// is sent in result.
|
|
auto sndIter = snd.regionToSendingOpMap.find(sndRegionNumber);
|
|
if (sndIter != snd.regionToSendingOpMap.end()) {
|
|
auto resultIter = result.regionToSendingOpMap.insert(
|
|
{resultRegion, sndIter->second});
|
|
if (!resultIter.second) {
|
|
resultIter.first->second =
|
|
resultIter.first->second->merge(sndIter->second);
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// At this point, we know that sndEltNumber is not in result.
|
|
//
|
|
// Check if the representative element number
|
|
// (i.e. Element(sndRegionNumber)) for this element in snd is in result. In
|
|
// that case, we know that we visited the representative number before we
|
|
// visited this elt number (since we are processing in order) so what ever
|
|
// is mapped to that number in snd must be the correct region for this
|
|
// element as well since this number is guaranteed to be greater than our
|
|
// representative and the number mapped to our representative in result must
|
|
// be
|
|
// <= our representative.
|
|
//
|
|
// In this case, we do not need to propagate 'send' into resultRegion
|
|
// since we would have handled that already when we visited our earlier
|
|
// representative element number.
|
|
{
|
|
auto iter = result.elementToRegionMap.find(Element(sndRegionNumber));
|
|
if (iter != result.elementToRegionMap.end()) {
|
|
result.elementToRegionMap.insert({sndEltNumber, iter->second});
|
|
result.pushMergeElementRegions(sndEltNumber, Element(sndRegionNumber));
|
|
// We want fresh_label to always be one element larger than our
|
|
// maximum element.
|
|
if (result.freshLabel <= Region(sndEltNumber))
|
|
result.freshLabel = Region(sndEltNumber + 1);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Otherwise, we have an element that is not in result and its
|
|
// representative is not in result. This means that we must be our
|
|
// representative in snd since we should have visited our representative
|
|
// earlier if we were not due to our traversal being in order. Thus just add
|
|
// this to result.
|
|
assert(sndEltNumber == Element(sndRegionNumber));
|
|
result.elementToRegionMap.insert({sndEltNumber, sndRegionNumber});
|
|
result.pushNewElementRegion(sndEltNumber);
|
|
auto sndIter = snd.regionToSendingOpMap.find(sndRegionNumber);
|
|
if (sndIter != snd.regionToSendingOpMap.end()) {
|
|
auto fstIter = result.regionToSendingOpMap.insert(
|
|
{sndRegionNumber, sndIter->second});
|
|
if (!fstIter.second)
|
|
fstIter.first->second = fstIter.first->second->merge(sndIter->second);
|
|
}
|
|
if (result.freshLabel <= sndRegionNumber)
|
|
result.freshLabel = Region(sndEltNumber + 1);
|
|
}
|
|
|
|
// We should have preserved canonicality during the computation above. It
|
|
// would be wasteful to need to canonicalize twice.
|
|
assert(result.is_canonical_correct());
|
|
|
|
// result is now the join.
|
|
return result;
|
|
}
|
|
|
|
bool Partition::popHistory(
|
|
SmallVectorImpl<IsolationHistory> &foundJoinedHistories) {
|
|
// We only allow for history rewinding if we are not tracking any
|
|
// sending operands. This is because the history rewinding does not
|
|
// care about sending. One can either construct a new Partition from
|
|
// the current Partition using Partition::removeSendingOperandSet or clear
|
|
// the sending information using Partition::clearSendingOperandState().
|
|
assert(regionToSendingOpMap.empty() &&
|
|
"Can only rewind history if not tracking any sending operands");
|
|
|
|
if (!history.getHead())
|
|
return false;
|
|
|
|
// Just put in a continue here to ensure that clang-format doesn't do weird
|
|
// things with the semicolon.
|
|
while (popHistoryOnce(foundJoinedHistories))
|
|
continue;
|
|
|
|
// Return if our history head is non-null so our user knows if there are more
|
|
// things to pop.
|
|
return history.getHead();
|
|
}
|
|
|
|
void Partition::print(llvm::raw_ostream &os) const {
|
|
SmallFrozenMultiMap<Region, Element, 8> multimap;
|
|
|
|
for (auto [eltNo, regionNo] : elementToRegionMap)
|
|
multimap.insert(regionNo, eltNo);
|
|
|
|
multimap.setFrozen();
|
|
|
|
os << "[";
|
|
for (auto [regionNo, elementNumbers] : multimap.getRange()) {
|
|
auto iter = regionToSendingOpMap.find(regionNo);
|
|
bool wasSent = iter != regionToSendingOpMap.end();
|
|
if (wasSent) {
|
|
os << '{';
|
|
} else {
|
|
os << '(';
|
|
}
|
|
|
|
int j = 0;
|
|
for (Element i : elementNumbers) {
|
|
os << (j++ ? " " : "") << i;
|
|
}
|
|
if (wasSent) {
|
|
os << '}';
|
|
} else {
|
|
os << ')';
|
|
}
|
|
}
|
|
os << "]\n";
|
|
}
|
|
|
|
void Partition::printVerbose(llvm::raw_ostream &os) const {
|
|
SmallFrozenMultiMap<Region, Element, 8> multimap;
|
|
|
|
for (auto [eltNo, regionNo] : elementToRegionMap)
|
|
multimap.insert(regionNo, eltNo);
|
|
|
|
multimap.setFrozen();
|
|
|
|
for (auto [regionNo, elementNumbers] : multimap.getRange()) {
|
|
auto iter = regionToSendingOpMap.find(regionNo);
|
|
bool wasSent = iter != regionToSendingOpMap.end();
|
|
|
|
os << "Region: " << regionNo << ". ";
|
|
if (wasSent) {
|
|
os << '{';
|
|
} else {
|
|
os << '(';
|
|
}
|
|
|
|
int j = 0;
|
|
for (Element i : elementNumbers) {
|
|
os << (j++ ? " " : "") << i;
|
|
}
|
|
if (wasSent) {
|
|
os << '}';
|
|
} else {
|
|
os << ')';
|
|
}
|
|
os << "\n";
|
|
os << "SentInsts:\n";
|
|
if (wasSent) {
|
|
for (auto op : iter->second->data()) {
|
|
os << " ";
|
|
op->print(os);
|
|
}
|
|
} else {
|
|
os << "None.\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
void Partition::printHistory(llvm::raw_ostream &os) const {
|
|
llvm::dbgs() << "History Dump!\n";
|
|
const auto *head = history.head;
|
|
|
|
if (!head)
|
|
return;
|
|
|
|
do {
|
|
switch (head->getKind()) {
|
|
case IsolationHistory::Node::AddNewRegionForElement:
|
|
os << "AddNewRegionForElement: " << head->getFirstArgAsElement();
|
|
break;
|
|
case IsolationHistory::Node::RemoveLastElementFromRegion:
|
|
os << "RemoveLastElementFromRegion: " << head->getFirstArgAsElement();
|
|
break;
|
|
case IsolationHistory::Node::RemoveElementFromRegion: {
|
|
os << "RemoveElementFromRegion: " << head->getFirstArgAsElement();
|
|
auto extraArgs = head->getAdditionalElementArgs();
|
|
if (extraArgs.empty())
|
|
break;
|
|
llvm::interleave(extraArgs, os, ", ");
|
|
break;
|
|
}
|
|
case IsolationHistory::Node::MergeElementRegions: {
|
|
os << "MergeElementRegions: " << head->getFirstArgAsElement();
|
|
auto extraArgs = head->getAdditionalElementArgs();
|
|
if (extraArgs.empty())
|
|
break;
|
|
os << ", ";
|
|
llvm::interleave(extraArgs, os, ", ");
|
|
break;
|
|
}
|
|
case IsolationHistory::Node::CFGHistoryJoin:
|
|
os << "CFGHistoryJoin";
|
|
break;
|
|
case IsolationHistory::Node::SequenceBoundary:
|
|
os << "SequenceBoundary";
|
|
break;
|
|
}
|
|
os << "\n";
|
|
|
|
} while ((head = head->getParent()));
|
|
}
|
|
|
|
bool Partition::is_canonical_correct() const {
|
|
#ifdef NDEBUG
|
|
return true;
|
|
#else
|
|
if (!canonical)
|
|
return true; // vacuously correct
|
|
|
|
auto fail = [&](Element i, int type) {
|
|
llvm::errs() << "FAIL(i=" << i << "; type=" << type << "): ";
|
|
print(llvm::errs());
|
|
return false;
|
|
};
|
|
|
|
for (auto &[eltNo, regionNo] : elementToRegionMap) {
|
|
// Labels should not exceed fresh_label.
|
|
if (regionNo >= freshLabel)
|
|
return fail(eltNo, 0);
|
|
|
|
// The label of a region should be at most as large as each index in it.
|
|
if ((unsigned)regionNo > eltNo)
|
|
return fail(eltNo, 1);
|
|
|
|
// Each region label should also be an element of the partition.
|
|
if (!elementToRegionMap.count(Element(regionNo)))
|
|
return fail(eltNo, 2);
|
|
|
|
// Each element that is also a region label should be mapped to itself.
|
|
if (elementToRegionMap.at(Element(regionNo)) != regionNo)
|
|
return fail(eltNo, 3);
|
|
}
|
|
|
|
// Before we do anything, validate region to region to sending op map.
|
|
validateRegionToSendingOpMapRegions();
|
|
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
Region Partition::merge(Element fst, Element snd, bool updateHistory) {
|
|
assert(elementToRegionMap.count(fst) && elementToRegionMap.count(snd));
|
|
|
|
// Remember: fstRegion and sndRegion are actually elements in
|
|
// elementToRegionMap... they are just the representative of the region
|
|
// (which is the smallest element number).
|
|
auto fstRegion = elementToRegionMap.at(fst);
|
|
auto sndRegion = elementToRegionMap.at(snd);
|
|
|
|
// Our value reps are the same... we can return either. Just return fstRegion.
|
|
if (fstRegion == sndRegion)
|
|
return fstRegion;
|
|
|
|
// To maintain canonicality, we require that fstRegion is always less than
|
|
// sndRegion. If we do not have that, swap first and second state.
|
|
if (fstRegion > sndRegion) {
|
|
std::swap(fst, snd);
|
|
std::swap(fstRegion, sndRegion);
|
|
}
|
|
|
|
Region result = fstRegion;
|
|
|
|
// Rename snd and snd's entire region to fst's region.
|
|
SmallVector<Element, 32> mergedElements;
|
|
horizontalUpdate(snd, fstRegion, mergedElements);
|
|
auto iter = regionToSendingOpMap.find(sndRegion);
|
|
if (iter != regionToSendingOpMap.end()) {
|
|
auto operand = iter->second;
|
|
regionToSendingOpMap.erase(iter);
|
|
regionToSendingOpMap.insert({fstRegion, operand});
|
|
}
|
|
|
|
assert(is_canonical_correct());
|
|
assert(elementToRegionMap.at(fst) == elementToRegionMap.at(snd));
|
|
|
|
// Now that we are correct/canonicalized, add the merge to our history.
|
|
if (updateHistory)
|
|
pushMergeElementRegions(fst, mergedElements);
|
|
return result;
|
|
}
|
|
|
|
void Partition::canonicalize() {
|
|
if (canonical)
|
|
return;
|
|
canonical = true;
|
|
|
|
validateRegionToSendingOpMapRegions();
|
|
std::map<Region, Region> oldRegionToRelabeledMap;
|
|
|
|
// We rely on in-order traversal of labels to ensure that we always take the
|
|
// lowest eltNumber.
|
|
for (auto &[eltNo, regionNo] : elementToRegionMap) {
|
|
if (!oldRegionToRelabeledMap.count(regionNo)) {
|
|
// if this is the first time encountering this region label,
|
|
// then this region label should be relabelled to this index,
|
|
// so enter that into the map
|
|
oldRegionToRelabeledMap.insert_or_assign(regionNo, Region(eltNo));
|
|
}
|
|
|
|
// Update this label with either its own index, or a prior index that
|
|
// shared a region with it.
|
|
regionNo = oldRegionToRelabeledMap.at(regionNo);
|
|
|
|
// The maximum index iterated over will be used here to appropriately
|
|
// set fresh_label.
|
|
freshLabel = Region(eltNo + 1);
|
|
}
|
|
|
|
// Then relabel our regionToSendingOpMap map if we need to by swapping out the
|
|
// old map and updating.
|
|
//
|
|
// TODO: If we just used an array for this, we could just rewrite and
|
|
// re-sort and not have to deal with potential allocations.
|
|
decltype(regionToSendingOpMap) oldMap = std::move(regionToSendingOpMap);
|
|
for (auto &[oldReg, op] : oldMap) {
|
|
auto iter = oldRegionToRelabeledMap.find(oldReg);
|
|
assert(iter != oldRegionToRelabeledMap.end());
|
|
regionToSendingOpMap[iter->second] = op;
|
|
}
|
|
|
|
assert(is_canonical_correct());
|
|
}
|
|
|
|
void Partition::horizontalUpdate(
|
|
Element targetElement, Region newRegion,
|
|
llvm::SmallVectorImpl<Element> &mergedElements) {
|
|
// It is on our caller to make sure a value is in elementToRegionMap.
|
|
Region oldRegion = elementToRegionMap.at(targetElement);
|
|
|
|
// If our old region is the same as our new region, we do not have anything
|
|
// to do.
|
|
if (oldRegion == newRegion)
|
|
return;
|
|
|
|
for (auto [element, region] : elementToRegionMap) {
|
|
if (region == oldRegion) {
|
|
elementToRegionMap.insert_or_assign(element, newRegion);
|
|
mergedElements.push_back(element);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Partition::popHistoryOnce(
|
|
SmallVectorImpl<IsolationHistory> &foundJoinedHistoryNodes) {
|
|
const auto *head = history.pop();
|
|
if (!head)
|
|
return false;
|
|
|
|
// When popping, we /always/ want to canonicalize.
|
|
canonicalize();
|
|
|
|
switch (head->getKind()) {
|
|
case IsolationHistory::Node::SequenceBoundary:
|
|
return false;
|
|
|
|
case IsolationHistory::Node::AddNewRegionForElement: {
|
|
// We added an element to its own region... so we should remove it and it
|
|
// should be the last element in the region.
|
|
auto iter = elementToRegionMap.find(head->getFirstArgAsElement());
|
|
assert(iter != elementToRegionMap.end());
|
|
Region oldRegion = iter->second;
|
|
regionToSendingOpMap.erase(oldRegion);
|
|
elementToRegionMap.erase(iter);
|
|
assert(llvm::none_of(elementToRegionMap,
|
|
[&](std::pair<Element, Region> pair) {
|
|
return pair.second == oldRegion;
|
|
}) &&
|
|
"Should have been last element?!");
|
|
return true;
|
|
}
|
|
case IsolationHistory::Node::RemoveLastElementFromRegion:
|
|
// We removed an element from a region and it was the last element. Just
|
|
// add new.
|
|
trackNewElement(head->getFirstArgAsElement(), false /*update history*/);
|
|
return true;
|
|
case IsolationHistory::Node::RemoveElementFromRegion:
|
|
// We removed an element from a specific region. So, we need to add it
|
|
// back.
|
|
assignElement(head->getFirstArgAsElement(),
|
|
head->getAdditionalElementArgs()[1],
|
|
false /*update history*/);
|
|
return true;
|
|
|
|
case IsolationHistory::Node::MergeElementRegions: {
|
|
// We merged two regions together. We need to remove all elements from the
|
|
// previous region into their own new region.
|
|
auto elementsToExtract = head->getAdditionalElementArgs();
|
|
assert(elementsToExtract.size());
|
|
|
|
removeElement(elementsToExtract[0]);
|
|
trackNewElement(elementsToExtract[0], false /*update history*/);
|
|
|
|
for (auto e : elementsToExtract.drop_front()) {
|
|
assert(head->getFirstArgAsElement() != e &&
|
|
"We assume that we are never removing all values when undoing "
|
|
"merging");
|
|
removeElement(e);
|
|
trackNewElement(e, false /*update history*/);
|
|
merge(e, elementsToExtract[0], false /*update history*/);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
case IsolationHistory::Node::CFGHistoryJoin:
|
|
// When we have a CFG History Merge, we cannot simply pop. Instead, we need
|
|
// to signal to the user that they need to visit each history node in turn
|
|
// by returning it in the out parameter.
|
|
auto newHistory = IsolationHistory(history.factory);
|
|
newHistory.head = head->getFirstArgAsNode();
|
|
foundJoinedHistoryNodes.push_back(newHistory);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// MARK: IsolationHistory
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
// Push onto the history list that \p value should be added into its own
|
|
// independent region.
|
|
IsolationHistory::Node *
|
|
IsolationHistory::pushNewElementRegion(Element element) {
|
|
unsigned size = Node::totalSizeToAlloc<Element>(0);
|
|
void *mem = factory->allocator.Allocate(size, alignof(Node));
|
|
head = new (mem) Node(Node::AddNewRegionForElement, head, element);
|
|
return getHead();
|
|
}
|
|
|
|
IsolationHistory::Node *
|
|
IsolationHistory::pushHistorySequenceBoundary(SILLocation loc) {
|
|
unsigned size = Node::totalSizeToAlloc<Element>(0);
|
|
void *mem = factory->allocator.Allocate(size, alignof(Node));
|
|
head = new (mem) Node(Node::SequenceBoundary, head, loc);
|
|
return getHead();
|
|
}
|
|
|
|
// Push onto the history that \p value should be removed from any region that it
|
|
// is apart of and placed within its own separate region.
|
|
void IsolationHistory::pushRemoveLastElementFromRegion(Element element) {
|
|
unsigned size = Node::totalSizeToAlloc<Element>(0);
|
|
void *mem = factory->allocator.Allocate(size, alignof(Node));
|
|
head = new (mem) Node(Node::RemoveLastElementFromRegion, head, element);
|
|
}
|
|
|
|
void IsolationHistory::pushRemoveElementFromRegion(
|
|
Element otherElementInOldRegion, Element element) {
|
|
unsigned size = Node::totalSizeToAlloc<Element>(1);
|
|
void *mem = factory->allocator.Allocate(size, alignof(Node));
|
|
head = new (mem) Node(Node::RemoveElementFromRegion, head, element,
|
|
{otherElementInOldRegion});
|
|
}
|
|
|
|
void IsolationHistory::pushMergeElementRegions(Element elementToMergeInto,
|
|
ArrayRef<Element> eltsToMerge) {
|
|
assert(llvm::none_of(eltsToMerge,
|
|
[&](Element elt) { return elt == elementToMergeInto; }));
|
|
unsigned size = Node::totalSizeToAlloc<Element>(eltsToMerge.size());
|
|
void *mem = factory->allocator.Allocate(size, alignof(Node));
|
|
head = new (mem)
|
|
Node(Node::MergeElementRegions, head, elementToMergeInto, eltsToMerge);
|
|
}
|
|
|
|
// Push that \p other should be merged into this region.
|
|
void IsolationHistory::pushCFGHistoryJoin(Node *otherNode) {
|
|
// If otherNode is nullptr or represents our same history, do not merge.
|
|
if (!otherNode || otherNode == head)
|
|
return;
|
|
|
|
// If we do not have any history, just take on the history of otherNode. We
|
|
// are going to merge our contents.
|
|
if (!head) {
|
|
head = otherNode;
|
|
return;
|
|
}
|
|
|
|
// Otherwise, create a node that joins our true head and other node as a side
|
|
// path we can follow.
|
|
unsigned size = Node::totalSizeToAlloc<Element>(0);
|
|
void *mem = factory->allocator.Allocate(size, alignof(Node));
|
|
head = new (mem) Node(Node(Node::CFGHistoryJoin, head, otherNode));
|
|
}
|
|
|
|
IsolationHistory::Node *IsolationHistory::pop() {
|
|
if (!head)
|
|
return nullptr;
|
|
|
|
auto *result = head;
|
|
head = head->parent;
|
|
return result;
|
|
}
|