From b18e79d07763da0e8612b9ab513ffedefaa5c1e9 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Sun, 11 Jun 2023 04:37:38 +0200 Subject: [PATCH] Dispatcher-Pipeline: solve allocation of JobTicket instances ...by defining a new scheme for access to custom allocators ...and then passing a reference to such an accessor into the JobTicket ctor, thereby allowing the ticket istelf recursively to place further JobTicket instances into the allocation space --> success, test passes (finally) --- src/lib/allocation-cluster.cpp | 4 +- src/lib/allocator-handle.hpp | 94 ++++++ src/steam/engine/job-ticket.hpp | 27 +- src/steam/fixture/segment.hpp | 32 +- tests/core/steam/engine/mock-dispatcher.hpp | 14 +- .../steam/fixture/fixture-segment-test.cpp | 4 +- wiki/thinkPad.ichthyo.mm | 277 ++++++++++++++++-- 7 files changed, 396 insertions(+), 56 deletions(-) create mode 100644 src/lib/allocator-handle.hpp diff --git a/src/lib/allocation-cluster.cpp b/src/lib/allocation-cluster.cpp index 5007e5c47..a6731aca1 100644 --- a/src/lib/allocation-cluster.cpp +++ b/src/lib/allocation-cluster.cpp @@ -34,6 +34,8 @@ #include "lib/util.hpp" #include "lib/sync.hpp" +#include + using util::isnil; @@ -66,7 +68,7 @@ namespace lib { class AllocationCluster::MemoryManager : public Sync { - typedef std::vector MemTable; + typedef std::deque MemTable; TypeInfo type_; MemTable mem_; size_t top_; ///< index of the next slot available for allocation diff --git a/src/lib/allocator-handle.hpp b/src/lib/allocator-handle.hpp new file mode 100644 index 000000000..b5fee425a --- /dev/null +++ b/src/lib/allocator-handle.hpp @@ -0,0 +1,94 @@ +/* + ALLOCATOR-HANDLE.hpp - front-end handle for custom allocation schemes + + Copyright (C) Lumiera.org + 2023, Hermann Vosseler + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +/** @file allocator-handle.hpp + ** A front-end/concept to allow access to custom memory management. + ** Minimalistic definition scheme for a functor-like object, which can be + ** passed to client code, offering a callback to generate new objects into + ** some custom allocation scheme not further disclosed. + ** + ** Lumiera employs various flavours of custom memory management, to handle + ** allocation demands from performance critical parts of the application. + ** Irrespective of the actual specifics of the allocation, typically there + ** is some _instance_ of an allocator maintained within a carefully crafted + ** context — leading to the necessity to dependency-inject a suitable front-end + ** into various connected parts of the application, to allow for coherent use + ** of allocation while avoiding tight coupling of implementation internals. + ** + ** Reduced to the bare minimum, the _ability to allocate_ can be represented + ** as a functor, which accepts arbitrary (suitable) arguments and returns a + ** reference to a newly allocated instance of some specific type; such an + ** _allocation front-end_ may then be passed as additional (template) + ** parameter to associated classes or functions, allowing to generate new + ** objects at stable memory location, which can then be wired internally. + ** + ** @todo 6/2023 this specification describes a *Concept*, not an actual + ** interface type. After the migration to C++20, it will thus be + ** possible to mark some arbitrary custom allocator / front-end + ** with such a concept, thereby documenting proper API usage. + ** + ** @see allocation-cluster.hpp + ** @see steam::fixture::Segment + ** @see steam::engine::JobTicket + */ + + +#ifndef LIB_ALLOCATOR_HANDLE_H +#define LIB_ALLOCATOR_HANDLE_H + +#include "lib/error.hpp" +#include "lib/nocopy.hpp" + +#include +#include + + + +namespace lib { + + + /** + * Placeholder implementation for a custom allocator + * @todo shall be replaced by an AllocationCluster eventually + * @note conforming to a prospective C++20 Concept `Allo` + * @remark using `std::list` container, since re-entrant allocation calls are possible, + * meaning that `emplace_front` will be called recursively from another ctor + * call invoked from `emplace_front`. Vector or Deque would be corrupted + * by such re-entrant calls, since both would alias the same entry.... + */ + template + class AllocatorHandle + : public std::list + { + public: + template + T& + operator() (ARGS&& ...args) + { + return this->emplace_front (std::forward (args)...); + } + }; + + + +} // namespace lib +#endif /*LIB_ALLOCATOR_HANDLE_H*/ diff --git a/src/steam/engine/job-ticket.hpp b/src/steam/engine/job-ticket.hpp index c8f532da6..e66936a6a 100644 --- a/src/steam/engine/job-ticket.hpp +++ b/src/steam/engine/job-ticket.hpp @@ -100,10 +100,11 @@ using lib::LUID; struct Prerequisite { Prerequisite* next{nullptr}; - JobTicket const& descriptor; + JobTicket const& prereqTicket; - Prerequisite (JobTicket const& ticket) - : descriptor{ticket} + template + Prerequisite (ExitNode const& node, ALO& allocateTicket) + : prereqTicket{allocateTicket (node, allocateTicket)} { } }; using Prerequisites = LinkedElements; @@ -131,7 +132,8 @@ using lib::LUID; template static LinkedElements buildProvisionSpec (IT); - static LinkedElements buildProvisionSpec (ExitNode const&); + template + static LinkedElements buildProvisionSpec (ExitNode const&, ALO&); private: JobTicket() { } ///< @internal as NIL marker, a JobTicket can be empty @@ -143,8 +145,9 @@ using lib::LUID; { } public: - JobTicket (ExitNode const& exitNode) - : provision_{buildProvisionSpec (exitNode)} + template + JobTicket (ExitNode const& exitNode, ALO& allocator) + : provision_{buildProvisionSpec (exitNode, allocator)} { } static const JobTicket NOP; @@ -163,7 +166,7 @@ using lib::LUID; : provision_[slotNr].requirements.begin() ,[](Prerequisite& prq) -> JobTicket const& { - return prq.descriptor; + return prq.prereqTicket; }); } @@ -259,9 +262,9 @@ using lib::LUID; operator->() const { REQUIRE (!empty() && toExplore_.top().isValid()); - REQUIRE (toExplore_.top()->descriptor.isValid()); + REQUIRE (toExplore_.top()->prereqTicket.isValid()); - return & toExplore_.top()->descriptor; + return & toExplore_.top()->prereqTicket; } @@ -336,8 +339,9 @@ using lib::LUID; ENSURE (not isnil (provisionSpec)); return provisionSpec; } + template inline LinkedElements - JobTicket::buildProvisionSpec (ExitNode const& exitNode) + JobTicket::buildProvisionSpec (ExitNode const& exitNode, ALO& allocTicket) { REQUIRE (not isnil (exitNode)); // has valid functor LinkedElements provisionSpec; @@ -345,8 +349,7 @@ using lib::LUID; JobFunctor& func = exitNode.getInvocationFunctor(); auto& provision = provisionSpec.emplace (func, exitNode, invoSeed); for (ExitNode const& preNode: exitNode.getPrerequisites()) - provision.requirements.emplace(preNode); /////////////////////////////////////////////TICKET #1292 : need to pass in Allocator as argument - //////////////////////////////////////////////////////////////////////OOO : where to ALLOCATE the prerequisite JobTickets ??!! + provision.requirements.emplace(preNode, allocTicket); ////////////////////////////////////TICKET #1292 : need to pass in generic Allocator as argument return provisionSpec; } diff --git a/src/steam/fixture/segment.hpp b/src/steam/fixture/segment.hpp index 15a784db2..3acb1fa14 100644 --- a/src/steam/fixture/segment.hpp +++ b/src/steam/fixture/segment.hpp @@ -36,6 +36,7 @@ #include "steam/fixture/node-graph-attachment.hpp" #include "steam/mobject/explicitplacement.hpp" #include "steam/engine/job-ticket.hpp" +#include "lib/allocator-handle.hpp" #include "lib/time/timevalue.hpp" #include "lib/itertools.hpp" #include "lib/util.hpp" @@ -67,8 +68,9 @@ namespace fixture { */ class Segment { - using TicketAlloc = std::deque; - using PortTable = std::deque>; + using JobTicket = engine::JobTicket; + using TicketAlloc = lib::AllocatorHandle; + using PortTable = std::deque>; protected: @@ -76,8 +78,8 @@ namespace fixture { TimeSpan span_; /** render plan / blueprint to use for this segment */ - TicketAlloc tickets_; - PortTable portTab_; + TicketAlloc ticketAlloc_; + PortTable portTable_; ///////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #725 : placeholder code /** relevant MObjects comprising this segment. */ @@ -96,15 +98,15 @@ namespace fixture { Segment (TimeSpan covered ,NodeGraphAttachment&& modelLink) : span_{covered} - , tickets_{} - , portTab_{} + , ticketAlloc_{} + , portTable_{} , exitNode{move (modelLink)} { } Segment (Segment const& original, TimeSpan changed) : span_{changed} - , tickets_{} // Note: not cloning tickets owned by Segment - , portTab_{} + , ticketAlloc_{} // Note: not cloning tickets owned by Segment + , portTable_{} , exitNode{original.exitNode} /////////////////////////////////////////////////////////////////////OOO really? cloning ExitNodes? { } @@ -120,10 +122,10 @@ namespace fixture { engine::JobTicket const& jobTicket (size_t portNr) const { - if (portNr >= portTab_.size()) + if (portNr >= portTable_.size()) unConst(this)->generateTickets_onDemand (portNr); - ASSERT (portNr < portTab_.size()); - return portTab_[portNr]; + ASSERT (portNr < portTable_.size()); + return portTable_[portNr]; } bool @@ -137,13 +139,13 @@ namespace fixture { void generateTickets_onDemand (size_t portNr) { - for (size_t i = portTab_.size(); i <= portNr; ++i) + for (size_t i = portTable_.size(); i <= portNr; ++i) if (isnil (exitNode[portNr])) - portTab_.emplace_back (engine::JobTicket::NOP); // disable this slot + portTable_.emplace_back (engine::JobTicket::NOP); // disable this slot else {// Ticket was not generated yet... - tickets_.emplace_back (exitNode[portNr]); - portTab_.emplace_back (tickets_.back()); // ref to new ticket ‣ slot + ticketAlloc_(exitNode[portNr], ticketAlloc_); + portTable_.emplace_back (ticketAlloc_.back()); // ref to new ticket ‣ slot } } }; diff --git a/tests/core/steam/engine/mock-dispatcher.hpp b/tests/core/steam/engine/mock-dispatcher.hpp index 2f5c02f67..329618ed9 100644 --- a/tests/core/steam/engine/mock-dispatcher.hpp +++ b/tests/core/steam/engine/mock-dispatcher.hpp @@ -55,6 +55,7 @@ #include "vault/engine/job.h" #include "vault/engine/dummy-job.hpp" #include "vault/real-clock.hpp" +#include "lib/allocator-handle.hpp" #include "lib/time/timevalue.hpp" #include "lib/diff/gen-node.hpp" #include "lib/linked-elements.hpp" @@ -119,15 +120,22 @@ namespace test { * @see DispatcherInterface_test */ class MockJobTicket - : public JobTicket + : private lib::AllocatorHandle + , public JobTicket { + auto& + allocator() + { + return static_cast&> (*this); + } + public: MockJobTicket() - : JobTicket{defineSimpleSpec()} + : JobTicket{defineSimpleSpec(), allocator()} { } MockJobTicket (HashVal seed) - : JobTicket{defineSimpleSpec (seed)} + : JobTicket{defineSimpleSpec (seed), allocator()} { } // template diff --git a/tests/core/steam/fixture/fixture-segment-test.cpp b/tests/core/steam/fixture/fixture-segment-test.cpp index a56229ee8..c7833e073 100644 --- a/tests/core/steam/fixture/fixture-segment-test.cpp +++ b/tests/core/steam/fixture/fixture-segment-test.cpp @@ -120,9 +120,9 @@ namespace test { CHECK (13 == getMarker (ticket)); auto prereq = ticket.getPrerequisites(); CHECK (not isnil(prereq)); - CHECK (23 == getMarker (*prereq)); + CHECK (55 == getMarker (*prereq)); // Note: order of prerequisites is flipped (by LinkedElements) ++prereq; - CHECK (55 == getMarker (*prereq)); + CHECK (23 == getMarker (*prereq)); ++prereq; CHECK (isnil(prereq)); } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 168ead176..065826904 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -64504,6 +64504,57 @@ + + + + + + + + + + + + + + + + + + + +

