Implement edge cases in insert/end and add a simple test case.

This commit is contained in:
Hans-Kristian Arntzen 2019-04-08 15:22:22 +02:00
parent 6f8982bf3f
commit 3a57286595
3 changed files with 294 additions and 6 deletions

View File

@ -415,11 +415,18 @@ if (SPIRV_CROSS_CLI)
add_executable(spirv-cross-c-api-test tests-other/c_api_test.c)
target_link_libraries(spirv-cross-c-api-test spirv-cross-c)
set_target_properties(spirv-cross-c-api-test PROPERTIES LINK_FLAGS "${spirv-cross-link-flags}")
add_executable(spirv-cross-small-vector-test tests-other/small_vector.cpp)
target_link_libraries(spirv-cross-small-vector-test spirv-cross-core)
set_target_properties(spirv-cross-small-vector-test PROPERTIES LINK_FLAGS "${spirv-cross-link-flags}")
if (CMAKE_COMPILER_IS_GNUCXX OR (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang"))
target_compile_options(spirv-cross-c-api-test PRIVATE -std=c89 -Wall -Wextra)
endif()
add_test(NAME spirv-cross-c-api-test
COMMAND $<TARGET_FILE:spirv-cross-c-api-test> ${CMAKE_CURRENT_SOURCE_DIR}/tests-other/c_api_test.spv)
add_test(NAME spirv-cross-small-vector-test
COMMAND $<TARGET_FILE:spirv-cross-small-vector-test>)
add_test(NAME spirv-cross-test
COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_shaders.py --parallel
${spirv-cross-externals}

View File

@ -222,7 +222,7 @@ protected:
// 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.
// It is *NOT* a drop-in replacement in general projects.
template <typename T, size_t N = 8>
class SmallVector : public VectorView<T>
{
@ -380,16 +380,89 @@ public:
void insert(T *itr, const T *insert_begin, const T *insert_end)
{
auto count = size_t(insert_end - insert_begin);
if (itr == this->end())
{
auto count = size_t(insert_end - insert_begin);
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
SPIRV_CROSS_THROW("Mid-insert not implemented.");
{
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<T *>(malloc(target_capacity * sizeof(T))) : stack_storage.data();
if (!new_buffer)
SPIRV_CROSS_THROW("Out of memory.");
// 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())
{
--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);
std::copy(insert_begin, insert_end, itr);
}
this->buffer_size += count;
}
}
T *erase(T *itr)
@ -401,9 +474,16 @@ public:
void erase(T *start_erase, T *end_erase)
{
if (end_erase != this->end())
SPIRV_CROSS_THROW("Mid-erase not implemented.");
resize(size_t(start_erase - this->begin()));
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)

View File

@ -0,0 +1,201 @@
/*
* Copyright 2019 Hans-Kristian Arntzen
*
* 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 "spirv_cross.hpp"
using namespace spirv_cross;
// Test the tricky bits of the implementation.
// Running the entire test suite on this implementation should find all other potential issues.
static int allocations = 0;
static int deallocations = 0;
#define SPVC_ASSERT(x) do { \
if (!(x)) SPIRV_CROSS_THROW("Assert: " #x " failed!"); \
} while(0)
struct RAIIInt
{
RAIIInt(int v_) : v(v_) { allocations++; }
~RAIIInt() { deallocations++; }
RAIIInt() { allocations++; }
RAIIInt(const RAIIInt &other) { v = other.v; allocations++; }
RAIIInt(RAIIInt &&other) SPIRV_CROSS_NOEXCEPT { v = other.v; allocations++; }
RAIIInt &operator=(RAIIInt &&) = default;
RAIIInt &operator=(const RAIIInt &) = default;
int v = 0;
};
static void propagate_stack_to_heap()
{
SmallVector<RAIIInt, 2> ints;
ints.emplace_back(1);
ints.emplace_back(2);
auto *old_data = ints.data();
SPVC_ASSERT(ints[0].v == 1);
SPVC_ASSERT(ints[1].v == 2);
ints.emplace_back(3);
SPVC_ASSERT(old_data != ints.data());
SPVC_ASSERT(ints[0].v == 1);
SPVC_ASSERT(ints[1].v == 2);
SPVC_ASSERT(ints[2].v == 3);
SPVC_ASSERT(ints.size() == 3);
}
static void insert_end()
{
SmallVector<RAIIInt, 2> ints;
ints.emplace_back(1);
ints.emplace_back(2);
const RAIIInt new_ints[3] = { 10, 20, 30 };
ints.insert(ints.end(), new_ints, new_ints + 3);
SPVC_ASSERT(ints.size() == 5);
SPVC_ASSERT(ints[0].v == 1);
SPVC_ASSERT(ints[1].v == 2);
SPVC_ASSERT(ints[2].v == 10);
SPVC_ASSERT(ints[3].v == 20);
SPVC_ASSERT(ints[4].v == 30);
}
static void insert_begin_realloc()
{
SmallVector<RAIIInt, 2> ints;
ints.emplace_back(1);
ints.emplace_back(2);
const RAIIInt new_ints[3] = { 10, 20, 30 };
ints.insert(ints.begin(), new_ints, new_ints + 3);
SPVC_ASSERT(ints.size() == 5);
SPVC_ASSERT(ints[0].v == 10);
SPVC_ASSERT(ints[1].v == 20);
SPVC_ASSERT(ints[2].v == 30);
SPVC_ASSERT(ints[3].v == 1);
SPVC_ASSERT(ints[4].v == 2);
}
static void insert_middle_realloc()
{
SmallVector<RAIIInt, 2> ints;
ints.emplace_back(1);
ints.emplace_back(2);
const RAIIInt new_ints[3] = { 10, 20, 30 };
ints.insert(ints.begin() + 1, new_ints, new_ints + 3);
SPVC_ASSERT(ints.size() == 5);
SPVC_ASSERT(ints[0].v == 1);
SPVC_ASSERT(ints[1].v == 10);
SPVC_ASSERT(ints[2].v == 20);
SPVC_ASSERT(ints[3].v == 30);
SPVC_ASSERT(ints[4].v == 2);
}
static void insert_begin_no_realloc()
{
SmallVector<RAIIInt, 2> ints;
ints.reserve(10);
ints.emplace_back(1);
ints.emplace_back(2);
const RAIIInt new_ints[3] = { 10, 20, 30 };
ints.insert(ints.begin(), new_ints, new_ints + 3);
SPVC_ASSERT(ints.size() == 5);
SPVC_ASSERT(ints[0].v == 10);
SPVC_ASSERT(ints[1].v == 20);
SPVC_ASSERT(ints[2].v == 30);
SPVC_ASSERT(ints[3].v == 1);
SPVC_ASSERT(ints[4].v == 2);
}
static void insert_middle_no_realloc()
{
SmallVector<RAIIInt, 2> ints;
ints.reserve(10);
ints.emplace_back(1);
ints.emplace_back(2);
const RAIIInt new_ints[3] = { 10, 20, 30 };
ints.insert(ints.begin() + 1, new_ints, new_ints + 3);
SPVC_ASSERT(ints.size() == 5);
SPVC_ASSERT(ints[0].v == 1);
SPVC_ASSERT(ints[1].v == 10);
SPVC_ASSERT(ints[2].v == 20);
SPVC_ASSERT(ints[3].v == 30);
SPVC_ASSERT(ints[4].v == 2);
}
static void erase_end()
{
SmallVector<RAIIInt, 2> ints;
ints.emplace_back(1);
ints.emplace_back(2);
ints.emplace_back(3);
ints.emplace_back(4);
ints.erase(ints.begin() + 1, ints.end());
SPVC_ASSERT(ints.size() == 1);
SPVC_ASSERT(ints[0].v == 1);
}
static void erase_middle()
{
SmallVector<RAIIInt, 2> ints;
ints.emplace_back(1);
ints.emplace_back(2);
ints.emplace_back(3);
ints.emplace_back(4);
ints.erase(ints.begin() + 1, ints.end() - 1);
SPVC_ASSERT(ints.size() == 2);
SPVC_ASSERT(ints[0].v == 1);
SPVC_ASSERT(ints[1].v == 4);
}
static void erase_start()
{
SmallVector<RAIIInt, 2> ints;
ints.emplace_back(1);
ints.emplace_back(2);
ints.emplace_back(3);
ints.emplace_back(4);
ints.erase(ints.begin(), ints.end() - 2);
SPVC_ASSERT(ints.size() == 2);
SPVC_ASSERT(ints[0].v == 3);
SPVC_ASSERT(ints[1].v == 4);
}
int main()
{
propagate_stack_to_heap();
insert_end();
insert_begin_realloc();
insert_begin_no_realloc();
insert_middle_realloc();
insert_middle_no_realloc();
erase_end();
erase_middle();
erase_start();
SPVC_ASSERT(allocations > 0 && deallocations > 0 && deallocations == allocations);
}