/* * Copyright 2019-2021 Hans-Kristian Arntzen * SPDX-License-Identifier: Apache-2.0 OR MIT * * 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. */ /* * At your option, you may choose to accept this material under either: * 1. The Apache License, Version 2.0, found at , or * 2. The MIT License, found at . */ #ifndef SPIRV_CROSS_CONTAINERS_HPP #define SPIRV_CROSS_CONTAINERS_HPP #include "spirv_cross_error_handling.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef SPIRV_CROSS_NAMESPACE_OVERRIDE #define SPIRV_CROSS_NAMESPACE SPIRV_CROSS_NAMESPACE_OVERRIDE #else #define SPIRV_CROSS_NAMESPACE spirv_cross #endif namespace SPIRV_CROSS_NAMESPACE { #ifndef SPIRV_CROSS_FORCE_STL_TYPES // std::aligned_storage does not support size == 0, so roll our own. template class AlignedBuffer { public: T *data() { #if defined(_MSC_VER) && _MSC_VER < 1900 // MSVC 2013 workarounds, sigh ... // Only use this workaround on MSVC 2013 due to some confusion around default initialized unions. // Spec seems to suggest the memory will be zero-initialized, which is *not* what we want. return reinterpret_cast(u.aligned_char); #else return reinterpret_cast(aligned_char); #endif } private: #if defined(_MSC_VER) && _MSC_VER < 1900 // MSVC 2013 workarounds, sigh ... union { char aligned_char[sizeof(T) * N]; double dummy_aligner; } u; #else alignas(T) char aligned_char[sizeof(T) * N]; #endif }; template class AlignedBuffer { public: T *data() { return nullptr; } }; // An immutable version of SmallVector which erases type information about storage. template class VectorView { public: T &operator[](size_t i) SPIRV_CROSS_NOEXCEPT { return ptr[i]; } const T &operator[](size_t i) const SPIRV_CROSS_NOEXCEPT { return ptr[i]; } bool empty() const SPIRV_CROSS_NOEXCEPT { return buffer_size == 0; } size_t size() const SPIRV_CROSS_NOEXCEPT { return buffer_size; } T *data() SPIRV_CROSS_NOEXCEPT { return ptr; } const T *data() const SPIRV_CROSS_NOEXCEPT { return ptr; } T *begin() SPIRV_CROSS_NOEXCEPT { return ptr; } T *end() SPIRV_CROSS_NOEXCEPT { return ptr + buffer_size; } const T *begin() const SPIRV_CROSS_NOEXCEPT { return ptr; } const T *end() const SPIRV_CROSS_NOEXCEPT { return ptr + buffer_size; } T &front() SPIRV_CROSS_NOEXCEPT { return ptr[0]; } const T &front() const SPIRV_CROSS_NOEXCEPT { return ptr[0]; } T &back() SPIRV_CROSS_NOEXCEPT { return ptr[buffer_size - 1]; } const T &back() const SPIRV_CROSS_NOEXCEPT { return ptr[buffer_size - 1]; } // Makes it easier to consume SmallVector. #if defined(_MSC_VER) && _MSC_VER < 1900 explicit operator std::vector() const { // Another MSVC 2013 workaround. It does not understand lvalue/rvalue qualified operations. return std::vector(ptr, ptr + buffer_size); } #else // Makes it easier to consume SmallVector. explicit operator std::vector() const & { return std::vector(ptr, ptr + buffer_size); } // If we are converting as an r-value, we can pilfer our elements. explicit operator std::vector() && { return std::vector(std::make_move_iterator(ptr), std::make_move_iterator(ptr + buffer_size)); } #endif // Avoid sliced copies. Base class should only be read as a reference. VectorView(const VectorView &) = delete; void operator=(const VectorView &) = delete; protected: VectorView() = default; T *ptr = nullptr; size_t buffer_size = 0; }; // Simple vector which supports up to N elements inline, without malloc/free. // We use a lot of throwaway vectors all over the place which triggers allocations. // This class only implements the subset of std::vector we need in SPIRV-Cross. // It is *NOT* a drop-in replacement in general projects. template class SmallVector : public VectorView { public: SmallVector() SPIRV_CROSS_NOEXCEPT { this->ptr = stack_storage.data(); buffer_capacity = N; } SmallVector(const T *arg_list_begin, const T *arg_list_end) SPIRV_CROSS_NOEXCEPT : SmallVector() { auto count = size_t(arg_list_end - arg_list_begin); reserve(count); for (size_t i = 0; i < count; i++, arg_list_begin++) new (&this->ptr[i]) T(*arg_list_begin); this->buffer_size = count; } SmallVector(std::initializer_list init) SPIRV_CROSS_NOEXCEPT : SmallVector(init.begin(), init.end()) { } SmallVector(SmallVector &&other) SPIRV_CROSS_NOEXCEPT : SmallVector() { *this = std::move(other); } SmallVector &operator=(SmallVector &&other) SPIRV_CROSS_NOEXCEPT { clear(); if (other.ptr != other.stack_storage.data()) { // Pilfer allocated pointer. if (this->ptr != stack_storage.data()) free(this->ptr); this->ptr = other.ptr; this->buffer_size = other.buffer_size; buffer_capacity = other.buffer_capacity; other.ptr = nullptr; other.buffer_size = 0; other.buffer_capacity = 0; } else { // Need to move the stack contents individually. reserve(other.buffer_size); for (size_t i = 0; i < other.buffer_size; i++) { new (&this->ptr[i]) T(std::move(other.ptr[i])); other.ptr[i].~T(); } this->buffer_size = other.buffer_size; other.buffer_size = 0; } return *this; } SmallVector(const SmallVector &other) SPIRV_CROSS_NOEXCEPT : SmallVector() { *this = other; } SmallVector &operator=(const SmallVector &other) SPIRV_CROSS_NOEXCEPT { if (this == &other) return *this; clear(); reserve(other.buffer_size); for (size_t i = 0; i < other.buffer_size; i++) new (&this->ptr[i]) T(other.ptr[i]); this->buffer_size = other.buffer_size; return *this; } explicit SmallVector(size_t count) SPIRV_CROSS_NOEXCEPT : SmallVector() { resize(count); } ~SmallVector() { clear(); if (this->ptr != stack_storage.data()) free(this->ptr); } void clear() SPIRV_CROSS_NOEXCEPT { for (size_t i = 0; i < this->buffer_size; i++) this->ptr[i].~T(); this->buffer_size = 0; } void push_back(const T &t) SPIRV_CROSS_NOEXCEPT { reserve(this->buffer_size + 1); new (&this->ptr[this->buffer_size]) T(t); this->buffer_size++; } void push_back(T &&t) SPIRV_CROSS_NOEXCEPT { reserve(this->buffer_size + 1); new (&this->ptr[this->buffer_size]) T(std::move(t)); this->buffer_size++; } void pop_back() SPIRV_CROSS_NOEXCEPT { // Work around false positive warning on GCC 8.3. // Calling pop_back on empty vector is undefined. if (!this->empty()) resize(this->buffer_size - 1); } template void emplace_back(Ts &&... ts) SPIRV_CROSS_NOEXCEPT { reserve(this->buffer_size + 1); new (&this->ptr[this->buffer_size]) T(std::forward(ts)...); this->buffer_size++; } void reserve(size_t count) SPIRV_CROSS_NOEXCEPT { if ((count > (std::numeric_limits::max)() / sizeof(T)) || (count > (std::numeric_limits::max)() / 2)) { // Only way this should ever happen is with garbage input, terminate. std::terminate(); } if (count > buffer_capacity) { size_t target_capacity = buffer_capacity; if (target_capacity == 0) target_capacity = 1; // Weird parens works around macro issues on Windows if NOMINMAX is not used. target_capacity = (std::max)(target_capacity, N); // Need to ensure there is a POT value of target capacity which is larger than count, // otherwise this will overflow. while (target_capacity < count) target_capacity <<= 1u; T *new_buffer = target_capacity > N ? static_cast(malloc(target_capacity * sizeof(T))) : stack_storage.data(); // If we actually fail this malloc, we are hosed anyways, there is no reason to attempt recovery. if (!new_buffer) std::terminate(); // In case for some reason two allocations both come from same stack. if (new_buffer != this->ptr) { // We don't deal with types which can throw in move constructor. for (size_t i = 0; i < this->buffer_size; i++) { new (&new_buffer[i]) T(std::move(this->ptr[i])); this->ptr[i].~T(); } } if (this->ptr != stack_storage.data()) free(this->ptr); this->ptr = new_buffer; buffer_capacity = target_capacity; } } void insert(T *itr, const T *insert_begin, const T *insert_end) SPIRV_CROSS_NOEXCEPT { auto count = size_t(insert_end - insert_begin); if (itr == this->end()) { reserve(this->buffer_size + count); for (size_t i = 0; i < count; i++, insert_begin++) new (&this->ptr[this->buffer_size + i]) T(*insert_begin); this->buffer_size += count; } else { if (this->buffer_size + count > buffer_capacity) { auto target_capacity = this->buffer_size + count; if (target_capacity == 0) target_capacity = 1; if (target_capacity < N) target_capacity = N; while (target_capacity < count) target_capacity <<= 1u; // Need to allocate new buffer. Move everything to a new buffer. T *new_buffer = target_capacity > N ? static_cast(malloc(target_capacity * sizeof(T))) : stack_storage.data(); // If we actually fail this malloc, we are hosed anyways, there is no reason to attempt recovery. if (!new_buffer) std::terminate(); // First, move elements from source buffer to new buffer. // We don't deal with types which can throw in move constructor. auto *target_itr = new_buffer; auto *original_source_itr = this->begin(); if (new_buffer != this->ptr) { while (original_source_itr != itr) { new (target_itr) T(std::move(*original_source_itr)); original_source_itr->~T(); ++original_source_itr; ++target_itr; } } // Copy-construct new elements. for (auto *source_itr = insert_begin; source_itr != insert_end; ++source_itr, ++target_itr) new (target_itr) T(*source_itr); // Move over the other half. if (new_buffer != this->ptr || insert_begin != insert_end) { while (original_source_itr != this->end()) { new (target_itr) T(std::move(*original_source_itr)); original_source_itr->~T(); ++original_source_itr; ++target_itr; } } if (this->ptr != stack_storage.data()) free(this->ptr); this->ptr = new_buffer; buffer_capacity = target_capacity; } else { // Move in place, need to be a bit careful about which elements are constructed and which are not. // Move the end and construct the new elements. auto *target_itr = this->end() + count; auto *source_itr = this->end(); while (target_itr != this->end() && source_itr != itr) { --target_itr; --source_itr; new (target_itr) T(std::move(*source_itr)); } // For already constructed elements we can move-assign. std::move_backward(itr, source_itr, target_itr); // For the inserts which go to already constructed elements, we can do a plain copy. while (itr != this->end() && insert_begin != insert_end) *itr++ = *insert_begin++; // For inserts into newly allocated memory, we must copy-construct instead. while (insert_begin != insert_end) { new (itr) T(*insert_begin); ++itr; ++insert_begin; } } this->buffer_size += count; } } void insert(T *itr, const T &value) SPIRV_CROSS_NOEXCEPT { insert(itr, &value, &value + 1); } T *erase(T *itr) SPIRV_CROSS_NOEXCEPT { std::move(itr + 1, this->end(), itr); this->ptr[--this->buffer_size].~T(); return itr; } void erase(T *start_erase, T *end_erase) SPIRV_CROSS_NOEXCEPT { if (end_erase == this->end()) { resize(size_t(start_erase - this->begin())); } else { auto new_size = this->buffer_size - (end_erase - start_erase); std::move(end_erase, this->end(), start_erase); resize(new_size); } } void resize(size_t new_size) SPIRV_CROSS_NOEXCEPT { if (new_size < this->buffer_size) { for (size_t i = new_size; i < this->buffer_size; i++) this->ptr[i].~T(); } else if (new_size > this->buffer_size) { reserve(new_size); for (size_t i = this->buffer_size; i < new_size; i++) new (&this->ptr[i]) T(); } this->buffer_size = new_size; } private: size_t buffer_capacity = 0; AlignedBuffer stack_storage; }; // A vector without stack storage. // Could also be a typedef-ed to std::vector, // but might as well use the one we have. template using Vector = SmallVector; #else // SPIRV_CROSS_FORCE_STL_TYPES template using SmallVector = std::vector; template using Vector = std::vector; template using VectorView = std::vector; #endif // SPIRV_CROSS_FORCE_STL_TYPES // An object pool which we use for allocating IVariant-derived objects. // We know we are going to allocate a bunch of objects of each type, // so amortize the mallocs. class ObjectPoolBase { public: virtual ~ObjectPoolBase() = default; virtual void deallocate_opaque(void *ptr) = 0; }; template class ObjectPool : public ObjectPoolBase { public: explicit ObjectPool(unsigned start_object_count_ = 16) : start_object_count(start_object_count_) { } template T *allocate(P &&... p) { if (vacants.empty()) { unsigned num_objects = start_object_count << memory.size(); T *ptr = static_cast(malloc(num_objects * sizeof(T))); if (!ptr) return nullptr; for (unsigned i = 0; i < num_objects; i++) vacants.push_back(&ptr[i]); memory.emplace_back(ptr); } T *ptr = vacants.back(); vacants.pop_back(); new (ptr) T(std::forward

