diff --git a/doc/corrade-changelog.dox b/doc/corrade-changelog.dox index 25b68f764..d08f45fee 100644 --- a/doc/corrade-changelog.dox +++ b/doc/corrade-changelog.dox @@ -83,6 +83,8 @@ namespace Corrade { @subsubsection corrade-changelog-latest-new-utility Utility library +- @ref Utility::allocateAligned() family of functions for overaligned + allocations, suitable for efficient SIMD operations - Added @ref Utility::Arguments::addArrayArgument() as a positional counterpart to @ref Utility::Arguments::addArrayOption() - @ref Utility::Configuration and @ref Utility::Arguments can now read and diff --git a/doc/snippets/Utility.cpp b/doc/snippets/Utility.cpp index a6751cc6a..625414b4e 100644 --- a/doc/snippets/Utility.cpp +++ b/doc/snippets/Utility.cpp @@ -43,6 +43,7 @@ #include "Corrade/Utility/Format.h" #include "Corrade/Utility/FormatStl.h" #include "Corrade/Utility/Macros.h" +#include "Corrade/Utility/Memory.h" #include "Corrade/Utility/Sha1.h" #include "Corrade/Utility/StlMath.h" @@ -352,6 +353,38 @@ args.addOption("input") }; int main() { +{ +typedef float __m256; +std::size_t size{}; +/* [allocateAligned] */ +Containers::Array<__m256> avxVectors = + Utility::allocateAligned<__m256>(size); +/* [allocateAligned] */ +} + +{ +typedef float Matrix4; +std::size_t size{}; +/* [allocateAligned-explicit] */ +Containers::Array avxMatrices = + Utility::allocateAligned(size); +/* [allocateAligned-explicit] */ +} + +{ +/* [allocateAligned-NoInit] */ +struct Foo { + explicit Foo(int) {} + DOXYGEN_IGNORE() +}; + +Containers::Array data = Utility::allocateAligned(NoInit, 5); + +int index = 0; +for(Foo& f: data) new(&f) Foo{index++}; +/* [allocateAligned-NoInit] */ +} + { /* [Configuration-usage] */ Utility::Configuration conf{"my.conf"}; diff --git a/src/Corrade/Containers/Array.h b/src/Corrade/Containers/Array.h index 75132dd0f..f5c36d7a1 100644 --- a/src/Corrade/Containers/Array.h +++ b/src/Corrade/Containers/Array.h @@ -138,6 +138,15 @@ is possible to initialize the array in a different way using so-called *tags*: @snippet Containers.cpp Array-usage-initialization + + +@m_class{m-note m-success} + +@par Aligned allocations + Please note that @ref Array allocations are by default only aligned to + @cpp 2*sizeof(void*) @ce. If you need overaligned memory for working with + SIMD types, use @ref Utility::allocateAligned() instead. + @subsection Containers-Array-usage-wrapping Wrapping externally allocated arrays By default the class makes all allocations using @cpp operator new[] @ce and diff --git a/src/Corrade/Containers/ArrayTuple.h b/src/Corrade/Containers/ArrayTuple.h index 7ac7610a9..698261ec7 100644 --- a/src/Corrade/Containers/ArrayTuple.h +++ b/src/Corrade/Containers/ArrayTuple.h @@ -76,6 +76,16 @@ only for as long as the instance exists. It's up to you what happens to the views after --- in the above case, all needed information is already contained in the `info` structure, so the views aren't needed after anymore. + + +@m_class{m-note m-success} + +@par Aligned allocations + Please note that @ref ArrayTuple allocations are by default only aligned to + @cpp 2*sizeof(void*) @ce. If you need overaligned memory for working with + SIMD types, use a @ref Containers-ArrayTuple-allocators-deleters "custom allocator" + together with a @ref Utility::allocateAligned() instead. + @section Containers-ArrayTuple-nontrivial Storing non-trivial types The usage isn't limited to just trivial types --- by default (or if you diff --git a/src/Corrade/Utility/CMakeLists.txt b/src/Corrade/Utility/CMakeLists.txt index fb2a62010..a170c8b96 100644 --- a/src/Corrade/Utility/CMakeLists.txt +++ b/src/Corrade/Utility/CMakeLists.txt @@ -63,6 +63,7 @@ if(WITH_UTILITY) Format.h FormatStl.h Macros.h + Memory.h MurmurHash2.h Resource.h Sha1.h diff --git a/src/Corrade/Utility/Memory.h b/src/Corrade/Utility/Memory.h new file mode 100644 index 000000000..19ed65657 --- /dev/null +++ b/src/Corrade/Utility/Memory.h @@ -0,0 +1,290 @@ +#ifndef Corrade_Utility_Memory_h +#define Corrade_Utility_Memory_h +/* + This file is part of Corrade. + + Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, + 2017, 2018, 2019, 2020, 2021 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Function @ref Corrade::Utility::allocateAligned() + * @m_since_latest + */ + +/* Not inside System.h because there we don't want the Array include, and + this header may grow with utilities for vmem-backed non-reallocating + containers or magic ring buffers */ + +#include "Corrade/Containers/Array.h" +#include "Corrade/Containers/initializeHelpers.h" +#include "Corrade/Utility/visibility.h" + +#ifdef CORRADE_TARGET_UNIX +#include +#elif defined(CORRADE_TARGET_WINDOWS) +/* as well, but I don't want to include all the nasty shit */ +extern "C" void* __cdecl _aligned_malloc(size_t, size_t); +extern "C" void __cdecl _aligned_free(void*); +#endif + +namespace Corrade { namespace Utility { + +/** +@brief Allocate aligned memory and value-initialize it +@tparam T Type of the returned array +@tparam alignment Allocation alignment, in bytes +@param size Count of @p T items to allocate. If @cpp 0 @ce, no + allocation is done. +@m_since_latest + +Compared to the classic C @ref std::malloc() or C++ @cpp new @ce that commonly +aligns only to @cpp 2*sizeof(void*) @ce, this function returns "overaligned" +allocations, which is mainly useful for efficient SIMD operations. Example +usage: + +@snippet Utility.cpp allocateAligned + +The alignment is implicitly @cpp alignof(T) @ce, but can be overriden with the +@p alignment template parameter. When specified explicitly, it is expected to +be a power-of-two value, at most @cpp 256 @ce bytes and the total byte size +being a multiple of the alignment: + +@snippet Utility.cpp allocateAligned-explicit + +The function is implemented using @m_class{m-doc-external} [aligned_alloc()](https://man.archlinux.org/man/aligned_alloc.3) +on @ref CORRADE_TARGET_UNIX "UNIX" systems and @m_class{m-doc-external} [_aligned_malloc()](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-malloc) +on @ref CORRADE_TARGET_WINDOWS "Windows". On other platforms (such as +@ref CORRADE_TARGET_EMSCRIPTEN "Emscripten"), if requested alignment is higher +than platform's default alignment, the allocation is done via a classic +@ref std::malloc() with an @cpp alignment - 1 @ce padding and the returned +pointer is then patched to satisfy the alignment. In all cases the returned +@ref Containers::Array has a custom deleter, which for non-trivial types calls +destructors on all types, and then either @ref std::free() or, in case of +Windows, @m_class{m-doc-external} [_aligned_free()](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-free) +is used to deallocate the memory. + +@section Utility-allocateAligned-initialization Array initialization + +Like with @ref Containers::Array, the returned array is by default +* *value-initialized*, which means that trivial types are zero-initialized and +the default constructor is called on other types. Different behavior can be +achieved with the following tags, compared to @ref Containers::Array the +initialization is performed separately from the allocation itself with either a +loop or a call to @ref std::memset(). + +- @ref allocateAligned(Containers::DefaultInitT, std::size_t) leaves trivial + types uninitialized and calls the default constructor elsewhere. Because of + the differing behavior for trivial types it's better to explicitly use + either the @ref Containers::ValueInit or @ref Containers::NoInit variants + instead. +- @ref allocateAligned(Containers::ValueInitT, std::size_t) is equivalent to + the default case, zero-initializing trivial types and calling the default + constructor elsewhere. Useful when you want to make the choice appear + explicit. +- @ref allocateAligned(Containers::NoInitT, std::size_t) does not initialize + anything. Useful for trivial types when you'll be overwriting the contents + anyway, for non-trivial types this is the dangerous option and you need to + call the constructor on all elements manually using placement new, + @ref std::uninitialized_copy() or similar --- see the function docs for an + example. +*/ +template inline Containers::Array allocateAligned(std::size_t size); + +/** +@brief Allocate aligned memory and default-initialize it +@m_since_latest + +Compared to @ref allocateAligned(std::size_t), trivial types are not +initialized and default constructor is called otherwise. Because of the +differing behavior for trivial types it's better to explicitly use either the +@ref allocateAligned(ValueInitT, std::size_t) or the +@ref allocateAligned(NoInitT, std::size_t) variant instead. + +Implemented via @ref allocateAligned(NoInitT, std::size_t) with a +loop calling the constructors on the returned allocation in case of non-trivial +types. +@see @ref allocateAligned(ValueInitT, std::size_t) +*/ +template Containers::Array allocateAligned(DefaultInitT, std::size_t size); + +/** +@brief Allocate aligned memory and value-initialize it +@m_since_latest + +Same as @ref allocateAligned(std::size_t), just more explicit. Implemented via +@ref allocateAligned(NoInitT, std::size_t) with either a +@ref std::memset() or a loop calling the constructors on the returned +allocation. +@see @ref allocateAligned(DefaultInitT, std::size_t) +*/ +template Containers::Array allocateAligned(ValueInitT, std::size_t size); + +/** +@brief Allocate aligned memory and leave it uninitialized +@m_since_latest + +Compared to @ref allocateAligned(std::size_t), the memory is left in an +unitialized state. For trivial types is equivalent to +@ref allocateAligned(DefaultInitT, std::size_t). For non-trivial +types, destruction is always done using a custom deleter that explicitly calls +the destructor on *all elements* --- which means that for non-trivial types +you're expected to construct all elements using placement new (or for example +@ref std::uninitialized_copy()) in order to avoid calling destructors on +uninitialized memory: + +@snippet Utility.cpp allocateAligned-NoInit + +@see @ref allocateAligned(DefaultInitT, std::size_t), + @ref allocateAligned(ValueInitT, std::size_t) +*/ +template Containers::Array allocateAligned(NoInitT, std::size_t size); + +namespace Implementation { + +#ifdef CORRADE_TARGET_WINDOWS +template void alignedDeleter(typename std::enable_if::value, T>::type* const data, std::size_t) { + _aligned_free(data); +} +template void alignedDeleter(typename std::enable_if::value, T>::type* const data, std::size_t size) { + for(std::size_t i = 0; i != size; ++i) data[i].~T(); + _aligned_free(data); +} +#else +template void alignedDeleter(typename std::enable_if::value, T>::type* const data, std::size_t) { + std::free(data); +} +template void alignedDeleter(typename std::enable_if::value, T>::type* const data, std::size_t size) { + for(std::size_t i = 0; i != size; ++i) data[i].~T(); + std::free(data); +} +#ifndef CORRADE_TARGET_UNIX +template void alignedOffsetDeleter(typename std::enable_if::value, T>::type* const data, std::size_t) { + /* Using a unsigned byte in order to be able to represent a 255 byte offset + as well */ + std::uint8_t* const dataChar = reinterpret_cast(data); + std::free(dataChar - *(dataChar -1)); +} +template void alignedOffsetDeleter(typename std::enable_if::value, T>::type* const data, std::size_t size) { + for(std::size_t i = 0; i != size; ++i) data[i].~T(); + + /* Using a unsigned byte in order to be able to represent a 255 byte offset + as well */ + std::uint8_t* const dataChar = reinterpret_cast(data); + std::free(dataChar - dataChar[-1]); +} +#endif +#endif + +} + +template Containers::Array allocateAligned(NoInitT, const std::size_t size) { + /* On non-Unix non-Windows platforms we're storing the alignment offset + in a byte right before the returned pointer. Because it's a byte, we + can represent a value of at most 255 there (256 would make no sense as + a 256-byte-aligned allocation can be only off by 255 bytes at most). + Again it's good to have the same requirements on all platforms so + checking this always. */ + static_assert(alignment && !(alignment & (alignment - 1)) && alignment <= 256, + "alignment expected to be a power of two not larger than 256"); + + /* Required only by aligned_alloc() I think, but it's good to have the same + requirements on all platforms for better portability */ + CORRADE_ASSERT(size*sizeof(T) % alignment == 0, "Utility::allocateAligned(): total byte size" << size*sizeof(T) << "not a multiple of a" << alignment << Debug::nospace << "-byte alignment", {}); + + /* Unix platforms */ + #ifdef CORRADE_TARGET_UNIX + /* For some reason, allocating zero bytes still returns a non-null pointer + which seems weird and confusing. Handle that explicitly instead. */ + if(!size) return {}; + + /* aligned_alloc() needs _ISOC11_SOURCE, let's hope it just works. If you + get a compilation error here, please complain -- in that case I need to + switch to posix_memalign(). */ + return Containers::Array{static_cast(aligned_alloc(alignment, size*sizeof(T))), size, Implementation::alignedDeleter}; + + /* Windows */ + #elif defined(CORRADE_TARGET_WINDOWS) + /* Zero size is not allowed: https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/aligned-malloc */ + if(!size) return {}; + + return Containers::Array{static_cast(_aligned_malloc(size*sizeof(T), alignment)), size, Implementation::alignedDeleter}; + + /* Other -- for allocations larger than the min alignment allocate with + (align - 1) more and align manually, then provide a custom deleter that + undoes this. */ + #else + /* Because we always allocate `alignment - 1` more than the size, it means + even zero-size allocations would be allocations. Not desirable. */ + if(!size) return {}; + + /* Using a unsigned byte in order to be able to represent a 255 byte offset + as well */ + std::uint8_t* pointer; + std::ptrdiff_t offset; + if(alignment <= Containers::Implementation::MinAllocatedSize) { + pointer = static_cast(std::malloc(size*sizeof(T))); + offset = 0; + } else { + pointer = static_cast(std::malloc(size*sizeof(T) + alignment - 1)); + /* Ugh, I'm dumb. Can't this be calculated somehow sane? */ + if(reinterpret_cast(pointer) % alignment == 0) + offset = 0; + else + offset = alignment - reinterpret_cast(pointer) % alignment; + } + CORRADE_INTERNAL_ASSERT((reinterpret_cast(pointer) + offset) % alignment == 0); + + /* If the offset is zero, use the classic std::free() directly. If not, + save the offset in the byte right before what the output pointer will + point to and use a different deleter that will undo this offset before + calling std::free(). */ + void(*deleter)(T*, std::size_t); + if(offset == 0) { + deleter = Implementation::alignedDeleter; + } else { + pointer[-1] = offset; /* looking great, isn't it */ + deleter = Implementation::alignedOffsetDeleter; + } + return Containers::Array{reinterpret_cast(pointer + offset), size, deleter}; + #endif +} + +template Containers::Array allocateAligned(DefaultInitT, const std::size_t size) { + Containers::Array out = allocateAligned(NoInit, size); + Containers::Implementation::arrayConstruct(DefaultInit, out.begin(), out.end()); + return out; +} + +template Containers::Array allocateAligned(ValueInitT, const std::size_t size) { + Containers::Array out = allocateAligned(NoInit, size); + Containers::Implementation::arrayConstruct(ValueInit, out.begin(), out.end()); + return out; +} + +template inline Containers::Array allocateAligned(std::size_t size) { + return allocateAligned(ValueInit, size); +} + +}} + +#endif diff --git a/src/Corrade/Utility/Test/CMakeLists.txt b/src/Corrade/Utility/Test/CMakeLists.txt index 518d4762b..7b36a733d 100644 --- a/src/Corrade/Utility/Test/CMakeLists.txt +++ b/src/Corrade/Utility/Test/CMakeLists.txt @@ -190,6 +190,9 @@ endif() corrade_add_test(UtilityFatalTest FatalTest.cpp) set_tests_properties(UtilityFatalTest PROPERTIES WILL_FAIL ON) +corrade_add_test(UtilityMemoryTest MemoryTest.cpp) +target_compile_definitions(UtilityMemoryTest PRIVATE "CORRADE_GRACEFUL_ASSERT") + set(UtilityDirectoryTest_SRCS DirectoryTest.cpp) if(CORRADE_TARGET_IOS) set_source_files_properties(DirectoryTestFiles PROPERTIES @@ -298,6 +301,7 @@ set_target_properties( UtilityFormatTest UtilityHashDigestTest UtilityMacrosTest + UtilityMemoryTest UtilityResourceTest UtilityResourceStaticTest UtilitySha1Test diff --git a/src/Corrade/Utility/Test/MemoryTest.cpp b/src/Corrade/Utility/Test/MemoryTest.cpp new file mode 100644 index 000000000..a5f850707 --- /dev/null +++ b/src/Corrade/Utility/Test/MemoryTest.cpp @@ -0,0 +1,263 @@ +/* + This file is part of Corrade. + + Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, + 2017, 2018, 2019, 2020, 2021 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include + +#include "Corrade/TestSuite/Tester.h" +#include "Corrade/TestSuite/Compare/Container.h" +#include "Corrade/TestSuite/Compare/Numeric.h" +#include "Corrade/Utility/DebugStl.h" +#include "Corrade/Utility/Memory.h" + +namespace Corrade { namespace Utility { namespace Test { namespace { + +struct MemoryTest: TestSuite::Tester { + explicit MemoryTest(); + + template void allocateAlignedTrivial(); + void allocateAlignedTrivialNoInit(); + void allocateAlignedTrivialDefaultInit(); + void allocateAlignedTrivialValueInit(); + void allocateAlignedNontrivialNoInit(); + void allocateAlignedNontrivialDefaultInit(); + void allocateAlignedNontrivialValueInit(); + + void allocateZeroSizeTrivial(); + void allocateZeroSizeNontrivial(); + + void allocateExplicitAlignment(); + void allocateExplicitAlignmentNoInit(); + void allocateExplicitAlignmentDefaultInit(); + void allocateExplicitAlignmentValueInit(); + + void allocateNotMultipleOfAlignment(); + + void resetCounters(); +}; + +MemoryTest::MemoryTest() { + addRepeatedTests({ + &MemoryTest::allocateAlignedTrivial<8>, + &MemoryTest::allocateAlignedTrivial<16>, + &MemoryTest::allocateAlignedTrivial<32>, + &MemoryTest::allocateAlignedTrivial<64>, + &MemoryTest::allocateAlignedTrivial<128>, + &MemoryTest::allocateAlignedTrivial<256>}, 100); + + addTests({&MemoryTest::allocateAlignedTrivialNoInit, + &MemoryTest::allocateAlignedTrivialDefaultInit, + &MemoryTest::allocateAlignedTrivialValueInit}); + + addTests({&MemoryTest::allocateAlignedNontrivialNoInit, + &MemoryTest::allocateAlignedNontrivialDefaultInit, + &MemoryTest::allocateAlignedNontrivialValueInit}, + &MemoryTest::resetCounters, &MemoryTest::resetCounters); + + addTests({&MemoryTest::allocateZeroSizeTrivial}); + + addTests({&MemoryTest::allocateZeroSizeNontrivial}, + &MemoryTest::resetCounters, &MemoryTest::resetCounters); + + addRepeatedTests({ + &MemoryTest::allocateExplicitAlignment, + &MemoryTest::allocateExplicitAlignmentNoInit, + &MemoryTest::allocateExplicitAlignmentDefaultInit, + &MemoryTest::allocateExplicitAlignmentValueInit}, 100); + + addTests({&MemoryTest::allocateNotMultipleOfAlignment}); +} + +template struct alignas(alignment) Aligned { + int someData; +}; + +template void MemoryTest::allocateAlignedTrivial() { + setTestCaseTemplateName(std::to_string(alignment)); + + Containers::Array> data = allocateAligned>(testCaseRepeatId() + 1); + CORRADE_VERIFY(data.data()); + CORRADE_COMPARE(data.size(), testCaseRepeatId() + 1); + CORRADE_COMPARE_AS(reinterpret_cast(data.data()), alignment, + TestSuite::Compare::Divisible); + /* No way to verify that we *didn't* zero-initialize */ +} + +struct alignas(32) FourLongs { + std::uint64_t data[4]; +}; + +void MemoryTest::allocateAlignedTrivialNoInit() { + Containers::Array data = allocateAligned(NoInit, 7); + CORRADE_VERIFY(data.data()); + CORRADE_COMPARE(data.size(), 7); + CORRADE_COMPARE_AS(reinterpret_cast(data.data()), 32, + TestSuite::Compare::Divisible); +} + +void MemoryTest::allocateAlignedTrivialDefaultInit() { + Containers::Array data = allocateAligned(DefaultInit, 7); + CORRADE_VERIFY(data.data()); + CORRADE_COMPARE(data.size(), 7); + CORRADE_COMPARE_AS(reinterpret_cast(data.data()), 32, + TestSuite::Compare::Divisible); + /* No way to verify that we *didn't* zero-initialize */ +} + +void MemoryTest::allocateAlignedTrivialValueInit() { + Containers::Array data = allocateAligned(ValueInit, 7); + CORRADE_VERIFY(data.data()); + CORRADE_COMPARE(data.size(), 7); + CORRADE_COMPARE_AS(reinterpret_cast(data.data()), 32, + TestSuite::Compare::Divisible); + CORRADE_COMPARE_AS(Containers::arrayView(data[0].data), + Containers::arrayView({0, 0, 0, 0}), + TestSuite::Compare::Container); +} + +struct alignas(32) Immovable { + static int constructed; + static int destructed; + + Immovable(const Immovable&) = delete; + Immovable(Immovable&&) = delete; + explicit Immovable() { ++constructed; } + ~Immovable() { ++destructed; } + Immovable& operator=(const Immovable&) = delete; + Immovable& operator=(Immovable&&) = delete; +}; + +int Immovable::constructed = 0; +int Immovable::destructed = 0; + +void MemoryTest::resetCounters() { + Immovable::constructed = Immovable::destructed = 0; +} + +void MemoryTest::allocateAlignedNontrivialNoInit() { + { + Containers::Array data = allocateAligned(NoInit, 7); + CORRADE_VERIFY(data.data()); + CORRADE_COMPARE(data.size(), 7); + CORRADE_COMPARE_AS(reinterpret_cast(data.data()), 32, + TestSuite::Compare::Divisible); + CORRADE_COMPARE(Immovable::constructed, 0); + } + + CORRADE_COMPARE(Immovable::constructed, 0); + CORRADE_COMPARE(Immovable::destructed, 7); +} + +void MemoryTest::allocateAlignedNontrivialDefaultInit() { + { + Containers::Array data = allocateAligned(DefaultInit, 7); + CORRADE_VERIFY(data.data()); + CORRADE_COMPARE(data.size(), 7); + CORRADE_COMPARE_AS(reinterpret_cast(data.data()), 32, + TestSuite::Compare::Divisible); + CORRADE_COMPARE(Immovable::constructed, 7); + } + + CORRADE_COMPARE(Immovable::constructed, 7); + CORRADE_COMPARE(Immovable::destructed, 7); +} + +void MemoryTest::allocateAlignedNontrivialValueInit() { + { + Containers::Array data = allocateAligned(ValueInit, 7); + CORRADE_VERIFY(data.data()); + CORRADE_COMPARE(data.size(), 7); + CORRADE_COMPARE_AS(reinterpret_cast(data.data()), 32, + TestSuite::Compare::Divisible); + CORRADE_COMPARE(Immovable::constructed, 7); + } + + CORRADE_COMPARE(Immovable::constructed, 7); + CORRADE_COMPARE(Immovable::destructed, 7); +} + +void MemoryTest::allocateZeroSizeTrivial() { + Containers::Array data = allocateAligned(0); + CORRADE_VERIFY(!data.data()); + CORRADE_COMPARE(data.size(), 0); +} + +void MemoryTest::allocateZeroSizeNontrivial() { + { + Containers::Array data = allocateAligned(0); + CORRADE_VERIFY(!data.data()); + CORRADE_COMPARE(data.size(), 0); + } + + CORRADE_COMPARE(Immovable::constructed, 0); + CORRADE_COMPARE(Immovable::destructed, 0); +} + +void MemoryTest::allocateExplicitAlignment() { + Containers::Array data = allocateAligned((testCaseRepeatId() + 1)*32); + CORRADE_VERIFY(data.data()); + CORRADE_COMPARE(data.size(), (testCaseRepeatId() + 1)*32); + CORRADE_COMPARE_AS(reinterpret_cast(data.data()), 32, + TestSuite::Compare::Divisible); +} + +void MemoryTest::allocateExplicitAlignmentNoInit() { + Containers::Array data = allocateAligned(NoInit, (testCaseRepeatId() + 1)*32); + CORRADE_VERIFY(data.data()); + CORRADE_COMPARE(data.size(), (testCaseRepeatId() + 1)*32); + CORRADE_COMPARE_AS(reinterpret_cast(data.data()), 32, + TestSuite::Compare::Divisible); +} + +void MemoryTest::allocateExplicitAlignmentDefaultInit() { + Containers::Array data = allocateAligned(DefaultInit, (testCaseRepeatId() + 1)*32); + CORRADE_VERIFY(data.data()); + CORRADE_COMPARE(data.size(), (testCaseRepeatId() + 1)*32); + CORRADE_COMPARE_AS(reinterpret_cast(data.data()), 32, + TestSuite::Compare::Divisible); +} + +void MemoryTest::allocateExplicitAlignmentValueInit() { + Containers::Array data = allocateAligned(ValueInit, (testCaseRepeatId() + 1)*32); + CORRADE_VERIFY(data.data()); + CORRADE_COMPARE(data.size(), (testCaseRepeatId() + 1)*32); + CORRADE_COMPARE_AS(reinterpret_cast(data.data()), 32, + TestSuite::Compare::Divisible); +} + +void MemoryTest::allocateNotMultipleOfAlignment() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + allocateAligned(17); + CORRADE_COMPARE(out.str(), "Utility::allocateAligned(): total byte size 34 not a multiple of a 32-byte alignment\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Corrade::Utility::Test::MemoryTest)