Yarn: Add Containers.hpp

The yarn::containers namespace holds STL-like container implementations that are optimized for avoiding heap allocations.

Bug: b/139010488
Change-Id: I9620488c607742132aff9dfdd35a7582a82bf687
Reviewed-on: https://swiftshader-review.googlesource.com/c/SwiftShader/+/34814
Tested-by: Ben Clayton <bclayton@google.com>
Reviewed-by: Nicolas Capens <nicolascapens@google.com>
Kokoro-Presubmit: kokoro <noreply+kokoro@google.com>
diff --git a/src/Yarn/Containers.hpp b/src/Yarn/Containers.hpp
new file mode 100644
index 0000000..b8b66a8
--- /dev/null
+++ b/src/Yarn/Containers.hpp
@@ -0,0 +1,255 @@
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+//
+// 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.
+
+// The yarn::containers namespace holds STL-like container implementations that
+// are optimized for avoiding heap allocations.
+// Note that unlike many other types in yarn, these containers offer no
+// protection for concurrent access and are considered 'thread-unsafe'.
+
+#ifndef yarn_containers_hpp
+#define yarn_containers_hpp
+
+#include "Debug.hpp"
+
+#include <algorithm> // std::max
+#include <type_traits> // std::aligned_storage
+#include <utility> // std::move
+
+#include <stddef.h> // size_t
+
+namespace yarn {
+namespace containers {
+
+////////////////////////////////////////////////////////////////////////////////
+// vector<T, BASE_CAPACITY>
+////////////////////////////////////////////////////////////////////////////////
+
+// vector is a container of contiguously stored elements.
+// Unlike std::vector, yarn::containers::vector keeps the first BASE_CAPACITY
+// elements internally, which will avoid dynamic heap allocations.
+// Once the vector exceeds BASE_CAPACITY elements, vector will allocate storage
+// from the heap.
+template <typename T, int BASE_CAPACITY>
+class vector
+{
+public:
+    inline vector() = default;
+
+    template <int BASE_CAPACITY_2>
+    inline vector(const vector<T, BASE_CAPACITY_2>& other);
+
+    template <int BASE_CAPACITY_2>
+    inline vector(vector<T, BASE_CAPACITY_2>&& other);
+
+    inline ~vector();
+
+    template <int BASE_CAPACITY_2>
+    inline vector<T, BASE_CAPACITY>& operator = (const vector<T, BASE_CAPACITY_2>&);
+
+    template <int BASE_CAPACITY_2>
+    inline vector<T, BASE_CAPACITY>& operator = (vector<T, BASE_CAPACITY_2>&&);
+
+    inline void push_back(const T& el);
+    inline void emplace_back(T&& el);
+    inline void pop_back();
+    inline T& front();
+    inline T& back();
+    inline T* begin();
+    inline T* end();
+    inline T& operator[] (size_t i);
+    inline const T& operator[] (size_t i) const;
+    inline size_t size() const;
+    inline size_t cap() const;
+    inline void resize(size_t n);
+    inline void reserve(size_t n);
+
+private:
+    using TStorage = typename std::aligned_storage<sizeof(T), alignof(T)>::type;
+
+    inline void free();
+
+    size_t count = 0;
+    size_t capacity = BASE_CAPACITY;
+    TStorage buffer[BASE_CAPACITY];
+    TStorage* elements = buffer;
+};
+
+template <typename T, int BASE_CAPACITY>
+template <int BASE_CAPACITY_2>
+vector<T, BASE_CAPACITY>::vector(const vector<T, BASE_CAPACITY_2>& other)
+{
+    *this = other;
+}
+
+template <typename T, int BASE_CAPACITY>
+template <int BASE_CAPACITY_2>
+vector<T, BASE_CAPACITY>::vector(vector<T, BASE_CAPACITY_2>&& other)
+{
+    *this = std::move(other);
+}
+
+template <typename T, int BASE_CAPACITY>
+vector<T, BASE_CAPACITY>::~vector()
+{
+    free();
+}
+
+template <typename T, int BASE_CAPACITY>
+template <int BASE_CAPACITY_2>
+vector<T, BASE_CAPACITY>& vector<T, BASE_CAPACITY>::operator = (const vector<T, BASE_CAPACITY_2>& other)
+{
+    free();
+    reserve(other.size());
+    count = other.size();
+    for (size_t i = 0; i < count; i++)
+    {
+        new (&reinterpret_cast<T*>(elements)[i]) T(other[i]);
+    }
+    return *this;
+}
+
+template <typename T, int BASE_CAPACITY>
+template <int BASE_CAPACITY_2>
+vector<T, BASE_CAPACITY>& vector<T, BASE_CAPACITY>::operator = (vector<T, BASE_CAPACITY_2>&& other)
+{
+    free();
+    reserve(other.size());
+    count = other.size();
+    for (size_t i = 0; i < count; i++)
+    {
+        new (&reinterpret_cast<T*>(elements)[i]) T(std::move(other[i]));
+    }
+    other.resize(0);
+    return *this;
+}
+
+template <typename T, int BASE_CAPACITY>
+void vector<T, BASE_CAPACITY>::push_back(const T& el)
+{
+    reserve(count + 1);
+    new (&reinterpret_cast<T*>(elements)[count]) T(el);
+    count++;
+}
+
+template <typename T, int BASE_CAPACITY>
+void vector<T, BASE_CAPACITY>::emplace_back(T&& el)
+{
+    reserve(count + 1);
+    new (&reinterpret_cast<T*>(elements)[count]) T(std::move(el));
+    count++;
+}
+
+template <typename T, int BASE_CAPACITY>
+void vector<T, BASE_CAPACITY>::pop_back()
+{
+    YARN_ASSERT(count > 0, "pop_back() called on empty vector");
+    count--;
+    reinterpret_cast<T*>(elements)[count].~T();
+}
+
+template <typename T, int BASE_CAPACITY>
+T& vector<T, BASE_CAPACITY>::front()
+{
+    YARN_ASSERT(count > 0, "front() called on empty vector");
+    return reinterpret_cast<T*>(elements)[0];
+}
+
+template <typename T, int BASE_CAPACITY>
+T& vector<T, BASE_CAPACITY>::back()
+{
+    YARN_ASSERT(count > 0, "back() called on empty vector");
+    return reinterpret_cast<T*>(elements)[count - 1];
+}
+
+template <typename T, int BASE_CAPACITY>
+T* vector<T, BASE_CAPACITY>::begin()
+{
+    return reinterpret_cast<T*>(elements);
+}
+
+template <typename T, int BASE_CAPACITY>
+T* vector<T, BASE_CAPACITY>::end()
+{
+    return reinterpret_cast<T*>(elements) + count;
+}
+
+template <typename T, int BASE_CAPACITY>
+T& vector<T, BASE_CAPACITY>::operator[] (size_t i)
+{
+    YARN_ASSERT(i < count, "index %d exceeds vector size %d", int(i), int(count));
+    return reinterpret_cast<T*>(elements)[i];
+}
+
+template <typename T, int BASE_CAPACITY>
+const T& vector<T, BASE_CAPACITY>::operator[] (size_t i) const
+{
+    YARN_ASSERT(i < count, "index %d exceeds vector size %d", int(i), int(count));
+    return reinterpret_cast<T*>(elements)[i];
+}
+
+template <typename T, int BASE_CAPACITY>
+size_t vector<T, BASE_CAPACITY>::size() const
+{
+    return count;
+}
+
+template <typename T, int BASE_CAPACITY>
+void vector<T, BASE_CAPACITY>::resize(size_t n)
+{
+    reserve(n);
+    while (count < n)
+    {
+        new (&reinterpret_cast<T*>(elements)[count++]) T();
+    }
+    while (n < count)
+    {
+        reinterpret_cast<T*>(elements)[--count].~T();
+    }
+}
+
+template <typename T, int BASE_CAPACITY>
+void vector<T, BASE_CAPACITY>::reserve(size_t n)
+{
+    if (n > capacity)
+    {
+        capacity = std::max<size_t>(n * 2, 8);
+        auto grown = new TStorage[capacity];
+        for (size_t i = 0; i < count; i++)
+        {
+            new (&reinterpret_cast<T*>(grown)[i]) T(std::move(reinterpret_cast<T*>(elements)[i]));
+        }
+        free();
+        elements = grown;
+    }
+}
+
+template <typename T, int BASE_CAPACITY>
+void vector<T, BASE_CAPACITY>::free()
+{
+    for (size_t i = 0; i < count; i++)
+    {
+        reinterpret_cast<T*>(elements)[i].~T();
+    }
+
+    if (elements != buffer)
+    {
+        delete []elements;
+        elements = nullptr;
+    }
+}
+
+} // namespace containers
+} // namespace yarn
+
+#endif  // yarn_containers_hpp
diff --git a/src/Yarn/Containers_test.cpp b/src/Yarn/Containers_test.cpp
new file mode 100644
index 0000000..d677606
--- /dev/null
+++ b/src/Yarn/Containers_test.cpp
@@ -0,0 +1,201 @@
+// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
+//
+// 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.
+
+#include "Containers.hpp"
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+#include <string>
+
+class ContainersVectorTest : public testing::Test {};
+
+TEST(ContainersVectorTest, Empty)
+{
+    yarn::containers::vector<std::string, 4> vector;
+    ASSERT_EQ(vector.size(), size_t(0));
+}
+
+TEST(ContainersVectorTest, WithinFixedCapIndex)
+{
+    yarn::containers::vector<std::string, 4> vector;
+    vector.resize(4);
+    vector[0] = "A";
+    vector[1] = "B";
+    vector[2] = "C";
+    vector[3] = "D";
+
+    ASSERT_EQ(vector[0], "A");
+    ASSERT_EQ(vector[1], "B");
+    ASSERT_EQ(vector[2], "C");
+    ASSERT_EQ(vector[3], "D");
+}
+
+TEST(ContainersVectorTest, BeyondFixedCapIndex)
+{
+    yarn::containers::vector<std::string, 1> vector;
+    vector.resize(4);
+    vector[0] = "A";
+    vector[1] = "B";
+    vector[2] = "C";
+    vector[3] = "D";
+
+    ASSERT_EQ(vector[0], "A");
+    ASSERT_EQ(vector[1], "B");
+    ASSERT_EQ(vector[2], "C");
+    ASSERT_EQ(vector[3], "D");
+}
+
+TEST(ContainersVectorTest, WithinFixedCapPushPop)
+{
+    yarn::containers::vector<std::string, 4> vector;
+    vector.push_back("A");
+    vector.push_back("B");
+    vector.push_back("C");
+    vector.push_back("D");
+
+    ASSERT_EQ(vector.size(), size_t(4));
+    ASSERT_EQ(vector.end() - vector.begin(), ptrdiff_t(4));
+
+    ASSERT_EQ(vector.front(), "A");
+    ASSERT_EQ(vector.back(), "D");
+    vector.pop_back();
+    ASSERT_EQ(vector.size(), size_t(3));
+    ASSERT_EQ(vector.end() - vector.begin(), ptrdiff_t(3));
+
+    ASSERT_EQ(vector.front(), "A");
+    ASSERT_EQ(vector.back(), "C");
+    vector.pop_back();
+    ASSERT_EQ(vector.size(), size_t(2));
+    ASSERT_EQ(vector.end() - vector.begin(), ptrdiff_t(2));
+
+    ASSERT_EQ(vector.front(), "A");
+    ASSERT_EQ(vector.back(), "B");
+    vector.pop_back();
+    ASSERT_EQ(vector.size(), size_t(1));
+    ASSERT_EQ(vector.end() - vector.begin(), ptrdiff_t(1));
+
+    ASSERT_EQ(vector.front(), "A");
+    ASSERT_EQ(vector.back(), "A");
+    vector.pop_back();
+    ASSERT_EQ(vector.size(), size_t(0));
+}
+
+TEST(ContainersVectorTest, BeyondFixedCapPushPop)
+{
+    yarn::containers::vector<std::string, 2> vector;
+    vector.push_back("A");
+    vector.push_back("B");
+    vector.push_back("C");
+    vector.push_back("D");
+
+    ASSERT_EQ(vector.size(), size_t(4));
+    ASSERT_EQ(vector.end() - vector.begin(), ptrdiff_t(4));
+
+    ASSERT_EQ(vector.front(), "A");
+    ASSERT_EQ(vector.back(), "D");
+    vector.pop_back();
+    ASSERT_EQ(vector.size(), size_t(3));
+    ASSERT_EQ(vector.end() - vector.begin(), ptrdiff_t(3));
+
+    ASSERT_EQ(vector.front(), "A");
+    ASSERT_EQ(vector.back(), "C");
+    vector.pop_back();
+    ASSERT_EQ(vector.size(), size_t(2));
+    ASSERT_EQ(vector.end() - vector.begin(), ptrdiff_t(2));
+
+    ASSERT_EQ(vector.front(), "A");
+    ASSERT_EQ(vector.back(), "B");
+    vector.pop_back();
+    ASSERT_EQ(vector.size(), size_t(1));
+    ASSERT_EQ(vector.end() - vector.begin(), ptrdiff_t(1));
+
+    ASSERT_EQ(vector.front(), "A");
+    ASSERT_EQ(vector.back(), "A");
+    vector.pop_back();
+    ASSERT_EQ(vector.size(), size_t(0));
+}
+
+TEST(ContainersVectorTest, CopyConstruct)
+{
+    yarn::containers::vector<std::string, 4> vectorA;
+
+    vectorA.resize(3);
+    vectorA[0] = "A";
+    vectorA[1] = "B";
+    vectorA[2] = "C";
+
+    yarn::containers::vector<std::string, 2> vectorB(vectorA);
+    ASSERT_EQ(vectorB.size(), size_t(3));
+    ASSERT_EQ(vectorB[0], "A");
+    ASSERT_EQ(vectorB[1], "B");
+    ASSERT_EQ(vectorB[2], "C");
+}
+
+TEST(ContainersVectorTest, MoveConstruct)
+{
+    yarn::containers::vector<std::string, 4> vectorA;
+
+    vectorA.resize(3);
+    vectorA[0] = "A";
+    vectorA[1] = "B";
+    vectorA[2] = "C";
+
+    yarn::containers::vector<std::string, 2> vectorB(std::move(vectorA));
+    ASSERT_EQ(vectorB.size(), size_t(3));
+    ASSERT_EQ(vectorB[0], "A");
+    ASSERT_EQ(vectorB[1], "B");
+    ASSERT_EQ(vectorB[2], "C");
+}
+
+TEST(ContainersVectorTest, Copy)
+{
+    yarn::containers::vector<std::string, 4> vectorA;
+    yarn::containers::vector<std::string, 2> vectorB;
+
+    vectorA.resize(3);
+    vectorA[0] = "A";
+    vectorA[1] = "B";
+    vectorA[2] = "C";
+
+    vectorB.resize(1);
+    vectorB[0] = "Z";
+
+    vectorB = vectorA;
+    ASSERT_EQ(vectorB.size(), size_t(3));
+    ASSERT_EQ(vectorB[0], "A");
+    ASSERT_EQ(vectorB[1], "B");
+    ASSERT_EQ(vectorB[2], "C");
+}
+
+TEST(ContainersVectorTest, Move)
+{
+    yarn::containers::vector<std::string, 4> vectorA;
+    yarn::containers::vector<std::string, 2> vectorB;
+
+    vectorA.resize(3);
+    vectorA[0] = "A";
+    vectorA[1] = "B";
+    vectorA[2] = "C";
+
+    vectorB.resize(1);
+    vectorB[0] = "Z";
+
+    vectorB = std::move(vectorA);
+    ASSERT_EQ(vectorA.size(), size_t(0));
+    ASSERT_EQ(vectorB.size(), size_t(3));
+    ASSERT_EQ(vectorB[0], "A");
+    ASSERT_EQ(vectorB[1], "B");
+    ASSERT_EQ(vectorB[2], "C");
+}