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
This commit is contained in:
Fischlurch 2024-06-13 19:04:44 +02:00
parent fd1ed7e78f
commit 3bbdf40c32
4 changed files with 144 additions and 12 deletions

View file

@ -43,9 +43,48 @@
** in the element array. The field ArrayBucket<I>::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++ `<type_traits>` 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
**
*/

View file

@ -91,6 +91,7 @@ namespace lib {
Deleter deleter;
/** mark start of the storage area */
alignas(I)
alignas(void*)
std::byte storage[sizeof(I)];

View file

@ -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<Dummy> 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<Dummy> 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<int> 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<Ali>().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<ShortBlocker>().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));
}
}

View file

@ -81780,7 +81780,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<icon BUILTIN="idea"/>
</node>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#4e3b7f" CREATED="1718034857705" FOLDED="true" ID="ID_725324060" MODIFIED="1718073781365" TEXT="Alignment beachten">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#4e3b7f" CREATED="1718034857705" FOLDED="true" ID="ID_725324060" MODIFIED="1718294109135" TEXT="Alignment beachten">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1718035817292" ID="ID_908227037" MODIFIED="1718035826853" TEXT="Verwendungen von sizeof(TY)">
<icon BUILTIN="idea"/>
@ -81835,7 +81835,8 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</html></richcontent>
</node>
<node CREATED="1718064742835" ID="ID_871080765" MODIFIED="1718064754941" TEXT="&#xbb;grob korrekt&#xab;">
<node CREATED="1718064756929" ID="ID_1870004223" MODIFIED="1718064798319" TEXT="w&#xe4;hle f&#xfc;r den Puffer das Alignment eines void* (&#xbb;slot&#xab;)"/>
<node CREATED="1718064756929" ID="ID_1870004223" MODIFIED="1718294057405" TEXT="w&#xe4;hle f&#xfc;r den Puffer das Alignment des Interface-Typs"/>
<node CREATED="1718294058002" ID="ID_1907851352" MODIFIED="1718294103590" TEXT="stelle zudem sicher, da&#xdf; das Alignment mindestens wie void* ist (ein &#xbb;slot&#xab;)"/>
<node CREATED="1718064809954" ID="ID_79404905" MODIFIED="1718064835981" TEXT="runde den Spread korrekt auf gem&#xe4;&#xdf; Element-Alignment"/>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1718064840726" ID="ID_1507935794" MODIFIED="1718065973474" TEXT="Problem: over-aligned objects werden nicht korrekt gehandhabt">
<icon BUILTIN="messagebox_warning"/>
@ -81875,9 +81876,7 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
<node COLOR="#338800" CREATED="1718067163803" ID="ID_1053644006" MODIFIED="1718067198266" TEXT="&quot;sizeof(TY)&quot; &#x27fc; reqSiz&lt;TY&gt;()">
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1718067214231" ID="ID_1276439764" LINK="#ID_1830672111" MODIFIED="1718067239581" TEXT="testen">
<icon BUILTIN="flag-yellow"/>
</node>
<node COLOR="#338800" CREATED="1718067214231" ID="ID_1276439764" LINK="#ID_1830672111" MODIFIED="1718293971538" TEXT="testen"/>
</node>
</node>
</node>
@ -83648,8 +83647,24 @@ Date:&#160;&#160;&#160;Thu Apr 20 18:53:17 2023 +0200<br/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1716857382441" ID="ID_1397767362" MODIFIED="1718219037095" TEXT="check_ElementStorage">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1718067224015" ID="ID_1830672111" MODIFIED="1718067235928" TEXT="Alignment-Behandlung verifizieren">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1718290606176" ID="ID_1457988951" MODIFIED="1718290634409" TEXT="grunds&#xe4;tzlich: Platzierung mit gleichm&#xe4;&#xdf;igem Spread">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1718067224015" ID="ID_1830672111" MODIFIED="1718293512166" TEXT="Alignment-Behandlung verifizieren">
<icon BUILTIN="button_ok"/>
<node COLOR="#e036b2" CREATED="1718294214083" ID="ID_1828779752" MODIFIED="1718294228266" TEXT="tja... wer testet">
<icon BUILTIN="smiley-oh"/>
</node>
<node CREATED="1718294234410" ID="ID_251500539" MODIFIED="1718294355847" TEXT="hab jetzt zudem alignas(I) mit dazugenommen">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
dadurch da&#223; alignas(void*) aber auch da ist, stellen wir mindestens &#187;slot&#171;-Alignment sicher; das l&#246;st zwar nicht das Problem wenn nur ein einzelner Element-Typ overaligned ist, aber schlie&#223;t Probleme mit allen regul&#228;ren Typen aus
</p>
</body>
</html></richcontent>
</node>
</node>
<node CREATED="1718202490965" ID="ID_247674037" MODIFIED="1718206085439" TEXT="Handhabung von non-copyable-Objekten">
<linktarget COLOR="#638ad5" DESTINATION="ID_247674037" ENDARROW="Default" ENDINCLINATION="-777;-16;" ID="Arrow_ID_10606647" SOURCE="ID_1146034926" STARTARROW="None" STARTINCLINATION="-438;67;"/>