Files
swift-mirror/include/swift/Basic/FrozenMultiMap.h
Michael Gottesman b619d30908 [ownership] Track /all/ non consuming uses and emit errors for all of them instead of relying on jsut the last one in a block.
Beyond allowing us to emit better errors, this will allow me to (in a subsequent
commit) count the amount of uses that are "outside" of the linear lifetime. I
can then compare that against a passed in set of "non consuming uses". If the
count of the number of uses "outside" of the linear lifetime equals the count of
the passed in set of "non consuming uses", then I know that /all/ non consuming
uses that I am testing against are not co-incident with the linear lifetime,
meaning that they can not effect (in a local, direct sense) the linear lifetime.

I am going to use that information to determine when it is safe to convert an
inout form a load [copy] to a load_borrow in the face of local mutations. I can
pass the set of local mutations as non-consuming uses to a linear lifetime
consisting of the load [copy]/destroy_values and thus prove that no writes occur
in between the load [copy]/destroy_value meaning it is safe to conver them to
borrow form.

NOTE: The aforementioned optimization is an extension of an optimization already
in tree that just bails if we have any writes to an inout locally, which is so
unfortunate.
2020-05-06 22:36:53 -07:00

276 lines
9.7 KiB
C++

//===--- FrozenMultiMap.h ----------------------------------*- C++ --------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 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
//
//===----------------------------------------------------------------------===//
///
/// \file
///
/// A 2 stage multi-map for use in contexts where one is iterating over an IR in
/// a read only way and then wants to use a multi-map for post-processing that
/// can support iteration in insertion order.
///
/// In the first stage, the multi-map can be initialized with new elements in a
/// purely additive fashion, but methods that rely on the map being frozen
/// (find, getRange(), erase, etc) assert.
///
/// Once the user has finished iterating over the IR, the map is frozen and then
/// can only be used for map operations (find), erasing operations (erase), and
/// deterministic range iteration.
///
/// This is accomplished by the internal implementation of the frozen multi map
/// just being an array of (key, value) pairs that when we freeze, we sort using
/// a stable sort on the key. Since we use a stable sort on the key, we know
/// that all of the individual multimap value arrays are still in insertion
/// order helping us avoid non-determinism like one must deal with when using
/// other maps.
///
//===----------------------------------------------------------------------===//
#ifndef SWIFT_BASIC_FROZENMULTIMAP_H
#define SWIFT_BASIC_FROZENMULTIMAP_H
#include "swift/Basic/LLVM.h"
#include "swift/Basic/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include <vector>
namespace swift {
template <typename Key, typename Value,
typename VectorStorage = std::vector<std::pair<Key, Optional<Value>>>>
class FrozenMultiMap {
VectorStorage storage;
bool frozen = false;
private:
struct PairToSecondElt;
struct PairWithTypeErasedOptionalSecondElt;
public:
using PairToSecondEltRange =
TransformRange<ArrayRef<std::pair<Key, Optional<Value>>>,
PairToSecondElt>;
FrozenMultiMap() = default;
void insert(const Key &key, const Value &value) {
assert(!isFrozen() && "Can not insert new keys once map is frozen");
storage.emplace_back(key, value);
}
Optional<PairToSecondEltRange> find(const Key &key) const {
assert(isFrozen() &&
"Can not perform a find operation until the map is frozen");
// Since our array is sorted, we need to first find the first pair with our
// inst as the first element.
auto start = std::lower_bound(
storage.begin(), storage.end(), std::make_pair(key, Value()),
[&](const std::pair<Key, Optional<Value>> &p1,
const std::pair<Key, Optional<Value>> &p2) {
return p1.first < p2.first;
});
// If we did not find that first element or that key has a .none value
// (signaling that we erased it), return None.
if (start == storage.end() || start->first != key ||
!start->second.hasValue()) {
return None;
}
// Ok, we found our first element. Now scan forward until we find a pair
// whose instruction is not our own instruction.
auto end = find_if_not(start, storage.end(),
[&](const std::pair<Key, Optional<Value>> &pair) {
return pair.first == key;
});
unsigned count = std::distance(start, end);
ArrayRef<std::pair<Key, Optional<Value>>> slice(&*start, count);
return PairToSecondEltRange(slice, PairToSecondElt());
}
bool erase(const Key &key) {
assert(isFrozen() &&
"Can not perform an erase operation until the map is frozen");
// Since our array is sorted, we need to first find the first pair with our
// inst as the first element.
auto start = std::lower_bound(
storage.begin(), storage.end(), std::make_pair(key, Value()),
[&](const std::pair<Key, Optional<Value>> &p1,
const std::pair<Key, Optional<Value>> &p2) {
return p1.first < p2.first;
});
// If we did not find that first element or that key has a .none value
// (signaling that we erased it), return false.
if (start == storage.end() || start->first != key ||
!start->second.hasValue()) {
return false;
}
// Ok, we found our element. Just set its value to .none to signal it was
// destroyed and then return true.
start->second = None;
return true;
}
bool isFrozen() const { return frozen; }
/// Set this map into its frozen state when we
void setFrozen() {
std::stable_sort(storage.begin(), storage.end(),
[&](const std::pair<Key, Optional<Value>> &lhs,
const std::pair<Key, Optional<Value>> &rhs) {
// Only compare the first entry so that we preserve
// insertion order.
return lhs.first < rhs.first;
});
frozen = true;
}
/// Reset the frozen multimap in an unfrozen state with its storage cleared.
void reset() {
storage.clear();
frozen = false;
}
unsigned size() const { return storage.size(); }
bool empty() const { return storage.empty(); }
struct iterator
: std::iterator<std::forward_iterator_tag,
std::pair<Key, Optional<PairToSecondEltRange>>> {
using base_iterator = typename decltype(storage)::iterator;
FrozenMultiMap &map;
base_iterator baseIter;
Optional<std::pair<Key, Optional<PairToSecondEltRange>>> currentValue;
iterator(FrozenMultiMap &map, base_iterator iter)
: map(map), baseIter(iter), currentValue() {
updateCurrentValue();
}
void updateCurrentValue() {
base_iterator end = map.storage.end();
// If we are end, set currentValue to be None.
if (baseIter == end) {
currentValue = None;
return;
}
// Otherwise, determine the next range that we are visiting.
auto rangeEnd =
std::find_if_not(std::next(baseIter), end,
[&](const std::pair<Key, Optional<Value>> &elt) {
return elt.first == baseIter->first;
});
Optional<PairToSecondEltRange> resultRange;
if (baseIter->second.hasValue()) {
unsigned count = std::distance(baseIter, rangeEnd);
ArrayRef<std::pair<Key, Optional<Value>>> slice(&*baseIter, count);
resultRange.emplace(slice, PairToSecondElt());
}
currentValue = {baseIter->first, resultRange};
}
iterator &operator++() {
baseIter =
std::find_if_not(std::next(baseIter), map.storage.end(),
[&](const std::pair<Key, Optional<Value>> &elt) {
return elt.first == baseIter->first;
});
updateCurrentValue();
return *this;
}
iterator operator++(int) {
auto tmp = *this;
baseIter = std::find_if_not(std::next(baseIter), map.storage.end(),
[&](const std::pair<Key, Value> &elt) {
return elt.first == baseIter->first;
});
updateCurrentValue();
return tmp;
}
std::pair<Key, Optional<PairToSecondEltRange>> operator*() const {
return *currentValue;
}
bool operator==(const iterator &RHS) const {
return baseIter == RHS.baseIter;
}
bool operator!=(const iterator &RHS) const {
return baseIter != RHS.baseIter;
}
};
struct ToNonErasedValues {
Optional<std::pair<Key, Optional<PairToSecondEltRange>>>
operator()(std::pair<Key, Optional<PairToSecondEltRange>> pair) const {
if (!pair.second.hasValue())
return None;
return pair;
}
};
using IgnoringErasedValueRangeType =
OptionalTransformRange<llvm::iterator_range<iterator>, ToNonErasedValues>;
using RangeType = TransformRange<IgnoringErasedValueRangeType,
PairWithTypeErasedOptionalSecondElt>;
/// Return a range of (key, ArrayRef<Value>) pairs. The keys are guaranteed to
/// be in key sorted order and the ArrayRef<Value> are in insertion order.
///
/// The range skips all erased (key, ArrayRef<Value>) entries.
RangeType getRange() const {
assert(isFrozen() &&
"Can not create range until data structure is frozen?!");
auto *self = const_cast<FrozenMultiMap *>(this);
iterator iter1 = iterator(*self, self->storage.begin());
iterator iter2 = iterator(*self, self->storage.end());
auto baseRange = llvm::make_range(iter1, iter2);
auto optRange = makeOptionalTransformRange(baseRange, ToNonErasedValues());
return makeTransformRange(optRange, PairWithTypeErasedOptionalSecondElt());
}
};
template <typename Key, typename Value, typename Storage>
struct FrozenMultiMap<Key, Value, Storage>::PairToSecondElt {
PairToSecondElt() {}
Value operator()(const std::pair<Key, Optional<Value>> &pair) const {
return *pair.second;
}
};
template <typename Key, typename Value, typename Storage>
struct FrozenMultiMap<Key, Value,
Storage>::PairWithTypeErasedOptionalSecondElt {
PairWithTypeErasedOptionalSecondElt() {}
std::pair<Key, PairToSecondEltRange>
operator()(const std::pair<Key, Optional<PairToSecondEltRange>> &pair) const {
return std::make_pair(pair.first, *pair.second);
}
};
template <typename Key, typename Value, unsigned SmallSize>
using SmallFrozenMultiMap =
FrozenMultiMap<Key, Value,
SmallVector<std::pair<Key, Optional<Value>>, SmallSize>>;
} // namespace swift
#endif