(p)...); return ptr; } void deallocate(T *ptr) { ptr->~T(); vacants.push_back(ptr); } void deallocate_opaque(void *ptr) override { deallocate(static_cast(ptr)); } void clear() { vacants.clear(); memory.clear(); } protected: Vector vacants; struct MallocDeleter { void operator()(T *ptr) { ::free(ptr); } }; SmallVector> memory; unsigned start_object_count; }; template class StringStream { public: StringStream() { reset(); } ~StringStream() { reset(); } // Disable copies and moves. Makes it easier to implement, and we don't need it. StringStream(const StringStream &) = delete; void operator=(const StringStream &) = delete; template ::value, int>::type = 0> StringStream &operator<<(const T &t) { auto s = std::to_string(t); append(s.data(), s.size()); return *this; } // Only overload this to make float/double conversions ambiguous. StringStream &operator<<(uint32_t v) { auto s = std::to_string(v); append(s.data(), s.size()); return *this; } StringStream &operator<<(char c) { append(&c, 1); return *this; } StringStream &operator<<(const std::string &s) { append(s.data(), s.size()); return *this; } StringStream &operator<<(const char *s) { append(s, strlen(s)); return *this; } template StringStream &operator<<(const char (&s)[N]) { append(s, strlen(s)); return *this; } std::string str() const { std::string ret; size_t target_size = 0; for (auto &saved : saved_buffers) target_size += saved.offset; target_size += current_buffer.offset; ret.reserve(target_size); for (auto &saved : saved_buffers) ret.insert(ret.end(), saved.buffer, saved.buffer + saved.offset); ret.insert(ret.end(), current_buffer.buffer, current_buffer.buffer + current_buffer.offset); return ret; } void reset() { for (auto &saved : saved_buffers) if (saved.buffer != stack_buffer) free(saved.buffer); if (current_buffer.buffer != stack_buffer) free(current_buffer.buffer); saved_buffers.clear(); current_buffer.buffer = stack_buffer; current_buffer.offset = 0; current_buffer.size = sizeof(stack_buffer); } private: struct Buffer { char *buffer = nullptr; size_t offset = 0; size_t size = 0; }; Buffer current_buffer; char stack_buffer[StackSize]; SmallVector saved_buffers; void append(const char *s, size_t len) { size_t avail = current_buffer.size - current_buffer.offset; if (avail < len) { if (avail > 0) { memcpy(current_buffer.buffer + current_buffer.offset, s, avail); s += avail; len -= avail; current_buffer.offset += avail; } saved_buffers.push_back(current_buffer); size_t target_size = len > BlockSize ? len : BlockSize; current_buffer.buffer = static_cast(malloc(target_size)); if (!current_buffer.buffer) SPIRV_CROSS_THROW("Out of memory."); memcpy(current_buffer.buffer, s, len); current_buffer.offset = len; current_buffer.size = target_size; } else { memcpy(current_buffer.buffer + current_buffer.offset, s, len); current_buffer.offset += len; } } }; } // namespace SPIRV_CROSS_NAMESPACE #endif