From 3bbdf40c32ce27c5dc843d1a1909128034ef59ab Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Thu, 13 Jun 2024 19:04:44 +0200 Subject: [PATCH] Library: verify element placement into storage ...use some pointer arithmetic for this test to verify some important cases of object placement empirically. Note: there is possibly a very special problematic case when ''over aligned objects'' are not placed in accordance to their alignment requirements. Fixing this problem would be non-trivial, and thus I have only left a note in #1204 --- src/lib/several-builder.hpp | 39 ++++++++++++ src/lib/several.hpp | 1 + tests/library/several-builder-test.cpp | 87 ++++++++++++++++++++++++-- wiki/thinkPad.ichthyo.mm | 29 ++++++--- 4 files changed, 144 insertions(+), 12 deletions(-) diff --git a/src/lib/several-builder.hpp b/src/lib/several-builder.hpp index c7c65ca15..aa65bfe63 100644 --- a/src/lib/several-builder.hpp +++ b/src/lib/several-builder.hpp @@ -43,9 +43,48 @@ ** in the element array. The field ArrayBucket::spread defines this spacing ** and thus the offset used for subscript access. ** + ** # Handling of data elements + ** + ** The ability to emplace a mixture of data types into the storage exposed through + ** the lib::Several front-end creates some complexities related to element handling. + ** The implementation relies on a rules and criteria based approach to decide on + ** a case by case base if some given data content is still acceptable. This allows + ** for rather tricky low-level usages, but has the downside to detect errors only + ** at runtime — which in this case is ameliorated by the limitation that elements + ** must be provided completely up-front, through the SeveralBuilder. + ** - in order to handle any data element, we must be able to invoke its destructor + ** - an arbitrary mixture of types can thus only be accepted if either we can + ** rely on a common virtual base class destructor, or if all data elements + ** are trivially destructible; these properties can be detected at compile + ** time with the help of the C++ `` library + ** - this container can accommodate _non-copyable_ data types, under the proviso + ** that the complete storage required is pre-allocated (using `reserve()` from + ** the builder API) + ** - otherwise, data can be filled in dynamically, expanding the storage as needed, + ** given that all existing elements can be safely re-located by move or copy + ** constructor into a new, larger storage buffer. + ** - alternatively, when data elements are even ''trivially copyable'' (e.g. POD data), + ** then it is even possible to increase the placement spread in the storage at the + ** point when the requirement to do so is discovered dynamically; objects can be + ** shifted to other locations by `std::memmove()` in this case. + ** - notably, lib::AllocationCluster has the ability to dynamically adapt an allocation, + ** but only if this happens to be currently the last allocation handed out; it can + ** thus be arranged even for an unknown number of non-copyable objects to be emplaced + ** when creating the suitable operational conditions. + ** A key point to note is the fact that the container does not capture and store the + ** actual data types persistently. Thus, the above rules must be applied in a way + ** to always ensure safe handling of the contained data. Typically, the first element + ** actually added will »prime« the container for a certain usage style, and after that, + ** some other usage patterns may be rejected. + ** ** @todo this is a first implementation solution from 6/2025 — and was deemed ** _roughly adequate_ at that time, yet should be revalidated once more ** observations pertaining real-world usage are available... + ** @warning there is a known problem with _over-alligned-types,_ which becomes + ** relevant when the _interface type_ has only lower alignment requirement, + ** but an individual element is added with higher alignment requirements. + ** In this case, while the spread is increased, still the placement of + ** the interface-type is used as anchor, possibly leading to misalignment. ** @see several-builder-test.cpp ** */ diff --git a/src/lib/several.hpp b/src/lib/several.hpp index 833395aa9..20190c929 100644 --- a/src/lib/several.hpp +++ b/src/lib/several.hpp @@ -91,6 +91,7 @@ namespace lib { Deleter deleter; /** mark start of the storage area */ + alignas(I) alignas(void*) std::byte storage[sizeof(I)]; diff --git a/tests/library/several-builder-test.cpp b/tests/library/several-builder-test.cpp index 7c378d7cf..75779bd29 100644 --- a/tests/library/several-builder-test.cpp +++ b/tests/library/several-builder-test.cpp @@ -46,7 +46,9 @@ using std::array; using std::rand; using lib::explore; +using util::isSameObject; using util::isLimited; +using util::getAddr; using util::isnil; using util::join; @@ -185,6 +187,9 @@ namespace test{ void check_Builder() { + // prepare to verify proper invocation of all constructors / destructors + Dummy::checksum() = 0; + { // Scenario-1 : Baseclass and arbitrary subclass elements SeveralBuilder builder; CHECK (isnil (builder)); @@ -240,6 +245,8 @@ namespace test{ CHECK (10 == elms.size()); CHECK (join (elms,"-") == "0-1-2-3-4-5-6-7-8-9"_expect); } + + CHECK (0 == Dummy::checksum()); } @@ -265,13 +272,12 @@ namespace test{ * grow dynamically. * - all these failure conditions are handled properly, including exceptions emanating * from element constructors; the container remains sane and no memory is leaked. - * @todo WIP 6/24 🔁 define ⟶ ✔ implement + * @todo WIP 6/24 ✔ define ⟶ ✔ implement */ void check_ErrorHandling() { - // prepare to verify proper invocation of all constructors / destructors - Dummy::checksum() = 0; + CHECK (0 == Dummy::checksum()); { // Scenario-1 : Baseclass and arbitrary subclass elements SeveralBuilder builder; @@ -464,12 +470,83 @@ namespace test{ } - /** @test TODO verify correct placement of instances within storage - * @todo WIP 6/24 🔁 define ⟶ implement + /** @test verify correct placement of instances within storage + * - use a low-level pointer calculation for this test to + * draw conclusions regarding the spacing of objects accepted + * into the lib::Several-container + * - demonstrate the simple data elements are packed efficiently + * - verify that special alignment requirements are observed + * - emplace several ''non copyable objects'' and then + * move-assign the lib::Several container instance; this + * demonstrates that the latter is just a access front-end, + * while the data elements reside in a fixed storage buffer + * @todo WIP 6/24 ✔ define ⟶ ✔ implement */ void check_ElementStorage() { + auto loc = [](auto& something){ return size_t(getAddr (something)); }; + auto calcSpread = [&](auto& several){ return loc(several[1]) - loc(several[0]); }; + + { // Scenario-1 : tightly packed values + Several elms = makeSeveral({21,34,55}).build(); + CHECK (21 == elms[0]); + CHECK (34 == elms[1]); + CHECK (55 == elms[2]); + CHECK (3 == elms.size()); + CHECK (sizeof(elms) == sizeof(void*)); + + CHECK (sizeof(int) == alignof(int)); + size_t spread = calcSpread (elms); + CHECK (spread == sizeof(int)); + CHECK (loc(elms.back()) == loc(elms.front()) + 2*spread); + } + + { // Scenario-2 : alignment + struct Ali + { + alignas(32) + char charm = 'u'; + }; + + auto elms = makeSeveral().fillElm(5).build(); + CHECK (5 == elms.size()); + CHECK (sizeof(elms) == sizeof(void*)); + + size_t spread = calcSpread (elms); + CHECK (spread == alignof(Ali)); + CHECK (loc(elms.front()) % alignof(Ali) == 0); + CHECK (loc(elms.back()) == loc(elms.front()) + 4*spread); + } + + { // Scenario-3 : noncopyable objects + auto elms = makeSeveral().fillElm(5).build(); + + auto v0 = elms[0].val; auto p0 = loc(elms[0]); + auto v1 = elms[1].val; auto p1 = loc(elms[1]); + auto v2 = elms[2].val; auto p2 = loc(elms[2]); + auto v3 = elms[3].val; auto p3 = loc(elms[3]); + auto v4 = elms[4].val; auto p4 = loc(elms[4]); + + CHECK (5 == elms.size()); + auto moved = move(elms); + CHECK (5 == moved.size()); + CHECK (isnil (elms)); + + CHECK (loc(moved[0]) == p0); + CHECK (loc(moved[1]) == p1); + CHECK (loc(moved[2]) == p2); + CHECK (loc(moved[3]) == p3); + CHECK (loc(moved[4]) == p4); + + CHECK (moved[0].val == v0); + CHECK (moved[1].val == v1); + CHECK (moved[2].val == v2); + CHECK (moved[3].val == v3); + CHECK (moved[4].val == v4); + + CHECK (calcSpread(moved) == sizeof(ShortBlocker)); + } } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 0529597a1..3e4696cfc 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -81780,7 +81780,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -81835,7 +81835,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -81875,9 +81876,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +
@@ -83648,8 +83647,24 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + + + + + + + + + +

+ dadurch daß alignas(void*) aber auch da ist, stellen wir mindestens »slot«-Alignment sicher; das löst zwar nicht das Problem wenn nur ein einzelner Element-Typ overaligned ist, aber schließt Probleme mit allen regulären Typen aus +

+ +
+