/* * Copyright 2020 ByteDance Games, Inc. All Rights Reserved. * * Author: Liuziming */ #pragma once #include #include #include #include #include #include #include "shm_helper.h" # if defined(__GNUC__) #include #include #include #ifdef OUTPUT_BASES extern std::map> g_bases_name; #endif #else #include "type_helper.h" #define REINTERPRET_CAST_TAG #endif namespace bg { namespace detail { struct BaseTypeInfo { TypeName type; ptrdiff_t offset; BaseTypeInfo(const char* type, ptrdiff_t offset) : type(type), offset(offset) {} }; class VptrObjectContainerBase { public: VptrObjectContainerBase(const TypeName& type, bool in_shm); virtual ~VptrObjectContainerBase(); virtual void FixObjects() = 0; virtual VptrObjectContainerBase* ShmNew() = 0; void FixVptr(); const TypeName& GetTypeName() const { return m_type; } const BaseTypeInfo* GetBaseTypeInfo(const TypeName& base_type); void RecordObject(void* ptr); void RemoveObject(void* ptr); protected: bool FixSelf(); void DoRemoveObject(size_t index); protected: static constexpr const double EXPANSION_FACTOR = 1.5; uint64_t m_version = 0; TypeName m_type; // As we have containers both in normal memory and shared // memory, so we make the pointer container as pointer here, // only initialize it when it's used in shared memory. std::vector>* m_objects = nullptr; // The following field is initialized in derived class constructor. std::vector* m_base_types = nullptr; }; template class VptrObjectContainer final : public VptrObjectContainerBase { public: static const T static_object; static const VptrObjectContainer static_container; /* * NOTE: The 2nd arg to `TypeName(...)` MUST BE 0 here, because when * we delete a pointer of polymorphic type, we can only know it's real * type with `typeid(*ptr).name()`, but we cannot get the size of that * real type, so we have to abandon size here, to match the info we can * get at deletion. The same as following calls to this function. */ VptrObjectContainer() : VptrObjectContainerBase(TypeName(typeid(T).name()), false) { SHM_DEBUG_ASSERT(this == &static_container); size_t count = GetBaseClassCount(); if(count > 0) { m_base_types = new std::vector(); m_base_types->reserve(count); FillBaseTypeInfo(m_base_types, Index2Type<0>()); } } VptrObjectContainer(const VptrObjectContainer& that) : VptrObjectContainerBase(that.m_type, true) { SHM_DEBUG_ASSERT(&that == &static_container); } ~VptrObjectContainer() final = default; void FixObjects() final { SHM_TRACE(">>>>> fixing vptr of objects in container(%p: %s)...", this, m_type.c_str()); for(void* ptr : *m_objects) { DoFix(ptr, std::integral_constant(), Index2Type<0>()); } SHM_TRACE("<<<<< vptr of objects in container(%p: %s) fixed.", this, m_type.c_str()); } VptrObjectContainerBase* ShmNew() final { SHM_DEBUG_ASSERT(this == &static_container); ShmAllocator> allocator; VptrObjectContainer* ptr = allocator.allocate(1); if(SHM_LIKELY(ptr)) { allocator.construct(ptr, *this); } return ptr; } private: template struct List { static constexpr size_t count = sizeof...(Types); }; template struct PrependList; template struct PrependList> { using ListType = List; }; template struct RemoveFirst; template struct RemoveFirst> { using RestList = List; }; struct NullType {}; template struct bases { typedef __reflection_typelist<> type; }; template # if defined(__GNUC__) using RefList = typename std::tr2::bases::type; #elif defined(_MSC_VER) using RefList = typename bases::type; #endif template struct DoMakeList; template struct DoMakeList { using ListType = List; }; template struct DoMakeList { using ListType = typename PrependList< typename RefList::first::type, typename DoMakeList< RefList::rest::type::empty::value, typename RefList::rest::type >::ListType >::ListType; }; template struct MakeList { using Result = typename DoMakeList< RefList::empty::value, RefList >::ListType; }; template struct GetNthBaseClass; template struct GetNthBaseClass> { using BaseType = typename GetNthBaseClass>::BaseType; }; template struct GetNthBaseClass<0, List> { using BaseType = Head; }; # if defined(__GNUC__) template struct BaseClassOffset { static constexpr const Derived* derived = &VptrObjectContainer::static_object; static constexpr const Base* base = static_cast(derived); static REINTERPRET_CAST_TAG ptrdiff_t value; static constexpr ptrdiff_t value = reinterpret_cast(base) - reinterpret_cast(derived); /* * This does not work with higher GCC version, due to C++ 11 standard specifies * that `reinterpret_cast` cannot be used inside constexpr expression. See this * link for detail: * https://stackoverflow.com/questions/24398102/constexpr-and-initialization-of-a-static-const-void-pointer-with-reinterpret-cas * * static constexpr ptrdiff_t value = * reinterpret_cast(static_cast(static_cast(0) + 1)) - * reinterpret_cast(static_cast(0) + 1); */ }; #endif template # if defined(__GNUC__) static constexpr ptrdiff_t GetBaseClassOffset() { using BaseList = typename MakeList::Result; using BaseType = typename GetNthBaseClass::BaseType; return BaseClassOffset::value; } #else static REINTERPRET_CAST_TAG ptrdiff_t GetBaseClassOffset() { using BaseList = typename MakeList::Result; using BaseType = typename GetNthBaseClass::BaseType; static constexpr const T* derived = &VptrObjectContainer::static_object; static constexpr const BaseType* base = static_cast(derived); return reinterpret_cast(base) - reinterpret_cast(derived); } #endif static constexpr size_t GetBaseClassCount() { // There is a NullType at the end of the list, so we compare with 1 here static_assert(MakeList::Result::count >= 1, "Invalid base class count"); return MakeList::Result::count - 1; } /* * The BEST solution should be the following: * Generate an base class offset array at compile time, during fixing, iterate * over the array and fix each vptr at corresponding offset. However, there is * a bug in GCC(4.8), which leads to the following code cannot compile: * * internal compiler error: in convert_nontype_argument, at cp/pt.c:5623 * Please submit a full bug report. * ... * * So, we have to use template function specialization instead to solve this * problem without a lot of ugly macro definitions. * * * template * struct OffsetHolder { * static constexpr ptrdiff_t base_offsets[sizeof...(offsets)] = { offsets... }; * }; * * template * struct DoGenerateOffsets { * using Result = typename DoGenerateOffsets< * total, * typename RemoveFirst::Rest, * // There is a NullType at the end of the list, we should make * // this consistent with value defined in GetBaseClassCount() * GetBaseClassOffset(), * offsets... * >::Result; * }; * * template * struct DoGenerateOffsets, offsets...> { * using Result = OffsetHolder; * }; * struct GenerateOffsets { * using Result = typename DoGenerateOffsets< * GetBaseClassCount(), typename MakeList::Result * >::Result; * }; * * void DoFix(void *ptr) { * using OffsetArray = GenerateOffsets::Result; * for (size_t i = 0; i < ARRAY_SIZE(OffsetArray::base_offsets); ++i) { * const ptrdiff_t offset = OffsetArray::base_offsets[i]; * // do fix vptr here * } * } * */ template struct Index2Type {}; template void FillBaseTypeInfo(std::vector* base_types, Index2Type) { using BaseList = typename MakeList::Result; using BaseType = typename GetNthBaseClass::BaseType; static constexpr const ptrdiff_t offset = GetBaseClassOffset(); static_assert(offset >= 0, "invalid offset"); BaseTypeInfo info(typeid(BaseType).name(), offset); base_types->push_back(info); FillBaseTypeInfo(base_types, Index2Type()); } void FillBaseTypeInfo(std::vector*, Index2Type) {} // This function means there is no base class for class T. void DoFix(void* ptr, std::false_type, Index2Type<0>) { static constexpr const T* t = &static_object; const uintptr_t* new_vptr = reinterpret_cast(reinterpret_cast(t)); uintptr_t* old_vptr = reinterpret_cast(reinterpret_cast(ptr)); if(!HasVptr::value) { SHM_ERROR("object(%p), type(%s), no base type, vptr not found(%#lx : %#lx), at least one virtual function is required.", ptr, m_type.c_str(), *old_vptr, *new_vptr); return; } SHM_DEBUG("object(%p), type(%s), no base type, old vptr(%#lx), new vptr(%#lx)", ptr, m_type.c_str(), *old_vptr, *new_vptr); if(*old_vptr != *new_vptr) { *old_vptr = *new_vptr; } } template void DoFix(void* ptr, std::true_type, Index2Type) { using BaseList = typename MakeList::Result; using BaseType = typename GetNthBaseClass::BaseType; static constexpr const ptrdiff_t offset = GetBaseClassOffset(); static constexpr const T* t = &static_object; const uintptr_t* new_vptr = reinterpret_cast(reinterpret_cast(t) + offset); uintptr_t* old_vptr = reinterpret_cast(reinterpret_cast(ptr) + offset); TypeName base_type(typeid(BaseType).name()); if(!HasVptr::value) { SHM_ERROR("object(%p), type(%s), base type(%s), base index(%lu锛? offset(%#lx), vptr(%#lx : %#lx) not found, a virtual destructor is recommended.", ptr, m_type.c_str(), base_type.c_str(), index, offset, *old_vptr, *new_vptr); } else { SHM_DEBUG("object(%p), type(%s), base type(%s), base index(%lu锛? offset(%#lx), old vptr(%#lx), new vptr(%#lx)", ptr, m_type.c_str(), base_type.c_str(), index, offset, *old_vptr, *new_vptr); if(*old_vptr != *new_vptr) { *old_vptr = *new_vptr; } } DoFix(ptr, std::true_type(), Index2Type()); } void DoFix(void*, std::true_type, Index2Type) {} }; //template const T VptrObjectContainer::static_object = T(); template const T VptrObjectContainer::static_object; template const VptrObjectContainer VptrObjectContainer::static_container; class VptrManager { public: static bool Init(); static void Fini(); ~VptrManager(); template static T* NewVptrObject(Args&&... args) { return NewVptrObjectExtraSpace(0, std::forward(args)...); } template static T* NewVptrObjectExtraSpace(size_t extra_space, Args&&... args) { static_assert(HasVptr::value, "type must be polymorphic or have vptr at least!"); const TypeName& type = VptrObjectContainer::static_container.GetTypeName(); // Do not use static assertion here, sometimes it might not need a virtual destructor. // static_assert(std::has_virtual_destructor::value, "polymorphic type must have a virtual destructor!"); if(!std::has_virtual_destructor::value) { SHM_ERROR("it's better to declare destructor virtual for polymorphic type(%s).", type.c_str()); } void* ptr = NewSpace(sizeof(T) + extra_space); if(SHM_LIKELY(ptr)) { new (ptr) T(std::forward(args)...); RecordObject(type, ptr); } return static_cast(ptr); } template static void DelVptrObject(T* ptr) { static_assert(HasVptr::value, "type must be polymorphic or have vptr at least!"); if(SHM_LIKELY(ptr)) { TypeName base_type(typeid(T).name()); TypeName real_type(typeid(*ptr).name()); void* real_ptr = GetRealPtr(base_type, real_type, ptr); RemoveObject(real_type, real_ptr); ptr->~T(); FreeSpace(real_ptr); } } static void FixVptr(); private: static void* NewSpace(size_t bytes); static void FreeSpace(void* ptr); static void* GetRealPtr(const TypeName& base_type, const TypeName& real_type, void* ptr); static void RecordObject(const TypeName& type, void* ptr); static void RemoveObject(const TypeName& type, void* ptr); private: template, typename Pred = std::equal_to> using Container = std::unordered_map>>; Container m_type_to_containers; }; } // namespace detail } // namespace bg