LCOV - code coverage report
Current view: top level - include/hpactor/mem - std_allocator.hpp (source / functions) Coverage Total Hit
Test: HPActor Coverage Lines: 88.3 % 60 53
Test Date: 2026-05-20 02:24:49 Functions: 79.4 % 131 104
Legend: Lines: hit not hit | Branches: + taken - not taken # not executed Branches: - 0 0

             Branch data     Line data    Source code
       1                 :             : // Copyright 2026 HPActor Contributors
       2                 :             : //
       3                 :             : // Licensed under the Apache License, Version 2.0 (the "License");
       4                 :             : // you may not use this file except in compliance with the License.
       5                 :             : // You may obtain a copy of the License at
       6                 :             : //
       7                 :             : //     http://www.apache.org/licenses/LICENSE-2.0
       8                 :             : //
       9                 :             : // Unless required by applicable law or agreed to in writing, software
      10                 :             : // distributed under the License is distributed on an "AS IS" BASIS,
      11                 :             : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
      12                 :             : // See the License for the specific language governing permissions and
      13                 :             : // limitations under the License.
      14                 :             : 
      15                 :             : #pragma once
      16                 :             : 
      17                 :             : #include <hpactor/mem/memory_config.hpp>
      18                 :             : #include <hpactor/mem/memory_region.hpp>
      19                 :             : #include <hpactor/mem/size_class.hpp>
      20                 :             : #include <hpactor/types/types.hpp>
      21                 :             : 
      22                 :             : #include <cstddef>
      23                 :             : #include <cstdlib>
      24                 :             : #include <memory>
      25                 :             : #include <type_traits>
      26                 :             : 
      27                 :             : namespace hpactor::mem {
      28                 :             : 
      29                 :             : /// std::allocator-compatible adapter for the HPActor slab allocator.
      30                 :             : ///
      31                 :             : /// Stateful: stores an ActorId for per-actor memory tracking and a RegionType
      32                 :             : /// for observability. All containers within an actor should use this allocator
      33                 :             : /// to route allocations through the slab allocator instead of the global heap.
      34                 :             : ///
      35                 :             : /// Two modes:
      36                 :             : ///   - Value mode:  MemStdAllocator(id(), RegionType::kActor) for local vars
      37                 :             : ///   - Pointer mode: MemStdAllocator(id_ptr(), RegionType::kActor) for actor
      38                 :             : ///     member containers — dereferences the pointer on each allocation so the
      39                 :             : ///     allocator tracks the live ActorId even when set_address() changes it.
      40                 :             : ///
      41                 :             : /// Allocations <= 4KB use the thread-local slab cache (bump + freelist).
      42                 :             : /// Oversized allocations fall back to std::malloc / std::free.
      43                 :             : template <typename T>
      44                 :             : class MemStdAllocator {
      45                 :             :   public:
      46                 :             :     using value_type = T;
      47                 :             :     using size_type = std::size_t;
      48                 :             :     using difference_type = std::ptrdiff_t;
      49                 :             : 
      50                 :             :     using propagate_on_container_copy_assignment = std::true_type;
      51                 :             :     using propagate_on_container_move_assignment = std::true_type;
      52                 :             :     using propagate_on_container_swap = std::true_type;
      53                 :             :     using is_always_equal = std::false_type;
      54                 :             : 
      55                 :           3 :     constexpr MemStdAllocator() noexcept = default;
      56                 :             : 
      57                 :             :     /// Value-based owner. Use for temporary/local containers.
      58                 :         678 :     explicit MemStdAllocator(ActorId owner,
      59                 :             :                              RegionType region = RegionType::kActor) noexcept
      60                 :         678 :         : owner_val_(owner), region_(region) {}
      61                 :             : 
      62                 :             :     /// Pointer-based owner. Use for actor member containers so the allocator
      63                 :             :     /// tracks the live ActorId (which may change via set_address).
      64                 :         125 :     explicit MemStdAllocator(const ActorId* owner_ptr,
      65                 :             :                              RegionType region = RegionType::kActor) noexcept
      66                 :         125 :         : owner_ptr_(owner_ptr), region_(region) {}
      67                 :             : 
      68                 :             :     template <typename U>
      69                 :        1454 :     MemStdAllocator(const MemStdAllocator<U>& other) noexcept
      70                 :        1454 :         : owner_ptr_(other.owner_ptr_), owner_val_(other.owner_val_),
      71                 :        1454 :           region_(other.region_) {}
      72                 :             : 
      73                 :         695 :     [[nodiscard]] T* allocate(size_t n) {
      74                 :         695 :         if (n == 0) {
      75                 :           1 :             return nullptr;
      76                 :             :         }
      77                 :         694 :         size_t bytes = n * sizeof(T);
      78                 :             : 
      79                 :         694 :         if (bytes > size_for_class(SizeClass::k4KB)) {
      80                 :             :             // NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
      81                 :           1 :             void* ptr = std::malloc(bytes);
      82                 :           1 :             if (!ptr) {
      83                 :           0 :                 std::abort();
      84                 :             :             }
      85                 :           1 :             return static_cast<T*>(ptr);
      86                 :             :         }
      87                 :             : 
      88                 :         693 :         void* ptr = nullptr;
      89                 :         693 :         ActorId owner = (owner_ptr_ != nullptr) ? *owner_ptr_ : owner_val_;
      90                 :         693 :         if (t_tla) {
      91                 :          19 :             ptr = t_tla->allocate_bytes(bytes, owner);
      92                 :             :         } else {
      93                 :             :             // NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
      94                 :         674 :             ptr = std::malloc(bytes);
      95                 :             :         }
      96                 :         693 :         if (!ptr) {
      97                 :           0 :             std::abort();
      98                 :             :         }
      99                 :         693 :         return static_cast<T*>(ptr);
     100                 :             :     }
     101                 :             : 
     102                 :         696 :     void deallocate(T* ptr, size_t n) noexcept {
     103                 :         696 :         if (!ptr || n == 0) {
     104                 :           2 :             return;
     105                 :             :         }
     106                 :         694 :         size_t bytes = n * sizeof(T);
     107                 :             : 
     108                 :         694 :         if (bytes > size_for_class(SizeClass::k4KB)) {
     109                 :             :             // NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
     110                 :           1 :             std::free(ptr);
     111                 :           1 :             return;
     112                 :             :         }
     113                 :             : 
     114                 :         693 :         if (t_tla) {
     115                 :          19 :             t_tla->deallocate(ptr);
     116                 :             :         } else {
     117                 :             :             // NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
     118                 :         674 :             std::free(ptr);
     119                 :             :         }
     120                 :             :     }
     121                 :             : 
     122                 :          14 :     ActorId owner() const noexcept {
     123                 :          14 :         return (owner_ptr_ != nullptr) ? *owner_ptr_ : owner_val_;
     124                 :             :     }
     125                 :           2 :     RegionType region() const noexcept { return region_; }
     126                 :             : 
     127                 :             :     template <typename U>
     128                 :             :     friend class MemStdAllocator;
     129                 :             : 
     130                 :             :   private:
     131                 :             :     const ActorId* owner_ptr_{nullptr};
     132                 :             :     ActorId owner_val_{};
     133                 :             :     RegionType region_{RegionType::kActor};
     134                 :             : };
     135                 :             : 
     136                 :             : template <typename T, typename U>
     137                 :           6 : bool operator==(const MemStdAllocator<T>& a,
     138                 :             :                 const MemStdAllocator<U>& b) noexcept {
     139                 :           6 :     return a.owner() == b.owner();
     140                 :             : }
     141                 :             : 
     142                 :             : template <typename T, typename U>
     143                 :           2 : bool operator!=(const MemStdAllocator<T>& a,
     144                 :             :                 const MemStdAllocator<U>& b) noexcept {
     145                 :           2 :     return !(a == b);
     146                 :             : }
     147                 :             : 
     148                 :             : // =========================================================================
     149                 :             : // MemDeleter — custom deleter for std::unique_ptr that routes deallocation
     150                 :             : // through the slab allocator instead of ::operator delete.
     151                 :             : // =========================================================================
     152                 :             : template <typename T>
     153                 :             : struct MemDeleter {
     154                 :             :     void operator()(T* ptr) const noexcept {
     155                 :             :         if (ptr) {
     156                 :             :             ptr->~T();
     157                 :             :             deallocate(ptr);
     158                 :             :         }
     159                 :             :     }
     160                 :             : };
     161                 :             : 
     162                 :             : // Unique pointer alias using slab deallocation.
     163                 :             : template <typename T>
     164                 :             : using MemUniquePtr = std::unique_ptr<T, MemDeleter<T>>;
     165                 :             : 
     166                 :             : // =========================================================================
     167                 :             : // allocate_shared — std::allocate_shared wrapper with MemStdAllocator.
     168                 :             : // Same return type as std::make_shared (no cascading type changes).
     169                 :             : // =========================================================================
     170                 :             : 
     171                 :             : /// Value-based owner.
     172                 :             : template <typename T, typename... Args>
     173                 :         658 : std::shared_ptr<T> allocate_shared(ActorId owner, RegionType region,
     174                 :             :                                    Args&&... args) {
     175                 :             :     return std::allocate_shared<T>(
     176                 :        1316 :         MemStdAllocator<T>(owner, region),
     177                 :         658 :         std::forward<Args>(args)...);
     178                 :             : }
     179                 :             : 
     180                 :             : /// Pointer-based owner — tracks the live ActorId (for actor members).
     181                 :             : template <typename T, typename... Args>
     182                 :           0 : std::shared_ptr<T> allocate_shared(const ActorId* owner_ptr,
     183                 :             :                                    RegionType region, Args&&... args) {
     184                 :             :     return std::allocate_shared<T>(
     185                 :           0 :         MemStdAllocator<T>(owner_ptr, region),
     186                 :           0 :         std::forward<Args>(args)...);
     187                 :             : }
     188                 :             : 
     189                 :             : // =========================================================================
     190                 :             : // allocate_unique — placement-new + slab allocation + MemDeleter.
     191                 :             : // Returns std::unique_ptr<T, MemDeleter<T>> (type differs from make_unique).
     192                 :             : // =========================================================================
     193                 :             : 
     194                 :             : /// Value-based owner.
     195                 :             : template <typename T, typename... Args>
     196                 :             : MemUniquePtr<T> allocate_unique(ActorId owner, RegionType region,
     197                 :             :                                 Args&&... args) {
     198                 :             :     void* mem = allocate(region, sizeof(T), owner);
     199                 :             :     if (!mem) {
     200                 :             :         std::abort();
     201                 :             :     }
     202                 :             :     // NOLINTNEXTLINE(cppcoreguidelines-owning-memory)
     203                 :             :     return MemUniquePtr<T>(::new (mem) T(std::forward<Args>(args)...));
     204                 :             : }
     205                 :             : 
     206                 :             : /// Pointer-based owner — tracks the live ActorId.
     207                 :             : template <typename T, typename... Args>
     208                 :             : MemUniquePtr<T> allocate_unique(const ActorId* owner_ptr,
     209                 :             :                                 RegionType region, Args&&... args) {
     210                 :             :     ActorId owner =
     211                 :             :         (owner_ptr != nullptr) ? *owner_ptr : ActorId{};
     212                 :             :     return allocate_unique<T>(owner, region, std::forward<Args>(args)...);
     213                 :             : }
     214                 :             : 
     215                 :             : // =========================================================================
     216                 :             : // SlabAllocated — CRTP base that overrides operator new/delete to route
     217                 :             : // through the slab allocator. Inherit from this to make std::make_unique
     218                 :             : // (and any other new-expression) use the slab allocator automatically.
     219                 :             : //
     220                 :             : // Requires t_tla and t_current_actor_id to be set on the calling thread
     221                 :             : // (the scheduler does this before dispatching actor work).
     222                 :             : // =========================================================================
     223                 :             : template <typename Derived>
     224                 :             : class SlabAllocated {
     225                 :             :   public:
     226                 :             :     // NOLINTNEXTLINE(cppcoreguidelines-no-malloc)
     227                 :        1031 :     static void* operator new(size_t size) {
     228                 :        1031 :         void* ptr = nullptr;
     229                 :        1031 :         if (t_tla) {
     230                 :           0 :             ptr = t_tla->allocate_bytes(size, current_actor_id());
     231                 :             :         }
     232                 :        1031 :         if (!ptr) {
     233                 :        1031 :             ptr = ::operator new(size);  // NOLINT
     234                 :             :         }
     235                 :        1031 :         return ptr;
     236                 :             :     }
     237                 :             : 
     238                 :        1024 :     static void operator delete(void* ptr) noexcept {
     239                 :        1024 :         if (!ptr) return;
     240                 :        1024 :         if (t_tla) {
     241                 :           0 :             t_tla->deallocate(ptr);
     242                 :             :         } else {
     243                 :        1024 :             ::operator delete(ptr);  // NOLINT
     244                 :             :         }
     245                 :             :     }
     246                 :             : 
     247                 :             :     // Array new/delete — required by C++ for completeness.
     248                 :             :     static void* operator new[](size_t size) = delete;
     249                 :             :     static void operator delete[](void* ptr) = delete;
     250                 :             : 
     251                 :             :   protected:
     252                 :             :     ~SlabAllocated() = default;
     253                 :             : };
     254                 :             : 
     255                 :             : } // namespace hpactor::mem
        

Generated by: LCOV version 2.0-1