2023-06-11 04:37:38 +02:00
/*
ALLOCATOR - HANDLE . hpp - front - end handle for custom allocation schemes
Copyright ( C ) Lumiera . org
2023 , Hermann Vosseler < Ichthyostega @ web . de >
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 0213 9 , 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"
2023-12-02 23:56:46 +01:00
# include <cstddef>
2023-06-11 04:37:38 +02:00
# include <utility>
# include <list>
namespace lib {
2024-05-26 23:54:32 +02:00
namespace allo { ///< Concepts and Adaptors for custom memory management
2024-05-26 19:31:30 +02:00
/////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1366 : define Allocator Concepts here
/// TODO the following Concepts can be expected here (with C++20)
/// - Allocator : for the bare memory allocation
/// - Factory : for object fabrication and disposal
/// - Handle : a functor front-end to be dependency-injected
/**
* Adapter to implement the * Factory * concept based on a ` std : : allocator `
* @ tparam ALO a std : : allocator instance or anything compliant to [ Allocator ]
* [ Allocator ] : https : //en.cppreference.com/w/cpp/named_req/Allocator
* @ note in addition to the abilities defined by the standard , this adapter
* strives to provide some kind of _lateral leeway , _ attempting to
* create dedicated allocators for other types than the BaseType
* implied by the given \ a ALO ( standard - allocator ) .
* - this is possible if the rebound allocator can be constructed
* from the given base allocator
* - alternatively , an attempt will be made to default - construct
* the rebound allocator for the other type requested .
2024-05-27 19:02:31 +02:00
* @ warning Both avenues for adaptation may fail ,
* which could lead to compilation or runtime failure .
* @ remark deliberately this class inherits from the allocator ,
* allowing to exploit empty - base - optimisation , since
* usage of monostate allocators is quite common .
2024-05-26 19:31:30 +02:00
*/
template < class ALO >
class StdFactory
2024-05-27 19:02:31 +02:00
: private ALO
2024-05-26 19:31:30 +02:00
{
using Allo = ALO ;
using AlloT = std : : allocator_traits < Allo > ;
using BaseType = typename Allo : : value_type ;
2024-05-27 19:02:31 +02:00
Allo & baseAllocator ( ) { return * this ; }
2024-05-26 19:31:30 +02:00
template < typename X >
auto
2024-05-27 19:02:31 +02:00
adaptAllocator ( )
2024-05-26 19:31:30 +02:00
{
using XAllo = typename AlloT : : template rebind_alloc < X > ;
if constexpr ( std : : is_constructible_v < XAllo , Allo > )
2024-05-27 19:02:31 +02:00
return XAllo { baseAllocator ( ) } ;
2024-05-26 19:31:30 +02:00
else
2024-05-27 19:02:31 +02:00
return XAllo { } ;
2024-05-26 19:31:30 +02:00
}
template < class ALOT , typename . . . ARGS >
typename ALOT : : pointer
2024-05-26 23:54:32 +02:00
construct ( typename ALOT : : allocator_type & allo , ARGS & & . . . args )
2024-05-26 19:31:30 +02:00
{
2024-05-26 23:54:32 +02:00
auto loc = ALOT : : allocate ( allo , 1 ) ;
2024-05-27 21:21:03 +02:00
try { ALOT : : construct ( allo , loc , std : : forward < ARGS > ( args ) . . . ) ; }
catch ( . . . )
{
ALOT : : deallocate ( allo , loc , 1 ) ;
throw ;
}
2024-05-26 19:31:30 +02:00
return loc ;
}
2024-05-26 23:54:32 +02:00
template < class ALOT >
2024-05-26 19:31:30 +02:00
void
2024-05-26 23:54:32 +02:00
destroy ( typename ALOT : : allocator_type & allo , typename ALOT : : pointer elm )
2024-05-26 19:31:30 +02:00
{
ALOT : : destroy ( allo , elm ) ;
ALOT : : deallocate ( allo , elm , 1 ) ;
}
public :
/**
* Create an instance of the adapter factory ,
* forwarding to the embedded standard conforming allocator
* for object creation and destruction and memory management .
* @ param allo ( optional ) instance of the C + + standard allocator
* used for delegation , will be default constructed if omitted .
* @ remark the adapted standard allocator is assumed to be either a copyable
* value object , or even a mono - state ; in both cases , a dedicated
* manager instance residing » elsewhere « is referred , rendering
* all those front - end instances exchangeable .
*/
StdFactory ( Allo allo = Allo { } )
2024-05-27 19:02:31 +02:00
: Allo { std : : move ( allo ) }
2024-05-26 19:31:30 +02:00
{ }
template < class XALO >
bool constexpr operator = = ( StdFactory < XALO > const & o ) const
{
2024-05-27 19:02:31 +02:00
return baseAllocator ( ) = = o . baseAllocator ( ) ;
2024-05-26 19:31:30 +02:00
}
template < class XALO >
bool constexpr operator ! = ( StdFactory < XALO > const & o ) const
{
2024-05-27 19:02:31 +02:00
return not ( * this = = o ) ;
2024-05-26 19:31:30 +02:00
}
/** create new element using the embedded allocator */
template < class TY , typename . . . ARGS >
TY *
create ( ARGS & & . . . args )
{
if constexpr ( std : : is_same_v < TY , BaseType > )
{
2024-05-27 19:02:31 +02:00
return construct < AlloT > ( baseAllocator ( ) , std : : forward < ARGS > ( args ) . . . ) ;
2024-05-26 19:31:30 +02:00
}
else
{
using XAlloT = typename AlloT : : template rebind_traits < TY > ;
2024-05-27 19:02:31 +02:00
auto xAllo = adaptAllocator < TY > ( ) ;
2024-05-26 19:31:30 +02:00
return construct < XAlloT > ( xAllo , std : : forward < ARGS > ( args ) . . . ) ;
}
}
/** destroy the given element and discard the associated memory */
template < class TY >
void
dispose ( TY * elm )
{
if constexpr ( std : : is_same_v < TY , BaseType > )
{
2024-05-27 19:02:31 +02:00
destroy < AlloT > ( baseAllocator ( ) , elm ) ;
2024-05-26 19:31:30 +02:00
}
else
{
using XAlloT = typename AlloT : : template rebind_traits < TY > ;
2024-05-27 19:02:31 +02:00
auto xAllo = adaptAllocator < TY > ( ) ;
2024-05-26 19:31:30 +02:00
destroy < XAlloT > ( xAllo , elm ) ;
}
}
} ;
}
2023-06-11 04:37:38 +02:00
2024-05-26 19:31:30 +02:00
///////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1366 : the following code becomes obsolete in the long term
2023-06-11 04:37:38 +02:00
/**
* Placeholder implementation for a custom allocator
* @ todo shall be replaced by an AllocationCluster eventually
2024-05-26 19:31:30 +02:00
* @ todo 5 / 2024 to be reworked and aligned with a prospective C + + 20 Allocator Concept /////////////////////TICKET #1366
2023-06-11 04:37:38 +02:00
* @ remark using ` std : : list ` container , since re - entrant allocation calls are possible ,
2023-06-12 17:21:41 +02:00
* meaning that further allocations will be requested recursively from a ctor .
* Moreover , for the same reason we separate the allocation from the ctor call ,
* so we can capture the address of the new allocation prior to any possible
* re - entrant call , and handle clean - up of allocation without requiring any
* additional state flags . . . . .
2023-06-11 04:37:38 +02:00
*/
2023-06-12 17:21:41 +02:00
template < typename TY >
2023-06-11 04:37:38 +02:00
class AllocatorHandle
{
2023-06-12 17:21:41 +02:00
struct Allocation
{
2023-12-02 23:56:46 +01:00
alignas ( TY )
std : : byte buf_ [ sizeof ( TY ) ] ;
2023-06-12 17:21:41 +02:00
template < typename . . . ARGS >
TY &
create ( ARGS & & . . . args )
{
return * new ( & buf_ ) TY { std : : forward < ARGS > ( args ) . . . } ;
}
TY &
access ( )
{
2023-12-02 23:56:46 +01:00
return * std : : launder ( reinterpret_cast < TY * > ( & buf_ ) ) ;
2023-06-12 17:21:41 +02:00
}
void
2024-05-26 19:31:30 +02:00
discard ( ) /// @warning strong assumption made here: Payload was created
2023-06-12 17:21:41 +02:00
{
access ( ) . ~ TY ( ) ;
}
} ;
std : : list < Allocation > storage_ ;
2023-06-11 04:37:38 +02:00
public :
template < typename . . . ARGS >
2023-06-12 17:21:41 +02:00
TY &
2023-06-11 04:37:38 +02:00
operator ( ) ( ARGS & & . . . args )
2023-06-12 17:21:41 +02:00
{ // EX_STRONG
auto pos = storage_ . emplace ( storage_ . end ( ) ) ; ////////////////////////////////////////////////////TICKET #230 : real implementation should care for concurrency here
try {
return pos - > create ( std : : forward < ARGS > ( args ) . . . ) ;
}
catch ( . . . )
{
storage_ . erase ( pos ) ; // EX_FREE
const char * errID = lumiera_error ( ) ;
ERROR ( memory , " Allocation failed with unknown exception. "
" Lumiera errorID=%s " , errID ? errID : " ?? " ) ;
throw ;
}
2023-06-11 04:37:38 +02:00
}
2023-06-12 17:21:41 +02:00
/** @note need to do explicit clean-up, since a ctor-call might have been failed,
* and we have no simple way to record this fact internally in Allocation ,
* short of wasting additional memory for a flag to mark this situation */
~ AllocatorHandle ( )
try {
for ( auto & alloc : storage_ )
alloc . discard ( ) ;
}
ERROR_LOG_AND_IGNORE ( memory , " clean-up of custom AllocatorHandle " )
2023-06-11 04:37:38 +02:00
} ;
} // namespace lib
# endif /*LIB_ALLOCATOR_HANDLE_H*/