diff --git a/src/lib/several-builder.hpp b/src/lib/several-builder.hpp index 31f73e544..ded0b5437 100644 --- a/src/lib/several-builder.hpp +++ b/src/lib/several-builder.hpp @@ -41,7 +41,15 @@ ** Since the actual data elements can (optionally) be of a different type than ** the exposed interface type \a I, additional storage and spacing is required ** in the element array. The field ArrayBucket::spread defines this spacing - ** and thus the offset used for subscript access. + ** and thus the offset used for subscript access. The actual data storage starts + ** immediately behind the ArrayBucket, which thus acts as a metadata header. + ** This arrangement requires a sufficiently sized raw memory allocation to place + ** the ArrayBucket and the actual data into. Moreover, the allocation code in + ** ElementFactory::create() is responsible to ensure proper alignment of the + ** data storage, especially when the payload data type has alignment requirements + ** beyond `alignof(void*)`, which is typically used by the standard heap allocator; + ** additional headroom is added proactively in this case, to be able to shift the + ** storage buffer ahead to the next alignment boundrary. ** ** # Handling of data elements ** @@ -144,9 +152,6 @@ namespace lib { /** * Helper to determine the »spread« required to hold * elements of type \a TY in memory _with proper alignment._ - * @warning assumes that the start of the buffer is also suitably aligned, - * which _may not be the case_ for **over-aligned objects** with - * `alignof(TY) > alignof(void*)` */ template size_t inline constexpr @@ -187,16 +192,30 @@ namespace lib { : Allo{std::move (allo)} { } + Bucket* - create (size_t cnt, size_t spread) + create (size_t cnt, size_t spread, size_t alignment =alignof(I)) { - size_t storageBytes = Bucket::requiredStorage (cnt, spread); + REQUIRE (cnt); + REQUIRE (spread); + size_t storageBytes = Bucket::storageOffset + cnt*spread; + if (alignment > alignof(void*)) // over-aligned data => reserve for alignment padding + storageBytes += (alignment - alignof(void*)); + // Step-1 : acquire the raw storage buffer std::byte* loc = AlloT::allocate (baseAllocator(), storageBytes); + ENSURE (0 == size_t(loc) % alignof(void*)); + + size_t offset = (size_t(loc) + Bucket::storageOffset) % alignment; + if (offset) // padding needed to next aligned location + offset = alignment - offset; + offset += Bucket::storageOffset; + ASSERT (storageBytes - offset >= cnt*spread); Bucket* bucket = reinterpret_cast (loc); using BucketAlloT = typename AlloT::template rebind_traits; auto bucketAllo = adaptAllocator(); - try { BucketAlloT::construct (bucketAllo, bucket, cnt*spread, spread); } + // Step-2 : construct the Bucket metadata | ▽ ArrayBucket ctor arg ▽ + try { BucketAlloT::construct (bucketAllo, bucket, storageBytes, offset, spread); } catch(...) { AlloT::deallocate (baseAllocator(), loc, storageBytes); @@ -205,6 +224,7 @@ namespace lib { return bucket; }; + template E& createAt (Bucket* bucket, size_t idx, ARGS&& ...args) @@ -218,6 +238,7 @@ namespace lib { return *loc; }; + template void destroy (ArrayBucket* bucket) @@ -234,7 +255,7 @@ namespace lib { ElmAlloT::destroy (elmAllo, elm); } } - size_t storageBytes = Bucket::requiredStorage (bucket->buffSiz); + size_t storageBytes = bucket->buffOffset + bucket->buffSiz; std::byte* loc = reinterpret_cast (bucket); AlloT::deallocate (baseAllocator(), loc, storageBytes); }; @@ -255,7 +276,7 @@ namespace lib { Bucket* realloc (Bucket* data, size_t cnt, size_t spread) { - Bucket* newBucket = Fac::create (cnt, spread); + Bucket* newBucket = Fac::create (cnt, spread, alignof(E)); if (data) try { newBucket->deleter = data->deleter; @@ -558,7 +579,7 @@ namespace lib { REQUIRE (oldSpread); REQUIRE (newSpread); REQUIRE (Coll::data_); - byte* oldPos = Coll::data_->storage; + byte* oldPos = Coll::data_->storage(); byte* newPos = oldPos; oldPos += idx * oldSpread; newPos += idx * newSpread; diff --git a/src/lib/several.hpp b/src/lib/several.hpp index 240992ba0..bd54806af 100644 --- a/src/lib/several.hpp +++ b/src/lib/several.hpp @@ -71,15 +71,21 @@ namespace lib { - namespace {// Storage implementation details + namespace {// Storage header implementation details - template + /** + * Metadata record placed immediately before the data storage. + * @remark SeveralBuilder uses a custom allocation scheme to acquire + * a sufficiently sized allocation for ArrayBucket + the data. + */ + template struct ArrayBucket { - ArrayBucket (size_t bytes, size_t elmSize = sizeof(I)) + ArrayBucket (size_t storageSize, size_t buffStart, size_t elmSize = sizeof(I)) : cnt{0} , spread{elmSize} - , buffSiz{bytes} + , buffSiz{storageSize - buffStart} + , buffOffset{buffStart} , deleter{nullptr} { } @@ -88,30 +94,24 @@ namespace lib { size_t cnt; size_t spread; size_t buffSiz; + size_t buffOffset; Deleter deleter; - /** mark start of the storage area */ - alignas(E) - std::byte storage[space]; + static constexpr size_t storageOffset = sizeof(ArrayBucket); - - static size_t - requiredStorage (size_t cnt, size_t spread =1) + /** data storage area starts immediately behind the ArrayBucket */ + std::byte* + storage() { - return sizeof(ArrayBucket) - sizeof(storage) - + cnt * spread; + return reinterpret_cast(this) + buffOffset; } - /** perform unchecked access into the storage area - * @note typically reaching behind the nominal end of this object - */ + /** perform unchecked access into the storage area */ I& subscript (size_t idx) { - std::byte* elm = storage; - size_t off = idx * spread; - elm += off; - ENSURE (storage <= elm and elm < storage+buffSiz); + std::byte* elm = storage() + (idx * spread); + ENSURE (storage() <= elm and elm < storage()+buffSiz); return * std::launder (reinterpret_cast (elm)); } diff --git a/tests/library/several-builder-test.cpp b/tests/library/several-builder-test.cpp index 7359c91d5..e33abaace 100644 --- a/tests/library/several-builder-test.cpp +++ b/tests/library/several-builder-test.cpp @@ -506,29 +506,9 @@ namespace test{ { // Scenario-2 : alignment struct Ali { - alignas(32) + alignas(64) char charm = 'u'; }; -SHOW_EXPR(sizeof(Ali)) -SHOW_EXPR(alignof(Ali)) -SHOW_EXPR(sizeof(ArrayBucket)) -SHOW_EXPR(alignof(ArrayBucket)) -SHOW_EXPR(sizeof(ArrayBucket)) -SHOW_EXPR(alignof(ArrayBucket)) -SHOW_EXPR(sizeof(ArrayBucket)) -SHOW_EXPR(alignof(ArrayBucket)) - - std::allocator> aliAllo; - std::allocator byteAllo; - - ArrayBucket * locAli = aliAllo.allocate(1); - std::byte* locByte = byteAllo.allocate(96); -SHOW_EXPR(loc(locAli)) -SHOW_EXPR(loc(locAli) % alignof(Ali)) -SHOW_EXPR(loc(locByte)) -SHOW_EXPR(loc(locByte) % alignof(Ali)) - aliAllo.destroy(locAli); - byteAllo.destroy(locByte); auto elms = makeSeveral().fillElm(5).build(); CHECK (5 == elms.size()); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 6d33488f0..9bd6d38c5 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -81799,7 +81799,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -81884,8 +81884,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -81895,76 +81895,72 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - - + + + + + + + + + + + + + + - - - +

auf Basis der buffSiz, d.h. der Größe des reinen Nutzdatenpuffers; auf diese konnte man noch den statisch ermittelbaren Overhead aufschlagen

- -
+
- - - +

Der Allocator macht nur eine raw-storage-Allocation, da is nix mit Alignment. Insofern kann der Beginn des gelieferten Speicherbereichs auch „daneben liegen“ — so daß wir einen Korrektur-Offset brauchen. Und der läßt sich nicht systematisch erschließen

- -
+
- + - - - - - - -

- Der Aufruf des Allokators erfolgt aus dem Builder, und an der Stelle ist ohne Weiteres der volle Typ konstelliert -

- - -
+ + + + - + + +
- - - + + + + - - - +

...to allocate storage required for a single object whose alignment requirement exceeds __STDCPP_DEFAULT_NEW_ALIGNMENT__

- -
+
@@ -81972,27 +81968,106 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - - +

...weshalb ich ja keinen wirklich passenden statischen Typ für ArrayBucket angeben kann, weil die Puffergröße erst zur Laufzeit bekannt wird.

- -
+
+ + + + + + + + +

+ das ist eine gewisse Vereinfachung, die aber aller Wahrscheinlichkeit von allen Plattformen erfüllt wird, allein schon weil man sonst an jedem Objekt mit VTable herumfummeln müßte +

+ +
+ + + + + + + + + + + - + + + + + + + + + + + + + + + +

+ Der Aufruf des Allokators erfolgt aus dem Builder, und an der Stelle ist ohne Weiteres der volle Typ konstelliert +

+ +
+
+ + + + +

+ Für den reinen Zugriff genügt ein vereinfachter Typ — insofern dadurch der Zugang zur dynamischen Layout-Information möglich wird +

+ +
+
+ + + + + + + + + + + + + +
    +
  • + verwende sizeof(ArrayBucket<I>) als Offset +
  • +
  • + dimensioniere die eigentliche Allokation so, daß vorne das ArrayBucket reinpasst, und dahinter alle gewünschten Daten +
  • +
+ +
+
+ + +
+
@@ -84299,7 +84374,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -84314,23 +84389,32 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - + - - - +

Vorsicht Falle: der Allocator kann das gar nicht machen, wenn man ihm im Sinn von »placement new« explizit die Position vorgibt

- -
+
+ + + + + + + + + + + +