From 524f6158f7f41991287e21864383f9768ea00708 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20W=C3=BCrl?= Date: Thu, 31 Oct 2024 14:10:58 +0100 Subject: [PATCH] Use mpy map and heap where possible, use root pointer This replaces the use of std::map by micropython heap based maps. As a result, we need a root pointer for the root map such that the mpy garbage collector does not collect the micropython-wrap objects. This root pointer needs to be declared once outside of micropython-wrap such that it is generated by the micropython generators. All objects referenced by the map should also be on the micropython-heap. In this way, the garbage collector can properly track lifetimes. This is the first step into allowing us to use micropython-wrap on mcus, and also allows us to reset the micropython-wrap state by resetting the global state map. --- README.md | 12 +++ classwrapper.h | 169 ++++++++++++++++++++++++++++++------------ detail/functioncall.h | 14 ++++ detail/index.h | 4 +- detail/micropython.h | 43 ++++++++--- detail/mpmap.h | 100 +++++++++++++++++++++++++ functionwrapper.h | 18 ++++- tests/cmodule.c | 3 + util.h | 4 +- 9 files changed, 304 insertions(+), 63 deletions(-) create mode 100644 detail/mpmap.h diff --git a/README.md b/README.md index a3fe7d1..ce3d4ed 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,18 @@ Consequently when running the [python test code](tests/py) using the standard Mi Just to get an idea here is a short sample of C++ code registration; code achieving the same using just the MicroPython API is not shown here but would likely be around 50 lines: +The micropython-wrap type table needs to be registered as root pointer. Safe this as a `.c` file and add it to micropythons +`MICROPY_SOURCE_QSTR` sources in cmake or `SRC_QSTR` in make. This is to make sure it is added to the global states root pointers +and thus not collected by micropythons garbage-collector. + +```c +#include "py/obj.h" + +//micropythons type table needs to be registered as root pointer to avoid garbage collection +//the user needs to do this at least once to be able to compile micropython-wrap +MP_REGISTER_ROOT_POINTER(mp_map_t* micropython_wrap_types_map_table); +``` + ```c++ #include diff --git a/classwrapper.h b/classwrapper.h index d341c7f..84186e0 100644 --- a/classwrapper.h +++ b/classwrapper.h @@ -4,9 +4,11 @@ #include "detail/callreturn.h" #include "detail/functioncall.h" #include "detail/index.h" +#include "detail/micropython.h" +#include "detail/mpmap.h" #include "detail/util.h" #include -#include +#include #if UPYWRAP_SHAREDPTROBJ #include #endif @@ -61,7 +63,7 @@ namespace upywrap //Just to make it clear what the intent is. enum class ConstructorOptions { - RegisterInStaticPyObjectStore + RegisterInGlobalTypeMap }; //Initialize the type and store it in the module's globals dict so it's accessible as .. @@ -77,26 +79,42 @@ namespace upywrap ClassWrapper( const char* name, mp_obj_dict_t* dict, decltype( mp_obj_type_t::flags ) flags = 0 ) : ClassWrapper( name, flags ) { - mp_obj_dict_store( dict, new_qstr( name ), &type ); - mp_obj_dict_store( dict, new_qstr( ( std::string( name ) + "_locals" ).data() ), MP_OBJ_FROM_PTR( MP_OBJ_TYPE_GET_SLOT( &type, locals_dict ) ) ); + mp_obj_dict_store( dict, new_qstr( name ), type_ptr ); + // locals_dict saving no longer required; the type is correctly placed on the heap now. } - //Initialize the type, storing the locals in StaticPyObjectStore to prevent GC collection. + //Initialize the type, storing the locals in the root pointer types map to prevent GC collection. ClassWrapper( const char* name, ConstructorOptions, decltype( mp_obj_type_t::flags ) flags = 0 ) : ClassWrapper( name, flags ) { - StaticPyObjectStore::Store( MP_OBJ_FROM_PTR( MP_OBJ_TYPE_GET_SLOT( &type, locals_dict ) ) ); + // Changed from upstream: To be able to properly reset this entry, we do not want to store the locals_dict in the static py object store, + // but rather in the root pointer type map so it's not visible to the mpy runtime. + auto* type_key = ClassWrapper::ClassHash(); + init_mpy_wrap_global_map(); + MPyMapView types_map { MP_STATE_VM(micropython_wrap_types_map_table) }; + auto* mpy_type_map = types_map[type_key]; + mp_map_elem_t* elem = mp_map_lookup(mpy_type_map, MP_OBJ_NEW_QSTR(qstr_from_str("locals_dict")), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + elem->value = MP_OBJ_FROM_PTR( MP_OBJ_TYPE_GET_SLOT( type_ptr, locals_dict ) ); + } + + /** + * @brief Replacement for typeid without RTTI. This uniquely identifies the classwrapper type within an executable. + * @warning The hashes will change when recompiling or on other targets. Do not use cross-device or cross-executable. + */ + static void* ClassHash() { + static int static_hash_marker = 0; // The address of this object is the unique hash of this classwrapper. + return &static_hash_marker; } static const mp_obj_type_t& Type() { - return *((const mp_obj_type_t*) &type); + return *((const mp_obj_type_t*) type_ptr); } template< class A > void StoreClassVariable( const char* name, const A& value ) { - mp_obj_dict_store( MP_OBJ_FROM_PTR( MP_OBJ_TYPE_GET_SLOT( &type, locals_dict ) ), new_qstr( name ), ToPy( value ) ); + mp_obj_dict_store( MP_OBJ_FROM_PTR( MP_OBJ_TYPE_GET_SLOT( type_ptr, locals_dict ) ), new_qstr( name ), ToPy( value ) ); } template< index_type name, class Ret, class... A > @@ -297,7 +315,8 @@ namespace upywrap { if( own ) { - return AsPyObj( native_obj_t( p ) ); + // Changed from upstream: owned objects must be placed on the micropython heap. We need to call the destructor ourselfes + return AsPyObj( native_obj_t( p, [](T* del_p) { del_p->~T(); m_free(del_p); } ) ); } return AsPyObj( native_obj_t( p, NoDelete ) ); } @@ -313,7 +332,7 @@ namespace upywrap assert( p ); CheckTypeIsRegistered(); auto o = (this_type*) m_malloc_with_finaliser( sizeof( this_type ) ); - o->base.type = (const mp_obj_type_t*) & type; + o->base.type = (const mp_obj_type_t*) type_ptr; o->cookie = defCookie; #if UPYWRAP_FULLTYPECHECK o->typeId = &typeid( T ); @@ -329,7 +348,7 @@ namespace upywrap static ClassWrapper< T >* AsNativeObjCheckedImpl( mp_obj_t arg ) { auto native = (this_type*) MP_OBJ_TO_PTR( arg ); - if( !mp_obj_is_exact_type( arg, (const mp_obj_type_t*) &type ) ) + if( !mp_obj_is_exact_type( arg, (const mp_obj_type_t*) type_ptr ) ) { //If whatever gets passed in doesn't remotely look like an object bail out. //Otherwise it's possible we're being passed an arbitrary 'opaque' ClassWrapper (so the cookie mathches) @@ -389,7 +408,7 @@ namespace upywrap } } CheckTypeIsRegistered(); //since we want to access type.name - RaiseTypeException( arg, qstr_str( type.name ) ); + RaiseTypeException( arg, qstr_str( type_ptr->name ) ); #if !defined( _MSC_VER ) || defined( _DEBUG ) return nullptr; #endif @@ -418,6 +437,46 @@ namespace upywrap #endif private: + /** + * @brief Initialize all maps used by micropython-wrap to store functions for this type. + * + * Initializes the functionPointers, setters and getters map and stores their views + * In the static variables used by micropython-wrap (for ease of rebasing and changing little). + * All maps are stored on the micropython heap and referenced by the global types map. + * Must be done only once. (And again when resetting the micropython state) + */ + static bool init_classwrapper_type_dict_and_function_maps() { + auto* type_key = ClassHash(); + bool is_initialized = true; + init_mpy_wrap_global_map(); + MPyMapView types_map { MP_STATE_VM(micropython_wrap_types_map_table) }; + if (!types_map.contains(type_key)) { + is_initialized = false; + } + if( !is_initialized ) + { + mp_map_t* type_map_ptr = m_new0(mp_map_t, 1); + types_map[type_key] = type_map_ptr; + MPyMapView type_map { type_map_ptr }; + + mp_map_t* fnct_pointers_map = m_new0(mp_map_t, 1); + type_map[qstr_from_str("functionPointers")] = fnct_pointers_map; + functionPointers = function_ptrs { fnct_pointers_map }; + + mp_map_t* setters_map = m_new0(mp_map_t, 1); + type_map[qstr_from_str("setters")] = setters_map; + setters = store_attr_map { setters_map }; + + mp_map_t* getters_map = m_new0(mp_map_t, 1); + type_map[qstr_from_str("getters")] = getters_map; + getters = load_attr_map { getters_map }; + + type_ptr = m_new0( mp_obj_full_type_t, 1 ); + type_map[qstr_from_str("full_type")] = (mp_map_t*)type_ptr; + } + return is_initialized; + } + ClassWrapper( const char* name, decltype( mp_obj_type_t::flags ) flags ) { //Initialize the static parts; note this will set the type's name, once, so while it's @@ -426,14 +485,14 @@ namespace upywrap //making it possible to store the same type with different names in another dict for instance. //Explicitly disable calling this with different flags though since that yields //a type which is actually different. - static bool init = false; - if( !init ) + bool is_inited = ClassWrapper::init_classwrapper_type_dict_and_function_maps(); + + if( !is_inited ) { OneTimeInit( name ); - type.flags = flags; - init = true; + type_ptr->flags = flags; } - else if( type.flags != flags ) + else if( type_ptr->flags != flags ) { RaiseTypeException( "ClassWrapper's type flags can only be set once" ); } @@ -476,13 +535,27 @@ namespace upywrap //native attribute store interface struct NativeSetterCallBase { - virtual void Call( mp_obj_t self_in, mp_obj_t value ) = 0; + virtual mp_obj_t Call( mp_obj_t self_in, mp_obj_t value ) = 0; + + /** + * @brief micropython-wrap allocates these objects using new. We want them on the micropython-heap + */ + static void* operator new(size_t size) { + return m_malloc(size); + } }; //native attribute load interface struct NativeGetterCallBase { virtual mp_obj_t Call( mp_obj_t self_in ) = 0; + + /** + * @brief micropython-wrap allocates these objects using new. We want them on the micropython-heap + */ + static void* operator new(size_t size) { + return m_malloc(size); + } }; template< class Map > @@ -502,14 +575,14 @@ namespace upywrap const auto attrValue = FindAttrMaybe( map, attr ); if( !attrValue ) { - RaiseAttributeException( type.name, attr ); + RaiseAttributeException( type_ptr->name, attr ); } return attrValue; } static mp_map_elem_t* LookupLocal( qstr attr ) { - auto locals_map = &( (mp_obj_dict_t*) MP_OBJ_TYPE_GET_SLOT( &type, locals_dict ) )->map; + auto locals_map = &( (mp_obj_dict_t*) MP_OBJ_TYPE_GET_SLOT( type_ptr, locals_dict ) )->map; return mp_map_lookup( locals_map, new_qstr( attr ), MP_MAP_LOOKUP ); } @@ -619,14 +692,16 @@ namespace upywrap void OneTimeInit( const char* name ) { + mp_obj_full_type_t& type = *type_ptr; + type.base.type = &mp_type_type; type.name = static_cast< decltype( type.name ) >( qstr_from_str( name ) ); //The ones we use here (so make sure the other locations stay in sync!). - MP_OBJ_TYPE_SET_SLOT( &type, make_new, nullptr, 0 ); + //MP_OBJ_TYPE_SET_SLOT( &type, make_new, nullptr, 0 ); // Do not set without using, it will actually try to call this nullptr MP_OBJ_TYPE_SET_SLOT( &type, locals_dict, mp_obj_new_dict( 0 ), 1 ); MP_OBJ_TYPE_SET_SLOT( &type, attr, attr, 2 ); MP_OBJ_TYPE_SET_SLOT( &type, binary_op, binary_op, 3 ); - MP_OBJ_TYPE_SET_SLOT( &type, call, nullptr, 5 ); + //MP_OBJ_TYPE_SET_SLOT( &type, call, nullptr, 5 ); // Do not set without using, it will actually try to call this nullptr MP_OBJ_TYPE_SET_SLOT( &type, print, instance_print, 6 ); //The ones we don't use, for completeness. type.slot_index_unary_op = 0; @@ -644,7 +719,7 @@ namespace upywrap static void CheckTypeIsRegistered() { - if( type.base.type == nullptr ) + if( type_ptr->base.type == nullptr ) { #if UPYWRAP_HAS_TYPEID std::string errorMessage( std::string( "Native type " ) + typeid( T ).name() + " has not been registered" ); @@ -657,7 +732,7 @@ namespace upywrap void AddFunctionToTable( const qstr name, mp_obj_t fun ) { - mp_obj_dict_store( MP_OBJ_TYPE_GET_SLOT( &type, locals_dict ), new_qstr( name ), fun ); + mp_obj_dict_store( MP_OBJ_TYPE_GET_SLOT( type_ptr, locals_dict ), new_qstr( name ), fun ); } void AddFunctionToTable( const char* name, mp_obj_t fun ) @@ -676,7 +751,7 @@ namespace upywrap AddFunctionToTable( name(), call_type::CreateUPyFunction( *callerObject ) ); if( std::string( name() ) == "__call__" ) { - MP_OBJ_TYPE_SET_SLOT( &type, call, instance_call, 5 ); + MP_OBJ_TYPE_SET_SLOT( type_ptr, call, instance_call, 5 ); } } @@ -705,7 +780,7 @@ namespace upywrap auto caller = call_type::CreateCaller( f ); caller->arguments = std::move( arguments ); functionPointers[ (void*) name ] = caller; - MP_OBJ_TYPE_SET_SLOT( &type, make_new, call_type::MakeNew, 0 ); + MP_OBJ_TYPE_SET_SLOT( type_ptr, make_new, call_type::MakeNew, 0 ); } template< index_type name, class Fun, class Ret, class... A > @@ -852,7 +927,7 @@ namespace upywrap Arguments::parsed_obj_t parsedArgs{}; f->arguments.Parse( n_args, n_kw, args, parsedArgs ); UPYWRAP_TRY - return AsPyObj( native_obj_t( Apply( f, parsedArgs.data(), make_index_sequence< sizeof...( A ) >() ) ) ); + return AsPyObj( Apply( f, parsedArgs.data(), make_index_sequence< sizeof...( A ) >() ) ); // Changed from upstream: do not auto-convert to shared_ptr UPYWRAP_CATCH } else if( n_args != sizeof...( A ) || n_kw ) @@ -860,7 +935,7 @@ namespace upywrap RaiseTypeException( ( std::string( "Wrong number of arguments in definition of " ) + index() ).data() ); } UPYWRAP_TRY - return AsPyObj( native_obj_t( Apply( f, args, make_index_sequence< sizeof...( A ) >() ) ) ); + return AsPyObj( Apply( f, args, make_index_sequence< sizeof...( A ) >() ) ); // Changed from upstream: do not auto-convert to shared_ptr UPYWRAP_CATCH } @@ -914,14 +989,14 @@ namespace upywrap }; typedef ClassWrapper< T > this_type; - using store_attr_map = std::map< qstr, NativeSetterCallBase* >; - using load_attr_map = std::map< qstr, NativeGetterCallBase* >; + using store_attr_map = MPyMapView; + using load_attr_map = MPyMapView; mp_obj_base_t base; //must always be the first member! std::int64_t cookie; //we'll use this to check if a pointer really points to a ClassWrapper const std::type_info* typeId; //and this will be used to check if types aren't being mixed native_obj_t obj; - static mp_obj_full_type_t type; + static mp_obj_full_type_t* type_ptr; static function_ptrs functionPointers; static store_attr_map setters; static load_attr_map getters; @@ -929,21 +1004,16 @@ namespace upywrap }; template< class T > - mp_obj_full_type_t ClassWrapper< T >::type = -#ifdef __GNUC__ - { { nullptr } }; //GCC bug 53119 -#else - { nullptr }; -#endif + mp_obj_full_type_t* ClassWrapper< T >::type_ptr = nullptr; template< class T > - function_ptrs ClassWrapper< T >::functionPointers; + function_ptrs ClassWrapper< T >::functionPointers { nullptr }; template< class T > - typename ClassWrapper< T >::store_attr_map ClassWrapper< T >::setters; + typename ClassWrapper< T >::store_attr_map ClassWrapper< T >::setters { nullptr }; template< class T > - typename ClassWrapper< T >::load_attr_map ClassWrapper< T >::getters; + typename ClassWrapper< T >::load_attr_map ClassWrapper< T >::getters { nullptr }; template< class T > const std::int64_t ClassWrapper< T >::defCookie = 0x12345678908765; @@ -1030,10 +1100,17 @@ namespace upywrap template< class T > struct ClassToPyObj { - static mp_obj_t Convert( T ) + static mp_obj_t Convert( T obj ) { +#if UPYWRAP_SHAREDPTROBJ + T* raw = (T*)m_malloc(sizeof(T)); + new (raw) T(obj); + std::shared_ptr ptr {raw, [](T* p) { p->~T(); m_free(p); }}; // Copy, own, destruct and free + return ClassToPyObj>::Convert(ptr); +#else static_assert( False< T >::value, "Conversion from value to ClassWrapper is not allowed, pass a reference or shared_ptr instead" ); return mp_const_none; +#endif } }; @@ -1111,14 +1188,12 @@ namespace upywrap static void InitWrapper() { - //Note: registered once, stays forever. Alternative would be to have a finalizer - //but then when a new function is needed it again has to go through initialization. - static wrapper_t reg( typeid( funct_t ).name(), wrapper_t::ConstructorOptions::RegisterInStaticPyObjectStore ); - static bool init = false; - if( !init ) + // Changed from upstream: We check init status using the root pointer types map, and register our callable type if it's not there + bool was_initialized = wrapper_t::init_classwrapper_type_dict_and_function_maps(); + if( !was_initialized ) { + wrapper_t reg( typeid( funct_t ).name(), wrapper_t::ConstructorOptions::RegisterInGlobalTypeMap ); reg.template Def< special_methods::__call__ >( &funct_t::operator () ); - init = true; } } }; diff --git a/detail/functioncall.h b/detail/functioncall.h index d220a00..3263278 100644 --- a/detail/functioncall.h +++ b/detail/functioncall.h @@ -198,6 +198,13 @@ namespace upywrap //Optional/keyword arguments. Arguments arguments; + + /** + * @brief micropython-wrap allocates these objects using new. We want them on the micropython-heap + */ + static void* operator new(size_t size) { + return m_malloc(size); + } }; //Normal member function call @@ -264,6 +271,13 @@ namespace upywrap //Optional/keyword arguments. Arguments arguments; + + /** + * @brief micropython-wrap allocates these objects using new. We want them on the micropython-heap + */ + static void* operator new(size_t size) { + return m_malloc(size); + } }; } diff --git a/detail/index.h b/detail/index.h index 36e76ba..b01d981 100644 --- a/detail/index.h +++ b/detail/index.h @@ -1,7 +1,7 @@ #ifndef MICROPYTHON_WRAP_DETAIL_INDEX_H #define MICROPYTHON_WRAP_DETAIL_INDEX_H -#include +#include "detail/mpmap.h" namespace upywrap { @@ -17,7 +17,7 @@ namespace upywrap #define func_name_def( n ) static const char* n(){ return #n; } //Map type used to store functions - typedef std::map< void*, void* > function_ptrs; + typedef MPyMapView function_ptrs; } #endif //#ifndef MICROPYTHON_WRAP_DETAIL_INDEX_H diff --git a/detail/micropython.h b/detail/micropython.h index 73f87f4..3fafb2f 100644 --- a/detail/micropython.h +++ b/detail/micropython.h @@ -1,6 +1,7 @@ #ifndef MICROPYTHON_WRAP_DETAIL_MICROPYTHON_H #define MICROPYTHON_WRAP_DETAIL_MICROPYTHON_H +#include "detail/mpmap.h" #include "micropythonc.h" #include #include @@ -11,6 +12,20 @@ namespace upywrap { + /** + * @brief This is the global map in the micropython state which is used to store the class types and function pointers. + * + * All objects that micropython-wrap allocates are referenced by this map, or by maps in this map. + * It needs to be initialized only once, but can be reset by assigning a new empty map to it. + */ + inline void init_mpy_wrap_global_map() { + static bool init = false; + if (!init) { + init = true; + MP_STATE_VM(micropython_wrap_types_map_table) = m_new0(mp_map_t, 1); + } + } + inline mp_obj_t new_qstr( qstr what ) { return MP_OBJ_NEW_QSTR( what ); @@ -367,6 +382,12 @@ namespace upywrap return *List(); } + static void ResetBackEnd() + { + // Other function like InitBackEnd will check if it's already initialized + *List() = nullptr; + } + static bool Initialized() { return !!*List(); @@ -479,21 +500,21 @@ namespace upywrap * so that if this dict is a root pointer or similar, anything in the list will not be sweeped by the GC. * Doesn't do anything if called already, so all translation units in the binary use the same instance. */ - inline void InitializePyObjectStore( mp_obj_dict_t& dict ) + inline void InitializePyObjectStore( ) { - if( !StaticPyObjectStore::Initialized() ) + // Changed from upstream: use map in root pointers instead of any random dict. This actually hides it from the mpy runtime. + upywrap::init_mpy_wrap_global_map(); + MPyMapView mpy_wrap_map { MP_STATE_VM(micropython_wrap_types_map_table) }; + + qstr static_py_obj_store_key = qstr_from_str( "_StaticPyObjectStore" ); + if( !mpy_wrap_map.contains( static_py_obj_store_key ) ) { - mp_obj_dict_store( &dict, new_qstr( "_StaticPyObjectStore" ), StaticPyObjectStore::InitBackEnd() ); + // If we can't find it in the map, the mpy state was probably reset. Reinit from scratch. + StaticPyObjectStore::ResetBackEnd(); + mpy_wrap_map[static_py_obj_store_key] = StaticPyObjectStore::InitBackEnd(); } } - /** - * Initialize PinPyObj with the module's globals dict. - */ - inline void InitializePyObjectStore( mp_obj_module_t& mod ) - { - InitializePyObjectStore( *mod.globals ); - } /** * Wrapper around mp_obj_new_module. @@ -503,7 +524,7 @@ namespace upywrap { const qstr qname = qstr_from_str( name ); auto mod = (mp_obj_module_t*) mp_obj_new_module( qname ); - InitializePyObjectStore( *mod ); + InitializePyObjectStore( ); return mod; } diff --git a/detail/mpmap.h b/detail/mpmap.h new file mode 100644 index 0000000..064a133 --- /dev/null +++ b/detail/mpmap.h @@ -0,0 +1,100 @@ +#pragma once + +#include "micropythonc.h" +#include +#include +#include +#include + +namespace upywrap { +/** + * @brief A wrapper around the MicroPython map type. + * + * This class provides a convenient way to interact with the MicroPython map type. + * Objects of this type do not own the map, and are not responsible for its lifetime. + * The observer functions and Iterator are crafted specifically to support the micropython-wrap library + * with as little changes as possible. As such, `void *` is not treated as mp_obj_t, but rather as an integer type when used as key. + */ +template +requires std::is_pointer_v +class MPyMapView { +public: + using mapped_type = Value; + explicit MPyMapView(mp_map_t* map_ptr) : map_ { map_ptr } {} + + Value& operator[](const qstr key) requires std::same_as { + mp_map_elem_t* elem = mp_map_lookup(map_, MP_OBJ_NEW_QSTR(key), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + return reinterpret_cast(elem->value); + } + + bool contains(const qstr key) const requires std::same_as { + mp_map_elem_t* elem = mp_map_lookup(map_, MP_OBJ_NEW_QSTR(key), MP_MAP_LOOKUP); + return elem != nullptr; + } + + /** + * @brief Converts a void pointer to an mp_obj_t. + * @warning While an mp_obj_t is technically a void*, this will rather convert the void* to an integer type. + */ + mp_obj_t uint_obj_from_void_ptr(const void* ptr) const { + auto key_uintptr = (uintptr_t)ptr; + auto key_mpuint = (mp_uint_t)key_uintptr; + mp_obj_t key_obj = mp_obj_new_int_from_uint(key_mpuint); + return key_obj; + } + + /** + * @brief Access the map with a void pointer key. Micropytho-wrap uses this to use the name() functions (index_type) as keys. + * + * We convert the void* here to an mp_uint_t, which is then used as the key in the micropython map. + */ + Value& operator[](const void* key) requires std::same_as { + mp_map_elem_t* elem = mp_map_lookup(map_, uint_obj_from_void_ptr(key), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); + return reinterpret_cast(elem->value); + } + + /** + * @brief Access the map with a void pointer key. Micropytho-wrap uses this to use the name() functions (index_type) as keys. + * + * We convert the void* here to an mp_uint_t, which is then used as the key in the micropython map. + */ + bool contains(const void* key) const requires std::same_as { + mp_map_elem_t* elem = mp_map_lookup(map_, uint_obj_from_void_ptr(key), MP_MAP_LOOKUP); + return elem != nullptr; + } + + /** + * @brief Minimal iterator for the MPyMapView. + * + * Micropython-wrap uses only find(), end() and ->, so we do not need to implement incrementing or dereferencing. + */ + class FakeIterator { + public: + FakeIterator() = default; + FakeIterator(Key key, Value val) : val_{ { key, val } } {} + + std::pair* operator->() { + return &*val_; + } + + bool operator==(const FakeIterator& other) const = default; + + private: + std::optional> val_ {}; + }; + + FakeIterator find(const Key& key) { + if (contains(key)) { + return FakeIterator {key, (*this)[key] }; + } + return {}; + } + + FakeIterator end() { + return {}; + } + +private: + mp_map_t* map_; +}; +} // namespace upywrap \ No newline at end of file diff --git a/functionwrapper.h b/functionwrapper.h index aef79d1..8513eaa 100644 --- a/functionwrapper.h +++ b/functionwrapper.h @@ -2,6 +2,8 @@ #define MICROPYTHON_WRAP_FUNCTIONWRAPPER #include "classwrapper.h" +#include "detail/index.h" +#include "detail/mpmap.h" namespace upywrap { @@ -43,9 +45,23 @@ namespace upywrap { } + static void init_function_pointers_map() { + upywrap::init_mpy_wrap_global_map(); + MPyMapView mpy_wrap_map { MP_STATE_VM(micropython_wrap_types_map_table) }; + + qstr fnct_ptrs_qstr = qstr_from_str( "__function_ptrs__" ); + if( !mpy_wrap_map.contains( fnct_ptrs_qstr ) ) + { + mp_map_t* new_function_ptrs = m_new0(mp_map_t, 1); + mpy_wrap_map[fnct_ptrs_qstr] = new_function_ptrs; + FunctionWrapper::functionPointers = MPyMapView( new_function_ptrs ); + } + } + template< index_type name, class Ret, class... A > void Def( Ret( *f ) ( A... ), Arguments arguments, typename SelectRetvalConverter< Ret >::type conv = nullptr ) { + init_function_pointers_map(); typedef NativeCall< name, Ret, A... > call_type; auto callerObject = call_type::CreateCaller( f ); @@ -126,7 +142,7 @@ namespace upywrap //we want a header only library but that yields multiply defined symbols when including this file more then once //so as a simple hack: define MMICROPYTHON_WRAP_FUNCTIONWRAPPER_DEFINED in all but one include location #ifndef MICROPYTHON_WRAP_FUNCTIONWRAPPER_DEFINED - function_ptrs FunctionWrapper::functionPointers; + function_ptrs FunctionWrapper::functionPointers { nullptr }; #endif } diff --git a/tests/cmodule.c b/tests/cmodule.c index 7da7f19..0abfc7e 100644 --- a/tests/cmodule.c +++ b/tests/cmodule.c @@ -4,3 +4,6 @@ extern void doinit_upywraptest(mp_obj_dict_t *); UPYWRAP_DEFINE_INIT_MODULE(upywraptest, doinit_upywraptest); MP_REGISTER_MODULE(MP_QSTR_upywraptest, upywraptest_module); MP_REGISTER_ROOT_POINTER(const mp_map_elem_t* upywraptest_module_globals_table); + +// Needs to be done once: Add global type map for micropython-wrap to the micropython state as root pointer +MP_REGISTER_ROOT_POINTER(mp_map_t* micropython_wrap_types_map_table); diff --git a/util.h b/util.h index d4c9544..d623524 100644 --- a/util.h +++ b/util.h @@ -109,9 +109,9 @@ namespace upywrap * Add an object to the given module's global dict. */ template< class T > - void StoreGlobal( mp_obj_module_t* mod, const char* name, const T& obj ) + void StoreGlobal( mp_obj_dict_t* mod, const char* name, const T& obj ) { - mp_obj_dict_store( MP_OBJ_FROM_PTR( mod->globals ), new_qstr( name ), ToPy( obj ) ); + mp_obj_dict_store( MP_OBJ_FROM_PTR( mod ), new_qstr( name ), ToPy( obj ) ); } }