From 381a0335a437db76ccb6027ab2ff6b054bdce99d Mon Sep 17 00:00:00 2001 From: Bernhard Manfred Gruber Date: Tue, 21 Sep 2021 21:57:22 +0200 Subject: [PATCH] run field constructors when views are allocated * allocView will run field constructors * add allocViewUninitialized to still have uninitialized views * add constructFields * remove isAllowedFieldType * add tree::Mapping::arrayDims() * enable many unit tests relying on fields being initialized * add tests for uninitialized views and for explicit construction --- docs/pages/api.rst | 4 + include/llama/Core.hpp | 13 +- include/llama/View.hpp | 83 ++++++++---- include/llama/mapping/One.hpp | 5 +- include/llama/mapping/tree/Mapping.hpp | 5 + tests/computedprop.cpp | 18 ++- tests/recorddimension.cpp | 175 +++++++++++++++---------- 7 files changed, 195 insertions(+), 108 deletions(-) diff --git a/docs/pages/api.rst b/docs/pages/api.rst index 99df26288b..1c005b5a18 100644 --- a/docs/pages/api.rst +++ b/docs/pages/api.rst @@ -36,6 +36,8 @@ Array dimensions .. doxygenstruct:: llama::ArrayDimsIndexRange :members: +.. doxygenfunction:: llama::forEachADCoord + Record dimension ---------------- @@ -77,6 +79,8 @@ View creation .. _label-api-allocView: .. doxygenfunction:: llama::allocView +.. doxygenfunction:: llama::constructFields +.. doxygenfunction:: llama::allocViewUninitialized .. doxygenfunction:: llama::allocViewStack .. doxygentypedef:: llama::One .. doxygenfunction:: llama::copyVirtualRecordStack diff --git a/include/llama/Core.hpp b/include/llama/Core.hpp index 0151c27ba3..036ac44eac 100644 --- a/include/llama/Core.hpp +++ b/include/llama/Core.hpp @@ -59,8 +59,7 @@ namespace llama /// @brief Tells whether the given type is allowed as a field type in LLAMA. Such types need to be trivially /// constructible and trivially destructible. template - inline constexpr bool isAllowedFieldType - = std::is_trivially_default_constructible_v&& std::is_trivially_destructible_v; + inline constexpr bool isAllowedFieldType = std::is_trivially_destructible_v; /// Record dimension tree node which may either be a leaf or refer to a child tree presented as another \ref /// Record. @@ -555,13 +554,11 @@ namespace llama template LLAMA_FN_HOST_ACC_INLINE void forEachADCoord(ArrayDims adSize, Func&& func, OuterIndices... outerIndices) { - for(std::size_t i = 0; i < adSize[0]; i++) - { - if constexpr(Dim > 1) + if constexpr(Dim > 0) + for(std::size_t i = 0; i < adSize[0]; i++) forEachADCoord(ArrayDims{pop_front(adSize)}, std::forward(func), outerIndices..., i); - else - std::forward(func)(ArrayDims{outerIndices..., i}); - } + else + std::forward(func)(ArrayDims{outerIndices...}); } namespace internal diff --git a/include/llama/View.hpp b/include/llama/View.hpp index 1abe862de2..cefa136d21 100644 --- a/include/llama/View.hpp +++ b/include/llama/View.hpp @@ -41,10 +41,66 @@ namespace llama } } // namespace internal + /// Same as \ref allocView but does not run field constructors. +#ifdef __cpp_lib_concepts + template +#else + template +#endif + LLAMA_FN_HOST_ACC_INLINE auto allocViewUninitialized(Mapping mapping = {}, const Allocator& alloc = {}) + -> View> + { + auto blobs = internal::makeBlobArray(alloc, mapping, std::make_index_sequence{}); + return {std::move(mapping), std::move(blobs)}; + } + + namespace internal + { + template + struct IsComputed : std::false_type + { + }; + + template + struct IsComputed> + : std::bool_constant + { + }; + } // namespace internal + + /// Returns true if the field accessed via the given mapping and record coordinate is a computed value. + template + inline constexpr bool isComputed = internal::IsComputed::value; + + /// Runs the constructor of all fields reachable through the given view. Computed fields are not constructed. + template + LLAMA_FN_HOST_ACC_INLINE void constructFields(View& view) + { + using View = View; + using RecordDim = typename View::RecordDim; + forEachADCoord( + view.mapping().arrayDims(), + [&](typename View::ArrayDims ad) + { + if constexpr(isRecord || internal::IsBoundedArray::value) + forEachLeaf( + [&](auto coord) + { + // TODO(bgruber): we could initialize computed fields if we can write to those. We could + // test if the returned value can be cast to a T& and then attempt to write. + if constexpr(!isComputed) + new(&view(ad)(coord)) GetType; + }); + else if constexpr(!isComputed>) + new(&view(ad)) RecordDim; + }); + } + /// Creates a view based on the given mapping, e.g. \ref AoS or \ref :SoA. For allocating the view's underlying /// memory, the specified allocator callable is used (or the default one, which is \ref bloballoc::Vector). The - /// allocator callable is called with the size of bytes to allocate for each blob of the mapping. This function is - /// the preferred way to create a \ref View. + /// allocator callable is called with the alignment and size of bytes to allocate for each blob of the mapping. + /// The constructors are run for all fields by calling \ref constructFields. This function is the preferred way to + /// create a \ref View. See also \ref allocViewUninitialized. #ifdef __cpp_lib_concepts template #else @@ -53,8 +109,9 @@ namespace llama LLAMA_FN_HOST_ACC_INLINE auto allocView(Mapping mapping = {}, const Allocator& alloc = {}) -> View> { - auto blobs = internal::makeBlobArray(alloc, mapping, std::make_index_sequence{}); - return {std::move(mapping), std::move(blobs)}; + auto view = allocViewUninitialized(std::move(mapping), alloc); + constructFields(view); + return view; } /// Allocates a \ref View holding a single record backed by stack memory (\ref bloballoc::Stack). @@ -216,24 +273,6 @@ namespace llama View* view; }; - namespace internal - { - template - struct IsComputed : std::false_type - { - }; - - template - struct IsComputed> - : std::bool_constant - { - }; - } // namespace internal - - /// Returns true if the field accessed via the given mapping and record coordinate is a computed value. - template - inline constexpr bool isComputed = internal::IsComputed::value; - /// Central LLAMA class holding memory for storage and giving access to values stored there defined by a mapping. A /// view should be created using \ref allocView. /// \tparam TMapping The mapping used by the view to map accesses into memory. diff --git a/include/llama/mapping/One.hpp b/include/llama/mapping/One.hpp index f54d962d17..0f8b96737d 100644 --- a/include/llama/mapping/One.hpp +++ b/include/llama/mapping/One.hpp @@ -38,8 +38,9 @@ namespace llama::mapping { // TODO(bgruber): not sure if this is the right approach, since we take any ArrayDims in the ctor ArrayDims ad; - for(auto i = 0; i < ArrayDims::rank; i++) - ad[i] = 1; + if constexpr(ArrayDims::rank > 0) + for(auto i = 0; i < ArrayDims::rank; i++) + ad[i] = 1; return ad; } diff --git a/include/llama/mapping/tree/Mapping.hpp b/include/llama/mapping/tree/Mapping.hpp index f9e8a945ff..61ca275340 100644 --- a/include/llama/mapping/tree/Mapping.hpp +++ b/include/llama/mapping/tree/Mapping.hpp @@ -196,6 +196,11 @@ namespace llama::mapping::tree { } + LLAMA_FN_HOST_ACC_INLINE auto arrayDims() const + { + return arrayDimsSize; + } + LLAMA_FN_HOST_ACC_INLINE auto blobSize(std::size_t const) const -> std::size_t { diff --git a/tests/computedprop.cpp b/tests/computedprop.cpp index 5783ba58a7..9484814562 100644 --- a/tests/computedprop.cpp +++ b/tests/computedprop.cpp @@ -124,6 +124,11 @@ namespace { } + auto arrayDims() const + { + return ArrayDims{}; + } + template static constexpr auto isComputed(llama::RecordCoord) { @@ -142,14 +147,14 @@ namespace TEST_CASE("fully_computed_mapping") { auto arrayDims = llama::ArrayDims<3>{10, 10, 10}; - auto mapping = ComputedMapping{arrayDims}; + auto mapping = ComputedMapping{arrayDims}; auto view = llama::allocView(mapping); using namespace tag; - CHECK(view(0u, 1u, 2u)(A{}, X{}) == 0); - CHECK(view(2u, 1u, 2u)(A{}, Y{}) == 4); - CHECK(view(2u, 5u, 2u)(A{}, Z{}) == 20); + CHECK(view(0u, 1u, 2u) == 0); + CHECK(view(2u, 1u, 2u) == 4); + CHECK(view(2u, 5u, 2u) == 20); } namespace @@ -170,6 +175,11 @@ namespace { } + auto arrayDims() const + { + return arrayDimsSize; + } + using Word = std::uint64_t; struct BoolRef diff --git a/tests/recorddimension.cpp b/tests/recorddimension.cpp index 27814773e6..5cc52c70ae 100644 --- a/tests/recorddimension.cpp +++ b/tests/recorddimension.cpp @@ -35,14 +35,14 @@ TEST_CASE("recorddim.record_with_int[3]") int& e2 = view(0u)(Tag{})(2_RC); } -// TEST_CASE("recorddim.record_with_std::complex") -//{ -// using RecordDim = llama::Record>>; -// auto view = allocView(llama::mapping::AoS{llama::ArrayDims{1}, RecordDim{}}); -// -// std::complex& e = view(0u)(Tag{}); -// e = {2, 3}; -//} +TEST_CASE("recorddim.record_with_std::complex") +{ + using RecordDim = llama::Record>>; + auto view = allocView(llama::mapping::AoS{llama::ArrayDims{1}, RecordDim{}}); + + std::complex& e = view(0u)(Tag{}); + e = {2, 3}; +} TEST_CASE("recorddim.record_with_std::array") { @@ -53,23 +53,24 @@ TEST_CASE("recorddim.record_with_std::array") e = {2, 3, 4, 5}; } -// TEST_CASE("recorddim.record_with_std::vector") +// FIXME(bgruber): LLAMA does not handle destructors yet +//TEST_CASE("recorddim.record_with_std::vector") //{ // using RecordDim = llama::Record>>; // auto view = allocView(llama::mapping::AoS{llama::ArrayDims{1}, RecordDim{}}); // // std::vector& e = view(0u)(Tag{}); -// // e = {2, 3, 4, 5}; // FIXME: LLAMA memory is uninitialized +// e = {2, 3, 4, 5}; //} -// TEST_CASE("recorddim.record_with_std::atomic") -//{ -// using RecordDim = llama::Record>>; -// auto view = allocView(llama::mapping::AoS{llama::ArrayDims{1}, RecordDim{}}); -// -// std::atomic& e = view(0u)(Tag{}); -// // e++; // FIXME: LLAMA memory is uninitialized -//} +TEST_CASE("recorddim.record_with_std::atomic") +{ + using RecordDim = llama::Record>>; + auto view = allocView(llama::mapping::AoS{llama::ArrayDims{1}, RecordDim{}}); + + std::atomic& e = view(0u)(Tag{}); + e++; +} TEST_CASE("recorddim.record_with_noncopyable") { @@ -113,62 +114,92 @@ TEST_CASE("recorddim.record_with_nonmoveable") e.value = 0; } -// TEST_CASE("recorddim.record_with_nondefaultconstructible") -//{ -// struct Element -// { -// Element() = delete; -// int value; -// }; -// -// using RecordDim = llama::Record>; -// auto view = allocView(llama::mapping::AoS{llama::ArrayDims{1}, RecordDim{}}); -// -// Element& e = view(0u)(Tag{}); -// e.value = 0; -//} +TEST_CASE("recorddim.record_with_nondefaultconstructible") +{ + struct Element + { + Element() = delete; + int value; + }; -// TEST_CASE("recorddim.record_with_nontrivial_ctor") -//{ -// struct Element -// { -// int value = 42; -// }; -// -// using RecordDim = llama::Record>; -// auto view = allocView(llama::mapping::AoS{llama::ArrayDims{1}, RecordDim{}}); -// -// Element& e = view(0u)(Tag{}); -// // CHECK(e.value == 42); // FIXME: LLAMA memory is uninitialized -//} + using RecordDim = llama::Record>; + auto view = llama::allocViewUninitialized(llama::mapping::AoS{llama::ArrayDims{1}, RecordDim{}}); -// namespace -//{ -// struct UniqueInt -// { -// int value = counter++; -// -// explicit operator int() const -// { -// return value; -// } -// -// private: -// inline static int counter = 0; -// }; -//} // namespace -// -// TEST_CASE("recorddim.record_with_nontrivial_ctor2") -//{ -// using RecordDim = llama::Record>; -// auto view = allocView(llama::mapping::AoS{llama::ArrayDims{1}, RecordDim{}}); -// -// // FIXME: LLAMA memory is uninitialized -// // CHECK(view(ArrayDims{0})(Tag{}) == 0); -// // CHECK(view(ArrayDims{1})(Tag{}) == 1); -// // CHECK(view(ArrayDims{2})(Tag{}) == 2); -// // CHECK(view(ArrayDims{15})(Tag{}) == 15); -//} + Element& e = view(0u)(Tag{}); + e.value = 0; +} + +namespace +{ + struct ElementWithCtor + { + int value = 42; + }; +} // namespace + +TEST_CASE("recorddim.record_with_nontrivial_ctor") +{ + using RecordDim = llama::Record>; + auto view = allocView(llama::mapping::AoS{llama::ArrayDims{1}, RecordDim{}}); + + ElementWithCtor& e = view(0u)(Tag{}); + CHECK(e.value == 42); +} + +namespace +{ + struct UniqueInt + { + int value = counter++; + + explicit operator int() const + { + return value; + } + + private: + inline static int counter = 0; + }; +} // namespace + +TEST_CASE("recorddim.record_with_nontrivial_ctor2") +{ + using RecordDim = llama::Record>; + auto view = allocView(llama::mapping::AoS{llama::ArrayDims{16}, RecordDim{}}); + + CHECK(view(llama::ArrayDims{0})(Tag{}).value == 0); + CHECK(view(llama::ArrayDims{1})(Tag{}).value == 1); + CHECK(view(llama::ArrayDims{2})(Tag{}).value == 2); + CHECK(view(llama::ArrayDims{15})(Tag{}).value == 15); +} + +TEST_CASE("recorddim.uninitialized_trivial") +{ + using RecordDim = int; + auto mapping = llama::mapping::AoS{llama::ArrayDims{256}, RecordDim{}}; + auto view = llama::allocViewUninitialized( + mapping, + [](auto /*alignment*/, std::size_t size) { return std::vector(size, std::byte{0xAA}); }); + + for(auto i = 0u; i < 256u; i++) + CHECK(view(i) == 0xAAAAAAAA); +} + +TEST_CASE("recorddim.uninitialized_ctor.constructFields") +{ + auto mapping = llama::mapping::AoS{llama::ArrayDims{256}, ElementWithCtor{}}; + auto view = llama::allocViewUninitialized( + mapping, + [](auto /*alignment*/, std::size_t size) { return std::vector(size, std::byte{0xAA}); }); + + for(auto i = 0u; i < 256u; i++) + CHECK(view(i).value == 0xAAAAAAAA); // ctor has not run + + llama::constructFields(view); // run ctors + + for(auto i = 0u; i < 256u; i++) + CHECK(view(i).value == 42); +} TEST_CASE("recorddim.int") {