diff options
-rw-r--r-- | CHANGES | 1 | ||||
-rw-r--r-- | docs/index.html | 58 | ||||
-rw-r--r-- | src/intercopycontext.cpp | 98 | ||||
-rw-r--r-- | src/intercopycontext.hpp | 2 | ||||
-rw-r--r-- | src/tools.cpp | 29 |
5 files changed, 134 insertions, 54 deletions
@@ -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]] |
58 | static int func_lookup_sentinel(lua_State* L_) | 58 | static 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]] |
67 | static int table_lookup_sentinel(lua_State* L_) | 67 | static 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]] |
76 | static int userdata_clone_sentinel(lua_State* L_) | 76 | static 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]] | ||
85 | static 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]] |
85 | std::string_view InterCopyContext::findLookupName() const | 94 | std::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]] | ||
694 | bool 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]] |
682 | bool InterCopyContext::tryCopyClonable() const | 746 | bool 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 | ||
194 | static void populate_func_lookup_table_recur(lua_State* const L_, StackIndex const dbIdx_, StackIndex const i_, int const depth_) | 194 | static 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_: |