Back to home page

EIC code displayed by LXR

 
 

    


Warning, file /acts/Core/include/Acts/Utilities/Any.hpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0008 
0009 #pragma once
0010 
0011 #include "Acts/Utilities/HashedString.hpp"
0012 
0013 #include <any>
0014 #include <array>
0015 #include <cassert>
0016 #include <cstddef>
0017 #include <utility>
0018 
0019 // #define _ACTS_ANY_ENABLE_VERBOSE
0020 // #define _ACTS_ANY_ENABLE_DEBUG
0021 // #define _ACTS_ANY_ENABLE_TRACK_ALLOCATIONS
0022 
0023 #if defined(_ACTS_ANY_ENABLE_TRACK_ALLOCATIONS)
0024 #include <iostream>
0025 #include <mutex>
0026 #include <set>
0027 #include <typeindex>
0028 #include <typeinfo>
0029 #endif
0030 
0031 #if defined(_ACTS_ANY_ENABLE_VERBOSE) || defined(_ACTS_ANY_ENABLE_DEBUG)
0032 #include <iomanip>
0033 #include <iostream>
0034 #endif
0035 
0036 #if defined(_ACTS_ANY_ENABLE_DEBUG)
0037 #define _ACTS_ANY_DEBUG(x) std::cout << x << std::endl;
0038 #else
0039 #define _ACTS_ANY_DEBUG(x)
0040 #endif
0041 
0042 #if defined(_ACTS_ANY_ENABLE_VERBOSE)
0043 #define _ACTS_ANY_VERBOSE(x) std::cout << x << std::endl;
0044 #define _ACTS_ANY_VERBOSE_BUFFER(s, b)              \
0045   do {                                              \
0046     std::cout << "" << s << ": 0x";                 \
0047     for (char c : b) {                              \
0048       std::cout << std::hex << static_cast<int>(c); \
0049     }                                               \
0050     std::cout << std::endl;                         \
0051   } while (0)
0052 #else
0053 #define _ACTS_ANY_VERBOSE(x)
0054 #define _ACTS_ANY_VERBOSE_BUFFER(s, b)
0055 #endif
0056 
0057 namespace Acts {
0058 
0059 #if defined(_ACTS_ANY_ENABLE_TRACK_ALLOCATIONS)
0060 static std::mutex _s_any_mutex;
0061 static std::set<std::pair<std::type_index, void*>> _s_any_allocations;
0062 
0063 #define _ACTS_ANY_TRACK_ALLOCATION(T, heap)                                  \
0064   do {                                                                       \
0065     std::lock_guard guard{_s_any_mutex};                                     \
0066     _s_any_allocations.emplace(std::type_index(typeid(T)), heap);            \
0067     _ACTS_ANY_DEBUG("Allocate type: " << typeid(T).name() << " at " << heap) \
0068   } while (0)
0069 
0070 #define _ACTS_ANY_TRACK_DEALLOCATION(T, heap)                                 \
0071   do {                                                                        \
0072     std::lock_guard guard{_s_any_mutex};                                      \
0073     auto it =                                                                 \
0074         _s_any_allocations.find(std::pair{std::type_index(typeid(T)), heap}); \
0075     if (it == _s_any_allocations.end()) {                                     \
0076       throw std::runtime_error{                                               \
0077           "Trying to deallocate heap address that we didn't allocate"};       \
0078     }                                                                         \
0079     _s_any_allocations.erase(it);                                             \
0080   } while (0)
0081 
0082 // Do not make member functions noexcept in the debug case
0083 #define _ACTS_ANY_NOEXCEPT /*nothing*/
0084 
0085 struct _AnyAllocationReporter {
0086   static void checkAllocations() {
0087     std::lock_guard guard{_s_any_mutex};
0088 
0089     if (!_s_any_allocations.empty()) {
0090       std::cout << "Not all allocations have been released" << std::endl;
0091       for (const auto& [idx, addr] : _s_any_allocations) {
0092         std::cout << "- " << idx.name() << ": " << addr << std::endl;
0093       }
0094       throw std::runtime_error{"AnyCheckAllocations failed"};
0095     }
0096   }
0097 
0098   ~_AnyAllocationReporter() noexcept { checkAllocations(); }
0099 };
0100 static _AnyAllocationReporter s_reporter;
0101 #else
0102 #define _ACTS_ANY_TRACK_ALLOCATION(T, heap) \
0103   do {                                      \
0104   } while (0)
0105 #define _ACTS_ANY_TRACK_DEALLOCATION(T, heap) \
0106   do {                                        \
0107   } while (0)
0108 #define _ACTS_ANY_NOEXCEPT noexcept
0109 #endif
0110 
0111 class AnyBaseAll {};
0112 
0113 /// Small opaque cache type which uses small buffer optimization
0114 template <std::size_t SIZE>
0115 class AnyBase : public AnyBaseAll {
0116   static_assert(sizeof(void*) <= SIZE, "Size is too small for a pointer");
0117 
0118  public:
0119   /// Construct with in-place type construction
0120   /// @tparam T Type to construct
0121   /// @tparam Args Constructor argument types
0122   /// @param args Arguments to forward to T's constructor
0123   template <typename T, typename... Args>
0124   explicit AnyBase(std::in_place_type_t<T> /*unused*/, Args&&... args) {
0125     using U = std::decay_t<T>;
0126     static_assert(
0127         std::is_move_assignable_v<U> && std::is_move_constructible_v<U>,
0128         "Type needs to be move assignable and move constructible");
0129     static_assert(
0130         std::is_copy_assignable_v<U> && std::is_copy_constructible_v<U>,
0131         "Type needs to be copy assignable and copy constructible");
0132 
0133     m_handler = makeHandler<U>();
0134     if constexpr (!heapAllocated<U>()) {
0135       // construct into local buffer
0136       /*U* ptr =*/new (m_data.data()) U(std::forward<Args>(args)...);
0137       _ACTS_ANY_VERBOSE("Construct local (this="
0138                         << this
0139                         << ") at: " << static_cast<void*>(m_data.data()));
0140     } else {
0141       // too large, heap allocate
0142       U* heap = new U(std::forward<Args>(args)...);
0143       _ACTS_ANY_DEBUG("Allocate type: " << typeid(U).name() << " at " << heap);
0144       _ACTS_ANY_TRACK_ALLOCATION(T, heap);
0145       setDataPtr(heap);
0146     }
0147   }
0148 
0149 #if defined(_ACTS_ANY_ENABLE_VERBOSE)
0150   AnyBase() { _ACTS_ANY_VERBOSE("Default construct this=" << this); };
0151 #else
0152   AnyBase() = default;
0153 #endif
0154 
0155   /// Construct from any value type
0156   /// @tparam T Type of the value to store
0157   /// @param value Value to store in the Any
0158   template <typename T>
0159   explicit AnyBase(T&& value) _ACTS_ANY_NOEXCEPT
0160     requires(!std::same_as<std::decay_t<T>, AnyBase<SIZE>>)
0161       : AnyBase{std::in_place_type<T>, std::forward<T>(value)} {}
0162 
0163   /// Get reference to stored value of specified type
0164   /// @tparam T Type to retrieve (must be exact type, no const/ref)
0165   /// @return Reference to the stored value
0166   /// @throws std::bad_any_cast if stored type doesn't match T
0167   template <typename T>
0168   T& as() {
0169     static_assert(std::is_same_v<T, std::decay_t<T>>,
0170                   "Please pass the raw type, no const or ref");
0171     if (m_handler == nullptr || m_handler->typeHash != typeHash<T>()) {
0172       throw std::bad_any_cast{};
0173     }
0174 
0175     _ACTS_ANY_VERBOSE("Get as "
0176                       << (m_handler->heapAllocated ? "heap" : "local"));
0177 
0178     return *reinterpret_cast<T*>(dataPtr());
0179   }
0180 
0181   /// Get const reference to stored value of specified type
0182   /// @tparam T Type to retrieve (must be exact type, no const/ref)
0183   /// @return Const reference to the stored value
0184   /// @throws std::bad_any_cast if stored type doesn't match T
0185   template <typename T>
0186   const T& as() const {
0187     static_assert(std::is_same_v<T, std::decay_t<T>>,
0188                   "Please pass the raw type, no const or ref");
0189     if (m_handler == nullptr || m_handler->typeHash != typeHash<T>()) {
0190       throw std::bad_any_cast{};
0191     }
0192 
0193     _ACTS_ANY_VERBOSE("Get as " << (m_handler->heap ? "heap" : "local"));
0194 
0195     return *reinterpret_cast<const T*>(dataPtr());
0196   }
0197 
0198   ~AnyBase() { destroy(); }
0199 
0200   /// Copy constructor
0201   /// @param other The AnyBase to copy from
0202   AnyBase(const AnyBase& other) _ACTS_ANY_NOEXCEPT {
0203     if (m_handler == nullptr && other.m_handler == nullptr) {
0204       // both are empty, noop
0205       return;
0206     }
0207 
0208     _ACTS_ANY_VERBOSE("Copy construct (this="
0209                       << this << ") at: " << static_cast<void*>(m_data.data()));
0210 
0211     m_handler = other.m_handler;
0212     copyConstruct(other);
0213   }
0214 
0215   /// Copy assignment operator
0216   /// @param other The AnyBase to copy from
0217   /// @return Reference to this object
0218   AnyBase& operator=(const AnyBase& other) _ACTS_ANY_NOEXCEPT {
0219     _ACTS_ANY_VERBOSE("Copy assign (this="
0220                       << this << ") at: " << static_cast<void*>(m_data.data()));
0221 
0222     if (m_handler == nullptr && other.m_handler == nullptr) {
0223       // both are empty, noop
0224       return *this;
0225     }
0226 
0227     if (m_handler == other.m_handler) {
0228       // same type, but checked before they're not both nullptr
0229       copy(std::move(other));
0230     } else {
0231       if (m_handler != nullptr) {
0232         // this object is not empty, but have different types => destroy
0233         destroy();
0234       }
0235       assert(m_handler == nullptr);
0236       m_handler = other.m_handler;
0237       copyConstruct(std::move(other));
0238     }
0239     return *this;
0240   }
0241 
0242   /// Move constructor
0243   /// @param other The AnyBase to move from
0244   AnyBase(AnyBase&& other) _ACTS_ANY_NOEXCEPT {
0245     _ACTS_ANY_VERBOSE("Move construct (this="
0246                       << this << ") at: " << static_cast<void*>(m_data.data()));
0247     if (m_handler == nullptr && other.m_handler == nullptr) {
0248       // both are empty, noop
0249       return;
0250     }
0251 
0252     m_handler = other.m_handler;
0253     moveConstruct(std::move(other));
0254   }
0255 
0256   /// Move assignment operator
0257   /// @param other The AnyBase to move from
0258   /// @return Reference to this object
0259   AnyBase& operator=(AnyBase&& other) _ACTS_ANY_NOEXCEPT {
0260     _ACTS_ANY_VERBOSE("Move assign (this="
0261                       << this << ") at: " << static_cast<void*>(m_data.data()));
0262     if (m_handler == nullptr && other.m_handler == nullptr) {
0263       // both are empty, noop
0264       return *this;
0265     }
0266 
0267     if (m_handler == other.m_handler) {
0268       // same type, but checked before they're not both nullptr
0269       move(std::move(other));
0270     } else {
0271       if (m_handler != nullptr) {
0272         // this object is not empty, but have different types => destroy
0273         destroy();
0274       }
0275       assert(m_handler == nullptr);
0276       m_handler = other.m_handler;
0277       moveConstruct(std::move(other));
0278     }
0279 
0280     return *this;
0281   }
0282 
0283   /// Check if the AnyBase contains a value
0284   /// @return True if a value is stored, false if empty
0285   explicit operator bool() const { return m_handler != nullptr; }
0286 
0287  private:
0288   void* dataPtr() {
0289     if (m_handler->heapAllocated) {
0290       return *reinterpret_cast<void**>(m_data.data());
0291     } else {
0292       return reinterpret_cast<void*>(m_data.data());
0293     }
0294   }
0295 
0296   void setDataPtr(void* ptr) { *reinterpret_cast<void**>(m_data.data()) = ptr; }
0297 
0298   const void* dataPtr() const {
0299     if (m_handler->heapAllocated) {
0300       return *reinterpret_cast<void* const*>(m_data.data());
0301     } else {
0302       return reinterpret_cast<const void*>(m_data.data());
0303     }
0304   }
0305 
0306   template <typename T>
0307   static consteval std::uint64_t typeHash() {
0308     // Technically, `__PRETTY_FUNCTION__` contains extra content, but should be
0309     // unique per type, that's all we care about.
0310     return detail::fnv1a_64(__PRETTY_FUNCTION__);
0311   }
0312 
0313   struct Handler {
0314     void (*destroy)(void* ptr) = nullptr;
0315     void (*moveConstruct)(void* from, void* to) = nullptr;
0316     void (*move)(void* from, void* to) = nullptr;
0317     void* (*copyConstruct)(const void* from, void* to) = nullptr;
0318     void (*copy)(const void* from, void* to) = nullptr;
0319     bool heapAllocated{false};
0320     std::uint64_t typeHash{0};
0321   };
0322 
0323   template <typename T>
0324   static const Handler* makeHandler() {
0325     static_assert(!std::is_same_v<T, AnyBase<SIZE>>, "Cannot wrap any in any");
0326     static const Handler static_handler = []() {
0327       Handler h;
0328       h.heapAllocated = heapAllocated<T>();
0329       if constexpr (!std::is_trivially_destructible_v<T> ||
0330                     heapAllocated<T>()) {
0331         h.destroy = &destroyImpl<T>;
0332       }
0333       if constexpr (!std::is_trivially_move_constructible_v<T> ||
0334                     heapAllocated<T>()) {
0335         h.moveConstruct = &moveConstructImpl<T>;
0336       }
0337       if constexpr (!std::is_trivially_move_assignable_v<T> ||
0338                     heapAllocated<T>()) {
0339         h.move = &moveImpl<T>;
0340       }
0341       if constexpr (!std::is_trivially_copy_constructible_v<T> ||
0342                     heapAllocated<T>()) {
0343         h.copyConstruct = &copyConstructImpl<T>;
0344       }
0345       if constexpr (!std::is_trivially_copy_assignable_v<T> ||
0346                     heapAllocated<T>()) {
0347         h.copy = &copyImpl<T>;
0348       }
0349 
0350       h.typeHash = typeHash<T>();
0351 
0352       _ACTS_ANY_DEBUG("Type: " << typeid(T).name());
0353       _ACTS_ANY_DEBUG(" -> destroy: " << h.destroy);
0354       _ACTS_ANY_DEBUG(" -> moveConstruct: " << h.moveConstruct);
0355       _ACTS_ANY_DEBUG(" -> move: " << h.move);
0356       _ACTS_ANY_DEBUG(" -> copyConstruct: " << h.copyConstruct);
0357       _ACTS_ANY_DEBUG(" -> copy: " << h.copy);
0358       _ACTS_ANY_DEBUG(
0359           " -> heapAllocated: " << (h.heapAllocated ? "yes" : "no"));
0360 
0361       return h;
0362     }();
0363     return &static_handler;
0364   }
0365 
0366   template <typename T>
0367   static constexpr bool heapAllocated() {
0368     return sizeof(T) > SIZE;
0369   }
0370 
0371   void destroy() {
0372     _ACTS_ANY_VERBOSE("Destructor this=" << this << " handler: " << m_handler);
0373     if (m_handler != nullptr && m_handler->destroy != nullptr) {
0374       _ACTS_ANY_VERBOSE("Non-trivial destruction");
0375       m_handler->destroy(dataPtr());
0376     }
0377     m_handler = nullptr;
0378   }
0379 
0380   void moveConstruct(AnyBase&& fromAny) {
0381     if (m_handler == nullptr) {
0382       return;
0383     }
0384 
0385     void* to = dataPtr();
0386     void* from = fromAny.dataPtr();
0387     if (m_handler->heapAllocated) {
0388       // stored on heap: just copy the pointer
0389       setDataPtr(fromAny.dataPtr());
0390       // do not delete in moved-from any
0391       fromAny.m_handler = nullptr;
0392       return;
0393     }
0394 
0395     if (m_handler->moveConstruct == nullptr) {
0396       _ACTS_ANY_VERBOSE("Trivially move construct");
0397       // trivially move constructible
0398       m_data = std::move(fromAny.m_data);
0399     } else {
0400       m_handler->moveConstruct(from, to);
0401     }
0402   }
0403 
0404   void move(AnyBase&& fromAny) {
0405     if (m_handler == nullptr) {
0406       return;
0407     }
0408 
0409     void* to = dataPtr();
0410     void* from = fromAny.dataPtr();
0411     if (m_handler->heapAllocated) {
0412       // stored on heap: just copy the pointer
0413       // need to delete existing pointer
0414       m_handler->destroy(dataPtr());
0415       setDataPtr(fromAny.dataPtr());
0416       // do not delete in moved-from any
0417       fromAny.m_handler = nullptr;
0418       return;
0419     }
0420 
0421     if (m_handler->move == nullptr) {
0422       _ACTS_ANY_VERBOSE("Trivially move");
0423       // trivially movable
0424       m_data = std::move(fromAny.m_data);
0425     } else {
0426       m_handler->move(from, to);
0427     }
0428   }
0429 
0430   void copyConstruct(const AnyBase& fromAny) {
0431     if (m_handler == nullptr) {
0432       return;
0433     }
0434 
0435     void* to = dataPtr();
0436     const void* from = fromAny.dataPtr();
0437 
0438     if (m_handler->copyConstruct == nullptr) {
0439       _ACTS_ANY_VERBOSE("Trivially copy construct");
0440       // trivially copy constructible
0441       m_data = fromAny.m_data;
0442     } else {
0443       void* copyAt = m_handler->copyConstruct(from, to);
0444       if (to == nullptr) {
0445         assert(copyAt != nullptr);
0446         // copy allocated, store pointer
0447         setDataPtr(copyAt);
0448       }
0449     }
0450   }
0451 
0452   void copy(const AnyBase& fromAny) {
0453     if (m_handler == nullptr) {
0454       return;
0455     }
0456 
0457     void* to = dataPtr();
0458     const void* from = fromAny.dataPtr();
0459 
0460     if (m_handler->copy == nullptr) {
0461       _ACTS_ANY_VERBOSE("Trivially copy");
0462       // trivially copyable
0463       m_data = fromAny.m_data;
0464     } else {
0465       m_handler->copy(from, to);
0466     }
0467   }
0468 
0469   template <typename T>
0470   static void destroyImpl(void* ptr) {
0471     assert(ptr != nullptr && "Address to destroy is nullptr");
0472     T* obj = static_cast<T*>(ptr);
0473     if constexpr (!heapAllocated<T>()) {
0474       // stored in place: just call the destructor
0475       _ACTS_ANY_VERBOSE("Destroy local at: " << ptr);
0476       obj->~T();
0477     } else {
0478       // stored on heap: delete
0479       _ACTS_ANY_DEBUG("Delete type: " << typeid(T).name()
0480                                       << " heap at: " << obj);
0481       _ACTS_ANY_TRACK_DEALLOCATION(T, obj);
0482       delete obj;
0483     }
0484   }
0485 
0486   template <typename T>
0487   static void moveConstructImpl(void* from, void* to) {
0488     _ACTS_ANY_VERBOSE("move const: " << from << " -> " << to);
0489     assert(from != nullptr && "Source is null");
0490     assert(to != nullptr && "Target is null");
0491     T* _from = static_cast<T*>(from);
0492     /*T* ptr =*/new (to) T(std::move(*_from));
0493   }
0494 
0495   template <typename T>
0496   static void moveImpl(void* from, void* to) {
0497     _ACTS_ANY_VERBOSE("move: " << from << " -> " << to);
0498     assert(from != nullptr && "Source is null");
0499     assert(to != nullptr && "Target is null");
0500 
0501     T* _from = static_cast<T*>(from);
0502     T* _to = static_cast<T*>(to);
0503 
0504     (*_to) = std::move(*_from);
0505   }
0506 
0507   template <typename T>
0508   static void* copyConstructImpl(const void* from, void* to) {
0509     _ACTS_ANY_VERBOSE("copy const: " << from << " -> " << to);
0510     assert(from != nullptr && "Source is null");
0511     const T* _from = static_cast<const T*>(from);
0512     if (to == nullptr) {
0513       assert(heapAllocated<T>() && "Received nullptr in local buffer case");
0514       to = new T(*_from);
0515       _ACTS_ANY_TRACK_ALLOCATION(T, to);
0516 
0517     } else {
0518       assert(!heapAllocated<T>() && "Received non-nullptr in heap case");
0519       /*T* ptr =*/new (to) T(*_from);
0520     }
0521     return to;
0522   }
0523 
0524   template <typename T>
0525   static void copyImpl(const void* from, void* to) {
0526     _ACTS_ANY_VERBOSE("copy: " << from << " -> " << to);
0527     assert(from != nullptr && "Source is null");
0528     assert(to != nullptr && "Target is null");
0529 
0530     const T* _from = static_cast<const T*>(from);
0531     T* _to = static_cast<T*>(to);
0532 
0533     (*_to) = *_from;
0534   }
0535 
0536   static constexpr std::size_t kMaxAlignment =
0537       std::max(alignof(std::max_align_t),
0538 #if defined(__AVX512F__)
0539                std::size_t{64}
0540 #elif defined(__AVX__)
0541                std::size_t{32}
0542 #elif defined(__SSE__)
0543                std::size_t{16}
0544 #else
0545                std::size_t{0}
0546   // Neutral element
0547   // for maximum
0548 #endif
0549       );
0550 
0551   alignas(kMaxAlignment) std::array<std::byte, SIZE> m_data{};
0552   const Handler* m_handler{nullptr};
0553 };
0554 
0555 /// @brief A type-safe container for single values of any type
0556 /// @details This is a custom implementation similar to std::any but optimized for small types
0557 ///          that can fit into a pointer-sized buffer. Values larger than a
0558 ///          pointer are stored on the heap.
0559 using Any = AnyBase<sizeof(void*)>;
0560 
0561 #undef _ACTS_ANY_VERBOSE
0562 #undef _ACTS_ANY_VERBOSE_BUFFER
0563 #undef _ACTS_ANY_ENABLE_VERBOSE
0564 
0565 }  // namespace Acts