blob: 793d40aa0ab882b4fe77f57256c195074f7bff0b [file] [log] [blame]
/* Copyright (c) 2015-2017, 2019-2022 The Khronos Group Inc.
* Copyright (c) 2015-2017, 2019-2022 Valve Corporation
* Copyright (c) 2015-2017, 2019-2022 LunarG, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Author: Tobin Ehlis <tobine@google.com>
* Author: Jeff Bolz <jbolz@nvidia.com>
* Author: John Zulauf <jzulauf@lunarg.com>
*/
#ifndef LAYER_DATA_H
#define LAYER_DATA_H
#include <cassert>
#include <limits>
#include <memory>
#include <map>
#include <unordered_map>
#include <set>
#include <algorithm>
#include <iterator>
#include <type_traits>
#ifdef USE_ROBIN_HOOD_HASHING
#include "robin_hood.h"
#else
#include <unordered_set>
#endif
// namespace aliases to allow map and set implementations to easily be swapped out
namespace layer_data {
#ifdef USE_ROBIN_HOOD_HASHING
template <typename T>
using hash = robin_hood::hash<T>;
template <typename Key, typename Hash = robin_hood::hash<Key>, typename KeyEqual = std::equal_to<Key>>
using unordered_set = robin_hood::unordered_set<Key, Hash, KeyEqual>;
template <typename Key, typename T, typename Hash = robin_hood::hash<Key>, typename KeyEqual = std::equal_to<Key>>
using unordered_map = robin_hood::unordered_map<Key, T, Hash, KeyEqual>;
template <typename Key, typename T>
using map_entry = robin_hood::pair<Key, T>;
// robin_hood-compatible insert_iterator (std:: uses the wrong insert method)
template <typename T>
class insert_iterator : public std::iterator<std::output_iterator_tag, void, void, void, void> {
public:
typedef typename T::value_type value_type;
typedef typename T::iterator iterator;
insert_iterator(T &t, iterator i) : container(&t), iter(i) {}
insert_iterator &operator=(const value_type &value) {
auto result = container->insert(value);
iter = result.first;
++iter;
return *this;
}
insert_iterator &operator=(value_type &&value) {
auto result = container->insert(std::move(value));
iter = result.first;
++iter;
return *this;
}
insert_iterator &operator*() { return *this; }
insert_iterator &operator++() { return *this; }
insert_iterator &operator++(int) { return *this; }
private:
T *container;
typename T::iterator iter;
};
#else
template <typename T>
using hash = std::hash<T>;
template <typename Key, typename Hash = std::hash<Key>, typename KeyEqual = std::equal_to<Key>>
using unordered_set = std::unordered_set<Key, Hash, KeyEqual>;
template <typename Key, typename T, typename Hash = std::hash<Key>, typename KeyEqual = std::equal_to<Key>>
using unordered_map = std::unordered_map<Key, T, Hash, KeyEqual>;
template <typename Key, typename T>
using map_entry = std::pair<Key, T>;
template <typename T>
using insert_iterator = std::insert_iterator<T>;
#endif
// Can't alias variadic functions even on C++14, and wrapping the 14 implementation wouldn't gain us anything...
// leave it in unconditionally and for backwards compatibility
template<typename T, typename... Args>
constexpr std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
} // namespace layer_data
// A vector class with "small string optimization" -- meaning that the class contains a fixed working store for N elements.
// Useful in in situations where the needed size is unknown, but the typical size is known If size increases beyond the
// fixed capacity, a dynamically allocated working store is created.
//
// NOTE: Unlike std::vector which only requires T to be CopyAssignable and CopyConstructable, small_vector requires T to be
// MoveAssignable and MoveConstructable
// NOTE: Unlike std::vector, iterators are invalidated by move assignment between small_vector objects effectively the
// "small string" allocation functions as an incompatible allocator.
template <typename T, size_t N, typename SizeType = uint8_t>
class small_vector {
public:
using value_type = T;
using reference = value_type &;
using const_reference = const value_type &;
using pointer = value_type *;
using const_pointer = const value_type *;
using iterator = pointer;
using const_iterator = const_pointer;
using size_type = SizeType;
static const size_type kSmallCapacity = N;
static const size_type kMaxCapacity = std::numeric_limits<size_type>::max();
static_assert(N <= kMaxCapacity, "size must be less than size_type::max");
small_vector() : size_(0), capacity_(N) {}
small_vector(const small_vector &other) : size_(0), capacity_(N) {
reserve(other.size_);
auto dest = GetWorkingStore();
for (const auto &value : other) {
new (dest) value_type(value);
++dest;
}
size_ = other.size_;
}
small_vector(small_vector &&other) : size_(0), capacity_(N) {
if (other.large_store_) {
// Can just take ownership of the other large store
large_store_ = std::move(other.large_store_);
capacity_ = other.capacity_;
other.capacity_ = kSmallCapacity;
} else {
auto dest = GetWorkingStore();
for (auto &value : other) {
new (dest) value_type(std::move(value));
value.~value_type();
++dest;
}
}
size_ = other.size_;
other.size_ = 0;
}
~small_vector() { clear(); }
bool operator==(const small_vector &rhs) const {
if (size_ != rhs.size_) return false;
auto value = begin();
for (const auto &rh_value : rhs) {
if (!(*value == rh_value)) {
return false;
}
++value;
}
return true;
}
small_vector &operator=(const small_vector &other) {
if (this != &other) {
reserve(other.size_); // reserve doesn't shrink!
auto dest = GetWorkingStore();
auto source = other.GetWorkingStore();
const auto overlap = std::min(size_, other.size_);
// Copy assign anywhere we have objects in this
for (size_type i = 0; i < overlap; i++) {
dest[i] = source[i];
}
// Copy construct anywhere we *don't* have objects in this
for (size_type i = overlap; i < other.size_; i++) {
new (dest + i) value_type(source[i]);
}
// Any entries in this past other_size_ must be cleaned up...
for (size_type i = other.size_; i < size_; i++) {
dest[i].~value_type();
}
size_ = other.size_;
}
return *this;
}
small_vector &operator=(small_vector &&other) {
if (this != &other) {
if (other.large_store_) {
clear(); // need to clean up any objects this owns.
// Can just take ownership of the other large store
large_store_ = std::move(other.large_store_);
capacity_ = other.capacity_;
size_ = other.size_;
other.capacity_ = kSmallCapacity;
} else {
// Other is using the small_store
auto source = other.begin();
iterator dest;
if (large_store_) {
// If this is using large store do a wholesale clobber of it.
ClearAndReset();
dest = GetWorkingStore();
} else {
// This is also using small store, so move assign where both have valid values
dest = GetWorkingStore();
// Move values where both vectors have valid values
for (size_type i = 0; i < std::min(size_, other.size_); i++) {
*dest = std::move(*source);
source->~value_type();
++dest;
++source;
}
}
// Other is bigger, placement new into the working store
// NOTE: this loop only runs when other is bigger
for (size_type i = size_; i < other.size_; i++) {
new (dest) value_type(std::move(*source));
source->~value_type();
++dest;
++source;
}
// Other is smaller, clean up the excess entries
// NOTE: this loop only runs when this is bigger
for (size_type i = other.size_; i < size_; i++) {
dest->~value_type();
++dest;
}
size_ = other.size_;
}
// When we're done other has no valid contents (all are moved or destructed)
other.size_ = 0;
}
return *this;
}
reference operator[](size_type pos) {
assert(pos < size_);
return GetWorkingStore()[pos];
}
const_reference operator[](size_type pos) const {
assert(pos < size_);
return GetWorkingStore()[pos];
}
// Like std::vector:: calling front or back on an empty container causes undefined behavior
reference front() {
assert(size_ > 0);
return GetWorkingStore()[0];
}
const_reference front() const {
assert(size_ > 0);
return GetWorkingStore()[0];
}
reference back() {
assert(size_ > 0);
return GetWorkingStore()[size_ - 1];
}
const_reference back() const {
assert(size_ > 0);
return GetWorkingStore()[size_ - 1];
}
bool empty() const { return size_ == 0; }
template <class... Args>
void emplace_back(Args &&...args) {
assert(size_ < kMaxCapacity);
reserve(size_ + 1);
new (GetWorkingStore() + size_) value_type(args...);
size_++;
}
void reserve(size_type new_cap) {
// Since this can't shrink, if we're growing we're newing
if (new_cap > capacity_) {
assert(capacity_ >= kSmallCapacity);
auto new_store = std::unique_ptr<BackingStore[]>(new BackingStore[new_cap]);
auto new_values = reinterpret_cast<pointer>(new_store.get());
auto working_store = GetWorkingStore();
for (size_type i = 0; i < size_; i++) {
new (new_values + i) value_type(std::move(working_store[i]));
working_store[i].~value_type();
}
large_store_ = std::move(new_store);
}
// No shrink here.
}
void clear() {
auto working_store = GetWorkingStore();
for (size_type i = 0; i < size_; i++) {
working_store[i].~value_type();
}
size_ = 0;
}
inline iterator begin() { return GetWorkingStore(); }
inline const_iterator cbegin() const { return GetWorkingStore(); }
inline const_iterator begin() const { return GetWorkingStore(); }
inline iterator end() { return GetWorkingStore() + size_; }
inline const_iterator cend() const { return GetWorkingStore() + size_; }
inline const_iterator end() const { return GetWorkingStore() + size_; }
inline size_type size() const { return size_; }
protected:
inline const_pointer GetWorkingStore() const {
const BackingStore *store = large_store_ ? large_store_.get() : small_store_;
return reinterpret_cast<const_pointer>(store);
}
inline pointer GetWorkingStore() {
BackingStore *store = large_store_ ? large_store_.get() : small_store_;
return reinterpret_cast<pointer>(store);
}
void ClearAndReset() {
clear();
large_store_.reset();
capacity_ = kSmallCapacity;
}
struct alignas(alignof(value_type)) BackingStore {
uint8_t data[sizeof(value_type)];
};
size_type size_;
size_type capacity_;
BackingStore small_store_[N];
std::unique_ptr<BackingStore[]> large_store_;
};
// This is a wrapper around unordered_map that optimizes for the common case
// of only containing a small number of elements. The first N elements are stored
// inline in the object and don't require hashing or memory (de)allocation.
template <typename Key, typename value_type, typename inner_container_type, typename value_type_helper, int N>
class small_container {
protected:
bool small_data_allocated[N];
value_type small_data[N];
inner_container_type inner_cont;
value_type_helper helper;
public:
small_container() {
for (int i = 0; i < N; ++i) {
small_data_allocated[i] = false;
}
}
class iterator {
typedef typename inner_container_type::iterator inner_iterator;
friend class small_container<Key, value_type, inner_container_type, value_type_helper, N>;
small_container<Key, value_type, inner_container_type, value_type_helper, N> *parent;
int index;
inner_iterator it;
public:
iterator() {}
iterator operator++() {
if (index < N) {
index++;
while (index < N && !parent->small_data_allocated[index]) {
index++;
}
if (index < N) {
return *this;
}
it = parent->inner_cont.begin();
return *this;
}
++it;
return *this;
}
bool operator==(const iterator &other) const {
if ((index < N) != (other.index < N)) {
return false;
}
if (index < N) {
return (index == other.index);
}
return it == other.it;
}
bool operator!=(const iterator &other) const { return !(*this == other); }
value_type &operator*() const {
if (index < N) {
return parent->small_data[index];
}
return *it;
}
value_type *operator->() const {
if (index < N) {
return &parent->small_data[index];
}
return &*it;
}
};
class const_iterator {
typedef typename inner_container_type::const_iterator inner_iterator;
friend class small_container<Key, value_type, inner_container_type, value_type_helper, N>;
const small_container<Key, value_type, inner_container_type, value_type_helper, N> *parent;
int index;
inner_iterator it;
public:
const_iterator() {}
const_iterator operator++() {
if (index < N) {
index++;
while (index < N && !parent->small_data_allocated[index]) {
index++;
}
if (index < N) {
return *this;
}
it = parent->inner_cont.begin();
return *this;
}
++it;
return *this;
}
bool operator==(const const_iterator &other) const {
if ((index < N) != (other.index < N)) {
return false;
}
if (index < N) {
return (index == other.index);
}
return it == other.it;
}
bool operator!=(const const_iterator &other) const { return !(*this == other); }
const value_type &operator*() const {
if (index < N) {
return parent->small_data[index];
}
return *it;
}
const value_type *operator->() const {
if (index < N) {
return &parent->small_data[index];
}
return &*it;
}
};
iterator begin() {
iterator it;
it.parent = this;
// If index 0 is allocated, return it, otherwise use operator++ to find the first
// allocated element.
it.index = 0;
if (small_data_allocated[0]) {
return it;
}
++it;
return it;
}
iterator end() {
iterator it;
it.parent = this;
it.index = N;
it.it = inner_cont.end();
return it;
}
const_iterator begin() const {
const_iterator it;
it.parent = this;
// If index 0 is allocated, return it, otherwise use operator++ to find the first
// allocated element.
it.index = 0;
if (small_data_allocated[0]) {
return it;
}
++it;
return it;
}
const_iterator end() const {
const_iterator it;
it.parent = this;
it.index = N;
it.it = inner_cont.end();
return it;
}
bool contains(const Key &key) const {
for (int i = 0; i < N; ++i) {
if (small_data_allocated[i] && helper.compare_equal(small_data[i], key)) {
return true;
}
}
// check size() first to avoid hashing key unnecessarily.
if (inner_cont.size() == 0) {
return false;
}
return inner_cont.find(key) != inner_cont.end();
}
typename inner_container_type::size_type count(const Key &key) const { return contains(key) ? 1 : 0; }
std::pair<iterator, bool> insert(const value_type &value) {
for (int i = 0; i < N; ++i) {
if (small_data_allocated[i] && helper.compare_equal(small_data[i], value)) {
iterator it;
it.parent = this;
it.index = i;
return std::make_pair(it, false);
}
}
// check size() first to avoid hashing key unnecessarily.
auto iter = inner_cont.size() > 0 ? inner_cont.find(helper.get_key(value)) : inner_cont.end();
if (iter != inner_cont.end()) {
iterator it;
it.parent = this;
it.index = N;
it.it = iter;
return std::make_pair(it, false);
} else {
for (int i = 0; i < N; ++i) {
if (!small_data_allocated[i]) {
small_data_allocated[i] = true;
helper.assign(small_data[i], value);
iterator it;
it.parent = this;
it.index = i;
return std::make_pair(it, true);
}
}
iter = inner_cont.insert(value).first;
iterator it;
it.parent = this;
it.index = N;
it.it = iter;
return std::make_pair(it, true);
}
}
typename inner_container_type::size_type erase(const Key &key) {
for (int i = 0; i < N; ++i) {
if (small_data_allocated[i] && helper.compare_equal(small_data[i], key)) {
small_data_allocated[i] = false;
return 1;
}
}
return inner_cont.erase(key);
}
typename inner_container_type::size_type size() const {
auto size = inner_cont.size();
for (int i = 0; i < N; ++i) {
if (small_data_allocated[i]) {
size++;
}
}
return size;
}
bool empty() const {
for (int i = 0; i < N; ++i) {
if (small_data_allocated[i]) {
return false;
}
}
return inner_cont.size() == 0;
}
void clear() {
for (int i = 0; i < N; ++i) {
small_data_allocated[i] = false;
}
inner_cont.clear();
}
};
// Helper function objects to compare/assign/get keys in small_unordered_set/map.
// This helps to abstract away whether value_type is a Key or a pair<Key, T>.
template <typename MapType>
class value_type_helper_map {
using PairType = typename MapType::value_type;
using Key = typename std::remove_const<typename PairType::first_type>::type;
public:
bool compare_equal(const PairType &lhs, const Key &rhs) const { return lhs.first == rhs; }
bool compare_equal(const PairType &lhs, const PairType &rhs) const { return lhs.first == rhs.first; }
void assign(PairType &lhs, const PairType &rhs) const {
// While the const_cast may be unsatisfactory, we are using small_data as
// stand-in for placement new and a small-block allocator, so the const_cast
// is minimal, contained, valid, and allows operators * and -> to avoid copies
const_cast<Key &>(lhs.first) = rhs.first;
lhs.second = rhs.second;
}
Key get_key(const PairType &value) const { return value.first; }
};
template <typename Key>
class value_type_helper_set {
public:
bool compare_equal(const Key &lhs, const Key &rhs) const { return lhs == rhs; }
void assign(Key &lhs, const Key &rhs) const { lhs = rhs; }
Key get_key(const Key &value) const { return value; }
};
template <typename Key, typename T, int N = 1>
class small_unordered_map
: public small_container<Key, typename layer_data::unordered_map<Key, T>::value_type, layer_data::unordered_map<Key, T>,
value_type_helper_map<layer_data::unordered_map<Key, T>>, N> {
public:
T &operator[](const Key &key) {
for (int i = 0; i < N; ++i) {
if (this->small_data_allocated[i] && this->helper.compare_equal(this->small_data[i], key)) {
return this->small_data[i].second;
}
}
auto iter = this->inner_cont.find(key);
if (iter != this->inner_cont.end()) {
return iter->second;
} else {
for (int i = 0; i < N; ++i) {
if (!this->small_data_allocated[i]) {
this->small_data_allocated[i] = true;
this->helper.assign(this->small_data[i], {key, T()});
return this->small_data[i].second;
}
}
return this->inner_cont[key];
}
}
};
template <typename Key, int N = 1>
class small_unordered_set : public small_container<Key, Key, layer_data::unordered_set<Key>, value_type_helper_set<Key>, N> {};
// For the given data key, look up the layer_data instance from given layer_data_map
template <typename DATA_T>
DATA_T *GetLayerDataPtr(void *data_key, small_unordered_map<void *, DATA_T *, 2> &layer_data_map) {
/* TODO: We probably should lock here, or have caller lock */
DATA_T *&got = layer_data_map[data_key];
if (got == nullptr) {
got = new DATA_T;
}
return got;
}
template <typename DATA_T>
void FreeLayerDataPtr(void *data_key, small_unordered_map<void *, DATA_T *, 2> &layer_data_map) {
delete layer_data_map[data_key];
layer_data_map.erase(data_key);
}
// For the given data key, look up the layer_data instance from given layer_data_map
template <typename DATA_T>
DATA_T *GetLayerDataPtr(void *data_key, std::unordered_map<void *, DATA_T *> &layer_data_map) {
DATA_T *debug_data;
/* TODO: We probably should lock here, or have caller lock */
auto got = layer_data_map.find(data_key);
if (got == layer_data_map.end()) {
debug_data = new DATA_T;
layer_data_map[(void *)data_key] = debug_data;
} else {
debug_data = got->second;
}
return debug_data;
}
template <typename DATA_T>
void FreeLayerDataPtr(void *data_key, std::unordered_map<void *, DATA_T *> &layer_data_map) {
auto got = layer_data_map.find(data_key);
assert(got != layer_data_map.end());
delete got->second;
layer_data_map.erase(got);
}
namespace layer_data {
struct in_place_t {};
static constexpr in_place_t in_place{};
// A C++11 approximation of std::optional
template <typename T>
class optional {
protected:
union Store {
Store(){}; // Do nothing. That's the point.
~Store(){}; // Not safe to destroy this object outside of its stateful container to clean up T if any.
typename std::aligned_storage<sizeof(T), alignof(T)>::type backing;
T obj;
};
public:
optional() : init_(false) {}
template <typename... Args>
explicit optional(in_place_t, const Args &...args) { emplace(args...); }
optional(const optional &other) : init_(false) { *this = other; }
optional(optional &&other) : init_(false) { *this = std::move(other); }
~optional() { DeInit(); }
template <typename... Args>
T &emplace(const Args &...args) {
init_ = true;
new (&store_.backing) T(args...);
return store_.obj;
}
T *operator&() {
if (init_) return &store_.obj;
return nullptr;
}
const T *operator&() const {
if (init_) return &store_.obj;
return nullptr;
}
T *operator->() {
if (init_) return &store_.obj;
return nullptr;
}
const T *operator->() const {
if (init_) return &store_.obj;
return nullptr;
}
operator bool() const { return init_; }
bool has_value() const { return init_; }
optional &operator=(const optional &other) {
if (other.has_value()) {
if (has_value()) {
store_.obj = other.store_.obj;
} else {
emplace(other.store_.obj);
}
} else {
DeInit();
}
return *this;
}
optional &operator=(optional &&other) {
if (other.has_value()) {
if (has_value()) {
store_.obj = std::move(other.store_.obj);
} else {
emplace(std::move(other.store_.obj));
}
} else {
DeInit();
}
return *this;
}
T& operator*() & {
assert(init_);
return store_.obj;
}
const T& operator*() const& {
assert(init_);
return store_.obj;
}
T&& operator*() && {
assert(init_);
return std::move(store_.obj);
}
const T&& operator*() const&& {
assert(init_);
return std::move(store_.obj);
}
protected:
inline void DeInit() {
if (init_) {
store_.obj.~T();
init_ = false;
}
}
Store store_;
bool init_;
};
} // namespace layer_data
#endif // LAYER_DATA_H