aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES1
-rw-r--r--docs/index.html58
-rw-r--r--src/intercopycontext.cpp98
-rw-r--r--src/intercopycontext.hpp2
-rw-r--r--src/tools.cpp29
5 files changed, 134 insertions, 54 deletions
diff --git a/CHANGES b/CHANGES
index a2dbad4..db44b2c 100644
--- a/CHANGES
+++ b/CHANGES
@@ -23,6 +23,7 @@ CHANGE 2: BGe 27-Nov-24
23 - strip_functions added. Only useful for Lua 5.3+. 23 - strip_functions added. Only useful for Lua 5.3+.
24 - verbose_errors removed. Use lane error_trace_level instead. 24 - verbose_errors removed. Use lane error_trace_level instead.
25 - with_timers is false by default. 25 - with_timers is false by default.
26 - Non-deep full userdata are processed during module registration just like ordinary module C functions, making them valid transferable (up)values (for example: io.stdin).
26 - Lanes: 27 - Lanes:
27 - Can no longer be "killed" by hard-stopping their thread without any resource cleanup (see lane:cancel()). 28 - Can no longer be "killed" by hard-stopping their thread without any resource cleanup (see lane:cancel()).
28 - lanes.gen() settings: 29 - lanes.gen() settings:
diff --git a/docs/index.html b/docs/index.html
index 9ac36b6..7432c53 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -436,11 +436,11 @@
436</p> 436</p>
437 437
438<p> 438<p>
439 Once Lanes is configured, one should register with Lanes the modules exporting functions that will be transferred either during lane generation or through <a href="#lindas">lindas</a>. 439 Once Lanes is configured, one should register with Lanes the modules exporting functions/userdata that will be transferred either during lane generation or through <a href="#lindas">lindas</a>.
440 <br /> 440 <br />
441 Use <tt>lanes.require()</tt> for this purpose. This will call the original <tt>require()</tt>, then add the result to the lookup databases. 441 Use <tt>lanes.require()</tt> for this purpose. This will call the original <tt>require()</tt>, then add the result to the lookup databases.
442 <br /> 442 <br />
443 It is also possible to register a given module <i>a posteriori</i> with <tt>lanes.register()</tt>. This function will raise an error if the registered module is not a function or table.<br /> 443 It is also possible to register a given module <i>a posteriori</i> with <tt>lanes.register()</tt>. This function will raise an error if the registered module is not a function, table, or full userdata.<br />
444 Embedders can call the equivalent function <tt>lanes_register()</tt> from the C side, through <tt>lua_call()</tt> or similar. 444 Embedders can call the equivalent function <tt>lanes_register()</tt> from the C side, through <tt>lua_call()</tt> or similar.
445</p> 445</p>
446 446
@@ -715,7 +715,7 @@
715 </td> 715 </td>
716 <td>table</td> 716 <td>table</td>
717 <td> 717 <td>
718 Lists modules that have to be required in order to be able to transfer functions they exposed. Non-Lua functions are <a href="#function_notes">searched in lookup tables</a>. 718 Lists modules that have to be required in order to be able to transfer functions/userdata they exposed. Non-Lua functions are <a href="#function_notes">searched in lookup tables</a>.
719 These tables are built from the modules listed here. <tt>required</tt> must be an array of strings, each one being the name of a module to be required. Each module is required with <tt>require()</tt> before the lanes function is invoked. 719 These tables are built from the modules listed here. <tt>required</tt> must be an array of strings, each one being the name of a module to be required. Each module is required with <tt>require()</tt> before the lanes function is invoked.
720 So, from the required module's point of view, requiring it manually from inside the lane body or having it required this way doesn't change anything. From the lane body's point of view, the only difference is that a module not creating a global won't be accessible. 720 So, from the required module's point of view, requiring it manually from inside the lane body or having it required this way doesn't change anything. From the lane body's point of view, the only difference is that a module not creating a global won't be accessible.
721 Therefore, a lane body will also have to require a module manually, but this won't do anything more (see Lua's <tt>require</tt> documentation). <br /> 721 Therefore, a lane body will also have to require a module manually, but this won't do anything more (see Lua's <tt>require</tt> documentation). <br />
@@ -785,8 +785,8 @@
785</p> 785</p>
786 786
787<p> 787<p>
788 If a lane body pulls a C function imported by a module required before Lanes itself (thus not through a hooked <tt>require()</tt>), the lane generator creation will raise an error. 788 If a lane body pulls a C function or userdata exposed by a module required before Lanes itself (thus not through a hooked <tt>require()</tt>), the lane generator creation will raise an error.
789 The function name it shows is a path where it was found by scanning <tt>_G</tt> and the registry. As a utility, the name guessing functionality is exposed as such: 789 The name in the message is a path where it was found by scanning <tt>_G</tt> and the registry. As a utility, the name guessing functionality is exposed as such:
790 790
791 <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%"> 791 <table border="1" bgcolor="#E0E0FF" cellpadding="10" style="width:50%">
792 <tr> 792 <tr>
@@ -1233,10 +1233,10 @@
1233 Characteristics of the Lanes implementation of lindas are: 1233 Characteristics of the Lanes implementation of lindas are:
1234 1234
1235 <ul> 1235 <ul>
1236 <li>Keys can be of boolean, number, string, light userdata, and deep userdata type. Tables and functions can't be keys because their identity isn't preserved when transfered from one Lua state to another.</li> 1236 <li>Keys can be of boolean, number, string, light userdata, and deep userdata type. Tables, functions and non-deep userdata can't be keys because their identity isn't preserved when transfered from one Lua state to another.</li>
1237 <li>values can be any type supported by inter-state copying (same <a href="#limitations">limits</a> as for function arguments and upvalues).</li> 1237 <li>Values can be any type supported by inter-state copying (same <a href="#limitations">limits</a> as for function arguments and upvalues).</li>
1238 <li> 1238 <li>
1239 Registered functions transiting into a Keeper state are converted to a special dummy function that holds its actual identity. On transit out, the identity is used to find the real function in the destination. 1239 Registered functions and userdata transiting into a Keeper state are converted to a special dummy closure that holds its actual identity. On transit out, the identity is used to find the real function or userdata in the destination.
1240 For that reason, it is not possible to run user code inside a Keeper state. The only exception is <a href="#on_state_create"><tt>on_state_create</tt></a>, which is handled in a special way. 1240 For that reason, it is not possible to run user code inside a Keeper state. The only exception is <a href="#on_state_create"><tt>on_state_create</tt></a>, which is handled in a special way.
1241 </li> 1241 </li>
1242 <li>Consuming method is <tt>:receive</tt> (not in).</li> 1242 <li>Consuming method is <tt>:receive</tt> (not in).</li>
@@ -1644,7 +1644,7 @@
1644 </ul> 1644 </ul>
1645 </li> 1645 </li>
1646 <li> 1646 <li>
1647 Non-converted full userdata can be passed only if it is prepared using the <a href="#deep_userdata">deep userdata</a> system, which handles its lifespan management. 1647 Non-converted full userdata can be passed only if it registered, or prepared using the <a href="#deep_userdata">deep userdata</a> system, which handles its lifespan management.
1648 </li> 1648 </li>
1649 <li> 1649 <li>
1650 Objects (tables with a metatable) are copyable between lanes. Metatables are assumed to be immutable; they are internally indexed and only copied once per each type of objects per lane. 1650 Objects (tables with a metatable) are copyable between lanes. Metatables are assumed to be immutable; they are internally indexed and only copied once per each type of objects per lane.
@@ -1661,55 +1661,57 @@
1661</p> 1661</p>
1662 1662
1663 1663
1664<h3 id="function_notes">Notes about passing C functions</h3> 1664<h3 id="function_notes">Notes about passing C functions and full userdata</h3>
1665 1665
1666<p> 1666<p>
1667 Functions are transfered as follows (more or less): 1667 C functions and full userdata are transfered as follows (more or less):
1668</p> 1668</p>
1669 1669
1670<table border="1" bgcolor="#FFFFE0" cellpadding="10" style="width:50%"><tr><td><pre> 1670<table border="1" bgcolor="#FFFFE0" cellpadding="10" style="width:50%"><tr><td><pre>
1671 // expects a C function on top of the source Lua stack 1671 // expects a C function or full userdata on top of the source Lua stack
1672 copyFunction(lua_State *dest, lua_State* source) 1672 copyValue(lua_State *dest, lua_State* source)
1673 { 1673 {
1674 // fetch function 'name' from source lookup database 1674 // fetch function 'name' from source lookup database
1675 char const* funcname = lookup_func_name(source, -1); 1675 char const* valuename = lookup_name(source, -1);
1676 // lookup a function bound to this name in the destination state, and push it on the stack 1676 // lookup a function or userdata bound to this name in the destination state, and push it on the stack
1677 push_resolved_func(dest, funcname); 1677 push_resolved_value(dest, valuename);
1678 } 1678 }
1679</pre></td></tr></table> 1679</pre></td></tr></table>
1680 1680
1681<p> 1681<p>
1682 The devil lies in the details: what does "function lookup" mean? 1682 The devil lies in the details: what does "lookup" mean?
1683</p> 1683</p>
1684 1684
1685<p> 1685<p>
1686 Since functions are first class values, they don't have a name. All we know for sure is that when a C module registers some functions, they are accessible to the script that required the module through some exposed variables. 1686 Since functions and full userdata are first class values, they don't have a name. All we know for sure is that when a C module registers some functions or full userdata, they are accessible to the script that required the module through some exposed variables.
1687 <br /> 1687 <br />
1688 For example, loading the <tt>string</tt> base library creates a table accessible when indexing the global environment with key <tt>"string"</tt>. Indexing this table with <tt>"match"</tt>, <tt>"gsub"</tt>, etc. will give us a function. 1688 For example, loading the <tt>string</tt> base library creates a table accessible when indexing the global environment with key <tt>"string"</tt>. Indexing this table with <tt>"match"</tt>, <tt>"gsub"</tt>, etc. will give us a function.
1689 <br /> 1689 <br />
1690 When a lane generator creates a lane and performs initializations described by the list of base libraries and the list of required modules, it recursively scans the table created by the initialisation of the module, looking for all values that are C functions. 1690 Similarly, loading the <tt>io</tt> base library creates a table accessible when indexing the global environment with key <tt>"io"</tt>. Indexing this table with <tt>"open"</tt>, will give us a function, and <tt>"stdin"</tt> will give us a full userdata.
1691 <br /> 1691 <br />
1692 Each time a function is encountered, the sequence of keys that reached that function is contatenated in a (hopefully) unique name. The [name, function] and [function, name] pairs are both stored in a lookup table in all involved Lua states (main Lua state and lanes states). 1692 When a lane generator creates a lane and performs initializations described by the list of base libraries and the list of required modules, it recursively scans the table created by the initialisation of the module, looking for all values that are C functions and full userdata.
1693 <br /> 1693 <br />
1694 Then when a function is transfered from one state to another, all we have to do is retrieve the name associated to a function in the source Lua state, then with that name retrieve the equivalent function that already exists in the destination state. 1694 Each time a function or full userdata is encountered, the sequence of keys traversed to reach it is contatenated in a (hopefully) unique name. The [name, value] and [value, name] pairs are both stored in a lookup table in all involved Lua states (main Lua state and lanes states).
1695 <br /> 1695 <br />
1696 Note that there is no need to transfer upvalues, as they are already bound to the function registered in the destination state. (And in any event, it is not possible to create a closure from a C function pushed on the stack, it can only be created with a <tt>lua_CFunction</tt> pointer). 1696 Then, when a function or full userdata is transfered from one state to another, all we have to do is retrieve the name associated to this value in the source Lua state, then with that name retrieve the equivalent value that already exists in the destination state.
1697 <br />
1698 Note that there is no need to transfer upvalues/uservalues, as they are already bound to the value registered in the destination state. (And in any event, it is not possible to create a closure from a C function pushed on the stack, it can only be created with a <tt>lua_CFunction</tt> pointer).
1697</p> 1699</p>
1698 1700
1699<p> 1701<p>
1700 There are several issues here: 1702 There are several issues here:
1701 <ul> 1703 <ul>
1702 <li> 1704 <li>
1703 Some base libraries register some C functions in the global environment. Because of that, Lanes must scan the global namespace to find all C functions (such as <tt>error</tt>, <tt>print</tt>, etc.).<br /> 1705 Some base libraries register some C functions in the global environment. Because of that, Lanes must scan the global namespace to find all C functions (such as <tt>error</tt>, <tt>print</tt>, <tt>require</tt>, etc.).<br />
1704 This happens a single time, when <a href="#initialization"><tt>lanes.configure()</tt></a> is called. Therefore, if some base libraries are not loaded before that point, it will not be possible to send values that reference stuff they offer unless they are manually registered with <tt>lanes.register()</tt>. 1706 This happens a single time, when <a href="#initialization"><tt>lanes.configure()</tt></a> is called. Therefore, if some base libraries are not loaded before that point, it will not be possible to send values that reference stuff they offer unless they are manually registered with <tt>lanes.register()</tt>.
1705 </li> 1707 </li>
1706 <li> 1708 <li>
1707 Nothing prevents a script to create other references to a C function. For example one could do 1709 Nothing prevents a script to create other references to a C function or full userdata. For example one could do
1708 <table border="1" bgcolor="#FFFFE0" cellpadding="10" style="width:50%"><tr><td><pre> 1710 <table border="1" bgcolor="#FFFFE0" cellpadding="10" style="width:50%"><tr><td><pre>
1709 string2 = string 1711 string2 = string
1710 </pre></td></tr></table> 1712 </pre></td></tr></table>
1711 When iterating over all keys of the global table, Lanes has no guarantee that it will hit <tt>"string"</tt> before or after <tt>"string2"</tt>. However, the values associated to <tt>string.match</tt> and <tt>string2.match</tt> are the same C function. 1713 When iterating over all keys of the global table, Lanes has no guarantee that it will hit <tt>"string"</tt> before or after <tt>"string2"</tt>. However, the values associated to <tt>string.match</tt> and <tt>string2.match</tt> are the same C function.
1712 Lanes doesn't normally expect a C function value to be encountered more than once. In the event it occurs, the shortest name that was computed is retained. 1714 Lanes doesn't normally expect a C function or full userdata value to be encountered more than once. In the event it occurs, the shortest name that was computed is retained.
1713 If Lanes processed <tt>"string2"</tt> first, it means that if the Lua state that contains the <tt>"string2"</tt> global name sends function <tt>string.match</tt>, <tt>lookup_func_name</tt> would return name <tt>"string2.match"</tt>, with the obvious effect that <tt>push_resolved_func</tt> 1715 If Lanes processed <tt>"string2"</tt> first, it means that if the Lua state that contains the <tt>"string2"</tt> global name sends function <tt>string.match</tt>, <tt>lookup_func_name</tt> would return name <tt>"string2.match"</tt>, with the obvious effect that <tt>push_resolved_func</tt>
1714 won't find <tt>"string2.match"</tt> in the destination lookup database, thus failing the transfer (even though this function exists, but is referenced under name <tt>"string.match"</tt>). 1716 won't find <tt>"string2.match"</tt> in the destination lookup database, thus failing the transfer (even though this function exists, but is referenced under name <tt>"string.match"</tt>).
1715 </li> 1717 </li>
@@ -1721,9 +1723,9 @@
1721 To circumvent this, Lanes has to select one name among all candidates, and the rule for this is to keep the 'smaller' one: first in byte count, then in lexical order. 1723 To circumvent this, Lanes has to select one name among all candidates, and the rule for this is to keep the 'smaller' one: first in byte count, then in lexical order.
1722 </li> 1724 </li>
1723 </ul> 1725 </ul>
1724 Another more immediate reason of failed transfer is when the destination state doesn't know about the C function that has to be transferred. This occurs if a function is transferred in a lane before it had a chance to scan the module. 1726 Another more immediate reason of failed transfer is when the destination state doesn't know about the C function or full userdata that has to be transferred. This occurs if a value is transferred in a lane before it had a chance to scan the module that exposed it.
1725 If the C function is sent through a <a href="#lindas">linda</a>, it is enough for the destination lane body to have required the module before the function is sent. 1727 If the C function or full userdata is sent through a <a href="#lindas">linda</a>, it is sufficient for the destination lane body to have required the module before the function is sent.
1726 But if the lane body provided to the generator has a C function as upvalue, the transfer itself must succeed, therefore the module that imported that C function must be required in the destination lane before the lane body starts executing. This is where the <a href = "#.required"><tt>.required</tt></a> options play their role. 1728 But if the lane body provided to the generator has a C function or full userdata as upvalue, the transfer itself must succeed, therefore the module that exposed that C function or full userdata must be required in the destination lane before the lane body starts executing. This is where the <a href = "#.required"><tt>.required</tt></a> options play their role.
1727</p> 1729</p>
1728 1730
1729 1731
diff --git a/src/intercopycontext.cpp b/src/intercopycontext.cpp
index 568e4cb..1be4c53 100644
--- a/src/intercopycontext.cpp
+++ b/src/intercopycontext.cpp
@@ -55,7 +55,7 @@ static int buf_writer(lua_State* L_, void const* b_, size_t size_, void* ud_)
55 55
56// function sentinel used to transfer native functions from/to keeper states 56// function sentinel used to transfer native functions from/to keeper states
57[[nodiscard]] 57[[nodiscard]]
58static int func_lookup_sentinel(lua_State* L_) 58static int func_lookup_sentinel(lua_State* const L_)
59{ 59{
60 raise_luaL_error(L_, "function lookup sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); 60 raise_luaL_error(L_, "function lookup sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1)));
61} 61}
@@ -64,7 +64,7 @@ static int func_lookup_sentinel(lua_State* L_)
64 64
65// function sentinel used to transfer native table from/to keeper states 65// function sentinel used to transfer native table from/to keeper states
66[[nodiscard]] 66[[nodiscard]]
67static int table_lookup_sentinel(lua_State* L_) 67static int table_lookup_sentinel(lua_State* const L_)
68{ 68{
69 raise_luaL_error(L_, "table lookup sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); 69 raise_luaL_error(L_, "table lookup sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1)));
70} 70}
@@ -73,23 +73,32 @@ static int table_lookup_sentinel(lua_State* L_)
73 73
74// function sentinel used to transfer cloned full userdata from/to keeper states 74// function sentinel used to transfer cloned full userdata from/to keeper states
75[[nodiscard]] 75[[nodiscard]]
76static int userdata_clone_sentinel(lua_State* L_) 76static int userdata_clone_sentinel(lua_State* const L_)
77{ 77{
78 raise_luaL_error(L_, "userdata clone sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1))); 78 raise_luaL_error(L_, "userdata clone sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1)));
79} 79}
80 80
81// ################################################################################################# 81// #################################################################################################
82 82
83// retrieve the name of a function/table in the lookup database 83// function sentinel used to transfer native table from/to keeper states
84[[nodiscard]]
85static int userdata_lookup_sentinel(lua_State* const L_)
86{
87 raise_luaL_error(L_, "userdata lookup sentinel for %s, should never be called", lua_tostring(L_, lua_upvalueindex(1)));
88}
89
90// #################################################################################################
91
92// retrieve the name of a function/table/userdata in the lookup database
84[[nodiscard]] 93[[nodiscard]]
85std::string_view InterCopyContext::findLookupName() const 94std::string_view InterCopyContext::findLookupName() const
86{ 95{
87 LUA_ASSERT(L1, lua_isfunction(L1, L1_i) || lua_istable(L1, L1_i)); // L1: ... v ... 96 LUA_ASSERT(L1, lua_isfunction(L1, L1_i) || lua_istable(L1, L1_i) || luaG_type(L1, L1_i) == LuaType::USERDATA);
88 STACK_CHECK_START_REL(L1, 0); 97 STACK_CHECK_START_REL(L1, 0); // L1: ... v ...
89 STACK_GROW(L1, 3); // up to 3 slots are necessary on error 98 STACK_GROW(L1, 3); // up to 3 slots are necessary on error
90 if (mode == LookupMode::FromKeeper) { 99 if (mode == LookupMode::FromKeeper) {
91 lua_CFunction const _f{ lua_tocfunction(L1, L1_i) }; // should *always* be one of the function sentinels 100 lua_CFunction const _f{ lua_tocfunction(L1, L1_i) }; // should *always* be one of the sentinel functions
92 if (_f == func_lookup_sentinel || _f == table_lookup_sentinel || _f == userdata_clone_sentinel) { 101 if (_f == func_lookup_sentinel || _f == table_lookup_sentinel || _f == userdata_clone_sentinel || _f == userdata_lookup_sentinel) {
93 lua_getupvalue(L1, L1_i, 1); // L1: ... v ... "f.q.n" 102 lua_getupvalue(L1, L1_i, 1); // L1: ... v ... "f.q.n"
94 } else { 103 } else {
95 // if this is not a sentinel, this is some user-created table we wanted to lookup 104 // if this is not a sentinel, this is some user-created table we wanted to lookup
@@ -110,8 +119,8 @@ std::string_view InterCopyContext::findLookupName() const
110 // popping doesn't invalidate the pointer since this is an interned string gotten from the lookup database 119 // popping doesn't invalidate the pointer since this is an interned string gotten from the lookup database
111 lua_pop(L1, (mode == LookupMode::FromKeeper) ? 1 : 2); // L1: ... v ... 120 lua_pop(L1, (mode == LookupMode::FromKeeper) ? 1 : 2); // L1: ... v ...
112 STACK_CHECK(L1, 0); 121 STACK_CHECK(L1, 0);
113 if (_fqn.empty() && !lua_istable(L1, L1_i)) { // raise an error if we try to send an unknown function (but not for tables) 122 if (_fqn.empty() && !lua_istable(L1, L1_i)) { // raise an error if we try to send an unknown function/userdata (but not for tables)
114 // try to discover the name of the function we want to send 123 // try to discover the name of the function/userdata we want to send
115 kLaneNameRegKey.pushValue(L1); // L1: ... v ... lane_name 124 kLaneNameRegKey.pushValue(L1); // L1: ... v ... lane_name
116 std::string_view const _from{ luaG_tostring(L1, kIdxTop) }; 125 std::string_view const _from{ luaG_tostring(L1, kIdxTop) };
117 lua_pushcfunction(L1, LG_nameof); // L1: ... v ... lane_name LG_nameof 126 lua_pushcfunction(L1, LG_nameof); // L1: ... v ... lane_name LG_nameof
@@ -331,7 +340,8 @@ void InterCopyContext::lookupNativeFunction() const
331 lua_rawget(L2, -2); // L1: ... f ... L2: {} f 340 lua_rawget(L2, -2); // L1: ... f ... L2: {} f
332 // nil means we don't know how to transfer stuff: user should do something 341 // nil means we don't know how to transfer stuff: user should do something
333 // anything other than function or table should not happen! 342 // anything other than function or table should not happen!
334 if (!lua_isfunction(L2, -1) && !lua_istable(L2, -1)) { 343 LuaType const _objType{ luaG_type(L2, kIdxTop) };
344 if (_objType != LuaType::FUNCTION && _objType != LuaType::TABLE && _objType != LuaType::USERDATA) {
335 kLaneNameRegKey.pushValue(L1); // L1: ... f ... lane_name 345 kLaneNameRegKey.pushValue(L1); // L1: ... f ... lane_name
336 std::string_view const _from{ luaG_tostring(L1, kIdxTop) }; 346 std::string_view const _from{ luaG_tostring(L1, kIdxTop) };
337 lua_pop(L1, 1); // L1: ... f ... 347 lua_pop(L1, 1); // L1: ... f ...
@@ -340,9 +350,10 @@ void InterCopyContext::lookupNativeFunction() const
340 lua_pop(L2, 1); // L2: {} f 350 lua_pop(L2, 1); // L2: {} f
341 raise_luaL_error( 351 raise_luaL_error(
342 getErrL(), 352 getErrL(),
343 "%s%s: function '%s' not found in %s destination transfer database.", 353 "%s%s: %s '%s' not found in %s destination transfer database.",
344 lua_isnil(L2, -1) ? "" : "INTERNAL ERROR IN ", 354 lua_isnil(L2, -1) ? "" : "INTERNAL ERROR IN ",
345 _from.empty() ? "main" : _from.data(), 355 _from.empty() ? "main" : _from.data(),
356 luaG_typename(L2, _objType),
346 _fqn.data(), 357 _fqn.data(),
347 _to.empty() ? "main" : _to.data()); 358 _to.empty() ? "main" : _to.data());
348 return; 359 return;
@@ -397,8 +408,8 @@ void InterCopyContext::copyCachedFunction() const
397 LUA_ASSERT(L1, lua_isfunction(L2, -1)); 408 LUA_ASSERT(L1, lua_isfunction(L2, -1));
398 } else { // function is native/LuaJIT: no need to cache 409 } else { // function is native/LuaJIT: no need to cache
399 lookupNativeFunction(); // L2: ... {cache} ... function 410 lookupNativeFunction(); // L2: ... {cache} ... function
400 // if the function was in fact a lookup sentinel, we can either get a function or a table here 411 // if the function was in fact a lookup sentinel, we can either get a function, table or full userdata here
401 LUA_ASSERT(L1, lua_isfunction(L2, -1) || lua_istable(L2, -1)); 412 LUA_ASSERT(L1, lua_isfunction(L2, kIdxTop) || lua_istable(L2, kIdxTop) || luaG_type(L2, kIdxTop) == LuaType::USERDATA);
402 } 413 }
403} 414}
404 415
@@ -678,6 +689,59 @@ bool InterCopyContext::pushCachedTable() const
678 689
679// ################################################################################################# 690// #################################################################################################
680 691
692// Push a looked-up full userdata.
693[[nodiscard]]
694bool InterCopyContext::lookupUserdata() const
695{
696 // get the name of the userdata we want to send
697 std::string_view const _fqn{ findLookupName() };
698 // push the equivalent userdata in the destination's stack, retrieved from the lookup table
699 STACK_CHECK_START_REL(L2, 0);
700 STACK_GROW(L2, 3); // up to 3 slots are necessary on error
701 switch (mode) {
702 default: // shouldn't happen, in theory...
703 raise_luaL_error(getErrL(), "internal error: unknown lookup mode");
704 break;
705
706 case LookupMode::ToKeeper:
707 // push a sentinel closure that holds the lookup name as upvalue
708 luaG_pushstring(L2, _fqn); // L1: ... f ... L2: "f.q.n"
709 lua_pushcclosure(L2, userdata_lookup_sentinel, 1); // L1: ... f ... L2: f
710 break;
711
712 case LookupMode::LaneBody:
713 case LookupMode::FromKeeper:
714 kLookupRegKey.pushValue(L2); // L1: ... f ... L2: {}
715 STACK_CHECK(L2, 1);
716 LUA_ASSERT(L1, lua_istable(L2, -1));
717 luaG_pushstring(L2, _fqn); // L1: ... f ... L2: {} "f.q.n"
718 lua_rawget(L2, -2); // L1: ... f ... L2: {} f
719 // nil means we don't know how to transfer stuff: user should do something
720 // anything other than function or table should not happen!
721 if (!lua_isfunction(L2, -1) && !lua_istable(L2, -1)) {
722 kLaneNameRegKey.pushValue(L1); // L1: ... f ... lane_name
723 std::string_view const _from{ luaG_tostring(L1, kIdxTop) };
724 lua_pop(L1, 1); // L1: ... f ...
725 kLaneNameRegKey.pushValue(L2); // L1: ... f ... L2: {} f lane_name
726 std::string_view const _to{ luaG_tostring(L2, kIdxTop) };
727 lua_pop(L2, 1); // L2: {} f
728 raise_luaL_error(
729 getErrL(),
730 "%s%s: userdata '%s' not found in %s destination transfer database.",
731 lua_isnil(L2, -1) ? "" : "INTERNAL ERROR IN ",
732 _from.empty() ? "main" : _from.data(),
733 _fqn.data(),
734 _to.empty() ? "main" : _to.data());
735 }
736 lua_remove(L2, -2); // L2: f
737 break;
738 }
739 STACK_CHECK(L2, 1);
740 return true;
741}
742
743// #################################################################################################
744
681[[nodiscard]] 745[[nodiscard]]
682bool InterCopyContext::tryCopyClonable() const 746bool InterCopyContext::tryCopyClonable() const
683{ 747{
@@ -1099,6 +1163,12 @@ bool InterCopyContext::interCopyUserdata() const
1099 return true; 1163 return true;
1100 } 1164 }
1101 1165
1166 // Last, let's try to see if this userdata is special (aka is it some userdata that we registered in our lookup databases during module registration?)
1167 if (lookupUserdata()) {
1168 LUA_ASSERT(L1, luaG_type(L2, kIdxTop) == LuaType::USERDATA || (lua_tocfunction(L2, kIdxTop) == userdata_lookup_sentinel)); // from lookup data. can also be userdata_lookup_sentinel if this is a userdata we know
1169 return true;
1170 }
1171
1102 raise_luaL_error(getErrL(), "can't copy non-deep full userdata across lanes"); 1172 raise_luaL_error(getErrL(), "can't copy non-deep full userdata across lanes");
1103} 1173}
1104 1174
diff --git a/src/intercopycontext.hpp b/src/intercopycontext.hpp
index 84d9e70..7008919 100644
--- a/src/intercopycontext.hpp
+++ b/src/intercopycontext.hpp
@@ -64,6 +64,8 @@ class InterCopyContext
64 64
65 // for use in inter_copy_userdata 65 // for use in inter_copy_userdata
66 [[nodiscard]] 66 [[nodiscard]]
67 bool lookupUserdata() const;
68 [[nodiscard]]
67 bool tryCopyClonable() const; 69 bool tryCopyClonable() const;
68 [[nodiscard]] 70 [[nodiscard]]
69 bool tryCopyDeep() const; 71 bool tryCopyDeep() const;
diff --git a/src/tools.cpp b/src/tools.cpp
index 827c4a4..cd64d13 100644
--- a/src/tools.cpp
+++ b/src/tools.cpp
@@ -123,7 +123,7 @@ namespace tools {
123 * receives 2 arguments: a name k and an object o 123 * receives 2 arguments: a name k and an object o
124 * add two entries ["fully.qualified.name"] = o 124 * add two entries ["fully.qualified.name"] = o
125 * and [o] = "fully.qualified.name" 125 * and [o] = "fully.qualified.name"
126 * where <o> is either a table or a function 126 * where <o> is either a table, a function, or a full userdata
127 * if we already had an entry of type [o] = ..., replace the name if the new one is shorter 127 * if we already had an entry of type [o] = ..., replace the name if the new one is shorter
128 * pops the processed object from the stack 128 * pops the processed object from the stack
129 */ 129 */
@@ -191,7 +191,7 @@ static void update_lookup_entry(lua_State* const L_, StackIndex const ctxBase_,
191 191
192// ################################################################################################# 192// #################################################################################################
193 193
194static void populate_func_lookup_table_recur(lua_State* const L_, StackIndex const dbIdx_, StackIndex const i_, int const depth_) 194static void populate_lookup_table_recur(lua_State* const L_, StackIndex const dbIdx_, StackIndex const i_, int const depth_)
195{ 195{
196 // slot dbIdx_ contains the lookup database table 196 // slot dbIdx_ contains the lookup database table
197 // slot dbIdx_ + 1 contains a table that, when concatenated, produces the fully qualified name of scanned elements in the table provided at slot i_ 197 // slot dbIdx_ + 1 contains a table that, when concatenated, produces the fully qualified name of scanned elements in the table provided at slot i_
@@ -199,7 +199,7 @@ static void populate_func_lookup_table_recur(lua_State* const L_, StackIndex con
199 // slot dbIdx_ + 2 contains a cache that stores all already visited tables to avoid infinite recursion loops 199 // slot dbIdx_ + 2 contains a cache that stores all already visited tables to avoid infinite recursion loops
200 StackIndex const _cache{ dbIdx_ + 2 }; 200 StackIndex const _cache{ dbIdx_ + 2 };
201 DEBUGSPEW_CODE(Universe* const _U{ Universe::Get(L_) }); 201 DEBUGSPEW_CODE(Universe* const _U{ Universe::Get(L_) });
202 DEBUGSPEW_CODE(DebugSpew(_U) << "populate_func_lookup_table_recur()" << std::endl); 202 DEBUGSPEW_CODE(DebugSpew(_U) << "populate_lookup_table_recur()" << std::endl);
203 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U }); 203 DEBUGSPEW_CODE(DebugSpewIndentScope _scope{ _U });
204 204
205 STACK_GROW(L_, 6); 205 STACK_GROW(L_, 6);
@@ -231,14 +231,14 @@ static void populate_func_lookup_table_recur(lua_State* const L_, StackIndex con
231 231
232 // we need to remember subtables to process them after functions encountered at the current depth (breadth-first search) 232 // we need to remember subtables to process them after functions encountered at the current depth (breadth-first search)
233 lua_newtable(L_); // L_: ... {i_} {bfc} 233 lua_newtable(L_); // L_: ... {i_} {bfc}
234 int const breadthFirstCache{ lua_gettop(L_) }; 234 StackIndex const _breadthFirstCache{ lua_gettop(L_) };
235 // iterate over all entries in the processed table 235 // iterate over all entries in the processed table
236 lua_pushnil(L_); // L_: ... {i_} {bfc} nil 236 lua_pushnil(L_); // L_: ... {i_} {bfc} nil
237 while (lua_next(L_, i_) != 0) { // L_: ... {i_} {bfc} k v 237 while (lua_next(L_, i_) != 0) { // L_: ... {i_} {bfc} k v
238 // just for debug, not actually needed 238 // just for debug, not actually needed
239 // std::string_view const _key{ (luaG_type(L_, -2) == LuaType::STRING) ? luaG_tostring(L_, -2) : "not a string" }; 239 // std::string_view const _key{ (luaG_type(L_, -2) == LuaType::STRING) ? luaG_tostring(L_, -2) : "not a string" };
240 // subtable: process it recursively 240 // subtable: process it recursively
241 if (lua_istable(L_, -1)) { // L_: ... {i_} {bfc} k {} 241 if (lua_istable(L_, kIdxTop)) { // L_: ... {i_} {bfc} k {}
242 // increment visit count to make sure we will actually scan it at this recursive level 242 // increment visit count to make sure we will actually scan it at this recursive level
243 lua_pushvalue(L_, -1); // L_: ... {i_} {bfc} k {} {} 243 lua_pushvalue(L_, -1); // L_: ... {i_} {bfc} k {} {}
244 lua_pushvalue(L_, -1); // L_: ... {i_} {bfc} k {} {} {} 244 lua_pushvalue(L_, -1); // L_: ... {i_} {bfc} k {} {} {}
@@ -250,22 +250,26 @@ static void populate_func_lookup_table_recur(lua_State* const L_, StackIndex con
250 // store the table in the breadth-first cache 250 // store the table in the breadth-first cache
251 lua_pushvalue(L_, -2); // L_: ... {i_} {bfc} k {} k 251 lua_pushvalue(L_, -2); // L_: ... {i_} {bfc} k {} k
252 lua_pushvalue(L_, -2); // L_: ... {i_} {bfc} k {} k {} 252 lua_pushvalue(L_, -2); // L_: ... {i_} {bfc} k {} k {}
253 lua_rawset(L_, breadthFirstCache); // L_: ... {i_} {bfc} k {} 253 lua_rawset(L_, _breadthFirstCache); // L_: ... {i_} {bfc} k {}
254 // generate a name, and if we already had one name, keep whichever is the shorter 254 // generate a name, and if we already had one name, keep whichever is the shorter
255 update_lookup_entry(L_, dbIdx_, depth_); // L_: ... {i_} {bfc} k 255 update_lookup_entry(L_, dbIdx_, depth_); // L_: ... {i_} {bfc} k
256 } else if (lua_isfunction(L_, -1) && (luaG_getfuncsubtype(L_, kIdxTop) != FuncSubType::Bytecode)) { 256 } else if (lua_isfunction(L_, kIdxTop) && (luaG_getfuncsubtype(L_, kIdxTop) != FuncSubType::Bytecode)) {
257 // generate a name, and if we already had one name, keep whichever is the shorter 257 // generate a name, and if we already had one name, keep whichever is the shorter
258 // this pops the function from the stack 258 // this pops the function from the stack
259 update_lookup_entry(L_, dbIdx_, depth_); // L_: ... {i_} {bfc} k 259 update_lookup_entry(L_, dbIdx_, depth_); // L_: ... {i_} {bfc} k
260 } else if (luaG_type(L_, kIdxTop) == LuaType::USERDATA) {
261 // generate a name, and if we already had one name, keep whichever is the shorter
262 // this pops the userdata from the stack
263 update_lookup_entry(L_, dbIdx_, depth_); // L_: ... {i_} {bfc} k
260 } else { 264 } else {
261 lua_pop(L_, 1); // L_: ... {i_} {bfc} k 265 lua_pop(L_, 1); // L_: ... {i_} {bfc} k
262 } 266 }
263 STACK_CHECK(L_, 2); 267 STACK_CHECK(L_, 2);
264 } 268 }
265 // now process the tables we encountered at that depth 269 // now process the tables we encountered at that depth
266 int const _deeper{ depth_ + 1 }; 270 int const _deeper{ depth_ + 1 };
267 lua_pushnil(L_); // L_: ... {i_} {bfc} nil 271 lua_pushnil(L_); // L_: ... {i_} {bfc} nil
268 while (lua_next(L_, breadthFirstCache) != 0) { // L_: ... {i_} {bfc} k {} 272 while (lua_next(L_, _breadthFirstCache) != 0) { // L_: ... {i_} {bfc} k {}
269 DEBUGSPEW_CODE(std::string_view const _key{ (luaG_type(L_, -2) == LuaType::STRING) ? luaG_tostring(L_, -2) : std::string_view{ "<not a string>" } }); 273 DEBUGSPEW_CODE(std::string_view const _key{ (luaG_type(L_, -2) == LuaType::STRING) ? luaG_tostring(L_, -2) : std::string_view{ "<not a string>" } });
270 DEBUGSPEW_CODE(DebugSpew(_U) << "table '"<< _key <<"'" << std::endl); 274 DEBUGSPEW_CODE(DebugSpew(_U) << "table '"<< _key <<"'" << std::endl);
271 DEBUGSPEW_CODE(DebugSpewIndentScope _scope2{ _U }); 275 DEBUGSPEW_CODE(DebugSpewIndentScope _scope2{ _U });
@@ -285,7 +289,7 @@ static void populate_func_lookup_table_recur(lua_State* const L_, StackIndex con
285 // push table name in fqn stack (note that concatenation will crash if name is a not string!) 289 // push table name in fqn stack (note that concatenation will crash if name is a not string!)
286 lua_pushvalue(L_, -2); // L_: ... {i_} {bfc} k {} k 290 lua_pushvalue(L_, -2); // L_: ... {i_} {bfc} k {} k
287 lua_rawseti(L_, _fqn, _deeper); // L_: ... {i_} {bfc} k {} 291 lua_rawseti(L_, _fqn, _deeper); // L_: ... {i_} {bfc} k {}
288 populate_func_lookup_table_recur(L_, dbIdx_, StackIndex{ lua_gettop(L_) }, _deeper); 292 populate_lookup_table_recur(L_, dbIdx_, StackIndex{ lua_gettop(L_) }, _deeper);
289 lua_pop(L_, 1); // L_: ... {i_} {bfc} k 293 lua_pop(L_, 1); // L_: ... {i_} {bfc} k
290 STACK_CHECK(L_, 2); 294 STACK_CHECK(L_, 2);
291 } 295 }
@@ -316,7 +320,8 @@ namespace tools {
316 StackIndex const _dbIdx{ lua_gettop(L_) }; 320 StackIndex const _dbIdx{ lua_gettop(L_) };
317 STACK_CHECK(L_, 1); 321 STACK_CHECK(L_, 1);
318 LUA_ASSERT(L_, lua_istable(L_, -1)); 322 LUA_ASSERT(L_, lua_istable(L_, -1));
319 if (luaG_type(L_, _in_base) == LuaType::FUNCTION) { // for example when a module is a simple function 323 LuaType const _moduleType{ luaG_type(L_, _in_base) };
324 if ((_moduleType == LuaType::FUNCTION) || (_moduleType == LuaType::USERDATA)) { // for example when a module is a simple function
320 if (_name.empty()) { 325 if (_name.empty()) {
321 _name = "nullptr"; 326 _name = "nullptr";
322 } 327 }
@@ -343,7 +348,7 @@ namespace tools {
343 // retrieve the cache, create it if we haven't done it yet 348 // retrieve the cache, create it if we haven't done it yet
344 std::ignore = kLookupCacheRegKey.getSubTable(L_, NArr{ 0 }, NRec{ 0 }); // L_: {} {fqn} {cache} 349 std::ignore = kLookupCacheRegKey.getSubTable(L_, NArr{ 0 }, NRec{ 0 }); // L_: {} {fqn} {cache}
345 // process everything we find in that table, filling in lookup data for all functions and tables we see there 350 // process everything we find in that table, filling in lookup data for all functions and tables we see there
346 populate_func_lookup_table_recur(L_, _dbIdx, _in_base, _startDepth); 351 populate_lookup_table_recur(L_, _dbIdx, _in_base, _startDepth);
347 lua_pop(L_, 3); // L_: 352 lua_pop(L_, 3); // L_:
348 } else { 353 } else {
349 lua_pop(L_, 1); // L_: 354 lua_pop(L_, 1); // L_: