diff options
Diffstat (limited to 'src/lib/util/dynamicclass.ipp')
-rw-r--r-- | src/lib/util/dynamicclass.ipp | 505 |
1 files changed, 505 insertions, 0 deletions
diff --git a/src/lib/util/dynamicclass.ipp b/src/lib/util/dynamicclass.ipp new file mode 100644 index 00000000000..d44c5e50e7d --- /dev/null +++ b/src/lib/util/dynamicclass.ipp @@ -0,0 +1,505 @@ +// license:BSD-3-Clause +// copyright-holders:Vas Crabb +#ifndef MAME_LIB_UTIL_DYNAMICCLASS_IPP +#define MAME_LIB_UTIL_DYNAMICCLASS_IPP + +#pragma once + +#include "dynamicclass.h" + + +namespace util { + +namespace detail { + +/// \brief Get offset to saved base virtual table pointer from type info +/// +/// Gets the offset to the saved base class virtual table pointer from +/// the location the dynamic derived class virtual table entry used for +/// recovery points to. +/// \return Offset from the type info to saved base class virtual table +/// pointer in bytes. +inline std::ptrdiff_t dynamic_derived_class_base::base_vtable_offset() +{ +#if MAME_ABI_CXX_TYPE == MAME_ABI_CXX_MSVC + return 0; +#else + return + reinterpret_cast<std::uint8_t *>(&reinterpret_cast<dynamic_derived_class_base *>(std::uintptr_t(0))->m_base_vtable) - + reinterpret_cast<std::uint8_t *>(&reinterpret_cast<dynamic_derived_class_base *>(std::uintptr_t(0))->m_type_info); +#endif +} + + +/// \brief Get base class virtual table pointer +/// +/// Gets the base class virtual pointer for an instance of a dynamic +/// derived class. +/// \tparam Base The base class type (usually determined automatically). +/// \param [in] object Base class member of dynamic derived class +/// instance. +/// \return Pointer to first virtual member function entry in base class +/// virtual table. +template <class Base> +inline std::uintptr_t const *dynamic_derived_class_base::get_base_vptr( + Base const &object) +{ + auto const vptr = *reinterpret_cast<std::uintptr_t const *>(&object); + auto const recovery = reinterpret_cast<std::uintptr_t const *>(vptr)[VTABLE_BASE_RECOVERY_INDEX]; + return *reinterpret_cast<std::uintptr_t const *const *>(recovery + base_vtable_offset()); +} + + +/// \brief Restore base class virtual table pointer +/// +/// Restores the base class virtual pointer in an instance of a dynamic +/// derived class. Must be called before calling the base class +/// destructor. +/// \tparam Base The base class type (usually determined automatically). +/// \param [in,out] object Base class member of dynamic derived class +/// instance. +template <class Base> +inline void dynamic_derived_class_base::restore_base_vptr( + Base &object) +{ + auto &vptr = *reinterpret_cast<std::uintptr_t *>(&object); + auto const recovery = reinterpret_cast<std::uintptr_t const *>(vptr)[VTABLE_BASE_RECOVERY_INDEX]; + vptr = *reinterpret_cast<std::uintptr_t const *>(recovery + base_vtable_offset()); + assert(reinterpret_cast<void const *>(vptr)); +} + + +/// \brief Resolve pointer to base class member function +/// +/// Given an instance and pointer to a base class member function, gets +/// the adjusted \c this pointer and conventional function pointer. +/// \tparam Base The base class type (usually determined automatically). +/// \tparam R Return type of member function (usually determined +/// automatically). +/// \tparam T Parameter types expected by the member function (usually +/// determined automatically). +/// \param [in] object Base class member of dynamic derived class +/// instance. +/// \param [in] func Pointer to member function of base class. +/// \return A \c std::pair containing the conventional function pointer +/// and adjusted \c this pointer. +/// \exception std::invalid_argument Thrown if the \p func argument is +/// not a supported member function. This includes non-virtual member +/// functions and virtual member functions that aren't supported for +/// overriding when using the MSVC C++ ABI. +template <typename Base, typename R, typename... T> +inline std::pair<R MAME_ABI_CXX_MEMBER_CALL (*)(void *, T...), void *> dynamic_derived_class_base::resolve_base_member_function( + Base &object, + R (Base::*func)(T...)) +{ + static_assert(supported_return_type<R>::value, "Unsupported member function return type"); + member_function_pointer_pun_t<decltype(func)> thunk; + thunk.ptr = func; +#if MAME_ABI_CXX_TYPE == MAME_ABI_CXX_MSVC + std::size_t const index = resolve_virtual_member_slot(thunk.equiv, sizeof(func)); + auto const vptr = reinterpret_cast<std::uintptr_t const *>(get_base_vptr(object)); + std::uintptr_t const* const entryptr = vptr + (index * MEMBER_FUNCTION_SIZE); + return std::make_pair( + MAME_ABI_CXX_VTABLE_FNDESC + ? reinterpret_cast<R MAME_ABI_CXX_MEMBER_CALL (*)(void *, T...)>(std::uintptr_t(entryptr)) + : reinterpret_cast<R MAME_ABI_CXX_MEMBER_CALL (*)(void *, T...)>(*entryptr), + &object); +#else + if (thunk.equiv.is_virtual()) + { + assert(!thunk.equiv.this_pointer_offset()); + auto const vptr = reinterpret_cast<std::uint8_t const *>(get_base_vptr(object)); + auto const entryptr = reinterpret_cast<std::uintptr_t const *>(vptr + thunk.equiv.virtual_table_entry_offset()); + return std::make_pair( + MAME_ABI_CXX_VTABLE_FNDESC + ? reinterpret_cast<R MAME_ABI_CXX_MEMBER_CALL (*)(void *, T...)>(std::uintptr_t(entryptr)) + : reinterpret_cast<R MAME_ABI_CXX_MEMBER_CALL (*)(void *, T...)>(*entryptr), + &object); + } + else + { + return std::make_pair( + reinterpret_cast<R MAME_ABI_CXX_MEMBER_CALL (*)(void *, T...)>(thunk.equiv.function_pointer()), + reinterpret_cast<std::uint8_t *>(&object) + thunk.equiv.this_pointer_offset()); + } +#endif +} + +template <typename Base, typename R, typename... T> +inline std::pair<R MAME_ABI_CXX_MEMBER_CALL (*)(void const *, T...), void const *> dynamic_derived_class_base::resolve_base_member_function( + Base const &object, + R (Base::*func)(T...) const) +{ + static_assert(supported_return_type<R>::value, "Unsupported member function return type"); + member_function_pointer_pun_t<decltype(func)> thunk; + thunk.ptr = func; +#if MAME_ABI_CXX_TYPE == MAME_ABI_CXX_MSVC + std::size_t const index = resolve_virtual_member_slot(thunk.equiv, sizeof(func)); + auto const vptr = reinterpret_cast<std::uintptr_t const *>(get_base_vptr(object)); + std::uintptr_t const* const entryptr = vptr + (index * MEMBER_FUNCTION_SIZE); + return std::make_pair( + MAME_ABI_CXX_VTABLE_FNDESC + ? reinterpret_cast<R MAME_ABI_CXX_MEMBER_CALL (*)(void const *, T...)>(std::uintptr_t(entryptr)) + : reinterpret_cast<R MAME_ABI_CXX_MEMBER_CALL (*)(void const *, T...)>(*entryptr), + &object); +#else + if (thunk.equiv.is_virtual()) + { + assert(!thunk.equiv.this_pointer_offset()); + auto const vptr = reinterpret_cast<std::uint8_t const *>(get_base_vptr(object)); + auto const entryptr = reinterpret_cast<std::uintptr_t const *>(vptr + thunk.equiv.virtual_table_entry_offset()); + return std::make_pair( + MAME_ABI_CXX_VTABLE_FNDESC + ? reinterpret_cast<R MAME_ABI_CXX_MEMBER_CALL (*)(void const *, T...)>(std::uintptr_t(entryptr)) + : reinterpret_cast<R MAME_ABI_CXX_MEMBER_CALL (*)(void const *, T...)>(*entryptr), + &object); + } + else + { + return std::make_pair( + reinterpret_cast<R MAME_ABI_CXX_MEMBER_CALL (*)(void const *, T...)>(thunk.equiv.function_pointer()), + reinterpret_cast<std::uint8_t const *>(&object) + thunk.equiv.this_pointer_offset()); + } +#endif +} + + +/// \brief Complete object destructor for dynamic derived class +/// +/// Restores the base class virtual table pointer, calls the extra data +/// and base class destructors, but does not free the memory occupied by +/// the object. Used when the base class type has a virtual destructor +/// to allow deleting instances of a dynamic derived class through +/// pointers to the base class type. +/// +/// Only used for the Itanium C++ ABI. +/// \param [in] object Reference to the object to destroy. +template <class Base, typename Extra> +void MAME_ABI_CXX_MEMBER_CALL dynamic_derived_class_base::destroyer<Base, Extra, std::enable_if_t<std::has_virtual_destructor_v<Base> > >::complete_object_destructor( + value_type<Base, Extra> &object) +{ + restore_base_vptr(object.base); + object.~value_type(); +} + + +/// \brief Deleting destructor for dynamic derived class +/// +/// Restores the base class virtual table pointer, calls the extra data +/// and base class destructors, and frees the memory occupied by the +/// object. Used when the base class type has a virtual destructor to +/// allow deleting instances of a dynamic derived class through pointers +/// to the base class type. +/// +/// Only used for the Itanium C++ ABI. +/// \param [in] object Pointer to the object to destroy. +template <class Base, typename Extra> +void MAME_ABI_CXX_MEMBER_CALL dynamic_derived_class_base::destroyer<Base, Extra, std::enable_if_t<std::has_virtual_destructor_v<Base> > >::deleting_destructor( + value_type<Base, Extra> *object) +{ + restore_base_vptr(object->base); + delete object; +} + + +/// \brief Destructor for dynamic derived class +/// +/// Restores the base class virtual table pointer, calls the extra data +/// and base class destructors, and optionally frees the memory occupied +/// by the object. Used when the base class type has a virtual +/// destructor to allow deleting instances of a dynamic derived class +/// through pointers to the base class type. +/// +/// Only used for the MSVC C++ ABI. +/// \param [in] object Pointer to the object to destroy. +/// \param [in] flags If bit 0 is set, the memory occupied by the object +/// will be freed. +/// \return The supplied object pointer. +template <class Base, typename Extra> +void *MAME_ABI_CXX_MEMBER_CALL dynamic_derived_class_base::destroyer<Base, Extra, std::enable_if_t<std::has_virtual_destructor_v<Base> > >::scalar_deleting_destructor( + value_type<Base, Extra> *object, + unsigned int flags) +{ + restore_base_vptr(object->base); + object->~value_type(); + if (flags & 1) + operator delete (static_cast<void *>(object)); + return object; +} + + +/// \brief Deleter for dynamic derived classes +/// +/// Restores the base class virtual table pointer, calls the extra data +/// and base class destructors, and frees the memory occupied by the +/// object. Used to delete instances of a dynamic derived class when +/// the base class type does not have a virtual destructor. +/// \param [in] object Pointer to the object to destroy. +template <class Base, typename Extra> +void dynamic_derived_class_base::destroyer<Base, Extra, std::enable_if_t<!std::has_virtual_destructor_v<Base> > >::operator()( + Base *object) const +{ + restore_base_vptr(*object); + delete reinterpret_cast<value_type<Base, Extra> *>(object); +} + +} // namespace detail + + +/// \brief Create a dynamic derived class +/// +/// Creates a new dynamic derived class. No base member functions are +/// overridden initially. +/// \param [in] name The unmangled name for the new dynamic derived +/// class. This will be mangled for use in the generated type info. +template <class Base, typename Extra, std::size_t VirtualCount> +dynamic_derived_class<Base, Extra, VirtualCount>::dynamic_derived_class( + std::string_view name) : + detail::dynamic_derived_class_base(name) +{ +#if MAME_ABI_CXX_TYPE == MAME_ABI_CXX_MSVC + m_vtable[0] = std::uintptr_t(&m_base_vtable); // for restoring the base vtable +#else + m_type_info.base_type = &typeid(Base); + m_vtable[0] = 0; // offset to top + m_vtable[1] = std::uintptr_t(&m_type_info); // type info +#endif + if constexpr (std::has_virtual_destructor_v<Base>) + { + if (MAME_ABI_CXX_TYPE == MAME_ABI_CXX_MSVC) + { + if (MAME_ABI_CXX_VTABLE_FNDESC) + { + std::copy_n( + reinterpret_cast<std::uintptr_t const *>(std::uintptr_t(&destroyer<Base, Extra>::scalar_deleting_destructor)), + MEMBER_FUNCTION_SIZE, + &m_vtable[VTABLE_PREFIX_ENTRIES]); + } + else + { + m_vtable[VTABLE_PREFIX_ENTRIES] = std::uintptr_t(&destroyer<Base, Extra>::scalar_deleting_destructor); + } + } + else + { + if (MAME_ABI_CXX_VTABLE_FNDESC) + { + std::copy_n( + reinterpret_cast<std::uintptr_t const *>(std::uintptr_t(&destroyer<Base, Extra>::complete_object_destructor)), + MEMBER_FUNCTION_SIZE, + &m_vtable[VTABLE_PREFIX_ENTRIES]); + std::copy_n( + reinterpret_cast<std::uintptr_t const *>(std::uintptr_t(&destroyer<Base, Extra>::deleting_destructor)), + MEMBER_FUNCTION_SIZE, + &m_vtable[VTABLE_PREFIX_ENTRIES + MEMBER_FUNCTION_SIZE]); + } + else + { + m_vtable[VTABLE_PREFIX_ENTRIES] = std::uintptr_t(&destroyer<Base, Extra>::complete_object_destructor); + m_vtable[VTABLE_PREFIX_ENTRIES + 1] = std::uintptr_t(&destroyer<Base, Extra>::deleting_destructor); + } + } + } + std::fill( + std::next(m_vtable.begin(), VTABLE_PREFIX_ENTRIES + (FIRST_OVERRIDABLE_MEMBER_OFFSET * MEMBER_FUNCTION_SIZE)), + m_vtable.end(), + std::uintptr_t(static_cast<void *>(nullptr))); +} + + +/// \brief Create a dynamic derived class using a prototype +/// +/// Creates a new dynamic derived class using an existing dynamic +/// derived class as a prototype. Overridden base class member +/// functions are inherited from the current state of the prototype. +/// The new dynamic derived class is not affected by any future +/// modifications to the prototype. The prototype may be destroyed +/// safely before the new dynamic derived class is destroyed (provided +/// all its instances are destroyed first). +/// \param [in] prototype The dynamic derived class to use as a +/// prototype. +/// \param [in] name The unmangled name for the new dynamic derived +/// class. This will be mangled for use in the generated type info. +template <class Base, typename Extra, std::size_t VirtualCount> +dynamic_derived_class<Base, Extra, VirtualCount>::dynamic_derived_class( + dynamic_derived_class const &prototype, + std::string_view name) : + detail::dynamic_derived_class_base(name), + m_vtable(prototype.m_vtable), + m_overridden(prototype.m_overridden) +{ + m_base_vtable = prototype.m_base_vtable; +#if MAME_ABI_CXX_TYPE == MAME_ABI_CXX_MSVC + m_vtable[0] = std::uintptr_t(&m_base_vtable); // for restoring the base vtable +#else + m_type_info.base_type = &typeid(Base); + m_vtable[1] = std::uintptr_t(&m_type_info); // type info +#endif +} + + +/// \brief Override a virtual member function +/// +/// Replace the virtual table entry for the specified base member +/// function with the supplied function. This applies to existing +/// instances as well as newly created instances. Note that if you are +/// using some technique to resolve pointers to virtual member functions +/// in advance, resolved pointers may not reflect the change. +/// \tparam R Return type of member function to override (usually +/// determined automatically). +/// \tparam T Parameter types expected by the member function to +/// override (usually determined automatically). +/// \param [in] slot A pointer to the base class member function to +/// override. Must be a pointer to a virtual member function. +/// \param [in] func A pointer to the function to use to override the +/// base class member function. +/// \exception std::invalid_argument Thrown if the \p slot argument is +/// not a supported virtual member function. +/// \sa restore_base_member_function +template <class Base, typename Extra, std::size_t VirtualCount> +template <typename R, typename... T> +void dynamic_derived_class<Base, Extra, VirtualCount>::override_member_function( + R (Base::*slot)(T...), + R MAME_ABI_CXX_MEMBER_CALL (*func)(type &, T...)) +{ + static_assert(supported_return_type<R>::value, "Unsupported member function return type"); + member_function_pointer_pun_t<decltype(slot)> thunk; + thunk.ptr = slot; + override_member_function(thunk.equiv, std::uintptr_t(func), sizeof(func)); +} + +template <class Base, typename Extra, std::size_t VirtualCount> +template <typename R, typename... T> +void dynamic_derived_class<Base, Extra, VirtualCount>::override_member_function( + R (Base::*slot)(T...) const, + R MAME_ABI_CXX_MEMBER_CALL (*func)(type const &, T...)) +{ + static_assert(supported_return_type<R>::value, "Unsupported member function return type"); + member_function_pointer_pun_t<decltype(slot)> thunk; + thunk.ptr = slot; + override_member_function(thunk.equiv, std::uintptr_t(func), sizeof(func)); +} + + +/// \brief Restore the base implementation of a member function +/// +/// If the specified virtual member function of the base class has been +/// overridden, restore the base class implementation. This applies to +/// existing instances as well as newly created instances. Note that if +/// you are using some technique to resolve pointers to virtual member +/// functions in advance, resolved pointers may not reflect the change. +/// \tparam R Return type of member function to restore (usually +/// determined automatically). +/// \tparam T Parameter types expected by the member function to +/// to restore (usually determined automatically). +/// \param [in] slot A pointer to the base class member function to +/// restore. Must be a pointer to a virtual member function. +/// \exception std::invalid_argument Thrown if the \p slot argument is +/// not a supported virtual member function. +/// \sa override_member_function +template <class Base, typename Extra, std::size_t VirtualCount> +template <typename R, typename... T> +void dynamic_derived_class<Base, Extra, VirtualCount>::restore_base_member_function( + R (Base::*slot)(T...)) +{ + member_function_pointer_pun_t<decltype(slot)> thunk; + thunk.ptr = slot; + std::size_t const index = resolve_virtual_member_slot(thunk.equiv, sizeof(slot)); + assert(index < VIRTUAL_MEMBER_FUNCTION_COUNT); + assert(FIRST_OVERRIDABLE_MEMBER_OFFSET <= index); + if (m_overridden[index - FIRST_OVERRIDABLE_MEMBER_OFFSET] && m_base_vtable) + { + std::copy_n( + reinterpret_cast<std::uintptr_t const *>(m_base_vtable) + (index * MEMBER_FUNCTION_SIZE), + MEMBER_FUNCTION_SIZE, + &m_vtable[VTABLE_PREFIX_ENTRIES + (index * MEMBER_FUNCTION_SIZE)]); + } + m_overridden[index - FIRST_OVERRIDABLE_MEMBER_OFFSET] = false; +} + + +/// \brief Create a new instance +/// +/// Creates a new instance of the dynamic derived class constructed with +/// the supplied arguments. +/// \tparam T Constructor argument types (usually determined +/// automatically). +/// \param [out] object Receives an pointer to the object storing the +/// base type and extra data. +/// \param [in] args Constructor arguments for the object to be +/// instantiated. If the first argument is +/// \c std::piecewise_construct, it must be followed by two tuples +/// containing the arguments to forward to the base class and extra +/// data constructors, respectively. If the first argument is not +/// \c std::piecewise_construct, all the arguments are forwarded to +/// the base class constructor. +/// \return A unique pointer to the new instance. +template <class Base, typename Extra, std::size_t VirtualCount> +template <typename... T> +typename dynamic_derived_class<Base, Extra, VirtualCount>::pointer dynamic_derived_class<Base, Extra, VirtualCount>::instantiate( + type *&object, + T &&... args) +{ + std::unique_ptr<type> result(new type(std::forward<T>(args)...)); + auto &vptr = *reinterpret_cast<std::uintptr_t const **>(&result->base); + if (!m_base_vtable) + { + assert(std::uintptr_t(result.get()) == std::uintptr_t(&result->base)); + m_base_vtable = vptr; + if (MAME_ABI_CXX_TYPE == MAME_ABI_CXX_MSVC) + m_vtable[1] = vptr[-1]; // use the base class complete object locator - too hard to fake + for (std::size_t i = 0; VirtualCount > i; ++i) + { + if (!m_overridden[i]) + { + std::size_t const offset = (i + FIRST_OVERRIDABLE_MEMBER_OFFSET) * MEMBER_FUNCTION_SIZE; + std::copy_n(vptr + offset, MEMBER_FUNCTION_SIZE, &m_vtable[VTABLE_PREFIX_ENTRIES + offset]); + } + } + } + vptr = &m_vtable[VTABLE_PREFIX_ENTRIES]; + object = result.get(); + return pointer(&result.release()->base); +} + + +/// \brief Replace member function in virtual table +/// +/// Does the actual work involved in replacing a virtual table entry to +/// override a virtual member function of the base class, avoiding +/// duplication between overloads. +/// \param [in] slot Internal representation of pointer to a virtual +/// member function of the base class. May be modified. +/// \param [in] func A pointer to the function to use to override the +/// base class member function reinterpreted as an unsigned integer of +/// equivalent size. +/// \param [in] size Size of the member function pointer type for the +/// \p slot argument. +/// \exception std::invalid_argument Thrown if the \p slot argument is +/// not a supported virtual member function. +template <class Base, typename Extra, std::size_t VirtualCount> +void dynamic_derived_class<Base, Extra, VirtualCount>::override_member_function( + member_function_pointer_equiv &slot, + std::uintptr_t func, + std::size_t size) +{ + std::size_t const index = resolve_virtual_member_slot(slot, size); + assert(index < VIRTUAL_MEMBER_FUNCTION_COUNT); + assert(FIRST_OVERRIDABLE_MEMBER_OFFSET <= index); + m_overridden[index - FIRST_OVERRIDABLE_MEMBER_OFFSET] = true; + if (MAME_ABI_CXX_VTABLE_FNDESC) + { + std::copy_n( + reinterpret_cast<std::uintptr_t const *>(func), + MEMBER_FUNCTION_SIZE, + &m_vtable[VTABLE_PREFIX_ENTRIES + (index * MEMBER_FUNCTION_SIZE)]); + } + else + { + m_vtable[VTABLE_PREFIX_ENTRIES + index] = func; + } +} + +} // namespace util + +#endif // MAME_LIB_UTIL_DYNAMICCLASS_IPP |