diff --git a/src/common/BUILD.gn b/src/common/BUILD.gn index dac8c00585..b36d7e7a4b 100644 --- a/src/common/BUILD.gn +++ b/src/common/BUILD.gn @@ -178,6 +178,7 @@ if (is_win || is_linux || is_mac || is_fuchsia || is_android) { "ityp_array.h", "ityp_bitset.h", "ityp_span.h", + "ityp_stack_vec.h", "vulkan_platform.h", "windows_with_undefs.h", "xlib_with_undefs.h", diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 1ab20234b1..2167d24982 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -49,6 +49,7 @@ target_sources(dawn_common PRIVATE "ityp_array.h" "ityp_bitset.h" "ityp_span.h" + "ityp_stack_vec.h" "vulkan_platform.h" "windows_with_undefs.h" "xlib_with_undefs.h" diff --git a/src/common/RefCounted.h b/src/common/RefCounted.h index b055d7b71e..90144a6043 100644 --- a/src/common/RefCounted.h +++ b/src/common/RefCounted.h @@ -122,6 +122,14 @@ class Ref { mPointee = nullptr; } + bool operator==(const T* other) const { + return mPointee == other; + } + + bool operator!=(const T* other) const { + return mPointee != other; + } + operator bool() { return mPointee != nullptr; } diff --git a/src/common/StackContainer.h b/src/common/StackContainer.h new file mode 100644 index 0000000000..0e9667e310 --- /dev/null +++ b/src/common/StackContainer.h @@ -0,0 +1,264 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is a modified copy of Chromium's /src/base/containers/stack_container.h + +#ifndef COMMON_STACKCONTAINER_H_ +#define COMMON_STACKCONTAINER_H_ + +#include "common/Compiler.h" + +#include +#include + +// This allocator can be used with STL containers to provide a stack buffer +// from which to allocate memory and overflows onto the heap. This stack buffer +// would be allocated on the stack and allows us to avoid heap operations in +// some situations. +// +// STL likes to make copies of allocators, so the allocator itself can't hold +// the data. Instead, we make the creator responsible for creating a +// StackAllocator::Source which contains the data. Copying the allocator +// merely copies the pointer to this shared source, so all allocators created +// based on our allocator will share the same stack buffer. +// +// This stack buffer implementation is very simple. The first allocation that +// fits in the stack buffer will use the stack buffer. Any subsequent +// allocations will not use the stack buffer, even if there is unused room. +// This makes it appropriate for array-like containers, but the caller should +// be sure to reserve() in the container up to the stack buffer size. Otherwise +// the container will allocate a small array which will "use up" the stack +// buffer. +template +class StackAllocator : public std::allocator { + public: + typedef typename std::allocator::pointer pointer; + typedef typename std::allocator::size_type size_type; + + // Backing store for the allocator. The container owner is responsible for + // maintaining this for as long as any containers using this allocator are + // live. + struct Source { + Source() : used_stack_buffer_(false) { + } + + // Casts the buffer in its right type. + T* stack_buffer() { + return reinterpret_cast(stack_buffer_); + } + const T* stack_buffer() const { + return reinterpret_cast(&stack_buffer_); + } + + // The buffer itself. It is not of type T because we don't want the + // constructors and destructors to be automatically called. Define a POD + // buffer of the right size instead. + alignas(T) char stack_buffer_[sizeof(T[stack_capacity])]; +#if defined(DAWN_COMPILER_GCC) && !defined(__x86_64__) && !defined(__i386__) + static_assert(alignof(T) <= 16, "http://crbug.com/115612"); +#endif + + // Set when the stack buffer is used for an allocation. We do not track + // how much of the buffer is used, only that somebody is using it. + bool used_stack_buffer_; + }; + + // Used by containers when they want to refer to an allocator of type U. + template + struct rebind { + typedef StackAllocator other; + }; + + // For the straight up copy c-tor, we can share storage. + StackAllocator(const StackAllocator& rhs) + : std::allocator(), source_(rhs.source_) { + } + + // ISO C++ requires the following constructor to be defined, + // and std::vector in VC++2008SP1 Release fails with an error + // in the class _Container_base_aux_alloc_real (from ) + // if the constructor does not exist. + // For this constructor, we cannot share storage; there's + // no guarantee that the Source buffer of Ts is large enough + // for Us. + // TODO: If we were fancy pants, perhaps we could share storage + // iff sizeof(T) == sizeof(U). + template + StackAllocator(const StackAllocator& other) : source_(nullptr) { + } + + // This constructor must exist. It creates a default allocator that doesn't + // actually have a stack buffer. glibc's std::string() will compare the + // current allocator against the default-constructed allocator, so this + // should be fast. + StackAllocator() : source_(nullptr) { + } + + explicit StackAllocator(Source* source) : source_(source) { + } + + // Actually do the allocation. Use the stack buffer if nobody has used it yet + // and the size requested fits. Otherwise, fall through to the standard + // allocator. + pointer allocate(size_type n) { + if (source_ && !source_->used_stack_buffer_ && n <= stack_capacity) { + source_->used_stack_buffer_ = true; + return source_->stack_buffer(); + } else { + return std::allocator::allocate(n); + } + } + + // Free: when trying to free the stack buffer, just mark it as free. For + // non-stack-buffer pointers, just fall though to the standard allocator. + void deallocate(pointer p, size_type n) { + if (source_ && p == source_->stack_buffer()) + source_->used_stack_buffer_ = false; + else + std::allocator::deallocate(p, n); + } + + private: + Source* source_; +}; + +// A wrapper around STL containers that maintains a stack-sized buffer that the +// initial capacity of the vector is based on. Growing the container beyond the +// stack capacity will transparently overflow onto the heap. The container must +// support reserve(). +// +// This will not work with std::string since some implementations allocate +// more bytes than requested in calls to reserve(), forcing the allocation onto +// the heap. http://crbug.com/709273 +// +// WATCH OUT: the ContainerType MUST use the proper StackAllocator for this +// type. This object is really intended to be used only internally. You'll want +// to use the wrappers below for different types. +template +class StackContainer { + public: + typedef TContainerType ContainerType; + typedef typename ContainerType::value_type ContainedType; + typedef StackAllocator Allocator; + + // Allocator must be constructed before the container! + StackContainer() : allocator_(&stack_data_), container_(allocator_) { + // Make the container use the stack allocation by reserving our buffer size + // before doing anything else. + container_.reserve(stack_capacity); + } + + // Getters for the actual container. + // + // Danger: any copies of this made using the copy constructor must have + // shorter lifetimes than the source. The copy will share the same allocator + // and therefore the same stack buffer as the original. Use std::copy to + // copy into a "real" container for longer-lived objects. + ContainerType& container() { + return container_; + } + const ContainerType& container() const { + return container_; + } + + // Support operator-> to get to the container. This allows nicer syntax like: + // StackContainer<...> foo; + // std::sort(foo->begin(), foo->end()); + ContainerType* operator->() { + return &container_; + } + const ContainerType* operator->() const { + return &container_; + } + +#ifdef UNIT_TEST + // Retrieves the stack source so that that unit tests can verify that the + // buffer is being used properly. + const typename Allocator::Source& stack_data() const { + return stack_data_; + } +#endif + + protected: + typename Allocator::Source stack_data_; + Allocator allocator_; + ContainerType container_; + + private: + StackContainer(const StackContainer& rhs) = delete; + StackContainer& operator=(const StackContainer& rhs) = delete; + StackContainer(StackContainer&& rhs) = delete; + StackContainer& operator=(StackContainer&& rhs) = delete; +}; + +// Range-based iteration support for StackContainer. +template +auto begin(const StackContainer& stack_container) + -> decltype(begin(stack_container.container())) { + return begin(stack_container.container()); +} + +template +auto begin(StackContainer& stack_container) + -> decltype(begin(stack_container.container())) { + return begin(stack_container.container()); +} + +template +auto end(StackContainer& stack_container) + -> decltype(end(stack_container.container())) { + return end(stack_container.container()); +} + +template +auto end(const StackContainer& stack_container) + -> decltype(end(stack_container.container())) { + return end(stack_container.container()); +} + +// StackVector ----------------------------------------------------------------- + +// Example: +// StackVector foo; +// foo->push_back(22); // we have overloaded operator-> +// foo[0] = 10; // as well as operator[] +template +class StackVector + : public StackContainer>, stack_capacity> { + public: + StackVector() + : StackContainer>, stack_capacity>() { + } + + // We need to put this in STL containers sometimes, which requires a copy + // constructor. We can't call the regular copy constructor because that will + // take the stack buffer from the original. Here, we create an empty object + // and make a stack buffer of its own. + StackVector(const StackVector& other) + : StackContainer>, stack_capacity>() { + this->container().assign(other->begin(), other->end()); + } + + StackVector& operator=(const StackVector& other) { + this->container().assign(other->begin(), other->end()); + return *this; + } + + // Vectors are commonly indexed, which isn't very convenient even with + // operator-> (using "->at()" does exception stuff we don't want). + T& operator[](size_t i) { + return this->container().operator[](i); + } + const T& operator[](size_t i) const { + return this->container().operator[](i); + } + + private: + // StackVector(const StackVector& rhs) = delete; + // StackVector& operator=(const StackVector& rhs) = delete; + StackVector(StackVector&& rhs) = delete; + StackVector& operator=(StackVector&& rhs) = delete; +}; + +#endif // COMMON_STACKCONTAINER_H_ diff --git a/src/common/ityp_stack_vec.h b/src/common/ityp_stack_vec.h new file mode 100644 index 0000000000..69c503976c --- /dev/null +++ b/src/common/ityp_stack_vec.h @@ -0,0 +1,102 @@ +// Copyright 2020 The Dawn Authors +// +// 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. + +#ifndef COMMON_ITYP_STACK_VEC_H_ +#define COMMON_ITYP_STACK_VEC_H_ + +#include "common/Assert.h" +#include "common/StackContainer.h" +#include "common/UnderlyingType.h" + +namespace ityp { + + template + class stack_vec : private StackVector { + using I = UnderlyingType; + using Base = StackVector; + static_assert(StaticCapacity <= std::numeric_limits::max(), ""); + + public: + stack_vec() : Base() { + } + stack_vec(Index size) : Base() { + this->container().resize(static_cast(size)); + } + + Value& operator[](Index i) { + ASSERT(i < size()); + return Base::operator[](static_cast(i)); + } + + constexpr const Value& operator[](Index i) const { + ASSERT(i < size()); + return Base::operator[](static_cast(i)); + } + + void resize(Index size) { + this->container().resize(static_cast(size)); + } + + void reserve(Index size) { + this->container().reserve(static_cast(size)); + } + + Value* data() { + return this->container().data(); + } + + const Value* data() const { + return this->container().data(); + } + + Value* begin() noexcept { + return this->container().begin(); + } + + const Value* begin() const noexcept { + return this->container().begin(); + } + + Value* end() noexcept { + return this->container().end(); + } + + const Value* end() const noexcept { + return this->container().end(); + } + + Value& front() { + return this->container().front(); + } + + const Value& front() const { + return this->container().front(); + } + + Value& back() { + return this->container().back(); + } + + const Value& back() const { + return this->container().back(); + } + + Index size() const { + return Index(static_cast(this->container().size())); + } + }; + +} // namespace ityp + +#endif // COMMON_ITYP_STACK_VEC_H_ diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn index b615518d6c..2b5dbe68c5 100644 --- a/src/tests/BUILD.gn +++ b/src/tests/BUILD.gn @@ -173,6 +173,7 @@ test("dawn_unittests") { "unittests/SerialMapTests.cpp", "unittests/SerialQueueTests.cpp", "unittests/SlabAllocatorTests.cpp", + "unittests/StackContainerTests.cpp", "unittests/SystemUtilsTests.cpp", "unittests/ToBackendTests.cpp", "unittests/TypedIntegerTests.cpp", diff --git a/src/tests/unittests/StackContainerTests.cpp b/src/tests/unittests/StackContainerTests.cpp new file mode 100644 index 0000000000..adba4fabb7 --- /dev/null +++ b/src/tests/unittests/StackContainerTests.cpp @@ -0,0 +1,171 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file is a modified copy of Chromium's /src/base/containers/stack_container_unittest.cc + +#include + +#define UNIT_TEST + +#include "common/RefCounted.h" +#include "common/StackContainer.h" + +#include +#include + +namespace { + + class Dummy : public RefCounted { + public: + explicit Dummy(int* alive) : mAlive(alive) { + ++*mAlive; + } + + private: + ~Dummy() { + --*mAlive; + } + + int* const mAlive; + }; + +} // namespace + +TEST(StackContainer, Vector) { + const int stack_size = 3; + StackVector vect; + const int* stack_buffer = &vect.stack_data().stack_buffer()[0]; + + // The initial |stack_size| elements should appear in the stack buffer. + EXPECT_EQ(static_cast(stack_size), vect.container().capacity()); + for (int i = 0; i < stack_size; i++) { + vect.container().push_back(i); + EXPECT_EQ(stack_buffer, &vect.container()[0]); + EXPECT_TRUE(vect.stack_data().used_stack_buffer_); + } + + // Adding more elements should push the array onto the heap. + for (int i = 0; i < stack_size; i++) { + vect.container().push_back(i + stack_size); + EXPECT_NE(stack_buffer, &vect.container()[0]); + EXPECT_FALSE(vect.stack_data().used_stack_buffer_); + } + + // The array should still be in order. + for (int i = 0; i < stack_size * 2; i++) + EXPECT_EQ(i, vect.container()[i]); + + // Resize to smaller. Our STL implementation won't reallocate in this case, + // otherwise it might use our stack buffer. We reserve right after the resize + // to guarantee it isn't using the stack buffer, even though it doesn't have + // much data. + vect.container().resize(stack_size); + vect.container().reserve(stack_size * 2); + EXPECT_FALSE(vect.stack_data().used_stack_buffer_); + + // Copying the small vector to another should use the same allocator and use + // the now-unused stack buffer. GENERALLY CALLERS SHOULD NOT DO THIS since + // they have to get the template types just right and it can cause errors. + std::vector> other(vect.container()); + EXPECT_EQ(stack_buffer, &other.front()); + EXPECT_TRUE(vect.stack_data().used_stack_buffer_); + for (int i = 0; i < stack_size; i++) + EXPECT_EQ(i, other[i]); +} + +TEST(StackContainer, VectorDoubleDelete) { + // Regression testing for double-delete. + typedef StackVector, 2> Vector; + Vector vect; + + int alive = 0; + Ref dummy = AcquireRef(new Dummy(&alive)); + EXPECT_EQ(alive, 1); + + vect->push_back(dummy); + EXPECT_EQ(alive, 1); + + Dummy* dummy_unref = dummy.Get(); + dummy = nullptr; + EXPECT_EQ(alive, 1); + + auto itr = std::find(vect->begin(), vect->end(), dummy_unref); + EXPECT_EQ(itr->Get(), dummy_unref); + vect->erase(itr); + EXPECT_EQ(alive, 0); + + // Shouldn't crash at exit. +} + +namespace { + + template + class AlignedData { + public: + AlignedData() { + memset(data_, 0, alignment); + } + ~AlignedData() = default; + alignas(alignment) char data_[alignment]; + }; + +} // anonymous namespace + +#define EXPECT_ALIGNED(ptr, align) EXPECT_EQ(0u, reinterpret_cast(ptr) & (align - 1)) + +TEST(StackContainer, BufferAlignment) { + StackVector text; + text->push_back(L'A'); + EXPECT_ALIGNED(&text[0], alignof(wchar_t)); + + StackVector doubles; + doubles->push_back(0.0); + EXPECT_ALIGNED(&doubles[0], alignof(double)); + + StackVector, 1> aligned16; + aligned16->push_back(AlignedData<16>()); + EXPECT_ALIGNED(&aligned16[0], 16); + +#if !defined(DAWN_COMPILER_GCC) || defined(__x86_64__) || defined(__i386__) + // It seems that non-X86 gcc doesn't respect greater than 16 byte alignment. + // See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=33721 for details. + // TODO(sbc):re-enable this if GCC starts respecting higher alignments. + StackVector, 1> aligned256; + aligned256->push_back(AlignedData<256>()); + EXPECT_ALIGNED(&aligned256[0], 256); +#endif +} + +template class StackVector; +template class StackVector, 2>; + +template +void CheckStackVectorElements(const StackVector& vec, std::initializer_list expected) { + auto expected_it = expected.begin(); + EXPECT_EQ(vec->size(), expected.size()); + for (T t : vec) { + EXPECT_NE(expected.end(), expected_it); + EXPECT_EQ(*expected_it, t); + ++expected_it; + } + EXPECT_EQ(expected.end(), expected_it); +} + +TEST(StackContainer, Iteration) { + StackVector vect; + vect->push_back(7); + vect->push_back(11); + + CheckStackVectorElements(vect, {7, 11}); + for (int& i : vect) { + ++i; + } + CheckStackVectorElements(vect, {8, 12}); + vect->push_back(13); + CheckStackVectorElements(vect, {8, 12, 13}); + vect->resize(5); + CheckStackVectorElements(vect, {8, 12, 13, 0, 0}); + vect->resize(1); + CheckStackVectorElements(vect, {8}); +}