From 053f7cff3c95acb915e6babfd306971f11bb7986 Mon Sep 17 00:00:00 2001 From: Benoit Germain Date: Sat, 5 Nov 2011 17:31:02 +0100 Subject: * process exit change: close everything at GC when main state closes, not when atexit() handlers are processed * Lua 5.2-style module: * module() is no longer used to implement lanes.lua * a global "lanes" variable is no longer created when the module is required * the Lanes module table is returned instead * Lanes must be initialized before used: * the first occurence of 'require "lanes"' produces a minimal interface that only contains a configure() function * the remainder of the interface is made available once this function is called * subsequent calls to configure() do nothing * configure() controls the number of keeper states and the startup of timers * LuaJIT 2 compatibility * non-Lua functions are no longer copied by creating a C closure from a C pointer, but through 2-way lookup tables * this means that if a lane function body pulls non-Lua functions, the lane generator description must contain the list of libraries and modules that exports them * introduces a change in configuration .globals management: contents are copied *after* std libs are loaded * new .required configuration entry to list modules that must be require()'ed before lane body is transferred * lane:cancel() wakes up waiting lindas like what is done at lane shutdown --- src/keeper.c | 112 +++++++--- src/keeper.h | 1 + src/lanes.c | 311 +++++++++++++++++++-------- src/lanes.lua | 79 +++++-- src/threading.c | 1 + src/tools.c | 649 +++++++++++++++++++++++++++++++++++++++++++------------- src/tools.h | 9 +- 7 files changed, 877 insertions(+), 285 deletions(-) (limited to 'src') diff --git a/src/keeper.c b/src/keeper.c index 5b355cb..1f69d40 100644 --- a/src/keeper.c +++ b/src/keeper.c @@ -90,61 +90,119 @@ char const *init_keepers( int const _nbKeepers) // Initialize Keeper states with bare minimum of libs (those required // by 'keeper.lua') // - lua_State *L= luaL_newstate(); - if (!L) + lua_State *K = luaL_newstate(); + if (!K) return "out of memory"; // to see VM name in Decoda debugger - lua_pushliteral( L, "Keeper #"); - lua_pushinteger( L, i + 1); - lua_concat( L, 2); - lua_setglobal( L, "decoda_name"); - - luaG_openlibs( L, "io,table,package" ); // 'io' for debugging messages, package because we need to require modules exporting idfuncs - serialize_require( L); + lua_pushliteral( K, "Keeper #"); + lua_pushinteger( K, i + 1); + lua_concat( K, 2); + lua_setglobal( K, "decoda_name"); + // 'io' for debugging messages, 'package' because we need to require modules exporting idfuncs + // the others because they export functions that we may store in a keeper for transfer between lanes + luaG_openlibs( K, "*"); + serialize_require( K); // Read in the preloaded chunk (and run it) // - if (luaL_loadbuffer( L, keeper_chunk, sizeof(keeper_chunk), "@keeper.lua")) + if( luaL_loadbuffer( K, keeper_chunk, sizeof(keeper_chunk), "@keeper.lua")) return "luaL_loadbuffer() failed"; // LUA_ERRMEM - if (lua_pcall( L, 0 /*args*/, 0 /*results*/, 0 /*errfunc*/ )) + if( lua_pcall( K, 0 /*args*/, 0 /*results*/, 0 /*errfunc*/)) { // LUA_ERRRUN / LUA_ERRMEM / LUA_ERRERR // - const char *err= lua_tostring(L,-1); - assert(err); + const char *err = lua_tostring(K, -1); + assert( err); return err; } - MUTEX_INIT( &GKeepers[i].lock_ ); - GKeepers[i].L= L; + MUTEX_INIT( &GKeepers[i].lock_); + GKeepers[i].L = K; //GKeepers[i].count = 0; } return NULL; // ok } +// cause each keeper state to populate its database of transferable functions with those from the specified module +void populate_keepers( lua_State *L) +{ + size_t name_len; + char const *name = luaL_checklstring( L, -1, &name_len); + size_t package_path_len; + char const *package_path; + size_t package_cpath_len; + char const *package_cpath; + int i; + + // we need to make sure that package.path & package.cpath are the same in the keepers +// than what is currently in use when the module is required in the caller's Lua state + STACK_CHECK(L) + STACK_GROW( L, 3); + lua_getglobal( L, "package"); + lua_getfield( L, -1, "path"); + package_path = luaL_checklstring( L, -1, &package_path_len); + lua_getfield( L, -2, "cpath"); + package_cpath = luaL_checklstring( L, -1, &package_cpath_len); + + for( i = 0; i < GNbKeepers; ++ i) + { + lua_State *K = GKeepers[i].L; + int res; + MUTEX_LOCK( &GKeepers[i].lock_); + STACK_CHECK(K) + STACK_GROW( K, 2); + lua_getglobal( K, "package"); + lua_pushlstring( K, package_path, package_path_len); + lua_setfield( K, -2, "path"); + lua_pushlstring( K, package_cpath, package_cpath_len); + lua_setfield( K, -2, "cpath"); + lua_pop( K, 1); + lua_getglobal( K, "require"); + lua_pushlstring( K, name, name_len); + res = lua_pcall( K, 1, 0, 0); + if( res != 0) + { + char const *err = luaL_checkstring( K, -1); + luaL_error( L, "error requiring '%s' in keeper state: %s", name, err); + } + STACK_END(K, 0) + MUTEX_UNLOCK( &GKeepers[i].lock_); + } + lua_pop( L, 3); + STACK_END(L, 0) +} + struct s_Keeper *keeper_acquire( const void *ptr) { - /* - * Any hashing will do that maps pointers to 0..GNbKeepers-1 - * consistently. - * - * Pointers are often aligned by 8 or so - ignore the low order bits - */ - unsigned int i= ((unsigned long)(ptr) >> 3) % GNbKeepers; - struct s_Keeper *K= &GKeepers[i]; + // can be 0 if this happens during main state shutdown (lanes is being GC'ed -> no keepers) + if( GNbKeepers == 0) + { + return NULL; + } + else + { + /* + * Any hashing will do that maps pointers to 0..GNbKeepers-1 + * consistently. + * + * Pointers are often aligned by 8 or so - ignore the low order bits + */ + unsigned int i= ((unsigned long)(ptr) >> 3) % GNbKeepers; + struct s_Keeper *K= &GKeepers[i]; - MUTEX_LOCK( &K->lock_); - //++ K->count; - return K; + MUTEX_LOCK( &K->lock_); + //++ K->count; + return K; + } } void keeper_release( struct s_Keeper *K) { //-- K->count; - MUTEX_UNLOCK( &K->lock_); + if( K) MUTEX_UNLOCK( &K->lock_); } void keeper_toggle_nil_sentinels( lua_State *L, int _val_i, int _nil_to_sentinel) diff --git a/src/keeper.h b/src/keeper.h index 27a0f68..1bcb36b 100644 --- a/src/keeper.h +++ b/src/keeper.h @@ -9,6 +9,7 @@ struct s_Keeper }; const char *init_keepers( int const _nbKeepers); +void populate_keepers( lua_State *L); struct s_Keeper *keeper_acquire( const void *ptr); void keeper_release( struct s_Keeper *K); void keeper_toggle_nil_sentinels( lua_State *L, int _val_i, int _nil_to_sentinel); diff --git a/src/lanes.c b/src/lanes.c index ed54b0f..44db625 100644 --- a/src/lanes.c +++ b/src/lanes.c @@ -51,7 +51,7 @@ * ... */ -const char *VERSION= "2.2.0"; +const char *VERSION= "3.0-beta"; /* =============================================================================== @@ -787,10 +787,11 @@ static void linda_id( lua_State *L, char const * const which) /* Clean associated structures in the keeper state. */ K= keeper_acquire(s); + if( K) // can be NULL if this happens during main state shutdown (lanes is GC'ed -> no keepers -> no need to cleanup) { keeper_call( K->L, "clear", L, s, 0 ); + keeper_release(K); } - keeper_release(K); /* There aren't any lanes waiting on these lindas, since all proxies * have been gc'ed. Right? @@ -1028,9 +1029,9 @@ volatile DEEP_PRELUDE *timer_deep; // = NULL /* * Process end; cancel any still free-running threads */ -static void selfdestruct_atexit( void ) +static int selfdestruct_atexit( lua_State *L) { - if (selfdestruct_first == SELFDESTRUCT_END) return; // no free-running threads + if (selfdestruct_first == SELFDESTRUCT_END) return 0; // no free-running threads // Signal _all_ still running threads to exit (including the timer thread) // @@ -1047,7 +1048,7 @@ static void selfdestruct_atexit( void ) // signal the linda the wake up the thread so that it can react to the cancel query // let us hope we never land here with a pointer on a linda that has been destroyed... SIGNAL_T *waiting_on = s->waiting_on; - s->waiting_on = NULL; + //s->waiting_on = NULL; // useful, or not? SIGNAL_ALL( waiting_on); } s = s->selfdestruct_next; @@ -1117,6 +1118,7 @@ static void selfdestruct_atexit( void ) // if ( selfdestruct_first != SELFDESTRUCT_END ) { unsigned n=0; +#if 0 MUTEX_LOCK( &selfdestruct_cs ); { struct s_lane *s= selfdestruct_first; @@ -1131,15 +1133,14 @@ static void selfdestruct_atexit( void ) // and works without the block (so let's leave those lanes running) // //we want to free memory and such when we exit. -#if 0 // 2.0.2: at least timer lane is still here // DEBUGEXEC(fprintf( stderr, "Left %d lane(s) with cancel request at process end.\n", n )); + n=0; #else // first thing we did was to raise the linda signals the threads were waiting on (if any) // therefore, any well-behaved thread should be in CANCELLED state // these are not running, and the state can be closed - n=0; MUTEX_LOCK( &selfdestruct_cs ); { struct s_lane *s= selfdestruct_first; @@ -1147,7 +1148,8 @@ static void selfdestruct_atexit( void ) { struct s_lane *next_s= s->selfdestruct_next; s->selfdestruct_next= NULL; // detach from selfdestruct chain - THREAD_KILL( &s->thread); + if( s->thread) // can be NULL if previous 'soft' termination succeeded + THREAD_KILL( &s->thread); // NO lua_close() in this case because we don't know where execution of the state was interrupted free( s); s = next_s; @@ -1161,6 +1163,7 @@ static void selfdestruct_atexit( void ) #endif } close_keepers(); + return 0; } @@ -1508,10 +1511,36 @@ LUAG_FUNC( set_debug_threadname) // [prio_int=0], // [globals_tbl], // [packagepath], +// [required], // [... args ...] ) // // Upvalues: metatable to use for 'lane_ud' // + +// helper function to require a module in the keeper states and in the target state +// source state contains module name at the top of the stack +static void require_one_module( lua_State *L, lua_State *L2, bool_t _fatal) +{ + size_t len; + char const *name = lua_tolstring( L, -1, &len); + // require the module in the target lane + STACK_GROW( L2, 2); + lua_getglobal( L2, "require"); + if( lua_isnil( L2, -1)) + { + lua_pop( L2, 1); + if( _fatal) + luaL_error( L, "cannot pre-require modules without loading package library first"); + } + else + { + lua_pushlstring( L2, name, len); + lua_pcall( L2, 1, 0, 0); + // we need to require this module in the keeper states as well + populate_keepers( L); + } +} + LUAG_FUNC( thread_new ) { lua_State *L2; @@ -1524,8 +1553,9 @@ LUAG_FUNC( thread_new ) uint_t glob= luaG_isany(L,5) ? 5:0; uint_t ppath = luaG_isany(L,6) ? 6:0; uint_t pcpath = luaG_isany(L,7) ? 7:0; + uint_t required = luaG_isany(L,8) ? 8:0; -#define FIXED_ARGS (7) +#define FIXED_ARGS (8) uint_t args= lua_gettop(L) - FIXED_ARGS; if (prio < THREAD_PRIO_MIN || prio > THREAD_PRIO_MAX) @@ -1541,40 +1571,20 @@ LUAG_FUNC( thread_new ) if (!L2) luaL_error( L, "'luaL_newstate()' failed; out of memory" ); - STACK_GROW( L,2 ); - - // Setting the globals table (needs to be done before loading stdlibs, - // and the lane function) - // - if (glob!=0) - { - STACK_CHECK(L) - if (!lua_istable(L,glob)) - luaL_error( L, "Expected table, got %s", luaG_typename(L,glob) ); - - lua_pushvalue( L, glob ); - - luaG_inter_move( L, L2, 1); // moves the table to L2 - - // L2 [-1]: table of globals - - // "You can change the global environment of a Lua thread using lua_replace" - // (refman-5.0.pdf p. 30) - // - lua_replace( L2, LUA_GLOBALSINDEX ); - STACK_END(L,0) - } + STACK_GROW( L, 2); // Selected libraries // if (libs) { - const char *err= luaG_openlibs( L2, libs ); + const char *err= luaG_openlibs( L2, libs); ASSERT_L( !err ); // bad libs should have been noticed by 'lanes.lua' - serialize_require( L2 ); + serialize_require( L2); } + ASSERT_L( lua_gettop(L2) == 0); + // package.path STACK_CHECK(L2) if( ppath) @@ -1582,15 +1592,17 @@ LUAG_FUNC( thread_new ) if (lua_type(L,ppath) != LUA_TSTRING) luaL_error( L, "expected packagepath as string, got %s", luaG_typename(L,ppath)); lua_getglobal( L2, "package"); - if( lua_isnil( L2, -1)) + if( lua_isnil( L2, -1)) // package library not loaded: do nothing { lua_pop( L2, 1); - luaL_error( L, "specifying a new path for packages, but lane doesn't load package library"); } - lua_pushvalue( L, ppath); - luaG_inter_move( L, L2, 1); // moves the new path to L2 - lua_setfield( L2, -2, "path"); // set package.path - lua_pop( L2, 1); + else + { + lua_pushvalue( L, ppath); + luaG_inter_move( L, L2, 1); // moves the new path to L2 + lua_setfield( L2, -2, "path"); // set package.path + lua_pop( L2, 1); + } } STACK_END(L2,0) @@ -1601,18 +1613,83 @@ LUAG_FUNC( thread_new ) if (lua_type(L,pcpath) != LUA_TSTRING) luaL_error( L, "expected packagecpath as string, got %s", luaG_typename(L,pcpath)); lua_getglobal( L2, "package"); - if( lua_isnil( L2, -1)) + if( lua_isnil( L2, -1)) // // package library not loaded: do nothing { lua_pop( L2, 1); - luaL_error( L, "specifying a new cpath for packages, but lane doesn't load package library"); } - lua_pushvalue( L, pcpath); - luaG_inter_move( L, L2, 1); // moves the new cpath to L2 - lua_setfield( L2, -2, "cpath"); // set package.cpath - lua_pop( L2, 1); + else + { + lua_pushvalue( L, pcpath); + luaG_inter_move( L, L2, 1); // moves the new cpath to L2 + lua_setfield( L2, -2, "cpath"); // set package.cpath + lua_pop( L2, 1); + } } STACK_END(L2,0) + // modules to require in the target lane *before* the function is transfered! + + //start by requiring lua51-lanes, since it is a bit special + // it not fatal if 'require' isn't loaded, just ignore (may cause function transfer errors later on if the lane pulls the lanes module itself) + STACK_CHECK(L) + STACK_CHECK(L2) + lua_pushliteral( L, "lua51-lanes"); + require_one_module( L, L2, FALSE); + lua_pop( L, 1); + STACK_END(L2,0) + STACK_END(L,0) + + STACK_CHECK(L) + STACK_CHECK(L2) + if( required) + { + int nbRequired = 1; + // should not happen, was checked in lanes.lua before calling thread_new() + if (lua_type(L, required) != LUA_TTABLE) + luaL_error( L, "expected required module list as a table, got %s", luaG_typename( L, required)); + lua_pushnil( L); + while( lua_next( L, required) != 0) + { + if (lua_type(L,-1) != LUA_TSTRING || lua_type(L,-2) != LUA_TNUMBER || lua_tonumber( L, -2) != nbRequired) + { + luaL_error( L, "required module list should be a list of strings."); + } + else + { + require_one_module( L, L2, TRUE); + } + lua_pop( L, 1); + ++ nbRequired; + } + } + STACK_END(L2,0) + STACK_END(L,0) + + // Appending the specified globals to the global environment + // *after* stdlibs have been loaded and modules required, in case we transfer references to native functions they exposed... + // + if (glob!=0) + { + STACK_CHECK(L) + STACK_CHECK(L2) + if (!lua_istable(L,glob)) + luaL_error( L, "Expected table, got %s", luaG_typename(L,glob)); + + lua_pushnil( L); + while( lua_next( L, glob)) + { + luaG_inter_copy( L, L2, 2); // moves the key/value pair to the L2 stack + // assign it in the globals table + lua_rawset( L2, LUA_GLOBALSINDEX); + lua_pop( L, 1); + } + + STACK_END(L2, 0) + STACK_END(L, 0) + } + + ASSERT_L( lua_gettop(L2) == 0); + // Lane main function // STACK_CHECK(L) @@ -1677,7 +1754,7 @@ LUAG_FUNC( thread_new ) lua_newtable( L); lua_setfenv( L, -2); - // Place 's' to registry, for 'cancel_test()' (even if 'cs'==0 we still + // Place 's' in registry, for 'cancel_test()' (even if 'cs'==0 we still // do cancel tests at pending send/receive). // lua_pushlightuserdata( L2, CANCEL_TEST_KEY ); @@ -1792,6 +1869,17 @@ static bool_t thread_cancel( struct s_lane *s, double secs, bool_t force) // if( s->status < DONE) { + // signal the linda the wake up the thread so that it can react to the cancel query + // let us hope we never land here with a pointer on a linda that has been destroyed... + //MUTEX_LOCK( &selfdestruct_cs ); + { + SIGNAL_T *waiting_on = s->waiting_on; + if( s->status == WAITING && waiting_on != NULL) + { + SIGNAL_ALL( waiting_on); + } + } + //MUTEX_UNLOCK( &selfdestruct_cs ); s->cancel_request = TRUE; // it's now signalled to stop done= #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) || (defined PTHREAD_TIMEDJOIN) @@ -1816,23 +1904,32 @@ static bool_t thread_cancel( struct s_lane *s, double secs, bool_t force) LUAG_FUNC( thread_cancel) { - struct s_lane *s= lua_toLane(L,1); - double secs= 0.0; - uint_t force_i=2; - bool_t force, done= TRUE; + if( lua_gettop( L) != 1 || lua_type( L, 1) != LUA_TUSERDATA) + { + return luaL_error( L, "invalid argument #1, did you use ':' as you should?"); + } + else + { + struct s_lane *s = lua_toLane( L, 1); + double secs = 0.0; + uint_t force_i = 2; + bool_t force, done= TRUE; - if (lua_isnumber(L,2)) { - secs= lua_tonumber(L,2); - force_i++; - } else if (lua_isnil(L,2)) - force_i++; + if( lua_isnumber( L, 2)) + { + secs = lua_tonumber( L, 2); + ++ force_i; + } + else if( lua_isnil( L, 2)) + ++ force_i; - force= lua_toboolean(L,force_i); // FALSE if nothing there + force = lua_toboolean( L, force_i); // FALSE if nothing there - done = thread_cancel( s, secs, force); + done = thread_cancel( s, secs, force); - lua_pushboolean( L, done); - return 1; + lua_pushboolean( L, done); + return 1; + } } //--- @@ -2183,7 +2280,7 @@ static void init_once_LOCKED( lua_State *L, volatile DEEP_PRELUDE ** timer_deep_ // Selfdestruct chain handling // MUTEX_INIT( &selfdestruct_cs ); - atexit( selfdestruct_atexit ); + //atexit( selfdestruct_atexit ); //--- // Linux needs SCHED_RR to change thread priorities, and that is only @@ -2233,7 +2330,17 @@ static void init_once_LOCKED( lua_State *L, volatile DEEP_PRELUDE ** timer_deep_ // The host Lua state must always have a reference to this Linda object in order for our 'timer_deep_ref' to be valid. // So store a reference that we will never actually use. - lua_pushlightuserdata(L, (void *)init_once_LOCKED); + // at the same time, use this object as a 'desinit' marker: + // when the main lua State is closed, this object will be GC'ed + { + lua_newuserdata( L, 1); + lua_newtable( L); + lua_pushcfunction( L, selfdestruct_atexit); + lua_setfield( L, -2, "__gc"); + lua_pushliteral( L, "AtExit"); + lua_setfield( L, -2, "__metatable"); + lua_setmetatable( L, -2); + } lua_insert(L, -2); // Swap key with the Linda object lua_rawset(L, LUA_REGISTRYINDEX); @@ -2241,14 +2348,12 @@ static void init_once_LOCKED( lua_State *L, volatile DEEP_PRELUDE ** timer_deep_ STACK_END(L,0) } -int -#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) -__declspec(dllexport) -#endif -luaopen_lanes( lua_State *L ) +static volatile long s_initCount = 0; + +LUAG_FUNC( configure ) { - static volatile int /*bool*/ go_ahead; // = 0 - int const nbKeepers = luaL_optint( L, 2, 1); + char const *name = luaL_checkstring( L, lua_upvalueindex( 1)); + int const nbKeepers = luaL_optint( L, 1, 1); luaL_argcheck( L, nbKeepers > 0, 2, "Number of keeper states must be > 0"); /* * Making one-time initializations. @@ -2259,31 +2364,29 @@ luaopen_lanes( lua_State *L ) */ #ifdef PLATFORM_WIN32 { - // TBD: Someone please replace this with reliable Win32 API code. Problem is, - // there's no autoinitializing locks (s.a. PTHREAD_MUTEX_INITIALIZER) in - // Windows so 'InterlockedIncrement' or something needs to be used. - // This is 99.9999% safe, though (and always safe if host is single-threaded) - // -- AKa 24-Jun-2009 - // - static volatile unsigned my_number; // = 0 - - if (my_number++ == 0) { // almost atomic + static volatile int /*bool*/ go_ahead; // = 0 + if( InterlockedCompareExchange( &s_initCount, 1, 0) == 0) + { init_once_LOCKED(L, &timer_deep, nbKeepers); go_ahead= 1; // let others pass - } else { + } + else + { while( !go_ahead ) { Sleep(1); } // changes threads } } #else - if (!go_ahead) { + if( s_initCount == 0) + { static pthread_mutex_t my_lock= PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&my_lock); { // Recheck now that we're within the lock // - if (!go_ahead) { + if (s_initCount == 0) + { init_once_LOCKED(L, &timer_deep, nbKeepers); - go_ahead= 1; + s_initCount = 1; } } pthread_mutex_unlock(&my_lock); @@ -2292,7 +2395,10 @@ luaopen_lanes( lua_State *L ) assert( timer_deep != 0 ); // Create main module interface table - lua_newtable(L); + lua_pushvalue( L, lua_upvalueindex( 2)); + // remove configure() (this function) from the module interface + lua_pushnil( L); + lua_setfield( L, -2, "configure"); luaL_register(L, NULL, lanes_functions); // metatable for threads @@ -2313,7 +2419,7 @@ luaopen_lanes( lua_State *L ) lua_setfield( L, -2, "join"); lua_pushcfunction( L, LG_thread_cancel); lua_setfield( L, -2, "cancel"); - lua_pushboolean( L, 0); + lua_pushliteral( L, "Lane"); lua_setfield( L, -2, "__metatable"); lua_pushcclosure( L, LG_thread_new, 1 ); // metatable as closure param @@ -2331,8 +2437,41 @@ luaopen_lanes( lua_State *L ) lua_pushlightuserdata( L, CANCEL_ERROR ); lua_setfield(L, -2, "cancel_error"); - // Return the local module table - return 1; + // register all native functions found in that module in the transferable functions database + // we process it before _G because we don't want to find the module when scanning _G (this would generate longer names) + populate_func_lookup_table( L, -1, name); + // record all existing C/JIT-fast functions + populate_func_lookup_table( L, LUA_GLOBALSINDEX, NULL); + // Return nothing + lua_pop( L, 1); + return 0; +} + +int +#if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) +__declspec(dllexport) +#endif +luaopen_lanes( lua_State *L ) +{ + // Create main module interface table + // we only have 1 closure, which must be called to configure Lanes + STACK_GROW( L, 3); + STACK_CHECK( L) + lua_newtable(L); + lua_pushvalue(L, 1); // module name + lua_pushvalue(L, -2); // module table + lua_pushcclosure( L, LG_configure, 2); + if( s_initCount == 0) + { + lua_setfield( L, -2, "configure"); + } + else // already initialized: call it mmediately and be done + { + lua_pushinteger( L, 666); // any value will do, it will be ignored + lua_call( L, 1, 0); + } + STACK_END( L, 1) + return 1; } diff --git a/src/lanes.lua b/src/lanes.lua index 252d151..8837e4b 100644 --- a/src/lanes.lua +++ b/src/lanes.lua @@ -39,11 +39,19 @@ THE SOFTWARE. =============================================================================== ]]-- -module( "lanes", package.seeall ) +-- Lua 5.1: module() creates a global variable +-- Lua 5.2: module() might go away +-- almost everything module() does is done by require() +-- -> simply create a table, populate it, return it, and be done +local lanes = {} + +lanes.configure = function( _nb_keepers, _timers) local mm = require "lua51-lanes" assert( type(mm)=="table" ) +-- configure() is available only the first time lua51-lanes is required process-wide, and we *must* call it to have the other functions in the interface +if mm.configure then mm.configure( _nb_keepers) end local thread_new = assert(mm.thread_new) @@ -140,6 +148,7 @@ end -- -- .globals: table of globals to set for a new thread (passed by value) -- +-- .required: table of packages to require -- ... (more options may be introduced later) ... -- -- Calling with a function parameter ('lane_func') ends the string/table @@ -161,7 +170,8 @@ local valid_libs= { ["*"]= true } -function gen( ... ) +-- PUBLIC LANES API +local function gen( ... ) local opt= {} local libs= nil local lev= 2 -- level for errors @@ -204,27 +214,34 @@ function gen( ... ) end end - local prio, cs, g_tbl, packagepath, packagecpath + local prio, cs, g_tbl, packagepath, packagecpath, required for k,v in pairs(opt) do if k=="priority" then prio= v - elseif k=="cancelstep" then cs= (v==true) and 100 or - (v==false) and 0 or - type(v)=="number" and v or - error( "Bad cancelstep: "..tostring(v), lev ) + elseif k=="cancelstep" then + cs = (v==true) and 100 or + (v==false) and 0 or + type(v)=="number" and v or + error( "Bad cancelstep: "..tostring(v), lev ) elseif k=="globals" then g_tbl= v - elseif k=="packagepath" then packagepath= v - elseif k=="packagecpath" then packagecpath= v + elseif k=="packagepath" then + packagepath = (type( v) == "string") and v or error( "Bad packagepath: " .. tostring( v), lev) + elseif k=="packagecpath" then + packagecpath = (type( v) == "string") and v or error( "Bad packagecpath: " .. tostring( v), lev) + elseif k=="required" then + required= (type( v) == "table") and v or error( "Bad required: " .. tostring( v), lev) --.. elseif k==1 then error( "unkeyed option: ".. tostring(v), lev ) else error( "Bad option: ".. tostring(k), lev ) end end + if not packagepath then packagepath = package.path end + if not packagecpath then packagecpath = package.cpath end -- Lane generator -- return function(...) - return thread_new( func, libs, cs, prio, g_tbl, packagepath, packagecpath, ...) -- args + return thread_new( func, libs, cs, prio, g_tbl, packagepath, packagecpath, required, ...) -- args end end @@ -235,12 +252,16 @@ end ----- -- lanes.linda() -> linda_ud -- -linda = mm.linda +-- PUBLIC LANES API +local linda = mm.linda ---=== Timers ===--- -local want_timers = true -if want_timers then + +-- PUBLIC LANES API +local timer = function() error "timers are not active" end + +if _timers ~= "NO_TIMERS" then local timer_gateway= assert( mm.timer_gateway ) -- @@ -424,6 +445,8 @@ if first_time then assert( key and wakeup_at and period ) set_timer( linda, key, wakeup_at, period>0 and period or nil ) + elseif secs == 0 then -- got no value while block-waiting? + WR( "timer lane: no linda, aborted?") end end end )() @@ -432,7 +455,8 @@ end ----- -- = timer( linda_h, key_val, date_tbl|first_secs [,period_secs] ) -- -function timer( linda, key, a, period ) +-- PUBLIC LANES API +timer = function( linda, key, a, period ) if a==0.0 then -- Caller expects to get current time stamp in Linda, on return @@ -456,7 +480,7 @@ function timer( linda, key, a, period ) timer_gateway:send( TGW_KEY, linda, key, wakeup_at, period ) end -end -- want_timers +end -- _timers ---=== Lock & atomic generators ===--- @@ -473,7 +497,8 @@ end -- want_timers -- Returns an access function that allows 'N' simultaneous entries between -- acquire (+M) and release (-M). For binary locks, use M==1. -- -function genlock( linda, key, N ) +-- PUBLIC LANES API +local function genlock( linda, key, N ) linda:limit(key,N) linda:set(key,nil) -- clears existing data @@ -506,7 +531,8 @@ end -- Returns an access function that allows atomic increment/decrement of the -- number in 'key'. -- -function genatomic( linda, key, initial_val ) +-- PUBLIC LANES API +local function genatomic( linda, key, initial_val ) linda:limit(key,2) -- value [,true] linda:set(key,initial_val or 0.0) -- clears existing data (also queue) @@ -522,4 +548,23 @@ end -- newuserdata = mm.newuserdata + -- activate full interface + lanes.gen = gen + lanes.linda = mm.linda + lanes.timer = timer + lanes.genlock = genlock + lanes.genatomic = genatomic + -- from now on, calling configure does nothing but checking that we don't call it with parameters that changed compared to the first invocation + lanes.configure = function( _nk, _t) + if _nk ~= _nb_keepers then + error( "mismatched configuration: " .. tostring( _nk) .. " keepers instead of " .. tostring( _nb_keepers)) + end + if _t ~= _timers then + error( "mismatched configuration: " .. tostring( _t) .. " timer activity instead of " .. tostring( _timers)) + end + end +end -- lanes.configure + --the end +return lanes + diff --git a/src/threading.c b/src/threading.c index 00be243..0a07d47 100644 --- a/src/threading.c +++ b/src/threading.c @@ -74,6 +74,7 @@ THE SOFTWARE. #if (defined PLATFORM_WIN32) || (defined PLATFORM_POCKETPC) static void FAIL( const char *funcname, int rc ) { fprintf( stderr, "%s() failed! (%d)\n", funcname, rc ); + __debugbreak(); // give a chance to the debugger! abort(); } #endif diff --git a/src/tools.c b/src/tools.c index f9854db..b412e84 100644 --- a/src/tools.c +++ b/src/tools.c @@ -31,6 +31,7 @@ THE SOFTWARE. */ #include "tools.h" +#include "keeper.h" #include "lualib.h" #include "lauxlib.h" @@ -66,7 +67,7 @@ void luaG_dump( lua_State* L ) { // enable it for more debugging. // STACK_CHECK(L) - STACK_GROW( L, 2 ) + STACK_GROW( L, 2); lua_getglobal( L, "tostring" ); // @@ -108,21 +109,302 @@ static const luaL_Reg libs[] = { static bool_t openlib( lua_State *L, const char *name, size_t len ) { - unsigned i; - bool_t all= strncmp( name, "*", len ) == 0; + unsigned i; + bool_t all= strncmp( name, "*", len ) == 0; - for( i=0; libs[i].name; i++ ) { - if (all || (strncmp(name, libs[i].name, len) ==0)) { - if (libs[i].func) { - STACK_GROW(L,2); - lua_pushcfunction( L, libs[i].func ); - lua_pushstring( L, libs[i].name ); - lua_call( L, 1, 0 ); - } - if (!all) return TRUE; - } - } - return all; + for( i=0; libs[i].name; i++ ) + { + if (all || (strncmp(name, libs[i].name, len) ==0)) + { + if (libs[i].func) + { + STACK_GROW(L,1); + STACK_CHECK(L) + lua_pushcfunction( L, libs[i].func); + // pushes the module table on the stack + lua_call( L, 0, 1); + populate_func_lookup_table( L, -1, libs[i].name); + // remove the module when we are done + lua_pop( L, 1); + STACK_END(L, 0) + } + if (!all) return TRUE; + } + } + return all; +} + +static int dummy_writer(lua_State *L, const void* p, size_t sz, void* ud) +{ + return 666; +} + + +/* + * differentiation between C, bytecode and JIT-fast functions + * + * + * +----------+------------+----------+ + * | bytecode | C function | JIT-fast | + * +-----------------+----------+------------+----------+ + * | lua_topointer | | | | + * +-----------------+----------+------------+----------+ + * | lua_tocfunction | NULL | | NULL | + * +-----------------+----------+------------+----------+ + * | lua_dump | 666 | 1 | 1 | + * +-----------------+----------+------------+----------+ + */ + +typedef enum +{ + FST_Bytecode, + FST_Native, + FST_FastJIT +} FuncSubType; + +FuncSubType luaG_getfuncsubtype( lua_State *L, int _i) +{ + if( lua_tocfunction( L, _i)) + { + return FST_Native; + } + { + int mustpush = 0, dumpres; + if( STACK_ABS( L, _i) != lua_gettop( L)) + { + lua_pushvalue( L, _i); + mustpush = 1; + } + // the provided writer fails with code 666 + // therefore, anytime we get 666, this means that lua_dump() attempted a dump + // all other cases mean this is either a C or LuaJIT-fast function + dumpres = lua_dump( L, dummy_writer, NULL); + lua_pop( L, mustpush); + if( dumpres == 666) + { + return FST_Bytecode; + } + } + return FST_FastJIT; +} + +static lua_CFunction luaG_tocfunction( lua_State *L, int _i, FuncSubType *_out) +{ + lua_CFunction p = lua_tocfunction( L, _i); + *_out = luaG_getfuncsubtype( L, _i); + return p; +} + + +#define LOOKUP_KEY "ddea37aa-50c7-4d3f-8e0b-fb7a9d62bac5" +#define LOOKUP_KEY_CACHE "d1059270-4976-4193-a55b-c952db5ab7cd" + + +// inspired from tconcat() in ltablib.c +static char const * luaG_pushFQN(lua_State *L, int t, int last) +{ + int i = 1; + luaL_Buffer b; + STACK_CHECK( L) + luaL_buffinit(L, &b); + for( ; i < last; i++) + { + lua_rawgeti( L, t, i); + luaL_addvalue( &b); + luaL_addlstring(&b, ".", 1); + } + if (i == last) /* add last value (if interval was not empty) */ + { + lua_rawgeti( L, t, i); + luaL_addvalue( &b); + } + luaL_pushresult( &b); + STACK_END( L, 1) + return lua_tostring( L, -1); +} + + +static void populate_func_lookup_table_recur( lua_State *L, int _ctx_base, int _i, int _depth) +{ + lua_Integer visit_count; + // slot 1 in the stack contains the table that receives everything we found + int const dest = _ctx_base; + // slot 2 contains a table that, when concatenated, produces the fully qualified name of scanned elements in the table provided at slot _i + int const fqn = _ctx_base + 1; + // slot 3 contains a cache that stores all already visited tables to avoid infinite recursion loops + int const cache = _ctx_base + 2; + // we need to remember subtables to process them after functions encountered at the current depth (breadth-first search) + int const breadth_first_cache = lua_gettop( L) + 1; + + STACK_GROW( L, 6); + // slot _i contains a table where we search for functions + STACK_CHECK( L) // ... {_i} + + // if table is already visited, we are done + lua_pushvalue( L, _i); // ... {_i} {} + lua_rawget( L, cache); // ... {_i} nil|n + visit_count = lua_tointeger( L, -1); // 0 if nil, else n + lua_pop( L, 1); // ... {_i} + STACK_MID( L, 0) + if( visit_count > 0) + { + return; + } + + // remember we visited this table (1-visit count) + lua_pushvalue( L, _i); // ... {_i} {} + lua_pushinteger( L, visit_count + 1); // ... {_i} {} 1 + lua_rawset( L, cache); // ... {_i} + STACK_MID( L, 0) + + // this table is at breadth_first_cache index + lua_newtable( L); // ... {_i} {bfc} + ASSERT_L( lua_gettop( L) == breadth_first_cache); + // iterate over all entries in the processed table + lua_pushnil( L); // ... {_i} {bfc} nil + while( lua_next( L, _i) != 0) // ... {_i} {bfc} k v + { + // just for debug, not actually needed + //char const * key = (lua_type( L, -2) == LUA_TSTRING) ? lua_tostring( L, -2) : "not a string"; + // subtable: process it recursively + if( lua_istable( L, -1)) // ... {_i} {bfc} k {} + { + // increment visit count to make sure we will actually scan it at this recursive level + lua_pushvalue( L, -1); // ... {_i} {bfc} k {} {} + lua_pushvalue( L, -1); // ... {_i} {bfc} k {} {} {} + lua_rawget( L, cache); // ... {_i} {bfc} k {} {} n? + visit_count = lua_tointeger( L, -1) + 1; // 1 if we got nil, else n+1 + lua_pop( L, 1); // ... {_i} {bfc} k {} {} + lua_pushinteger( L, visit_count); // ... {_i} {bfc} k {} {} n + lua_rawset( L, cache); // ... {_i} {bfc} k {} + // store the table in the breadth-first cache + lua_pushvalue( L, -2); // ... {_i} {bfc} k {} k + lua_insert( L, -2); // ... {_i} {bfc} k k {} + lua_rawset( L, breadth_first_cache); // ... {_i} {bfc} k + STACK_MID( L, 2) + } + else if( lua_isfunction( L, -1)) // ... {_i} {bfc} k func + { + if( luaG_getfuncsubtype( L, -1) != FST_Bytecode) + { + char const *fqnString; + bool_t not_registered; + // first, skip everything if the function is already known + lua_pushvalue( L, -1); // ... {_i} {bfc} k func func + lua_rawget( L, dest); // ... {_i} {bfc} k func name? + not_registered = lua_isnil( L, -1); + lua_pop( L, 1); // ... {_i} {bfc} k func + if( not_registered) + { + ++ _depth; + // push function name in fqn stack (note that concatenation will crash if name is a not string!) + lua_pushvalue( L, -2); // ... {_i} {bfc} k func k + lua_rawseti( L, fqn, _depth); // ... {_i} {bfc} k func + // generate name + fqnString = luaG_pushFQN( L, fqn, _depth); // ... {_i} {bfc} k func "f.q.n" + //puts( fqnString); + // prepare the stack for database feed + lua_pushvalue( L, -1); // ... {_i} {bfc} k func "f.q.n" "f.q.n" + lua_pushvalue( L, -3); // ... {_i} {bfc} k func "f.q.n" "f.q.n" func + // t["f.q.n"] = func + lua_rawset( L, dest); // ... {_i} {bfc} k func "f.q.n" + // t[func] = "f.q.n" + lua_rawset( L, dest); // ... {_i} {bfc} k + // remove table name from fqn stack + lua_pushnil( L); // ... {_i} {bfc} k nil + lua_rawseti( L, fqn, _depth); // ... {_i} {bfc} k + -- _depth; + } + else + { + lua_pop( L, 1); // ... {_i} {bfc} k + } + } + else + { + lua_pop( L, 1); // ... {_i} {bfc} k + } + } + else + { + lua_pop( L, 1); // ... {_i} {bfc} k + } + STACK_MID( L, 2) + } + // now process the tables we encountered at that depth + ++ _depth; + lua_pushnil( L); // ... {_i} {bfc} nil + while( lua_next( L, breadth_first_cache) != 0) // ... {_i} {bfc} k {} + { + // un-visit this table in case we do need to process it + lua_pushvalue( L, -1); // ... {_i} {bfc} k {} {} + lua_rawget( L, cache); // ... {_i} {bfc} k {} n + ASSERT_L( lua_type( L, -1) == LUA_TNUMBER); + visit_count = lua_tointeger( L, -1) - 1; + lua_pop( L, 1); // ... {_i} {bfc} k {} + lua_pushvalue( L, -1); // ... {_i} {bfc} k {} {} + if( visit_count > 0) + { + lua_pushinteger( L, visit_count); // ... {_i} {bfc} k {} {} n + } + else + { + lua_pushnil( L); // ... {_i} {bfc} k {} {} nil + } + lua_rawset( L, cache); // ... {_i} {bfc} k {} + // push table name in fqn stack (note that concatenation will crash if name is a not string!) + lua_pushvalue( L, -2); // ... {_i} {bfc} k {} k + lua_rawseti( L, fqn, _depth); // ... {_i} {bfc} k {} + populate_func_lookup_table_recur( L, _ctx_base, lua_gettop( L), _depth); // ... {_i} {bfc} k {} + lua_pop( L, 1); // ... {_i} {bfc} k + STACK_MID( L, 2) + } + // remove table name from fqn stack + lua_pushnil( L); // ... {_i} {bfc} nil + lua_rawseti( L, fqn, _depth); // ... {_i} {bfc} + -- _depth; + // we are done with our cache + lua_pop( L, 1); // ... {_i} + STACK_END( L, 0) + // we are done // ... {_i} {bfc} +} + +/* + * create a "fully.qualified.name" <-> function equivalence dabase + */ +void populate_func_lookup_table( lua_State *L, int _i, char const *_name) +{ + int const ctx_base = lua_gettop( L) + 1; + int const in_base = STACK_ABS( L, _i); + int const start_depth = _name ? 1 : 0; + //printf( "%p: populate_func_lookup_table('%s')\n", L, _name ? _name : "NULL"); + STACK_GROW( L, 3); + STACK_CHECK( L) + lua_getfield( L, LUA_REGISTRYINDEX, LOOKUP_KEY); // {}? + if( lua_isnil( L, -1)) // nil + { + lua_pop( L, 1); // + lua_newtable( L); // {} + lua_pushvalue( L, -1); // {} {} + lua_setfield( L, LUA_REGISTRYINDEX, LOOKUP_KEY); // {} + } + lua_newtable( L); // {} {fqn} + if( _name) + { + lua_pushstring( L, _name); // {} {fqn} "name" + lua_rawseti( L, -2, start_depth); // {} {fqn} + } + lua_getfield( L, LUA_REGISTRYINDEX, LOOKUP_KEY_CACHE); // {} {fqn} {cache}? + if( lua_isnil( L, -1)) + { + lua_pop( L, 1); // {} {fqn} + lua_newtable( L); // {} {fqn} {cache} + lua_pushvalue( L, -1); // {} {fqn} {cache} {cache} + lua_setfield( L, LUA_REGISTRYINDEX, LOOKUP_KEY_CACHE); // {} {fqn} {cache} + } + populate_func_lookup_table_recur( L, ctx_base, in_base, start_depth); // {...} {fqn} {cache} + lua_pop( L, 3); + STACK_END( L, 0) } /* @@ -139,33 +421,39 @@ static bool_t openlib( lua_State *L, const char *name, size_t len ) { */ #define is_name_char(c) (isalpha(c) || (c)=='*') -const char *luaG_openlibs( lua_State *L, const char *libs ) { - const char *p; - unsigned len; +const char *luaG_openlibs( lua_State *L, const char *libs) +{ + const char *p; + unsigned len; if (!libs) return NULL; // no libs, not even 'base' - // 'lua.c' stops GC during initialization so perhaps its a good idea. :) - // - lua_gc(L, LUA_GCSTOP, 0); + // 'lua.c' stops GC during initialization so perhaps its a good idea. :) + // + lua_gc( L, LUA_GCSTOP, 0); - // Anything causes 'base' to be taken in - // - STACK_GROW(L,2); - lua_pushcfunction( L, luaopen_base ); - lua_pushliteral( L, "" ); - lua_call( L, 1, 0 ); - - for( p= libs; *p; p+=len ) { - len=0; - while (*p && !is_name_char(*p)) p++; // bypass delimiters - while (is_name_char(p[len])) len++; // bypass name - if (len && (!openlib( L, p, len ))) - break; - } - lua_gc(L, LUA_GCRESTART, 0); + // Anything causes 'base' to be taken in + // + STACK_GROW(L,2); + STACK_CHECK(L) + lua_pushcfunction( L, luaopen_base); + lua_call( L, 0, 1); + // after opening base, register the functions they exported in our name<->function database + populate_func_lookup_table( L, LUA_GLOBALSINDEX, NULL); + lua_pop( L, 1); + STACK_MID( L, 0); + for( p= libs; *p; p+=len ) + { + len=0; + while (*p && !is_name_char(*p)) p++; // bypass delimiters + while (is_name_char(p[len])) len++; // bypass name + if (len && (!openlib( L, p, len ))) + break; + } + STACK_END(L,0) + lua_gc(L, LUA_GCRESTART, 0); - return *p ? p : NULL; + return *p ? p : NULL; } @@ -284,7 +572,7 @@ luaG_IdFunction get_idfunc( lua_State *L, int index ) { luaG_IdFunction ret; - index= STACK_ABS(L,index); + index = STACK_ABS( L, index); STACK_GROW(L,1); @@ -696,7 +984,7 @@ uint_t get_mt_id( lua_State *L, int i ) { static uint_t last_id= 0; uint_t id; - i= STACK_ABS(L,i); + i = STACK_ABS( L, i); STACK_GROW(L,3); @@ -819,8 +1107,8 @@ static void inter_copy_func( lua_State *L2, uint_t L2_cache_i, lua_State *L, uin static void push_cached_func( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i ) { + void * const aspointer = (void*)lua_topointer( L, i ); // TBD: Merge this and same code for tables - ASSERT_L( L2_cache_i != 0 ); STACK_GROW(L2,3); @@ -832,7 +1120,7 @@ static void push_cached_func( lua_State *L2, uint_t L2_cache_i, lua_State *L, ui // We don't need to use the from state ('L') in ID since the life span // is only for the duration of a copy (both states are locked). // - lua_pushlightuserdata( L2, (void*)lua_topointer( L, i )); // push a light userdata uniquely representing the function + lua_pushlightuserdata( L2, aspointer); // push a light userdata uniquely representing the function //fprintf( stderr, "<< ID: %s >>\n", lua_tostring(L2,-1) ); @@ -886,9 +1174,46 @@ static void push_cached_func( lua_State *L2, uint_t L2_cache_i, lua_State *L, ui // // L2 [-1]: function - ASSERT_L( lua_isfunction(L2,-1) ); + ASSERT_L( lua_isfunction(L2,-1)); } +/* +* Push a looked-up native/LuaJIT function. +*/ +static void lookup_native_func( lua_State *L2, lua_State *L, uint_t i) +{ + char const *fqn; + size_t len; + _ASSERT_L( L, lua_isfunction( L, i)); + STACK_CHECK( L) + STACK_CHECK( L2) + // fetch the name from the source state's lookup table + lua_getfield( L, LUA_REGISTRYINDEX, LOOKUP_KEY); // {} + _ASSERT_L( L, lua_istable( L, -1)); + lua_pushvalue( L, i); // {} f + lua_rawget( L, -2); // {} "f.q.n" + fqn = lua_tolstring( L, -1, &len); + if( !fqn) + { + luaL_error( L, "function not found in origin transfer database."); + } + // push the equivalent function in the destination's stack, retrieved from the lookup table + lua_getfield( L2, LUA_REGISTRYINDEX, LOOKUP_KEY); // {} + _ASSERT_L( L2, lua_istable( L2, -1)); + lua_pushlstring( L2, fqn, len); // {} "f.q.n" + lua_pop( L, 2); // + lua_rawget( L2, -2); // {} f + if( !lua_isfunction( L2, -1)) + { + // yarglah: luaL_error formatting doesn't support string width modifier! + char message[256]; + sprintf( message, "function %*s not found in destination transfer database.", len, fqn); + luaL_error( L, message); + } + lua_remove( L2, -2); // f + STACK_END( L2, 1) + STACK_END( L, 0) +} #define LOG_FUNC_INFO 0 @@ -900,114 +1225,132 @@ enum e_vt { }; static bool_t inter_copy_one_( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i, enum e_vt value_type ); -static void inter_copy_func( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i ) { - - lua_CFunction cfunc= lua_tocfunction( L,i ); - unsigned n; - - ASSERT_L( L2_cache_i != 0 ); - - STACK_GROW(L,2); - - STACK_CHECK(L) - if (!cfunc) { // Lua function - luaL_Buffer b; - const char *s; - size_t sz; - int tmp; - const char *name= NULL; - int linedefined = 0; +static void inter_copy_func( lua_State *L2, uint_t L2_cache_i, lua_State *L, uint_t i ) +{ + FuncSubType funcSubType; + lua_CFunction cfunc = luaG_tocfunction( L, i, &funcSubType); // NULL for LuaJIT-fast && bytecode functions + i = STACK_ABS( L, i); -#if LOG_FUNC_INFO - // "To get information about a function you push it onto the - // stack and start the what string with the character '>'." - // - { lua_Debug ar; - lua_pushvalue( L, i ); - lua_getinfo(L, ">nS", &ar); // fills 'name' 'namewhat' and 'linedefined', pops function - name= ar.namewhat; - linedefined = ar.linedefined; - fprintf( stderr, "NAME: %s @ %d\n", ar.short_src, linedefined); // just gives NULL - } -#endif // LOG_FUNC_INFO - // 'lua_dump()' needs the function at top of stack - // - if (i!=-1) lua_pushvalue( L, i ); + ASSERT_L( L2_cache_i != 0 ); + STACK_GROW(L,2); + STACK_CHECK(L) - luaL_buffinit(L,&b); - tmp= lua_dump(L, buf_writer, &b); - ASSERT_L(tmp==0); - // - // "value returned is the error code returned by the last call - // to the writer" (and we only return 0) + if( funcSubType == FST_Bytecode) + { + unsigned n; + luaL_Buffer b; + // 'lua_dump()' needs the function at top of stack + // if already on top of the stack, no need to push again + int needToPush = (i != lua_gettop( L)); + if( needToPush) + lua_pushvalue( L, i); + + luaL_buffinit( L, &b); + // + // "value returned is the error code returned by the last call + // to the writer" (and we only return 0) + // not sure this could ever fail but for memory shortage reasons + if( lua_dump( L, buf_writer, &b) != 0) + { + luaL_error( L, "internal error: function dump failed."); + } - luaL_pushresult(&b); // pushes dumped string on 'L' - s= lua_tolstring(L,-1,&sz); - ASSERT_L( s && sz ); + luaL_pushresult( &b); // pushes dumped string on 'L' - if (i!=-1) lua_remove( L, -2 ); + // if not pushed, no need to pop + if( needToPush) + { + lua_remove( L, -2); + } - // Note: Line numbers seem to be taken precisely from the - // original function. 'name' is not used since the chunk - // is precompiled (it seems...). - // - // TBD: Can we get the function's original name through, as well? - // - if (luaL_loadbuffer(L2, s, sz, name) != 0) { - // chunk is precompiled so only LUA_ERRMEM can happen - // "Otherwise, it pushes an error message" - // - STACK_GROW( L,1 ); - luaL_error( L, "%s", lua_tostring(L2,-1) ); - } - lua_pop(L,1); // remove the dumped string - STACK_MID(L,0) - } + // transfer the bytecode, then the upvalues, to create a similar closure + { + const char *name= NULL; + + #if LOG_FUNC_INFO + // "To get information about a function you push it onto the + // stack and start the what string with the character '>'." + // + { + lua_Debug ar; + lua_pushvalue( L, i ); + lua_getinfo(L, ">nS", &ar); // fills 'name' 'namewhat' and 'linedefined', pops function + name= ar.namewhat; + fprintf( stderr, "NAME: %s @ %d\n", ar.short_src, ar.linedefined); // just gives NULL + } + #endif // LOG_FUNC_INFO + { + const char *s; + size_t sz; + s = lua_tolstring( L, -1, &sz); + ASSERT_L( s && sz); + + // Note: Line numbers seem to be taken precisely from the + // original function. 'name' is not used since the chunk + // is precompiled (it seems...). + // + // TBD: Can we get the function's original name through, as well? + // + if (luaL_loadbuffer(L2, s, sz, name) != 0) + { + // chunk is precompiled so only LUA_ERRMEM can happen + // "Otherwise, it pushes an error message" + // + STACK_GROW( L,1); + luaL_error( L, "%s", lua_tostring(L2,-1)); + } + lua_pop( L, 1); // remove the dumped string + } + STACK_MID( L, 0) + + /* push over any upvalues; references to this function will come from + * cache so we don't end up in eternal loop. + */ + for( n=0; lua_getupvalue( L, i, 1+n ) != NULL; n++ ) + { + if ((!cfunc) && lua_equal(L,i,-1)) + { + /* Lua closure that has a (recursive) upvalue to itself + */ + lua_pushvalue( L2, -((int)n)-1 ); + } + else + { + if( !inter_copy_one_( L2, L2_cache_i, L, lua_gettop(L), VT_NORMAL)) + luaL_error( L, "Cannot copy upvalue type '%s'", luaG_typename( L, -1)); + } + lua_pop( L, 1); + } + // L2: function + 'n' upvalues (>=0) + + STACK_MID(L,0) + + // Set upvalues (originally set to 'nil' by 'lua_load') + { + int func_index = lua_gettop( L2) - n; + for( ; n > 0; -- n) + { + char const *rc = lua_setupvalue( L2, func_index, n); + // + // "assigns the value at the top of the stack to the upvalue and returns its name. + // It also pops the value from the stack." + + ASSERT_L(rc); // not having enough slots? + } + } + } + } + else // C function OR LuaJIT fast function!!! + { #if LOG_FUNC_INFO - else - { - fprintf( stderr, "NAME: [C] function %p \n", cfunc); - } + fprintf( stderr, "NAME: [C] function %p \n", cfunc); #endif // LOG_FUNC_INFO - - /* push over any upvalues; references to this function will come from - * cache so we don't end up in eternal loop. - */ - for( n=0; lua_getupvalue( L, i, 1+n ) != NULL; n++ ) { - if ((!cfunc) && lua_equal(L,i,-1)) { - /* Lua closure that has a (recursive) upvalue to itself - */ - lua_pushvalue( L2, -((int)n)-1 ); - } else { - if (!inter_copy_one_( L2, L2_cache_i, L, lua_gettop(L), VT_NORMAL )) - luaL_error( L, "Cannot copy upvalue type '%s'", luaG_typename(L,-1) ); - } - lua_pop(L,1); - } - // L2: function + 'n' upvalues (>=0) - - STACK_MID(L,0) - - if (cfunc) { - lua_pushcclosure( L2, cfunc, n ); // eats up upvalues - } else { - // Set upvalues (originally set to 'nil' by 'lua_load') - // - int func_index= lua_gettop(L2)-n; - - for( ; n>0; n-- ) { - const char *rc= lua_setupvalue( L2, func_index, n ); - // - // "assigns the value at the top of the stack to the upvalue and returns its name. - // It also pops the value from the stack." - - ASSERT_L(rc); // not having enough slots? - } - } - STACK_END(L,0) + // No need to transfer upvalues for C/JIT functions since they weren't actually copied, only looked up + lookup_native_func( L2, L, i); + } + STACK_END(L,0) } - /* * Copies a value from 'L' state (at index 'i') to 'L2' state. Does not remove * the original value. @@ -1300,32 +1643,36 @@ MUTEX_T require_cs; // // Upvalues: [1]: original 'require' function // -static int new_require( lua_State *L ) +static int new_require( lua_State *L) { - int rc; - int args= lua_gettop(L); + int rc, i; + int args = lua_gettop( L); + //char const *modname = luaL_checkstring( L, 1); - STACK_GROW(L,1); - STACK_CHECK(L) + STACK_GROW( L, args + 1); + STACK_CHECK( L) + + lua_pushvalue( L, lua_upvalueindex(1)); + for( i = 1; i <= args; ++ i) + lua_pushvalue( L, i); // Using 'lua_pcall()' to catch errors; otherwise a failing 'require' would // leave us locked, blocking any future 'require' calls from other lanes. // MUTEX_LOCK( &require_cs); { - lua_pushvalue( L, lua_upvalueindex(1) ); - lua_insert( L, 1 ); - - rc= lua_pcall( L, args, 1 /*retvals*/, 0 /*errfunc*/ ); + rc = lua_pcall( L, args, 1 /*retvals*/, 0 /*errfunc*/ ); // // LUA_ERRRUN / LUA_ERRMEM } MUTEX_UNLOCK( &require_cs); + // the required module (or an error message) is left on the stack as returned value by original require function + STACK_END( L, 1) + if (rc) lua_error(L); // error message already at [-1] - STACK_END(L,0) return 1; } diff --git a/src/tools.h b/src/tools.h index a080257..1c9b00a 100644 --- a/src/tools.h +++ b/src/tools.h @@ -10,10 +10,10 @@ #include -// Note: The < -10000 test is to leave registry/global/upvalue indices untouched +// Note: The < LUA_REGISTRYINDEX test is to leave registry/global/upvalue indices untouched // #define /*int*/ STACK_ABS(L,n) \ - ( ((n) >= 0 || (n) <= -10000) ? (n) : lua_gettop(L) +(n) +1 ) + ( ((n) >= 0 || (n) <= LUA_REGISTRYINDEX) ? (n) : lua_gettop(L) +(n) +1 ) #ifdef NDEBUG #define _ASSERT_L(lua,c) /*nothing*/ @@ -24,7 +24,7 @@ #define DEBUG() /*nothing*/ #define DEBUGEXEC(_code) {} /*nothing*/ #else - #define _ASSERT_L(lua,c) { if (!(c)) luaL_error( lua, "ASSERT failed: %s:%d '%s'", __FILE__, __LINE__, #c ); } + #define _ASSERT_L(lua,c) do { if (!(c)) luaL_error( lua, "ASSERT failed: %s:%d '%s'", __FILE__, __LINE__, #c ); } while( 0) // #define STACK_CHECK(L) { int _oldtop_##L = lua_gettop(L); #define STACK_MID(L,change) { int a= lua_gettop(L)-_oldtop_##L; int b= (change); \ @@ -37,7 +37,7 @@ #endif #define ASSERT_L(c) _ASSERT_L(L,c) -#define STACK_GROW(L,n) { if (!lua_checkstack(L,n)) luaL_error( L, "Cannot grow stack!" ); } +#define STACK_GROW(L,n) do { if (!lua_checkstack(L,n)) luaL_error( L, "Cannot grow stack!" ); } while( 0) #define LUAG_FUNC( func_name ) static int LG_##func_name( lua_State *L ) @@ -72,6 +72,7 @@ int luaG_inter_move( lua_State *L, lua_State *L2, uint_t n); extern MUTEX_T deep_lock; extern MUTEX_T mtid_lock; +void populate_func_lookup_table( lua_State *L, int _i, char const *_name); void serialize_require( lua_State *L); extern MUTEX_T require_cs; -- cgit v1.2.3-55-g6feb