From 22f4b9dd7e52adacc802610a99e251335c74502c Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 11 Dec 2024 02:36:17 +0100 Subject: [PATCH] Invocation: implement the chaining and linking functionality This basically solves this implementation challenge: It was possible to construct a ''compile-time type-safe'' overlay, while using force-casts ''without any metadata'' at runtime. Obviously this is a dangerous setup, but ''should be resonably safe'' when used within the defined scheme... --- src/lib/hetero-data.hpp | 91 ++++++++-- tests/library/hetero-data-test.cpp | 48 +++++- wiki/thinkPad.ichthyo.mm | 267 +++++++++++++++++++++-------- 3 files changed, 309 insertions(+), 97 deletions(-) diff --git a/src/lib/hetero-data.hpp b/src/lib/hetero-data.hpp index c6bf07c27..168b7b909 100644 --- a/src/lib/hetero-data.hpp +++ b/src/lib/hetero-data.hpp @@ -30,7 +30,7 @@ ** cache locality of recently used stack frames, thereby avoiding heap allocations altogether. ** ** @todo WIP-WIP this is the draft of a design sketch regarding the render node network, - ** which seems to be still pretty much in flux as of 12/2024 + ** which seems to be still pretty much in flux as of 12/2024 ** @see HeteroData_test ** @see steam::engine::TurnoutSystem (use case) ** @@ -48,6 +48,7 @@ //#include "lib/linked-elements.hpp" #include "lib/meta/typelist.hpp" #include "lib/meta/typelist-manip.hpp" +//#include "lib/meta/typelist-util.hpp" #include "lib/meta/typeseq-util.hpp" #include "lib/test/test-helper.hpp" @@ -59,39 +60,48 @@ namespace lib { + /** + */ + template + class HeteroData; + + struct StorageLoc : util::NonCopyable { StorageLoc* next{nullptr}; }; - template + template struct StorageFrame : protected StorageLoc , std::tuple { using std::tuple::tuple; + + template + void linkInto (HeteroData&); }; - /** - */ - template - class HeteroData; - template - class HeteroData,TAIL>> - : StorageFrame + template + class HeteroData,TAIL>> + : StorageFrame { using _Self = HeteroData; using _Tail = HeteroData; using Tuple = std::tuple; - using Frame = StorageFrame; + using Frame = StorageFrame; static constexpr size_t localSiz = sizeof...(DATA); template static constexpr bool isLocal = slot < localSiz; + template + using PickType = std::conditional_t, std::tuple_element + , typename _Tail::template PickType>; + _Tail& accessTail() { @@ -114,8 +124,8 @@ namespace lib { } template - using Elm_t = std::conditional_t, std::tuple_element_t - , typename _Tail::template Elm_t>; + using Elm_t = typename PickType::type; + template Elm_t& @@ -143,10 +153,10 @@ namespace lib { }; template - struct Constructor + struct Chain { - using NewFrame = StorageFrame; - using ChainType = meta::Append,NewFrame>; + using NewFrame = StorageFrame; + using ChainType = HeteroData,NewFrame>::List>; template static NewFrame @@ -156,7 +166,7 @@ namespace lib { } template - using ChainConstructor = typename ChainType::template Constructor; + using ChainExtension = typename ChainType::template Chain; template using Accessor = typename ChainType::template Accessor<_Self::size()+slot>; @@ -174,13 +184,15 @@ namespace lib { template using Elm_t = void; + template + using PickType = void; }; template class HeteroData - : public HeteroData, meta::NullType>> + : public HeteroData, meta::NullType>> { - using _Front = HeteroData, meta::NullType>>; + using _Front = HeteroData, meta::NullType>>; public: using NewFrame = typename _Front::Frame; @@ -195,5 +207,48 @@ namespace lib { }; + namespace { + /** + * @internal helper for safety-check when attaching segments. + * New segments are created by a constructor functor, guided by a + * type signature describing the complete chain. When attaching new + * segments, we can not verify that the base chain to extend does really + * match the presumed chain structure as encoded into the type (since this + * base chain does not store any meta data). But at least we can verify + * that the number of real segment-links matches the assumed structure. + * Notably it does not really matter what is stored in the base chain, + * as long as this segment count matches, because accessor functors + * generated by a `HeteroData::Chain` constructor will always address + * only their own (newly added) segment. + */ + inline StorageLoc*& + checkedTraversal (size_t segments, StorageLoc* last) + { + REQUIRE(last); + while (segments and last->next) + { + last = last->next; + --segments; + } + ASSERT (last->next == nullptr and segments == 1 + ,"Failure to attach new data segment to HeteroData: " + "assumed type structure does not match real connectivity, " + "end-of-chain encountered with %d type segment(s) remaining" + , segments); + return last->next; + } + } + + template + template + inline void + StorageFrame::linkInto (HeteroData& prefixChain) + { + StorageLoc* firstSeg = reinterpret_cast (&prefixChain); + StorageLoc*& lastLink = checkedTraversal (seg, firstSeg); + ENSURE (lastLink == nullptr); + lastLink = this; + } + } // namespace lib #endif /*LIB_HETERO_DATA_H*/ diff --git a/tests/library/hetero-data-test.cpp b/tests/library/hetero-data-test.cpp index c787dbdb4..75fd5368e 100644 --- a/tests/library/hetero-data-test.cpp +++ b/tests/library/hetero-data-test.cpp @@ -20,19 +20,18 @@ #include "lib/hetero-data.hpp" #include "lib/meta/trait.hpp" #include "lib/test/diagnostic-output.hpp"/////////////////TODO +#include "lib/util.hpp" +#include namespace lib { namespace test{ + using std::string; using meta::is_Subclass; + using util::isSameObject; - namespace { // probe victims - - }//(End) test data - - #define TYPE(_EXPR_) showType() @@ -54,11 +53,13 @@ namespace test{ // checksum = 0; verify_FrontBlock(); + verify_ChainBlock(); } + /** @test build a free standing data tuple block to start a chain */ void - verify_FrontBlock () + verify_FrontBlock() { using Block1 = HeteroData; CHECK ((is_Subclass>())); @@ -74,6 +75,41 @@ namespace test{ b2.get<1>() = 3.14; CHECK (3.14 == b2.get<1>()); } + + + /** @test construct a follow-up data tuple block and hook it into the chain */ + void + verify_ChainBlock() + { + using Block1 = HeteroData; + CHECK ((is_Subclass>())); + + using Constructor = Block1::Chain; + using Block2 = Constructor::NewFrame; + CHECK ((is_Subclass>())); + + auto b1 = Block1::build (41); + auto b2 = Constructor::build (1.61, "Φ"); + b2.linkInto(b1); + + auto& chain2 = reinterpret_cast (b1); + CHECK (b1.size() == 1); + CHECK (chain2.size() == 3); + + CHECK (41 == chain2.get<0>()); + CHECK (1.61 == chain2.get<1>()); + CHECK ("Φ" == chain2.get<2>()); + + chain2.get<0>()++; + chain2.get<1>() = (1 + sqrt(5)) / 2; + + CHECK (b1.get<0>() == 42); + CHECK (chain2.get<0>() == 42); + CHECK (std::get<0> (b2) == "1.618034"_expect); + + CHECK (isSameObject (chain2.get<0>() ,b1.get<0>())); + CHECK (isSameObject (chain2.get<2>() ,std::get<1>(b2))); + } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 3b936008d..4c463b80d 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -22038,9 +22038,7 @@ - - - +

...praktisch könnte es zwar sein, daß wir darauf angewiesen sind, das Widget schon zu kennen. Konkret ist das aber im Moment nicht der Fall, und ich sollte mir darüber jetzt auch keine Gedanken machen; das Design muß ohnehin später nochmal überarbeitet werden... @@ -22719,9 +22717,7 @@ - - - +

...abstraktes Interface @@ -23041,9 +23037,7 @@ - - - +

...nach allen gängigen Prinzipien der instrumentellen Vernunft. @@ -23532,9 +23526,7 @@ - - - +

sollte callable sein @@ -24698,9 +24690,7 @@ - - - +

Invariante gilt auf einem Fork @@ -26795,9 +26785,7 @@ - - - +

wozu haben wir das Gtk::Grid @@ -29747,9 +29735,7 @@ - - - +

als ob es darauf ankäme... @@ -32191,9 +32177,7 @@ - - - +

man sollte den GtkStyleContext nutzen @@ -36283,9 +36267,7 @@ - - - +

  • @@ -38332,9 +38314,7 @@ - - - +

    ...anstatt bloß die Ausführung entsprechender Commands zurückzuweisen @@ -39362,9 +39342,7 @@ - - - +

    ...dieser zeichnet die Kreise mit Ausdehnung der Allocation, und auch genau mit dieser Farbe und Linienbreite @@ -39727,9 +39705,7 @@ - - - +

    Beachte: nicht verwechseln mit absoluten Angaben. @@ -40229,9 +40205,7 @@ - - - +

    müßten also dann jeweils selber dies in die Kenn-Parameter übersetzen ⟹ Gefahr von Code-Duplikation und inkonsistentem Verhalten @@ -40425,9 +40399,7 @@ - - - +

    alle 20 Schritte ein Sprung, bzw. sogar nur alle 10 Schritte bei 96kHz, denn 1/96000 = 10.41666666 µTick @@ -87758,7 +87730,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
    - + @@ -87791,14 +87763,59 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
    - + + - + + + + - + + + + + + + + +

    + kurz gesagt:  std::conditional soll zwischen zwei wohldefinierten Typen auswählen; es ist eigentlich nicht für Metaprogrammierung gedacht +

    + +
    + +
    + + + + + + + +

    + Ein Traits-Template hat eingeschachtelte Typen, mit denen sich weitere Eigenschaften abgreifen lassen. Das Traits-Template ist wohldefiniert, wenn es mit den gegebenen Typ-Parametern instantiiert werden kann. Das heißt, eingeschachtelte Definitionen müssen syntaktisch korrekt sein, werden aber nur weiter ausgewertet, wenn man diese Definitionen dann tatsächlich verwendet. Das ist ein grundlegendes Verhalten des Instantiierungs-Modells von C++, das u.A. auch Forward-Deklarationen möglich macht. Das kann man sich hier zunutze machen ... allerdings muß ich nun dafür ein Helper-Template anlegen, damit auf beiden Zweigen so etwas wie ein Traits-Template ausgewählt werden kann. Ich führe also eine private Def ein: PickType<slot>. Dann ist das Elm_t<slot> wie üblich nur noch eine getemplatete Abkürzung, aber erst diese greift auf die nested types tatsächlich zu +

    + +
    + +
    +
    + + + + +

    + Das ist aber der Knackpunkt bei diesem Design — ich mache den ganzen komplexen Typ-Overlay nur, damit ich zur Laufzeit keinerlei Metadaten speichern muß; Zugriff erfolgt über Accessoren, die sozusagen „eingebaute Leitplanken“ haben, dann aber einfach einen force-Cast machen. Pro Level möchte ich eigentlich nur eine einzige Indirektion haben +

    + +
    + +
    + @@ -87815,6 +87832,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
    + @@ -87897,18 +87915,21 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
    - + - - + + + + + - + @@ -87998,23 +88019,27 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
    - - + + + +

    - eben weil wir im HeteroData-Chain selber (also dem TurnoutSystem) keine Metadaten unterbringen wollen + eben weil wir im HeteroData-Chain selber (also dem TurnoutSystem) keine Metadaten unterbringen wollen ⟹ damit aber drehen sich alle Verifikationen dieser Typ-Struktor im Kreis, auch die size()-Funktion ist ja constexpr und analysiert wieder nur die Typ-Signatur; ob tatsächlich die angebilchen Tupel-Felder im Speicher liegen kann man ohne RTTI nicht prüfen

    -
    + + +
    - +

    - ...dieser Check würde prüfen, ob wir nach einer vorkonfigurierten Anzahl an Schritten exakt hinter dem Ende des aktuellen Chain stehen — denn genau dann kann ein damit konform konstruierter Accessor keinen Schaden mehr anrichten: der lokale Tupel-Typ steckt ja im Kontext des Konstruktors, und wurde deshalb soeben zum Anlegen des konkreten Datentupels verwendet; solange die Vorgänger-Chain in der Länge paßt, ist es im Grunde egal, was da sonst noch für Datenwerte liegen, so lange wir nur grade am Ende der Vorgänger-Chain auf den Pointer zu unserem neu erzeugten Datenwert landen + ...dieser Check würde prüfen, ob wir nach einer vorkonfigurierten Anzahl an Schritten exakt hinter dem Ende des aktuellen Chain stehen — denn genau dann kann ein damit konform konstruierter Accessor keinen Schaden mehr anrichten: der lokale Tupel-Typ steckt ja im Kontext des Konstruktors, und wurde deshalb soeben zum Anlegen des konkreten Datentupels verwendet; solange die Vorgänger-Chain in der Zahl der Segmente paßt, ist es im Grunde egal, was da sonst noch für Datenwerte liegen, so lange wir nur grade am Ende der Vorgänger-Chain auf den Pointer zu unserem neu erzeugten Datenwert landen

    @@ -88029,7 +88054,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
    - + @@ -88060,26 +88085,39 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
    +
    - - + + + +

    - Daten-Record wird markiert mit Template-Parameter prefixLen + Daten-Record wird markiert mit Template-Parameter seg

    -
    -
    - - + +

    - die kann unmittelbar die Länge prüfen und dann an's Ende gehen und den Pointer dort setzen + Wir zählen ganz einfach die Nummer der Segmente mit, die wir anhängen. Der erste Storage-Frame wird mit 0 initialisiert, und jedesmal, wenn ein erweiterter Typ konstruiert wird, erhöht sich diese Segment-Nummer +

    + + +
    +
    + + + + + +

    + ...die kann unmittelbar die Länge prüfen und dann an's Ende gehen und den Pointer dort per Referenz exponieren; der eigentliche Check besteht lediglich darin, daß wir die Segment-Anzahl in der Typ-Definition mit den beobachteten »pointer-hops« vergleichen. Das mag nach verblüffend wenig aussehen, ist aber grade hinreichend. Wie oben schon gesagt: was in den früheren Segmenten konkret gespeichert wurde, kann uns für diesen Zugriffs-Schutz komplett egal sein, solange wir in unserem eigenen Segment bleiben, und die Zahl der Schritte bis dorthin stimmt.

    @@ -88116,18 +88154,29 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
    - + - + - + - + + + + +

    + denn die weitere Verknüpfung wird explizit in einem zweiten Schritt gemacht, und das ist gut so +

    + + +
    + +
    @@ -88158,8 +88207,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
    ...das ist dann die einzige Typ-Signatur, die der Benutzer selber schreiben muß: HeteroData<X,Y,Z>

    - - +
    @@ -88193,13 +88241,86 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
    denn wir haben ja alle expliziten Accessor-Funktionen direkt auf dem Objekt nochmal definiert, und zudem wird das C++ Tuple-Protokoll implementiert

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

    + Nicht von der Typ-Info blenden lassen: die wird hier nur einmal im Kreis geschoben. Wir nehmen zwar irgend ein Front-End, müssen das aber dann auf den vollen Chain-Typ force-casten (denn das ist das Grundkonzept, das uns Zugriff ohne virtuelle Funktionen ermöglicht). Die einzige runtime-Info  ist die Anzahl der »Hops« zum nächsten Segment, d.h. die Anzahl an Pointern, denen wir über die Kette folgen. Diese muß mit der Anzahl der Komponenten im Typ zusammenpassen +

    + + +
    + +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +