Logo AND Algorithmique Numérique Distribuée

Public GIT Repository
Lua: separate bindings, state cloning functions and helper functions
[simgrid.git] / src / bindings / lua / lua_state_cloner.c
1 /* Copyright (c) 2010. The SimGrid Team.
2  * All rights reserved.                                                     */
3
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. */
6
7 /* SimGrid Lua state management                                             */
8
9 #include "lua_state_cloner.h"
10 #include "lua_utils.h"
11 #include "xbt.h"
12 #include "xbt/log.h"
13 #include <lauxlib.h>
14 #include <lualib.h>
15
16 XBT_LOG_NEW_DEFAULT_SUBCATEGORY(lua_state_cloner, lua, "Lua state management");
17
18 static lua_State* sglua_get_father(lua_State* L);
19 static void sglua_move_value_impl(lua_State* src, lua_State* dst, const char* name);
20 static int l_get_from_father(lua_State* L);
21
22 /**
23  * @brief Returns the father of a state, i.e. the state that created it.
24  * @param L a Lua state
25  * @return its father, or NULL if the state was not created by sglua_clone_state()
26  */
27 static lua_State* sglua_get_father(lua_State* L) {
28
29                                   /* ... */
30   lua_pushstring(L, "simgrid.father");
31                                   /* ... "simgrid.father" */
32   lua_rawget(L, LUA_REGISTRYINDEX);
33                                   /* ... father */
34   lua_State* father = lua_touserdata(L, -1);
35   lua_pop(L, 1);
36                                   /* ... */
37   return father;
38 }
39
40 /**
41  * @brief Pops a value from a state and pushes it onto the stack of another
42  * state.
43  *
44  * @param src the source state
45  * @param dst the destination state
46  */
47 void sglua_move_value(lua_State* src, lua_State* dst) {
48
49   if (src != dst) {
50
51     /* get the list of visited tables from father at index 1 of dst */
52                                   /* src: ... value
53                                      dst: ... */
54     lua_getfield(dst, LUA_REGISTRYINDEX, "simgrid.father_visited_tables");
55                                   /* dst: ... visited */
56     lua_insert(dst, 1);
57                                   /* dst: visited ... */
58
59     sglua_move_value_impl(src, dst, sglua_tostring(src, -1));
60                                   /* src: ...
61                                      dst: visited ... value */
62     lua_remove(dst, 1);
63                                   /* dst: ... value */
64     sglua_stack_dump("src after xmove: ", src);
65     sglua_stack_dump("dst after xmove: ", dst);
66   }
67 }
68
69 /**
70  * @brief Pops a value from the stack of a source state and pushes it on the
71  * stack of another state.
72  *
73  * If the value is a table, its content is copied recursively. To avoid cycles,
74  * a table of previously visited tables must be present at index 1 of dst.
75  * Its keys are pointers to visited tables in src and its values are the tables
76  * already built.
77  *
78  * TODO: add support of closures
79  *
80  * @param src the source state
81  * @param dst the destination state, with a list of visited tables at index 1
82  * @param name a name describing the value
83  */
84 static void sglua_move_value_impl(lua_State* src, lua_State *dst, const char* name) {
85
86   luaL_checkany(src, -1);                  /* check the value to copy */
87   luaL_checktype(dst, 1, LUA_TTABLE);      /* check the presence of a table of
88                                               previously visited tables */
89
90   int indent = (lua_gettop(dst) - 1) * 6;
91   XBT_DEBUG("%sCopying data %s", sglua_get_spaces(indent), name);
92   indent += 2;
93
94   sglua_stack_dump("src before copying a value (should be ... value): ", src);
95   sglua_stack_dump("dst before copying a value (should be visited ...): ", dst);
96
97   switch (lua_type(src, -1)) {
98     /* TODO implement the copy of each type in a separate function */
99
100     case LUA_TNIL:
101       lua_pushnil(dst);
102       break;
103
104     case LUA_TNUMBER:
105       lua_pushnumber(dst, lua_tonumber(src, -1));
106       break;
107
108     case LUA_TBOOLEAN:
109       lua_pushboolean(dst, lua_toboolean(src, -1));
110       break;
111
112     case LUA_TSTRING:
113       /* no worries about memory: lua_pushstring makes a copy */
114       lua_pushstring(dst, lua_tostring(src, -1));
115       break;
116
117     case LUA_TFUNCTION:
118       /* it's a function that does not exist yet in L2 */
119
120       if (lua_iscfunction(src, -1)) {
121         /* it's a C function: just copy the pointer */
122         lua_CFunction f = lua_tocfunction(src, -1);
123         lua_pushcfunction(dst, f);
124       }
125       else {
126         /* it's a Lua function: dump it from src */
127         XBT_DEBUG("%sDumping Lua function '%s'", sglua_get_spaces(indent), name);
128
129         s_buffer_t buffer;
130         buffer.capacity = 64;
131         buffer.size = 0;
132         buffer.data = xbt_new(char, buffer.capacity);
133
134         /* copy the binary chunk from src into a buffer */
135         int error = lua_dump(src, sglua_memory_writer, &buffer);
136         xbt_assert(!error, "Failed to dump function '%s' from the source state: error %d",
137               name, error);
138
139         /* load the chunk into dst */
140         error = luaL_loadbuffer(dst, buffer.data, buffer.size, name);
141         xbt_assert(!error, "Failed to load function '%s' into the destination state: %s",
142             name, lua_tostring(dst, -1));
143         XBT_DEBUG("%sFunction '%s' successfully loaded", sglua_get_spaces(indent), name);
144       }
145       break;
146
147     case LUA_TTABLE:
148
149       /* first register the table in the source state itself */
150       lua_getfield(src, LUA_REGISTRYINDEX, "simgrid.visited_tables");
151                                   /* src: ... table visited */
152       lua_pushvalue(src, -2);
153                                   /* src: ... table visited table */
154       lua_pushlightuserdata(src, (void*) lua_topointer(src, -1));
155                                   /* src: ... table visited table psrctable */
156       lua_pushvalue(src, -1);
157                                   /* src: ... table visited table psrctable psrctable */
158       lua_pushvalue(src, -3);
159                                   /* src: ... table visited table psrctable psrctable table */
160       lua_settable(src, -5);
161                                   /* src: ... table visited table psrctable */
162       lua_settable(src, -3);
163                                   /* src: ... table visited */
164       lua_pop(src, 1);
165                                   /* src: ... table */
166
167       /* see if this table was already known by dst */
168       lua_pushlightuserdata(dst, (void*) lua_topointer(src, -1));
169                                   /* dst: visited ... psrctable */
170       lua_gettable(dst, 1);
171                                   /* dst: visited ... table/nil */
172       if (lua_istable(dst, -1)) {
173         XBT_DEBUG("%sNothing to do: table already visited (%p)",
174             sglua_get_spaces(indent), lua_topointer(src, -1));
175                                   /* dst: visited ... table */
176       }
177       else {
178         XBT_DEBUG("%sFirst visit of this table (%p)", sglua_get_spaces(indent),
179             lua_topointer(src, -1));
180                                   /* dst: visited ... nil */
181         lua_pop(dst, 1);
182                                   /* dst: visited ... */
183
184         /* first visit: create the new table in dst */
185         lua_newtable(dst);
186                                   /* dst: visited ... table */
187
188         /* mark the table as visited to avoid infinite recursion */
189         lua_pushlightuserdata(dst, (void*) lua_topointer(src, -1));
190                                   /* dst: visited ... table psrctable */
191         lua_pushvalue(dst, -2);
192                                   /* dst: visited ... table psrctable table */
193         lua_pushvalue(dst, -1);
194                                   /* dst: visited ... table psrctable table table */
195         lua_pushvalue(dst, -3);
196                                   /* dst: visited ... table psrctable table table psrctable */
197         lua_settable(dst, 1);
198                                   /* dst: visited ... table psrctable table */
199         lua_settable(dst, 1);
200                                   /* dst: visited ... table */
201         XBT_DEBUG("%sTable marked as visited", sglua_get_spaces(indent));
202
203         sglua_stack_dump("dst after marking the table as visited (should be visited ... table): ", dst);
204
205         /* copy the metatable if any */
206         int has_meta_table = lua_getmetatable(src, -1);
207                                   /* src: ... table mt? */
208         if (has_meta_table) {
209           XBT_DEBUG("%sCopying metatable", sglua_get_spaces(indent));
210                                   /* src: ... table mt */
211           sglua_move_value_impl(src, dst, "metatable");
212                                   /* src: ... table
213                                      dst: visited ... table mt */
214           lua_setmetatable(dst, -2);
215                                   /* dst: visited ... table */
216         }
217         else {
218           XBT_DEBUG("%sNo metatable", sglua_get_spaces(indent));
219         }
220
221         sglua_stack_dump("src before traversing the table (should be ... table): ", src);
222         sglua_stack_dump("dst before traversing the table (should be visited ... table): ", dst);
223
224         /* traverse the table of src and copy each element */
225         lua_pushnil(src);
226                                   /* src: ... table nil */
227         while (lua_next(src, -2) != 0) {
228                                   /* src: ... table key value */
229
230           XBT_DEBUG("%sCopying table element %s", sglua_get_spaces(indent),
231               sglua_keyvalue_tostring(src, -2, -1));
232
233           sglua_stack_dump("src before copying table element (should be ... table key value): ", src);
234           sglua_stack_dump("dst before copying table element (should be visited ... table): ", dst);
235
236           /* copy the key */
237           lua_pushvalue(src, -2);
238                                   /* src: ... table key value key */
239           indent += 2;
240           XBT_DEBUG("%sCopying the key part of the table element",
241               sglua_get_spaces(indent));
242           sglua_move_value_impl(src, dst, sglua_tostring(src, -1));
243                                   /* src: ... table key value
244                                      dst: visited ... table key */
245           XBT_DEBUG("%sCopied the key part of the table element",
246               sglua_get_spaces(indent));
247
248           /* copy the value */
249           XBT_DEBUG("%sCopying the value part of the table element",
250               sglua_get_spaces(indent));
251           sglua_move_value_impl(src, dst, sglua_tostring(src, -1));
252                                   /* src: ... table key
253                                      dst: visited ... table key value */
254           XBT_DEBUG("%sCopied the value part of the table element",
255               sglua_get_spaces(indent));
256           indent -= 2;
257
258           /* set the table element */
259           lua_settable(dst, -3);
260                                   /* dst: visited ... table */
261
262           /* the key stays on top of src for next iteration */
263           sglua_stack_dump("src before next iteration (should be ... table key): ", src);
264           sglua_stack_dump("dst before next iteration (should be visited ... table): ", dst);
265
266           XBT_DEBUG("%sTable element copied", sglua_get_spaces(indent));
267         }
268         XBT_DEBUG("%sFinished traversing the table", sglua_get_spaces(indent));
269       }
270       break;
271
272     case LUA_TLIGHTUSERDATA:
273       lua_pushlightuserdata(dst, lua_touserdata(src, -1));
274       break;
275
276     case LUA_TUSERDATA:
277     {
278       /* copy the data */
279                                   /* src: ... udata
280                                      dst: visited ... */
281       size_t size = lua_objlen(src, -1);
282       void* src_block = lua_touserdata(src, -1);
283       void* dst_block = lua_newuserdata(dst, size);
284                                   /* dst: visited ... udata */
285       memcpy(dst_block, src_block, size);
286
287       /* copy the metatable if any */
288       int has_meta_table = lua_getmetatable(src, -1);
289                                   /* src: ... udata mt? */
290       if (has_meta_table) {
291         XBT_DEBUG("%sCopying metatable of userdata (%p)",
292             sglua_get_spaces(indent), lua_topointer(src, -1));
293                                   /* src: ... udata mt */
294         lua_State* father = sglua_get_father(dst);
295
296         if (father != NULL && src != father && sglua_get_father(src) == father) {
297           XBT_DEBUG("%sGet the metatable from my father",
298               sglua_get_spaces(indent));
299           /* I don't want the metatable of src, I want the father's copy of the
300              same metatable */
301
302           /* get from src the pointer to the father's copy of this metatable */
303           lua_pushstring(src, "simgrid.father_visited_tables");
304                                   /* src: ... udata mt "simgrid.visited_tables" */
305           lua_rawget(src, LUA_REGISTRYINDEX);
306                                   /* src: ... udata mt visited */
307           lua_pushvalue(src, -2);
308                                   /* src: ... udata mt visited mt */
309           lua_gettable(src, -2);
310                                   /* src: ... udata mt visited pfathermt */
311
312           /* copy the metatable from the father world into dst */
313           lua_pushstring(father, "simgrid.visited_tables");
314                                   /* father: ... "simgrid.visited_tables" */
315           lua_rawget(father, LUA_REGISTRYINDEX);
316                                   /* father: ... visited */
317           lua_pushlightuserdata(father, (void*) lua_topointer(src, -1));
318                                   /* father: ... visited pfathermt */
319           lua_gettable(father, -2);
320                                   /* father: ... visited mt */
321           sglua_move_value_impl(father, dst, "(father metatable)");
322                                   /* father: ... visited
323                                      dst: visited ... udata mt */
324           lua_pop(father, 1);
325                                   /* father: ... */
326           lua_pop(src, 3);
327                                   /* src: ... udata */
328
329           /* TODO make helper functions for this kind of operations */
330         }
331         else {
332           XBT_DEBUG("%sI have no father", sglua_get_spaces(indent));
333           sglua_move_value_impl(src, dst, "metatable");
334                                   /* src: ... udata
335                                      dst: visited ... udata mt */
336         }
337         lua_setmetatable(dst, -2);
338                                   /* dst: visited ... udata */
339
340         XBT_DEBUG("%sMetatable of userdata copied", sglua_get_spaces(indent));
341       }
342       else {
343         XBT_DEBUG("%sNo metatable for this userdata",
344             sglua_get_spaces(indent));
345                                   /* src: ... udata */
346       }
347     }
348     break;
349
350     case LUA_TTHREAD:
351       XBT_WARN("Cannot copy a thread from the source state.");
352       lua_pushnil(dst);
353       break;
354   }
355
356   /* pop the value from src */
357   lua_pop(src, 1);
358
359   indent -= 2;
360   XBT_DEBUG("%sData copied", sglua_get_spaces(indent));
361
362   sglua_stack_dump("src after copying a value (should be ...): ", src);
363   sglua_stack_dump("dst after copying a value (should be visited ... value): ", dst);
364 }
365
366 /**
367  * @brief Copies a global value or a registry value from the father state.
368  *
369  * The state L must have a father, i.e. it should have been created by
370  * clone_lua_state().
371  * This function is meant to be an __index metamethod.
372  * Consequently, it assumes that the stack has two elements:
373  * a table (either the environment or the registry of L) and the string key of
374  * a value that does not exist yet in this table. It copies the corresponding
375  * value from the father state and pushes it on the stack of L.
376  * If the value does not exist in the father state either, nil is pushed.
377  *
378  * TODO: make this function thread safe. If the simulation runs in parallel,
379  * several simulated processes may trigger this __index metamethod at the same
380  * time and get globals from maestro.
381  *
382  * @param L the current state
383  * @return number of return values pushed (always 1)
384  */
385 static int l_get_from_father(lua_State *L) {
386
387   /* check the arguments */
388   luaL_checktype(L, 1, LUA_TTABLE);
389   const char* key = luaL_checkstring(L, 2);
390                                                /* L:      table key */
391   XBT_DEBUG("__index of '%s' begins", key);
392
393   /* want a global or a registry value? */
394   int pseudo_index;
395   if (lua_equal(L, 1, LUA_REGISTRYINDEX)) {
396     /* registry */
397     pseudo_index = LUA_REGISTRYINDEX;
398     XBT_DEBUG("Will get the value from the registry of the father");
399   }
400   else {
401     /* global */
402     pseudo_index = LUA_GLOBALSINDEX;
403     XBT_DEBUG("Will get the value from the globals of the father");
404   }
405
406   /* get the father */
407   lua_State* father = sglua_get_father(L);
408
409   if (father == NULL) {
410     XBT_WARN("This state has no father");
411     lua_pop(L, 3);
412     lua_pushnil(L);
413     return 1;
414   }
415                                   /* L:      table key */
416
417   /* get the list of visited tables */
418   lua_pushstring(L, "simgrid.father_visited_tables");
419                                   /* L:      table key "simgrid.father_visited_tables" */
420   lua_rawget(L, LUA_REGISTRYINDEX);
421                                   /* L:      table key visited */
422   lua_insert(L, 1);
423                                   /* L:      visited table key */
424
425   /* get the value from the father */
426   lua_getfield(father, pseudo_index, key);
427                                   /* father: ... value */
428
429   /* push the value onto the stack of L */
430   sglua_move_value_impl(father, L, key);
431                                   /* father: ...
432                                      L:      visited table key value */
433   lua_remove(L, 1);
434                                   /* L:      table key value */
435
436   /* prepare the return value of __index */
437   lua_pushvalue(L, -1);
438                                   /* L:      table key value value */
439   lua_insert(L, 1);
440                                   /* L:      value table key value */
441
442   /* save the copied value in the table for subsequent accesses */
443   lua_settable(L, -3);
444                                   /* L:      value table */
445   lua_settop(L, 1);
446                                   /* L:      value */
447
448   XBT_DEBUG("__index of '%s' returns %s", key, sglua_tostring(L, -1));
449
450   return 1;
451 }
452
453 /**
454  * @brief Creates a new Lua state and get its environment from an existing state.
455  *
456  * The state created is independent from the existing one and has its own
457  * copies of global and registry values.
458  * However, the global and registry values are not copied right now from
459  * the original state; they are copied only the first time they are accessed.
460  * This behavior saves time and memory, and is okay for Simgrid's needs.
461  *
462  * TODO: if the simulation runs in parallel, copy everything right now?
463  *
464  * @param father an existing state
465  * @return the state created
466  */
467 lua_State* sglua_clone_state(lua_State *father) {
468
469   /* create the new state */
470   lua_State *L = luaL_newstate();
471
472   /* set its environment and its registry:
473    * - create a table newenv
474    * - create a metatable mt
475    * - set mt.__index = a function that copies the global from the father state
476    * - set mt as the metatable of the registry
477    * - set mt as the metatable of newenv
478    * - set newenv as the environment of the new state
479    */
480   lua_pushthread(L);                        /* thread */
481   lua_newtable(L);                          /* thread newenv */
482   lua_newtable(L);                          /* thread newenv mt */
483   lua_pushvalue(L, LUA_REGISTRYINDEX);      /* thread newenv mt reg */
484   lua_pushcfunction(L, l_get_from_father);  /* thread newenv mt reg f */
485   lua_setfield(L, -3, "__index");           /* thread newenv mt reg */
486   lua_pushvalue(L, -2);                     /* thread newenv mt reg mt */
487   lua_setmetatable(L, -2);                  /* thread newenv mt reg */
488   lua_pop(L, 1);                            /* thread newenv mt */
489   lua_setmetatable(L, -2);                  /* thread newenv */
490   lua_setfenv(L, -2);                       /* thread */
491   lua_pop(L, 1);                            /* -- */
492
493   /* set a pointer to the father */
494   lua_pushstring(L, "simgrid.father");      /* "simgrid.father" */
495   lua_pushlightuserdata(L, father);         /* "simgrid.father" father */
496   lua_rawset(L, LUA_REGISTRYINDEX);
497                                             /* -- */
498
499   /* create the table of visited tables from the father */
500   lua_pushstring(L, "simgrid.father_visited_tables");
501                                             /* "simgrid.father_visited_tables" */
502   lua_newtable(L);                          /* "simgrid.father_visited_tables" visited */
503   lua_rawset(L, LUA_REGISTRYINDEX);
504                                             /* -- */
505
506   /* create the table of my own visited tables */
507   lua_pushstring(L, "simgrid.visited_tables");
508                                             /* "simgrid.visited_tables" */
509   lua_newtable(L);                          /* "simgrid.visited_tables" visited */
510   lua_rawset(L, LUA_REGISTRYINDEX);
511                                             /* -- */
512
513   /* open the standard libs (theoretically, this is not necessary as they can
514    * be inherited like any global values, but without a proper support of
515    * closures, iterators like ipairs don't work). */
516   XBT_DEBUG("Metatable of globals and registry set, opening standard libraries now");
517   luaL_openlibs(L);
518
519   XBT_DEBUG("New state created");
520
521   return L;
522 }