diff options
Diffstat (limited to 'src/state.cpp')
-rw-r--r-- | src/state.cpp | 449 |
1 files changed, 449 insertions, 0 deletions
diff --git a/src/state.cpp b/src/state.cpp new file mode 100644 index 0000000..4a5f995 --- /dev/null +++ b/src/state.cpp | |||
@@ -0,0 +1,449 @@ | |||
1 | /* | ||
2 | * STATE.CPP | ||
3 | * | ||
4 | * Lua tools to support Lanes. | ||
5 | */ | ||
6 | |||
7 | /* | ||
8 | =============================================================================== | ||
9 | |||
10 | Copyright (C) 2002-10 Asko Kauppi <akauppi@gmail.com> | ||
11 | 2011-24 benoit Germain <bnt.germain@gmail.com> | ||
12 | |||
13 | Permission is hereby granted, free of charge, to any person obtaining a copy | ||
14 | of this software and associated documentation files (the "Software"), to deal | ||
15 | in the Software without restriction, including without limitation the rights | ||
16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
17 | copies of the Software, and to permit persons to whom the Software is | ||
18 | furnished to do so, subject to the following conditions: | ||
19 | |||
20 | The above copyright notice and this permission notice shall be included in | ||
21 | all copies or substantial portions of the Software. | ||
22 | |||
23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
29 | THE SOFTWARE. | ||
30 | |||
31 | =============================================================================== | ||
32 | */ | ||
33 | |||
34 | #include "state.h" | ||
35 | |||
36 | #include "lanes.h" | ||
37 | #include "tools.h" | ||
38 | #include "universe.h" | ||
39 | |||
40 | // ################################################################################################ | ||
41 | |||
42 | /*---=== Serialize require ===--- | ||
43 | */ | ||
44 | |||
45 | //--- | ||
46 | // [val,...]= new_require( ... ) | ||
47 | // | ||
48 | // Call 'old_require' but only one lane at a time. | ||
49 | // | ||
50 | // Upvalues: [1]: original 'require' function | ||
51 | // | ||
52 | [[nodiscard]] static int luaG_new_require(lua_State* L) | ||
53 | { | ||
54 | int rc; | ||
55 | int const args = lua_gettop( L); // args | ||
56 | Universe* U = universe_get( L); | ||
57 | //char const* modname = luaL_checkstring( L, 1); | ||
58 | |||
59 | STACK_GROW( L, 1); | ||
60 | |||
61 | lua_pushvalue( L, lua_upvalueindex( 1)); // args require | ||
62 | lua_insert( L, 1); // require args | ||
63 | |||
64 | // Using 'lua_pcall()' to catch errors; otherwise a failing 'require' would | ||
65 | // leave us locked, blocking any future 'require' calls from other lanes. | ||
66 | |||
67 | U->require_cs.lock(); | ||
68 | // starting with Lua 5.4, require may return a second optional value, so we need LUA_MULTRET | ||
69 | rc = lua_pcall( L, args, LUA_MULTRET, 0 /*errfunc*/ ); // err|result(s) | ||
70 | U->require_cs.unlock(); | ||
71 | |||
72 | // the required module (or an error message) is left on the stack as returned value by original require function | ||
73 | |||
74 | if( rc != LUA_OK) // LUA_ERRRUN / LUA_ERRMEM ? | ||
75 | { | ||
76 | raise_lua_error(L); | ||
77 | } | ||
78 | // should be 1 for Lua <= 5.3, 1 or 2 starting with Lua 5.4 | ||
79 | return lua_gettop(L); // result(s) | ||
80 | } | ||
81 | |||
82 | // ################################################################################################# | ||
83 | |||
84 | /* | ||
85 | * Serialize calls to 'require', if it exists | ||
86 | */ | ||
87 | void serialize_require(DEBUGSPEW_PARAM_COMMA( Universe* U) lua_State* L) | ||
88 | { | ||
89 | STACK_GROW(L, 1); | ||
90 | STACK_CHECK_START_REL(L, 0); | ||
91 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "serializing require()\n" INDENT_END)); | ||
92 | |||
93 | // Check 'require' is there and not already wrapped; if not, do nothing | ||
94 | // | ||
95 | lua_getglobal(L, "require"); | ||
96 | if (lua_isfunction(L, -1) && lua_tocfunction(L, -1) != luaG_new_require) | ||
97 | { | ||
98 | // [-1]: original 'require' function | ||
99 | lua_pushcclosure(L, luaG_new_require, 1 /*upvalues*/); | ||
100 | lua_setglobal(L, "require"); | ||
101 | } | ||
102 | else | ||
103 | { | ||
104 | // [-1]: nil | ||
105 | lua_pop(L, 1); | ||
106 | } | ||
107 | |||
108 | STACK_CHECK(L, 0); | ||
109 | } | ||
110 | |||
111 | // ################################################################################################ | ||
112 | |||
113 | /*---=== luaG_newstate ===---*/ | ||
114 | |||
115 | [[nodiscard]] static int require_lanes_core(lua_State* L) | ||
116 | { | ||
117 | // leaves a copy of 'lanes.core' module table on the stack | ||
118 | luaL_requiref( L, "lanes.core", luaopen_lanes_core, 0); | ||
119 | return 1; | ||
120 | } | ||
121 | |||
122 | // ################################################################################################# | ||
123 | |||
124 | static luaL_Reg const libs[] = | ||
125 | { | ||
126 | { LUA_LOADLIBNAME, luaopen_package}, | ||
127 | { LUA_TABLIBNAME, luaopen_table}, | ||
128 | { LUA_STRLIBNAME, luaopen_string}, | ||
129 | { LUA_MATHLIBNAME, luaopen_math}, | ||
130 | #ifndef PLATFORM_XBOX // no os/io libs on xbox | ||
131 | { LUA_OSLIBNAME, luaopen_os}, | ||
132 | { LUA_IOLIBNAME, luaopen_io}, | ||
133 | #endif // PLATFORM_XBOX | ||
134 | #if LUA_VERSION_NUM >= 503 | ||
135 | { LUA_UTF8LIBNAME, luaopen_utf8}, | ||
136 | #endif | ||
137 | #if LUA_VERSION_NUM >= 502 | ||
138 | #ifdef luaopen_bit32 | ||
139 | { LUA_BITLIBNAME, luaopen_bit32}, | ||
140 | #endif | ||
141 | { LUA_COLIBNAME, luaopen_coroutine}, // Lua 5.2: coroutine is no longer a part of base! | ||
142 | #else // LUA_VERSION_NUM | ||
143 | { LUA_COLIBNAME, nullptr }, // Lua 5.1: part of base package | ||
144 | #endif // LUA_VERSION_NUM | ||
145 | { LUA_DBLIBNAME, luaopen_debug}, | ||
146 | #if LUAJIT_FLAVOR() != 0 // building against LuaJIT headers, add some LuaJIT-specific libs | ||
147 | //#pragma message( "supporting JIT base libs") | ||
148 | { LUA_BITLIBNAME, luaopen_bit}, | ||
149 | { LUA_JITLIBNAME, luaopen_jit}, | ||
150 | { LUA_FFILIBNAME, luaopen_ffi}, | ||
151 | #endif // LUAJIT_FLAVOR() | ||
152 | |||
153 | { LUA_DBLIBNAME, luaopen_debug}, | ||
154 | { "lanes.core", require_lanes_core}, // So that we can open it like any base library (possible since we have access to the init function) | ||
155 | // | ||
156 | { "base", nullptr }, // ignore "base" (already acquired it) | ||
157 | { nullptr, nullptr } | ||
158 | }; | ||
159 | |||
160 | // ################################################################################################# | ||
161 | |||
162 | static void open1lib(DEBUGSPEW_PARAM_COMMA(Universe* U) lua_State* L, char const* name_, size_t len_) | ||
163 | { | ||
164 | int i; | ||
165 | for( i = 0; libs[i].name; ++ i) | ||
166 | { | ||
167 | if( strncmp( name_, libs[i].name, len_) == 0) | ||
168 | { | ||
169 | lua_CFunction libfunc = libs[i].func; | ||
170 | name_ = libs[i].name; // note that the provided name_ doesn't necessarily ends with '\0', hence len_ | ||
171 | if (libfunc != nullptr) | ||
172 | { | ||
173 | bool const isLanesCore{ libfunc == require_lanes_core }; // don't want to create a global for "lanes.core" | ||
174 | DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "opening %.*s library\n" INDENT_END, (int) len_, name_)); | ||
175 | STACK_CHECK_START_REL(L, 0); | ||
176 | // open the library as if through require(), and create a global as well if necessary (the library table is left on the stack) | ||
177 | luaL_requiref( L, name_, libfunc, !isLanesCore); | ||
178 | // lanes.core doesn't declare a global, so scan it here and now | ||
179 | if( isLanesCore == true) | ||
180 | { | ||
181 | populate_func_lookup_table( L, -1, name_); | ||
182 | } | ||
183 | lua_pop( L, 1); | ||
184 | STACK_CHECK( L, 0); | ||
185 | } | ||
186 | break; | ||
187 | } | ||
188 | } | ||
189 | } | ||
190 | |||
191 | // ################################################################################################# | ||
192 | |||
193 | // just like lua_xmove, args are (from, to) | ||
194 | static void copy_one_time_settings(Universe* U, Source L, Dest L2) | ||
195 | { | ||
196 | STACK_GROW(L, 2); | ||
197 | STACK_CHECK_START_REL(L, 0); | ||
198 | STACK_CHECK_START_REL(L2, 0); | ||
199 | |||
200 | DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "copy_one_time_settings()\n" INDENT_END)); | ||
201 | DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); | ||
202 | |||
203 | CONFIG_REGKEY.pushValue(L); // config | ||
204 | // copy settings from from source to destination registry | ||
205 | if (luaG_inter_move(U, L, L2, 1, LookupMode::LaneBody) != InterCopyResult::Success) // // config | ||
206 | { | ||
207 | luaL_error( L, "failed to copy settings when loading lanes.core"); // doesn't return | ||
208 | } | ||
209 | // set L2:_R[CONFIG_REGKEY] = settings | ||
210 | CONFIG_REGKEY.setValue(L2, [](lua_State* L) { lua_insert(L, -2); }); // config | ||
211 | STACK_CHECK(L2, 0); | ||
212 | STACK_CHECK(L, 0); | ||
213 | DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); | ||
214 | } | ||
215 | |||
216 | // ################################################################################################# | ||
217 | |||
218 | void initialize_on_state_create( Universe* U, lua_State* L) | ||
219 | { | ||
220 | STACK_CHECK_START_REL(L, 1); // settings | ||
221 | lua_getfield(L, -1, "on_state_create"); // settings on_state_create|nil | ||
222 | if( !lua_isnil(L, -1)) | ||
223 | { | ||
224 | // store C function pointer in an internal variable | ||
225 | U->on_state_create_func = lua_tocfunction(L, -1); // settings on_state_create | ||
226 | if (U->on_state_create_func != nullptr) | ||
227 | { | ||
228 | // make sure the function doesn't have upvalues | ||
229 | char const* upname = lua_getupvalue(L, -1, 1); // settings on_state_create upval? | ||
230 | if (upname != nullptr) // should be "" for C functions with upvalues if any | ||
231 | { | ||
232 | (void) luaL_error(L, "on_state_create shouldn't have upvalues"); | ||
233 | } | ||
234 | // remove this C function from the config table so that it doesn't cause problems | ||
235 | // when we transfer the config table in newly created Lua states | ||
236 | lua_pushnil(L); // settings on_state_create nil | ||
237 | lua_setfield(L, -3, "on_state_create"); // settings on_state_create | ||
238 | } | ||
239 | else | ||
240 | { | ||
241 | // optim: store marker saying we have such a function in the config table | ||
242 | U->on_state_create_func = (lua_CFunction) initialize_on_state_create; | ||
243 | } | ||
244 | } | ||
245 | lua_pop(L, 1); // settings | ||
246 | STACK_CHECK(L, 1); | ||
247 | } | ||
248 | |||
249 | // ################################################################################################# | ||
250 | |||
251 | lua_State* create_state(Universe* U, lua_State* from_) | ||
252 | { | ||
253 | lua_State* L; | ||
254 | #if LUAJIT_FLAVOR() == 64 | ||
255 | // for some reason, LuaJIT 64 bits does not support creating a state with lua_newstate... | ||
256 | L = luaL_newstate(); | ||
257 | #else // LUAJIT_FLAVOR() == 64 | ||
258 | if (U->provide_allocator != nullptr) // we have a function we can call to obtain an allocator | ||
259 | { | ||
260 | lua_pushcclosure( from_, U->provide_allocator, 0); | ||
261 | lua_call( from_, 0, 1); | ||
262 | { | ||
263 | AllocatorDefinition* const def{ lua_tofulluserdata<AllocatorDefinition>(from_, -1) }; | ||
264 | L = lua_newstate( def->m_allocF, def->m_allocUD); | ||
265 | } | ||
266 | lua_pop( from_, 1); | ||
267 | } | ||
268 | else | ||
269 | { | ||
270 | // reuse the allocator provided when the master state was created | ||
271 | L = lua_newstate(U->protected_allocator.m_allocF, U->protected_allocator.m_allocUD); | ||
272 | } | ||
273 | #endif // LUAJIT_FLAVOR() == 64 | ||
274 | |||
275 | if (L == nullptr) | ||
276 | { | ||
277 | luaL_error(from_, "luaG_newstate() failed while creating state; out of memory"); // doesn't return | ||
278 | } | ||
279 | return L; | ||
280 | } | ||
281 | |||
282 | // ################################################################################################# | ||
283 | |||
284 | void call_on_state_create(Universe* U, lua_State* L, lua_State* from_, LookupMode mode_) | ||
285 | { | ||
286 | if (U->on_state_create_func != nullptr) | ||
287 | { | ||
288 | STACK_CHECK_START_REL(L, 0); | ||
289 | DEBUGSPEW_CODE(fprintf(stderr, INDENT_BEGIN "calling on_state_create()\n" INDENT_END)); | ||
290 | if (U->on_state_create_func != (lua_CFunction) initialize_on_state_create) | ||
291 | { | ||
292 | // C function: recreate a closure in the new state, bypassing the lookup scheme | ||
293 | lua_pushcfunction(L, U->on_state_create_func); // on_state_create() | ||
294 | } | ||
295 | else // Lua function located in the config table, copied when we opened "lanes.core" | ||
296 | { | ||
297 | if (mode_ != LookupMode::LaneBody) | ||
298 | { | ||
299 | // if attempting to call in a keeper state, do nothing because the function doesn't exist there | ||
300 | // this doesn't count as an error though | ||
301 | STACK_CHECK(L, 0); | ||
302 | return; | ||
303 | } | ||
304 | CONFIG_REGKEY.pushValue(L); // {} | ||
305 | STACK_CHECK(L, 1); | ||
306 | lua_getfield(L, -1, "on_state_create"); // {} on_state_create() | ||
307 | lua_remove(L, -2); // on_state_create() | ||
308 | } | ||
309 | STACK_CHECK(L, 1); | ||
310 | // capture error and raise it in caller state | ||
311 | if (lua_pcall(L, 0, 0, 0) != LUA_OK) | ||
312 | { | ||
313 | luaL_error(from_, "on_state_create failed: \"%s\"", lua_isstring(L, -1) ? lua_tostring(L, -1) : lua_typename(L, lua_type(L, -1))); | ||
314 | } | ||
315 | STACK_CHECK(L, 0); | ||
316 | } | ||
317 | } | ||
318 | |||
319 | // ################################################################################################# | ||
320 | |||
321 | /* | ||
322 | * Like 'luaL_openlibs()' but allows the set of libraries be selected | ||
323 | * | ||
324 | * nullptr no libraries, not even base | ||
325 | * "" base library only | ||
326 | * "io,string" named libraries | ||
327 | * "*" all libraries | ||
328 | * | ||
329 | * Base ("unpack", "print" etc.) is always added, unless 'libs' is nullptr. | ||
330 | * | ||
331 | * *NOT* called for keeper states! | ||
332 | * | ||
333 | */ | ||
334 | lua_State* luaG_newstate(Universe* U, Source from_, char const* libs_) | ||
335 | { | ||
336 | Dest const L{ create_state(U, from_) }; | ||
337 | |||
338 | STACK_GROW(L, 2); | ||
339 | STACK_CHECK_START_ABS(L, 0); | ||
340 | |||
341 | // copy the universe as a light userdata (only the master state holds the full userdata) | ||
342 | // that way, if Lanes is required in this new state, we'll know we are part of this universe | ||
343 | universe_store( L, U); | ||
344 | STACK_CHECK(L, 0); | ||
345 | |||
346 | // we'll need this every time we transfer some C function from/to this state | ||
347 | LOOKUP_REGKEY.setValue(L, [](lua_State* L) { lua_newtable(L); }); | ||
348 | STACK_CHECK(L, 0); | ||
349 | |||
350 | // neither libs (not even 'base') nor special init func: we are done | ||
351 | if (libs_ == nullptr && U->on_state_create_func == nullptr) | ||
352 | { | ||
353 | DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "luaG_newstate(nullptr)\n" INDENT_END)); | ||
354 | return L; | ||
355 | } | ||
356 | |||
357 | DEBUGSPEW_CODE(fprintf( stderr, INDENT_BEGIN "luaG_newstate()\n" INDENT_END)); | ||
358 | DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_add(1, std::memory_order_relaxed)); | ||
359 | |||
360 | // copy settings (for example because it may contain a Lua on_state_create function) | ||
361 | copy_one_time_settings( U, from_, L); | ||
362 | |||
363 | // 'lua.c' stops GC during initialization so perhaps its a good idea. :) | ||
364 | lua_gc( L, LUA_GCSTOP, 0); | ||
365 | |||
366 | |||
367 | // Anything causes 'base' to be taken in | ||
368 | // | ||
369 | if (libs_ != nullptr) | ||
370 | { | ||
371 | // special "*" case (mainly to help with LuaJIT compatibility) | ||
372 | // as we are called from luaopen_lanes_core() already, and that would deadlock | ||
373 | if( libs_[0] == '*' && libs_[1] == 0) | ||
374 | { | ||
375 | DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "opening ALL standard libraries\n" INDENT_END)); | ||
376 | luaL_openlibs( L); | ||
377 | // don't forget lanes.core for regular lane states | ||
378 | open1lib( DEBUGSPEW_PARAM_COMMA( U) L, "lanes.core", 10); | ||
379 | libs_ = nullptr; // done with libs | ||
380 | } | ||
381 | else | ||
382 | { | ||
383 | DEBUGSPEW_CODE( fprintf( stderr, INDENT_BEGIN "opening base library\n" INDENT_END)); | ||
384 | #if LUA_VERSION_NUM >= 502 | ||
385 | // open base library the same way as in luaL_openlibs() | ||
386 | luaL_requiref( L, "_G", luaopen_base, 1); | ||
387 | lua_pop( L, 1); | ||
388 | #else // LUA_VERSION_NUM | ||
389 | lua_pushcfunction( L, luaopen_base); | ||
390 | lua_pushstring( L, ""); | ||
391 | lua_call( L, 1, 0); | ||
392 | #endif // LUA_VERSION_NUM | ||
393 | } | ||
394 | } | ||
395 | STACK_CHECK(L, 0); | ||
396 | |||
397 | // scan all libraries, open them one by one | ||
398 | if( libs_) | ||
399 | { | ||
400 | char const* p; | ||
401 | unsigned int len = 0; | ||
402 | for( p = libs_; *p; p += len) | ||
403 | { | ||
404 | // skip delimiters ('.' can be part of name for "lanes.core") | ||
405 | while( *p && !isalnum( *p) && *p != '.') | ||
406 | ++ p; | ||
407 | // skip name | ||
408 | len = 0; | ||
409 | while( isalnum( p[len]) || p[len] == '.') | ||
410 | ++ len; | ||
411 | // open library | ||
412 | open1lib( DEBUGSPEW_PARAM_COMMA( U) L, p, len); | ||
413 | } | ||
414 | } | ||
415 | lua_gc( L, LUA_GCRESTART, 0); | ||
416 | |||
417 | serialize_require( DEBUGSPEW_PARAM_COMMA( U) L); | ||
418 | |||
419 | // call this after the base libraries are loaded and GC is restarted | ||
420 | // will raise an error in from_ in case of problem | ||
421 | call_on_state_create(U, L, from_, LookupMode::LaneBody); | ||
422 | |||
423 | STACK_CHECK(L, 0); | ||
424 | // after all this, register everything we find in our name<->function database | ||
425 | lua_pushglobaltable( L); // Lua 5.2 no longer has LUA_GLOBALSINDEX: we must push globals table on the stack | ||
426 | STACK_CHECK(L, 1); | ||
427 | populate_func_lookup_table(L, -1, nullptr); | ||
428 | |||
429 | #if 0 && USE_DEBUG_SPEW() | ||
430 | // dump the lookup database contents | ||
431 | lua_getfield(L, LUA_REGISTRYINDEX, LOOKUP_REGKEY); // {} | ||
432 | lua_pushnil(L); // {} nil | ||
433 | while (lua_next(L, -2)) // {} k v | ||
434 | { | ||
435 | lua_getglobal(L, "print"); // {} k v print | ||
436 | lua_pushlstring(L, debugspew_indent, U->debugspew_indent_depth.load(std::memory_order_relaxed)); // {} k v print " " | ||
437 | lua_pushvalue(L, -4); // {} k v print " " k | ||
438 | lua_pushvalue(L, -4); // {} k v print " " k v | ||
439 | lua_call(L, 3, 0); // {} k v | ||
440 | lua_pop(L, 1); // {} k | ||
441 | } | ||
442 | lua_pop(L, 1); // {} | ||
443 | #endif // USE_DEBUG_SPEW() | ||
444 | |||
445 | lua_pop(L, 1); | ||
446 | STACK_CHECK(L, 0); | ||
447 | DEBUGSPEW_CODE(U->debugspew_indent_depth.fetch_sub(1, std::memory_order_relaxed)); | ||
448 | return L; | ||
449 | } | ||