From f9f4c841e77ea36797a2ea06cd8859581dfbef94 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Tue, 14 May 2024 17:29:39 +0200 Subject: Shuffling code around --- src/cancel.cpp | 1 + src/debugspew.h | 48 ++++++++++ src/intercopycontext.cpp | 1 + src/keeper.cpp | 147 ++----------------------------- src/keeper.h | 5 +- src/lane.cpp | 1 + src/lanes.cpp | 84 +----------------- src/lanesconf.h | 2 + src/linda.cpp | 176 ++++++++++++++++++------------------- src/macros_and_utils.h | 15 ---- src/state.cpp | 10 +-- src/state.h | 5 +- src/tools.cpp | 1 + src/universe.cpp | 222 ++++++++++++++++++++++++++++++++++++++++++++++- src/universe.h | 36 +------- 15 files changed, 388 insertions(+), 366 deletions(-) create mode 100644 src/debugspew.h (limited to 'src') diff --git a/src/cancel.cpp b/src/cancel.cpp index d0a1349..ff7af1e 100644 --- a/src/cancel.cpp +++ b/src/cancel.cpp @@ -35,6 +35,7 @@ THE SOFTWARE. #include "cancel.h" +#include "debugspew.h" #include "lane.h" // ################################################################################################# diff --git a/src/debugspew.h b/src/debugspew.h new file mode 100644 index 0000000..0c3e14a --- /dev/null +++ b/src/debugspew.h @@ -0,0 +1,48 @@ +#pragma once + +#include "lanesconf.h" +#include "universe.h" + +// ################################################################################################# + +#if USE_DEBUG_SPEW() + +class DebugSpewIndentScope +{ + private: + Universe* const U; + + public: + static char const* const debugspew_indent; + + DebugSpewIndentScope(Universe* U_) + : U{ U_ } + { + if (U) + U->debugspewIndentDepth.fetch_add(1, std::memory_order_relaxed); + } + + ~DebugSpewIndentScope() + { + if (U) + U->debugspewIndentDepth.fetch_sub(1, std::memory_order_relaxed); + } +}; + +// ################################################################################################# + +#define INDENT_BEGIN "%.*s " +#define INDENT_END(U_) , (U_ ? U_->debugspewIndentDepth.load(std::memory_order_relaxed) : 0), DebugSpewIndentScope::debugspew_indent +#define DEBUGSPEW_CODE(_code) _code +#define DEBUGSPEW_OR_NOT(a_, b_) a_ +#define DEBUGSPEW_PARAM_COMMA(param_) param_, +#define DEBUGSPEW_COMMA_PARAM(param_) , param_ + +#else // USE_DEBUG_SPEW() + +#define DEBUGSPEW_CODE(_code) +#define DEBUGSPEW_OR_NOT(a_, b_) b_ +#define DEBUGSPEW_PARAM_COMMA(param_) +#define DEBUGSPEW_COMMA_PARAM(param_) + +#endif // USE_DEBUG_SPEW() diff --git a/src/intercopycontext.cpp b/src/intercopycontext.cpp index 6e5d1d8..6a26018 100644 --- a/src/intercopycontext.cpp +++ b/src/intercopycontext.cpp @@ -26,6 +26,7 @@ THE SOFTWARE. #include "intercopycontext.h" +#include "debugspew.h" #include "deep.h" #include "universe.h" diff --git a/src/keeper.cpp b/src/keeper.cpp index dcfa2ec..dcfb431 100644 --- a/src/keeper.cpp +++ b/src/keeper.cpp @@ -545,146 +545,6 @@ int keepercall_count(lua_State* L_) // Keeper API, accessed from linda methods // ################################################################################################# -/* - * Pool of keeper states - * - * Access to keeper states is locked (only one OS thread at a time) so the - * bigger the pool, the less chances of unnecessary waits. Lindas map to the - * keepers randomly, by a hash. - */ - -// called as __gc for the keepers array userdata -void close_keepers(Universe* U_) -{ - if (U_->keepers != nullptr) { - int _nbKeepers{ U_->keepers->nb_keepers }; - // NOTE: imagine some keeper state N+1 currently holds a linda that uses another keeper N, and a _gc that will make use of it - // when keeper N+1 is closed, object is GCed, linda operation is called, which attempts to acquire keeper N, whose Lua state no longer exists - // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success - // which is early-outed with a U->keepers->nbKeepers null-check - U_->keepers->nb_keepers = 0; - for (int _i = 0; _i < _nbKeepers; ++_i) { - lua_State* const K{ U_->keepers->keeper_array[_i].L }; - U_->keepers->keeper_array[_i].L = KeeperState{ nullptr }; - if (K != nullptr) { - lua_close(K); - } else { - // detected partial init: destroy only the mutexes that got initialized properly - _nbKeepers = _i; - } - } - for (int _i = 0; _i < _nbKeepers; ++_i) { - U_->keepers->keeper_array[_i].~Keeper(); - } - // free the keeper bookkeeping structure - U_->internalAllocator.free(U_->keepers, sizeof(Keepers) + (_nbKeepers - 1) * sizeof(Keeper)); - U_->keepers = nullptr; - } -} - -// ################################################################################################# - -/* - * Initialize keeper states - * - * If there is a problem, returns nullptr and pushes the error message on the stack - * else returns the keepers bookkeeping structure. - * - * Note: Any problems would be design flaws; the created Lua state is left - * unclosed, because it does not really matter. In production code, this - * function never fails. - * settings table is expected at position 1 on the stack - */ -void init_keepers(Universe* U_, lua_State* L_) -{ - LUA_ASSERT(L_, lua_gettop(L_) == 1 && lua_istable(L_, 1)); - STACK_CHECK_START_REL(L_, 0); // L_: settings - lua_getfield(L_, 1, "nb_keepers"); // L_: settings nb_keepers - int const _nb_keepers{ static_cast(lua_tointeger(L_, -1)) }; - lua_pop(L_, 1); // L_: settings - if (_nb_keepers < 1) { - raise_luaL_error(L_, "Bad number of keepers (%d)", _nb_keepers); - } - STACK_CHECK(L_, 0); - - lua_getfield(L_, 1, "keepers_gc_threshold"); // L_: settings keepers_gc_threshold - int const keepers_gc_threshold{ static_cast(lua_tointeger(L_, -1)) }; - lua_pop(L_, 1); // L_: settings - STACK_CHECK(L_, 0); - - // Keepers contains an array of 1 Keeper, adjust for the actual number of keeper states - { - size_t const bytes = sizeof(Keepers) + (_nb_keepers - 1) * sizeof(Keeper); - U_->keepers = static_cast(U_->internalAllocator.alloc(bytes)); - if (U_->keepers == nullptr) { - raise_luaL_error(L_, "init_keepers() failed while creating keeper array; out of memory"); - } - U_->keepers->Keepers::Keepers(); - U_->keepers->gc_threshold = keepers_gc_threshold; - U_->keepers->nb_keepers = _nb_keepers; - - for (int _i = 0; _i < _nb_keepers; ++_i) { - U_->keepers->keeper_array[_i].Keeper::Keeper(); - } - } - for (int _i = 0; _i < _nb_keepers; ++_i) { - // note that we will leak K if we raise an error later - KeeperState const _K{ create_state(U_, L_) }; // L_: settings K: - if (_K == nullptr) { - raise_luaL_error(L_, "init_keepers() failed while creating keeper states; out of memory"); - } - - U_->keepers->keeper_array[_i].L = _K; - - if (U_->keepers->gc_threshold >= 0) { - lua_gc(_K, LUA_GCSTOP, 0); - } - - STACK_CHECK_START_ABS(_K, 0); - - // copy the universe pointer in the keeper itself - universe_store(_K, U_); - STACK_CHECK(_K, 0); - - // make sure 'package' is initialized in keeper states, so that we have require() - // this because this is needed when transferring deep userdata object - luaL_requiref(_K, LUA_LOADLIBNAME, luaopen_package, 1); // L_: settings K: package - lua_pop(_K, 1); // L_: settings K: - STACK_CHECK(_K, 0); - serialize_require(DEBUGSPEW_PARAM_COMMA(U_) _K); - STACK_CHECK(_K, 0); - - // copy package.path and package.cpath from the source state - if (luaG_getmodule(L_, LUA_LOADLIBNAME) != LuaType::NIL) { // L_: settings package K: - // when copying with mode LookupMode::ToKeeper, error message is pushed at the top of the stack, not raised immediately - InterCopyContext _c{ U_, DestState{ _K }, SourceState{ L_ }, {}, SourceIndex{ lua_absindex(L_, -1) }, {}, LookupMode::ToKeeper, {} }; - if (_c.inter_copy_package() != InterCopyResult::Success) { // L_: settings ... error_msg K: - // if something went wrong, the error message is at the top of the stack - lua_remove(L_, -2); // L_: settings error_msg - raise_lua_error(L_); - } - } - lua_pop(L_, 1); // L_: settings K: - STACK_CHECK(L_, 0); - STACK_CHECK(_K, 0); - - // attempt to call on_state_create(), if we have one and it is a C function - // (only support a C function because we can't transfer executable Lua code in keepers) - // will raise an error in L_ in case of problem - callOnStateCreate(U_, _K, L_, LookupMode::ToKeeper); - - // to see VM name in Decoda debugger - lua_pushfstring(_K, "Keeper #%d", _i + 1); // L_: settings K: "Keeper #n" - lua_setglobal(_K, "decoda_name"); // L_: settings K: - // create the fifos table in the keeper state - kFifosRegKey.setValue(_K, [](lua_State* L_) { lua_newtable(L_); }); // L_: settings K: - STACK_CHECK(_K, 0); - } - STACK_CHECK(L_, 0); -} - -// ################################################################################################# - Keeper* Linda::acquireKeeper() const { int const _nbKeepers{ U->keepers->nb_keepers }; @@ -789,3 +649,10 @@ KeeperCallResult keeper_call(KeeperState K_, keeper_api_t func_, lua_State* L_, return _result; } + +// ################################################################################################# + +void Keepers::CreateFifosTable(lua_State* L_) +{ + kFifosRegKey.setValue(L_, [](lua_State* L_) { lua_newtable(L_); }); +} diff --git a/src/keeper.h b/src/keeper.h index ebe2946..62d9ec8 100644 --- a/src/keeper.h +++ b/src/keeper.h @@ -33,14 +33,13 @@ struct Keepers int gc_threshold{ 0 }; int nb_keepers{ 0 }; Keeper keeper_array[1]; + + static void CreateFifosTable(lua_State* L_); }; // xxh64 of string "kNilSentinel" generated at https://www.pelock.com/products/hash-calculator static constexpr UniqueKey kNilSentinel{ 0xC457D4EDDB05B5E4ull, "lanes.null" }; -void init_keepers(Universe* U_, lua_State* L_); -void close_keepers(Universe* U_); - void keeper_toggle_nil_sentinels(lua_State* L_, int start_, LookupMode const mode_); [[nodiscard]] int keeper_push_linda_storage(Linda& linda_, DestState L_); diff --git a/src/lane.cpp b/src/lane.cpp index bc82291..4c33afb 100644 --- a/src/lane.cpp +++ b/src/lane.cpp @@ -26,6 +26,7 @@ THE SOFTWARE. #include "lane.h" +#include "debugspew.h" #include "intercopycontext.h" #include "tools.h" #include "threading.h" diff --git a/src/lanes.cpp b/src/lanes.cpp index 6a84422..b3f7be7 100644 --- a/src/lanes.cpp +++ b/src/lanes.cpp @@ -575,84 +575,6 @@ LUAG_FUNC(wakeup_conv) return 1; } -// ################################################################################################# -// ################################### custom allocator support #################################### -// ################################################################################################# - -// same as PUC-Lua l_alloc -extern "C" [[nodiscard]] static void* libc_lua_Alloc([[maybe_unused]] void* ud_, [[maybe_unused]] void* ptr_, [[maybe_unused]] size_t osize_, size_t nsize_) -{ - if (nsize_ == 0) { - free(ptr_); - return nullptr; - } else { - return realloc(ptr_, nsize_); - } -} - -// ################################################################################################# - -[[nodiscard]] static int luaG_provide_protected_allocator(lua_State* L_) -{ - Universe* const _U{ universe_get(L_) }; - // push a new full userdata on the stack, giving access to the universe's protected allocator - [[maybe_unused]] AllocatorDefinition* const def{ new (L_) AllocatorDefinition{ _U->protectedAllocator.makeDefinition() } }; - return 1; -} - -// ################################################################################################# - -// called once at the creation of the universe (therefore L is the master Lua state everything originates from) -// Do I need to disable this when compiling for LuaJIT to prevent issues? -static void initialize_allocator_function(Universe* U_, lua_State* L_) -{ - STACK_CHECK_START_REL(L_, 1); // L_: settings - lua_getfield(L_, -1, "allocator"); // L_: settings allocator|nil|"protected" - if (!lua_isnil(L_, -1)) { - // store C function pointer in an internal variable - U_->provideAllocator = lua_tocfunction(L_, -1); // L_: settings allocator - if (U_->provideAllocator != nullptr) { - // make sure the function doesn't have upvalues - char const* upname = lua_getupvalue(L_, -1, 1); // L_: settings allocator upval? - if (upname != nullptr) { // should be "" for C functions with upvalues if any - raise_luaL_error(L_, "config.allocator() shouldn't have upvalues"); - } - // remove this C function from the config table so that it doesn't cause problems - // when we transfer the config table in newly created Lua states - lua_pushnil(L_); // L_: settings allocator nil - lua_setfield(L_, -3, "allocator"); // L_: settings allocator - } else if (lua_type(L_, -1) == LUA_TSTRING) { // should be "protected" - LUA_ASSERT(L_, strcmp(lua_tostring(L_, -1), "protected") == 0); - // set the original allocator to call from inside protection by the mutex - U_->protectedAllocator.initFrom(L_); - U_->protectedAllocator.installIn(L_); - // before a state is created, this function will be called to obtain the allocator - U_->provideAllocator = luaG_provide_protected_allocator; - } - } else { - // just grab whatever allocator was provided to lua_newstate - U_->protectedAllocator.initFrom(L_); - } - lua_pop(L_, 1); // L_: settings - STACK_CHECK(L_, 1); - - lua_getfield(L_, -1, "internal_allocator"); // L_: settings "libc"|"allocator" - { - char const* const _allocator{ lua_tostring(L_, -1) }; - if (strcmp(_allocator, "libc") == 0) { - U_->internalAllocator = AllocatorDefinition{ libc_lua_Alloc, nullptr }; - } else if (U_->provideAllocator == luaG_provide_protected_allocator) { - // user wants mutex protection on the state's allocator. Use protection for our own allocations too, just in case. - U_->internalAllocator = U_->protectedAllocator.makeDefinition(); - } else { - // no protection required, just use whatever we have as-is. - U_->internalAllocator = U_->protectedAllocator; - } - } - lua_pop(L_, 1); // L_: settings - STACK_CHECK(L_, 1); -} - // ################################################################################################# // ######################################## Module linkage ######################################### // ################################################################################################# @@ -728,9 +650,9 @@ LUAG_FUNC(configure) #endif // HAVE_LANE_TRACKING() // Linked chains handling _U->selfdestructFirst = SELFDESTRUCT_END; - initialize_allocator_function(_U, L_); - initializeOnStateCreate(_U, L_); - init_keepers(_U, L_); + _U->initializeAllocatorFunction(L_); + InitializeOnStateCreate(_U, L_); + _U->initializeKeepers(L_); STACK_CHECK(L_, 1); // Initialize 'timerLinda'; a common Linda object shared by all states diff --git a/src/lanesconf.h b/src/lanesconf.h index aae4f5b..b35becc 100644 --- a/src/lanesconf.h +++ b/src/lanesconf.h @@ -39,3 +39,5 @@ #define LANES_API extern #endif // __cplusplus #endif // (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) + +#define USE_DEBUG_SPEW() 0 diff --git a/src/linda.cpp b/src/linda.cpp index ad40eef..a7cf011 100644 --- a/src/linda.cpp +++ b/src/linda.cpp @@ -38,6 +38,72 @@ THE SOFTWARE. #include +// ################################################################################################# + +static void check_key_types(lua_State* L_, int start_, int end_) +{ + for (int _i{ start_ }; _i <= end_; ++_i) { + LuaType const t{ lua_type_as_enum(L_, _i) }; + switch (t) { + case LuaType::BOOLEAN: + case LuaType::NUMBER: + case LuaType::STRING: + continue; + + case LuaType::LIGHTUSERDATA: + static constexpr std::array, 3> kKeysToCheck{ kLindaBatched, kCancelError, kNilSentinel }; + for (UniqueKey const& key : kKeysToCheck) { + if (key.equals(L_, _i)) { + raise_luaL_error(L_, "argument #%d: can't use %s as a key", _i, key.debugName); + break; + } + } + break; + } + raise_luaL_error(L_, "argument #%d: invalid key type (not a boolean, string, number or light userdata)", _i); + } +} + +// ################################################################################################# + +/* + * string = linda:__tostring( linda_ud) + * + * Return the stringification of a linda + * + * Useful for concatenation or debugging purposes + */ + +template +[[nodiscard]] static int LindaToString(lua_State* L_, int idx_) +{ + Linda* const _linda{ ToLinda(L_, idx_) }; + if (_linda != nullptr) { + char _text[128]; + int _len; + if (_linda->getName()) + _len = sprintf(_text, "Linda: %.*s", (int) sizeof(_text) - 8, _linda->getName()); + else + _len = sprintf(_text, "Linda: %p", _linda); + lua_pushlstring(L_, _text, _len); + return 1; + } + return 0; +} + +// ################################################################################################# + +template +[[nodiscard]] static inline Linda* ToLinda(lua_State* L_, int idx_) +{ + Linda* const _linda{ static_cast(LindaFactory::Instance.toDeep(L_, idx_)) }; + if constexpr (!OPT) { + luaL_argcheck(L_, _linda != nullptr, idx_, "expecting a linda object"); // doesn't return if linda is nullptr + LUA_ASSERT(L_, _linda->U == universe_get(L_)); + } + return _linda; +} + // ################################################################################################# // ################################################################################################# @@ -66,27 +132,6 @@ Linda::~Linda() // ################################################################################################# -void Linda::setName(char const* name_, size_t len_) -{ - // keep default - if (!name_ || len_ == 0) { - return; - } - ++len_; // don't forget terminating 0 - if (len_ < kEmbeddedNameLength) { - nameVariant.emplace(); - char* const _name{ std::get(nameVariant).data() }; - memcpy(_name, name_, len_); - } else { - AllocatedName& _name = std::get(nameVariant); - _name.name = static_cast(U->internalAllocator.alloc(len_)); - _name.len = len_; - memcpy(_name.name, name_, len_); - } -} - -// ################################################################################################# - char const* Linda::getName() const { if (std::holds_alternative(nameVariant)) { @@ -102,45 +147,6 @@ char const* Linda::getName() const // ################################################################################################# -template -[[nodiscard]] static inline Linda* ToLinda(lua_State* L_, int idx_) -{ - Linda* const _linda{ static_cast(LindaFactory::Instance.toDeep(L_, idx_)) }; - if constexpr (!OPT) { - luaL_argcheck(L_, _linda != nullptr, idx_, "expecting a linda object"); // doesn't return if linda is nullptr - LUA_ASSERT(L_, _linda->U == universe_get(L_)); - } - return _linda; -} - -// ################################################################################################# - -static void check_key_types(lua_State* L_, int start_, int end_) -{ - for (int _i{ start_ }; _i <= end_; ++_i) { - LuaType const t{ lua_type_as_enum(L_, _i) }; - switch (t) { - case LuaType::BOOLEAN: - case LuaType::NUMBER: - case LuaType::STRING: - continue; - - case LuaType::LIGHTUSERDATA: - static constexpr std::array, 3> kKeysToCheck{ kLindaBatched, kCancelError, kNilSentinel }; - for (UniqueKey const& key : kKeysToCheck) { - if (key.equals(L_, _i)) { - raise_luaL_error(L_, "argument #%d: can't use %s as a key", _i, key.debugName); - break; - } - } - break; - } - raise_luaL_error(L_, "argument #%d: invalid key type (not a boolean, string, number or light userdata)", _i); - } -} - -// ################################################################################################# - // used to perform all linda operations that access keepers int Linda::ProtectedCall(lua_State* L_, lua_CFunction f_) { @@ -175,6 +181,29 @@ int Linda::ProtectedCall(lua_State* L_, lua_CFunction f_) // ################################################################################################# +void Linda::setName(char const* name_, size_t len_) +{ + // keep default + if (!name_ || len_ == 0) { + return; + } + ++len_; // don't forget terminating 0 + if (len_ < kEmbeddedNameLength) { + nameVariant.emplace(); + char* const _name{ std::get(nameVariant).data() }; + memcpy(_name, name_, len_); + } else { + AllocatedName& _name = std::get(nameVariant); + _name.name = static_cast(U->internalAllocator.alloc(len_)); + _name.len = len_; + memcpy(_name.name, name_, len_); + } +} + +// ################################################################################################# +// ########################################## Lua API ############################################## +// ################################################################################################# + /* * bool= linda:linda_send([timeout_secs=nil,] key_num|str|bool|lightuserdata, ...) * @@ -649,33 +678,6 @@ LUAG_FUNC(linda_deep) // ################################################################################################# -/* - * string = linda:__tostring( linda_ud) - * - * Return the stringification of a linda - * - * Useful for concatenation or debugging purposes - */ - -template -[[nodiscard]] static int LindaToString(lua_State* L_, int idx_) -{ - Linda* const _linda{ ToLinda(L_, idx_) }; - if (_linda != nullptr) { - char _text[128]; - int _len; - if (_linda->getName()) - _len = sprintf(_text, "Linda: %.*s", (int) sizeof(_text) - 8, _linda->getName()); - else - _len = sprintf(_text, "Linda: %p", _linda); - lua_pushlstring(L_, _text, _len); - return 1; - } - return 0; -} - -// ################################################################################################# - LUAG_FUNC(linda_tostring) { return LindaToString(L_, 1); diff --git a/src/macros_and_utils.h b/src/macros_and_utils.h index 5987e73..d71c9da 100644 --- a/src/macros_and_utils.h +++ b/src/macros_and_utils.h @@ -61,21 +61,6 @@ template // ################################################################################################# -#define USE_DEBUG_SPEW() 0 -#if USE_DEBUG_SPEW() -#define INDENT_BEGIN "%.*s " -#define INDENT_END(U_) , (U_ ? U_->debugspewIndentDepth.load(std::memory_order_relaxed) : 0), DebugSpewIndentScope::debugspew_indent -#define DEBUGSPEW_CODE(_code) _code -#define DEBUGSPEW_OR_NOT(a_, b_) a_ -#define DEBUGSPEW_PARAM_COMMA(param_) param_, -#define DEBUGSPEW_COMMA_PARAM(param_) , param_ -#else // USE_DEBUG_SPEW() -#define DEBUGSPEW_CODE(_code) -#define DEBUGSPEW_OR_NOT(a_, b_) b_ -#define DEBUGSPEW_PARAM_COMMA(param_) -#define DEBUGSPEW_COMMA_PARAM(param_) -#endif // USE_DEBUG_SPEW() - #ifdef NDEBUG #define LUA_ASSERT(L_, c) ; // nothing diff --git a/src/state.cpp b/src/state.cpp index 56ac74b..a6c9859 100644 --- a/src/state.cpp +++ b/src/state.cpp @@ -215,7 +215,7 @@ static void copy_one_time_settings(Universe* U_, SourceState L1_, DestState L2_) // ################################################################################################# -void initializeOnStateCreate(Universe* U_, lua_State* L_) +void InitializeOnStateCreate(Universe* U_, lua_State* L_) { STACK_CHECK_START_REL(L_, 1); // L_: settings lua_getfield(L_, -1, "on_state_create"); // L_: settings on_state_create|nil @@ -234,7 +234,7 @@ void initializeOnStateCreate(Universe* U_, lua_State* L_) lua_setfield(L_, -3, "on_state_create"); // L_: settings on_state_create } else { // optim: store marker saying we have such a function in the config table - U_->onStateCreateFunc = reinterpret_cast(initializeOnStateCreate); + U_->onStateCreateFunc = reinterpret_cast(InitializeOnStateCreate); } } lua_pop(L_, 1); // L_: settings @@ -272,12 +272,12 @@ lua_State* create_state([[maybe_unused]] Universe* U_, lua_State* from_) // ################################################################################################# -void callOnStateCreate(Universe* U_, lua_State* L_, lua_State* from_, LookupMode mode_) +void CallOnStateCreate(Universe* U_, lua_State* L_, lua_State* from_, LookupMode mode_) { if (U_->onStateCreateFunc != nullptr) { STACK_CHECK_START_REL(L_, 0); DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "calling on_state_create()\n" INDENT_END(U_))); - if (U_->onStateCreateFunc != reinterpret_cast(initializeOnStateCreate)) { + if (U_->onStateCreateFunc != reinterpret_cast(InitializeOnStateCreate)) { // C function: recreate a closure in the new state, bypassing the lookup scheme lua_pushcfunction(L_, U_->onStateCreateFunc); // on_state_create() } else { // Lua function located in the config table, copied when we opened "lanes.core" @@ -393,7 +393,7 @@ lua_State* luaG_newstate(Universe* U_, SourceState from_, char const* libs_) // call this after the base libraries are loaded and GC is restarted // will raise an error in from_ in case of problem - callOnStateCreate(U_, _L, from_, LookupMode::LaneBody); + CallOnStateCreate(U_, _L, from_, LookupMode::LaneBody); STACK_CHECK(_L, 0); // after all this, register everything we find in our name<->function database diff --git a/src/state.h b/src/state.h index 1b25736..626a696 100644 --- a/src/state.h +++ b/src/state.h @@ -1,5 +1,6 @@ #pragma once +#include "debugspew.h" #include "macros_and_utils.h" // forwards @@ -15,5 +16,5 @@ void serialize_require(DEBUGSPEW_PARAM_COMMA(Universe* U_) lua_State* L_); // ################################################################################################# -void initializeOnStateCreate(Universe* U_, lua_State* L_); -void callOnStateCreate(Universe* U_, lua_State* L_, lua_State* from_, LookupMode mode_); +void InitializeOnStateCreate(Universe* U_, lua_State* L_); +void CallOnStateCreate(Universe* U_, lua_State* L_, lua_State* from_, LookupMode mode_); diff --git a/src/tools.cpp b/src/tools.cpp index a70fdc9..eff865c 100644 --- a/src/tools.cpp +++ b/src/tools.cpp @@ -34,6 +34,7 @@ THE SOFTWARE. #include "tools.h" +#include "debugspew.h" #include "universe.h" DEBUGSPEW_CODE(char const* const DebugSpewIndentScope::debugspew_indent = "----+----!----+----!----+----!----+----!----+----!----+----!----+----!----+"); diff --git a/src/universe.cpp b/src/universe.cpp index dd293f2..9ab1290 100644 --- a/src/universe.cpp +++ b/src/universe.cpp @@ -31,8 +31,10 @@ THE SOFTWARE. #include "universe.h" #include "deep.h" +#include "intercopycontext.h" #include "keeper.h" #include "lane.h" +#include "state.h" // ################################################################################################# @@ -77,6 +79,224 @@ Universe::Universe() return _U; } +// ################################################################################################# +// ################################### custom allocator support #################################### +// ################################################################################################# + +// same as PUC-Lua l_alloc +extern "C" [[nodiscard]] static void* libc_lua_Alloc([[maybe_unused]] void* ud_, [[maybe_unused]] void* ptr_, [[maybe_unused]] size_t osize_, size_t nsize_) +{ + if (nsize_ == 0) { + free(ptr_); + return nullptr; + } else { + return realloc(ptr_, nsize_); + } +} + +// ################################################################################################# + +[[nodiscard]] static int luaG_provide_protected_allocator(lua_State* L_) +{ + Universe* const _U{ universe_get(L_) }; + // push a new full userdata on the stack, giving access to the universe's protected allocator + [[maybe_unused]] AllocatorDefinition* const def{ new (L_) AllocatorDefinition{ _U->protectedAllocator.makeDefinition() } }; + return 1; +} + +// ################################################################################################# + +/* + * Pool of keeper states + * + * Access to keeper states is locked (only one OS thread at a time) so the + * bigger the pool, the less chances of unnecessary waits. Lindas map to the + * keepers randomly, by a hash. + */ + +// called as __gc for the keepers array userdata +void Universe::closeKeepers() +{ + if (keepers != nullptr) { + int _nbKeepers{ keepers->nb_keepers }; + // NOTE: imagine some keeper state N+1 currently holds a linda that uses another keeper N, and a _gc that will make use of it + // when keeper N+1 is closed, object is GCed, linda operation is called, which attempts to acquire keeper N, whose Lua state no longer exists + // in that case, the linda operation should do nothing. which means that these operations must check for keeper acquisition success + // which is early-outed with a keepers->nbKeepers null-check + keepers->nb_keepers = 0; + for (int _i = 0; _i < _nbKeepers; ++_i) { + lua_State* const _K{ keepers->keeper_array[_i].L }; + keepers->keeper_array[_i].L = KeeperState{ nullptr }; + if (_K != nullptr) { + lua_close(_K); + } else { + // detected partial init: destroy only the mutexes that got initialized properly + _nbKeepers = _i; + } + } + for (int _i = 0; _i < _nbKeepers; ++_i) { + keepers->keeper_array[_i].~Keeper(); + } + // free the keeper bookkeeping structure + internalAllocator.free(keepers, sizeof(Keepers) + (_nbKeepers - 1) * sizeof(Keeper)); + keepers = nullptr; + } +} + +// ################################################################################################# + +// called once at the creation of the universe (therefore L_ is the master Lua state everything originates from) +// Do I need to disable this when compiling for LuaJIT to prevent issues? +void Universe::initializeAllocatorFunction(lua_State* L_) +{ + STACK_CHECK_START_REL(L_, 1); // L_: settings + lua_getfield(L_, -1, "allocator"); // L_: settings allocator|nil|"protected" + if (!lua_isnil(L_, -1)) { + // store C function pointer in an internal variable + provideAllocator = lua_tocfunction(L_, -1); // L_: settings allocator + if (provideAllocator != nullptr) { + // make sure the function doesn't have upvalues + char const* upname = lua_getupvalue(L_, -1, 1); // L_: settings allocator upval? + if (upname != nullptr) { // should be "" for C functions with upvalues if any + raise_luaL_error(L_, "config.allocator() shouldn't have upvalues"); + } + // remove this C function from the config table so that it doesn't cause problems + // when we transfer the config table in newly created Lua states + lua_pushnil(L_); // L_: settings allocator nil + lua_setfield(L_, -3, "allocator"); // L_: settings allocator + } else if (lua_type(L_, -1) == LUA_TSTRING) { // should be "protected" + LUA_ASSERT(L_, strcmp(lua_tostring(L_, -1), "protected") == 0); + // set the original allocator to call from inside protection by the mutex + protectedAllocator.initFrom(L_); + protectedAllocator.installIn(L_); + // before a state is created, this function will be called to obtain the allocator + provideAllocator = luaG_provide_protected_allocator; + } + } else { + // just grab whatever allocator was provided to lua_newstate + protectedAllocator.initFrom(L_); + } + lua_pop(L_, 1); // L_: settings + STACK_CHECK(L_, 1); + + lua_getfield(L_, -1, "internal_allocator"); // L_: settings "libc"|"allocator" + { + char const* const _allocator{ lua_tostring(L_, -1) }; + if (strcmp(_allocator, "libc") == 0) { + internalAllocator = AllocatorDefinition{ libc_lua_Alloc, nullptr }; + } else if (provideAllocator == luaG_provide_protected_allocator) { + // user wants mutex protection on the state's allocator. Use protection for our own allocations too, just in case. + internalAllocator = protectedAllocator.makeDefinition(); + } else { + // no protection required, just use whatever we have as-is. + internalAllocator = protectedAllocator; + } + } + lua_pop(L_, 1); // L_: settings + STACK_CHECK(L_, 1); +} + +// ################################################################################################# + +/* + * Initialize keeper states + * + * If there is a problem, returns nullptr and pushes the error message on the stack + * else returns the keepers bookkeeping structure. + * + * Note: Any problems would be design flaws; the created Lua state is left + * unclosed, because it does not really matter. In production code, this + * function never fails. + * settings table is expected at position 1 on the stack + */ +void Universe::initializeKeepers(lua_State* L_) +{ + LUA_ASSERT(L_, lua_gettop(L_) == 1 && lua_istable(L_, 1)); + STACK_CHECK_START_REL(L_, 0); // L_: settings + lua_getfield(L_, 1, "nb_keepers"); // L_: settings nb_keepers + int const _nb_keepers{ static_cast(lua_tointeger(L_, -1)) }; + lua_pop(L_, 1); // L_: settings + if (_nb_keepers < 1) { + raise_luaL_error(L_, "Bad number of keepers (%d)", _nb_keepers); + } + STACK_CHECK(L_, 0); + + lua_getfield(L_, 1, "keepers_gc_threshold"); // L_: settings keepers_gc_threshold + int const keepers_gc_threshold{ static_cast(lua_tointeger(L_, -1)) }; + lua_pop(L_, 1); // L_: settings + STACK_CHECK(L_, 0); + + // Keepers contains an array of 1 Keeper, adjust for the actual number of keeper states + { + size_t const bytes = sizeof(Keepers) + (_nb_keepers - 1) * sizeof(Keeper); + keepers = static_cast(internalAllocator.alloc(bytes)); + if (keepers == nullptr) { + raise_luaL_error(L_, "out of memory while creating keepers"); + } + keepers->Keepers::Keepers(); + keepers->gc_threshold = keepers_gc_threshold; + keepers->nb_keepers = _nb_keepers; + + for (int _i = 0; _i < _nb_keepers; ++_i) { + keepers->keeper_array[_i].Keeper::Keeper(); + } + } + for (int _i = 0; _i < _nb_keepers; ++_i) { + // note that we will leak K if we raise an error later + KeeperState const _K{ create_state(this, L_) }; // L_: settings K: + if (_K == nullptr) { + raise_luaL_error(L_, "out of memory while creating keeper states"); + } + + keepers->keeper_array[_i].L = _K; + + if (keepers->gc_threshold >= 0) { + lua_gc(_K, LUA_GCSTOP, 0); + } + + STACK_CHECK_START_ABS(_K, 0); + + // copy the universe pointer in the keeper itself + universe_store(_K, this); + STACK_CHECK(_K, 0); + + // make sure 'package' is initialized in keeper states, so that we have require() + // this because this is needed when transferring deep userdata object + luaL_requiref(_K, LUA_LOADLIBNAME, luaopen_package, 1); // L_: settings K: package + lua_pop(_K, 1); // L_: settings K: + STACK_CHECK(_K, 0); + serialize_require(DEBUGSPEW_PARAM_COMMA(this) _K); + STACK_CHECK(_K, 0); + + // copy package.path and package.cpath from the source state + if (luaG_getmodule(L_, LUA_LOADLIBNAME) != LuaType::NIL) { // L_: settings package K: + // when copying with mode LookupMode::ToKeeper, error message is pushed at the top of the stack, not raised immediately + InterCopyContext _c{ this, DestState{ _K }, SourceState{ L_ }, {}, SourceIndex{ lua_absindex(L_, -1) }, {}, LookupMode::ToKeeper, {} }; + if (_c.inter_copy_package() != InterCopyResult::Success) { // L_: settings ... error_msg K: + // if something went wrong, the error message is at the top of the stack + lua_remove(L_, -2); // L_: settings error_msg + raise_lua_error(L_); + } + } + lua_pop(L_, 1); // L_: settings K: + STACK_CHECK(L_, 0); + STACK_CHECK(_K, 0); + + // attempt to call on_state_create(), if we have one and it is a C function + // (only support a C function because we can't transfer executable Lua code in keepers) + // will raise an error in L_ in case of problem + CallOnStateCreate(this, _K, L_, LookupMode::ToKeeper); + + // to see VM name in Decoda debugger + lua_pushfstring(_K, "Keeper #%d", _i + 1); // L_: settings K: "Keeper #n" + lua_setglobal(_K, "decoda_name"); // L_: settings K: + // create the fifos table in the keeper state + Keepers::CreateFifosTable(_K); + STACK_CHECK(_K, 0); + } + STACK_CHECK(L_, 0); +} + // ################################################################################################# void Universe::terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTimeout_, CancelOp op_) @@ -160,7 +380,7 @@ int universe_gc(lua_State* L_) _U->timerLinda = nullptr; } - close_keepers(_U); + _U->closeKeepers(); // remove the protected allocator, if any _U->protectedAllocator.removeFrom(L_); diff --git a/src/universe.h b/src/universe.h index 8244980..4ed1776 100644 --- a/src/universe.h +++ b/src/universe.h @@ -9,7 +9,7 @@ extern "C" } #endif // __cplusplus -#include "macros_and_utils.h" +#include "lanesconf.h" #include "tracker.h" #include "uniquekey.h" @@ -54,11 +54,6 @@ class AllocatorDefinition allocF = lua_getallocf(L_, &allocUD); } - void* lua_alloc(void* ptr_, size_t osize_, size_t nsize_) - { - allocF(allocUD, ptr_, osize_, nsize_); - } - void* alloc(size_t nsize_) { return allocF(allocUD, nullptr, 0, nsize_); @@ -185,6 +180,9 @@ class Universe Universe& operator=(Universe const&) = delete; Universe& operator=(Universe&&) = delete; + void closeKeepers(); + void initializeAllocatorFunction(lua_State* L_); + void initializeKeepers(lua_State* L_); void terminateFreeRunningLanes(lua_State* L_, lua_Duration shutdownTimeout_, CancelOp op_); }; @@ -196,32 +194,6 @@ void universe_store(lua_State* L_, Universe* U_); // ################################################################################################# -#if USE_DEBUG_SPEW() -class DebugSpewIndentScope -{ - private: - Universe* const U; - - public: - static char const* const debugspew_indent; - - DebugSpewIndentScope(Universe* U_) - : U{ U_ } - { - if (U) - U->debugspewIndentDepth.fetch_add(1, std::memory_order_relaxed); - } - - ~DebugSpewIndentScope() - { - if (U) - U->debugspewIndentDepth.fetch_sub(1, std::memory_order_relaxed); - } -}; -#endif // USE_DEBUG_SPEW() - -// ################################################################################################# - [[nodiscard]] inline Universe* universe_get(lua_State* L_) { STACK_CHECK_START_REL(L_, 0); -- cgit v1.2.3-55-g6feb