From f4b0527c7d11b3a95d44b880cbdd4aae2d58ca8d Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Mon, 15 Apr 2024 14:39:05 +0200 Subject: C++ migration: deep userdata API rework. bye bye idfunc, hello DeepFactory --- src/deep.cpp | 149 ++++++++++++----------------- src/deep.h | 62 ++++++++---- src/keeper.cpp | 2 + src/keeper.h | 2 +- src/lanes.cpp | 6 +- src/linda.cpp | 292 ++++++++++++++++++++++++++++----------------------------- 6 files changed, 249 insertions(+), 264 deletions(-) (limited to 'src') diff --git a/src/deep.cpp b/src/deep.cpp index d0b8123..780c86e 100644 --- a/src/deep.cpp +++ b/src/deep.cpp @@ -47,11 +47,11 @@ THE SOFTWARE. /*---=== Deep userdata ===---*/ /* -* 'registry[REGKEY]' is a two-way lookup table for 'idfunc's and those type's +* 'registry[REGKEY]' is a two-way lookup table for 'factory's and those type's * metatables: * -* metatable -> idfunc -* idfunc -> metatable +* metatable -> factory +* factory -> metatable */ // crc64/we of string "DEEP_LOOKUP_KEY" generated at http://www.nitrxgen.net/hashgen/ static constexpr UniqueKey DEEP_LOOKUP_KEY{ 0x9fb9b4f3f633d83dull }; @@ -84,8 +84,8 @@ static void set_deep_lookup(lua_State* L) // ################################################################################################ /* -* Pops the key (metatable or idfunc) off the stack, and replaces with the -* deep lookup value (idfunc/metatable/nil). +* Pops the key (metatable or factory) off the stack, and replaces with the +* deep lookup value (factory/metatable/nil). */ static void get_deep_lookup(lua_State* L) { @@ -104,21 +104,21 @@ static void get_deep_lookup(lua_State* L) // ################################################################################################ /* -* Return the registered ID function for 'index' (deep userdata proxy), +* Return the registered factory for 'index' (deep userdata proxy), * or nullptr if 'index' is not a deep userdata proxy. */ -[[nodiscard]] static inline luaG_IdFunction get_idfunc(lua_State* L, int index, LookupMode mode_) +[[nodiscard]] static inline DeepFactory* get_factory(lua_State* L, int index, LookupMode mode_) { // when looking inside a keeper, we are 100% sure the object is a deep userdata if (mode_ == LookupMode::FromKeeper) { - DeepPrelude** const proxy{ lua_tofulluserdata(L, index) }; - // we can (and must) cast and fetch the internally stored idfunc - return (*proxy)->idfunc; + DeepPrelude* const proxy{ *lua_tofulluserdata(L, index) }; + // we can (and must) cast and fetch the internally stored factory + return &proxy->m_factory; } else { - // essentially we are making sure that the metatable of the object we want to copy is stored in our metatable/idfunc database + // essentially we are making sure that the metatable of the object we want to copy is stored in our metatable/factory database // it is the only way to ensure that the userdata is indeed a deep userdata! // of course, we could just trust the caller, but we won't STACK_GROW( L, 1); @@ -129,10 +129,10 @@ static void get_deep_lookup(lua_State* L) return nullptr; // no metatable: can't be a deep userdata object! } - // replace metatable with the idfunc pointer, if it is actually a deep userdata - get_deep_lookup( L); // deep ... idfunc|nil + // replace metatable with the factory pointer, if it is actually a deep userdata + get_deep_lookup( L); // deep ... factory|nil - luaG_IdFunction const ret{ *lua_tolightuserdata(L, -1) }; // nullptr if not a userdata + DeepFactory* const ret{ lua_tolightuserdata(L, -1) }; // nullptr if not a userdata lua_pop( L, 1); STACK_CHECK( L, 0); return ret; @@ -141,14 +141,10 @@ static void get_deep_lookup(lua_State* L) // ################################################################################################ -void free_deep_prelude(lua_State* L, DeepPrelude* prelude_) +void DeepFactory::DeleteDeepObject(lua_State* L, DeepPrelude* o_) { - ASSERT_L(prelude_->idfunc); STACK_CHECK_START_REL(L, 0); - // Call 'idfunc( "delete", deep_ptr )' to make deep cleanup - lua_pushlightuserdata( L, prelude_); - prelude_->idfunc( L, DeepOp::Delete); - lua_pop(L, 1); + o_->m_factory.deleteDeepObjectInternal(L, o_); STACK_CHECK(L, 0); } @@ -162,8 +158,8 @@ void free_deep_prelude(lua_State* L, DeepPrelude* prelude_) */ [[nodiscard]] static int deep_userdata_gc(lua_State* L) { - DeepPrelude** const proxy{ lua_tofulluserdata(L, 1) }; - DeepPrelude* p = *proxy; + DeepPrelude* const* const proxy{ lua_tofulluserdata(L, 1) }; + DeepPrelude* const p{ *proxy }; // can work without a universe if creating a deep userdata from some external C module when Lanes isn't loaded // in that case, we are not multithreaded and locking isn't necessary anyway @@ -178,17 +174,9 @@ void free_deep_prelude(lua_State* L, DeepPrelude* prelude_) lua_insert( L, -2); // __gc self lua_call( L, 1, 0); // } - // 'idfunc' expects a clean stack to work on - lua_settop( L, 0); - free_deep_prelude( L, p); - - // top was set to 0, then userdata was pushed. "delete" might want to pop the userdata (we don't care), but should not push anything! - if ( lua_gettop( L) > 1) - { - return luaL_error( L, "Bad idfunc(DeepOp::Delete): should not push anything"); - } + // we don't really know what remains on the stack at that point (depending on us finding a __gc or not), but we don't care + DeepFactory::DeleteDeepObject(L, p); } - *proxy = nullptr; // make sure we don't use it any more, just in case return 0; } @@ -196,14 +184,14 @@ void free_deep_prelude(lua_State* L, DeepPrelude* prelude_) /* * Push a proxy userdata on the stack. - * returns nullptr if ok, else some error string related to bad idfunc behavior or module require problem + * returns nullptr if ok, else some error string related to bad factory behavior or module require problem * (error cannot happen with mode_ == LookupMode::ToKeeper) * - * Initializes necessary structures if it's the first time 'idfunc' is being + * Initializes necessary structures if it's the first time 'factory' is being * used in this Lua state (metatable, registring it). Otherwise, increments the * reference count. */ -char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode mode_) +char const* DeepFactory::PushDeepProxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode mode_) { // Check if a proxy already exists push_registry_subtable_mode( L, DEEP_PROXY_CACHE_KEY, "v"); // DPC @@ -228,24 +216,25 @@ char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode m *proxy = prelude; prelude->m_refcount.fetch_add(1, std::memory_order_relaxed); // one more proxy pointing to this deep data - // Get/create metatable for 'idfunc' (in this state) - lua_pushlightuserdata( L, std::bit_cast(prelude->idfunc)); // DPC proxy idfunc + // Get/create metatable for 'factory' (in this state) + DeepFactory& factory = prelude->m_factory; + lua_pushlightuserdata( L, std::bit_cast(&factory)); // DPC proxy factory get_deep_lookup( L); // DPC proxy metatable? if( lua_isnil( L, -1)) // // No metatable yet. { - char const* modname; int oldtop = lua_gettop( L); // DPC proxy nil lua_pop( L, 1); // DPC proxy // 1 - make one and register it if (mode_ != LookupMode::ToKeeper) { - (void) prelude->idfunc( L, DeepOp::Metatable); // DPC proxy metatable - if( lua_gettop( L) - oldtop != 0 || !lua_istable( L, -1)) + factory.createMetatable(L); // DPC proxy metatable + if (lua_gettop(L) - oldtop != 0 || !lua_istable(L, -1)) { + // factory didn't push exactly 1 value, or the value it pushed is not a table: ERROR! lua_settop( L, oldtop); // DPC proxy X lua_pop( L, 3); // - return "Bad idfunc(eOP_metatable): unexpected pushed value"; + return "Bad DeepFactory::createMetatable overload: unexpected pushed value"; } // if the metatable contains a __gc, we will call it from our own lua_getfield( L, -1, "__gc"); // DPC proxy metatable __gc @@ -271,22 +260,11 @@ char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode m // Memorize for later rounds lua_pushvalue( L, -1); // DPC proxy metatable metatable - lua_pushlightuserdata( L, std::bit_cast(prelude->idfunc)); // DPC proxy metatable metatable idfunc + lua_pushlightuserdata(L, std::bit_cast(&factory)); // DPC proxy metatable metatable factory set_deep_lookup( L); // DPC proxy metatable - // 2 - cause the target state to require the module that exported the idfunc - // this is needed because we must make sure the shared library is still loaded as long as we hold a pointer on the idfunc - { - int oldtop_module = lua_gettop( L); - modname = (char const*) prelude->idfunc( L, DeepOp::Module); // DPC proxy metatable - // make sure the function pushed nothing on the stack! - if( lua_gettop( L) - oldtop_module != 0) - { - lua_pop( L, 3); // - return "Bad idfunc(eOP_module): should not push anything"; - } - } - if (nullptr != modname) // we actually got a module name + // 2 - cause the target state to require the module that exported the factory + if (char const* const modname{ factory.moduleName() }; modname) // we actually got a module name { // L.registry._LOADED exists without having registered the 'package' library. lua_getglobal( L, "require"); // DPC proxy metatable require() @@ -309,7 +287,7 @@ char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode m if( require_result != LUA_OK) { // failed, return the error message - lua_pushfstring( L, "error while requiring '%s' identified by idfunc(eOP_module): ", modname); + lua_pushfstring( L, "error while requiring '%s' identified by DeepFactory::moduleName: ", modname); lua_insert( L, -2); // DPC proxy metatable prefix error lua_concat( L, 2); // DPC proxy metatable error return lua_tostring( L, -1); @@ -323,7 +301,7 @@ char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode m else // no L.registry._LOADED; can this ever happen? { lua_pop( L, 6); // - return "unexpected error while requiring a module identified by idfunc(eOP_module)"; + return "unexpected error while requiring a module identified by DeepFactory::moduleName"; } } else // a module name, but no require() function :-( @@ -334,7 +312,7 @@ char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode m } } STACK_CHECK(L, 2); // DPC proxy metatable - ASSERT_L(lua_type(L, -2) == LUA_TUSERDATA); + ASSERT_L(lua_type_as_enum(L, -2) == LuaType::USERDATA); ASSERT_L(lua_istable( L, -1)); lua_setmetatable( L, -2); // DPC proxy @@ -343,7 +321,7 @@ char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode m lua_pushvalue( L, -2); // DPC proxy deep proxy lua_rawset( L, -4); // DPC proxy lua_remove( L, -2); // proxy - ASSERT_L(lua_type(L, -1) == LUA_TUSERDATA); + ASSERT_L(lua_type_as_enum(L, -1) == LuaType::USERDATA); STACK_CHECK(L, 0); return nullptr; } @@ -353,56 +331,47 @@ char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode m /* * Create a deep userdata * -* proxy_ud= deep_userdata( idfunc [, ...] ) -* -* Creates a deep userdata entry of the type defined by 'idfunc'. -* Parameters found on the stack are left as is passed on to the 'idfunc' "new" invocation. -* -* 'idfunc' must fulfill the following features: +* proxy_ud= deep_userdata( [...] ) * -* lightuserdata = idfunc( DeepOp::New [, ...] ) -- creates a new deep data instance -* void = idfunc( DeepOp::Delete, lightuserdata ) -- releases a deep data instance -* tbl = idfunc( DeepOp::Metatable ) -- gives metatable for userdata proxies +* Creates a deep userdata entry of the type defined by the factory. +* Parameters found on the stack are left as is and passed on to DeepFactory::newDeepObjectInternal. * -* Reference counting and true userdata proxying are taken care of for the -* actual data type. +* Reference counting and true userdata proxying are taken care of for the actual data type. * * Types using the deep userdata system (and only those!) can be passed between * separate Lua states via 'luaG_inter_move()'. * -* Returns: 'proxy' userdata for accessing the deep data via 'luaG_todeep()' +* Returns: 'proxy' userdata for accessing the deep data via 'DeepFactory::toDeep()' */ -int luaG_newdeepuserdata(Dest L, luaG_IdFunction idfunc, int nuv_) +int DeepFactory::pushDeepUserdata(Dest L, int nuv_) const { STACK_GROW( L, 1); STACK_CHECK_START_REL(L, 0); int const oldtop{ lua_gettop(L) }; - DeepPrelude* const prelude{ static_cast(idfunc(L, DeepOp::New)) }; + DeepPrelude* const prelude{ newDeepObjectInternal(L) }; if (prelude == nullptr) { - return luaL_error( L, "idfunc(DeepOp::New) failed to create deep userdata (out of memory)"); + return luaL_error( L, "DeepFactory::newDeepObjectInternal failed to create deep userdata (out of memory)"); } - if( prelude->magic != DEEP_VERSION) + if( prelude->m_magic != DEEP_VERSION) { // just in case, don't leak the newly allocated deep userdata object - lua_pushlightuserdata( L, prelude); - idfunc( L, DeepOp::Delete); - return luaL_error( L, "Bad idfunc(DeepOp::New): DEEP_VERSION is incorrect, rebuild your implementation with the latest deep implementation"); + deleteDeepObjectInternal(L, prelude); + return luaL_error( L, "Bad Deep Factory: DEEP_VERSION is incorrect, rebuild your implementation with the latest deep implementation"); } - ASSERT_L(prelude->m_refcount.load(std::memory_order_relaxed) == 0); // 'push_deep_proxy' will lift it to 1 - prelude->idfunc = idfunc; + ASSERT_L(prelude->m_refcount.load(std::memory_order_relaxed) == 0); // 'DeepFactory::PushDeepProxy' will lift it to 1 + ASSERT_L(&prelude->m_factory == this); if( lua_gettop( L) - oldtop != 0) { // just in case, don't leak the newly allocated deep userdata object - lua_pushlightuserdata( L, prelude); - idfunc( L, DeepOp::Delete); - return luaL_error( L, "Bad idfunc(DeepOp::New): should not push anything on the stack"); + deleteDeepObjectInternal(L, prelude); + return luaL_error(L, "Bad DeepFactory::newDeepObjectInternal overload: should not push anything on the stack"); } - char const* const errmsg{ push_deep_proxy(L, prelude, nuv_, LookupMode::LaneBody) }; // proxy + char const* const errmsg{ DeepFactory::PushDeepProxy(L, prelude, nuv_, LookupMode::LaneBody) }; // proxy if (errmsg != nullptr) { return luaL_error( L, errmsg); @@ -419,11 +388,11 @@ int luaG_newdeepuserdata(Dest L, luaG_IdFunction idfunc, int nuv_) * Reference count is not changed, and access to the deep userdata is not * serialized. It is the module's responsibility to prevent conflicting usage. */ -DeepPrelude* luaG_todeep(lua_State* L, luaG_IdFunction idfunc, int index) +DeepPrelude* DeepFactory::toDeep(lua_State* L, int index) const { STACK_CHECK_START_REL(L, 0); - // ensure it is actually a deep userdata - if (get_idfunc(L, index, LookupMode::LaneBody) != idfunc) + // ensure it is actually a deep userdata we created + if (get_factory(L, index, LookupMode::LaneBody) != this) { return nullptr; // no metatable, or wrong kind } @@ -444,8 +413,8 @@ DeepPrelude* luaG_todeep(lua_State* L, luaG_IdFunction idfunc, int index) */ bool copydeep(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode mode_, char const* upName_) { - luaG_IdFunction const idfunc { get_idfunc(L, i, mode_) }; - if (idfunc == nullptr) + DeepFactory* const factory { get_factory(L, i, mode_) }; + if (factory == nullptr) { return false; // not a deep userdata } @@ -463,7 +432,7 @@ bool copydeep(Universe* U, Dest L2, int L2_cache_i, Source L, int i, LookupMode lua_pop( L, 1); // ... u [uv]* STACK_CHECK( L, nuv); - char const* errmsg{ push_deep_proxy(L2, *lua_tofulluserdata(L, i), nuv, mode_) }; // u + char const* errmsg{ DeepFactory::PushDeepProxy(L2, *lua_tofulluserdata(L, i), nuv, mode_) }; // u // transfer all uservalues of the source in the destination { diff --git a/src/deep.h b/src/deep.h index 7be5c5d..7c0aa6d 100644 --- a/src/deep.h +++ b/src/deep.h @@ -28,34 +28,60 @@ enum class LookupMode FromKeeper // send a function from a keeper state to a lane }; -enum class DeepOp -{ - New, - Delete, - Metatable, - Module, -}; - -using luaG_IdFunction = void*(*)(lua_State* L, DeepOp op_); - // ################################################################################################ // xxh64 of string "DEEP_VERSION_3" generated at https://www.pelock.com/products/hash-calculator -static constexpr UniqueKey DEEP_VERSION{ 0xB2CC0FD9C0AE9674ull }; +static constexpr UniqueKey DEEP_VERSION{ 0xB2CC0FD9C0AE9674ull, "DEEP_VERSION_3" }; // should be used as header for deep userdata // a deep userdata is a full userdata that stores a single pointer to the actual DeepPrelude-derived object struct DeepPrelude { - UniqueKey const magic{ DEEP_VERSION }; - // when stored in a keeper state, the full userdata doesn't have a metatable, so we need direct access to the idfunc - luaG_IdFunction idfunc { nullptr }; + UniqueKey const m_magic{ DEEP_VERSION }; + // when stored in a keeper state, the full userdata doesn't have a metatable, so we need direct access to the factory + class DeepFactory& m_factory; // data is destroyed when refcount is 0 std::atomic m_refcount{ 0 }; + + DeepPrelude(DeepFactory& factory_) + : m_factory{ factory_ } + { + } }; -[[nodiscard]] char const* push_deep_proxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode mode_); -void free_deep_prelude(lua_State* L, DeepPrelude* prelude_); +// external C modules should create a single object implementing that interface for each Deep userdata class they want to expose +class DeepFactory +{ + protected: + + // protected non-virtual destructor: Lanes won't manage the Factory's lifetime + DeepFactory() = default; + ~DeepFactory() = default; + + public: + + // non-copyable, non-movable + DeepFactory(DeepFactory const&) = delete; + DeepFactory(DeepFactory const&&) = delete; + DeepFactory& operator=(DeepFactory const&) = delete; + DeepFactory& operator=(DeepFactory const&&) = delete; + + private: + + // NVI: private overrides + virtual DeepPrelude* newDeepObjectInternal(lua_State* L) const = 0; + virtual void deleteDeepObjectInternal(lua_State* L, DeepPrelude* o_) const = 0; + virtual void createMetatable(lua_State* L) const = 0; + virtual char const* moduleName() const = 0; + + public: + + // NVI: public interface + int pushDeepUserdata(Dest L, int nuv_) const; + DeepPrelude* toDeep(lua_State* L, int index) const; + static void DeleteDeepObject(lua_State* L, DeepPrelude* o_); + static char const* PushDeepProxy(Dest L, DeepPrelude* prelude, int nuv_, LookupMode mode_); +}; -LANES_API [[nodiscard]] int luaG_newdeepuserdata(Dest L, luaG_IdFunction idfunc, int nuv_); -LANES_API [[nodiscard]] DeepPrelude* luaG_todeep(lua_State* L, luaG_IdFunction idfunc, int index); +//LANES_API [[nodiscard]] int luaG_newdeepuserdata(Dest L, DeepFactory& factory_, int nuv_); +//LANES_API [[nodiscard]] DeepPrelude* luaG_todeep(lua_State* L, DeepFactory &factory_, int index); diff --git a/src/keeper.cpp b/src/keeper.cpp index 61321e1..36733e3 100644 --- a/src/keeper.cpp +++ b/src/keeper.cpp @@ -833,6 +833,8 @@ KeeperCallResult keeper_call(Universe* U, lua_State* K, keeper_api_t func_, lua_ KeeperCallResult result; int const args{ starting_index ? (lua_gettop(L) - starting_index + 1) : 0 }; int const top_K{ lua_gettop(K) }; + // if we didn't do anything wrong, the keeper stack should be clean + ASSERT_L(lua_gettop(K) == 0); STACK_GROW(K, 2); diff --git a/src/keeper.h b/src/keeper.h index 7ec8b15..c1ee244 100644 --- a/src/keeper.h +++ b/src/keeper.h @@ -34,7 +34,7 @@ struct Keepers static constexpr uintptr_t KEEPER_MAGIC_SHIFT{ 3 }; // crc64/we of string "NIL_SENTINEL" generated at http://www.nitrxgen.net/hashgen/ -static constexpr UniqueKey NIL_SENTINEL{ 0x7eaafa003a1d11a1ull, "internal nil sentinel" }; +static constexpr UniqueKey NIL_SENTINEL{ 0x7eaafa003a1d11a1ull, "linda.null" }; void init_keepers(Universe* U, lua_State* L); void close_keepers(Universe* U); diff --git a/src/lanes.cpp b/src/lanes.cpp index 1f795cc..d9262cf 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp @@ -544,14 +544,12 @@ static void selfdestruct_add(Lane* lane_) } } - // necessary so that calling free_deep_prelude doesn't crash because linda_id expects a linda lightuserdata at absolute slot 1 - lua_settop(L, 0); // no need to mutex-protect this as all threads in the universe are gone at that point if (U->timer_deep != nullptr) // test ins case some early internal error prevented Lanes from creating the deep timer { [[maybe_unused]] int const prev_ref_count{ U->timer_deep->m_refcount.fetch_sub(1, std::memory_order_relaxed) }; ASSERT_L(prev_ref_count == 1); // this should be the last reference - free_deep_prelude(L, U->timer_deep); + DeepFactory::DeleteDeepObject(L, U->timer_deep); U->timer_deep = nullptr; } @@ -1840,7 +1838,7 @@ LUAG_FUNC(configure) STACK_CHECK(L, 2); { - char const* errmsg{ push_deep_proxy(Dest{ L }, U->timer_deep, 0, LookupMode::LaneBody) }; // settings M timer_deep + char const* errmsg{ DeepFactory::PushDeepProxy(Dest{ L }, U->timer_deep, 0, LookupMode::LaneBody) }; // settings M timer_deep if (errmsg != nullptr) { return luaL_error(L, errmsg); diff --git a/src/linda.cpp b/src/linda.cpp index dbd6b21..103f4ed 100644 --- a/src/linda.cpp +++ b/src/linda.cpp @@ -45,6 +45,18 @@ THE SOFTWARE. // xxh64 of string "CANCEL_ERROR" generated at https://www.pelock.com/products/hash-calculator static constexpr UniqueKey BATCH_SENTINEL{ 0x2DDFEE0968C62AA7ull, "linda.batched" }; +class LindaFactory : public DeepFactory +{ + private: + + DeepPrelude* newDeepObjectInternal(lua_State* L) const override; + void deleteDeepObjectInternal(lua_State* L, DeepPrelude* o_) const override; + void createMetatable(lua_State* L) const override; + char const* moduleName() const override; +}; +// I'm not totally happy with having a global variable. But since it's stateless, it will do for the time being. +static LindaFactory g_LindaFactory; + /* * Actual data is kept within a keeper state, which is hashed by the 'Linda' * pointer (which is same to all userdatas pointing to it). @@ -82,7 +94,8 @@ class Linda : public DeepPrelude // Deep userdata MUST start with this header static void operator delete(void* p_) { static_cast(p_)->U->internal_allocator.free(p_, sizeof(Linda)); } Linda(Universe* U_, uintptr_t group_, char const* name_, size_t len_) - : U{ U_ } + : DeepPrelude{ g_LindaFactory } + , U{ U_ } , group{ group_ << KEEPER_MAGIC_SHIFT } { setName(name_, len_); @@ -141,12 +154,13 @@ class Linda : public DeepPrelude // Deep userdata MUST start with this header return nullptr; } }; -[[nodiscard]] static void* linda_id(lua_State*, DeepOp); -template +// ################################################################################################# + +template [[nodiscard]] static inline Linda* ToLinda(lua_State* L, int idx_) { - Linda* const linda{ static_cast(luaG_todeep(L, linda_id, idx_)) }; + Linda* const linda{ static_cast(g_LindaFactory.toDeep(L, idx_)) }; if constexpr (!OPT) { luaL_argcheck(L, linda != nullptr, idx_, "expecting a linda object"); @@ -171,7 +185,6 @@ static void check_key_types(lua_State* L, int start_, int end_) case LuaType::LIGHTUSERDATA: { - // NIL_SENTINEL isn't publicly exposed, but it doesn't hurt to check static constexpr std::array, 3> to_check{ BATCH_SENTINEL, CANCEL_ERROR, NIL_SENTINEL }; for (UniqueKey const& key : to_check) { @@ -199,12 +212,16 @@ LUAG_FUNC(linda_protected_call) lua_State* const KL{ K ? K->L : nullptr }; if (KL == nullptr) return 0; + // if we didn't do anything wrong, the keeper stack should be clean + ASSERT_L(lua_gettop(KL) == 0); // retrieve the actual function to be called and move it before the arguments lua_pushvalue(L, lua_upvalueindex(1)); lua_insert(L, 1); // do a protected call int const rc{ lua_pcall(L, lua_gettop(L) - 1, LUA_MULTRET, 0) }; + // whatever happens, the keeper state stack must be empty when we are done + lua_settop(KL, 0); // release the keeper keeper_release(K); @@ -838,179 +855,152 @@ LUAG_FUNC(linda_towatch) // ################################################################################################# -/* -* Identity function of a shared userdata object. -* -* lightuserdata= linda_id( "new" [, ...] ) -* = linda_id( "delete", lightuserdata ) -* -* Creation and cleanup of actual 'deep' objects. 'luaG_...' will wrap them into -* regular userdata proxies, per each state using the deep data. -* -* tbl= linda_id( "metatable" ) -* -* Returns a metatable for the proxy objects ('__gc' method not needed; will -* be added by 'luaG_...') -* -* string= linda_id( "module") -* -* Returns the name of the module that a state should require -* in order to keep a handle on the shared library that exported the idfunc -* -* = linda_id( str, ... ) -* -* For any other strings, the ID function must not react at all. This allows -* future extensions of the system. -*/ -[[nodiscard]] static void* linda_id(lua_State* L, DeepOp op_) +DeepPrelude* LindaFactory::newDeepObjectInternal(lua_State* L) const { - switch( op_) + size_t name_len = 0; + char const* linda_name = nullptr; + unsigned long linda_group = 0; + // should have a string and/or a number of the stack as parameters (name and group) + switch (lua_gettop(L)) { - case DeepOp::New: + default: // 0 + break; + + case 1: // 1 parameter, either a name or a group + if (lua_type(L, -1) == LUA_TSTRING) { - size_t name_len = 0; - char const* linda_name = nullptr; - unsigned long linda_group = 0; - // should have a string and/or a number of the stack as parameters (name and group) - switch (lua_gettop(L)) - { - default: // 0 - break; + linda_name = lua_tolstring(L, -1, &name_len); + } + else + { + linda_group = (unsigned long) lua_tointeger(L, -1); + } + break; - case 1: // 1 parameter, either a name or a group - if (lua_type(L, -1) == LUA_TSTRING) - { - linda_name = lua_tolstring(L, -1, &name_len); - } - else - { - linda_group = (unsigned long) lua_tointeger(L, -1); - } - break; + case 2: // 2 parameters, a name and group, in that order + linda_name = lua_tolstring(L, -2, &name_len); + linda_group = (unsigned long) lua_tointeger(L, -1); + break; + } - case 2: // 2 parameters, a name and group, in that order - linda_name = lua_tolstring(L, -2, &name_len); - linda_group = (unsigned long) lua_tointeger(L, -1); - break; - } + /* The deep data is allocated separately of Lua stack; we might no + * longer be around when last reference to it is being released. + * One can use any memory allocation scheme. + * just don't use L's allocF because we don't know which state will get the honor of GCing the linda + */ + Universe* const U{ universe_get(L) }; + Linda* linda{ new (U) Linda{ U, linda_group, linda_name, name_len } }; + return linda; +} - /* The deep data is allocated separately of Lua stack; we might no - * longer be around when last reference to it is being released. - * One can use any memory allocation scheme. - * just don't use L's allocF because we don't know which state will get the honor of GCing the linda - */ - Universe* const U{ universe_get(L) }; - Linda* linda{ new (U) Linda{ U, linda_group, linda_name, name_len } }; - return linda; - } +// ################################################################################################# - case DeepOp::Delete: +void LindaFactory::deleteDeepObjectInternal(lua_State* L, DeepPrelude* o_) const +{ + Linda* const linda{ static_cast(o_) }; + ASSERT_L(linda); + Keeper* const myK{ which_keeper(linda->U->keepers, linda->hashSeed()) }; + // if collected after the universe, keepers are already destroyed, and there is nothing to clear + if (myK) + { + // if collected from my own keeper, we can't acquire/release it + // because we are already inside a protected area, and trying to do so would deadlock! + bool const need_acquire_release{ myK->L != L }; + // Clean associated structures in the keeper state. + Keeper* const K{ need_acquire_release ? keeper_acquire(linda->U->keepers, linda->hashSeed()) : myK }; + // hopefully this won't ever raise an error as we would jump to the closest pcall site while forgetting to release the keeper mutex... + [[maybe_unused]] KeeperCallResult const result{ keeper_call(linda->U, K->L, KEEPER_API(clear), L, linda, 0) }; + ASSERT_L(result.has_value() && result.value() == 0); + if (need_acquire_release) { - Linda* const linda{ lua_tolightuserdata(L, 1) }; - ASSERT_L(linda); - Keeper* const myK{ which_keeper(linda->U->keepers, linda->hashSeed()) }; - // if collected after the universe, keepers are already destroyed, and there is nothing to clear - if (myK) - { - // if collected from my own keeper, we can't acquire/release it - // because we are already inside a protected area, and trying to do so would deadlock! - bool const need_acquire_release{ myK->L != L }; - // Clean associated structures in the keeper state. - Keeper* const K{ need_acquire_release ? keeper_acquire(linda->U->keepers, linda->hashSeed()) : myK }; - // hopefully this won't ever raise an error as we would jump to the closest pcall site while forgetting to release the keeper mutex... - [[maybe_unused]] KeeperCallResult const result{ keeper_call(linda->U, K->L, KEEPER_API(clear), L, linda, 0) }; - ASSERT_L(result.has_value() && result.value() == 0); - if (need_acquire_release) - { - keeper_release(K); - } - } - - delete linda; // operator delete overload ensures things go as expected - return nullptr; + keeper_release(K); } + } - case DeepOp::Metatable: - { - STACK_CHECK_START_REL(L, 0); - lua_newtable(L); - // metatable is its own index - lua_pushvalue(L, -1); - lua_setfield(L, -2, "__index"); + delete linda; // operator delete overload ensures things go as expected +} - // protect metatable from external access - lua_pushliteral(L, "Linda"); - lua_setfield(L, -2, "__metatable"); +// ################################################################################################# - lua_pushcfunction(L, LG_linda_tostring); - lua_setfield(L, -2, "__tostring"); +void LindaFactory::createMetatable(lua_State* L) const +{ + STACK_CHECK_START_REL(L, 0); + lua_newtable(L); + // metatable is its own index + lua_pushvalue(L, -1); + lua_setfield(L, -2, "__index"); - // Decoda __towatch support - lua_pushcfunction(L, LG_linda_towatch); - lua_setfield(L, -2, "__towatch"); + // protect metatable from external access + lua_pushliteral(L, "Linda"); + lua_setfield(L, -2, "__metatable"); - lua_pushcfunction(L, LG_linda_concat); - lua_setfield(L, -2, "__concat"); + lua_pushcfunction(L, LG_linda_tostring); + lua_setfield(L, -2, "__tostring"); - // protected calls, to ensure associated keeper is always released even in case of error - // all function are the protected call wrapper, where the actual operation is provided as upvalue - // note that this kind of thing can break function lookup as we use the function pointer here and there - // TODO: change that and use different functions! + // Decoda __towatch support + lua_pushcfunction(L, LG_linda_towatch); + lua_setfield(L, -2, "__towatch"); - lua_pushcfunction(L, LG_linda_send); - lua_pushcclosure(L, LG_linda_protected_call, 1); - lua_setfield(L, -2, "send"); + lua_pushcfunction(L, LG_linda_concat); + lua_setfield(L, -2, "__concat"); - lua_pushcfunction(L, LG_linda_receive); - lua_pushcclosure(L, LG_linda_protected_call, 1); - lua_setfield(L, -2, "receive"); + // protected calls, to ensure associated keeper is always released even in case of error + // all function are the protected call wrapper, where the actual operation is provided as upvalue + // note that this kind of thing can break function lookup as we use the function pointer here and there + // TODO: change that and use different functions! - lua_pushcfunction(L, LG_linda_limit); - lua_pushcclosure(L, LG_linda_protected_call, 1); - lua_setfield(L, -2, "limit"); + lua_pushcfunction(L, LG_linda_send); + lua_pushcclosure(L, LG_linda_protected_call, 1); + lua_setfield(L, -2, "send"); - lua_pushcfunction(L, LG_linda_set); - lua_pushcclosure(L, LG_linda_protected_call, 1); - lua_setfield(L, -2, "set"); + lua_pushcfunction(L, LG_linda_receive); + lua_pushcclosure(L, LG_linda_protected_call, 1); + lua_setfield(L, -2, "receive"); - lua_pushcfunction(L, LG_linda_count); - lua_pushcclosure(L, LG_linda_protected_call, 1); - lua_setfield(L, -2, "count"); + lua_pushcfunction(L, LG_linda_limit); + lua_pushcclosure(L, LG_linda_protected_call, 1); + lua_setfield(L, -2, "limit"); - lua_pushcfunction(L, LG_linda_get); - lua_pushcclosure(L, LG_linda_protected_call, 1); - lua_setfield(L, -2, "get"); + lua_pushcfunction(L, LG_linda_set); + lua_pushcclosure(L, LG_linda_protected_call, 1); + lua_setfield(L, -2, "set"); - lua_pushcfunction(L, LG_linda_cancel); - lua_setfield(L, -2, "cancel"); + lua_pushcfunction(L, LG_linda_count); + lua_pushcclosure(L, LG_linda_protected_call, 1); + lua_setfield(L, -2, "count"); - lua_pushcfunction(L, LG_linda_deep); - lua_setfield(L, -2, "deep"); + lua_pushcfunction(L, LG_linda_get); + lua_pushcclosure(L, LG_linda_protected_call, 1); + lua_setfield(L, -2, "get"); - lua_pushcfunction(L, LG_linda_dump); - lua_pushcclosure(L, LG_linda_protected_call, 1); - lua_setfield(L, -2, "dump"); + lua_pushcfunction(L, LG_linda_cancel); + lua_setfield(L, -2, "cancel"); - // some constants - BATCH_SENTINEL.pushKey(L); - lua_setfield(L, -2, "batched"); + lua_pushcfunction(L, LG_linda_deep); + lua_setfield(L, -2, "deep"); - NIL_SENTINEL.pushKey(L); - lua_setfield(L, -2, "null"); + lua_pushcfunction(L, LG_linda_dump); + lua_pushcclosure(L, LG_linda_protected_call, 1); + lua_setfield(L, -2, "dump"); - STACK_CHECK(L, 1); - return nullptr; - } + // some constants + BATCH_SENTINEL.pushKey(L); + lua_setfield(L, -2, "batched"); - case DeepOp::Module: - // linda is a special case because we know lanes must be loaded from the main lua state - // to be able to ever get here, so we know it will remain loaded as long a the main state is around - // in other words, forever. - default: - { - return nullptr; - } - } + NIL_SENTINEL.pushKey(L); + lua_setfield(L, -2, "null"); + + STACK_CHECK(L, 1); +} + +// ################################################################################################# + +char const* LindaFactory::moduleName() const +{ + // linda is a special case because we know lanes must be loaded from the main lua state + // to be able to ever get here, so we know it will remain loaded as long a the main state is around + // in other words, forever. + return nullptr; } // ################################################################################################# @@ -1034,5 +1024,5 @@ LUAG_FUNC(linda) luaL_checktype(L, 1, LUA_TSTRING); luaL_checktype(L, 2, LUA_TNUMBER); } - return luaG_newdeepuserdata(Dest{ L }, linda_id, 0); + return g_LindaFactory.pushDeepUserdata(Dest{ L }, 0); } -- cgit v1.2.3-55-g6feb