1 /* Copyright (c) 2010. The SimGrid Team.
2 * All rights reserved. */
4 /* This program is free software; you can redistribute it and/or modify it
5 * under the terms of the license (GNU LGPL) which comes with this package. */
7 /* SimGrid Lua state management */
9 #include "lua_state_cloner.h"
10 #include "lua_utils.h"
16 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(lua_state_cloner, lua, "Lua state management");
18 static lua_State* sglua_get_father(lua_State* L);
19 static int l_get_from_father(lua_State* L);
21 static void sglua_move_value_impl(lua_State* src, lua_State* dst, const char* name);
22 static void sglua_copy_nil(lua_State* src, lua_State* dst);
23 static void sglua_copy_number(lua_State* src, lua_State* dst);
24 static void sglua_copy_boolean(lua_State* src, lua_State* dst);
25 static void sglua_copy_string(lua_State* src, lua_State* dst);
26 static void sglua_copy_table(lua_State* src, lua_State* dst);
27 static void sglua_copy_function(lua_State* src, lua_State* dst);
28 static void sglua_copy_lightuserdata(lua_State* src, lua_State* dst);
29 static void sglua_copy_userdata(lua_State* src, lua_State* dst);
30 static void sglua_copy_thread(lua_State* src, lua_State* dst);
33 * @brief Returns the father of a state, i.e. the state that created it.
34 * @param L a Lua state
35 * @return its father, or NULL if the state was not created by sglua_clone_state()
37 static lua_State* sglua_get_father(lua_State* L) {
40 lua_pushstring(L, "simgrid.father");
41 /* ... "simgrid.father" */
42 lua_rawget(L, LUA_REGISTRYINDEX);
44 lua_State* father = lua_touserdata(L, -1);
51 * @brief Pops a value from a state and pushes it onto the stack of another
54 * @param src the source state
55 * @param dst the destination state
57 void sglua_move_value(lua_State* src, lua_State* dst) {
61 /* get the list of visited tables from father at index 1 of dst */
64 lua_getfield(dst, LUA_REGISTRYINDEX, "simgrid.father_visited_tables");
65 /* dst: ... visited */
67 /* dst: visited ... */
69 sglua_move_value_impl(src, dst, sglua_tostring(src, -1));
71 dst: visited ... value */
74 sglua_stack_dump("src after xmove: ", src);
75 sglua_stack_dump("dst after xmove: ", dst);
80 * @brief Pops a value from the stack of a source state and pushes it on the
81 * stack of another state.
83 * If the value is a table, its content is copied recursively. To avoid cycles,
84 * a table of previously visited tables must be present at index 1 of dst.
85 * Its keys are pointers to visited tables in src and its values are the tables
88 * TODO: add support of closures
90 * @param src the source state
91 * @param dst the destination state, with a list of visited tables at index 1
92 * @param name a name describing the value (for debugging purposes)
94 static void sglua_move_value_impl(lua_State* src, lua_State *dst, const char* name) {
96 luaL_checkany(src, -1); /* check the value to copy */
97 luaL_checktype(dst, 1, LUA_TTABLE); /* check the presence of a table of
98 previously visited tables */
100 int indent = (lua_gettop(dst) - 1) * 6;
101 XBT_DEBUG("%sCopying data %s", sglua_get_spaces(indent), name);
103 sglua_stack_dump("src before copying a value (should be ... value): ", src);
104 sglua_stack_dump("dst before copying a value (should be visited ...): ", dst);
106 switch (lua_type(src, -1)) {
109 sglua_copy_nil(src, dst);
113 sglua_copy_number(src, dst);
117 sglua_copy_boolean(src, dst);
121 sglua_copy_string(src, dst);
125 sglua_copy_function(src, dst);
129 sglua_copy_table(src, dst);
132 case LUA_TLIGHTUSERDATA:
133 sglua_copy_lightuserdata(src, dst);
137 sglua_copy_userdata(src, dst);
141 sglua_copy_thread(src, dst);
145 /* the value has been copied to dst: remove it from src */
149 XBT_DEBUG("%sData copied", sglua_get_spaces(indent));
151 sglua_stack_dump("src after copying a value (should be ...): ", src);
152 sglua_stack_dump("dst after copying a value (should be visited ... value): ", dst);
156 * @brief Copies the nil value on the top of src to the top of dst.
157 * @param src source state
158 * @param dst destination state
160 static void sglua_copy_nil(lua_State* src, lua_State* dst) {
165 * @brief Copies the number value on the top of src to the top of dst.
166 * @param src source state
167 * @param dst destination state
169 static void sglua_copy_number(lua_State* src, lua_State* dst) {
170 lua_pushnumber(dst, lua_tonumber(src, -1));
174 * @brief Copies the boolean value on the top of src to the top of dst.
175 * @param src source state
176 * @param dst destination state
178 static void sglua_copy_boolean(lua_State* src, lua_State* dst) {
179 lua_pushboolean(dst, lua_toboolean(src, -1));
182 static void sglua_copy_string(lua_State* src, lua_State* dst) {
184 /* no worries about memory: lua_pushstring makes a copy */
185 lua_pushstring(dst, lua_tostring(src, -1));
189 * @brief Copies the table value on the top of src to the top of dst.
191 * A deep copy of the table is made. If the table has a metatable, the
192 * metatable is also copied.
193 * If the table is already known by the destination state, nothing is
196 * @param src source state
197 * @param dst destination state
199 static void sglua_copy_table(lua_State* src, lua_State* dst) {
201 int indent = (lua_gettop(dst) - 1) * 6 + 2;
203 /* first register the table in the source state itself */
204 lua_getfield(src, LUA_REGISTRYINDEX, "simgrid.visited_tables");
205 /* src: ... table visited */
206 lua_pushvalue(src, -2);
207 /* src: ... table visited table */
208 lua_pushlightuserdata(src, (void*) lua_topointer(src, -1));
209 /* src: ... table visited table psrctable */
210 lua_pushvalue(src, -1);
211 /* src: ... table visited table psrctable psrctable */
212 lua_pushvalue(src, -3);
213 /* src: ... table visited table psrctable psrctable table */
214 lua_settable(src, -5);
215 /* src: ... table visited table psrctable */
216 lua_settable(src, -3);
217 /* src: ... table visited */
221 /* see if this table was already known by dst */
222 lua_pushlightuserdata(dst, (void*) lua_topointer(src, -1));
223 /* dst: visited ... psrctable */
224 lua_gettable(dst, 1);
225 /* dst: visited ... table/nil */
226 if (lua_istable(dst, -1)) {
227 XBT_DEBUG("%sNothing to do: table already visited (%p)",
228 sglua_get_spaces(indent), lua_topointer(src, -1));
229 /* dst: visited ... table */
232 XBT_DEBUG("%sFirst visit of this table (%p)", sglua_get_spaces(indent),
233 lua_topointer(src, -1));
234 /* dst: visited ... nil */
236 /* dst: visited ... */
238 /* first visit: create the new table in dst */
240 /* dst: visited ... table */
242 /* mark the table as visited right now to avoid infinite recursion */
243 lua_pushlightuserdata(dst, (void*) lua_topointer(src, -1));
244 /* dst: visited ... table psrctable */
245 lua_pushvalue(dst, -2);
246 /* dst: visited ... table psrctable table */
247 lua_pushvalue(dst, -1);
248 /* dst: visited ... table psrctable table table */
249 lua_pushvalue(dst, -3);
250 /* dst: visited ... table psrctable table table psrctable */
251 lua_settable(dst, 1);
252 /* dst: visited ... table psrctable table */
253 lua_settable(dst, 1);
254 /* dst: visited ... table */
255 XBT_DEBUG("%sTable marked as visited", sglua_get_spaces(indent));
257 sglua_stack_dump("dst after marking the table as visited (should be visited ... table): ", dst);
259 /* copy the metatable if any */
260 int has_meta_table = lua_getmetatable(src, -1);
261 /* src: ... table mt? */
262 if (has_meta_table) {
263 XBT_DEBUG("%sCopying metatable", sglua_get_spaces(indent));
264 /* src: ... table mt */
265 sglua_copy_table(src, dst);
266 /* dst: visited ... table mt */
269 lua_setmetatable(dst, -2);
270 /* dst: visited ... table */
273 XBT_DEBUG("%sNo metatable", sglua_get_spaces(indent));
276 sglua_stack_dump("src before traversing the table (should be ... table): ", src);
277 sglua_stack_dump("dst before traversing the table (should be visited ... table): ", dst);
279 /* traverse the table of src and copy each element */
281 /* src: ... table nil */
282 while (lua_next(src, -2) != 0) {
283 /* src: ... table key value */
285 XBT_DEBUG("%sCopying table element %s", sglua_get_spaces(indent),
286 sglua_keyvalue_tostring(src, -2, -1));
288 sglua_stack_dump("src before copying table element (should be ... table key value): ", src);
289 sglua_stack_dump("dst before copying table element (should be visited ... table): ", dst);
292 lua_pushvalue(src, -2);
293 /* src: ... table key value key */
295 XBT_DEBUG("%sCopying the key part of the table element",
296 sglua_get_spaces(indent));
297 sglua_move_value_impl(src, dst, sglua_tostring(src, -1));
298 /* src: ... table key value
299 dst: visited ... table key */
300 XBT_DEBUG("%sCopied the key part of the table element",
301 sglua_get_spaces(indent));
304 XBT_DEBUG("%sCopying the value part of the table element",
305 sglua_get_spaces(indent));
306 sglua_move_value_impl(src, dst, sglua_tostring(src, -1));
307 /* src: ... table key
308 dst: visited ... table key value */
309 XBT_DEBUG("%sCopied the value part of the table element",
310 sglua_get_spaces(indent));
313 /* set the table element */
314 lua_settable(dst, -3);
315 /* dst: visited ... table */
317 /* the key stays on top of src for next iteration */
318 sglua_stack_dump("src before next iteration (should be ... table key): ", src);
319 sglua_stack_dump("dst before next iteration (should be visited ... table): ", dst);
321 XBT_DEBUG("%sTable element copied", sglua_get_spaces(indent));
323 XBT_DEBUG("%sFinished traversing the table", sglua_get_spaces(indent));
328 * @brief Copies the function on the top of src to the top of dst.
330 * It can be a Lua function or a C function.
331 * Copying upvalues is not implemented yet (TODO).
333 * @param src source state
334 * @param dst destination state
336 static void sglua_copy_function(lua_State* src, lua_State* dst) {
338 if (lua_iscfunction(src, -1)) {
339 /* it's a C function: just copy the pointer */
340 lua_CFunction f = lua_tocfunction(src, -1);
341 lua_pushcfunction(dst, f);
344 /* it's a Lua function: dump it from src */
346 s_sglua_buffer_t buffer;
347 buffer.capacity = 64;
349 buffer.data = xbt_new(char, buffer.capacity);
351 /* copy the binary chunk from src into a buffer */
352 int error = lua_dump(src, sglua_memory_writer, &buffer);
353 xbt_assert(!error, "Failed to dump the function from the source state: error %d",
356 /* load the chunk into dst */
357 error = luaL_loadbuffer(dst, buffer.data, buffer.size, "(dumped function)");
358 xbt_assert(!error, "Failed to load the function into the destination state: %s",
359 lua_tostring(dst, -1));
364 * @brief Copies the light userdata on the top of src to the top of dst.
365 * @param src source state
366 * @param dst destination state
368 static void sglua_copy_lightuserdata(lua_State* src, lua_State* dst) {
369 lua_pushlightuserdata(dst, lua_touserdata(src, -1));
373 * @brief Copies the full userdata on the top of src to the top of dst.
375 * If the userdata has a metatable, the metatable is also copied.
377 * @param src source state
378 * @param dst destination state
380 static void sglua_copy_userdata(lua_State* src, lua_State* dst) {
382 int indent = (lua_gettop(dst) - 1) * 6 + 2;
387 size_t size = lua_objlen(src, -1);
388 void* src_block = lua_touserdata(src, -1);
389 void* dst_block = lua_newuserdata(dst, size);
390 /* dst: visited ... udata */
391 memcpy(dst_block, src_block, size);
393 /* copy the metatable if any */
394 int has_meta_table = lua_getmetatable(src, -1);
395 /* src: ... udata mt? */
396 if (has_meta_table) {
397 XBT_DEBUG("%sCopying metatable of userdata (%p)",
398 sglua_get_spaces(indent), lua_topointer(src, -1));
399 /* src: ... udata mt */
400 lua_State* father = sglua_get_father(dst);
402 if (father != NULL && src != father && sglua_get_father(src) == father) {
403 XBT_DEBUG("%sGet the metatable from my father",
404 sglua_get_spaces(indent));
405 /* I don't want the metatable of src, I want the father's copy of the
408 /* get from src the pointer to the father's copy of this metatable */
409 lua_pushstring(src, "simgrid.father_visited_tables");
410 /* src: ... udata mt "simgrid.visited_tables" */
411 lua_rawget(src, LUA_REGISTRYINDEX);
412 /* src: ... udata mt visited */
413 lua_pushvalue(src, -2);
414 /* src: ... udata mt visited mt */
415 lua_gettable(src, -2);
416 /* src: ... udata mt visited pfathermt */
418 /* copy the metatable from the father world into dst */
419 lua_pushstring(father, "simgrid.visited_tables");
420 /* father: ... "simgrid.visited_tables" */
421 lua_rawget(father, LUA_REGISTRYINDEX);
422 /* father: ... visited */
423 lua_pushlightuserdata(father, (void*) lua_topointer(src, -1));
424 /* father: ... visited pfathermt */
425 lua_gettable(father, -2);
426 /* father: ... visited mt */
427 sglua_move_value_impl(father, dst, "(father metatable)");
428 /* father: ... visited
429 dst: visited ... udata mt */
435 /* TODO make helper functions for this kind of operations */
438 XBT_DEBUG("%sI have no father", sglua_get_spaces(indent));
439 sglua_move_value_impl(src, dst, "metatable");
441 dst: visited ... udata mt */
443 lua_setmetatable(dst, -2);
444 /* dst: visited ... udata */
446 XBT_DEBUG("%sMetatable of userdata copied", sglua_get_spaces(indent));
449 XBT_DEBUG("%sNo metatable for this userdata",
450 sglua_get_spaces(indent));
456 * @brief This operation is not supported (yet?) so this function pushes nil.
458 * @param src source state
459 * @param dst destination state
461 static void sglua_copy_thread(lua_State* src, lua_State* dst) {
463 XBT_WARN("Cannot copy a thread from the source state.");
468 * @brief Copies a global value or a registry value from the father state.
470 * The state L must have a father, i.e. it should have been created by
472 * This function is meant to be an __index metamethod.
473 * Consequently, it assumes that the stack has two elements:
474 * a table (either the environment or the registry of L) and the string key of
475 * a value that does not exist yet in this table. It copies the corresponding
476 * value from the father state and pushes it on the stack of L.
477 * If the value does not exist in the father state either, nil is pushed.
479 * TODO: make this function thread safe. If the simulation runs in parallel,
480 * several simulated processes may trigger this __index metamethod at the same
481 * time and get globals from maestro.
483 * @param L the current state
484 * @return number of return values pushed (always 1)
486 static int l_get_from_father(lua_State *L) {
488 /* check the arguments */
489 luaL_checktype(L, 1, LUA_TTABLE);
490 const char* key = luaL_checkstring(L, 2);
492 XBT_DEBUG("__index of '%s' begins", key);
494 /* want a global or a registry value? */
496 if (lua_equal(L, 1, LUA_REGISTRYINDEX)) {
498 pseudo_index = LUA_REGISTRYINDEX;
499 XBT_DEBUG("Will get the value from the registry of the father");
503 pseudo_index = LUA_GLOBALSINDEX;
504 XBT_DEBUG("Will get the value from the globals of the father");
508 lua_State* father = sglua_get_father(L);
510 if (father == NULL) {
511 XBT_WARN("This state has no father");
518 /* get the list of visited tables */
519 lua_pushstring(L, "simgrid.father_visited_tables");
520 /* L: table key "simgrid.father_visited_tables" */
521 lua_rawget(L, LUA_REGISTRYINDEX);
522 /* L: table key visited */
524 /* L: visited table key */
526 /* get the value from the father */
527 lua_getfield(father, pseudo_index, key);
528 /* father: ... value */
530 /* push the value onto the stack of L */
531 sglua_move_value_impl(father, L, key);
533 L: visited table key value */
535 /* L: table key value */
537 /* prepare the return value of __index */
538 lua_pushvalue(L, -1);
539 /* L: table key value value */
541 /* L: value table key value */
543 /* save the copied value in the table for subsequent accesses */
549 XBT_DEBUG("__index of '%s' returns %s", key, sglua_tostring(L, -1));
555 * @brief Creates a new Lua state and get its environment from an existing state.
557 * The state created is independent from the existing one and has its own
558 * copies of global and registry values.
559 * However, the global and registry values are not copied right now from
560 * the original state; they are copied only the first time they are accessed.
561 * This behavior saves time and memory, and is okay for Simgrid's needs.
563 * TODO: if the simulation runs in parallel, copy everything right now?
565 * @param father an existing state
566 * @return the state created
568 lua_State* sglua_clone_state(lua_State *father) {
570 /* create the new state */
571 lua_State *L = luaL_newstate();
573 /* set its environment and its registry:
574 * - create a table newenv
575 * - create a metatable mt
576 * - set mt.__index = a function that copies the global from the father state
577 * - set mt as the metatable of the registry
578 * - set mt as the metatable of newenv
579 * - set newenv as the environment of the new state
581 lua_pushthread(L); /* thread */
582 lua_newtable(L); /* thread newenv */
583 lua_newtable(L); /* thread newenv mt */
584 lua_pushvalue(L, LUA_REGISTRYINDEX); /* thread newenv mt reg */
585 lua_pushcfunction(L, l_get_from_father); /* thread newenv mt reg f */
586 lua_setfield(L, -3, "__index"); /* thread newenv mt reg */
587 lua_pushvalue(L, -2); /* thread newenv mt reg mt */
588 lua_setmetatable(L, -2); /* thread newenv mt reg */
589 lua_pop(L, 1); /* thread newenv mt */
590 lua_setmetatable(L, -2); /* thread newenv */
591 lua_setfenv(L, -2); /* thread */
592 lua_pop(L, 1); /* -- */
594 /* set a pointer to the father */
595 lua_pushstring(L, "simgrid.father"); /* "simgrid.father" */
596 lua_pushlightuserdata(L, father); /* "simgrid.father" father */
597 lua_rawset(L, LUA_REGISTRYINDEX);
600 /* create the table of visited tables from the father */
601 lua_pushstring(L, "simgrid.father_visited_tables");
602 /* "simgrid.father_visited_tables" */
603 lua_newtable(L); /* "simgrid.father_visited_tables" visited */
604 lua_rawset(L, LUA_REGISTRYINDEX);
607 /* create the table of my own visited tables */
608 lua_pushstring(L, "simgrid.visited_tables");
609 /* "simgrid.visited_tables" */
610 lua_newtable(L); /* "simgrid.visited_tables" visited */
611 lua_rawset(L, LUA_REGISTRYINDEX);
614 /* open the standard libs (theoretically, this is not necessary as they can
615 * be inherited like any global values, but without a proper support of
616 * closures, iterators like ipairs don't work). */
617 XBT_DEBUG("Metatable of globals and registry set, opening standard libraries now");
620 XBT_DEBUG("New state created");