+ und zwar typischerweise per Indirektion an einen low-level Allocator, bei dem es sich aber auch um einen Slot in einem Allocation-Cluster handeln kann +

+ +
+
+
+
+ + + + + + +
    +
  • + bevorzugt: die »achitektonische Lösung« : es ist dafür gesorgt daß die Allokator-Impl garantiert länger lebt als der Client +
  • +
  • + in schwierigen Fällen: ref-counting Front-End-Handle denkbar +
  • +
+ +
+ +
+
+ + + + +
@@ -70068,7 +70119,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -71981,9 +72032,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -74098,7 +74150,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -74109,9 +74161,9 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - + @@ -74120,8 +74172,14 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + + + + + + @@ -74265,8 +74323,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + @@ -74283,14 +74341,145 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + - - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ aktuell wäre spezifisch besser — +

+

+ aber AllocationCluster wäre generisch +

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

+ Beschluß: Allo ≡ Funktor mit variadischen Argumenten +

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

+ exakt dieses Problem hatte ich schon vor einigen Wochen, bei der ersten (damals Mock)-Implementierung: da die Prerequisites private sind, gibt es keine andere Lösung als sie aus dem übergeordneten ctor heraus zu konstruieren, und damit re-entrant aus dem JobTicket-ctor ein weiteres JobTicket zu allozieren. Vector und Deque können das nicht hanhaben, und es kommt zu einem gefährlichen Aliasing, bei dem das geschachtelte Ticket an der gleichen Stelle im Speicher steht wie das Haupt-Ticket, und damit dessen ctor-Aufruf korrumpiert +

+ +
+ + +
+
+
+ + + + + + + + + + + +
@@ -74343,7 +74532,8 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + + @@ -74373,7 +74563,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -74397,10 +74587,10 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + - - + + @@ -74442,7 +74632,7 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- + @@ -74472,13 +74662,19 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
- - + + + +
+ + + +
@@ -81010,6 +81206,41 @@ Date:   Thu Apr 20 18:53:17 2023 +0200
+ + + + + + + +

+ ...also für template templtate parameter; +

+

+ für diese muß man allerdings stets die umständlichere requires-Syntax verwenden +

+

+ +

+
template <template <typename...> class ALO>
+concept Allocator = true; // actual constraint here
+
+template <template <typename...> class ALO>
+  requires Allocator<ALO>
+class Something
+{ };
+

+ +

+

+ ...man kann aber auch ein Concept als Kombination weiterer definieren +

+ +
+ + +
+